ReAct 思考-行动-观察循环的底层实现机制

引言:会用 create_react_agent 还不够,还要知道它背后发生了什么

上一篇我们已经用 create_react_agent 快速搭建了一个最小 ReAct Agent。

它可以:

  • 判断是否需要调用工具
  • 调用天气工具
  • 调用计算器工具
  • 根据工具结果生成最终回答

但很多人跑通 Demo 后,仍然会有一个疑问:

Agent 到底是怎么知道下一步该"思考",还是该"行动"的?

或者更具体一点:

  • Prompt 是怎么设计的?
  • 模型为什么会选择某个 Tool?
  • Function Calling 到底发生了什么?
  • Observation 是怎么写回上下文的?
  • 一次 ReAct 循环内部到底经历了哪些步骤?

如果你只是调用封装好的 API,这些问题很容易被隐藏起来。

但只要你准备做更复杂的 Agent,例如:

  • 多工具 Agent
  • 企业系统 Agent
  • LangGraph 自定义 Agent
  • 带 Memory 的 Agent
  • 可中断、可回放的 Agent

就必须理解 ReAct 循环的底层实现机制。

这篇文章,我们就把 ReAct 拆开,一层一层看清楚。


一、ReAct 循环到底在循环什么?

ReAct 的完整名字是:

text 复制代码
Reason + Act

也就是:

text 复制代码
思考 + 行动

但真正运行时,它不是只有两步,而是一个闭环:

text 复制代码
Thought(思考)
→ Action(行动)
→ Observation(观察)
→ Thought(继续思考)
→ ...
→ Final Answer(结束)

可以理解成:

text 复制代码
模型先判断当前信息够不够
如果不够,就调用工具
工具返回结果后,再把结果交给模型
模型继续判断是否完成

例如:

text 复制代码
用户:北京今天25度,比昨天高3度,那昨天多少度?

Thought: 我需要计算 25 - 3
Action: calculator("25-3")
Observation: 22
Thought: 已经得到结果
Final Answer: 昨天是22度。

这就是最小的 ReAct 循环。


二、ReAct 的核心不是 Tool,而是"决策循环"

很多新手会误以为:

ReAct = Tool Calling

其实不完全对。

Tool Calling 只是 ReAct 里的 Action 部分。

真正关键的是:

模型每一轮都要判断:现在是继续调用工具,还是输出最终答案。

也就是说,ReAct 的核心问题是:

text 复制代码
当前状态下,下一步应该做什么?

可能的答案只有两类:

1. 行动:调用工具

例如:

text 复制代码
我需要查天气 → 调 get_weather
我需要计算 → 调 calculator
我需要搜索 → 调 search

2. 结束:输出最终答案

例如:

text 复制代码
信息已经足够 → 直接回答用户

所以 ReAct 的本质是一个循环决策系统:

text 复制代码
判断 → 执行 → 观察 → 再判断

三、Prompt 模板:ReAct 的"行为规则"

Agent 会不会正确行动,很大程度取决于 Prompt。

一个 ReAct Prompt 通常包含三部分:

  1. System Prompt
  2. Tool 描述
  3. Few-shot 示例

四、System Prompt:告诉 Agent 它应该怎么工作

System Prompt 是 Agent 的最高层规则。

它会告诉模型:

  • 你是什么角色
  • 你有哪些工具
  • 什么时候应该调用工具
  • 什么时候应该输出最终答案
  • 不允许做什么

一个简化版 System Prompt 可以这样写:

text 复制代码
你是一个会使用工具解决问题的 AI Agent。

你需要遵守以下规则:
1. 如果问题需要实时信息、外部数据或计算,必须调用工具。
2. 不要凭空猜测工具可以查询到的信息。
3. 每次工具返回结果后,都要基于 Observation 继续判断下一步。
4. 如果已经获得足够信息,就输出 Final Answer。

这段 Prompt 的作用非常关键。

如果没有它,模型可能会直接猜:

text 复制代码
北京今天应该是晴天。

而不是调用天气工具。

所以,在 ReAct 中,System Prompt 的核心任务是:

限制模型不要乱答,引导它在需要时使用工具。


五、Tool 描述:模型选择工具的依据

Tool 本质上是你暴露给模型的能力。

例如:

python 复制代码
@tool
def get_weather(city: str) -> str:
    """查询指定城市今天的天气和温度。"""
    ...

对模型来说,它能看到的不是工具代码,而是工具的"描述"。

通常包括:

  • 工具名称
  • 工具说明
  • 参数名称
  • 参数类型
  • 参数描述

例如:

text 复制代码
工具名:get_weather
功能:查询指定城市今天的天气和温度
参数:city,城市名称

模型会根据这些信息决定:

text 复制代码
用户问天气 → 应该调用 get_weather
用户问数学 → 应该调用 calculator

所以 Tool 描述写得越清楚,Agent 越容易选对工具。


六、Few-shot:给 Agent 看"正确行动示例"

仅靠规则,有时候还不够。

更稳定的方式是给模型几个示例。

这就是 Few-shot。

例如:

text 复制代码
示例1:
用户:北京天气怎么样?
Thought: 这个问题需要实时天气信息。
Action: get_weather({"city": "北京"})
Observation: 北京今天晴天,25度。
Final Answer: 北京今天晴天,25度。

示例2:
用户:37 * 12 等于多少?
Thought: 这个问题需要计算。
Action: calculator({"expression": "37*12"})
Observation: 444
Final Answer: 37 * 12 等于 444。

Few-shot 的价值是:

不只是告诉模型规则,而是演示模型应该怎么做。

尤其在以下场景非常有用:

  • 多工具选择
  • 参数格式复杂
  • 输出格式必须固定
  • 模型经常不调用工具

七、Agent 如何决定"思考"还是"行动"?

严格来说,模型每一轮并不会真的有一个显式按钮叫"思考"或"行动"。

它做的是:

根据当前上下文,生成下一步输出。

而这个输出可能是两种形式。

1. 普通文本输出

例如:

text 复制代码
北京今天晴天,25度。

这表示模型认为:

当前信息已经足够,可以直接回答。

2. Tool Call 输出

例如:

json 复制代码
{
  "tool_name": "get_weather",
  "arguments": {
    "city": "北京"
  }
}

这表示模型认为:

当前信息不够,需要调用工具。

也就是说,Agent 框架会根据模型输出判断:

text 复制代码
如果模型返回 tool_calls → 执行工具
如果模型返回普通消息 → 结束或继续

这就是"思考还是行动"的底层判断。


八、Function Calling 原理:模型不是执行函数,而是生成调用请求

这是理解 Tool Calling 最重要的一点:

模型不会真的执行函数。

模型只是生成一个结构化调用请求。

例如用户问:

text 复制代码
北京天气怎么样?

模型返回:

json 复制代码
{
  "name": "get_weather",
  "arguments": {
    "city": "北京"
  }
}

然后真正执行函数的是 Agent 框架。

执行流程是:

text 复制代码
模型生成 tool_call
↓
框架解析 tool_call
↓
找到对应 Python 函数
↓
传入参数并执行
↓
拿到返回结果
↓
把结果作为 Observation 写回上下文

所以 Function Calling 的本质是:

模型负责"决定调用什么",代码负责"真正执行"。


九、自定义 Tool 编写:三个关键点

写 Tool 并不难,但要写得让模型"会用",需要注意三个关键点。

1. 工具名要清楚

不推荐:

python 复制代码
def func1(...):
    ...

推荐:

python 复制代码
def get_weather(city: str) -> str:
    ...

工具名最好能直接表达用途。

2. docstring 要清楚

不推荐:

python 复制代码
"""获取信息"""

推荐:

python 复制代码
"""查询指定城市今天的天气和温度。参数 city 是城市名称,例如 北京、上海。"""

docstring 越清楚,模型越容易正确调用。

3. 参数要简单

不推荐:

python 复制代码
def query(data: dict):
    ...

推荐:

python 复制代码
def query_order(order_id: str):
    ...

参数越明确,模型越不容易传错。


十、示例:天气 Tool 与计算器 Tool

python 复制代码
from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """查询指定城市今天的天气和温度。参数 city 是城市名称,例如 北京、上海、广州。"""
    data = {
        "北京": "北京今天晴天,25度。",
        "上海": "上海今天多云,28度。"
    }
    return data.get(city, f"暂时没有查询到 {city} 的天气。")


@tool
def calculator(expression: str) -> str:
    """计算一个简单数学表达式,例如 2+3、25-3、37*12。"""
    try:
        return str(eval(expression))
    except Exception as e:
        return f"计算失败:{e}"

这两个 Tool 对模型来说非常清楚:

  • 天气问题 → get_weather
  • 数学问题 → calculator

十一、单次 ReAct 循环执行流程拆解

下面我们拆解一次完整执行。

用户输入:

text 复制代码
北京今天25度,比昨天高3度,那昨天多少度?

第一步:构造消息

python 复制代码
messages = [
    {"role": "system", "content": system_prompt},
    {"role": "user", "content": "北京今天25度,比昨天高3度,那昨天多少度?"}
]

此时模型看到:

  • 系统规则
  • 用户问题
  • 可用工具描述

第二步:模型判断是否需要工具

模型可能生成:

json 复制代码
{
  "tool_name": "calculator",
  "arguments": {
    "expression": "25-3"
  }
}

这说明模型判断:

当前问题需要计算。

第三步:框架执行工具

框架执行:

python 复制代码
calculator("25-3")

得到结果:

text 复制代码
22

第四步:Observation 写回上下文

框架会把工具结果追加到消息中:

text 复制代码
Observation: 22

模型下一轮能看到:

text 复制代码
用户问题:北京今天25度,比昨天高3度,那昨天多少度?
工具结果:22

第五步:模型生成最终答案

模型输出:

text 复制代码
昨天是22度。

这就是一次最小 ReAct 循环。


十二、用伪代码理解底层循环

下面是一个简化版 ReAct 循环:

python 复制代码
while True:
    response = llm.invoke(messages, tools=tools)

    if response.tool_calls:
        for tool_call in response.tool_calls:
            tool_name = tool_call["name"]
            tool_args = tool_call["args"]

            result = run_tool(tool_name, tool_args)

            messages.append({
                "role": "tool",
                "name": tool_name,
                "content": result
            })

        continue

    else:
        final_answer = response.content
        break

这段伪代码解释了 ReAct 的核心:

text 复制代码
模型输出 tool_calls → 执行工具 → 写回结果 → 再问模型
模型输出普通答案 → 结束

这就是 create_react_agent 背后最重要的机制。


十三、为什么 Observation 必须写回上下文?

很多 Agent 出错,问题就出在这里。

Tool 已经执行了,但模型最终还是瞎猜。

原因通常是:

Tool 的结果没有正确写回上下文。

例如:

text 复制代码
Tool 返回:北京今天晴天,25度

但下一轮 LLM 输入里没有这条 Observation。

那么模型根本不知道工具查到了什么。

所以,ReAct 中最关键的状态更新是:

text 复制代码
Tool Result → Observation → Messages / State

如果这一步断了,ReAct 循环就失效了。


十四、Agent 什么时候停止?

ReAct 循环不能无限执行。

它需要停止条件。

通常有几种:

1. 模型输出 Final Answer

也就是不再返回 tool_calls。

2. 达到最大循环次数

例如:

python 复制代码
max_iterations = 5

防止死循环。

3. 工具调用失败次数过多

例如同一个 Tool 连续失败 3 次,就停止。

4. 业务规则强制停止

例如:

  • 权限不足
  • 参数非法
  • 用户取消

生产环境中,一定不能只依赖模型自己停止。

建议同时设置:

  • 最大循环次数
  • 最大 Token
  • 最大耗时
  • Tool 调用次数限制

十五、为什么 ReAct 能减少幻觉?

普通 LLM 直接回答时,很容易出现幻觉。

因为它只能依赖模型内部参数。

ReAct 通过工具,把外部世界接进来:

text 复制代码
不知道 → 查工具
算不准 → 调计算器
缺数据 → 搜索 / 查数据库

所以它减少幻觉的方式不是"让模型更聪明",而是:

让模型不要凭空猜,而是先获取证据。

但要注意:

ReAct 不能自动消除所有幻觉。

如果:

  • 工具返回错
  • Observation 没写回
  • Prompt 没要求基于工具回答
  • 模型忽略工具结果

仍然会产生错误。

所以 ReAct 只是减少幻觉的框架,不是万能保证。


十六、常见问题:为什么 Agent 不调用 Tool?

1. Tool 描述不清楚

例如:

python 复制代码
"""获取信息"""

模型不知道什么时候用。

2. Prompt 没要求必须使用工具

如果你不告诉模型"实时数据必须调用工具",它可能直接回答。

3. 模型工具调用能力弱

有些模型不擅长 Function Calling。

4. 用户问题不明确

例如:

text 复制代码
这个怎么样?

缺少上下文,模型不知道该调用哪个工具。


十七、常见问题:为什么 Tool 调用了但答案还是错?

可能原因:

  1. Tool 返回值本身错了
  2. Tool 结果没写回 messages
  3. 模型没有基于 Observation 回答
  4. State 字段名不一致
  5. 后续节点覆盖了正确结果

排查顺序建议:

text 复制代码
先单独跑 Tool
↓
看 tool_call 参数
↓
看 Tool 返回值
↓
看 Observation 是否写回
↓
看最终 LLM 输入里有没有 Observation

十八、从预构建 Agent 到自定义 Graph

create_react_agent 很适合快速上手。

但当你需要更复杂控制时,就要自己写 LangGraph。

例如你想控制:

  • 每一步 State 结构
  • Tool 失败后的重试
  • 某些工具需要人工确认
  • 某些步骤需要中断
  • 某些节点需要持久化

这时就需要手写:

  • State
  • Node
  • Edge
  • Conditional Edge

但无论怎么写,底层仍然是这套机制:

text 复制代码
LLM 判断 → Tool 调用 → Observation 写回 → 再判断

结语

一句话总结:

ReAct 的底层机制,就是让模型在"输出答案"和"调用工具"之间不断做决策。

它真正重要的不是某个 API,而是这条循环:

text 复制代码
Thought → Action → Observation → Thought → Final Answer

理解这条循环后,你就能看懂:

  • create_react_agent 为什么能工作
  • Tool Calling 为什么重要
  • Observation 为什么必须写回
  • Agent 为什么会死循环
  • 为什么复杂 Agent 最后都要管理 State

下一篇,我们可以继续深入:

手写 ReAct Agent:不用 create_react_agent,自己实现思考-行动-观察循环。

相关推荐
JaydenAI2 小时前
[FastMCP设计、原理与应用-17]从服务器向客户端的反向通知
python·ai编程·ai agent·mcp·fastmcp
老刘说AI2 小时前
Text2SQL到数据智能
人工智能·python·低代码·语言模型·langchain
knight_9___2 小时前
RAG面试篇10
人工智能·python·机器学习·agent·rag
灵机一物2 小时前
灵机一物AI原生电商小程序、PC端(已上线)-GPT-5.5 深度技术实测评测:Agent能力全面爆发,Codex重构开发范式,实测碾压Opus 4.7
openai·ai编程·gpt5.5·大模型技术评测
knight_9___2 小时前
RAG面试篇11
java·面试·职场和发展·agent·rag·智能体
腾飞开源2 小时前
06_系统架构设计
微服务架构·智能决策·langgraph·deepseek·智能体开发·fastmcp·langsmith
hjxu20162 小时前
【LangGraph入门 1】第一个LangGraph-HelloWorld
langchain
easyllm3 小时前
GPT-5.5 全系上架 NoneLinear
gpt·openai·ai编程·智能体·大模型api·新模型上架·gpt5.5