LangGraph 不仅仅是一个图框架,它是构建具有长期记忆、决策能力和复杂交互的智能代理(Intelligent Agents)的强大引擎。它通过将应用程序解构为可控的状态机,让你的 LLM 应用从简单的问答升级为能够自主思考、行动和适应的复杂系统。
借用官方给的说法,其中重要点已经做了单独标注,下面的内容地址在文章最后有做说明
LangGraph is a library for building
stateful, multi-actor
applications with LLMs. The main use cases for LangGraph areconversational agents
, andlong-running
,multi-step LLM applications
or any LLM application that would benefit from built-in support forpersistent checkpoints
,cycles
andhuman-in-the-loop interactions
(ie. LLM and human collaboration).
LG 构建的应用是:
- 有状态的
- 多角色的
LG 可以用来构建:- 对话智能体
- 长时间运行的、多阶段的 LLM 应用
LG 内置支持- 检查点恢复
- 循环逻辑(做过智能体的朋友应该清楚,在调用工具等获取结果不能做到 LLM 认为的满足用户需求的,会进行循环重新获取结果,直到满意之后或者达到设定循环阈值)
- 用户可反复交互
LangGraph shortens the time-to-market for developers using LangGraph, with a one-liner command to start a production-ready HTTP microservice for your LangGraph applications, with built-in persistence. This lets you focus on the logic of your LangGraph graph, and leave the scaling and API design to us. The API is inspired by the OpenAI assistants API, and is designed to fit in alongside your existing services.
LG 很有用,你只需要一行命令就可以运行一个 HTTP 微服务,在开发过程中你只需要关注你的 LG 图,对于scaling and API design
由 LG 实现,并且 API 设计来源于 OpenAI的 API 方式
StateGraph
,它允许你定义一个应用程序的状态流转和逻辑结构。想象它是一个有限状态机,但具有更强大的能力,例如循环、条件分支和持久化状态。定义: 连接图中节点的方向性链接,表示信息从一个节点流向另一个节点。
类型:
add_edge(start_node, end_node)
: 定义一个从 start_node
到 end_node
的直接连接。set_entry_point(node)
: 设置图的起始节点。set_finish_point(node)
: 设置图的结束节点。add_conditional_edges(start_node, condition_function, mapping)
: 这是 LangGraph 的核心特性。condition_function
根据 start_node
的输出返回一个键,然后根据 mapping
将控制流路由到不同的 end_node
。condition_function
的返回值必须是 mapping
中的一个键,或者是一个特殊的键,如 END
来结束图的执行。作用: 控制工作流程的执行顺序和数据传递路径。
StateGraph
的核心特性是其可变状态。这意味着你可以在图的执行过程中不断更新和修改共享的状态对象。LangGraph 的设计解决了传统 LangChain 链在处理复杂代理和循环逻辑时的局限性:
Checkpointer
机制保证了即使在长时间运行或中断的情况下,代理也能恢复其状态并继续执行。构建一个 LangGraph 应用程序遵循清晰的步骤,将复杂逻辑分解为可管理的部分。
让我们构建一个更贴近真实应用的例子:一个能够理解用户意图、检索相关信息、使用工具并生成智能回复的多代理聊天助手。它包含以下核心代理/功能:
1. 定义状态 (AgentState
)
这是整个图的核心,所有节点都通过它共享信息。
from typing import List, TypedDict, Optional
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage
class AgentState(TypedDict):
"""
一个表示代理状态的 TypedDict。
- messages: 历史消息列表,用于维护对话上下文。
- tool_calls: LLM 决定要调用的工具列表,结构通常由 LLM 提示决定。
- tool_outputs: 工具调用后的输出结果。
- retrieved_docs: 检索到的相关文档内容。
- analysis_result: 分析师对检索内容的提炼和总结。
- error_message: 错误信息,用于错误处理。
"""
messages: List[BaseMessage]
tool_calls: Optional[List[dict]]
tool_outputs: Optional[List[str]]
retrieved_docs: Optional[List[str]]
analysis_result: Optional[str]
error_message: Optional[str]
2. 创建节点函数
每个节点都是一个接收
AgentState
并返回Dict
以更新状态的 Python 函数。
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
# 初始化 LLM 模型 (实际应用中可能需要更复杂的配置)
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# 假设有一些工具
def search_web_tool(query: str) -> str:
# 模拟网页搜索工具
print(f"Executing web search for: {query}")
return f"Search result for '{query}': AI agents are a hot topic."
def retrieve_document_tool(query: str) -> str:
# 模拟文档检索工具
print(f"Executing document retrieval for: {query}")
return f"Document content for '{query}': LangGraph is a framework for building stateful, multi-actor applications with LLMs."
# --- LLM 代理节点 ---
def router_agent(state: AgentState) -> dict:
"""
根据用户输入决定路由到哪个子代理或工具。
这里简化为基于关键词判断。
"""
print("\n--- Router Agent ---")
last_message = state["messages"][-1].content.lower()
if "search" in last_message or "最新消息" in last_message:
# LLM 决定调用搜索工具,并在状态中留下工具调用意图
tool_call_intent = [{"tool_name": "search_web", "query": last_message}]
return {"tool_calls": tool_call_intent, "messages": state["messages"] + [AIMessage(content="Routing to search...")]}
elif "document" in last_message or "文档" in last_message:
tool_call_intent = [{"tool_name": "retrieve_document", "query": last_message}]
return {"tool_calls": tool_call_intent, "messages": state["messages"] + [AIMessage(content="Routing to document retrieval...")]}
else:
# 否则直接路由到回复生成
return {"messages": state["messages"] + [AIMessage(content="Routing to response generation...")]}
def researcher_agent(state: AgentState) -> dict:
"""
根据 router 代理的工具调用意图,执行实际的工具并更新状态。
"""
print("\n--- Researcher Agent ---")
tool_calls = state.get("tool_calls")
tool_outputs = []
retrieved_docs = []
if tool_calls:
for tool_call in tool_calls:
tool_name = tool_call.get("tool_name")
query = tool_call.get("query")
if tool_name == "search_web":
output = search_web_tool(query)
tool_outputs.append(output)
elif tool_name == "retrieve_document":
output = retrieve_document_tool(query)
tool_outputs.append(output)
retrieved_docs.append(output) # 将工具输出也作为检索到的文档内容
state["messages"].append(ToolMessage(content=output, tool_call_id=f"{tool_name}-{query}"))
return {"tool_outputs": tool_outputs, "retrieved_docs": retrieved_docs, "messages": state["messages"]}
def analyst_agent(state: AgentState) -> dict:
"""
分析研究员获取的信息,并提炼出关键点。
"""
print("\n--- Analyst Agent ---")
retrieved_docs = state.get("retrieved_docs", [])
if not retrieved_docs:
return {"analysis_result": "No relevant information found for analysis."}
# 模拟 LLM 分析过程
analysis_prompt = f"请分析以下信息并提炼关键点,用于生成用户回复:\n{' '.join(retrieved_docs)}"
response = llm.invoke([HumanMessage(content=analysis_prompt)])
analysis_content = response.content
state["messages"].append(AIMessage(content=f"Analysis: {analysis_content}"))
return {"analysis_result": analysis_content, "messages": state["messages"]}
def response_generator_agent(state: AgentState) -> dict:
"""
结合所有信息,生成最终的用户回复。
"""
print("\n--- Response Generator Agent ---")
messages = state["messages"]
analysis_result = state.get("analysis_result", "")
# 根据上下文和分析结果生成最终回复
final_response_prompt = f"根据以下对话和分析结果,生成一个简洁友好的回复。如果分析结果存在,请整合进去:\n对话历史: {messages}\n分析结果: {analysis_result}"
response = llm.invoke([HumanMessage(content=final_response_prompt)])
final_answer = response.content
state["messages"].append(AIMessage(content=final_answer))
return {"messages": state["messages"]}
def error_handler(state: AgentState) -> dict:
"""
处理流程中的错误或异常情况。
"""
print("\n--- Error Handler ---")
error_msg = state.get("error_message", "Unknown error occurred.")
print(f"Error caught: {error_msg}. Attempting to re-route or terminate.")
# 这里可以添加更复杂的错误处理逻辑,例如重试、通知管理员等
# 对于简单示例,我们尝试将消息重新路由回 Router 或直接结束
return {"messages": state["messages"] + [AIMessage(content=f"An error occurred: {error_msg}. Please try again or rephrase your request.")]}
3. 定义条件函数 (路由逻辑)
def route_decision(state: AgentState) -> str:
"""
根据 Router Agent 的输出决定下一步。
"""
tool_calls = state.get("tool_calls")
if tool_calls:
# 如果有工具调用意图,则路由到研究员代理
return "call_researcher"
# 否则,假设是直接回复意图,路由到回复生成器
# (实际中,Router Agent 应该明确返回下一步是 'direct_reply' 或 'error')
last_message_content = state["messages"][-1].content.lower()
if "routing to response generation" in last_message_content: # 检查 router 代理发出的信号
return "generate_response"
elif "error occurred" in last_message_content: # 检查错误处理器的信号
return "handle_error"
else: # 默认情况或复杂决策
return "generate_response"
def research_decision(state: AgentState) -> str:
"""
研究员代理完成后,决定是否进入分析阶段。
"""
tool_outputs = state.get("tool_outputs")
if tool_outputs and any(tool_outputs): # 如果有工具输出
return "analyze_research"
return "handle_error" # 如果没有有效输出,则进入错误处理
def analysis_decision(state: AgentState) -> str:
"""
分析师代理完成后,决定是否可以生成回复。
"""
analysis_result = state.get("analysis_result")
if analysis_result:
return "generate_final_response"
return "handle_error" # 如果分析失败,则进入错误处理
4. 构建状态图 (StateGraph
)
from langgraph.graph import StateGraph, END
# 实例化图
workflow = StateGraph(AgentState)
# 添加节点
workflow.add_node("router_agent", router_agent)
workflow.add_node("researcher_agent", researcher_agent)
workflow.add_node("analyst_agent", analyst_agent)
workflow.add_node("response_generator", response_generator_agent)
workflow.add_node("error_handler", error_handler)
# 设置入口点
workflow.set_entry_point("router_agent")
# --- 添加条件边 ---
# 路由器代理的决策
workflow.add_conditional_edges(
"router_agent",
route_decision, # 基于 router_agent 的输出进行决策
{
"call_researcher": "researcher_agent",
"generate_response": "response_generator",
"handle_error": "error_handler",
}
)
# 研究员代理完成后的决策
workflow.add_conditional_edges(
"researcher_agent",
research_decision,
{
"analyze_research": "analyst_agent",
"handle_error": "error_handler",
}
)
# 分析师代理完成后的决策
workflow.add_conditional_edges(
"analyst_agent",
analysis_decision,
{
"generate_final_response": "response_generator",
"handle_error": "error_handler",
}
)
# 错误处理器后的决策 (可以尝试重新路由,也可以直接结束)
def error_re_route_decision(state: AgentState) -> str:
# 简单的错误处理:如果不是致命错误,尝试回到路由器重新处理
# 实际中可能需要更复杂的逻辑来判断是否重试
return END # 为简化流程,这里直接结束
workflow.add_conditional_edges(
"error_handler",
error_re_route_decision,
{
"re_route": "router_agent", # 理论上可以重路由,但此处简化为 END
END: END
}
)
# --- 设置终点 ---
# 如果流程直接到 response_generator,它就是终点
workflow.set_finish_point("response_generator")
workflow.set_finish_point("error_handler") # 错误处理器也可以作为终点
5. 编译并运行
# 编译图
app = workflow.compile()
# 运行示例
print("--- 场景一:需要搜索 ---")
initial_state_1 = {"messages": [HumanMessage(content="最新的AI agent发展趋势是什么?请帮我搜索。")]}
for s in app.stream(initial_state_1):
print(s)
print("\n--- 场景二:直接回复 ---")
initial_state_2 = {"messages": [HumanMessage(content="你好,很高兴和你交流。")]}
for s in app.stream(initial_state_2):
print(s)
print("\n--- 场景三:需要文档检索 (假设关键词触发) ---")
initial_state_3 = {"messages": [HumanMessage(content="LangGraph的文档在哪里可以找到?")]}
for s in app.stream(initial_state_3):
print(s)
# 如果想可视化,可以保存为 Mermaid 文件或图片
# from IPython.display import Image, display
# display(Image(app.get_graph().draw_png()))
Mutable State
): LangGraph 的核心。理解状态的结构和合并策略至关重要。对于复杂的数据结构(如嵌套字典或自定义对象),你可能需要定义自定义的合并函数。Streaming
):
app.stream()
可以实现 token-by-token 的流式输出,提升用户体验。StateGraph
定义自定义的 reducer
。app.get_graph().draw_png()
或集成 LangSmith 来可视化你的图,这对于理解和调试复杂流程非常有帮助。LangGraph 官方文档:
永远是第一手资料,包含最新的 API 和最佳实践。
LangChain Academy (LangGraph 部分):
官方教程,循序渐进,适合初学者。
LangChain Blog:
关注官方博客,获取最新功能更新和真实用例分析。
LangSmith:
用于可视化、调试和监控 LangGraph 应用程序。强烈推荐使用!
GitHub 示例:
学习和借鉴的最佳实践来源(值得学习一下)