1. 先搞清楚一件事智能体开发为什么必须谈“模式”做智能体开发有一段时间的朋友基本都会撞上同一个尴尬期Demo 跑得飞起产品上就翻车。对话机器人聊三五轮开始复读带工具调用的 Agent 该调 API 的时候突然开始编参数多智能体协作更是灾难现场两个角色互相甩锅、来回踢皮球。我自己也经历过这个阶段后来读到一本讲 LangGraph 智能体设计模式的书才意识到问题不出在模型能力也不出在框架本身而是整个开发思路还停留在“写函数”的层面没有上升到“设计模式”的层面。这本书的核心观点其实很朴素智能体的行为不可控主要不是因为模型乱说话而是因为开发者把控制逻辑全部交给了模型自由发挥。设计模式要解决的恰恰是“怎么在保留模型灵活性的同时把流程边界画清楚”。比如让模型在有限选项里做路由决策而不是从零开始写一段行动方案让模型先生成计划再逐步执行而不是一口气输出全部步骤让一个评审模型去检查生成模型的结果而不是指望单一模型又当选手又当裁判。这里说的模式和传统软件开发里的设计模式是一回事就是被反复验证过的、能解决特定场景问题的通用骨架。你不需要每次从零设计控制流直接套用这些骨架再根据业务场景调整节点细节就行。对于已经写过几个简单 Agent、想往更高阶方向走的开发者来说这本书提供了一条从“能跑”到“稳定”的路径。下面我会把书里最核心的几个模式拆开讲再用 LangGraph 的代码演示怎么落地最后附上我实际踩坑的排错记录。2. 几个值得长期记住的智能体设计模式2.1 反思模式让模型自己检查自己反思模式Reflection是书里最容易被低估、但性价比最高的一个模式。思路不复杂一个生成模型负责产出答案另一个评审模型负责检查答案发现问题就退回重写。传统做法是只靠提示词让同一个模型“再想想”但实测下来效果很不稳定模型经常会说“这个答案已经很好了”然后原样输出。反思模式的实现要点是“分工”。生成器和评审器用同一个底层模型没问题但提示词必须完全分开。评审器的任务不是产出内容而是找毛病逻辑是否自洽、信息是否过时、格式是否合规、有没有幻觉。在 LangGraph 里实现可以设计成一个带条件的循环生成节点 - 评审节点 - 如果评审不通过就回到生成节点达到最大迭代次数才强制退出。from typing import TypedDict, Literal from langgraph.graph import StateGraph, END class AgentState(TypedDict): task: str draft: str feedback: str iterations: int def generate(state: AgentState) - dict: # 调用模型生成初稿这里省略具体调用代码 draft call_llm(f根据任务生成答案{state[task]}) return {draft: draft, iterations: state[iterations] 1} def reflect(state: AgentState) - dict: # 调用模型检查初稿返回结构化意见 feedback call_llm(f检查以下答案的问题{state[draft]}) return {feedback: feedback} def should_continue(state: AgentState) - Literal[generate, done]: if 没问题 in state[feedback] or state[iterations] 3: return done return generate graph StateGraph(AgentState) graph.add_node(generate, generate) graph.add_node(reflect, reflect) graph.set_entry_point(generate) graph.add_edge(generate, reflect) graph.add_conditional_edges(reflect, should_continue) graph.add_edge(done, END)这里有个容易忽略的细节迭代次数上限。没有这个上限模型可能会陷入无限循环尤其是面对开放性问题时评审模型总能找到新的“改进空间”。我习惯把上限设在 2 到 3 次超过就直接返回当前结果。实践表明多数情况下第二次生成就已经明显变好第三次基本没有质变反而会增加延迟和成本。这个模式最适合内容生成类场景比如报告撰写、代码生成、文案创作。如果你做的是信息抽取或分类任务反思带来的收益会小很多因为这类任务的正确性更多取决于输入质量和提示词而不是生成后的自我纠错。2.2 计划-执行模式先把任务拆开再一个个解决计划-执行模式Plan-and-Execute的思路是模仿人类处理复杂任务的方式先整体规划再分步执行。对比传统的单步 ReAct 模式推理一步、行动一步、观察一步循环往复计划-执行模式最大的区别是“先制定完整计划再开始执行”。你可以把它理解成装修房子靠谱的包工头会先出一份施工方案告诉你先拆墙、再改水电、然后贴砖而不是边做边想下一步。这个模式在处理多步骤、多工具调用的任务时优势特别明显。ReAct 模式每一步都要切换推理和行动上下文会不断膨胀模型容易在长任务中忘记最初的意图。而计划-执行模式把“规划”和“执行”拆开规划节点一次性把子任务拆好执行阶段每个子任务都对着明确的输入输出上下文只保留当前子任务相关的内容大幅降低信息干扰。from langgraph.graph import StateGraph, END class PlanState(TypedDict): task: str plan: list[str] results: dict current_step: int def planner(state: PlanState) - dict: plan call_llm(f把任务拆分成子步骤每步一句话{state[task]}) return {plan: plan, current_step: 0} def executor(state: PlanState) - dict: step state[plan][state[current_step]] result execute_step(step) # 可能是调用工具、查库或简单生成 return {results: {step: result}, current_step: state[current_step] 1} def route(state: PlanState): if state[current_step] len(state[plan]): return done return executor计划-执行模式要注意“计划”和“执行”之间的信息传递。很多第一次实现的开发者会把所有子任务的输入都塞在 state 里结果 state 越来越大性能不升反降。我的做法是每个子任务只保留任务描述、输入引用、输出结果三个字段执行完就扔整个 state 的体量保持平稳。这个模式也有缺点如果计划本身就错了后面执行再完美也没有意义。所以需要给规划器增加一个校验步骤或者在执行途中允许重新规划。书中建议的是“验证点”机制每执行 2 到 3 个子任务就让规划器看一眼当前进度判断是否需要调整后续步骤。这相当于把纯一次性的计划变成了有反馈的滚动计划兼顾效率和灵活性。2.3 路由模式把选择权交给模型但选项由你定路由模式Router是我在实际项目中用得最多的模式也是 LangGraph 里条件边最典型的应用场景。它的想法很简单与其让模型从一个巨大的系统提示词里自己找路径不如把它当作一个“分诊台”只负责判断当前请求属于哪个类别然后交给对应的专门处理流程。举个例子我在某个公司做过一个内部知识库问答系统。用户的输入五花八门有的在问规章制度有的在查项目文档有的只是想闲聊。如果只用一个带 RAG 的通用 Agent结果就是每类问题都处理得一般。用路由模式后入口处一个轻量模型做分类三类问题分别走三个不同的处理节点制度咨询直接查结构化知识库答案要求严格引用条款编号。文档检索走向量检索答案需要标注出处。闲聊走轻量对话不调用任何检索工具。这样每个节点只需要面对一种类型的问题提示词可以写得很窄、很具体。模型在一个小范围内的判断准确率远高于在一个大范围内的自由发挥。路由决策本身也用结构化输出让模型返回一个固定的分类标签而不是自然语言这样后续的条件判断就非常稳定。from typing import Literal from langgraph.graph import StateGraph, END class RouterState(TypedDict): query: str intent: Literal[policy, doc_search, chat] def intent_classifier(state: RouterState) - dict: intent call_llm( f判断用户意图只返回 policy / doc_search / chat 之一{state[query]} ) return {intent: intent.strip()} def route_by_intent(state: RouterState): return state[intent] graph StateGraph(RouterState) graph.add_node(classifier, intent_classifier) graph.add_node(policy_handler, handle_policy) graph.add_node(doc_handler, handle_doc_search) graph.add_node(chat_handler, handle_chat) graph.set_entry_point(classifier) graph.add_conditional_edges(classifier, route_by_intent, { policy: policy_handler, doc_search: doc_handler, chat: chat_handler, })路由模式的关键技巧在于“选项数量”。经验值是分类别超过 6 个时模型的判断准确率会明显下降这时候就要做两级路由先分大类再在大类里分小类。还有一个小经验路由用的模型可以不和主流程用同一个分类任务对模型能力要求低用便宜快速的模型就行能省不少成本。2.4 多智能体协作模式主管分配任务工人专注执行书里最后一部分讲多智能体协作核心模式是 Supervisor主管加 Worker工人。主管节点的职责是理解用户请求、拆解任务、把任务派给合适的 Worker然后汇总结果。Worker 节点各自负责一个领域互不干扰。和前面说的路由模式不同多智能体的任务不是简单分类而是需要多个 Worker 协作完成同一个目标。多智能体模式最忌讳的是“两个 Worker 直接互相聊天”。很多人一拿到 LangGraph 就兴奋地设计成 Agent A 生成结果给 Agent B 审核Agent B 退回给 Agent A 修改再让 Agent C 补充意见。这种设计听起来很聪明跑起来就是灾难每个模型都在自己的上下文里理解对方的输出经过两三轮传递后信息严重失真还极易陷入循环。正确的姿势是引入一个主管做中枢。所有信息都通过主管中转Worker 之间不直接通信。主管拥有全局视角能看到每个 Worker 的输入输出做出下一步决策。LangGraph 对这个模式的支持很到位它允许节点之间通过共享状态协调天然就是 Supervisor Workers 的形状。from langgraph.graph import StateGraph, END class TeamState(TypedDict): request: str subtasks: list[dict] completed: dict final_answer: str def supervisor(state: TeamState) - dict: # 分配子任务或判断是否全部完成 pass def researcher_worker(state: TeamState) - dict: # 执行研究类任务 pass def writer_worker(state: TeamState) - dict: # 执行写作类任务 pass graph StateGraph(TeamState) graph.add_node(supervisor, supervisor) graph.add_node(researcher, researcher_worker) graph.add_node(writer, writer_worker) graph.add_edge(supervisor, researcher) graph.add_edge(supervisor, writer)用了多智能体模式后最大的感受是能用单智能体解决的事情不要轻易上多智能体。多一个节点就多一次模型调用链路过长时延迟和成本都会显著上升。书里的建议是单个任务复杂度不高、只需要一种专业能力时老老实实用单 Agent。只有当任务需要两种以上截然不同的领域知识且各自需要独立上下文时才值得引入多智能体协作。3. 结合 LangGraph 实操落地的几个关键要点3.1 把状态设计当成数据库设计LangGraph 的核心抽象是 StateGraph所有节点都是接收 state、返回部分 state 的函数。这意味着整个智能体共享一个状态对象它的结构直接决定了系统能做什么、容易出什么问题。很多初学者的状态设计就是几个字典字段随手放等到流程复杂起来就开始痛苦字段命名混乱、类型不一致、节点之间误覆盖。状态设计应该像设计数据库表一样认真。每个字段要有清晰的语义谁写入、谁读取、什么类型、是否允许为空都得提前定义。LangGraph 里有一个机制叫 reducer可以用来控制字段的更新方式。默认情况下返回的字段值会直接覆盖旧值。但对对话历史这种需要累积追加的字段就得用 reducers 定义追加逻辑。from typing import Annotated, TypedDict from operator import add class GoodState(TypedDict): query: str # 用户输入只写一次 messages: Annotated[list, add] # 对话历史追加累积 tool_results: dict # 工具调用结果按工具名存储 retry_count: int # 重试次数覆盖更新这个设计有个特别实用的好处可观测性。每个节点只需要声明自己消费哪些字段、生产哪些字段整个数据流一目了然。调试的时候打开 state 快照就知道哪个节点往里面塞了不该塞的东西。我个人还会给每个节点加上运行日志输出它会修改的字段名这个习惯在排查问题的时候帮了大忙。3.2 节点写得“窄”图才能“宽”节点设计是 LangGraph 项目里最容易被低估的环节。一个常见的反模式是“万能节点”把所有逻辑塞进一个节点比如先调用 LLM再解析输出再查数据库再调用工具最后拼装答案全写在一个函数里。这种写法表面上简单实际上是放弃了一个关键能力条件路由。如果节点拆得足够细你可以把每个环节单独拿出来做分支。比如“LLM 生成”和“结果校验”分开就能在校验失败时直接走重试分支而不是整个流程重来。“调用工具”和“解析结果”分开就能在工具报错时走异常处理分支而不会污染主流程。窄节点还有一个好处是可测试性。每个节点都像一个小函数输入输出明确可以直接单测。我在做模拟项目 X 的时候把整个流程拆成了 8 个节点每个节点的测试成本都极低改一个节点不会影响其他节点。对比之前一个大型单体节点的写法回归测试的工作量至少少了一半。3.3 记忆与长会话的几种处理思路长会话和多轮对话是智能体开发绕不开的坑。模型自带的上下文窗口再大也经不住几轮长对话的消耗。书里介绍了三种记忆方案短期上下文记忆、滚动窗口记忆、摘要记忆。实际项目中通常不是单选而是组合使用。滚动窗口是最容易实现的只保留最近 N 轮对话超出部分直接丢弃。适合对历史信息不敏感的场景。摘要记忆则是把更早的对话压缩成一段话保留关键信息适合需要长期记忆但上下文预算有限的场景。我实践下来最稳的组合是最近 5 轮完整保留 更早内容压缩成结构化摘要。这个方案平衡了即时信息和历史脉络延迟和成本都可控。LangGraph 里实现记忆可以直接把记忆内容放在状态字段里也可以借助持久化层把每个会话的历史记录存到外部存储。后者的好处是服务重启后会话不丢适合生产环境。但要注意持久化不是免费的会带来额外的序列化和 IO 开销。如果只是一个简单的测试 Demo直接用内存状态就够了。3.4 循环必须有终止条件给流程装个保险丝LangGraph 的图结构支持循环这既是优势也是风险。反思模式要循环、计划-执行模式可能要循环、多智能体协作要循环但如果终止条件写得不严谨就会遇到请求卡死、成本飙升的情况。所有循环边都必须配上条件判断而且条件判断里必须有“硬退出”的兜底逻辑。常见的做法是给迭代次数设上限或者给总耗时设上限两者取先到者。我习惯把最大迭代次数写在图的配置里而不是代码里这样调整参数不需要改逻辑。另外条件判断函数要设计成纯函数只依赖 state 里的字段不要依赖外部变量否则调试时会非常痛苦。4. 实测心得照着模式搭一个多轮对话智能体4.1 场景选型和图结构设计前面讲了不少理论这里我给一个完整的实战案例。当时我们的场景是做一个小型法律咨询助手用户会问不同类型的法律问题需要结合本地法规库做检索回答同时要能处理追问、澄清这类复杂对话。我没有直接写一个巨大的“对话机器人”而是按照设计模式拆成了四条链路意图识别链路判断用户是全新建问、追问澄清还是闲聊。检索问答链路处理新建问题先检索法规库再生成答案。澄清追问链路当用户信息不足时先提问澄清不急着检索。兜底链路超出范围的问题直接说明不支持并引导回正轨。图结构上入口是一个路由节点后面接三个独立子图。这个设计的来源就是 2.3 节说的路由模式。搭建完之后最大的体会是以后新增一种问题类型不需要动其他链路只要加一个节点、改一下路由表就行。4.2 核心节点代码级讲解这里用最核心的“检索问答链路”来演示。这条链路要完成三件事生成检索词、检索法规库、生成最终答案。我拆成了三个窄节点每个节点职责非常清晰。class QAState(TypedDict): question: str search_query: str retrieved_docs: list[str] answer: str def build_search_query(state: QAState) - dict: # 根据用户问题生成适合检索的关键词 search_query call_llm(f把问题改写成检索关键词直接输出关键词不要解释{state[question]}) return {search_query: search_query} def retrieve(state: QAState) - dict: # 使用关键词检索法规库拿到相关条文 docs vector_db.search(state[search_query], top_k5) return {retrieved_docs: docs} def generate_answer(state: QAState) - dict: # 仅基于检索到的条文生成答案要求标注条款来源 answer call_llm( 你是法律咨询助手。只能根据给定条文回答不得自行编造。\n f条文{state[retrieved_docs]}\n f问题{state[question]} ) return {answer: answer}三个节点串成一条直线build_search_query - retrieve - generate_answer。这个链路里的关键点在于检索和生成是两个完全不同的子任务分开后可以单独优化。我后来把检索词生成模型换成了小型快速模型把答案生成模型保留了高配的大模型成本和效果都达到了最佳平衡。4.3 测试视角怎么快速验证链路是否健康链路搭好后我习惯先用“模拟数据”而不是真实模型调用来做初步验证。把每个节点的输入输出人工指定好确认图的结构和状态传递没有 bug再接真实模型。这一步能省掉大量的调试时间因为绝大多数结构性问题比如字段写错、分支条件漏了根本不涉及模型调用。接上真实模型后我通常准备一组覆盖正常流程、边界流程、异常流程的测试问题。正常流程只要一个常见问题就行边界流程要覆盖空输入、超长输入、模糊提问异常流程要覆盖检索不到结果、工具返回超时、模型返回空值。这些问题每个链路跑一遍基本就能暴露 80% 的稳定性问题。书里也提到一个观点智能体测试的重点不是“正确率”而是“失败时的表现”。这一点我非常认同——模型输出从“好”到“特别好”很难量化但“崩溃”和“不崩溃”是明确的。5. 常见问题与排错速查5.1 高频问题对照表这几个月照着设计模式重构了几个项目把最常遇到的问题整理成了速查表给正在调试的朋友一个参考现象可能原因排查方法解决方案节点执行了但状态没变化返回的 key 写错和 state 定义不一致打印节点返回的 dict 做对比统一字段命名用常量定义 key图执行到最后没有输出缺少终点节点或条件分支没有指向 END检查所有条件边的返回值给每个分支都画清楚终点对话几轮后模型开始重复状态里塞了完整历史上下文超限查看每次调用的实际 token 数切换滚动窗口或摘要记忆工具调用结果格式乱工具返回的原始内容直接进了提示词查看工具返回前后状态快照加一个解析节点清洗数据循环一直不退出条件判断没有覆盖所有情况给迭代计数加日志设置最大迭代次数硬上限路由分类经常分错类别太模糊存在交叉语义打印路由输出看误判情况增加示例样本缩小分类范围5.2 几个额外的实战提醒第一别把大段历史塞进工具调用的上下文。有一次我们的 Agent 在调用数据库工具时把整个对话历史都拼进了查询参数结果工具没被调用成功反而白白消耗了大量 token。工具调用的输入应当只包含必要的信息最好是当前步骤所需的最小字段集合。第二异常要显式地装进状态里不要靠日志。LangGraph 的节点一旦抛异常整个图的执行就会中断状态数据全部丢失。更稳妥的做法是在节点内部捕获异常把错误信息写进 state 的 error 字段然后走条件分支去重试或兜底。这样图始终在控制之下不会裸奔崩溃。第三评估智能体系统不要只看端到端的效果要分层评估。把路由准确性、检索召回率、答案引用正确率分开测哪个环节弱就优化哪个环节。端到端指标只能告诉你“坏了”分层指标才能告诉你“坏在哪”。5.3 关于框架版本的两点注意LangGraph 本身还在快速迭代不同版本的 API 差异不能忽视。我早期照着旧版本文档写的一个项目升级版本后很多边和节点的定义方式都变了花了大半天才迁移过来。建议做法是锁定版本并在项目里记录当时使用的 API 形态避免文档和实际代码对不上。另一个注意点不是所有东西都需要画成图。如果你的流程只有一个线性调用没有分支、没有循环直接用简单代码链式调用就好没有必要引入图框架增加复杂度。之所以用 LangGraph是因为它管理复杂状态流的能力简单场景强行用框架反而是自找麻烦。6. 读完整本书我最大的改变是什么要说整本书给我留下的最有价值的习惯就是“先画图再写代码”。以前做智能体我习惯直接写 prompt写完后发现控制流混乱了再回头补逻辑。现在我会先拿白板把状态字段、节点、边、条件分支画出来确认每一条路径都有终点再开始写第一个节点函数。这个习惯让开发的返工率低了非常多。另外一个体会是设计模式不是数学公式不是照着套就万事大吉。同一个模式在不同场景下要做的调整很多比如反思模式的迭代次数、路由模式的分类粒度、多智能体中主管的决策频率都需要根据实际数据反复调优。书里给的是骨架真正的血和肉要靠自己的场景来填充。如果你现在正处于“能写智能体但经常失控”的阶段可以先把这几种模式在独立的 Demo 上试一遍感受一下不同控制流对行为稳定性的影响。等你真正把路由、反思、计划-执行都过了一遍就会对“智能体架构设计”这个说法建立自己的判断力。到那时候再回去看那些“智能体翻车现场”的帖子你会发现大部分问题都能在架构层面提前避免。