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 方式
1. LangGraph 核心概念:深入理解
1.1 状态图 (StateGraph) - 应用程序的蓝图
- 本质: LangGraph 的核心是
StateGraph
,它允许你定义一个应用程序的状态流转和逻辑结构。想象它是一个有限状态机,但具有更强大的能力,例如循环、条件分支和持久化状态。 - 状态的唯一性: 在 LangGraph 中,状态是==单一且可变的==。这意味着所有节点都操作同一个共享状态对象。当一个节点返回状态更新时,LangGraph 会智能地将这些更新合并到主状态中。
- 为什么重要: 它强制你以结构化、可预测的方式思考代理的行为,这对于调试和维护复杂系统至关重要。
1.2 节点 (Nodes) - 代理的原子操作
- 功能: 每个节点都是图中的一个独立执行单元。它可以是一个 LLM 调用、一个工具函数、一个数据处理函数,甚至是一个子图。
- 输入/输出: 节点通常接收当前完整状态 作为输入,并返回一个字典来更新状态。这种模式确保了节点之间的解耦和清晰的数据流。
- 隔离性: 节点是高度模块化的。你可以独立测试和开发每个节点,然后将它们组合成复杂的图。
1.3 边 (Edges) - 控制流与数据流的桥梁
定义: 连接图中节点的方向性链接,表示信息从一个节点流向另一个节点。
类型:
- 普通边 (Start/End Edges):
add_edge(start_node, end_node)
: 定义一个从start_node
到end_node
的直接连接。set_entry_point(node)
: 设置图的起始节点。set_finish_point(node)
: 设置图的结束节点。
- 条件边 (Conditional Edges):
add_conditional_edges(start_node, condition_function, mapping)
: 这是 LangGraph 的核心特性。condition_function
根据start_node
的输出返回一个键,然后根据mapping
将控制流路由到不同的end_node
。condition_function
的返回值必须是mapping
中的一个键,或者是一个特殊的键,如END
来结束图的执行。
作用: 控制工作流程的执行顺序和数据传递路径。
1.4 状态管理 (State Management) 与持久化 - 代理的记忆与生命
- 可变状态:
StateGraph
的核心特性是其可变状态。这意味着你可以在图的执行过程中不断更新和修改共享的状态对象。 - 状态的合并: 当节点返回状态更新时,LangGraph 使用智能合并策略(对列表进行追加,对字典进行更新)来确保状态的正确累积。你可以自定义合并策略。
- Checkpointer (检查点):这是实现长期记忆和断点恢复的关键。 Checkpointer允许你在图的执行过程中将当前状态持久化到数据库(如 SQLite、PostgreSQL、Redis、MongoDB 等)。
- 用途:
- 断点续传: 用户会话中断后可以从上次的对话点继续。
- 调试: 回溯到特定状态进行分析。
- 记忆: 代理可以在不同的调用之间记住信息。
- 并发: 多个用户可以同时与同一个代理交互,各自的状态独立存储。
- 用途:
2. LangGraph 的优势:超越简单链
LangGraph 的设计解决了传统 LangChain 链在处理复杂代理和循环逻辑时的局限性:
- 原生支持循环 (Cycles): 这是 LangGraph 的杀手级特性。它使得构建 ReAct (Reasoning and Acting) 模式的代理变得简单而优雅,代理可以不断地"思考-行动-观察",直到任务完成。
- 清晰的代理决策流: 通过将决策逻辑(条件边)和执行逻辑(节点)分离,应用程序的结构变得高度透明和可控。
- 可观测性与调试: 由于其图结构,你可以清晰地看到每个步骤的执行顺序、状态变化和决策路径。与 LangSmith 的集成提供了无与伦比的跟踪和可视化能力。
- 健壮性与恢复:
Checkpointer
机制保证了即使在长时间运行或中断的情况下,代理也能恢复其状态并继续执行。 - 灵活性与扩展性: 节点可以是任何 Python 函数,这意味着你可以集成任何自定义逻辑、工具或外部系统。
3. 构建 LangGraph 应用程序:从概念到实践
构建一个 LangGraph 应用程序遵循清晰的步骤,将复杂逻辑分解为可管理的部分。
步骤概览:
- 定义应用程序状态 (Define Application State): 确定所有需要在代理之间共享和持久化的信息。
- 创建功能节点 (Create Functional Nodes): 实现每个独立任务的 Python 函数。
- 构建状态图 (Build the StateGraph): 定义节点的连接和条件路由。
- 编译并运行 (Compile and Run): 实例化并执行你的 LangGraph 应用程序。
一个高级的"检索增强型多代理聊天助手"工作流
让我们构建一个更贴近真实应用的例子:一个能够理解用户意图、检索相关信息、使用工具并生成智能回复的多代理聊天助手。它包含以下核心代理/功能:
- 路由器 (Router Agent): 根据用户输入决定是进行工具调用、知识检索还是直接回复。
- 研究员 (Researcher Agent): 负责利用检索工具(如网页搜索、文档查询)获取信息。
- 分析师 (Analyst Agent): 对研究员获取的信息进行分析和提炼,准备用于回答。
- 回复生成器 (Response Generator Agent): 结合所有信息,生成最终的用户回复。
- 错误处理器 (Error Handler): 处理异常情况。
▛ 系统内存状态 ▜ 🌐 需要网络搜索 📚 需要文档检索 💡 直接回复 ❓ 路由失败 ✅ 研究完成 🔄 需要重试 ✅ 分析就绪 ❓ 需要补充数据 📝 生成回复 🔄 重新路由 ⏹️ 终止流程 📨 消息历史 📋 工具调用计划 📊 工具输出结果 🗂️ 检索文档集 📈 分析报告 [开始] (用户输入) 🔄 路由器代理 🔍 搜索工具 📂 文档检索工具 🔬 研究员代理 📊 分析师代理 💬 回复生成器代理 (⚠️ 错误处理) [✨ 生成最终回复]
代码实现骨架
1. 定义状态 (AgentState
)
这是整个图的核心,所有节点都通过它共享信息。
python
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 函数。
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. 定义条件函数 (路由逻辑)
python
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
)
python
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. 编译并运行
python
# 编译图
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()))
4. 真实应用场景下的 LangGraph
- RAG (Retrieval Augmented Generation) 深度优化:
- 问题: 传统的 RAG 链式调用缺乏灵活性,无法处理复杂的用户意图(如需要多轮检索、检索结果需要二次提炼、甚至在检索失败时进行反思)。
- LangGraph 解决方案:
- 动态检索: Router 节点根据用户查询动态选择不同的检索器(向量数据库、知识图谱、网页搜索)。
- 多轮检索与重排序: 第一次检索结果不满意时,代理可以自我修正,调整查询或使用不同的检索策略,进行第二轮检索。
- 结果分析与整合: 引入专门的"分析代理"对检索到的多个文档进行摘要、对比、去重、冲突解决,然后传递给生成代理。
- RAG 反思: 在生成回复前,代理可以"反思"检索到的信息是否足够、是否相关,如果不足,则重新进入检索循环。
- 企业级客服自动化 (AI Agent + Human-in-the-Loop):
- 问题: 纯 AI 客服无法处理复杂问题,但人工客服成本高。
- LangGraph 解决方案:
- 意图识别与路由: 根据用户问题路由到不同的专家代理(例如,技术支持代理、销售代理、退货代理)。
- 知识库检索与工具使用: 专家代理利用 LangGraph 内置的工具调用能力,查询内部知识库、CRM 系统、订单系统。
- 升级到人工: 当代理无法解决问题或检测到用户情绪激动时,通过条件边将对话流转到"人工客服接管"节点,发送通知并传递完整的对话历史和上下文。
- 人工辅助与学习: 人工客服介入后,系统记录人工客服的操作和回复,用于训练优化 AI 代理。
- 项目管理与任务自动化:
- 问题: 自动化工作流通常是线性的,缺乏应对动态变化和决策的能力。
- LangGraph 解决方案:
- 任务分解代理: 接收高层任务,将其分解为子任务,并分配给不同的执行代理。
- 执行代理: 每个执行代理负责完成一个特定子任务,例如代码生成、文档编写、数据分析。
- 进度检查与反馈循环: 定期检查子任务进度,如果遇到障碍,反馈给任务分解代理进行重新规划或寻求人工干预。
- 成果整合: 将所有子任务的成果整合,生成最终报告或输出。
5. 进阶
- 可变状态 (
Mutable State
): LangGraph 的核心。理解状态的结构和合并策略至关重要。对于复杂的数据结构(如嵌套字典或自定义对象),你可能需要定义自定义的合并函数。 - Checkpointer 配置:
- 选择合适的后端:SQLite 适合本地开发和小型应用,Redis、PostgreSQL 适合生产环境。
- 理解如何加载和保存特定线程的状态。
- 流式传输 (
Streaming
):app.stream()
可以实现 token-by-token 的流式输出,提升用户体验。- 你也可以流式传输中间步骤,用于调试或构建更高级的 UI。
- 自定义合并函数: 当默认的字典更新或列表追加无法满足需求时,可以为
StateGraph
定义自定义的reducer
。 - 错误处理策略:
- 在每个节点内部进行健壮的错误处理(try-except)。
- 使用专门的"错误处理节点"来捕获和管理图级别的错误,进行日志记录、通知或回滚。
- 测试: 由于 LangGraph 应用程序是可组合的,你可以对每个节点进行单元测试,然后对整个图进行集成测试。
- 可视化: 强烈建议使用
app.get_graph().draw_png()
或集成 LangSmith 来可视化你的图,这对于理解和调试复杂流程非常有帮助。 - 性能优化:
- 缓存 LLM 调用: 使用 LangChain 的缓存机制。
- 并行化: 识别图中可以并行执行的节点,LangGraph 支持并行执行。
- 异步操作: 对于 I/O 密集型操作(如工具调用),使用异步函数。
6. 一些官方资料
-
LangGraph 官方文档:
永远是第一手资料,包含最新的 API 和最佳实践。
-
LangChain Academy (LangGraph 部分):
官方教程,循序渐进,适合初学者。
-
LangChain Blog:
关注官方博客,获取最新功能更新和真实用例分析。
-
LangSmith:
用于可视化、调试和监控 LangGraph 应用程序。强烈推荐使用!
-
GitHub 示例:
学习和借鉴的最佳实践来源(值得学习一下)