ReAct:让 Agent 学会思考

7/6/2026 AI AgentReAct思维链CoT

# ReAct:让 Agent 学会思考

让 Agent 学会"边想边做"的经典范式

🔗 从上一章延续的问题

第3章的天气 Agent 能查天气、能规划行程,但它不会思考为什么要查——用户问"明天适合跑步吗",它直接调 get_weather,却没有先推理"适合跑步需要看天气+空气质量"。这导致了两个问题:① 可能漏掉该调的工具,② 可能调了不必要的工具浪费 token。ReAct 就是为了解决这两个问题而诞生的——让 Agent 在行动前先思考,观察后再反思

🎯 本章先掌握 3 件事

这一章内容很多,但如果你是第一次接触 ReAct,只要先搞懂三件事就够了:为什么要先想再做、Thought / Action / Observation 如何形成闭环、它和上一章工具调用有什么关系

你可以先读 4.1 到 4.4,把 ReAct 的核心循环吃透;4.5 之后的 ReWOO、LLM Compiler、Reflexion 和生产级主循环更适合作为进阶阅读,不需要一次全吞下去。

# 4.1 从一个问题开始

假设用户问 Agent:"2024年诺贝尔物理学奖得主是谁?他们的研究领域是什么?"

一个没有规划能力的 Agent 可能直接让 LLM 回答——但 LLM 的知识截止到训练日期,可能不知道 2024 年的诺奖。它需要搜索。但什么时候搜?搜什么?搜完之后做什么?

这就是 ReAct 要解决的问题。

ReAct = Reasoning + Acting

让 LLM 交替进行"推理(Thought)"和"行动(Action)",通过"观察(Observation)"获取反馈,循环直到得出最终答案。

# 4.2 ReAct 的诞生背景

在 ReAct 之前,Agent 面临两难:

只推理(Reasoning-only) 只行动(Acting-only)
LLM 用 CoT 一步步推理 LLM 直接调用工具
✓ 推理能力强 ✓ 能获取外部信息
✗ 无法获取外部信息 ✗ 没有推理规划
✗ 可能产生幻觉 ✗ 不知道下一步做什么
→ 想得到,做不到 → 做得到,想不到

2022 年,Yao 等人在论文 "ReAct: Synergizing Reasoning and Acting in Language Models" 中提出了 ReAct 范式,将两者结合:

💡 ReAct 的核心洞察

**推理指导行动,行动反馈推理。**LLM 先"想"该做什么(Thought),再"做"(Action),然后"看"结果(Observation),基于结果再"想"下一步——形成闭环。

# 4.3 ReAct 循环:Thought → Action → Observation

ReAct 的核心是三个角色的循环:

流程:Thought(推理思考) → Action(调用工具) → Observation(观察结果) → Final Answer(最终回答)

  • Thought →[决定做什么]→ Action
  • Action →[工具返回]→ Observation
  • Observation →[继续推理]→ Thought(虚线,回到循环)
  • Observation →[信息足够]→ Final Answer

# 逐步演示:一个完整的 ReAct 执行

Thought #1

我需要先搜索2024年诺贝尔物理学奖得主。

Action #1

search("2024年诺贝尔物理学奖得主")

Observation #1

搜索结果:2024年诺贝尔物理学奖授予 John Hopfield 和 Geoffrey Hinton,表彰他们在机器学习和神经网络领域的基础发现。

Thought #2

知道了得主是 Hopfield 和 Hinton。现在需要了解他们的具体研究领域。Hinton 以深度学习闻名,Hopfield 以 Hopfield 网络闻名。我已有足够信息回答。

Action #2

finish("2024年诺贝尔物理学奖得主是 John Hopfield 和 Geoffrey Hinton...")

Observation #2

任务完成,输出最终答案。

Final Answer

2024年诺贝尔物理学奖授予 John Hopfield 和 Geoffrey Hinton。Hopfield 的研究领域是物理学和神经网络的交叉——Hopfield 网络;Hinton 被称为"深度学习之父",研究领域是深度学习和反向传播算法。

# 4.4 ReAct 的 Prompt 模板

ReAct 的实现核心是 System Prompt。来看一个标准的 ReAct prompt:

尽可能回答以下问题。你可以使用以下工具:

**可用工具:**
- search(query): 搜索互联网获取信息
- lookup(keyword): 在上一次搜索结果中查找关键词
- finish(answer): 提交最终答案

**格式要求:**
每一步必须使用以下格式:

Thought: 你对下一步该做什么的推理
Action: 工具名称(参数)

Observation: 工具返回的结果
... (Thought/Action/Observation 可以重复多次)

Thought: 我现在知道答案了
Action: finish(最终答案)

**开始!**
问题: {user_question}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 关键设计点

  1. 强制结构化输出:用 "Thought:" "Action:" "Observation:" 标记,让 LLM 的输出可被程序解析。
  2. 工具结果由框架注入:"Observation:" 不是 LLM 生成的,是框架执行工具后拼接上去的。
  3. 循环终止条件:当 LLM 输出 finish(answer) 时结束循环。也可设置最大步数防止死循环。
  4. 推理可见性:Thought 部分是 LLM 的"内心独白",让推理过程可审计、可调试。

# 4.5 ReWOO 模式(Reasoning Without Observation)

ReAct 的核心循环是"边想边做"——每一步 Thought 之后立刻 Action,获得 Observation 后再想下一步。这很直观,但有一个严重的问题:每一步的 Observation 都会被塞入上下文,随着步数增加,token 消耗线性膨胀。

ReWOO 提出了一个反直觉的思路:先想好所有步骤,再统一执行。

ReWOO = Reasoning Without Observation

2023 年由 Xu 等人提出。核心思想:让 LLM 在没有 Observation 的情况下,先规划出完整的推理链(Planner),然后由 Worker 统一执行所有工具调用,最后由 Solver 综合所有结果给出答案。

# Planner → Worker → Solver 三阶段

ReWOO 把 ReAct 的循环拆成了三个独立阶段:

流程:Planner(规划所有步骤) →[输出步骤列表]→ Worker(并行执行工具) →[返回所有结果]→ Solver(综合结果回答)

🔍 三阶段详解

Planner(规划器):LLM 一次性生成完整的推理链,每一步用 #E 标记期望的工具调用。因为还没有 Observation,Planner 必须靠自身推理决定步骤——这就是 "Without Observation" 的含义。

Worker(执行器):逐个执行 Planner 规划的工具调用,将结果填入 #E 占位符。Worker 是程序化的,不需要 LLM 参与——纯工具执行。

Solver(求解器):LLM 接收已填入结果的完整推理链,综合所有信息生成最终答案。此时 LLM 看到的是"规划+真实结果"的完整视图。

# ReAct vs ReWOO 流程对比

流程(ReAct 串行 6 步):Thought₁ → Action₁ → Obs₁ → Thought₂ → Action₂ → Obs₂ → Answer

流程(ReWOO 3 步):Planner →[步骤列表]→ Worker(并行执行) →[所有结果]→ Solver → Answer

ReAct:边想边做

  • 每一步 Thought 都需要携带完整历史上下文
  • Observation 逐步累积,token 随步数线性增长
  • LLM 调用次数 = 步数 × 2(Thought + 解析)
  • 优势:灵活调整,每步可基于 Observation 改变策略

ReWOO:先想后做

  • Planner 只调用 LLM 1次,生成完整推理链
  • Worker 执行工具,不消耗 LLM token
  • Solver 只调用 LLM 1次,综合答案
  • 总 LLM 调用 = 2次,远少于 ReAct 的 N 次

# ReWOO 代码示例

from openai import OpenAI

client = OpenAI()
TOOLS = {"search": search_web, "weather": get_weather, "calc": calculate}

# ===== Phase 1: Planner =====
PLANNER_PROMPT = """你是一个规划器。根据用户问题,生成一系列步骤来解决问题。
每一步用 #E 标记需要执行的工具调用。

格式:
Plan: 第一步的推理
#E1 = tool_name("参数")
Plan: 第二步的推理(可引用 #E1 的结果)
#E2 = tool_name("参数")
...

问题: {question}"""

def plan_steps(question):
    """Phase 1: LLM 一次性规划所有步骤"""
    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "system", "content": PLANNER_PROMPT},
                  {"role": "user", "content": question}]
    )
    return resp.choices[0].message.content

# ===== Phase 2: Worker =====
def execute_steps(plan_text):
    """Phase 2: 解析并执行所有 #E 工具调用"""
    import re
    results = {}
    # 查找所有 #E 标记
    steps = re.findall(r'#E(\d+)\s*=\s*(\w+)\((".*?"|\'.*?\'|.*?)\)', plan_text)
    for step_id, tool_name, tool_input in steps:
        tool_input = tool_input.strip('"\'')
        if tool_name in TOOLS:
            results[f"#E{step_id}"] = TOOLS[tool_name](tool_input)
        else:
            results[f"#E{step_id}"] = f"Error: {tool_name} not found"
    return results

# ===== Phase 3: Solver =====
SOLVER_PROMPT = """根据以下规划和执行结果,给出最终答案。

规划:
{plan}

执行结果:
{results}

请综合以上信息,给出完整答案。"""

def solve_answer(plan_text, results):
    """Phase 3: LLM 综合所有结果生成答案"""
    # 将结果填入规划的 #E 占位符
    filled_plan = plan_text
    for key, value in results.items():
        filled_plan = filled_plan.replace(key, f"[结果: {value}]")
    
    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "system", "content": SOLVER_PROMPT},
                  {"role": "user", "content": 
                   f"规划:\n{plan_text}\n\n执行结果:\n{filled_plan}"}]
    )
    return resp.choices[0].message.content

# ===== 完整流程 =====
def rewoo_agent(question):
    """ReWOO: Planner → Worker → Solver"""
    # 1. 规划
    plan = plan_steps(question)
    # 2. 执行
    results = execute_steps(plan)
    # 3. 求解
    answer = solve_answer(plan, results)
    return answer

# 使用
answer = rewoo_agent("北京今天天气如何?适合户外活动吗?")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

⚠️ ReWOO 的局限

ReWOO 的 Planner 必须在没有 Observation 的条件下规划所有步骤,这意味着它无法根据中间结果调整策略。如果第一步的搜索结果出乎意料,Planner 无法"转向"——它只能沿着预设路径走到底。这是 ReWOO 相比 ReAct 的核心劣势。

# 4.6 LLM Compiler 模式

ReAct 是串行的:Thought₁ → Action₁ → Observation₁ → Thought₂ → Action₂ → …。如果 Agent 需要调用 5 个独立的工具,即使它们之间没有依赖关系,也必须一个一个等。

LLM Compiler 提出了并行执行的思路:让 LLM 一次性输出多个独立的工具调用,然后并行执行它们。

LLM Compiler

2023 年由 Kim 等人提出。核心思想:将 Agent 的执行计划编译成依赖图(DAG),识别可并行的步骤,同时执行没有依赖关系的工具调用,显著减少总执行时间。

# 依赖图分析:识别可并行的步骤

关键洞察:很多工具调用之间没有数据依赖。比如:

串行(ReAct)

Thought → search("北京天气") → Obs₁ → Thought → search("上海天气") → Obs₂ → Thought → search("广州天气") → Obs₃ → Thought → 比较三城市 → Answer

总计:4轮LLM + 3次搜索 = ~12s

并行(LLM Compiler)

Planner → 并行执行 [search("北京天气"), search("上海天气"), search("广州天气")] → Solver → Answer

总计:1轮LLM + 1次并行搜索 = ~4s

流程:用户问题 → 编译器(生成DAG) → Tool A(独立)/ Tool B(独立)/ Tool C(依赖A) → Solver(综合)

  • 编译器 →[并行]→ Tool A、Tool B
  • 编译器 →[需等A]→ Tool C(虚线,需等待)
  • Tool A →[A的结果]→ Tool C(虚线)
  • Tool A、Tool B、Tool C → Solver(综合)

# 并行调用多个工具:代码示例

import asyncio
from openai import OpenAI

client = OpenAI()

# ===== Step 1: 编译器生成并行计划 =====
COMPILER_PROMPT = """根据用户问题,生成工具调用计划。
标记依赖关系:如果一个调用需要另一个的结果,标注 depends_on。

输出JSON格式:
[
  {"id": 1, "tool": "search", "args": {"query": "北京天气"},
   "depends_on": []},
  {"id": 2, "tool": "search", "args": {"query": "上海天气"},
   "depends_on": []},
  {"id": 3, "tool": "compare", "args": {"data_from": [1, 2]},
   "depends_on": [1, 2]}
]

问题: {question}"""

async def compile_plan(question):
    """LLM 编译:生成带依赖关系的工具调用计划"""
    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "system", "content": COMPILER_PROMPT},
                  {"role": "user", "content": question}],
        response_format={"type": "json_object"}
    )
    import json
    return json.loads(resp.choices[0].message.content)

# ===== Step 2: 并行执行独立工具 =====
async def execute_parallel(plan):
    """按依赖图并行执行:无依赖的步骤同时运行"""
    results = {}
    completed = set()
    
    while len(completed) < len(plan):
        # 找出所有依赖已完成的步骤
        ready = [step for step in plan
                 if step["id"] not in completed
                 and all(d in completed for d in step["depends_on"])]
        
        if not ready:
            break  # 防止死锁
        
        # 并行执行所有就绪步骤
        tasks = []
        for step in ready:
            task = asyncio.create_task(
                call_tool_async(step["tool"], step["args"], results)
            )
            tasks.append((step["id"], task))
        
        # 等待本轮所有任务完成
        for step_id, task in tasks:
            results[step_id] = await task
            completed.add(step_id)
    
    return results

async def call_tool_async(tool_name, args, prev_results):
    """异步调用单个工具"""
    # 替换依赖引用为实际结果
    if "data_from" in args:
        args["data"] = [prev_results[d] for d in args["data_from"]]
    
    # 模拟工具执行(实际项目中替换为真实API调用)
    await asyncio.sleep(1)  # 模拟网络延迟
    return f"{tool_name} 结果: {args}"

# ===== Step 3: Solver 综合答案 =====
async def solve_with_results(question, plan, results):
    """Solver:根据工具调用结果综合最终答案"""
    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": "根据以下工具调用结果,回答用户问题。"},
            {"role": "user", "content": f"问题: {question}\n\n结果: {results}"}
        ]
    )
    return resp.choices[0].message.content

async def llm_compiler_agent(question):
    """LLM Compiler 完整流程"""
    plan = await compile_plan(question)
    results = await execute_parallel(plan)
    answer = solve_with_results(question, plan, results)
    return answer

# 使用
answer = await llm_compiler_agent(
    "北京、上海、广州三城市今天天气如何?哪个最适合户外活动?"
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

📊 效率对比

指标 ReAct(串行) LLM Compiler(并行)
LLM 调用次数 N 次(每步1次) 1 次编译 + 1 求解
工具执行时间 N × 单次耗时 ≈ max(并行组耗时)
总耗时(3个独立工具) ~12s ~4s(3x加速)
灵活性 高(可中途调整) 低(预编译路径)

# 4.7 Reflexion 模式(反思校验)

ReAct 有一个隐含假设:LLM 的推理是对的。它只观察工具返回的外部结果,从不质疑自己的推理质量。如果 LLM 在 Thought 中犯了逻辑错误,ReAct 无法发现——它只会继续沿着错误方向走。

Reflexion 给 Agent 加了一个自我反思环节。

Reflexion = ReAct + Self-Reflection

2023 年由 Shinn 等人提出。核心思想:Agent 执行完任务后,回头审视自己的推理过程,评估结果质量,发现问题则重新尝试。通过多轮"执行→反思→改进"循环,逐步提升答案质量。

流程:Act(执行任务) →[输出结果]→ Evaluate(评估结果) →[质量达标]→ Done(质量达标)

  • Evaluate →[质量不足]→ Reflect(反思推理)(虚线)
  • Reflect →[生成反思]→ Retry(带着反思重试)
  • Retry →[重新执行]→ Act(虚线,回到循环)

# ReAct vs Reflexion:观察什么?

ReAct:只观察外部 Reflexion:还观察自身
Thought: 我觉得应该搜X 执行任务,得到结果
Action: search("X") 评估结果质量:答案是否完整?
Observation: 搜索结果... 反思推理过程:决策是否合理?
→ 观察工具返回的结果 → 观察外部结果 + 自身推理质量
→ 不评估"搜X这个决策本身是否合理" → 发现推理错误可以修正

# Reflexion 循环实现

from openai import OpenAI

client = OpenAI()

REFLECT_PROMPT = """你刚刚完成了一个任务,但结果不够好。
回顾你的推理过程和执行结果,找出问题所在。

之前的尝试:
{trajectory}

评估反馈:
{evaluation}

请反思:
1. 哪一步推理出了问题?
2. 应该怎么做才更好?
3. 下次尝试时需要注意什么?

输出格式:
Reflection: 你的反思内容"""

def react_agent_with_context(question, context=""):
    """带上下文的 ReAct Agent(复用 react_agent 逻辑,注入额外上下文)"""
    messages = [{"role": "system", "content": REACT_PROMPT + context}]
    messages.append({"role": "user", "content": question})
    trajectory = []
    for step in range(10):
        resp = client.chat.completions.create(model="gpt-4o", messages=messages)
        thought = resp.choices[0].message.content
        trajectory.append(thought)
        if "finish" in thought.lower():
            return {"answer": thought, "trajectory": "\n".join(trajectory)}
        messages.append({"role": "assistant", "content": thought})
    return {"answer": thought, "trajectory": "\n".join(trajectory)}

def reflexion_agent(question, max_trials=3):
    """Reflexion: 执行→评估→反思→重试"""
    reflections = []  # 累积的反思经验
    
    for trial in range(max_trials):
        # 1. 执行任务(使用 ReAct + 历史反思作为额外上下文)
        context = ""
        if reflections:
            context = f"\n之前的反思经验:\n" + "\n".join(reflections)
        
        result = react_agent_with_context(question, context)
        
        # 2. 评估结果质量
        evaluation = evaluate_result(question, result)
        
        if evaluation["score"] >= 0.8:  # 质量达标
            return result
        
        # 3. 反思推理过程
        reflection = client.chat.completions.create(
            model="gpt-4o",
            messages=[
                {"role": "system", "content": REFLECT_PROMPT},
                {"role": "user", "content": 
                 f"之前的尝试:\n{result['trajectory']}\n\n"
                 f"评估反馈:\n{evaluation['feedback']}"}
            ]
        ).choices[0].message.content
        
        reflections.append(reflection)
        print(f"Trial {trial+1}: score={evaluation['score']:.2f}")
        print(f"Reflection: {reflection[:100]}...")
    
    return result  # 达到最大尝试次数,返回最后结果

def evaluate_result(question, result):
    """LLM 评估答案质量"""
    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": 
             "评估以下答案的质量。输出JSON: {\"score\": 0-1, \"feedback\": \"...\"}"},
            {"role": "user", "content": 
             f"问题: {question}\n答案: {result['answer']}"}
        ],
        response_format={"type": "json_object"}
    )
    import json
    return json.loads(resp.choices[0].message.content)

# 使用
answer = reflexion_agent(
    "解释量子纠缠的原理,并说明它与经典关联的区别"
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

💡 Reflexion 的核心价值

Reflexion 不只是"多做几遍"。它的关键是反思内容会累积——每轮尝试的反思都被保存下来,作为下一轮的额外上下文。这意味着 Agent 在"从失败中学习",而不是盲目重试。这就像人类做题:第一次做错,反思原因,第二次带着反思去做,大概率比第一次好。

# 4.8 Structured Output(结构化输出)

ReAct 用 "Thought:" "Action:" 标记强制 LLM 输出特定格式,但文本标记解析极不稳定——LLM 可能输出 "Thought: ..." 或 "思考: ..." 或干脆不写标记。这是 Agent 开发中最头疼的问题之一。

结构化输出(Structured Output)就是为了彻底解决 LLM 输出不可靠的问题。

Structured Output

通过 JSON Schema 约束,让 LLM 的输出严格符合预定义的结构——字段名、类型、必填项、枚举值都被锁定。LLM 不再"自由发挥",而是像填表一样输出。

# 三种方式对比

📊 JSON Mode vs Function Calling vs Structured Output

方式 约束强度 字段类型保证 必填项保证 适用场景
JSON Mode 弱(只保证是合法JSON) ✗ 不保证 ✗ 不保证 简单JSON输出
Function Calling 中(参数有Schema) △ 部分保证 △ 部分保证 工具调用
Structured Output 强(完整Schema约束) ✓ 严格保证 ✓ 严格保证 Agent结构化推理

# OpenAI 结构化输出 + Pydantic 验证

from pydantic import BaseModel, Field
from openai import OpenAI
from typing import List, Optional

client = OpenAI()

# ===== 1. 用 Pydantic 定义输出 Schema =====
class AgentStep(BaseModel):
    """Agent 单步推理的结构化输出"""
    thought: str = Field(
        description="对当前状态的推理分析"
    )
    action: str = Field(
        description="要调用的工具名称",
        enum=["search", "lookup", "calculate", "finish"]
    )
    action_input: str = Field(
        description="工具的输入参数"
    )
    confidence: float = Field(
        description="对当前推理的置信度 0-1",
        ge=0, le=1
    )

class AgentResponse(BaseModel):
    """Agent 完整响应的结构化输出"""
    steps: List[AgentStep] = Field(
        description="推理步骤列表"
    )
    final_answer: Optional[str] = Field(
        description="最终答案(仅当 action=finish 时提供)",
        default=None
    )
    needs_more_info: bool = Field(
        description="是否需要更多信息才能回答"
    )

# ===== 2. 用 OpenAI Structured Output 调用 =====
def structured_agent(question):
    """使用 Structured Output 的 Agent"""
    response = client.beta.chat.completions.parse(
        model="gpt-4o-2024-08-06",
        messages=[
            {"role": "system", "content": 
             "你是一个研究助手。分析问题并给出推理步骤。"},
            {"role": "user", "content": question}
        ],
        response_format=AgentResponse  # Pydantic 模型直接作为 Schema
    )
    
    # 3. 返回的是已验证的 Pydantic 对象
    result = response.choices[0].message.parsed
    
    # 类型安全!result.thought 是 str,result.confidence 是 float
    for step in result.steps:
        print(f"Thought: {step.thought}")
        print(f"Action: {step.action}({step.action_input})")
        print(f"Confidence: {step.confidence:.2f}")
    
    if result.final_answer:
        print(f"Answer: {result.final_answer}")
    
    return result

# ===== 3. Pydantic 自动验证 =====
# 如果 LLM 输出的 confidence 超出 0-1 范围,Pydantic 会自动拒绝
# 如果 action 不是枚举值之一,Pydantic 会自动拒绝
# 如果 steps 是空列表,可以根据业务规则添加额外验证

def validate_agent_response(response: AgentResponse):
    """业务层面的额外验证"""
    if not response.steps:
        raise ValueError("Agent 必须至少输出一个推理步骤")
    if response.needs_more_info and response.final_answer:
        raise ValueError("标注需要更多信息时不应有最终答案")
    if response.steps[-1].action == "finish" and not response.final_answer:
        raise ValueError("finish 动作必须提供最终答案")
    return True

# 使用
result = structured_agent("量子计算和经典计算的本质区别是什么?")
validate_agent_response(result)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

✓ Structured Output 的核心优势

  • 100% 格式保证:LLM 输出严格符合 Schema,无需正则解析
  • 类型安全:字段类型、枚举值、范围约束全部锁定
  • 可验证:Pydantic 自动验证 + 业务规则二次校验
  • 开发效率:不用写解析代码,直接用 Pydantic 模型

# 4.9 上下文压缩实战

ReAct 的致命问题是上下文膨胀。每一步 Thought + Action + Observation 都被追加到消息历史中,10步任务可能积累上千 token 的上下文。到第20步,LLM 可能因为上下文太长而"遗忘"最初的问题。

⚠️ 上下文膨胀的真实数据

Step 1: ~200 tokens (system + question + thought₁ + action₁ + obs₁) Step 5: ~1,000 tokens (累积5轮) Step 10: ~2,000 tokens Step 20: ~4,000 tokens → LLM 开始遗忘早期信息 Step 50: ~10,000 tokens → 接近模型上下文窗口上限

# 三种压缩策略

  1. 截断(Truncation):最粗暴——直接丢弃最早的 N 条消息。优点是简单,缺点是可能丢失关键信息。

    messages = messages[-K:]
    # 只保留最近K条
    
    1
    2
  2. 摘要(Summarization):用 LLM 将早期对话压缩成摘要。保留语义,但消耗额外 LLM 调用。

    summary = llm.summarize(old_msgs)
    messages = [summary] + recent_msgs
    
    1
    2
  3. 语义保护型压缩:最精细——锁定核心指令,只压缩中间步骤。确保关键信息不丢失。

    locked = [system, question]
    compressed = compress(middle)
    messages = locked + compressed + recent
    
    1
    2
    3

# 语义保护型压缩:锁定核心指令

流程:System Prompt(🔒 锁定) + 用户问题(🔒 锁定) + 中间步骤(✂️ 压缩) + 最近3步(🔒 保留) → 压缩后上下文

  • System Prompt →[原样保留]→ 压缩后上下文
  • 用户问题 →[原样保留]→ 压缩后上下文
  • 中间步骤(1~N-3) →[压缩为摘要]→ 压缩后上下文
  • 最近3步 →[原样保留]→ 压缩后上下文

# 压缩前后 Token 对比

from openai import OpenAI

client = OpenAI()

def semantic_compress(messages, keep_recent=3):
    """语义保护型压缩:锁定核心指令,压缩中间步骤"""
    
    # 1. 锁定:System Prompt 和原始问题
    locked = messages[:2]  # system + user question
    
    # 2. 保留:最近K步(完整不压缩)
    recent = messages[-(keep_recent * 3):]  # 每步3条(thought+action+obs)
    
    # 3. 压缩:中间所有步骤 → 一段摘要
    middle = messages[2:-(keep_recent * 3)]
    
    if len(middle) > 6:  # 只有中间步骤足够多才压缩
        summary = client.chat.completions.create(
            model="gpt-4o-mini",  # 用小模型压缩,节省成本
            messages=[
                {"role": "system", "content": 
                 "将以下对话历史压缩为简短摘要,保留关键决策和发现。"
                 "忽略冗余的推理过程,只保留结论性信息。"},
                {"role": "user", "content": 
                 f"对话历史:\n{format_messages(middle)}"}
            ]
        ).choices[0].message.content
        
        compressed_middle = [
            {"role": "system", "content": f"[历史摘要] {summary}"}
        ]
    else:
        compressed_middle = middle
    
    # 4. 组合:锁定 + 压缩 + 保留
    return locked + compressed_middle + recent

def format_messages(messages):
    """格式化消息列表为可读文本"""
    text = ""
    for msg in messages:
        role = msg["role"]
        content = msg["content"][:200]  # 每条截断到200字
        text += f"[{role}] {content}\n"
    return text

# ===== Token 对比示例 =====
def compare_compression(original_msgs, compressed_msgs):
    """压缩前后 token 对比"""
    def count_tokens(msgs):
        # 粗略估算:1个中文字≈1.5token,1个英文词≈1token
        total = 0
        for msg in msgs:
            content = msg["content"]
            chinese_chars = sum(1 for c in content if '一' <= c <= '鿿')
            english_words = len(content.split()) - chinese_chars
            total += chinese_chars * 1.5 + english_words
        return int(total)
    
    orig_tokens = count_tokens(original_msgs)
    comp_tokens = count_tokens(compressed_msgs)
    ratio = comp_tokens / orig_tokens
    
    print(f"原始 token: ~{orig_tokens}")
    print(f"压缩 token: ~{comp_tokens}")
    print(f"压缩率: {(1-ratio)*100:.1f}%")
    return orig_tokens, comp_tokens

# ===== 实战数据 =====
# 假设一个20步 ReAct 任务:
# 原始消息数 = 2(system+question) + 20*3(steps) = 62条
# 原始 token ≈ 4000

# 压缩后消息数 = 2(锁定) + 1(摘要) + 3*3(保留) = 12条  
# 压缩后 token ≈ 800

# 压缩率: 80% token节省!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

📊 压缩实战数据(20步任务)

指标 未压缩 截断 摘要压缩 语义保护型
消息条数 62 18 20 12
估算 Token ~4000 ~1200 ~1000 ~800
核心指令安全 ✗ 可能丢失 △ 摘要可能遗漏 ✓ 锁定保护
额外 LLM 调用 0 0 1(摘要生成) 1(摘要生成)

# 4.10 用 50 行代码实现 ReAct

import re
from openai import OpenAI

client = OpenAI()
TOOLS = {"search": search_web, "lookup": lookup_keyword}

def react_agent(question, max_steps=10):
    messages = [{"role": "system", "content": REACT_PROMPT}]
    messages.append({"role": "user", "content": question})
    
    for step in range(max_steps):
        # 1. LLM 生成 Thought + Action
        response = client.chat.completions.create(
            model="gpt-4o", messages=messages
        )
        output = response.choices[0].message.content
        messages.append({"role": "assistant", "content": output})
        
        # 2. 解析 Action
        action_match = re.search(r'Action: (\w+)\((.*?)\)', output)
        if not action_match:
            break
            
        tool_name = action_match.group(1)
        tool_input = action_match.group(2).strip('"\'')
        
        # 3. 检查是否完成
        if tool_name == "finish":
            return tool_input
        
        # 4. 执行工具
        if tool_name in TOOLS:
            result = TOOLS[tool_name](tool_input)
        else:
            result = f"Error: tool {tool_name} not found"
        
        # 5. 注入 Observation
        messages.append({"role": "user", "content": f"Observation: {result}"})
    
    return "达到最大步数限制"

# 使用
answer = react_agent("2024年诺贝尔物理学奖得主是谁?")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

核心就这几步:LLM 生成 → 解析 Action → 执行工具 → 注入 Observation → 循环

# 4.11 ReAct 的优势与局限

✓ 优势

  • 简单直观:Thought-Action-Observation 三段式,易于理解和实现
  • 推理可审计:Thought 让每步推理可见,方便调试
  • 错误可恢复:Observation 反馈让 Agent 能发现错误并调整
  • 工具可组合:多步推理中可调用不同工具
  • 通用性强:不依赖特定模型,任何 LLM 都能用

✗ 局限

  • Token 消耗大:每步都带完整历史,长任务 token 消耗线性增长
  • 串行执行:Thought→Action→Observation 严格串行,无法并行
  • 依赖 LLM 质量:推理质量直接取决于 LLM 能力
  • 格式不稳定:LLM 可能输出错误格式导致解析失败
  • 容易循环:某些情况下 Agent 会陷入循环不收敛

# 4.12 ReAct 的演进与变体

本章介绍了 ReAct 的多个演进变体,下面是完整的 ReAct 家族演进图谱:

🔄 ReAct 家族演进

变体 改进点 核心机制 年份
ReAct 原始版:Thought-Action-Observation 推理+行动交替循环 2022
ReWOO 先规划再执行,减少token消耗 Planner→Worker→Solver 2023
LLM Compiler 并行执行独立工具调用 依赖图(DAG)编译+并行 2023
Reflexion 加入自我反思,从失败中学习 执行→评估→反思→重试循环 2023
LATS 结合蒙特卡洛树搜索+ReAct 树搜索+推理行动 2023
Function Calling ReAct 用原生 Function Calling 替代文本解析 结构化工具调用 2023+

# 4.13 工程深度:生产级 Agent 主循环设计

前面我们学习了 ReAct、ReWOO、Reflexion 等推理模式,但它们都是概念模型。生产环境中,Agent 主循环需要解决一系列工程问题:烧钱怎么控制?死循环怎么检测?AI 输出被截断怎么办?接口超时怎么重试?本节基于 WaLiCode 项目的 streamingAgent.ts 真实实现,讲解生产级 Agent 主循环的六大工程机制。

# 4.13.1 双模式设计:Chat vs Agent

WaLiCode 的 Agent 主循环支持两种模式,用对比表格展示差异:

维度 Chat 模式 Agent 模式
交互方式 一问一答,不自动续写 自动多轮,直到任务完成
最大轮次 1 轮 20 轮(可配置)
工具调用 每轮最多 5 次 每轮最多 5 次,总计最多 200 次
重试次数 3 次 5 次(更宽容)
适用场景 简单问答、闲聊 编码任务、文件操作、复杂工作流
Token 预算 无全局限制 200k token 全局预算

为什么需要两种模式?如果用 Agent 模式处理简单问答,AI 可能会"过度思考"——明明一句话能回答的问题,它非要调几个工具验证一下,浪费 token 和时间。反过来,用 Chat 模式处理编码任务,AI 无法自动连续执行多步操作,用户体验很差。

// 伪代码:双模式入口
interface AgentLoopConfig {
  mode: 'chat' | 'agent';
  maxRounds: number;          // chat: 1, agent: 20
  maxToolCallsPerRound: number; // 默认 5
  maxTotalToolCalls: number;   // agent: 200
  maxRetries: number;          // chat: 3, agent: 5
  tokenBudget: number;         // agent: 200000
}

async function runAgentLoop(
  messages: Message[],
  config: AgentLoopConfig,
  callbacks: StreamingCallbacks
) {
  let round = 0;
  let totalToolCalls = 0;
  let totalTokens = 0;

  while (round < config.maxRounds) {
    // ... 主循环逻辑
    round++;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 4.13.2 Token Budget 管理

Agent 模式下 AI 会自动多轮执行,如果不加限制,一个复杂任务可能消耗几十万 token——用户可能一觉醒来收到天价账单。WaLiCode 设计了全局 Token Budget 机制:

// 伪代码:Token Budget 管理
const GLOBAL_TOKEN_BUDGET = 200_000; // 20万 token 全局预算

let totalTokensAcrossRounds = 0;

while (round < maxRounds) {
  // 检查全局预算
  if (totalTokensAcrossRounds >= GLOBAL_TOKEN_BUDGET) {
    callbacks.onText('\n⚠️ 已达到全局 Token 预算限制,自动停止。');
    break;
  }

  // 流式接收 AI 输出
  for await (const event of ai.stream(currentMessages)) {
    if (event.type === 'text') {
      const deltaTokens = countTokens(event.content);
      totalTokensAcrossRounds += deltaTokens;
      // ... 实时累加
    }
  }
  round++;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

关键设计点:

  • 全局预算:不是单轮限制,而是整个会话的总量限制。20 万 token 约等于 15 万字中文,足够完成大部分编码任务
  • 实时累加:每收到一段流式输出就立即计数,不等一轮结束才统计。这样即使单轮输出就超预算也能及时停止
  • 优雅停止:超预算时不直接中断,而是注入提示让 AI 自然收尾,避免输出半截代码

# 4.13.3 死循环保护(Diminishing Returns Detection)

Agent 模式下 AI 可能陷入死循环——反复调用同一个工具、传完全相同的参数、得到完全相同的错误结果。这不罕见:AI 看到 FileNotFoundError,于是去 read_file,文件确实不存在,于是又去 write_file,写错了路径,又看到 FileNotFoundError……

WaLiCode 的检测策略:连续 2 轮执行完全相同的失败工具调用时自动终止

// 伪代码:死循环检测
let lastRoundToolCalls: string[] = [];
let lastRoundErrors: string[] = [];

while (round < maxRounds) {
  const currentRoundToolCalls: string[] = [];
  const currentRoundErrors: string[] = [];

  // 执行工具调用...
  for (const tc of toolCalls) {
    const signature = `${tc.name}:${JSON.stringify(tc.args)}`;
    currentRoundToolCalls.push(signature);
    if (tc.status === 'error') {
      currentRoundErrors.push(signature);
    }
  }

  // 检测:连续 2 轮相同的失败工具调用
  const sameErrors = currentRoundErrors.length > 0
    && currentRoundErrors.every(e => lastRoundErrors.includes(e))
    && currentRoundErrors.length === lastRoundErrors.length;

  if (sameErrors) {
    callbacks.onText('\n⚠️ 检测到重复失败,自动停止以避免死循环。');
    break;
  }

  lastRoundToolCalls = currentRoundToolCalls;
  lastRoundErrors = currentRoundErrors;
  round++;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

为什么阈值设为 2 而不是 1?因为有些问题需要重试一次才能确认——比如网络抖动导致的临时失败,第二次重试可能就成功了。设为 2 是在"尽早发现死循环"和"允许合理重试"之间的平衡。

# 4.13.4 max_tokens 截断恢复

AI 模型有输出长度限制(如 4096 token)。当输出被截断时,finish_reason 会变成 'length' 而不是 'stop'。如果直接忽略,用户会看到半截代码或不完整的回答。WaLiCode 的方案是注入 "please continue" 自动恢复

// 伪代码:截断恢复
for await (const event of ai.stream(currentMessages)) {
  if (event.type === 'text') {
    assistantContent += event.content;
  }
  if (event.type === 'finish') {
    if (event.finish_reason === 'length') {
      // 输出被 max_tokens 截断
      wasTruncated = true;
    }
  }
}

if (wasTruncated && round < maxRounds) {
  // 注入 "please continue" 让 AI 从截断处继续
  currentMessages.push({ role: 'assistant', content: assistantContent });
  currentMessages.push({ role: 'user', content: '请继续输出,从刚才截断的地方接着写。' });
  assistantContent = '';  // 重置,新轮次会追加
  wasTruncated = false;
  continue;  // 进入下一轮
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

注意事项:

  • 不是所有截断都应该恢复:如果 AI 已经输出了完整答案但 finish_reason 误报为 length,恢复会导致多余输出。需要结合内容判断
  • 恢复次数有限:最多恢复 2-3 次,避免无限恢复。如果连续截断说明任务太复杂,应该拆分
  • 上下文膨胀:每次恢复都会增加上下文长度,可能触发上下文压缩

# 4.13.5 指数退避重试

AI API 调用可能遇到临时性错误:429(限流)、503(服务不可用)、网络超时。直接报错会让用户重试,体验很差。WaLiCode 的方案是自动重试,指数退避:

// 伪代码:指数退避重试
const MAX_RETRIES = 5;  // agent 模式

for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
  try {
    // 尝试调用 AI API
    for await (const event of ai.stream(currentMessages)) {
      callbacks.onText(event.content, assistantContent);
    }
    break;  // 成功,跳出重试循环
  } catch (err) {
    if (!isRetryable(err)) throw err;  // 不可重试的错误直接抛出
    if (attempt === MAX_RETRIES) throw err;  // 重试次数用尽

    // 指数退避:1s → 2s → 4s → 8s → 16s
    const delayMs = Math.pow(2, attempt) * 1000;
    callbacks.onText(`\n⏳ 遇到临时错误,${delayMs/1000}s 后重试...`);
    await abortableSleep(delayMs);
  }
}

function isRetryable(err: Error): boolean {
  if (err instanceof HTTPError) {
    return [429, 500, 502, 503, 504].includes(err.status);
  }
  return err instanceof NetworkError || err instanceof TimeoutError;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

流程:调用 AI API →[成功]→ 成功 / →[异常]→ 出错 → 可重试?(429/5xx/超时) →[是]→ 等待 2^n 秒(1→2→4→8→16) →[重试]→ 调用 AI API(循环) / →[否]→ 抛出错误

# 4.13.6 Idle Timeout 与 413 反应式压缩

两个额外的保护机制:

Idle Timeout(空闲超时):10 分钟无活动自动停止。防止 Agent 在用户离开后继续烧钱。

const IDLE_TIMEOUT_MS = 10 * 60 * 1000; // 10 分钟

let lastActivityTime = Date.now();

// 在主循环中检查
if (Date.now() - lastActivityTime > IDLE_TIMEOUT_MS) {
  callbacks.onText('\n⚠️ 空闲超时,自动停止。');
  break;
}

// 每次收到事件时更新
for await (const event of ai.stream(...)) {
  lastActivityTime = Date.now();
  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

413 反应式压缩:当 API 返回 413(请求体过大)时,紧急压缩上下文后重试。

try {
  for await (const event of ai.stream(currentMessages)) {
    // ...
  }
} catch (err) {
  if (err instanceof HTTPError && err.status === 413) {
    // 请求体过大,紧急压缩上下文
    const summary = await generateAiSummary(currentMessages);
    currentMessages = [
      { role: 'system', content: '之前的对话已压缩:' + summary },
      ...recentMessages  // 保留最近的几轮
    ];
    // 用压缩后的上下文重试
    continue;
  }
  throw err;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

413 压缩与正常压缩的区别:

  • 触发时机:正常压缩在 token 达到 80% 时主动触发;413 压缩在请求被拒绝后被动触发
  • 压缩力度:正常压缩保留近期 3-5 轮;413 压缩可能只保留最近 1-2 轮,更激进
  • 用户感知:正常压缩用户无感;413 压缩可能丢失重要上下文,影响回答质量

🔗 4.13 核心要点

双模式:Chat 简单高效,Agent 自动多轮,按场景选择避免浪费。

Token Budget:全局预算防止烧钱,实时累加及时停止。

死循环保护:连续 2 轮相同失败自动终止,阈值 2 平衡了灵敏度和容错。

截断恢复:max_tokens 截断时注入 continue 自动续写,有限次恢复避免无限循环。

指数退避:临时错误自动重试,2^n 秒退避避免雪崩,区分可重试和不可重试错误。

413 压缩:请求过大时紧急压缩重试,是正常压缩的兜底方案。

# 面试八股

Q1: 什么是 ReAct 模式?它解决了什么问题?

A: ReAct = Reasoning + Acting,让 LLM 交替进行**推理(Thought)、行动(Action)、观察(Observation)**的循环,直到得出最终答案。

解决的问题:纯推理模型(CoT)无法获取外部信息,容易幻觉;纯行动模型直接调用工具但缺乏规划。ReAct 将推理和行动结合,推理指导行动,行动反馈推理。

Q2: ReAct 循环中 Thought、Action、Observation 各自的作用?

A: Thought(推理):LLM 的"内心独白",分析当前状态,决定下一步该做什么。是推理过程的核心,也是可审计性的基础。 Action(行动):基于 Thought 的决策,调用具体工具。格式为 Action: tool_name(input)。由 Agent 框架解析和执行。 Observation(观察):工具执行后返回的结果。注意:这不是 LLM 生成的,而是框架执行工具后注入到对话中的。LLM 基于 Observation 进行下一轮 Thought。

Q3: ReAct 的 Prompt 是怎么设计的?为什么需要强制格式?

A: Prompt 通过 "Thought:" "Action:" "Observation:" 标记强制结构化输出。

需要强制格式的原因:Agent 框架需要从 LLM 输出中解析出"要调用什么工具"和"传什么参数"。如果 LLM 输出自然语言,解析极其困难且不可靠。强制格式让输出可程序化解析,同时保持推理过程的可读性。

Q4: ReAct 模式有哪些局限性?后续如何改进?

A: 局限:① Token 消耗线性增长(每步带完整历史);② 严格串行无法并行;③ 格式解析不稳定;④ 可能陷入死循环。

改进方向:① ReWOO 先规划后执行减少token;② LLM Compiler 并行执行独立步骤;③ Reflexion 加入自我反思从失败学习;④ Structured Output 解决格式不稳定;⑤ 上下文压缩控制 token 消耗。

Q5: ReAct 中的 Observation 是 LLM 生成的吗?为什么?

A: 不是。 Observation 是 Agent 框架执行工具后,将真实结果注入到对话历史中的。

这是最容易踩坑的点。如果让 LLM 自己"想象" Observation,就失去了与真实世界交互的能力,变成纯粹的推理游戏。ReAct 的核心价值就在于 Action 真实执行、Observation 真实反馈。这也是 ReAct 相比纯 CoT 的根本优势。

Q6: ReWOO 与 ReAct 的核心区别是什么?

A: ReAct 是边思考边执行(Thought→Action→Observation 循环),每步都需要 LLM 参与,token 消耗随步数线性增长。 ReWOO 是先规划后执行(Planner→Worker→Solver),LLM 只调用2次(规划+求解),Worker 执行工具不消耗 LLM token。

ReWOO 的优势是 token 消耗大幅减少;劣势是 Planner 无法根据中间结果调整策略,如果第一步规划错误,后续只能沿着错误路径走到底。

Q7: LLM Compiler 如何实现并行执行?它的适用条件是什么?

A: LLM Compiler 让 LLM 一次性输出多个工具调用,构建依赖图(DAG),识别没有数据依赖的步骤同时执行。

适用条件:工具调用之间确实没有依赖关系(如并行搜索多个城市天气)。如果有依赖(如先搜结果再基于结果做计算),则依赖步骤必须等前置步骤完成。并行加速效果取决于独立步骤的占比。

Q8: Structured Output 相比文本解析(ReAct的 "Action:" 标记)有什么本质优势?

A: 文本解析:LLM 输出 "Action: search("query")",用正则解析。不稳定——LLM 可能输出格式偏差(少冒号、多空格、换行位置不同),导致解析失败。 Structured Output:通过 JSON Schema + Pydantic 约束,LLM 输出严格符合预定义结构。字段名、类型、枚举值、范围全部锁定,100% 可靠解析,无需正则。

本质区别:文本解析是"尽力而为",Structured Output 是"强制保证"

Q9: 上下文压缩的三种策略分别是什么?语义保护型压缩的核心思想?

A: 截断:直接丢弃最早的消息,简单但可能丢失关键信息。 摘要:用 LLM 将早期对话压缩成摘要,保留语义但消耗额外调用。 语义保护型压缩:将消息分为三层——锁定层(System Prompt + 原始问题,不压缩)、压缩层(中间步骤,摘要压缩)、保留层(最近K步,原样保留)。核心思想是确保关键指令永远不丢失,只压缩可牺牲的中间推理过程。

# 课后练习

# 第 1 题(单选)

ReAct 模式的全称是什么?

A. Reactive Action B. Reasoning + Acting C. Real-time Action D. Recursive Agent

答案与解析

答案:B

ReAct = Reasoning + Acting,将推理和行动交替进行,通过观察工具返回结果不断调整推理方向。

# 第 2 题(单选)

在 ReAct 循环中,Observation 是由谁生成的?

A. LLM 自己想象生成 B. Agent 框架执行工具后注入的真实结果 C. 用户手动输入 D. 系统随机生成

答案与解析

答案:B

Observation 是 Agent 框架执行工具后的真实结果,不是 LLM 生成的。这是 ReAct 与纯 CoT 的根本区别——Action 真实执行,Observation 真实反馈。

# 第 3 题(单选)

ReAct 相比纯 CoT(思维链)的最大优势是什么?

A. 推理速度更快 B. 能够与外部世界交互获取真实信息 C. Token 消耗更少 D. 不需要 LLM 就能运行

答案与解析

答案:B

CoT 只能基于 LLM 内部知识推理,无法获取外部信息。ReAct 通过 Action 调用工具获取真实信息,通过 Observation 反馈给 LLM,解决了信息获取问题。

# 第 4 题(单选)

ReAct 的循环终止条件通常是什么?

A. LLM 输出 finish(answer) 或达到最大步数 B. Token 消耗超过预算 C. 用户手动停止 D. 工具返回错误

答案与解析

答案:A

两种终止条件:① LLM 判断信息足够,输出 finish(answer) 提交最终答案;② 达到最大步数限制(防止死循环)。两者满足任一即停止。

# 第 5 题(单选)

ReAct Prompt 中为什么需要强制 "Thought:" "Action:" 格式?

A. 让输出更好看 B. 让 Agent 框架能程序化解析出工具调用 C. 减少 Token 消耗 D. 提高 LLM 推理质量

答案与解析

答案:B

强制格式是为了让 Agent 框架能从 LLM 输出中可靠地解析出"调用什么工具"和"传什么参数"。自然语言输出无法可靠解析。

# 第 6 题(单选)

ReWOO 模式中,Planner 的核心作用是什么?

A. 执行所有工具调用 B. 在没有 Observation 的情况下一次性规划所有推理步骤 C. 评估答案质量并决定是否重试 D. 将多个工具调用并行执行

答案与解析

答案:B

ReWOO 的 Planner 在没有 Observation 的情况下,靠自身推理一次性生成完整的推理链(用 #E 标记工具调用)。这就是"Reasoning Without Observation"的含义。

# 第 7 题(单选)

LLM Compiler 的核心加速原理是什么?

A. 使用更快的 LLM 模型 B. 减少 LLM 调用次数 C. 识别无依赖的工具调用并行执行 D. 压缩上下文减少 token

答案与解析

答案:C

LLM Compiler 构建依赖图(DAG),识别没有数据依赖的工具调用同时执行。3个独立搜索从串行12s降到并行4s,加速3倍。

# 第 8 题(单选)

语义保护型压缩的核心思想是什么?

A. 直接丢弃最早的消息 B. 将所有消息用 LLM 生成摘要 C. 锁定核心指令不压缩,只压缩中间步骤 D. 只保留最近1步消息

答案与解析

答案:C

语义保护型压缩将消息分为三层:锁定层(System Prompt+原始问题)、压缩层(中间步骤摘要化)、保留层(最近K步原样保留)。确保关键信息永远不丢失。

# 第 9 题(多选)

以下哪些是 ReAct 模式的优势?(多选)

A. 推理过程可见可审计(Thought 部分) B. 能通过工具获取外部真实信息 C. 错误可恢复(基于 Observation 调整) D. 支持多工具并行调用

答案与解析

答案:A、B、C

ReAct 是严格串行的(Thought→Action→Observation),不支持并行调用工具。其他三项都是 ReAct 的优势。

# 第 10 题(多选)

关于 ReAct 的局限性和改进,以下说法正确的是?(多选)

A. Token 消耗随步数线性增长是主要局限之一 B. Reflexion 在 ReAct 基础上加入了自我反思能力 C. Function Calling 可以替代文本解析提升格式稳定性 D. ReAct 可以自然支持多 Agent 协作

答案与解析

答案:A、B、C

ReAct 本身是单 Agent 模式,不直接支持多 Agent 协作(需要额外框架如 CrewAI)。其他三项都正确。

# 第 11 题(多选)

ReWOO 相比 ReAct 的优势和劣势分别是?(多选)

A. 优势:LLM 调用次数从 N 降到 2,token 大幅节省 B. 优势:可以根据中间结果灵活调整策略 C. 劣势:Planner 无法根据 Observation 调整规划 D. 劣势:Worker 执行工具仍消耗大量 LLM token

答案与解析

答案:A、C

ReWOO 的 Worker 是纯程序化工具执行,不消耗 LLM token,所以选项D错误。选项B是 ReAct 的优势而非 ReWOO 的——ReWOO 的 Planner 无法中途调整,这是它的劣势。

# 第 12 题(多选)

关于 Structured Output,以下说法正确的是?(多选)

A. JSON Mode 只保证输出是合法 JSON,不保证字段类型和必填项 B. Function Calling 的参数有 Schema 约束,但保证程度中等 C. Structured Output 通过完整 JSON Schema 约束,100% 保证格式 D. Structured Output 可以完全替代 Reflexion 的反思能力

答案与解析

答案:A、B、C

Structured Output 解决的是输出格式可靠性问题,与 Reflexion 的反思推理能力是不同维度的问题,不能替代。