资讯动态

LangChain、LangGraph与Deep Agents:构建可上线复杂Agent系统的三层架构实践

发布时间:2026/10/15 10:05:40 来源:尧图企业网站定制
1. 从能跑通到能上线这套组合拳到底在解决什么问题第一次接触 LangChain 的时候我跟大多数人一样觉得这东西就是个把大模型调用封装得更优雅的库。写个LLMChain套个PromptTemplate再接个OutputParser跑通了就觉得自己会了。直到真正拿它去做一个需要多轮决策、需要调用外部工具、需要在失败时自动重试的项目才发现单靠 LangChain 那套链式结构代码会迅速膨胀成一团乱麻——条件分支嵌套、状态传递靠全局变量、调试的时候根本不知道是哪一步出了问题。后来 LangGraph 出来了再后来 Deep Agents 这个概念也被提了出来。我把这三样东西放在一起用了一段时间踩了不少坑也总结出了一些真正能落地的经验。这篇文章就是把这些东西掰开揉碎讲清楚LangChain 负责什么、LangGraph 补上了什么、Deep Agents 又在这个基础上加了什么以及在实际项目里怎么把它们组合起来用。先说结论性的判断方便你决定要不要往下读LangChain是基础层解决的是和大模型对话、解析输出、调用工具这些原子操作的标准接口问题。它的价值在于生态和抽象不在于它本身有多复杂。LangGraph是编排层解决的是多个步骤之间怎么流转、状态怎么管理、出错怎么回退的问题。它把 Agent 的执行过程建模成一张有向图每个节点是一个操作边是流转条件。Deep Agents是模式层它不是一个具体的库而是一种架构思路——把复杂任务拆解成规划、执行、反思、修正的循环让 Agent 具备长程任务的处理能力。这三者的关系不是替代而是叠加。你可以只用 LangChain 写个简单的问答机器人也可以用 LangGraph 编排一个多 Agent 协作系统而 Deep Agents 的思路则决定了你这个系统能不能处理那些需要几十步才能完成的任务。这篇文章适合谁看如果你已经写过至少一个能跑通的大模型应用但对怎么让它稳定、可维护、能处理复杂任务感到困惑那这篇就是写给你的。如果你完全没接触过 LangChain建议先花半天时间把官方 Quickstart 跑一遍再回来看这篇效果会好很多。下面我会按照设计思路 → 核心细节 → 实操过程 → 问题排查这个顺序展开每一部分都会给出具体的代码示例和参数说明。代码基于 Python版本以我写这篇文章时的稳定版为准如果你用的是其他版本个别 API 可能有差异注意对照官方文档。2. 整体架构设计为什么是这三层而不是别的组合2.1 先搞清楚每一层到底在管什么很多人一开始会把 LangChain 和 LangGraph 混着用结果写出来的代码既不像链式调用也不像图编排四不像。要避免这个问题得先把每一层的职责边界划清楚。LangChain 的职责边界它管的是单次操作。一次模型调用、一次工具执行、一次输出解析这些都是 LangChain 的范畴。它提供的ChatModel、Tool、OutputParser、Retriever这些抽象本质上都是在定义一个操作长什么样。你用它来写一个 RAG 的检索环节或者写一个需要调用计算器的工具节点都很合适。但如果你试图用 LangChain 的Chain去表达先检索、再判断相关性、不相关就重新检索、相关就生成答案这种带循环的逻辑就会很别扭。LangGraph 的职责边界它管的是多个操作之间怎么连。状态怎么定义、节点之间怎么跳转、条件边怎么写、中断怎么恢复这些是 LangGraph 的核心。它不关心你每个节点内部是用 LangChain 写的还是手写的它只关心输入状态和输出状态。这就意味着你可以把 LangChain 的那些组件当作 LangGraph 节点里的零件来用而不是让 LangChain 的 Chain 去承载整个流程。Deep Agents 的职责边界它管的是任务怎么拆、怎么跟踪、怎么修正。这不是一个代码层面的东西而是一种设计模式。一个 Deep Agent 通常包含几个关键角色规划器负责把大任务拆成子任务执行器负责逐个完成反思器负责检查结果是否达标修正器负责在失败时调整策略。这套东西可以用 LangGraph 来实现也可以用别的框架但用 LangGraph 来实现是最自然的因为它的图结构天然适合表达这种循环。我画不出图这里也不允许画但你可以这样理解三者的关系LangChain 是砖块LangGraph 是钢筋骨架Deep Agents 是建筑图纸。没有图纸你也能用砖块和钢筋搭个棚子但没有钢筋你只能用砖块垒个矮墙而没有砖块钢筋骨架也立不起来。2.2 为什么不用纯 LangChain 硬扛复杂流程我试过。早期做一个需要检索 → 判断 → 可能重新检索 → 生成 → 自检 → 可能重新生成的问答系统我用 LangChain 的SequentialChain加上一堆if-else硬写。结果是状态传递靠一个不断膨胀的字典每个 Chain 都要手动把需要的字段挑出来传进去漏一个就报错。重试逻辑写在 Chain 外面用while循环包着一旦嵌套两层以上代码就没法看了。调试的时候日志打出来是一长串嵌套调用根本分不清哪一层是哪一步。想加一个人工审核的中断点发现得把整个流程拆开重写。后来换成 LangGraph同样的逻辑代码量少了大概三分之一而且结构清晰得多。每个节点就是一个函数输入输出都是明确定义的状态条件边用简单的函数表达中断点用interrupt机制就能实现。这不是说 LangGraph 有多神奇而是它把流程控制这件事从业务逻辑里抽出来了让你能分别关注。2.3 Deep Agents 思路带来的关键转变在没有引入 Deep Agents 思路之前我写的 Agent 基本都是一步到位的给一个任务它调用一次或几次工具然后给出答案。这种模式在简单任务上没问题但遇到需要多步推理、需要根据中间结果调整策略的任务就很容易失败。Deep Agents 的核心转变在于把完成任务变成一个迭代改进的过程。具体来说它引入了几个关键机制显式规划在开始执行之前先让模型生成一个任务分解列表而不是直接开始做。这个规划步骤本身可以是一个 LangGraph 节点。状态跟踪用一个结构化的状态对象记录已经完成了哪些子任务、当前在做什么、遇到了什么问题而不是靠模型的上下文记忆。反思循环每个子任务完成后让模型检查结果是否满足要求不满足就触发修正。终止条件明确什么情况下算完成、什么情况下算失败避免无限循环。这些机制单独看都不复杂但组合起来就能让 Agent 处理那些需要十几步甚至几十步才能完成的任务。我实测下来在同样的模型能力下加了这套机制的 Agent 在复杂任务上的成功率比一步到位的版本高出不少具体数字因任务而异但趋势是明显的。2.4 技术选型的几个关键决策点在实际搭建的时候有几个决策点需要提前想清楚不然后面改起来很痛苦。第一个决策状态用什么结构。LangGraph 的状态可以是一个TypedDict也可以是一个 Pydantic 模型。我的建议是如果状态字段不多且类型简单用TypedDict就够了如果状态里有嵌套结构、需要校验、或者字段很多用 Pydantic 模型会更清晰。另外要注意LangGraph 的状态更新默认是覆盖语义如果你需要追加语义比如往列表里加东西需要用Annotated配合 reducer 函数。第二个决策节点粒度怎么定。一个节点可以是一个简单的模型调用也可以是一整个子流程。粒度太细图会变得很复杂节点之间的边会很多粒度太粗又失去了图编排的灵活性。我的经验是一个节点对应一个语义上完整的操作比如检索文档是一个节点判断相关性是另一个节点生成答案又是一个节点。不要把调用模型和解析输出拆成两个节点除非解析逻辑本身很复杂需要单独处理。第三个决策用不用预置的 Agent 类型。LangGraph 提供了一些预置的 Agent 构造函数比如create_react_agent。这些预置类型在简单场景下很好用但一旦你需要自定义状态、自定义节点逻辑就会觉得受限。我的建议是先用预置类型快速验证想法确认可行后再改成手写图。不要一上来就手写那样会浪费很多时间在样板代码上。第四个决策工具怎么组织。如果工具数量少少于 10 个可以直接全部塞给模型让它自己选。如果工具数量多建议做分组或者用检索的方式动态选择工具否则模型的工具选择准确率会下降。LangChain 的Tool抽象支持给工具写详细的描述这个描述的质量直接影响模型的选择准确率值得花时间打磨。3. 核心细节拆解状态、节点、边、工具这四样东西怎么用对3.1 状态定义别小看这个 TypedDict状态是 LangGraph 里最基础也最容易出错的地方。我见过不少人把状态定义成一个巨大的字典什么字段都往里塞结果节点之间互相污染调试的时候根本不知道哪个字段是谁改的。一个清晰的状态定义应该遵循几个原则字段名要能自解释。不要用data、result这种模糊的名字用retrieved_docs、relevance_score、final_answer这种一看就知道是什么的名字。区分输入字段和中间字段。输入字段是外部传进来的中间字段是节点产生的。虽然 LangGraph 不强制区分但在文档里标注清楚有助于维护。列表类型的字段要用 reducer。如果你有一个messages字段需要不断追加直接定义成list会导致每次更新都覆盖必须用Annotated[list, add_messages]这样的写法。下面是一个我常用的状态定义模板你可以直接参考from typing import Annotated, TypedDict from langgraph.graph.message import add_messages class AgentState(TypedDict): # 输入字段 user_query: str # 消息历史用 add_messages reducer 实现追加语义 messages: Annotated[list, add_messages] # 规划相关 plan: list[str] current_step: int # 执行相关 retrieved_docs: list[dict] tool_results: list[dict] # 反思相关 reflection: str is_complete: bool # 错误处理 error_count: int last_error: str这里有几个细节值得说。add_messages是 LangGraph 提供的一个 reducer它会智能地处理消息的追加和更新比如根据消息 ID 去重。如果你自己写 reducer要注意处理各种边界情况不如直接用官方的。error_count和last_error这两个字段是我后来加的用来做错误重试控制没有它们的话一旦某个节点反复失败图会陷入死循环。注意状态字段不是越多越好。每加一个字段就意味着所有节点都要考虑这个字段的存在。我建议在初期只加必要的字段等确实需要了再加。加字段的时候要检查所有节点确保它们不会因为新字段而行为异常。3.2 节点函数输入输出都是状态别搞特殊LangGraph 的节点就是一个函数输入是状态输出是要更新的状态字段。听起来简单但实际写的时候有几个坑。第一个坑返回部分状态还是完整状态。LangGraph 允许你只返回要更新的字段比如return {retrieved_docs: docs}它会自动合并到现有状态里。这比返回完整状态方便得多但要注意如果你返回的字段类型和状态定义不一致会在运行时才报错而不是定义时报错。所以建议在节点函数里加类型注解让静态检查工具帮你发现问题。第二个坑节点里的异常处理。如果节点函数抛异常整个图会中断。有些异常是你希望中断的比如配置错误有些是你希望捕获并重试的比如网络超时。我的做法是在节点内部用 try-except 捕获可重试的异常更新error_count和last_error字段然后通过条件边决定是重试还是放弃。这样图本身不会中断但你能控制重试逻辑。第三个坑节点里的模型调用怎么组织。如果你在节点里直接用 LangChain 的ChatModel要注意每次调用都会创建新的连接。在高频调用的场景下建议把模型实例作为节点函数的参数传入或者用单例模式管理。LangGraph 支持通过config传递依赖这是一个比较优雅的做法。下面是一个典型的节点函数示例展示了规划节点的写法def planning_node(state: AgentState) - dict: 根据用户查询生成任务计划 llm get_llm() # 获取模型实例 prompt f你是一个任务规划助手。请把下面的任务拆解成3-5个可执行的子任务。 任务{state[user_query]} 请以JSON数组格式返回子任务列表每个子任务是一个字符串。 try: response llm.invoke(prompt) plan parse_json_response(response.content) return {plan: plan, current_step: 0} except Exception as e: return {error_count: state.get(error_count, 0) 1, last_error: str(e)}这个节点做了三件事构造提示词、调用模型、解析结果。解析失败时更新错误计数而不是抛异常这样图可以继续走条件边来决定下一步。3.3 条件边流程控制的真正核心如果说节点是图的肉那条件边就是图的骨。LangGraph 的条件边通过一个函数来决定下一步走哪个节点这个函数的返回值是目标节点的名字。写条件边函数的时候有几个经验函数要纯粹。条件边函数不应该有副作用只根据状态做判断。如果你需要在判断前做一些计算把计算放到节点里条件边只读结果。返回值要明确。返回的字符串必须是图里真实存在的节点名否则运行时会报错。建议用常量或者枚举来管理节点名避免拼写错误。要有默认分支。条件边函数应该覆盖所有可能的情况包括异常情况。如果所有条件都不满足应该有一个兜底的分支而不是返回 None 导致图卡住。下面是一个典型的条件边示例用于决定规划完成后是继续执行还是直接结束def should_continue(state: AgentState) - str: 决定下一步走向 # 如果错误太多放弃 if state.get(error_count, 0) 3: return handle_failure # 如果任务已完成结束 if state.get(is_complete, False): return end # 如果还有未完成的步骤继续执行 if state.get(current_step, 0) len(state.get(plan, [])): return execute_step # 所有步骤完成进入反思 return reflect这个函数覆盖了四种情况每种都返回一个明确的节点名。注意end是 LangGraph 的特殊节点名表示图执行结束。3.4 工具集成描述比实现更重要在 Agent 系统里工具是模型和外部世界交互的接口。LangChain 的Tool抽象让定义工具变得很简单但很多人忽略了工具描述的重要性。模型选择工具的依据主要是工具的名称和描述。如果描述写得含糊模型就会选错工具或者在不该用工具的时候用工具。我踩过的坑包括两个功能相似的工具描述太接近模型随机选工具描述里没写清楚参数格式模型传错参数工具描述里没写清楚适用场景模型在不合适的场景下调用。一个好的工具描述应该包含功能说明这个工具是做什么的一句话说清楚。适用场景什么情况下应该用这个工具什么情况下不应该用。参数说明每个参数的类型、含义、格式要求。返回值说明返回什么格式的数据模型应该怎么理解。下面是一个工具定义的示例from langchain_core.tools import tool tool def search_knowledge_base(query: str, top_k: int 5) - list[dict]: 在知识库中搜索相关文档。 适用场景当你需要查找事实性信息、文档内容、或者需要引用来源时使用。 不适用场景当你需要计算、或者需要实时数据时不要用这个工具。 参数 query: 搜索关键词应该是自然语言描述的问题或关键词组合。 top_k: 返回的文档数量默认5范围1-20。 返回 一个字典列表每个字典包含 content文档内容和 score相关性分数。 # 实际实现 return do_search(query, top_k)这个描述看起来有点啰嗦但实测下来模型选择工具的准确率比简短描述高出不少。花在写描述上的时间会在调试阶段省回来。提示如果你的工具数量超过 15 个考虑做工具分组。比如把搜索类工具放一组计算类工具放一组先让模型选组再在组内选工具。这样能显著降低模型的选择难度。4. 实操过程从零搭一个带反思循环的 Deep Agent4.1 环境准备与依赖安装先把环境搭起来。我用的是 Python 3.11LangChain 和 LangGraph 的版本以你安装时的最新稳定版为准。依赖不多但要注意版本兼容性。pip install langchain langchain-core langgraph langchain-openai如果你用的是其他模型提供商把langchain-openai换成对应的包。我建议在虚拟环境里装避免和系统里的其他包冲突。装完之后先跑一个最小示例验证环境没问题from langchain_openai import ChatOpenAI from langgraph.graph import StateGraph, END from typing import TypedDict class SimpleState(TypedDict): input: str output: str def process_node(state: SimpleState) - dict: llm ChatOpenAI(modelgpt-4o-mini) result llm.invoke(state[input]) return {output: result.content} graph StateGraph(SimpleState) graph.add_node(process, process_node) graph.set_entry_point(process) graph.add_edge(process, END) app graph.compile() result app.invoke({input: 你好}) print(result[output])这段代码能跑通说明环境没问题。如果报错大概率是 API Key 没配好或者模型名称写错了。4.2 定义状态和节点把规划、执行、反思拆开接下来定义完整的状态和节点。我按照 Deep Agents 的思路把流程拆成四个核心节点规划、执行、反思、修正。from typing import Annotated, TypedDict from langgraph.graph.message import add_messages class DeepAgentState(TypedDict): user_query: str messages: Annotated[list, add_messages] plan: list[str] current_step: int step_results: list[dict] reflection: str is_complete: bool error_count: int last_error: str max_steps: int这里加了max_steps字段用来防止无限循环。这是一个很重要的保护机制后面会详细说。规划节点的实现前面已经给过示例这里补充一下执行节点和反思节点。执行节点的职责是根据当前步骤决定调用哪个工具执行工具记录结果。def execute_node(state: DeepAgentState) - dict: 执行当前步骤 current_step state[current_step] plan state[plan] if current_step len(plan): return {is_complete: True} step_desc plan[current_step] llm get_llm_with_tools() # 获取绑定了工具的模型 # 构造执行提示 prompt f当前任务{step_desc} 已有结果{state.get(step_results, [])} 请决定是否需要调用工具如果需要调用合适的工具。 try: response llm.invoke(prompt) # 处理工具调用 if response.tool_calls: tool_results execute_tool_calls(response.tool_calls) new_result {step: current_step, desc: step_desc, result: tool_results} else: new_result {step: current_step, desc: step_desc, result: response.content} return { step_results: state.get(step_results, []) [new_result], current_step: current_step 1, messages: [response] } except Exception as e: return { error_count: state.get(error_count, 0) 1, last_error: str(e) }反思节点的职责是检查到目前为止的结果是否满足要求如果不满足给出修正建议。def reflect_node(state: DeepAgentState) - dict: 反思当前进展 llm get_llm() prompt f原始任务{state[user_query]} 已完成的步骤和结果 {format_step_results(state.get(step_results, []))} 请评估这些结果是否足以回答原始任务 如果足够返回 COMPLETE。 如果不够指出缺少什么并给出下一步建议。 response llm.invoke(prompt) reflection response.content if COMPLETE in reflection.upper(): return {reflection: reflection, is_complete: True} else: # 把反思结果作为新的步骤加入计划 new_plan state[plan] [reflection] return {reflection: reflection, plan: new_plan}这两个节点的关键区别在于执行节点关注做反思节点关注检查。分开之后每个节点的逻辑都很清晰调试的时候也容易定位问题。4.3 组装图条件边怎么写才不出错节点定义好了接下来把它们组装成图。这一步的关键是条件边的设计。from langgraph.graph import StateGraph, END def build_deep_agent(): graph StateGraph(DeepAgentState) # 添加节点 graph.add_node(plan, planning_node) graph.add_node(execute, execute_node) graph.add_node(reflect, reflect_node) graph.add_node(handle_failure, handle_failure_node) # 设置入口 graph.set_entry_point(plan) # 规划后进入执行 graph.add_edge(plan, execute) # 执行后的条件分支 graph.add_conditional_edges( execute, route_after_execute, { continue: execute, # 还有步骤继续执行 reflect: reflect, # 步骤完成进入反思 fail: handle_failure # 错误太多处理失败 } ) # 反思后的条件分支 graph.add_conditional_edges( reflect, route_after_reflect, { complete: END, # 任务完成 continue: execute, # 需要继续 fail: handle_failure # 错误太多 } ) # 失败处理节点结束 graph.add_edge(handle_failure, END) return graph.compile()条件边函数route_after_execute和route_after_reflect的实现def route_after_execute(state: DeepAgentState) - str: # 错误检查 if state.get(error_count, 0) 3: return fail # 步骤检查 if state.get(current_step, 0) len(state.get(plan, [])): return continue # 步骤都完成了进入反思 return reflect def route_after_reflect(state: DeepAgentState) - str: if state.get(error_count, 0) 3: return fail if state.get(is_complete, False): return complete # 检查是否超过最大步数 if len(state.get(plan, [])) state.get(max_steps, 20): return fail return continue这里有几个细节值得注意。route_after_execute里先检查错误再检查步骤最后才进入反思。这个顺序很重要因为如果错误太多继续执行只会浪费资源。route_after_reflect里加了最大步数检查防止反思节点不断往计划里加新步骤导致无限循环。注意条件边函数的返回值必须是字典里定义的键不能是节点名本身。这是一个容易搞混的地方。字典的键是条件边函数的返回值值是目标节点名。4.4 运行与调试怎么知道每一步在干什么图组装好之后运行起来很简单app build_deep_agent() result app.invoke({ user_query: 帮我分析一下最近三个月的销售数据找出增长最快的产品类别, plan: [], current_step: 0, step_results: [], is_complete: False, error_count: 0, max_steps: 20 }) print(result[step_results])但调试的时候光看最终结果是不够的。你需要知道每一步的输入输出是什么。LangGraph 提供了流式执行的能力可以逐步查看状态变化for event in app.stream(initial_state): for node_name, node_output in event.items(): print(f节点{node_name}) print(f输出{node_output}) print(---)这样你就能看到每个节点执行后的状态更新。如果某个节点的输出不符合预期就能快速定位。另外LangGraph 支持把执行过程可视化。虽然这里不能画图但你可以用app.get_graph().draw_mermaid()生成一个 Mermaid 格式的图描述然后粘贴到支持 Mermaid 的工具里查看。这个功能在调试复杂图的时候很有用。4.5 参数调优几个影响效果的关键参数在实际运行中有几个参数对效果影响很大值得单独调。第一个是最大步数max_steps。设得太小复杂任务做不完设得太大失败的任务会浪费很多资源。我的经验是根据任务的平均复杂度来设一般 15-25 之间比较合适。如果任务特别复杂可以设到 50但要配合超时机制。第二个是错误重试阈值。我在条件边里用的是 3 次这个数字不是固定的。如果工具调用容易失败比如依赖外部 API可以设高一点如果错误通常是逻辑问题设低一点更好避免浪费时间。第三个是反思的触发频率。我现在的设计是每完成所有步骤后反思一次。但有些任务适合每完成一个步骤就反思一次这样能更早发现问题。代价是模型调用次数增加成本上升。你可以根据任务的重要性和预算来权衡。第四个是模型的温度参数。规划节点和反思节点建议用较低的温度0.1-0.3保证输出的稳定性执行节点可以稍微高一点0.3-0.7保留一些灵活性。这个不是绝对的需要根据实际效果调整。5. 常见问题与排查技巧实录5.1 图跑不起来先检查这几个地方问题一节点名拼写错误。这是最常见的错误。条件边函数返回的字符串必须和add_node时用的名字完全一致包括大小写。我建议把节点名定义成常量比如NODE_PLAN plan然后在所有地方引用常量避免手写字符串。问题二状态字段类型不匹配。如果状态定义里current_step是int但某个节点返回了字符串会在运行时才报错。解决办法是在节点函数上加类型注解并用 mypy 之类的工具做静态检查。问题三忘记设置入口点。set_entry_point必须调用否则图不知道从哪里开始。这个错误比较明显但新手容易忘。问题四条件边没有覆盖所有情况。如果条件边函数在某些状态下返回了未定义的键图会报错。解决办法是在条件边函数里加一个兜底分支或者用assert确保返回值在预期范围内。5.2 图跑起来了但结果不对排查思路症状一Agent 陷入循环。表现为图一直执行不结束或者反复执行同样的步骤。原因通常是条件边逻辑有问题或者反思节点不断往计划里加新步骤。排查方法是打印每次循环的状态看current_step和plan的变化。解决办法是加最大步数限制并检查反思节点的逻辑。症状二工具调用失败。表现为执行节点报错或者工具返回空结果。原因可能是工具描述不清楚导致模型传错参数或者工具本身有问题。排查方法是单独测试工具函数确认它能正常工作然后检查模型生成的工具调用参数是否符合预期。症状三反思节点不生效。表现为任务明明没完成但反思节点返回了 COMPLETE。原因可能是反思提示词写得不够严格或者模型能力不足。解决办法是优化提示词明确列出完成的标准如果模型能力有限可以考虑用更强的模型来做反思。症状四状态字段被意外覆盖。表现为某个字段的值在某个节点后突然变了。原因通常是节点返回了不该返回的字段或者 reducer 配置有问题。排查方法是检查每个节点的返回值确保只返回要更新的字段。5.3 常见问题速查表问题现象可能原因排查方法解决方案图不执行未设置入口点检查set_entry_point添加入口点设置节点名报错拼写不一致对比节点名和条件边返回值用常量管理节点名无限循环条件边逻辑错误打印状态变化加最大步数限制工具调用失败描述不清或参数错误单独测试工具优化工具描述反思不生效提示词不严格检查反思输出优化提示词或用更强模型状态被覆盖节点返回多余字段检查节点返回值只返回要更新的字段类型错误状态定义与返回值不符加类型注解用静态检查工具执行超时任务太复杂或模型太慢查看日志拆分任务或换模型5.4 几个我踩过的坑和对应的技巧坑一把模型实例放在节点函数里创建。一开始我每个节点都ChatOpenAI(...)一下结果发现每次调用都要重新建立连接速度很慢。后来改成用模块级的单例或者通过config传递速度快了不少。坑二状态里存了不可序列化的对象。LangGraph 在某些场景下需要序列化状态比如做检查点如果状态里有数据库连接、文件句柄这类对象会报错。解决办法是只存必要的数据把连接管理放在节点外部。坑三条件边函数里有副作用。我一开始在条件边函数里做了一些日志记录和状态更新结果发现这些操作会被执行多次因为条件边可能被多次调用。后来把所有副作用都移到节点里条件边只做纯判断。坑四忽略了 token 消耗。Deep Agents 的反思循环会显著增加模型调用次数token 消耗比普通 Agent 高不少。我建议在开发阶段就加上 token 统计了解每个节点的消耗情况避免上线后成本失控。坑五没有做超时控制。如果某个工具调用卡住了整个图会一直等。解决办法是给工具调用加超时超时后当作错误处理触发重试或失败分支。提示在开发阶段建议把每个节点的输入输出都打到日志里包括完整的提示词和模型返回。这样出问题的时候能快速定位。上线后可以降低日志级别但关键节点还是要保留日志。6. 一些关于扩展和优化的个人体会这套东西搭起来之后能做的事情比我想象的多。我后来在它基础上加了几个扩展效果不错这里简单说一下思路。扩展一多 Agent 协作。把执行节点拆成多个专门的 Agent比如一个负责搜索、一个负责计算、一个负责写作用一个协调节点来分配任务。LangGraph 的图结构天然支持这种模式每个 Agent 是一个子图协调节点根据任务类型路由到不同的子图。扩展二人工审核中断点。用 LangGraph 的interrupt机制在关键步骤前暂停等人工确认后再继续。这在需要高准确率的场景下很有用比如涉及金额计算或者对外发送内容的时候。扩展三持久化和恢复。LangGraph 支持检查点机制可以把图的状态存到数据库里中断后从上次的状态恢复。这对于长时间运行的任务很有价值避免因为意外中断而从头开始。扩展四动态工具加载。根据任务类型动态加载不同的工具集而不是一开始就把所有工具都绑给模型。这样能减少模型的困惑提高工具选择的准确率。这些扩展每一个都值得单独写一篇这里只是提一下方向。如果你已经把基础版本跑通了可以挑一个感兴趣的试试。最后分享一个我在调优过程中总结的小技巧不要试图一次性把所有节点都做到完美。先把最简单的版本跑通确认流程没问题然后逐个节点优化。每次只改一个地方改完就测试确认有效再改下一个。这样虽然看起来慢但实际上比一次性大改要快得多因为出问题的时候容易定位。另外模型的选择对效果影响很大。同样的图结构换个模型可能效果差很多。如果发现某个节点效果不好先别急着改逻辑试试换个模型有时候会有惊喜。

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

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

免费获取报价 →
↑