LangGraph:状态机与循环
如果你做过稍微复杂一点的 Agent,很快就会遇到一个现实问题:流程根本不是线性的。
它通常更像这样:
- 读资料
- 调工具
- 发现信息不够
- 再查一次
- 结果不对
- 回退重试
这也是为什么很多人用纯链式方式搭 Agent,最后会越来越难维护。LangGraph 之所以有吸引力,不是因为它“更高级”,而是因为它把循环、分支和状态显式地拉到了台面上。
我自己会把 LangGraph 看成一个很典型的“第二阶段工具”。第一阶段你只是想把 Agent 跑起来,很多东西还可以靠 prompt 和几个函数硬接;第二阶段你开始反复遇到重试、分支、回退、断点,这时候图式编排才会真的变顺手。
Turn this chapter into hands-on prompt practice
Use the interactive lab to practice real prompt tasks in minutes.
#1. 核心概念:StateGraph
LangGraph 的核心思路可以概括成一句话:
把 Agent 当成一个有状态的流程图,而不是一串 prompt。
它把三个东西讲得很清楚:
- 当前状态是什么
- 下一步该跑哪个节点
- 什么情况下继续、回退或者结束
stateDiagram-v2 [*] --> LLM : 输入 LLM --> Tool : 调用工具 Tool --> LLM : 返回结果 state LLM { [*] --> Thinking Thinking --> Deciding } state Tool { API_Call --> Parse_Result } LLM --> End : 完成任务 Tool --> Error : 异常 Error --> LLM : 重试 (Loop)
#State(状态)
状态就是整个流程共享的那份“任务现场”。所有节点都围绕它工作。
一个好的状态对象,通常不会什么都塞,而是只放当前流程真正需要反复读写的部分,比如消息、错误信息、阶段结果、迭代次数。
我通常不建议把整个世界都塞进 state。state 一旦变成“什么都记一点”的大字典,后面就很容易出现另外一种问题:流程看起来有状态,实际没人知道哪几个字段才是关键字段。
pythonclass AgentState(TypedDict): messages: list[BaseMessage] current_code: str error_logs: str iterations: int
#Nodes(节点)
节点负责做事。它可以是:
- 调一次 LLM
- 调一个工具
- 做格式校验
- 跑测试
- 决定是否需要人工审批
重点不是节点名字,而是节点职责要尽量单一。不然一旦出了问题,你很难知道是推理错了、工具错了,还是状态污染了。
这也是很多人第一次把 Agent 做复杂以后才意识到的问题。流程一长,如果一个节点同时做“推理 + 调工具 + 处理错误 + 更新状态”,最后 trace 看起来会非常难读。你知道它错了,但不容易知道它到底错在哪一步。
#Edges(边)
边定义流程怎么走。
- 普通边:执行完 A 直接去 B
- 条件边:根据状态决定去 B、C,还是回到 A
很多 Agent 的“智能感”,其实就体现在条件边上。它让系统不只是往前冲,而是能根据结果改路线。
如果换成更贴近真实项目的说法,条件边其实回答的是几个很朴素的问题:
- 现在该继续还是该停
- 这次失败值不值得重试
- 是回上一层,还是换一条路径
很多“看起来像智能体”的行为,底层其实就是这些判断被写清楚了。
#2. 一个最典型的例子:写代码,检查,不行就重来
假设你要做一个简单的 coding agent,流程可能就是:
- 先写代码
- 再检查
- 如果失败,回去重写
- 如果通过,结束
这正是 LangGraph 适合处理的场景。
如果让我用一句更直接的话来概括:只要你已经确定“失败以后不是直接报错退出,而是还要继续判断下一步”,LangGraph 这类思路就很值得看。
graph LR Start((Start)) --> Write[编写代码] Write --> Check[运行检查] Check -->|有错误| Write Check -->|通过| End((End))
pythonfrom langgraph.graph import StateGraph, END # 1. 定义状态 class State(TypedDict): code: str error: str # 2. 定义节点函数 def write_code(state): # 调用 LLM 生成代码 return {"code": "print('hello')"} def check_code(state): # 模拟运行检查 if "error" in state["code"]: return {"error": "Syntax Error"} return {"error": ""} # 3. 定义条件逻辑 def router(state): if state["error"]: return "write_code" # 有错,循环回去重写 return "end" # 没错,结束 # 4. 构建图 workflow = StateGraph(State) workflow.add_node("write_code", write_code) workflow.add_node("check_code", check_code) workflow.set_entry_point("write_code") workflow.add_edge("write_code", "check_code") workflow.add_conditional_edges( "check_code", router, {"write_code": "write_code", "end": END} ) app = workflow.compile()
这个例子虽然小,但它已经比“单次 prompt 生成代码”更接近真实工程。因为真实 coding agent 很少一次就成,通常都是:
- 先写
- 再跑
- 报错
- 回看日志
- 再改
一旦流程里天然带循环,图模型就会比线性链更顺手。
#3. Human-in-the-loop:生产环境里这一步非常关键
Agent 并不值得无条件信任。越接近真实资源,越需要人为介入。
比如下面这些动作,很多团队都会要求显式审批:
- 发邮件给客户
- 改生产配置
- 提交数据库写操作
- 部署代码
LangGraph 的一个现实价值,就是它比较适合在关键节点停下来,等人检查状态,再决定继续。
这种断点能力很重要,因为真正上线以后,系统不是“能自动跑就行”,而是要“自动跑的时候还能被接管”。
这一点对 Google 索引本身没有直接帮助,但对内容质量有帮助。因为很多真正有经验的工程文章,都会强调“哪里必须交回给人”,而不是把自动化写成万能答案。这样的内容通常也更像真人经验总结,而不是概念拼装。
这也是我自己很看重的一点。凡是把 Agent 写成“全自动就能解决一切”的内容,短期也许吸引眼球,但长期质量通常不高,读者也很难真正拿去落地。
#4. 为什么很多复杂 Agent 会选 LangGraph
| 特性 | 传统链式编排 | LangGraph |
|---|---|---|
| 控制流 | 偏线性 | 明确支持分支和循环 |
| 状态管理 | 容易散落在各处 | 状态集中定义 |
| 失败恢复 | 处理起来别扭 | 更容易设计重试和回退 |
| 长流程维护 | 规模一大就容易乱 | 更适合复杂流程 |
如果你在搜“LangGraph 适合什么场景”或者“LangGraph 和 LangChain 区别”,其实想找的通常也就是这类判断:什么时候图比链更值得上。
#小结
LangGraph 真正解决的,不是“怎么让 Agent 看起来更聪明”,而是“怎么让它在复杂流程里别失控”。
如果你的 Agent 只是一次问答,没必要上这么重的框架。
我会再说得更直白一点:如果你现在还没遇到循环、回退、状态持久化这些问题,那大概率还没到非用 LangGraph 不可的时候。
但只要你开始遇到下面这些需求:
- 需要循环重试
- 需要持久化状态
- 需要中途人工接管
- 需要明确的分支控制
那 LangGraph 这类状态流思路就会变得很有价值。
如果你的流程还停留在“一问一答 + 最多调一个工具”,那真的不用急着上。LangGraph 更像是你在 Agent 做到第二阶段以后,自然会碰到的答案,而不是所有项目的起点。
所以它更像一把工程化工具,不是一个入门仪式。什么时候开始频繁觉得链式流程很别扭,什么时候再认真上它,通常最合适。