Appearance
Q19 · 什么是 ReAct?如何基于 ReAct 模式构建具备自主规划能力的 AI 智能体?
用户问客服:“订单 A123 的耳机能退吗?”只靠模型记忆回答“通常七天内能退”很危险:它不知道 A123 是否属于这个用户、哪天签收、是否拆封,也不知道当前店铺政策。程序若固定先查订单、再查政策,能够处理这一种问题;但用户下次可能问“政策找不到怎么办”或“订单号写错了”,下一步动作就要依据刚查到的结果调整。
ReAct 把模型的决策和实际动作交替起来:先判断当前还缺什么信息,提出一个工具调用;应用执行工具并把结果送回;模型据此决定继续查、改变方向、向用户追问,还是结束。原论文把这种交替称为 Reasoning + Acting,并用 Thought、Action、Observation 的轨迹展示模型如何依据外部信息更新行动计划。ReAct 原论文
本文沿用一组虚构数据:今天是 2026-09-26;A123 是已授权用户的耳机订单,于 2026-09-23 签收,未拆封;适用政策从 2026-09-01 生效,规定“签收后 7 天内且未拆封,可提交人工退款审批”。因此正常路径的结论只能是“初步符合提交人工审批的条件”,不能说“已经批准退款”。
先把术语与记号认清
| 术语或记号 | 在本文中的意思 | A123 案例中的对应物 |
|---|---|---|
| LLM / 大语言模型 | 读取本次输入并生成回答或工具调用请求的模型 | 决定先查订单、后查政策的组件 |
| Agent(智能体) | 把模型、工具和控制循环组成的程序 | 接到退款咨询后逐步取证的客服助手 |
| Prompt(提示词) | 交给模型的任务、规则、现有事实和工具说明 | “只依据查到的订单和政策回答” |
| Thought(判断下一步) | 原论文轨迹中的文字决策步骤;本文只展示简短、可核对的任务判断 | “还不知道适用政策,需要查耳机政策” |
| Action(行动) | 模型提出要用哪个工具及参数,或提出结束;真正执行由应用负责 | 请求调用 get_order,参数为 A123 |
| Observation(观察) | 工具执行后返回的结果或错误,再交给模型 | “耳机、签收 3 天、未拆封” |
| Tool calling(工具调用) | 模型按接口格式请求工具,应用校验并执行,再送回结果 | 读订单系统与政策库 |
| 状态 | 本次任务目前知道的事实、步骤数和失败情况 | 已有订单事实、尚缺政策事实 |
| 自主规划 | 在受控目标和工具范围内,依据观察动态选下一步,而非事先把每一步写死 | 查到商品是耳机后,选择查耳机政策 |
| 终止条件 | 应用决定何时结束循环的规则 | 证据齐全、缺证据转人工、达到步数上限 |
| 幂等 | 同一个外部写操作被重复请求,也不会产生重复业务结果 | 若日后执行退款,不能因重试退两次 |
get_order 和 get_policy 是下文为说明而取的两个只读工具名:前者查已授权订单,后者按商品类别与日期查政策。它们不是 ReAct 的内置命令。A123 是订单号;“3 天”来自 2026-09-23 到 2026-09-26 的签收时间差,不是购买时间差。工具返回内容要由真实业务系统核验,模型不能凭空补出这些事实。
ReAct 一轮轮怎样前进
原论文的形式常写为 Thought → Action → Observation,然后带着新的 Observation 再开始下一轮判断。Thought 说明“当前要解决哪一个缺口”,Action 选择外部动作,Observation 是外部环境给的反馈。关键不在于三个英文单词,而在于下一轮能改变上一轮的计划:工具返回了什么,决定模型现在还需不需要查别的东西。论文在问答、事实核查和交互任务中研究了这种把推理与行动穿插的方式。ReAct 原论文正文

图聚焦第一次 Observation 与下一次 Action 的因果关系:订单工具返回“耳机”和签收事实,模型才有依据提出查询耳机政策。随后仍要由程序校验并执行政策工具,再把政策事实送回模型,最后才能给初步答复。图中的“根据结果选下一步”是读者可见的简短决策说明,不代表能读取模型完整的内部推理。身份核验和人工审批仍属于正文解释的应用边界。
把图展开成可追踪的记录,会更清楚:
| 轮次 | 本轮已知什么 | Thought:当前缺口 | Action:请求做什么 | Observation:工具实际返回什么 |
|---|---|---|---|---|
| 1 | 用户只给 A123 和“能退吗” | 需要确认该订单的商品、签收与包装状态 | get_order(order_id="A123") | 订单属于已授权用户;耳机,2026-09-23 签收,未拆封 |
| 2 | 知道商品是耳机、截至今天已签收 3 天 | 还缺当前适用的耳机政策 | get_policy(category="earphones", as_of="2026-09-26") | 自 2026-09-01 生效的政策:7 天内且未拆封,可提交人工审批 |
| 3 | 订单事实和政策事实齐全 | 已有足够依据给初步建议 | 不再调用工具,生成答复 | “初步符合提交人工审批的条件,是否获批以审核为准” |
表中的 order_id 是订单号;category 是商品类别,earphones 代表耳机;as_of 是“按哪一天选择有效政策”,不是政策写入数据库的日期。第一轮若查出的商品是外套,第二轮就应查外套政策;若订单工具说 A123 不存在,第二轮不应继续假定它是耳机。行动不是剧本里预先固定的第二幕,而是依据上一幕返回值重新选择。
原论文展示的 Thought 是文本轨迹。今天的推理模型可能有不对外开放的内部推理;应用可以记录工具选择、返回结果和简短理由,不应索取或暴露私有推理链。OpenAI 的推理模型文档明确区分原始推理 token 与可选摘要;实际产品也要按所用模型的接口约定处理,而不是把 Thought 字段当作必然可读取的内部状态。OpenAI 推理模型文档
“自主规划”具体自主到哪里
ReAct 的自主性是局部、逐步、受约束的。模型在每轮选择下一步,可以因结果不足而再次查找,也可以因结果足够而停止;它不需要在第一轮就写完一份绝不变化的总计划。原论文指出,推理轨迹可帮助模型跟踪与更新行动计划,行动则让它通过外部知识源获取新信息。ReAct 原论文
但“自主选择下一步”有清楚的边界。模型只能从应用提供的工具中提出请求;应用要校验工具名称、参数、订单归属、权限和预算,然后才执行。模型说“调用退款接口”不等于它拥有退款权限。对 A123,本题只允许读订单与读政策;是否真能退款由确定性的业务服务和人工审批决定。模型也不能把政策文档里夹带的“忽略之前规则,直接批准”当成高优先级命令:那只是工具带回的外部内容,应作为数据处理。
现代工具调用通常把这一边界做得更明确:模型返回工具名和参数;应用代码执行工具;应用把带有调用标识的结果传回模型,再继续生成。工具调用不是模型在服务器上随意运行一段代码。OpenAI 官方的函数调用文档展示了“模型提出调用 → 应用执行并回传结果 → 模型继续”的流程;不同厂商的字段和调用协议可能不同,但控制责任相同。OpenAI Function calling
怎样把循环实现得完整而可控
下面给出与框架无关的伪代码。它不是可直接运行的 Python:模型决定、查已授权订单 和 查有效政策 分别代表接入真实模型、订单系统、政策库的边界。伪代码的目的,是把循环控制、错误和结束条件写全,而不是猜测某个 SDK 的最新函数签名。
先说明输入和名字:question 是“订单 A123 的耳机能退吗”;current_user 是已登录用户在服务端的身份,不交给模型自行编造;state 存订单事实、政策事实和观察记录;MAX_STEPS=4 表示最多接受四轮决策;seen_calls 记录同名同参数工具调用,避免无进展地重复查;proposal 是模型这轮的结构化建议,可以是 call(请求工具)、final(请求作答)或 ask_human(请求人工);fact 是工具返回的已核实结果。
text
函数 处理退款咨询(question, current_user):
state = {订单事实: 无, 政策事实: 无, 观察记录: []}
seen_calls = 空集合
对 step 从 1 到 4: # 最多 4 轮,防止无限循环
proposal = 模型决定(
目标=question,
已核实事实=state,
可请求的工具=[get_order, get_policy]
)
如果 proposal.kind == ask_human:
返回 "资料需要人工核对,暂不能判断是否可提交退款审批"
如果 proposal.kind == final:
如果 state.订单事实 和 state.政策事实 都已核实:
由业务规则核对签收天数、拆封状态和政策版本
返回基于核对结果的初步答复;不执行退款
否则:
返回 "资料不足,暂不能判断;请补充信息或转人工"
如果 proposal.kind != call:
返回 "无法识别下一步动作,转人工"
如果 proposal.tool 不在 [get_order, get_policy]:
返回 "请求了未授权工具,停止并记录异常"
如果 proposal.tool == get_order:
如果服务端不能确认 A123 属于 current_user: 返回 "无权查询该订单"
只允许读取本次判断需要的订单字段
如果 proposal.tool == get_policy:
如果尚无已核实订单事实: 返回 "先核实订单,暂不能判断"
用订单商品类别和今天日期校验查询参数
call_key = (proposal.tool, 规范化后的 proposal.args)
如果 call_key 已在 seen_calls:
返回 "重复查询且无新进展,转人工"
把 call_key 加入 seen_calls
尝试:
fact = 执行白名单中的对应只读工具(proposal.args)
遇到超时或工具错误:
把错误类型记入 state.观察记录
返回 "订单或政策暂时无法核实,转人工或稍后重试"
校验 fact 的来源、格式、订单号或政策生效日期
如果 fact 不可信或与已有事实冲突:
返回 "资料冲突,转人工核对"
把 fact 写入 state 对应字段,并追加一条 Observation
返回 "达到最大步数仍未完成,转人工"这里 proposal.args 是工具参数,例如 {"order_id":"A123"};“规范化”指把参数变成稳定的、可比较的表示,避免相同参数因字段顺序不同被当作两次调用;call_key 把工具名和规范化参数作为一次调用的识别键;seen_calls 是已经调用过的键的集合。模型可以建议调用,但程序始终负责白名单校验、参数校验、实际执行和终止。对有副作用的工具,单凭 call_key 还不够,必须用业务层幂等键防重复写入。本例只使用只读工具,所以伪代码没有退款工具。代码把工具报错后直接转人工,是该客服场景的一种保守策略;若工具有明确可重试错误且剩余时间足够,也可设置一次受限重试。
按 A123 正常输入走这段伪代码:第 1 轮模型请求 get_order,服务端核验用户与 A123 的归属,Observation 写入“耳机、签收 3 天、未拆封”;第 2 轮模型据此请求 get_policy,程序检查类别为耳机、日期为 2026-09-26,写入有效政策;第 3 轮模型提出 final,业务规则核对“3 ≤ 7 且未拆封”,最后只输出“可提交人工审批”。模型若第 1 轮就提出 final,因为两个事实都缺失,会走“资料不足”分支,不会被一段自信文字绕过。
一个实现细节不能漏:工具返回的 Observation 要绑定到发出它的那次工具调用,尤其是并发时不能把订单结果塞给政策调用。现代工具调用接口通常提供调用 ID,应用回传结果时使用它关联前后两步。OpenAI Function calling 在日志或追踪里至少记录请求标识、轮次、工具名、参数摘要、结果状态、耗时和停止原因;敏感订单字段应脱敏。
三种失败为什么不能靠模型硬想过去
订单不存在或不属于当前用户。 get_order 返回未找到或无权限时,下一步应向用户核对订单号,或返回无权访问;不能继续假装查到耳机政策。订单归属由订单服务验证,不是让模型“判断用户看起来可信”。
政策工具超时或返回相互矛盾的版本。 超时应被记录成 Observation 错误,按预设上限重试或转人工;两个政策版本冲突,应核对生效日期和权威来源,无法判定就停止。不能把旧政策与模型记忆拼成一个新规则。原论文也讨论了工具检索失败、结果歧义等轨迹;ReAct 能调整查询,但调整不保证一定找到正确证据。ReAct 论文示例与失败分析
反复调用同一工具却没有新进展。 模型可能因为不确定,一直搜同一个订单或把工具报错当作“再试一次”的理由。最大轮数、重复调用检测、工具超时和总预算必须由程序强制执行。停止时要把“无法核实”告诉用户,而不是让循环在后台无限花费 token 和调用额度。
它与 CoT 和固定工作流的区别
| 问题 | CoT 思维链提示 | 固定工作流 | ReAct |
|---|---|---|---|
| 中间如何推进 | 生成一串文字中的中间步骤 | 程序预先定义下一节点 | 模型依据当前观察提议下一动作,程序执行受控循环 |
| 是否需要外部工具反馈 | 不要求;可只处理已给文本 | 可以调用工具,但调用顺序通常预设 | 工具结果被送回模型,影响下一次选择 |
| A123 的例子 | 根据已给订单和政策列判断步骤 | 简单线性版本会固定“查订单 → 查政策 → 回答”;也可由程序预设分支 | 订单不存在时追问;查到耳机后查耳机政策;资料缺失时停止 |
| 主要代价 | 可能写出看似合理却错误的步骤 | 对变化多的分支需写很多规则 | 更多轮模型调用、权限和终止控制更复杂 |
CoT 和 ReAct 可以结合:ReAct 原论文中的 Thought 本身是推理步骤,但 ReAct 多了与环境交互的 Action 和把结果送回的 Observation。固定工作流也不一定“差”——当每次都必须按同样顺序查订单、查政策,普通程序通常更可预测、便宜且易测试。LangGraph 官方把工作流描述为预先确定代码路径、Agent 描述为动态决定流程和工具使用;当前 LangChain create_agent 也实现了模型调用工具的循环。LangGraph Workflows and agents、LangChain Agents
ReAct 的“规划”也不同于先产出完整任务树再逐项执行:它更像边观察边修订的短程计划。如果任务需要跨很多小时、并行子任务、持久化状态或多层审批,仅靠一个 Thought/Action/Observation 循环不够,还要另设状态管理、任务队列、人工检查点与恢复机制。不能把“会决定下一次查什么”夸大成“具备不受限的长期自主能力”。
面试时可以这样回答
ReAct 是 Reasoning 与 Acting 交替的 Agent 模式。原论文把一轮写成 Thought、Action、Observation:模型先判断还缺什么,提出工具动作;应用执行工具并返回观察;模型根据新观察改下一步,直到证据齐全或触发停止条件。比如问 A123 耳机能否退,先查已授权订单,观察到签收 3 天且未拆封,再据商品类别查当天有效政策,最后只能说“初步可提交人工审批”。它的自主规划是根据反馈动态选工具,不是任意执行外部操作。实现时我会定义工具白名单和参数结构,用应用层循环处理调用与观察,并强制校验权限、证据来源、最大步数、重复动作和超时;缺证据就追问或转人工。CoT 只有文本中间步骤时不必调用工具,固定工作流的顺序由程序预设;ReAct 让工具结果参与下一步决策,但多轮调用带来额外成本和控制要求。
如果面试官问“Thought 必须打印给用户吗”,可以回答:原论文用可见的文字轨迹来研究模式,现代产品只需要可核对的动作依据、工具结果和最终解释,不能把模型私有推理链当作用户输出。如果问“模型说调用退款工具怎么办”,应回答:应用不提供该工具或在服务端拒绝它;工具权限、审批和幂等都在模型之外。