AI Agent 的"推理-行动-观察"循环(ReAct Loop)是如何运作的
ReAct 不是一个框架,不是一个库,不是一个产品。它是一种让 Agent 能真正"干活"的运作机制——推理告诉 Agent 下一步做什么,行动去触发外部系统,观察把结果拿回来修正判断,三步循环往复,直到任务完成。
一、为什么需要 ReAct
一句话回答:单纯让模型"想"会产生幻觉,单纯让模型"做"会失去方向,只有推理和行动交替推进、互相校正,Agent 才能在复杂任务里保持准确。
2022 年底,Google Research 与普林斯顿大学的研究者联合发表了论文《ReAct: Synergizing Reasoning and Acting in Language Models》,提出了一个看起来朴素但影响深远的问题:让语言模型单纯生成推理链(Chain-of-Thought),和单纯执行动作,哪个效果更好?
实验结论是:都不够好。
纯推理链让模型在自己的"内心独白"里打转,无法获取外部信息,遇到需要实时数据的任务就会开始编造。纯动作执行缺乏对任务目标的整体把握,容易陷在局部步骤里,忘了自己最终要做什么。
ReAct 的核心思路是:推理和行动必须交替进行,且互相通知。每一步推理为下一个动作提供方向,每次动作执行后的结果反馈回推理过程,修正接下来的判断。这个闭环,就是"推理-行动-观察"循环的来源。

二、循环的三个节点
节点一:Thought(推理)
Agent 在这一步不调用任何工具,只是输出一段内部独白,用自然语言描述当前的判断:任务目标是什么,已有哪些信息,还缺什么,下一步该做什么以及为什么这样做。
这段推理默认对用户不可见,但它是后续动作合理性的来源。跳过这一步直接行动,工具调用就变成了盲目触发。
节点二:Action(行动)
基于推理结论,Agent 选定一个工具并生成调用参数。这一步输出的是结构化内容,不是自由文本:
Action: search_contract_database
Action Input: {"status": "overdue", "date_range": "2024Q3"}
工具调用发出后,执行权转交给外部系统——数据库、HTTP 接口、文件系统,或者企业内部的业务平台。模型本身在这个阶段暂停生成,等待结果回传。
节点三:Observation(观察)
外部系统返回执行结果,以文本形式注入 Agent 的上下文:
Observation: Found 3 overdue contracts. Contract IDs: CTR-8821, CTR-8834, CTR-8901.
Total overdue amount: ¥486,000. Responsible AEs: 张伟 (2), 陈浩 (1).
Agent 读取这条观察结果,进入下一轮 Thought,决定是否继续调用工具,还是判断任务已经完成,输出最终结论。
循环就这样一轮一轮推进,直到触发终止条件。
三、完整代码实现
下面是一个精简的 ReAct Agent 实现,不依赖 LangChain 或其他高层封装,直接调用 OpenAI 兼容接口,方便看清底层逻辑。
import json
from typing import Callable
from openai import OpenAI
# ──────────────────────────────────────────
# 1. 工具函数(模拟企业内部系统)
# ──────────────────────────────────────────
def search_contracts(customer_id: str = None, status: str = None) -> dict:
"""合同数据库查询 — 对接 R²AIN SUITE 合同管理模块"""
mock_db = [
{"id": "CTR-8821", "customer": "上海科创有限公司", "customer_id": "C-001",
"amount": 180000, "status": "overdue", "ae": "张伟", "due_date": "2024-09-15"},
{"id": "CTR-8834", "customer": "北京联合集团", "customer_id": "C-001",
"amount": 230000, "status": "overdue", "ae": "张伟", "due_date": "2024-09-30"},
{"id": "CTR-8901", "customer": "深圳科技园", "customer_id": "C-002",
"amount": 76000, "status": "overdue", "ae": "陈浩", "due_date": "2024-10-01"},
]
results = [c for c in mock_db if not status or c["status"] == status]
return {"contracts": results, "total": len(results)}
def send_notification(ae_name: str, contract_ids: list[str], message: str) -> dict:
"""站内消息通知 — 对接 R²AIN SUITE 消息中心"""
print(f"[NOTIFY] 发送给 {ae_name}: 合同 {contract_ids} | 内容: {message}")
return {"status": "sent", "recipient": ae_name, "timestamp": "2024-11-05T14:30:00"}
def generate_report(data: dict, report_type: str) -> dict:
"""报表生成 — 对接 R²AIN SUITE 报表引擎"""
return {
"report_id": "RPT-20241105-001",
"type": report_type,
"status": "generated",
"download_url": "https://rainsuite.internal/reports/RPT-20241105-001.xlsx"
}
RAINSUITE_TOOLS: dict[str, Callable] = {
"search_contracts": search_contracts,
"send_notification": send_notification,
"generate_report": generate_report,
}
# 工具的 JSON Schema 描述,供模型选择时参考
TOOL_SCHEMAS = [
{
"type": "function",
"function": {
"name": "search_contracts",
"description": "查询合同数据库,支持按状态、客户 ID 等条件过滤",
"parameters": {
"type": "object",
"properties": {
"customer_id": {"type": "string", "description": "客户 ID,可选"},
"status": {"type": "string", "enum": ["active", "overdue", "closed"]}
}
}
}
},
{
"type": "function",
"function": {
"name": "send_notification",
"description": "向指定客户经理发送站内通知",
"parameters": {
"type": "object",
"required": ["ae_name", "contract_ids", "message"],
"properties": {
"ae_name": {"type": "string"},
"contract_ids": {"type": "array", "items": {"type": "string"}},
"message": {"type": "string"}
}
}
}
},
{
"type": "function",
"function": {
"name": "generate_report",
"description": "生成汇总报告并返回下载链接",
"parameters": {
"type": "object",
"required": ["data", "report_type"],
"properties": {
"data": {"type": "object"},
"report_type": {"type": "string", "enum": ["overdue_summary", "ae_performance"]}
}
}
}
}
]
# ──────────────────────────────────────────
# 2. ReAct 主循环
# ──────────────────────────────────────────
SYSTEM_PROMPT = """你是一个企业合同管理 Agent。
接到任务后,你的工作方式:
1. 分析目标,拆解为若干子任务
2. 选择合适工具逐步执行
3. 观察每步结果,判断是否继续
4. 全部完成后输出执行摘要
每次调用工具前,先说明你的推理依据。
"""
def run_react_loop(user_goal: str, client: OpenAI, model: str = "gpt-4o", max_iterations: int = 10) -> str:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_goal}
]
for i in range(max_iterations):
print(f"\n[Iteration {i+1}]")
response = client.chat.completions.create(
model=model, messages=messages, tools=TOOL_SCHEMAS, tool_choice="auto"
)
message = response.choices[0].message
finish_reason = response.choices[0].finish_reason
if message.content:
print(f"[THOUGHT]\n{message.content}")
# 无工具调用且模型主动停止 → 任务完成
if finish_reason == "stop" and not message.tool_calls:
return message.content
if not message.tool_calls:
return message.content or "Agent 提前终止"
messages.append({
"role": "assistant",
"content": message.content,
"tool_calls": [tc.model_dump() for tc in message.tool_calls]
})
# 执行工具,将结果作为 Observation 注入上下文
for tool_call in message.tool_calls:
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
print(f"[ACTION] {fn_name}({fn_args})")
try:
result = RAINSUITE_TOOLS[fn_name](**fn_args) if fn_name in RAINSUITE_TOOLS \
else {"error": f"未知工具: {fn_name}"}
except Exception as e:
result = {"error": str(e)}
observation = json.dumps(result, ensure_ascii=False)
print(f"[OBSERVATION]\n{observation}")
messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": observation})
return "已达到最大迭代次数,任务未完成"
# ──────────────────────────────────────────
# 3. 运行入口
# ──────────────────────────────────────────
if __name__ == "__main__":
client = OpenAI(api_key="your-api-key")
goal = """
帮我处理本季度所有逾期合同:
1. 查出全部逾期合同
2. 按负责客户经理分组,分别发送催款通知
3. 生成逾期合同汇总报告
4. 给我一份执行结果摘要
"""
print(run_react_loop(goal, client))
代码里有几个细节值得单独说一下。
区分只读工具和写入工具。 search_contracts 是只读查询,重复调用没有副作用;send_notification 会真实触发消息发送,Agent 判断失误就会产生难以撤回的后果。生产环境里,涉及写入、通知、支付类操作的工具,建议加入人工审核节点(Human-in-the-Loop),不要让 Agent 全程自主决策。
**messages** 列表就是 Agent 的状态载体。 每一轮的 Thought、Action、Observation 都追加进去,形成完整的执行上下文。这里并没有什么神秘的外部记忆,就是滚动增长的对话历史。当然,任务步骤一旦变多,这个列表会快速消耗上下文配额,到那时才需要引入摘要压缩或外部向量存储。
**max_iterations** 是必要的安全阀。 工具持续报错时,模型可能陷入反复重试的死循环。硬性的迭代上限是最简单也最有效的保护手段。
四、完整循环的执行过程
用合同管理这个场景,把 ReAct Loop 的节奏完整走一遍:
[Iteration 1]
THOUGHT: 先查出所有逾期合同,了解数量、金额和负责人分布。
ACTION: search_contracts(status="overdue")
OBSERVATION: {"contracts": [
{"id":"CTR-8821","ae":"张伟","amount":180000},
{"id":"CTR-8834","ae":"张伟","amount":230000},
{"id":"CTR-8901","ae":"陈浩","amount":76000}
], "total": 3}
[Iteration 2]
THOUGHT: 共 3 份逾期合同,合计 ¥486,000。张伟负责 2 份,陈浩 1 份。
先通知张伟。
ACTION: send_notification(ae_name="张伟",
contract_ids=["CTR-8821","CTR-8834"],
message="您负责的 2 份合同(共 ¥41 万)已逾期,请尽快跟进")
OBSERVATION: {"status": "sent", "recipient": "张伟"}
[Iteration 3]
THOUGHT: 张伟通知完成,继续通知陈浩。
ACTION: send_notification(ae_name="陈浩",
contract_ids=["CTR-8901"],
message="您负责的合同 CTR-8901(¥76,000)已逾期,请尽快跟进")
OBSERVATION: {"status": "sent", "recipient": "陈浩"}
[Iteration 4]
THOUGHT: 通知全部发出,生成汇总报告。
ACTION: generate_report(data={...}, report_type="overdue_summary")
OBSERVATION: {"report_id":"RPT-20241105-001",
"download_url":"https://rainsuite.internal/reports/RPT-20241105-001.xlsx"}
[Iteration 5]
THOUGHT: 所有子任务完成,输出摘要。
DONE: 本次处理共涉及 3 份逾期合同(总计 ¥486,000)。
已分别向张伟(2 份)和陈浩(1 份)发送催款通知。
汇总报告已生成:https://rainsuite.internal/reports/RPT-20241105-001.xlsx
5 轮迭代,人工处理需要 15-20 分钟的任务压缩到秒级完成。效率的来源不是模型有多聪明,而是推理与执行的交替结构让每一步都有依据、有反馈、有纠偏空间。

五、ReAct 的真实局限
任何值得认真用的技术,都值得提前了解它的边界。
上下文配额是有限资源。 每轮 Observation 都会消耗 token,复杂任务的执行历史很快会把上下文撑满。应对方式是引入历史摘要压缩,或者将需要长期保留的信息迁移到外部向量数据库。
工具数量多了,模型的选择准确率会下降。 当可用工具超过 20 个,模型选错工具或生成格式错误的调用参数的概率会明显上升。解决思路是在工具的 Schema 描述上下功夫,或者在模型选工具之前先用一个路由层缩小候选范围。
有工具调用不代表没有幻觉。 模型可能在 Thought 阶段推理出错,导致工具调用本身成功,但整个任务方向是偏的。评估 Agent 质量时,工具调用成功率只是过程指标,任务目标是否真正达成才是结果指标。
并行工具调用需要额外处理。 上面示例里通知张伟和通知陈浩是串行执行的,实际上完全可以并发。OpenAI 和 Anthropic 的主流模型已支持单次响应返回多个工具调用请求,但执行层需要相应改造为并发处理,代码复杂度会上升一个台阶。
六、小结
ReAct Loop 的本质,是让推理和行动成为彼此的上下文,而不是两个独立步骤的简单拼接。
这个机制给了 Agent 一种"边做边想、根据反馈调整"的工作方式,让它在面对多步骤、不确定性高的任务时不容易跑偏。它不完美,但目前是工程上可解释性最好、落地最稳定的 Agent 实现路径。
评估一个 Agent 平台是否真正支持 ReAct,而不只是"接了工具调用的 API",可以问四个问题:能不能查看每步的推理过程;工具调用失败时有没有自动重试机制;是否支持人工介入节点的设置;多 Agent 协作时,子 Agent 的执行结果能否正确传递给上层编排。
这四个问题问完,基本就能判断面对的是一个真 Agent 平台,还是一个包了皮的聊天机器人。
上一篇:[AI Agent 与普通 AI 助手的区别是什么?]
不是一个框架,不是一个库,不是一个产品。它是一种让 Agent 能真正"干活"的运作机制——推理告诉 Agent 下一步做什么,行动去触发外部系统,观察把结果拿回来修正判断,三步循环往复,直到任务完成。
浙公网安备 33010602011771号