AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

ReAct:看一次结果,再决定下一步

我的学习 →

正在读取学习记录…

1. 动作与观察怎样交替

ReAct把推理与动作交替组织:先根据目标和已有资料选择动作,执行后取得观察,再根据新观察决定下一步。Action是发出的动作,Observation是工具返回的结果,Final是最终答复。

例如用户问退款期限,第一次查常见问题没有找到,第二次改查政策文档,取得7天的规定后回答。这像查书时先看目录,没找到就换索引:后一个动作利用了前一次查找结果。

查询主资料返回空,转向备用资料返回7天,再带来源回答
每次查询产生一条观察。取得证据后结束,仍无证据时保留未确认结果。

ReAct论文提出推理轨迹与环境动作交错使用的方法。工程中可以观察工具请求、返回和状态变化;模型选择动作的逻辑接入应用程序,由程序检查工具、参数和预算。

2. 先运行固定资料的反馈循环

先在 PowerShell 创建并进入学习目录:

Bash
mkdir "F:\学习\l3-react" -Force
cd "F:\学习\l3-react"

保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。

同一新目录创建react_lab.py。资料和候选顺序固定在代码中,便于逐次看到反馈怎样改变路径。

先在 PowerShell 创建并进入学习目录:

Bash
mkdir "F:\学习\l3-react" -Force
cd "F:\学习\l3-react"

保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。

Python
"""通过两份固定资料观察动作与结果交替。"""
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))
Bash
ls
python react_lab.py found-second

这条命令先查primary,观察为空,再查secondary。关键事件按顺序是:

TXT
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. 用几条命令比较不同结果

Bash
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
Python
# 核对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()
Bash
python -m unittest test_react_lab -v

七项测试检查首轮命中、二次查询、无资料、额度、工具失败和禁用工具。来源可以进一步保存文档编号、片段与版本。回答引用一个来源后,读者应能沿它回到实际资料。

连续重复同一来源时,比较请求参数和结果是否有进展,必要时换方法或结束。回注错误时保留具体类别,方便下一轮区分无资料与查询失败。

回顾与下一步

提出动作,检查后执行,观察返回,再选下一步或结束。接下来 计划式执行提前安排带依赖的步骤。

课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v1.0.1

选择题 · 先判断边界

关于“ReAct 的用途与局限”,哪种做法更稳妥?

对应知识点:L3-react

选择一个答案
练习 2 / 4 · v1.0.0

简答题 · 解释失败

写出一个失败输入并说明记录字段。

对应知识点:L3-react

练习 3 / 4 · v1.0.0

代码题 · 边界测试

补充正常、边界、失败三组输入和预期结果。

对应知识点:L3-react

练习 4 / 4 · v1.0.0

场景题 · 结果未知

证据不足时如何设计状态和人工接管?

对应知识点:L3-react

图片查看器
100%

文章配图 · 按 Esc 关闭