ReAct:让 Agent 学会思考
# 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}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 关键设计点
- 强制结构化输出:用 "Thought:" "Action:" "Observation:" 标记,让 LLM 的输出可被程序解析。
- 工具结果由框架注入:"Observation:" 不是 LLM 生成的,是框架执行工具后拼接上去的。
- 循环终止条件:当 LLM 输出 finish(answer) 时结束循环。也可设置最大步数防止死循环。
- 推理可见性: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("北京今天天气如何?适合户外活动吗?")
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(
"北京、上海、广州三城市今天天气如何?哪个最适合户外活动?"
)
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(
"解释量子纠缠的原理,并说明它与经典关联的区别"
)
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)
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 → 接近模型上下文窗口上限
# 三种压缩策略
截断(Truncation):最粗暴——直接丢弃最早的 N 条消息。优点是简单,缺点是可能丢失关键信息。
messages = messages[-K:] # 只保留最近K条1
2摘要(Summarization):用 LLM 将早期对话压缩成摘要。保留语义,但消耗额外 LLM 调用。
summary = llm.summarize(old_msgs) messages = [summary] + recent_msgs1
2语义保护型压缩:最精细——锁定核心指令,只压缩中间步骤。确保关键信息不丢失。
locked = [system, question] compressed = compress(middle) messages = locked + compressed + recent1
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节省!
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年诺贝尔物理学奖得主是谁?")
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++;
}
}
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++;
}
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++;
}
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; // 进入下一轮
}
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;
}
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();
// ...
}
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;
}
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 的反思推理能力是不同维度的问题,不能替代。