LangGraph实战:构建可编排的多智能体工作流系统
2026/8/29 11:37:08 网站建设 项目流程

如果你正在尝试构建一个能自主协作、完成复杂任务的AI智能体系统,大概率会遇到这样的困境:单个Agent能力有限,而让多个Agent协同工作,代码很快就会变成难以维护的“意大利面条”——状态流转混乱、消息传递复杂、错误处理困难。

这正是LangGraph要解决的核心问题。它不是一个全新的Agent框架,而是LangChain生态中一个专门用于构建有状态、多智能体工作流的库。很多人误以为它只是LangChain的一个“高级版”,但实际上,它的设计理念更接近一个为Agent协作而生的“微服务编排引擎”。它把复杂的多智能体交互,抽象成清晰、可调试的“图”(Graph),让你能用声明式的方式定义谁在什么时候、做什么、以及接下来该谁做。

本文将带你彻底搞懂LangGraph,并手把手实现一个从零到一的多智能体协作系统。你将学到的不只是几个API调用,而是如何用“图”的思维来设计和实现可扩展的智能体架构。读完本文,你将能:

  1. 理解核心:清晰掌握LangGraph中State、Node、Edge、Condition等核心概念及其设计哲学。
  2. 搭建环境:快速配置开发环境,并理解不同大模型后端(OpenAI、Ollama等)的接入方式。
  3. 实战编码:完成一个完整的“旅行规划多智能体”项目,涵盖从需求分析到代码落地的全流程。
  4. 掌握进阶:学会如何为智能体添加“记忆”、实现循环判断、以及进行错误处理和调试。
  5. 避开深坑:识别开发中的常见陷阱,并获得可直接用于生产的最佳实践建议。

我们直接从最关键的架构思想开始。

1. 为什么你需要LangGraph?从“脚本”到“编排”的思维跃迁

在LangGraph出现之前,构建多智能体系统通常有两种方式:

  1. 线性脚本式:在代码里硬编码调用顺序,例如先调用“研究Agent”,拿到结果后再调用“写作Agent”。这种方式简单,但毫无灵活性,增加一个审核Agent就需要重写流程。
  2. 消息队列式:让每个Agent监听一个消息队列,进行异步通信。这解决了耦合问题,但带来了新的复杂度:消息格式定义、序列化、错误重试、状态全局管理等,需要大量基础设施工作。

LangGraph提供了一种折中且优雅的方案:用“图”来显式定义工作流。你可以把每个智能体(或任何函数)看作图中的一个“节点”(Node),节点之间的连线“边”(Edge)定义了执行路径。一个特殊的“状态”(State)对象在整个图中流转,携带了所有上下文信息。

这种模式带来了几个立竿见影的好处:

  • 可视化与可调试性:工作流不再是隐藏在代码逻辑里,而是可以直观地画出来。LangGraph Studio甚至能让你实时调试图的执行。
  • 内置状态管理:你不用自己设计一个全局变量或数据库来传递数据,State对象帮你安全地管理。
  • 灵活的流程控制:支持条件分支(if-else)、循环(while)、并行等复杂逻辑,轻松实现“如果分析结果不完整,则重新分析”这类需求。
  • 与LangChain无缝集成:可以直接使用LangChain丰富的组件(Tools, Prompts, LLMs),生态优势明显。

接下来,我们深入其核心概念。

2. LangGraph核心四要素:State, Node, Edge, Conditional Edge

理解这四个概念,就掌握了LangGraph的命门。

2.1 State:工作流的共享内存

State是一个字典(或Pydantic模型),它是在整个图执行过程中唯一传递的对象。所有节点都读取和修改它。设计一个好的State结构是成功的第一步。

关键点:State应该是扁平的、描述当前任务进度的数据结构。例如,一个写作Agent的State可能包含{"topic": str, "research_materials": list, "outline": str, "final_draft": str}

2.2 Node:执行单元

Node就是一个普通的Python函数(或可调用对象),它接收当前的State,执行一些操作(如调用LLM、运行工具、处理数据),然后返回一个包含对State修改的字典。

def research_agent(state: dict): """研究节点:根据主题搜集信息""" topic = state["topic"] # 调用LLM或搜索工具进行研究 research_result = call_llm(f"请搜集关于{topic}的资料") # 返回要更新到State中的内容 return {"research_materials": research_result}

2.3 Edge:执行路径

Edge定义了节点执行完毕后,下一步应该去哪个节点。最简单的边是“起始边”和“普通边”,它们直接连接两个节点。

2.4 Conditional Edge:让图“活”起来

这是LangGraph最强大的特性之一。条件边根据当前State的内容,动态决定下一个节点。这实现了if-else和循环逻辑。

def should_continue(state: dict) -> str: """根据大纲质量决定下一步:继续润色还是结束""" outline_quality = state.get("outline_quality", "poor") if outline_quality == "good": return "end" # 前往结束节点 else: return "rewrite" # 前往重写节点

把这四个要素组合起来,你就得到了一个能处理复杂逻辑的工作流“图”。下面,我们进入实战环节。

3. 环境准备:模型、依赖与工具选择

在开始写代码前,需要准备好基础环境。本文以OpenAI GPT-4o模型为例,同时也会说明如何切换为本地模型(如通过Ollama)。

3.1 创建虚拟环境与安装依赖

强烈建议使用虚拟环境来管理依赖。

# 1. 创建并激活虚拟环境 (以conda为例) conda create -n langgraph-demo python=3.10 conda activate langgraph-demo # 2. 安装核心库 pip install langgraph langchain langchain-openai # 3. 安装可选工具库(用于示例中的搜索、计算等) pip install langchain-community duckduckgo-search tavily-python
  • langgraph: 核心库。
  • langchain: 提供链、提示词模板等基础组件。
  • langchain-openai: OpenAI模型官方集成。
  • langchain-community,duckduckgo-search,tavily-python: 用于给Agent提供搜索、计算等能力的工具。

3.2 配置API密钥

你需要准备OpenAI的API密钥。将其设置为环境变量是最安全的方式。

# Linux/Mac export OPENAI_API_KEY='your-api-key-here' # Windows (PowerShell) $env:OPENAI_API_KEY='your-api-key-here'

在代码中,可以通过os.getenv读取。

如果你想使用本地模型(如Ollama)

  1. 安装Ollama并拉取模型(如llama3.1):ollama pull llama3.1
  2. 安装对应的LangChain集成:pip install langchain-ollama
  3. 在代码中将ChatOpenAI替换为ChatOllama,并指定基础URL和模型名。
from langchain_ollama import ChatOllama llm = ChatOllama(model="llama3.1", base_url="http://localhost:11434")

注意:本地模型在复杂逻辑推理和指令遵循上可能弱于GPT-4,建议在概念验证阶段使用GPT-4,部署时根据成本和要求选择模型。

4. 实战:构建一个旅行规划多智能体系统

我们将构建一个包含三个智能体的系统:

  1. 目的地研究Agent:根据用户模糊需求(如“我想去一个温暖的海边放松”),推荐具体目的地并列出理由。
  2. 行程规划Agent:针对选定的目的地,生成一份详细的每日行程安排。
  3. 预算评估Agent:根据行程,估算大致花费,并给出省钱建议。

4.1 第一步:定义State

State是我们工作流的蓝图。我们使用Pydantic BaseModel来获得类型提示和验证。

from typing import TypedDict, List, Optional, Annotated from langgraph.graph.message import add_messages import operator # 使用TypedDict定义State(LangGraph推荐方式) class TravelPlanState(TypedDict): """旅行规划工作流的状态""" # 用户原始输入 user_request: str # 研究Agent的输出:推荐的目的地列表 recommended_destinations: List[str] # 用户选择或系统首选的目的地 selected_destination: Optional[str] # 行程规划Agent输出的详细行程 detailed_itinerary: Optional[str] # 预算评估Agent输出的预算分析 budget_analysis: Optional[str] # 用于在节点间传递消息的历史(可选,用于更复杂的对话) messages: Annotated[list, add_messages]

这里我们使用了TypedDictAnnotatedadd_messages是一个特殊的缩减器(reducer),它能自动将新的消息追加到messages列表中,这对于构建对话式Agent非常有用。在本例中,我们主要用前几个字段。

4.2 第二步:创建各个智能体节点

每个节点都是一个函数,接收State,返回State的更新。

from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate import os # 初始化LLM llm = ChatOpenAI(model="gpt-4o", api_key=os.getenv("OPENAI_API_KEY")) def research_destination_node(state: TravelPlanState) -> dict: """节点1:研究目的地""" user_request = state["user_request"] # 构建提示词 prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个专业的旅行顾问,擅长根据用户的模糊描述推荐具体目的地。"), ("human", "用户的需求是:{request}。请推荐2-3个最符合要求的具体旅行目的地(城市或地区),并为每个目的地用一句话说明推荐理由。") ]) # 创建链并调用 chain = prompt | llm response = chain.invoke({"request": user_request}) # 解析响应,这里简单返回内容,实际可做结构化解析 recommendations = response.content # 更新State return { "recommended_destinations": [rec.strip() for rec in recommendations.split('\n') if rec.strip()], "selected_destination": recommendations.split('\n')[0].split('。')[0] if recommendations else None # 简单取第一个作为默认选择 } def plan_itinerary_node(state: TravelPlanState) -> dict: """节点2:规划行程""" destination = state["selected_destination"] if not destination: return {"detailed_itinerary": "错误:未选择目的地"} prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个资深的行程规划师,能为任何目的地制定详尽的3天2晚行程。"), ("human", "为目的地 {dest} 规划一份详细的3天2晚行程。包括每天的上午、下午、晚上的活动安排,餐饮建议,以及交通提示。格式清晰。") ]) chain = prompt | llm response = chain.invoke({"dest": destination}) return {"detailed_itinerary": response.content} def assess_budget_node(state: TravelPlanState) -> dict: """节点3:评估预算""" destination = state["selected_destination"] itinerary = state["detailed_itinerary"] prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个精明的财务分析师,擅长估算旅行开销。"), ("human", """ 目的地:{dest} 参考行程: {itinerary} 请根据以上行程,估算一位中等消费水平旅行者的总花费(按人民币计算)。请按以下类别拆分: 1. 机票/交通(大交通) 2. 住宿 3. 餐饮 4. 门票与活动 5. 市内交通与其他 最后给出一个总预算范围,并提供2-3条节省预算的实用建议。 """) ]) chain = prompt | llm response = chain.invoke({"dest": destination, "itinerary": itinerary}) return {"budget_analysis": response.content}

4.3 第三步:构建图并定义流程

这是将节点和边组装起来的关键步骤。

from langgraph.graph import StateGraph, END # 1. 创建一个图,并指定State的类型 workflow = StateGraph(TravelPlanState) # 2. 将节点添加到图中 workflow.add_node("research", research_destination_node) workflow.add_node("plan", plan_itinerary_node) workflow.add_node("assess_budget", assess_budget_node) # 3. 设置入口点 workflow.set_entry_point("research") # 4. 添加普通边(线性执行) workflow.add_edge("research", "plan") workflow.add_edge("plan", "assess_budget") workflow.add_edge("assess_budget", END) # END是LangGraph内置的结束节点 # 5. 编译图 app = workflow.compile()

现在,一个简单的线性工作流就定义好了:研究 -> 规划 -> 评估预算 -> 结束。

4.4 第四步:运行与测试

让我们运行这个工作流,看看效果。

# 定义初始状态 initial_state: TravelPlanState = { "user_request": "我想在12月去一个温暖、有美食、适合放松的海边目的地,预算中等。", "recommended_destinations": [], "selected_destination": None, "detailed_itinerary": None, "budget_analysis": None, "messages": [] } # 运行图 final_state = app.invoke(initial_state) # 打印结果 print("="*50) print("用户需求:", final_state["user_request"]) print("\n--- 推荐目的地 ---") for i, dest in enumerate(final_state["recommended_destinations"], 1): print(f"{i}. {dest}") print("\n--- 选定目的地 ---") print(final_state["selected_destination"]) print("\n--- 详细行程 ---") print(final_state["detailed_itinerary"]) print("\n--- 预算评估 ---") print(final_state["budget_analysis"]) print("="*50)

执行上述代码,你将得到一份完整的旅行规划报告。但这只是一个开始。真正的威力在于引入条件逻辑

5. 进阶:引入条件边与循环,打造智能工作流

假设我们想让系统更智能:如果研究Agent推荐的目的地都不太理想(例如,LLM自己判断推荐信心不足),则让一个“人工审核”节点介入,或者让用户选择。

我们需要修改State和流程。

5.1 扩展State并修改研究节点

class EnhancedTravelState(TypedDict): user_request: str recommended_destinations: List[str] # 新增:研究质量评分 research_confidence: float selected_destination: Optional[str] detailed_itinerary: Optional[str] budget_analysis: Optional[str] # 新增:是否需要人工介入 needs_human_review: bool messages: Annotated[list, add_messages] def research_destination_node_v2(state: EnhancedTravelState) -> dict: """增强版研究节点:同时输出信心评分""" user_request = state["user_request"] prompt = ChatPromptTemplate.from_messages([ ("system", """你是一个专业的旅行顾问。请根据用户需求推荐2-3个目的地,并为你这次推荐的总体信心打分(0.0-1.0)。 信心基于需求的明确性和目的地的匹配度。输出格式为: 目的地1: 理由1 目的地2: 理由2 信心: 0.85"""), ("human", "用户需求:{request}") ]) chain = prompt | llm response = chain.invoke({"request": user_request}) content = response.content # 简单解析(实际应用应使用更稳健的解析,如OutputParser) lines = [l.strip() for l in content.split('\n') if l.strip()] dests = [] confidence = 0.7 # 默认值 for line in lines: if line.startswith("信心:"): try: confidence = float(line.split(':')[1].strip()) except: pass elif ':' in line and not line.startswith("信心"): dests.append(line) return { "recommended_destinations": dests, "research_confidence": confidence, "selected_destination": dests[0].split(':')[0] if dests else None, "needs_human_review": confidence < 0.6 # 信心低于0.6则需人工审核 }

5.2 创建人工审核节点和条件路由函数

def human_review_node(state: EnhancedTravelState) -> dict: """模拟人工审核节点(实际中可能是发送邮件、写入工单系统)""" print(f"\n[模拟人工审核] 系统对推荐目的地信心不足({state['research_confidence']:.2f})。") print(f"推荐结果: {state['recommended_destinations']}") # 模拟人工输入,这里我们硬编码一个选择 # 实际场景中,这里可以连接到一个UI界面或等待API回调 manual_choice = "三亚" print(f"[模拟人工审核] 人工干预,选择目的地: {manual_choice}") return {"selected_destination": manual_choice, "needs_human_review": False} def route_after_research(state: EnhancedTravelState) -> str: """条件路由函数:决定研究后是去人工审核还是继续规划""" if state.get("needs_human_review", False): return "human_review" else: return "plan"

5.3 重新构建带条件分支的图

from langgraph.graph import StateGraph, END workflow_v2 = StateGraph(EnhancedTravelState) # 添加节点 workflow_v2.add_node("research", research_destination_node_v2) workflow_v2.add_node("human_review", human_review_node) workflow_v2.add_node("plan", plan_itinerary_node) # 复用之前的节点 workflow_v2.add_node("assess_budget", assess_budget_node) # 复用之前的节点 # 设置入口 workflow_v2.set_entry_point("research") # 添加条件边:研究完成后,根据条件路由 workflow_v2.add_conditional_edges( "research", route_after_research, # 这个函数返回下一个节点的名字 { "human_review": "human_review", "plan": "plan" } ) # 添加普通边 workflow_v2.add_edge("human_review", "plan") # 人工审核后继续规划 workflow_v2.add_edge("plan", "assess_budget") workflow_v2.add_edge("assess_budget", END) app_v2 = workflow_v2.compile()

现在,当你运行app_v2.invoke(initial_state)时,如果LLM对推荐信心不足(比如用户需求非常模糊:“我想出去走走”),工作流会自动跳转到“人工审核”节点,模拟人工干预后再继续后续流程。

6. 运行、调试与可视化

6.1 运行与检查状态

LangGraph的app.invoke()返回最终状态。你还可以使用app.stream()来流式执行,观察每个节点执行前后的状态变化,这对调试至关重要。

# 流式执行,观察每一步 inputs = EnhancedTravelState(user_request="我想去个有意思的地方", ...) # 初始化其他字段 for step in app_v2.stream(inputs): node_name, node_output = next(iter(step.items())) print(f"--- 节点 [{node_name}] 执行完成 ---") print(f"输出: {node_output}\n")

6.2 使用LangGraph Studio进行可视化(强烈推荐)

LangGraph Studio是一个Web界面,可以可视化你的图结构,并逐步调试执行过程。

  1. 安装:pip install langgraph-cli
  2. 在项目目录下启动:langgraph studio
  3. 浏览器打开http://localhost:8501
  4. 将你的图编译代码(app = workflow.compile())保存到一个Python文件(如travel_agent.py),然后在Studio中打开它。

你可以看到节点和边的可视化图,点击节点可以查看输入/输出,极大地简化了复杂工作流的理解和调试。

7. 常见问题与排查思路

在开发LangGraph应用时,你可能会遇到以下典型问题:

问题现象可能原因排查方式解决方案
KeyError当访问State字段1. State的TypedDict定义与节点返回的更新字典键不匹配。
2. 前驱节点未返回该字段。
1. 检查State类型定义和所有节点的返回值。
2. 使用app.stream()观察每个节点后的State。
确保所有节点返回的字典键都在State类型中有定义,或使用Optional类型。
图编译失败,提示节点未定义add_edgeadd_conditional_edges中引用了未添加的节点名。仔细检查add_nodeadd_edge调用中的节点名字符串是否完全一致。使用常量或枚举来定义节点名,避免拼写错误。
条件边路由函数返回的值不在映射中add_conditional_edges的映射字典未包含路由函数所有可能的返回值。检查路由函数的所有return语句,确保每个返回值都出现在边映射的key中。在边映射中使用default键设置默认路由,或确保全覆盖。
LLM调用超时或报错1. API密钥错误或额度不足。
2. 网络问题。
3. 提示词导致LLM输出格式不符合预期。
1. 检查环境变量和账单。
2. 增加超时设置。
3. 简化提示词,使用OutputParser
1. 配置正确的API密钥和代理。
2. 使用llm = ChatOpenAI(..., timeout=30)
3. 使用with_structured_outputPydanticOutputParser确保输出格式。
图陷入无限循环条件边逻辑错误,导致在几个节点间来回跳转,无法到达END使用LangGraph Studio可视化执行流程,检查循环路径。在条件路由函数中添加“最大重试次数”逻辑,或确保存在指向END的路径。
状态更新不符合预期多个节点并发修改同一字段(如果设置了并发),或缩减器(reducer)逻辑有误。理解Annotated字段的缩减器(如add_messages)是如何工作的。对于非列表的简单字段,直接赋值覆盖即可。对于列表,明确使用operator.add等缩减器或手动合并逻辑。

8. 最佳实践与工程建议

将LangGraph用于实际项目时,遵循以下建议可以避免很多麻烦:

  1. 精心设计State

    • 保持扁平:避免嵌套过深的结构,简化数据的存取。
    • 明确类型:始终使用TypedDictPydantic BaseModel来定义State,以获得良好的类型提示和早期错误检测。
    • 区分持久态与临时态:考虑哪些数据需要在整个工作流中持久化,哪些只是中间计算产物。
  2. 节点设计原则

    • 单一职责:每个节点只做一件事。例如,一个节点负责调用LLM,另一个节点负责解析LLM的响应。
    • 幂等性与容错:节点函数应尽可能设计成幂等的,并包含基本的错误处理(如重试、降级)。
    • 可测试性:将节点函数与LangGraph的绑定逻辑分离,使其可以独立进行单元测试。
  3. 利用LangChain生态

    • 工具(Tools):为你的Agent配备搜索、计算、代码执行等工具,大幅扩展其能力。使用@tool装饰器轻松创建。
    • 提示词模板:使用ChatPromptTemplate管理复杂的提示词,支持少样本、聊天历史等。
    • 输出解析器:使用PydanticOutputParserJsonOutputParser确保LLM输出结构化数据,避免脆弱的字符串解析。
  4. 生产环境部署

    • 持久化State:对于长时间运行或需要中断恢复的工作流,需要将State持久化到数据库。LangGraph的Checkpointer抽象支持此功能。
    • 异步支持:LangGraph天然支持异步节点(async def),在需要调用外部API时使用异步可以极大提高吞吐量。
    • 监控与日志:为关键节点添加详细的日志记录,记录输入、输出和耗时。考虑集成像LangSmith这样的LLM应用监控平台。
  5. 版本控制与团队协作

    • 将工作流图的定义代码化,并纳入Git版本控制。
    • 当工作流逻辑变更时,通过LangGraph Studio可视化对比变更,确保理解对执行路径的影响。

9. 总结与下一步探索

通过本文,你已经掌握了LangGraph构建多智能体系统的核心脉络:从State设计Node实现Edge连接条件分支。我们完成了一个具备基础决策能力的旅行规划多智能体,并探讨了可视化调试和工程化实践。

LangGraph的真正价值在于,它将Agent从“一次性对话”的范畴,提升到了“可编程、可观测、可维护的复杂业务流程”的层面。这为开发AI原生应用提供了坚实的工程基础。

下一步,你可以从这些方向深入:

  1. 集成真实工具:为你的Agent接入搜索引擎API、数据库查询、邮件发送等真实工具,让它能真正操作外部系统。
  2. 探索更复杂模式:研究StateGraphadd_node()add_edge()之外的高级API,如实现子图(as_node)、并行执行、动态节点添加等。
  3. 接入记忆与知识库:结合LangChain的RAG(检索增强生成)技术,让Agent拥有长期记忆和专属知识库,处理更专业的领域问题。
  4. 实现人工在环:在关键决策点(如预算超标、方案冲突)设计human_review节点,将决策权交给人,构建人机协同系统。
  5. 性能优化:对于耗时长的节点(如网络请求),使用异步实现;对于可以并行的分支,利用LangGraph的并发执行能力。

建议你将本文的示例代码作为起点,复制到本地,修改提示词、增加节点、尝试不同的条件逻辑,亲手体验“画”出一个智能工作流的感觉。当你遇到问题时,多利用app.stream()LangGraph Studio进行调试,它们是你理解复杂工作流的最佳伙伴。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询