资讯动态

LangGraph状态机实战:构建具备循环与人工审核的智能研究助手Agent

发布时间:2026/8/8 20:22:23 来源:尧图企业网站定制
1. 项目概述从 LangChain 到 LangGraphAgent 的范式跃迁如果你已经用 LangChain 搭建过一些简单的 AI 应用比如一个能联网搜索的问答机器人那你大概率已经接触过“Agent”这个概念了。在 LangChain 的早期版本里Agent 通常被理解为一个“工具调用者”你给它一个目标比如“查一下今天上海的天气”它就会规划步骤“我需要调用天气查询工具”然后执行调用 API最后给你结果。这个过程是线性的、一次性的。但当我们开始构建更复杂的应用时比如一个能持续与用户对话、管理长期任务、在多个工具间灵活跳转的智能客服或者一个能自主分析数据、撰写报告并发送邮件的自动化助手这种简单的“规划-执行”模式就显得力不从心了。这时LangGraph就登场了。它不是要取代 LangChain而是 LangChain 生态系统中的一个专门用于构建有状态、多步骤、可循环的复杂 Agent 的框架。你可以把它想象成从“单次函数调用”升级到了“一个完整的应用程序”。它的核心思想就是引入了计算机科学中一个经典且强大的概念——状态机。为什么状态机如此重要因为现实世界中的任务很少是“一锤子买卖”。一个客服对话有多个回合每个回合的上下文用户历史、已查询信息、用户情绪都在变化一个数据分析任务可能需要先清洗数据再分析发现异常后再回头重新清洗最后生成可视化图表。这些流程都拥有明确的“状态”比如“等待用户输入”、“数据清洗中”、“生成报告中”和状态之间的“转移条件”比如“用户提问”触发从“等待”到“处理”“清洗完成”触发到“分析”。用状态机来建模这些流程逻辑会变得异常清晰和健壮。所以当你看到“高级 AgentLangGraph 与状态机”这个标题时它指向的正是 AI 应用开发的下一个阶段如何构建那些真正具备复杂逻辑、能够处理非线性工作流、并且能维持长期记忆和上下文的智能体。这不再是玩具 demo而是迈向生产级 AI 应用的关键一步。接下来我将以一个“智能研究助手”Agent 为例带你彻底拆解 LangGraph 的核心三要素并手把手实现一个具备循环、分支和人工审核能力的复杂状态机。2. 核心三要素拆解State、Node、Edge要理解 LangGraph必须吃透它的三个核心抽象State、Node和Edge。这就像建房子的地基、砖块和钢筋三者结合才能构筑起稳固的架构。2.1 State智能体的记忆与上下文在 LangGraph 中State 是一个字典它定义了整个工作流运行过程中需要携带和更新的所有信息。你可以把它理解为 Agent 的“工作内存”或“上下文白板”。与 LangChain 中每次调用都相对独立的链不同LangGraph 的 State 会在整个图执行过程中持续存在并被修改。State 的定义与注解State 通常使用 Pydantic 的BaseModel来定义这能提供清晰的类型提示和验证。对于我们的研究助手State 可能包含from typing import List, Dict, Any, Optional, Annotated from typing_extensions import TypedDict from langgraph.graph.message import add_messages import operator # 方式一使用 TypedDict更灵活兼容性好 class AgentState(TypedDict): # 对话消息历史LangGraph 提供了专用注解来简化消息列表的合并操作 messages: Annotated[List[Dict], add_messages] # 用户输入的研究主题 research_topic: str # 从网络上搜集到的原始资料列表 gathered_sources: List[Dict[str, Any]] # 分析后的关键发现 key_findings: List[str] # 生成的报告草稿 report_draft: Optional[str] # 一个控制流程的标志位例如“是否需要人工审核” needs_human_review: bool # 人工审核的反馈意见 human_feedback: Optional[str]这里有几个关键点Annotated[List[Dict], add_messages]这是 LangGraph 的一个“魔法”。add_messages是一个归约器它定义了当多个节点同时向state[‘messages’]字段写入时如何合并这些值。对于消息列表最常见的操作就是追加。这确保了对话历史能正确累积而不会被覆盖。状态即数据流图中的每个节点都读取和修改这个共享的 State。例如“搜索节点”会向gathered_sources添加数据“分析节点”会读取gathered_sources并生成key_findings。设计原则State 应该包含所有必要的上下文但也要保持精简。避免将中间计算过程等临时变量塞进去专注于输入、输出和控制流数据。2.2 Node执行具体任务的函数Node 是图中的节点每个节点都是一个普通的 Python 函数或可调用对象。这个函数接收当前的State作为参数执行一些操作调用 LLM、使用工具、处理数据然后返回一个包含对 State更新内容的字典。节点的编写范式一个典型的节点函数看起来是这样的def search_node(state: AgentState) - Dict[str, Any]: 负责根据主题进行网络搜索的节点。 print(f“[搜索节点] 正在搜索主题{state[‘research_topic’]}”) # 1. 准备搜索查询这里可以加入查询优化逻辑 search_query f“{state[‘research_topic’]} latest research 2024” # 2. 调用搜索工具例如 Tavily Search API、Serper API 或 DuckDuckGo # 假设我们有一个 search_web 函数 search_results search_web(search_query, max_results5) # 3. 对结果进行初步处理提取标题、链接、摘要 processed_sources [] for result in search_results: processed_sources.append({ “title”: result.get(“title”), “url”: result.get(“url”), “snippet”: result.get(“snippet”)[:200] “...” # 截断摘要 }) # 4. 返回要更新的 State 部分 # 注意我们返回的是 gathered_sources而不是整个 state。 # LangGraph 会自动将这个字典与当前 state 合并。 return {“gathered_sources”: processed_sources}关键理解节点函数不直接修改传入的state对象。它只是基于state进行计算然后返回一个字典指明要更新哪些字段。返回的字典中的键必须与 State 中定义的字段名对应。一个节点可以很复杂比如内部封装了一个 LangChain Chain也可以很简单只是一个逻辑判断。2.3 Edge决定流程走向的规则Edge 定义了图中节点之间的连接关系更重要的是它决定了在某个节点执行完毕后下一个该执行哪个节点。这是状态机逻辑的核心。Edge 分为两种普通边直接从一个节点连接到另一个节点无条件执行。条件边根据 State 中的某个条件动态决定下一个节点。条件边通过一个特殊的conditional_edge来创建它连接到一个“路由函数”。这个路由函数检查 State并返回下一个要执行的节点的名称字符串。条件边的实战在我们的研究助手中在“生成报告”节点之后我们可能希望引入一个人工审核环节。但并非所有报告都需要审核只有当内容敏感或置信度低时才需要。我们可以这样设计from langgraph.graph import END, START def should_review(state: AgentState) - str: 路由函数决定下一步是人工审核还是直接结束。 # 这里可以设计更复杂的逻辑例如 # - 检查报告是否涉及特定关键词如“医疗建议”、“投资” # - 调用一个 LLM 来判断报告内容的置信度 # - 基于 state[‘needs_human_review’] 标志位可能由之前的节点设置 sensitive_keywords [“医疗”, “法律”, “财务建议”, “投资”] report_text state.get(“report_draft”, “”).lower() if any(keyword in report_text for keyword in sensitive_keywords): return “human_review_node” # 前往人工审核节点 elif state.get(“needs_human_review”, False): return “human_review_node” else: return END # 直接结束整个图的工作流然后在构建图时你会将“生成报告节点”通过一个conditional_edge连接到这个路由函数而路由函数的结果“human_review_node”或END决定了真正的流向。注意START和END是 LangGraph 中两个特殊的节点名分别代表图的入口和出口。把 State、Node、Edge 组合起来你就得到了一个完整的、可定义复杂业务逻辑的“蓝图”。接下来我们就用这个蓝图搭建一个真实的研究助手。3. 构建智能研究助手从蓝图到代码让我们实现一个具备完整流程的研究助手 Agent。它的工作流是接收主题 - 并行搜索与读取本地文档 - 综合分析 - 生成报告 - 条件性人工审核 - 最终输出。3.1 定义完整状态与工具首先我们完善 State 并准备一些工具函数。from typing import List, Dict, Any, Optional, Annotated, Literal from typing_extensions import TypedDict from langgraph.graph.message import add_messages import asyncio # 假设的工具函数和模型调用 from some_tool_module import tavily_search, read_pdf_text from some_llm_module import call_llm class ResearchAgentState(TypedDict): 研究助手 Agent 的完整状态定义。 messages: Annotated[List[Dict], add_messages] research_topic: str # 来源分为网络和本地 web_sources: List[Dict[str, Any]] local_sources: List[Dict[str, Any]] # 综合后的资料 synthesized_info: Optional[str] key_findings: List[str] report_draft: Optional[str] needs_human_review: bool human_feedback: Optional[str] # 一个标志记录当前流程步骤可用于调试或复杂路由 current_step: Literal[“start”, “gathering”, “analyzing”, “reporting”, “reviewing”, “end”]3.2 实现各个功能节点我们将创建多个节点每个负责一项具体工作。节点1任务规划与初始化这个节点负责解析用户输入明确研究主题并初始化状态。def planning_node(state: ResearchAgentState) - Dict[str, Any]: 规划节点解析输入初始化任务。 # 通常最后一条用户消息是输入 user_input state[“messages”][-1][“content”] if state[“messages”] else state.get(“research_topic”, “”) # 可以在这里调用一个 LLM 来更好地提炼研究主题和子问题 # 例如用户说“帮我研究一下太阳能电池的最新进展”LLM 可以提炼出“钙钛矿太阳能电池效率”、“硅基异质结技术”等子方向。 # 这里为了简化我们直接使用输入作为主题。 refined_topic user_input print(f“[规划节点] 研究主题已确定{refined_topic}”) return { “research_topic”: refined_topic, “current_step”: “gathering”, “web_sources”: [], # 初始化空列表 “local_sources”: [], “synthesized_info”: None, “key_findings”: [], “report_draft”: None, “needs_human_review”: False, “human_feedback”: None, }节点2与3并行信息搜集研究需要多来源。我们可以让网络搜索和本地文档读取同时进行。这需要用到 LangGraph 的并行化能力。def web_search_node(state: ResearchAgentState) - Dict[str, Any]: 网络搜索节点。 topic state[“research_topic”] print(f“[网络搜索节点] 正在搜索{topic}”) try: # 使用 Tavily、Serper 或 DuckDuckGo 等搜索 API results tavily_search( queryf“{topic} site:.edu OR site:.gov OR site:.org recent”, max_results5, include_answerFalse, search_depth“basic” ) # 格式化结果 web_sources [] for r in results.get(“results”, []): web_sources.append({ “title”: r.get(“title”, “No Title”), “url”: r.get(“url”, “”), “content”: r.get(“content”, “”)[:500], # 取前500字符 “score”: r.get(“score”, 0.0) }) return {“web_sources”: web_sources} except Exception as e: print(f“网络搜索失败{e}”) return {“web_sources”: []} # 失败时返回空不影响整体流程 def local_doc_node(state: ResearchAgentState) - Dict[str, Any]: 本地文档读取节点。 # 假设我们有一个已知的本地文档路径列表或者从 state 中获取 doc_paths [“./data/research_paper1.pdf”, “./data/industry_report.pdf”] local_sources [] for path in doc_paths: try: text read_pdf_text(path) # 简单提取前几行作为“标题”并截取部分内容 local_sources.append({ “title”: path.split(“/”)[-1], “path”: path, “content”: text[:1000] “...” if len(text) 1000 else text }) except Exception as e: print(f“读取文档 {path} 失败{e}”) print(f“[本地文档节点] 已读取 {len(local_sources)} 份文档。”) return {“local_sources”: local_sources}节点4信息综合与分析这个节点接收并行搜集的结果调用 LLM 进行总结、去重、提取关键发现。def analysis_node(state: ResearchAgentState) - Dict[str, Any]: 分析节点综合所有信息提取关键发现。 print(“[分析节点] 开始综合与分析信息...”) web_info “\n\n”.join([f“标题{s[‘title’]}\n内容{s[‘content’]}” for s in state[“web_sources”]]) local_info “\n\n”.join([f“文档{s[‘title’]}\n内容{s[‘content’]}” for s in state[“local_sources”]]) all_info f“## 网络信息\n{web_info}\n\n## 本地文档信息\n{local_info}” # 构造给 LLM 的提示词 prompt f“”” 你是一个专业的研究分析员。请基于以下关于“{state[‘research_topic’]}”的资料完成以下任务 1. **综合摘要**用一段话200字以内概括所有资料的核心观点。 2. **提取关键发现**列出3-5条最重要的发现或事实每条用短句陈述。 3. **评估信息质量**整体上这些资料的可靠性和全面性如何高/中/低 请严格按照以下 JSON 格式输出不要有任何其他文字 json {{ “summary”: “综合摘要文本”, “key_findings”: [“发现一”, “发现二”, “...”], “reliability”: “高/中/低” }}资料如下 {all_info} “””try: response call_llm( model“gpt-4”, messages[{“role”: “user”, “content”: prompt}], temperature0.2, response_format{“type”: “json_object”} # 要求 JSON 输出 ) import json analysis_result json.loads(response[“choices”][0][“message”][“content”]) # 根据可靠性评估决定是否需要人工审核 needs_review analysis_result.get(“reliability”, “中”) in [“低”] return { “synthesized_info”: analysis_result.get(“summary”, “”), “key_findings”: analysis_result.get(“key_findings”, []), “needs_human_review”: needs_review, “current_step”: “reporting” } except Exception as e: print(f“分析过程出错{e}”) # 出错时标记需要人工审核 return { “synthesized_info”: “分析过程出现错误。”, “key_findings”: [], “needs_human_review”: True, “current_step”: “reporting” }**节点5报告生成** 基于分析结果生成一份结构化的报告草稿。 python def report_generation_node(state: ResearchAgentState) - Dict[str, Any]: 报告生成节点。 print(“[报告生成节点] 正在撰写报告草稿...”) prompt f“”” 你是一名技术文档工程师。请基于以下关于“{state[‘research_topic’]}”的分析结果撰写一份简洁的专业报告草稿。 **分析摘要** {state[‘synthesized_info’]} **关键发现** {chr(10).join(‘- ‘ f for f in state[‘key_findings’])} **报告要求** 1. 标题明确。 2. 包含“概述”、“主要发现”、“结论”三个部分。 3. 语言客观、精炼避免主观臆断。 4. 如果某些发现存在不确定性或信息源可靠性为‘低’请在报告中用‘[待核实]’标注。 5. 报告总长度控制在500字以内。 请直接输出报告正文不需要额外的解释。 “”” try: response call_llm( model“gpt-4”, messages[{“role”: “user”, “content”: prompt}], temperature0.3 ) draft response[“choices”][0][“message”][“content”] return { “report_draft”: draft, “current_step”: “reviewing” # 进入审核阶段 } except Exception as e: print(f“报告生成失败{e}”) return { “report_draft”: “报告生成失败。”, “current_step”: “reviewing”, “needs_human_review”: True # 生成失败也需人工介入 }节点6人工审核交互节点这是一个特殊的节点它可能需要暂停工作流等待外部输入如用户在界面上点击“通过”或输入反馈。在 LangGraph 中这可以通过“暂停”或“检查点”机制实现但为了简化我们模拟一个自动判断。def human_review_node(state: ResearchAgentState) - Dict[str, Any]: 人工审核节点模拟。 print(“[人工审核节点] 报告已生成等待审核...”) # 在实际应用中这里可能会 # 1. 将报告发送到审核队列如数据库、消息队列。 # 2. 调用 graph.checkpoint() 保存当前状态并暂停。 # 3. 等待一个外部事件如 HTTP 回调来恢复执行并携带 human_feedback。 # 为了演示我们模拟一个自动通过的逻辑或者根据 needs_human_review 决定。 if state[“needs_human_review”]: # 模拟人工审核后给出了反馈 simulated_feedback “报告整体不错但第三点发现的数据来源请再核实一下。” print(f“[模拟] 收到人工反馈{simulated_feedback}”) return { “human_feedback”: simulated_feedback, “current_step”: “revising” } else: print(“[模拟] 无需人工审核自动通过。”) return {“current_step”: “end”} # 前往结束节点7报告修订可选如果收到了人工反馈这个节点负责根据反馈修改报告。def revision_node(state: ResearchAgentState) - Dict[str, Any]: 根据人工反馈修订报告。 feedback state[“human_feedback”] draft state[“report_draft”] print(f“[修订节点] 正在根据反馈进行修订。反馈{feedback}”) prompt f“”” 以下是报告草稿和审核反馈请根据反馈修改报告草稿。 **原始报告草稿** {draft} **审核反馈** {feedback} 请输出修改后的完整报告。如果反馈不涉及具体修改可以保持原样。 “”” try: response call_llm( model“gpt-4”, messages[{“role”: “user”, “content”: prompt}], temperature0.1 ) revised_draft response[“choices”][0][“message”][“content”] return { “report_draft”: revised_draft, “current_step”: “end” } except Exception as e: print(f“修订失败{e}”) return {“current_step”: “end”} # 即使失败也尝试结束3.3 组装图并定义流程逻辑现在我们将所有节点和边组装起来形成完整的工作流。from langgraph.graph import StateGraph, END # 1. 创建图构建器并指定状态结构 workflow StateGraph(ResearchAgentState) # 2. 添加节点 workflow.add_node(“plan”, planning_node) workflow.add_node(“search_web”, web_search_node) workflow.add_node(“search_local”, local_doc_node) workflow.add_node(“analyze”, analysis_node) workflow.add_node(“generate_report”, report_generation_node) workflow.add_node(“human_review”, human_review_node) workflow.add_node(“revise”, revision_node) # 3. 设置入口点 workflow.set_entry_point(“plan”) # 4. 添加边定义流程 # 规划之后并行执行网络搜索和本地搜索 workflow.add_edge(“plan”, “search_web”) workflow.add_edge(“plan”, “search_local”) # 两个搜索节点都完成后再进入分析节点。 # 这里需要用到 add_conditional_edges 或 add_edge 的聚合功能。 # 更常见的模式是使用一个“聚合节点”来等待并行任务但为简化我们假设它们都完成后自动进入分析。 # 我们可以通过让 analyze 节点在 state 中检查两个源是否都已存在非空来隐式实现或者使用更高级的构造。 # 这里我们采用一个简单方式从 plan 直接连到 analyze但在 analyze 节点内等待/检查数据。 # 实际上更规范的做法是使用 LangGraph 的 Pregel 的并发特性。为了清晰我们调整一下逻辑 # 让 plan 之后先到一个“协调节点”由它来并发触发搜索然后等待结果。但代码会复杂很多。 # 作为教程我们简化顺序执行 web - local - analyze这仍然是有效的状态机只是没并发。 workflow.add_edge(“search_web”, “search_local”) workflow.add_edge(“search_local”, “analyze”) # 分析之后生成报告 workflow.add_edge(“analyze”, “generate_report”) # 报告生成后根据条件决定是进入人工审核还是结束 def after_report_route(state: ResearchAgentState) - str: if state[“needs_human_review”]: return “human_review” else: return END workflow.add_conditional_edges( “generate_report”, after_report_route, { “human_review”: “human_review”, END: END } ) # 人工审核后进入修订节点 workflow.add_edge(“human_review”, “revise”) # 修订后结束 workflow.add_edge(“revise”, END) # 5. 编译图 app workflow.compile() # 6. 可视化需要安装 graphviz try: from IPython.display import Image, display display(Image(app.get_graph().draw_mermaid_png())) except: print(“无法显示图形但图已编译完成。”)3.4 运行与调试现在我们可以运行这个研究助手了。# 初始化状态 initial_state: ResearchAgentState { “messages”: [{“role”: “user”, “content”: “请帮我研究一下大语言模型在代码生成方面的最新进展和主要挑战。”}], “research_topic”: “”, “web_sources”: [], “local_sources”: [], “synthesized_info”: None, “key_findings”: [], “report_draft”: None, “needs_human_review”: False, “human_feedback”: None, “current_step”: “start” } # 运行图 final_state app.invoke(initial_state) print(“\n” “”*50) print(“最终报告”) print(“”*50) print(final_state[“report_draft”]) print(“\n关键发现”, final_state[“key_findings”]) print(“当前步骤”, final_state[“current_step”])运行后你会在控制台看到各个节点的执行日志并最终得到一份生成的研究报告。这个流程清晰地展示了信息如何在不同节点间流动状态如何被逐步更新以及条件逻辑如何影响执行路径。4. 高级特性与生产级考量当你掌握了基础构建方法后以下高级特性能让你的 LangGraph Agent 更强大、更稳健。4.1 持久化与检查点让 Agent 拥有“记忆”对于长时间运行或需要中断恢复的任务持久化至关重要。LangGraph 与 LangChain 生态深度集成支持将运行状态保存到数据库如 PostgreSQL、MySQL或内存中。from langgraph.checkpoint import MemorySaver from langgraph.graph import StateGraph, START, END # 在编译图时加入检查点管理器 checkpointer MemorySaver() workflow StateGraph(ResearchAgentState, checkpointercheckpointer) # ... 添加节点和边 ... app workflow.compile() # 运行时会自动创建检查点 config {“configurable”: {“thread_id”: “user_123_research_task”}} initial_state {…} # 第一次调用 result1 app.invoke(initial_state, configconfig) # 假设任务在这里因某种原因暂停了state 已被保存。 # 之后可以从最后一个检查点恢复执行 # 我们通过传入一个空的输入并指定从上一个线程继续 resumed_state app.invoke( {“messages”: [{“role”: “user”, “content”: “继续”}]}, # 可以传入新的消息来影响流程 configconfig )MemorySaver适用于开发和测试。在生产环境中你会使用PostgresSaver或MongoDBSaver将状态持久化到外部存储从而实现跨会话、跨服务器重启的 Agent 状态恢复。这对于构建客服对话机器人等长周期应用是必备功能。4.2 子图与模块化构建复杂系统的基石当你的 Agent 变得非常复杂时将所有逻辑塞进一个图里会难以维护。子图允许你将一部分功能例如一个完整的“搜索-评估-过滤”流程封装成一个独立的、可复用的图然后作为单个节点嵌入到主图中。from langgraph.graph import StateGraph as SubStateGraph # 1. 定义一个“信息验证”子图 def create_validation_subgraph(): sub_builder SubStateGraph(ResearchAgentState) def validate_source_node(state): # 验证单个信息来源的可信度 # ... return {“source_credibility_score”: 0.95} def aggregate_validation_node(state): # 聚合所有验证结果 # ... return {“overall_credibility”: “high”} sub_builder.add_node(“validate”, validate_source_node) sub_builder.add_node(“aggregate”, aggregate_validation_node) sub_builder.add_edge(“validate”, “aggregate”) sub_builder.set_entry_point(“validate”) sub_builder.set_finish_point(“aggregate”) # 子图有明确的结束点 return sub_builder.compile() # 2. 在主图中将这个子图作为一个节点添加 validation_subgraph create_validation_subgraph() workflow.add_node(“validate_sources”, validation_subgraph) # 3. 在主图的适当位置连接这个节点 workflow.add_edge(“search_web”, “validate_sources”) workflow.add_edge(“validate_sources”, “analyze”)这样做的好处是关注点分离主图逻辑清晰子图负责特定复杂功能。可复用性同一个验证子图可以被用在多个不同的主图中。可测试性子图可以独立进行单元测试。4.3 流式输出与中断对于需要实时向用户反馈进度的 Agent如一边搜索一边显示结果或者需要支持用户中途取消的任务流式输出和中断机制很重要。流式输出LangGraph 的app.stream()方法可以让你逐步获取每个节点执行后的状态更新。inputs {“messages”: [{“role”: “user”, “content”: “研究主题”}]} config {“configurable”: {“thread_id”: “stream_demo”}} for event in app.stream(inputs, configconfig, stream_mode“values”): # event 是一个元组 (node_name, state_update) node, state_update event print(f“节点 [{node}] 执行完毕。”) if “report_draft” in state_update and state_update[“report_draft”]: print(“报告草稿已更新片段...”) # 你可以将 state_update 中的部分内容如新的消息实时发送给前端中断处理你可以在节点函数中检查某个标志位例如来自一个全局信号或数据库或者通过app.invoke()的config传入超时设置来实现优雅的中断。更精细的控制可能需要结合检查点在每次节点执行前检查是否收到“取消”指令。4.4 错误处理与韧性生产级 Agent 必须能妥善处理失败如 API 超时、网络错误、LLM 输出格式错误。节点级容错在每个节点函数内部使用try...except并返回一个代表错误的状态如{“error”: “API timeout”, “step”: “retry”}。图级容错路由利用条件边根据 State 中是否有error字段将流程路由到一个专门的“错误处理节点”。这个节点可以尝试重试、降级处理如使用备用模型、或通知用户。超时设置在调用 LLM 或外部 API 时务必设置超时参数。验证与重试对于关键节点可以设计一个包装器在失败时自动重试几次。def robust_llm_call(prompt, max_retries3): for i in range(max_retries): try: return call_llm(prompt, timeout30) # 设置超时 except TimeoutError: print(f“LLM 调用超时第 {i1} 次重试...”) if i max_retries - 1: raise except Exception as e: print(f“LLM 调用失败{e}”) raise # 非超时错误直接抛出将这些策略结合起来你的 Agent 就能在部分组件失效时依然提供有价值的服务或者至少能给出清晰的错误报告而不是直接崩溃。5. 常见问题与避坑指南在实际开发中你会遇到各种各样的问题。以下是我从多个项目中总结出的高频问题和解决方案。5.1 状态管理混乱问题State 字段设计不合理导致节点间数据污染或难以追踪。例如多个节点都修改同一个列表但意图不同。解决方案字段职责单一化为不同的数据阶段设计不同的字段。例如不要只用sources而是分为raw_sources、filtered_sources、processed_sources。使用不可变数据结构在返回更新字典时尽量创建新的列表或字典而不是修改传入 state 中的对象。虽然 LangGraph 的合并机制会处理但清晰的意图有助于调试。添加调试字段像我们例子中的current_step或者last_updated_by这样的字段在复杂流程中非常有助于追踪状态变化轨迹。5.2 条件边路由逻辑过于复杂问题路由函数should_do_something(state)里塞满了大量的if-else判断难以维护和测试。解决方案路由表模式将路由逻辑抽象成配置。例如定义一个字典键为条件名值为一个判断函数和对应的目标节点。routing_rules [ {“name”: “needs_detail”, “condition”: lambda s: len(s[‘findings’]) 3, “target”: “detail_search_node”}, {“name”: “is_controversial”, “condition”: lambda s: “争议” in s[‘summary’], “target”: “human_review_node”}, {“name”: “default”, “condition”: lambda s: True, “target”: END}, ]然后在路由函数中遍历这个列表返回第一个满足条件的target。专用路由节点如果路由逻辑极其复杂可以将其作为一个独立的节点。这个节点不干别的只负责计算下一个节点名称并返回。这样可以将路由逻辑从conditional_edge中分离出来便于单独测试和优化。5.3 并行与同步的坑问题像我们例子中想并行执行web_search和local_doc但简单的add_edge无法实现真正的并发最终变成了顺序执行。解决方案使用Pregel的并发特性LangGraph 底层基于 Pregel 模型。要实现真正的并发需要将多个节点添加到同一个“层”并正确配置它们的依赖关系。这通常涉及更底层的StateGraph配置或者使用Channel的概念。对于大多数应用如果并发不是性能瓶颈顺序执行简化版也足够清晰。明确聚合点如果必须并发一定要设计一个明确的“聚合节点”该节点等待所有并发分支的输出都就绪后再执行后续操作。可以在 State 中设置标志位或者利用 LangGraph 更高级的Channel和Barrier原语。5.4 LLM 调用成本与延迟问题图中每个节点都调用 LLM导致单次运行成本高、速度慢。解决方案缓存对内容变化不大的查询如“总结以下文本”这类提示词固定、输入变化不大的操作使用 LLM 调用缓存。LangChain/LangGraph 社区有一些缓存集成方案。批处理将多个小的、独立的 LLM 调用合并成一个批处理提示。例如分析节点中需要评估多个信息来源的可信度可以设计一个提示词让 LLM 一次性对所有来源打分而不是循环调用 N 次。模型分级不是所有步骤都需要 GPT-4。对于信息提取、简单分类等任务使用更便宜、更快的模型如 Claude Haiku, GPT-3.5-Turbo。只在需要深度推理、创意生成或关键决策时使用大模型。异步调用如果图中有真正的并发节点确保使用异步的 LLM 客户端如openai.AsyncOpenAI来并行执行这些调用而不是同步等待。5.5 调试与可视化困难问题图执行到哪一步了State 变成什么样了为什么卡住了解决方案善用日志在每个节点的开始和结束打印关键信息包括传入的 State 片段和返回的更新。使用结构化的日志格式如 JSON方便搜索和分析。利用检查点不仅为了持久化检查点也是强大的调试工具。你可以在任何步骤暂停检查保存的 State 快照。图形化调试app.get_graph().draw_mermaid_png()生成流程图帮你宏观理解逻辑。对于单次运行可以手动记录每个节点的输入输出绘制出实际的执行路径图。单元测试子图将复杂的子图单独编译和测试用固定的输入验证其输出是否符合预期。这比测试整个大图要容易得多。构建 LangGraph Agent 是一个迭代过程。从最简单的线性流程开始逐步添加分支、循环、并行和错误处理。时刻记住状态机的基本思想当前状态 事件/条件 下一个状态和动作。用这个思维模型去设计你的节点和边你会发现再复杂的业务逻辑也能被清晰地建模和实现。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价