LangGraph:状态机与循环
如果你做过稍微复杂一点的 Agent,很快就会遇到一个现实问题:流程根本不是线性的。
它通常更像这样:
- 读资料
- 调工具
- 发现信息不够
- 再查一次
- 结果不对
- 回退重试
这也是为什么很多人用纯链式方式搭 Agent,最后会越来越难维护。LangGraph 之所以有吸引力,不是因为它“更高级”,而是因为它把循环、分支和状态显式地拉到了台面上。
我自己会把 LangGraph 看成一个很典型的“第二阶段工具”。第一阶段你只是想把 Agent 跑起来,很多东西还可以靠 prompt 和几个函数硬接;第二阶段你开始反复遇到重试、分支、回退、断点,这时候图式编排才会真的变顺手。
[PROMPT_LAB_BANNER]
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 一旦变成“什么都记一点”的大字典,后面就很容易出现另外一种问题:流程看起来有状态,实际没人知道哪几个字段才是关键字段。
class 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))
from 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 做到第二阶段以后,自然会碰到的答案,而不是所有项目的起点。
所以它更像一把工程化工具,不是一个入门仪式。什么时候开始频繁觉得链式流程很别扭,什么时候再认真上它,通常最合适。