ReAct:看一次结果,再决定下一步
正在读取学习记录…
1. 动作与观察怎样交替
ReAct把推理与动作交替组织:先根据目标和已有资料选择动作,执行后取得观察,再根据新观察决定下一步。Action是发出的动作,Observation是工具返回的结果,Final是最终答复。
例如用户问退款期限,第一次查常见问题没有找到,第二次改查政策文档,取得7天的规定后回答。这像查书时先看目录,没找到就换索引:后一个动作利用了前一次查找结果。
ReAct论文提出推理轨迹与环境动作交错使用的方法。工程中可以观察工具请求、返回和状态变化;模型选择动作的逻辑接入应用程序,由程序检查工具、参数和预算。
2. 先运行固定资料的反馈循环
先在 PowerShell 创建并进入学习目录:
mkdir "F:\学习\l3-react" -Force
cd "F:\学习\l3-react"保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。
同一新目录创建react_lab.py。资料和候选顺序固定在代码中,便于逐次看到反馈怎样改变路径。
先在 PowerShell 创建并进入学习目录:
mkdir "F:\学习\l3-react" -Force
cd "F:\学习\l3-react"保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。
"""通过两份固定资料观察动作与结果交替。"""
from __future__ import annotations
import argparse
import json
DOCUMENTS = {
"primary": {"refund": "退款申请应在购买后 7 天内提交。"},
"secondary": {"refund": "退款窗口为 7 天;超过窗口需人工审核。"},
}
# 场景名决定这次查询返回资料、空结果还是错误。
def search(source: str, query: str, scenario: str) -> str | None:
if scenario == "tool-failure":
raise RuntimeError("fixture_tool_unavailable")
if scenario == "missing":
return None
if scenario == "found-second" and source == "primary":
return None
return DOCUMENTS[source].get(query)
# 每次动作后保存观察,找到资料时立即形成回答。
def run(scenario: str, max_actions: int = 2) -> dict:
if scenario not in {"found-first", "found-second", "missing", "tool-failure", "forbidden-proposal"} or max_actions < 1:
return {"status": "rejected", "reason": "invalid_input", "events": []}
events: list[dict] = []
for source in ("primary", "secondary"):
if len([item for item in events if item["type"] == "action"]) >= max_actions:
events.append({"type": "stop", "reason": "max_actions"})
return {"status": "budget_exhausted", "answer": None, "events": events}
proposed_tool = "delete_all" if scenario == "forbidden-proposal" else "search"
events.append({"type": "scripted_proposal", "tool": proposed_tool, "source": source})
allowed = proposed_tool == "search" and source in DOCUMENTS
events.append({"type": "program_check", "allowed": allowed, "reason": "allowlisted_source_and_budget" if allowed else "tool_not_allowlisted"})
if not allowed:
events.append({"type": "stop", "reason": "forbidden_proposal"})
return {"status": "rejected", "answer": None, "events": events}
events.append({"type": "action", "tool": proposed_tool, "source": source, "query": "refund"})
try:
result = search(source, "refund", scenario)
except RuntimeError as error:
events.append({"type": "observation", "source": source, "error": str(error)})
continue
events.append({"type": "observation", "source": source, "text": result})
if result:
events.append({"type": "final", "citation": source})
return {"status": "completed", "answer": result, "events": events}
if scenario == "tool-failure":
events.append({"type": "stop", "reason": "repeated_tool_failure"})
return {"status": "needs_human", "answer": None, "events": events}
events.append({"type": "stop", "reason": "no_evidence"})
return {"status": "unknown", "answer": None, "events": events}
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("scenario", choices=["found-first", "found-second", "missing", "tool-failure", "forbidden-proposal"])
parser.add_argument("--max-actions", type=int, default=2)
arguments = parser.parse_args()
print(json.dumps(run(arguments.scenario, arguments.max_actions), ensure_ascii=False, indent=2))ls
python react_lab.py found-second这条命令先查primary,观察为空,再查secondary。关键事件按顺序是:
scripted_proposal: search / primary
program_check: allowed=true
action: search / primary
observation: text=null
scripted_proposal: search / secondary
program_check: allowed=true
action: search / secondary
observation: 退款窗口为 7 天;超过窗口需人工审核。
final: citation=secondary
status: completed终端的完整输出为JSON,保留上述字段和其他动作信息。DOCUMENTS 保存两份资料,search 根据场景返回文字、空值或错误。run 每轮先检查动作额度,再提出工具和来源,程序批准后调用,最后保存观察。
找到文字时,代码将它放进answer,保存citation,然后完成。第一份空时,进入下一来源。两份都空时,返回unknown。两次工具报错时,返回needs_human,帮助调用方选择下一处理方式。
3. 用几条命令比较不同结果
python react_lab.py found-first
python react_lab.py missing
python react_lab.py tool-failure
python react_lab.py forbidden-proposal
python react_lab.py found-second --max-actions 1| 输入 | 观察与动作 | 最后状态 |
|---|---|---|
| found-first | 主资料取得7天 | completed |
| found-second | 主资料空,备用资料命中 | completed |
| missing | 两份资料都空 | unknown |
| tool-failure | 查询服务连续失败 | needs_human |
| forbidden-proposal | 提出未开放工具 | rejected |
| found-second,额度1 | 主资料空后额度耗尽 | budget_exhausted |
把主资料退款期限从7天改为14天,运行found-first,answer会随资料变化,来源仍是primary。恢复后,运行found-second,它仍引用备用资料。这个改动把“资料变化”和“选择哪份资料”两件事区分开来。
4. 固定流程与动态选择怎样取舍
当前循环的候选顺序是primary、secondary,属于确定性的反馈演示。真实ReAct应用可以由模型根据观察提出下一工具。应用仍限制可用工具和参数,保留最大动作数与停止原因。
| 任务 | 适合的组织方式 | 原因 |
|---|---|---|
| 固定先查A再查B | 程序工作流 | 步骤已知,容易测试 |
| 根据新信息选择查询对象 | 反馈循环 | 下一步依赖观察 |
| 多阶段明确依赖任务 | 先计划再执行 | 方便检查步骤遗漏 |
| 金额写入与批准 | 明确审批流程 | 操作对象与金额要绑定 |
Anthropic工程模式对照了预定路径的工作流与动态Agent。选择时比较任务结果、动作数、等待时间与费用,而不是仅看模式名称。
5. 保存测试与来源
同目录创建test_react_lab.py:
下面的测试检查主资料为空后改查备用来源、连续工具失败和未允许的工具请求。
测试代码:test_react_lab.py
# 核对ReAct的正常结果和错误分支;断言失败时会显示对应的测试。
import unittest
from react_lab import run
class ReactLabTests(unittest.TestCase):
def test_first_source_finishes_in_one_action(self):
result = run("found-first")
self.assertEqual(result["status"], "completed")
self.assertEqual(sum(e["type"] == "action" for e in result["events"]), 1)
self.assertEqual(result["events"][-1]["citation"], "primary")
def test_second_source_uses_feedback(self):
result = run("found-second")
self.assertEqual(result["status"], "completed")
self.assertEqual(sum(e["type"] == "action" for e in result["events"]), 2)
self.assertIsNone(next(e for e in result["events"] if e["type"] == "observation")["text"])
self.assertEqual(result["events"][-1]["citation"], "secondary")
def test_missing_evidence_stops_unknown(self):
result = run("missing")
self.assertEqual(result["status"], "unknown")
self.assertIsNone(result["answer"])
def test_budget_limit_is_not_success(self):
result = run("found-second", max_actions=1)
self.assertEqual(result["status"], "budget_exhausted")
self.assertIsNone(result["answer"])
def test_repeated_tool_failure_escalates(self):
result = run("tool-failure")
self.assertEqual(result["status"], "needs_human")
self.assertEqual(result["events"][-1]["reason"], "repeated_tool_failure")
def test_proposal_and_program_check_are_distinct(self):
events = run("found-first")["events"]
self.assertEqual([e["type"] for e in events[:4]], ["scripted_proposal", "program_check", "action", "observation"])
def test_forbidden_proposal_is_rejected_before_action(self):
result = run("forbidden-proposal")
self.assertEqual(result["status"], "rejected")
self.assertFalse(any(e["type"] == "action" for e in result["events"]))
self.assertFalse(next(e for e in result["events"] if e["type"] == "program_check")["allowed"])
if __name__ == "__main__":
unittest.main()python -m unittest test_react_lab -v七项测试检查首轮命中、二次查询、无资料、额度、工具失败和禁用工具。来源可以进一步保存文档编号、片段与版本。回答引用一个来源后,读者应能沿它回到实际资料。
连续重复同一来源时,比较请求参数和结果是否有进展,必要时换方法或结束。回注错误时保留具体类别,方便下一轮区分无资料与查询失败。
回顾与下一步
提出动作,检查后执行,观察返回,再选下一步或结束。接下来 计划式执行提前安排带依赖的步骤。
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · 先判断边界
关于“ReAct 的用途与局限”,哪种做法更稳妥?
对应知识点:L3-react
简答题 · 解释失败
写出一个失败输入并说明记录字段。
对应知识点:L3-react
代码题 · 边界测试
补充正常、边界、失败三组输入和预期结果。
对应知识点:L3-react
场景题 · 结果未知
证据不足时如何设计状态和人工接管?
对应知识点:L3-react
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。