真正难的不是“LangGraph 里有没有 State”,而是:你的数据到底该怎么放,才能既方便模型理解,又方便节点协作和流程控制。
一、 这篇解决什么问题
很多人在第一次写 LangGraph 时,虽然已经知道 State 是全局状态,也知道节点返回的是状态补丁,但一到真正设计字段时,很快就会陷入混乱:
- 用户问题、工具结果、总结文本、路由标记、审批状态,到底是不是都能塞进
messages? - 为什么有些数据放进
messages很顺,但有些数据一放进去,整个图就越来越乱? - 为什么有些字段必须单独拆出来,比如
question_type、need_search、approval_status? messages和结构化字段之间,到底该怎么分工?
这篇文章要回答的核心问题就是:在 LangGraph 里,状态不仅要“能存”,更要“存得合理”。
二、 先给一句最短结论
如果你在设计状态时感到犹豫,请直接用这句话作为判断中轴:
messages更适合存放“要持续喂给模型看的对话与工具交互历史”;独立字段更适合存放“要给代码逻辑、路由判断和节点协作使用的结构化状态”。
进一步解释:
- 如果一段数据的主要用途是让模型继续理解上下文,优先考虑放进
messages。 - 如果一段数据的主要用途是让程序判断流程怎么走,优先考虑拆成独立字段。
三、 为什么“什么都塞进 messages”会出问题
在最开始做小 Demo 时,把所有中间信息都塞进messages看起来很省事。因为模型本来就会读messages,工具结果也常常会回填成ToolMessage,最后总结、判断、路由理由,好像也能当作文本塞进去。
但问题是,一旦流程稍微复杂一点,这种做法会迅速失控。
常见问题:
- 路由判断变脆如果你要判断“是否需要搜索”、“是否需要审批”、“当前是否已完成某步检查”,而这些信息只存在某段自然语言消息里,你就不得不在代码里从一堆文本里“猜”。
- 结构化数据难取像
search_results、approval_status、retry_count、question_type这类状态,如果混进消息文本,后续节点读取和复用会很痛苦。 - Prompt 污染所有信息都塞进消息后,模型上下文会越来越长、越来越杂,真正重要的信息反而容易被淹没。
- 业务状态和对话状态混在一起这会让图的可维护性快速下降。
一句话总结:messages很重要,但它不是万能垃圾桶。
四、 messages 到底适合放什么
最适合放进messages的内容:
- 用户输入:比如用户提问、用户补充说明、用户修改需求。
- 模型输出:比如 AI 回复、模型提出的工具调用请求、模型基于工具结果给出的阶段性总结。
- 工具回执:比如
ToolMessage、搜索结果的文本摘要、计算结果、API 返回的可读说明。 - 需要持续暴露给模型的上下文:如果某段内容后面还需要被模型多次参考,放进
messages通常是合理的。
一句话概括:messages适合承载“对模型可见的交互历史”。
五、 什么时候必须拆成独立字段
这是状态设计的重头戏。适合拆字段的典型情况:
- 用于流程路由比如
need_search、need_approval、is_completed、next_step。这类字段本质上是流程控制信号,应该让代码直接读取,而不是从消息文本里猜。 - 用于跨节点共享的结构化结果比如
search_results、parsed_query、approval_decision、customer_profile。这些数据往往要被多个节点复用,拆成独立字段会更稳定。 - 用于统计、计数、状态机控制比如
retry_count、attempts、status、error_type。这类信息本来就是程序状态,放进messages反而别扭。 - 不适合直接暴露给模型的大体量数据比如原始搜索结果 JSON、数据库记录列表、大段中间结构化结果。这些通常更适合存进独立字段,或者存摘要给模型、原始数据给程序。
一句话总结:结构化字段不是为了“多建几个变量”,而是为了把状态从自然语言里解耦出来。
六、 一个最小反例:把所有东西都塞进 messages
用户问:“今天有哪些值得关注的 AI 新闻?”
如果你把下面这些全塞进messages:
- 问题分类结果
- 是否需要搜索
- 搜索原始返回
- 搜索结果筛选理由
- 是否继续下一轮搜索
- 最终摘要
那么很快就会出现这些问题:路由节点要从文本里判断need_search;总结节点要从一堆消息里硬扒搜索结果;模型每轮都被喂很多本来不必重复看的中间文本。状态既难维护,也难调试。
一句话点评:能跑不等于设计合理。
七、 一个正例:把 messages 和业务字段分开
真正的工程级设计,职责必须清晰分明。我们可以这样设计:
class State(TypedDict): messages: Annotated[list[AnyMessage], add_messages] question_type: str | None need_search: bool search_query: str | None search_results: list[str] | None summary: str | None各字段分工:
messages:保存用户问题、模型回复、工具消息。供模型继续理解上下文。question_type:给路由和节点逻辑判断问题类型。need_search:给条件边直接判断下一步是否进入搜索节点。search_query:给搜索节点用,不必每次都从消息文本里重解析。search_results:给总结节点读的结构化中间产物。summary:给最终回答节点用的中间结论。
八、 一个实用判断口诀
在实战中,你可以直接用这套口诀来拍板状态设计:
- 要给模型反复看->优先考虑放进
messages - 要给代码判断流程->优先拆成独立字段
- 要跨节点稳定传递结构化结果->优先拆成独立字段
- 内容太大、太原始、太脏->不要直接塞进
messages - 既要给模型看,又要给程序判断-> 考虑“双层表示”:给模型一份摘要,给程序一份结构化字段
九、 常见误区
- 误区一:
messages就是 State 的全部。不是。messages很常见,但绝不是全部状态。 - 误区二:字段越少越高级。不是。乱塞进
messages反而会让设计更脆弱。 - 误区三:只要模型能读懂,就不用拆结构化字段。这是典型误区。程序逻辑和模型理解是两套需求。
- 误区四:所有结构化字段都要暴露给模型。也不一定。很多字段本来就只该给路由逻辑和节点代码使用。
十、 总结
在 LangGraph 里,State 的难点从来不只是“怎么存数据”,而是“怎么让数据在模型、节点和流程控制之间分工合理”。
messages适合承载对模型可见的交互历史,独立字段适合承载程序要稳定读取和复用的结构化状态。
一旦你把所有东西都塞进messages,图虽然也许还能跑,但很快就会变得脆弱、难调、难扩展;而一旦你学会把“对话上下文”和“业务状态”拆开,整个图的可维护性会明显上一个台阶。
所以,State 设计的关键不在于“字段越少越好”,而在于:每一份数据都应该出现在最适合它的位置。