ReAct 模式:循环推理的本质

ReAct(Reason + Act)是目前主流 Agent 架构的基础模式。它的逻辑很直白:LLM 观察当前上下文,进行推理,决定是否调用某个工具;如果调用,就执行工具、把结果交还给 LLM,LLM 再次推理,决定下一步——直到它认为任务完成,不再产生工具调用为止。

用 LangGraph 的语言描述,这个模式的图结构是:

graph TD
    START(["START"]) --> agent["agent\n调用 LLM"]
    agent -->|"AIMessage.tool_calls 非空"| tools["tools\nToolNode 并发执行工具"]
    tools --> agent
    agent -->|"AIMessage.tool_calls 为空"| END(["END"])
    style agent fill:#dae8fc,stroke:#6c8ebf
    style tools fill:#d5e8d4,stroke:#82b366
    style START fill:#f5f5f5,stroke:#666
    style END fill:#f5f5f5,stroke:#666

两个节点,一条条件边,一个循环。整个图的逻辑可以在一张白纸上画清楚。把这张图翻译成代码,就是 create_react_agent() 的核心。

这个函数在 langgraph.prebuilt 模块里,大约 300 行(含文档、边界处理和可选特性),其中真正描述图结构的核心代码不超过 30 行。这个规模本身就说明了一件事:一个标准的 ReAct Agent 并没有什么神秘的内部魔法,它就是一个极其简洁的图。


create_react_agent() 的图结构拆解

阅读源码,去掉可选特性和边界处理,create_react_agent() 的骨干结构是这样的:

workflow = StateGraph(state_schema=AgentState)
# 两个节点
workflow.add_node("agent", call_model)     # 调用 LLM
workflow.add_node("tools", tool_node)      # 执行工具
# 入口是 agent 节点
workflow.set_entry_point("agent")
# agent 节点的条件路由:有 tool_calls 去 tools,否则结束
workflow.add_conditional_edges("agent", should_continue, ["tools", END])
# tools 节点执行完毕后,无条件回到 agent
workflow.add_edge("tools", "agent")
return workflow.compile(checkpointer=checkpointer, store=store, ...)

call_model 是一个普通函数,它从状态里取出消息列表,调用 LLM,把返回的 AIMessage 追加到 messages 里:

def call_model(state, runtime, config):
    response = model.invoke(state["messages"], config)
    return {"messages": [response]}

这里没有手动管理消息列表。call_model 只是返回 {"messages": [response]},框架会用 add_messages 这个 reducer 把新消息追加到现有列表上。整个对话历史不需要节点去管理,它靠 reducer 的累积语义自然形成。

这个设计选择很关键:状态的合并逻辑("追加"还是"覆盖")是在状态模式定义时声明的,而不是在每个节点里手动实现的。这样做的好处是,节点代码只需要关心"我要输出什么",不需要关心"现有状态是什么样子、我该如何合并"。

should_continue 是另一个简单函数,它检查消息列表最后一条是否是带有 tool_calls 的 AIMessage:

def should_continue(state):
    last_message = state["messages"][-1]
    if isinstance(last_message, AIMessage) and last_message.tool_calls:
        return "tools"   # 有工具调用,继续循环
    return END           # 没有工具调用,退出

这就是整个 ReAct 循环的控制流逻辑——十行以内。


ToolNode:横切关注点的集中地

ToolNode 是执行工具的节点,也是 prebuilt 层里技术含量最高的部分。它不只是"接收工具调用,调用函数,返回结果",它还承担了工具执行的所有横切关注点:注入机制、错误处理、并发执行。