LangGraph 状态设计与节点编排
参考资料
一句话总结
LangGraph 的核心不是“把一个大 Prompt 交给模型”,而是先把任务拆成一组职责明确的节点,再通过共享状态把这些节点串起来。这个过程中最重要的设计原则之一是:State 里保存原始数据,Prompt 在节点内部按需格式化。
从流程出发,而不是从 Prompt 出发
官方文档用“客服邮件处理 Agent”举了一个很典型的例子:一个 Agent 需要读取邮件、识别意图、搜索文档、起草回复、必要时升级给人工,并在最终完成发送。
如果直接把这些步骤塞进一个大模型调用里,虽然看起来简单,但很快会遇到几个问题:
- 每一步依赖的上下文并不相同
- 有些步骤是推理,有些步骤是查数据,有些步骤是执行动作
- 不同节点的错误处理方式不一样
- 中间结果需要保留,方便后续节点继续使用
所以 LangGraph 的思路是:先还原业务流程,再映射成图结构。
图里的每个节点只做一件事,节点之间通过状态传递信息,至于下一步去哪里,则由节点根据当前结果决定。
LangGraph 的五步思考方式
结合文档内容,可以把 LangGraph 的设计过程概括成五步:
1. 先把业务流程拆成离散步骤
每个步骤最终会对应一个节点,例如:
Read Email:读取和解析邮件Classify Intent:识别问题类型、紧急程度和主题Doc Search:检索知识库Bug Track:写入或更新缺陷系统Draft Reply:生成回复草稿Human Review:进入人工审核Send Reply:真正发送邮件
这里的关键点不是“节点越少越好”,而是节点职责要单一。
如果一个节点同时负责分类、检索、起草回复、决定是否人工审核,那它很快就会变成一个不可维护的黑盒。
2. 明确每个步骤到底在做什么
官方文档把节点大致分成四类:
- LLM steps:理解、分析、生成文本、做推理判断
- Data steps:从外部系统或知识库拉取数据
- Action steps:执行发送邮件、创建工单这类外部动作
- User input steps:需要人工介入或补充信息
这种分类很实用,因为它直接决定了节点的设计方式:
- LLM 节点重点关注输入上下文和输出结构
- 数据节点重点关注参数、重试、缓存
- 动作节点重点关注幂等性和失败补偿
- 人工节点重点关注中断与恢复
3. 设计共享状态
State 是整个图的共享记忆。
它的作用不是简单“存点变量”,而是让不同节点都能基于一致的数据视图工作。
文档里给出的判断标准很直接:
- 如果某段信息需要跨步骤保留,就放进 state
- 如果某段信息可以从已有数据推导出来,就不要存,按需计算
以邮件 Agent 为例,适合放进 state 的通常有:
- 原始邮件内容和发件人信息
- 意图分类结果
- 知识库检索结果
- 客户历史信息
- 回复草稿
- 执行过程中的元数据
4. 把每个步骤实现成节点函数
LangGraph 节点本质上就是 Python 函数:读取当前状态,做自己的工作,再返回对状态的更新,必要时决定下一跳。
文档中的设计重点不是“函数怎么写”,而是错误怎么分层处理:
- 瞬时错误:交给系统自动重试
- LLM 可恢复错误:把错误写回 state,让模型重新决策
- 用户可修复错误:通过
interrupt()暂停并等待补充信息 - 未知错误:直接抛出,方便调试
- 重试后仍失败的场景:走补偿或恢复分支
5. 最后再连线成图
很多人第一次接触 LangGraph 时,会把注意力都放在“边怎么画”。
但文档强调的重点恰恰相反:真正的路由决策通常发生在节点内部,而不是边上。
也就是说,图结构可以很简洁:
START -> read_emailread_email -> classify_intentsend_reply -> END
至于分类后是去检索、去建 bug、还是进人工审核,通常通过 Command(update=..., goto=...) 在节点内部决定。
为什么要“State 保存原始数据,Prompt 按需格式化”
这是整篇文档里最值得反复记住的一条原则。
官方建议非常明确:不要把格式化后的 Prompt 文本存进 state,而是把原始数据存进去,真正调用模型时再在节点内部拼 Prompt。
这样设计的价值主要有四点。
1. 同一份数据可以被不同节点以不同方式消费
例如一封用户邮件,分类节点需要的是“意图、紧急程度、主题”;回复生成节点需要的是“问题背景、检索结果、客户历史”;人工审核节点需要的是“原文 + 草稿 + 风险信息”。
如果你在 state 里提前塞了某一种固定格式的 Prompt,其他节点很可能还得重新拆开、重新组织,反而更麻烦。
2. Prompt 模板可以独立演化
Prompt 是经常要调的,但 state schema 往往更稳定。
如果把 Prompt 拼接结果直接存进 state,那么每次改模板,都可能牵动状态结构和兼容逻辑。
反过来,如果 state 只存原始数据,Prompt 只是节点内部的一层视图,那么你可以很自由地优化提示词,而不必频繁改状态定义。
3. 调试会更清晰
调试 Agent 时,一个高频问题是:“模型到底看到了什么数据?”
如果 state 里存的是原始字段,比如 email_content、classification、search_results、customer_history,那排查起来会非常直接。
你能清楚看到:
- 上游节点产出了什么
- 下游节点消费了什么
- 是数据本身有问题,还是 Prompt 组装方式有问题
4. 更有利于长期演进
随着 Agent 变复杂,state 结构通常会越来越像“业务事实层”,而 Prompt 更像“解释层”或“渲染层”。
把两者分开之后,Agent 才更容易:
- 新增节点
- 替换模型
- 重写模板
- 扩展状态字段
- 保持旧状态兼容
一个更合理的 State 应该长什么样
文档中的邮件 Agent 状态设计很有代表性,大致包含三类内容:
1. 原始输入数据
email_contentsender_emailemail_id
这类字段是事实本身,后续节点都会反复用到,必须长期保留。
2. 中间推理或外部查询结果
classificationsearch_resultscustomer_history
这些结果可能来自 LLM,也可能来自外部系统,但都属于后续节点会复用的中间产物。
3. 生成内容与过程产物
draft_responsemessages
例如回复草稿需要经过人工审核,就不能只存在某个函数的局部变量里,而必须进 state。
这里最值得注意的一点是:文档中的 state 存的是结构化结果和原始结果,而不是“已经整理好的提示词文本”。
节点内部如何消费这些原始状态
State 存原始数据,并不意味着 Prompt 会更难写。
正确做法是:在节点内部临时把原始数据格式化成当前任务需要的上下文。
比如:
- 分类节点临时拼出“邮件正文 + 发件人 + 分类要求”
- 检索节点临时拼出“意图 + topic”作为检索 query
- 回复节点临时把检索结果格式化成项目符号,把客户信息转成上下文说明
- 人工审核节点则直接把原始邮件、草稿和风险标签打包给人工
这个思路很像前后端分层:
- state 更像数据库或领域模型
- prompt 更像视图层
一旦接受这个分层,整个 Agent 的可维护性会好很多。
LangGraph 里的错误处理也值得单独学
很多人把 Agent 错误处理理解成“失败了就重试”,但 LangGraph 文档给出的思路更细。
瞬时错误:重试
网络抖动、限流、临时不可用,适合交给 RetryPolicy。
这种错误本质上和业务语义无关,系统层自动处理即可。
LLM 可恢复错误:写回 state 再回环
如果工具调用失败、结构化解析失败,但模型有机会根据错误信息修正自己的动作,就把错误作为上下文的一部分写回 state,再回到上游节点。
这种方式的重点是:不要把所有失败都当成系统异常,有些失败本身就是模型后续推理的输入。
用户可修复错误:interrupt()
如果缺少账号 ID、订单号、确认信息等内容,就不要让模型硬猜,而是中断图执行,等待人工补充后再恢复。
这也是 LangGraph 很适合做业务 Agent 的原因之一:它天然支持“执行到一半停下来,拿到信息后继续跑”。
真正的未知错误:直接抛出
文档特别强调一点:处理不了的异常不要硬吃掉。
未知异常应该暴露出来,让开发者看到真实问题,而不是在图里悄悄吞掉。
对实际开发最有帮助的几个启发
结合这篇文档,我觉得有几条经验特别适合直接带到工程里:
1. 先设计状态,再写 Prompt
很多 Agent 一开始就急着调提示词,但真正决定系统上限的,往往是状态结构是否清晰。
只要 state 设计混乱,后面加节点、调路由、做恢复都很痛苦。
2. 节点函数一定要“单职责”
每个节点只做一类事情:分类、检索、执行动作、人工审核。
职责越单一,状态越稳定,调试也越容易。
3. 把 Prompt 视为“状态的渲染结果”
这是一种非常重要的思维转变。
Prompt 不是主数据,state 才是;Prompt 只是当前节点为了完成任务,对 state 做的一次格式化投影。
4. 路由逻辑尽量由节点返回结果决定
不要把所有分支逻辑都堆在图外部。
如果节点已经产出了结构化结论,比如 intent、urgency,那就让节点直接返回下一跳,更符合 LangGraph 的工作方式。
5. 错误处理要按“谁能修”来分层
不是所有错误都该重试,也不是所有错误都该交给人工。
更合理的做法是先判断:这个错误最适合由系统、模型、用户还是开发者来解决。
总结
Thinking in LangGraph 真正想传达的,不只是“怎么写一个图”,而是怎么用图的方式思考 Agent 系统。
其中最关键的设计原则就是:
- 先拆流程,再建节点
- 用共享 state 连接节点
- state 保存原始数据
- prompt 在节点内部按需格式化
- 把错误处理、人工介入和路由决策都纳入图的设计中
如果把 LangGraph 只当成一个“多步调用框架”,很容易写出能跑但难维护的 Agent。
但如果把它理解成“围绕状态流转来组织推理、动作和人工协作的执行框架”,很多设计选择就会自然清晰起来。