ReAct 框架
本节定位
CoT 解决的是:
- 先拆步骤再回答
但 Agent 常常还会遇到另一类问题:
- 只靠脑内推导不够
- 必须去外部查、算、搜、看
这时系统需要的不只是“思考”,还要“行动”。
ReAct 的核心就是把两件事交织起来:
先想下一步该做什么,再调工具拿观察,再根据观察继续想。
学习目标
- 理解 ReAct 的核心循环:
Thought -> Action -> Observation - 理解它和纯 CoT 的差别
- 通过可运行示例看懂一个最小 ReAct agent loop
- 理解 ReAct 最适合什么问题,什么情况下会变得笨重
一、为什么只靠“想”还不够?
1.1 因为很多答案不在模型脑子里
例如:
- 今天北京天气怎么样?
- 某个订单现在是什么状态?
- 这两个数精确相加是多少?
这些问题都依赖:
- 实时外部信息
- 精确工具能力
如果模型只靠自己“猜”,
就会出现:
- 幻觉
- 过度自信
- 算错
1.2 ReAct 的本质:边想边拿新信息
它的典型循环是:
Thought
我现在缺什么信息?Action
我该调用哪个工具?Observation
工具返回了什么?- 再进入下一轮思考
这让 Agent 不再只是“脑补答案”,
而是可以逐步靠近真实环境。
1.3 一个类比:像做调查,而不是闭门写作
纯 CoT 更像在草稿纸上推题。
ReAct 更像做调查:
- 先想应该去查什么
- 去拿证据
- 再根据证据继续判断
二、ReAct 和 CoT 的根本差别
2.1 CoT 偏“内部推导”
核心问题是:
- 如何拆步骤
- 如何保持中间状态
2.2 ReAct 偏“推导 + 外部交互”
它额外多了一层:
- 什么时候该向外部要信息
所以 ReAct 更像:
- CoT + Tool Loop
2.3 为什么这对 Agent 特别关键?
因为 Agent 不只是做静态问答。
它经常要:
- 查知识库
- 调数据库
- 算数
- 执行命令
这些都要求它在思考过程中不断接入外部世界。
三、先跑一个真正的 ReAct 最小闭环
下面这个例子会模拟一个小型电商助手。
用户问:
- 退款规则是什么?
- 订单金额
299 + 15最终会退多少?
Agent 需要:
- 先查退款政策
- 再调用计算器
- 最后整合出答案
def search_policy(topic):
policies = {
"refund": "未发货订单可直接申请退款,款项原路返回,通常 3 到 7 个工作日到账。",
}
return policies.get(topic, "未找到相关政策。")
def calculator(expression):
return str(eval(expression, {"__builtins__": {}}, {}))
def policy(state):
trace = state["trace"]
question = state["question"]
if not any(item["action"] == "search_policy" for item in trace):
return {
"thought": "我需要先确认退款政策,再回答规则部分。",
"action": "search_policy",
"args": {"topic": "refund"},
}
if not any(item["action"] == "calculator" for item in trace):
return {
"thought": "我已经知道政策了,接下来计算退款金额 299 + 15。",
"action": "calculator",
"args": {"expression": "299 + 15"},
}
policy_text = next(item["observation"] for item in trace if item["action"] == "search_policy")
amount = next(item["observation"] for item in trace if item["action"] == "calculator")
return {
"thought": "信息已经足够,可以给出最终回答。",
"action": None,
"answer": f"{policy_text} 本单预计退款金额为 {amount} 元。",
}
TOOLS = {
"search_policy": search_policy,
"calculator": calculator,
}
def run_react(question, max_steps=5):
state = {"question": question, "trace": []}
for _ in range(max_steps):
decision = policy(state)
if decision["action"] is None:
return state["trace"], decision["answer"]
tool_name = decision["action"]
observation = TOOLS[tool_name](**decision["args"])
state["trace"].append(
{
"thought": decision["thought"],
"action": tool_name,
"args": decision["args"],
"observation": observation,
}
)
return state["trace"], "达到最大步数,未能完成任务。"
trace, answer = run_react("退款规则是什么?订单金额 299 + 15 最终会退多少?")
print("trace:")
for item in trace:
print(item)
print("\nfinal answer:")
print(answer)
3.1 这段代码最应该怎么读?
建议按这个顺序:
- 先看
policy
理解 agent 每轮如何决定“下一步” - 再看
TOOLS理解外部能力从哪来 - 最后看
run_react理解完整循环如何把 trace 逐步积累起来
3.2 trace 为什么这么重要?
因为 ReAct 不是一次出答案,
而是逐步推进。
有了 trace,你才能知道:
- 想了什么
- 调了什么
- 看到了什么
- 为什么最后会给出这个答案
这对调试非常关键。
3.3 为什么 ReAct 往往比“直接一次调用工具”更强?
因为真实问题经常不是一步完成。
工具调用顺序可能依赖前一步结果。
例如这里:
- 要先确认政策
- 再算金额
- 再组织回答
这就是 ReAct 最擅长的结构。
四、ReAct 什么时候最好用?
4.1 任务需要多轮观察
例如:
- 先搜再算
- 先查再比
- 先看状态再决定下一步