自由循环的 Agent 为什么不够用
把大模型套上工具循环(经典的 ReAct 模式:模型边思考边调工具,直到它认为任务完成),搭起来很快,但跑起来之后你就失去了对流程的控制:它可能绕远路,可能在错误的前提下反复尝试,可能在不该自动执行的环节直接执行了危险动作;每一步为什么这么走,事后也很难审计。业务里的 Agent 往往需要「确定性的骨架 + 模型的智能填空」:哪些步骤必须走、什么条件走哪个分支、哪里要人签字、进程崩了能不能从半路恢复——这些都要求把控制流显式画出来,而不是埋在模型的自由发挥里。
LangGraph(LangChain 生态下的编排库)给出的答案是把工作流写成状态机:节点是步骤,边是走向,状态在节点间流转。对流程稳定、需要审计与人工节点的场景,这种显式结构换来的是可控与可测。
三个核心概念
- State:贯穿全程的共享状态对象,通常用 TypedDict 或 Pydantic 模型定义。图的执行就是状态在节点间的一次次接力。
- Node:一个普通函数,接收当前状态,干活,返回状态增量(只写变化的字段,框架负责合并进状态)。
- Edge:节点之间的边。固定边无条件跳转;条件边(conditional edge)按当前状态算出下一个节点,分支与循环都靠它表达。
State 的字段还可以声明「归并方式」(reducer):默认是覆盖——节点返回什么字段就替换什么;把某个字段配置成追加式合并后,节点返回的该字段内容会被追加进列表而不是覆盖,多个节点都往同一个消息列表写时不会互相冲掉。这是对话类应用里最常用的配置,读示例代码时见到「追加合并」的字样,指的就是它。
最小示例:生成与评审的循环
写一个两节点循环:节点 draft 生成草稿,节点 review 评审;评审不通过打回 draft,通过则结束。
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
draft: str
approved: bool
def draft(state: State) -> dict:
return {"draft": call_llm("写一版产品介绍草稿")} # call_llm 为示意
def review(state: State) -> dict:
ok = judge(state["draft"]) # judge 为示意
return {"approved": ok}
def route(state: State) -> str: # 条件边的路由函数
return END if state["approved"] else "draft"
g = StateGraph(State)
g.add_node("draft", draft)
g.add_node("review", review)
g.add_edge(START, "draft") # 入口
g.add_edge("draft", "review") # 固定边
g.add_conditional_edges("review", route) # 条件边:分支与循环
app = g.compile()
result = app.invoke({"draft": "", "approved": False})
整个控制流一目了然:入口进 draft,draft 到 review,review 之后由 route 依据状态决定是结束还是打回重来。想改流程——加一个「改写」节点、给评审加第二个出口——都是在图上加节点和边,不必动节点内部逻辑。单测也容易:把 call_llm 与 judge 换成假实现,整张图就能离线跑通,这是端到端裸循环最难给的东西。
Checkpointer:持久化与人工介入
编译时传入 checkpointer(如内存版的 MemorySaver),之后每一步结束都会落一个状态检查点;调用时用 config 里的 thread_id 标识一条执行线程。这带来两个关键能力:
- 断点续跑:进程崩了,用同一个 thread_id 再次 invoke,从最近的检查点继续,已完成的节点不必重跑,长流程的容错成本骤降。
- human-in-the-loop(人在回路中):编译时把某个节点标记为
interrupt_before,图跑到该节点前自动暂停,等人审核或修改状态后再恢复执行;也可以在节点内部调用interrupt()主动暂停。以审批为例:图走到断点暂停,把当前状态交给人;人确认后以同一个 thread_id 恢复,图从暂停点继续,之前的节点一个都不重跑。审批流、危险操作放行这类需求,落点就在这里。
与裸 ReAct 循环的对比
| 维度 | 裸 ReAct 循环 | LangGraph 状态机 |
|---|---|---|
| 控制流 | 隐式,模型自己决定 | 显式,写在图里 |
| 可测试性 | 基本只能端到端验证 | 节点可单测、图可离线跑 |
| 人工介入与恢复 | 自己造轮子 | interrupt 与 checkpointer 原生支持 |
| 搭建成本 | 极低 | 要定义状态与图结构 |
怎么选?建议先写裸循环跑通、验证业务价值;当出现三类信号之一——同一段流程在多个入口重复实现、出了问题查不清模型当时看到了什么、需要一个必须人签字的步骤——就值得迁移到图。迁移通常也不必推倒重来:裸循环里的提示词与工具定义原样可用,变的只是外层的控制结构。
适用边界与小结
不是所有 Agent 都该上图:探索型任务(开放式调研、代码探查)路径不可预知,硬画成状态机只会束缚模型,裸循环更轻。图的甜区是流程相对稳定、需要分支循环、需要审计留痕、需要人工节点的业务 Agent——客服工单处理、内容生产流水线、审批类自动化。
要点回顾:State 是共享状态,Node 是状态增量的生产者,Edge(尤其条件边)表达分支与循环;checkpointer 把「状态机」升级成「可恢复、可介入的状态机」。先画清楚流程图,再落成代码,是写这类 Agent 的正确顺序。
读者留言
COMMENTS 暂无还没有留言,来说第一句?