第28章:Agent——让 LLM 自主完成多步任务
前面几章我们看到 LLM 如何回答问题、如何检索增强知识、如何调用外部工具。但这些场景有一个共同前提:每一步都由人类触发。人提问,LLM 回答;人决定下一步,LLM 继续回答。LLM 始终是被动的响应者。
现实中很多任务并非一问一答能解决的。"帮我调研一下量子计算的最新进展,整理成一份报告"——这需要搜索、阅读多篇文献、筛选信息、整合观点、最终撰写。如果每一步都要人类手动触发,效率极低。能不能给 LLM 一个目标,让它自己规划并执行?这就是 Agent(智能体)的核心动机。
28.1 从问答到任务的跨越
聊天模式 vs. Agent 模式
在聊天(Chat)模式下,控制权始终在人类手中:
人类 → [提问] → LLM → [回答] → 人类 → [决定下一步] → ...
在 Agent 模式下,LLM 获得了目标,并自主决定接下来做什么:
人类 → [给定目标] → Agent → [规划+执行+观察] → Agent → [循环] → ... → 人类 ← [最终结果]
这一跨越需要 LLM 具备四种核心能力的循环:
| 阶段 | 英文 | 含义 |
|---|---|---|
| 感知 | Perceive | 读取当前状态、上下文、工具返回结果 |
| 规划 | Plan | 决定下一步应该做什么 |
| 行动 | Act | 调用工具或生成输出 |
| 观察 | Observe | 获取行动结果,更新状态 |
这四个阶段不断循环,直到目标完成或达到终止条件。
一个具体例子:撰写研究报告
假设目标是"写一份关于 Transformer 架构最新进展的研究报告"。Agent 的执行轨迹可能是:
- 规划:这个任务需要搜索近期论文、阅读摘要、整合关键进展、撰写报告
- 行动:调用搜索工具,查询"Transformer architecture 2024 advances"
- 观察:获得 10 条搜索结果,包含标题和摘要
- 规划:筛选出 3 篇最相关的论文,分别获取详细内容
- 行动:调用网页读取工具,获取论文详情(重复 3 次)
- 观察:得到三篇论文的关键内容
- 行动:整合信息,生成报告草稿
- 规划:报告是否满足要求?是 → 结束
整个过程中,人类只在开始时给出目标,在结束时接收结果。
:::info 为什么不直接让 LLM 一步生成报告? 因为 LLM 的知识有截止日期,且无法访问实时信息。Agent 通过工具调用弥补了这一局限。此外,分步执行允许每步验证,比一步生成更可靠。 :::
28.2 ReAct 框架:Thought → Action → Observation
问题:如何让 LLM "想清楚再行动"?
早期的工具调用方案让 LLM 直接输出动作,缺乏显式推理过程。这导致两个问题:一是 LLM 容易犯低级错误(行动前没有充分思考),二是难以调试(我们不知道为什么 LLM 做出这个选择)。
Yao et al.(2022)提出的 ReAct(Reasoning + Acting)框架解决了这个问题:在每次行动之前,强制 LLM 先输出一段思考(Thought),然后再给出行动(Action),最后观察结果(Observation)。
ReAct 的结构
Thought: 我需要查询北京今天的天气,才能给出穿搭建议。
Action: search("北京今天天气")
Observation: 北京今天晴,气温 18-28°C,风力2级。
Thought: 天气晴朗,温度适中偏热,适合薄外套或短袖。
Action: finish("建议穿轻薄外套或T恤,搭配长裤即可。")
每个循环包含三个组件:
- Thought:显式推理,解释为什么要做下一步操作
- Action:具体的工具调用或操作
- Observation:工具返回的结果(由环境提供,不是 LLM 生成的)
代码示例:一个简单的 ReAct 循环
下面是一个用 Python 实现的 ReAct Agent,能够查询天气并推荐穿搭:
import json
from anthropic import Anthropic
client = Anthropic()
# 模拟工具函数
def get_weather(city: str) -> dict:
"""模拟天气查询工具"""
mock_data = {
"北京": {"temp_min": 18, "temp_max": 28, "condition": "晴", "wind": 2},
"上海": {"temp_min": 22, "temp_max": 30, "condition": "多云", "wind": 3},
"哈尔滨": {"temp_min": -5, "temp_max": 5, "condition": "小雪", "wind": 4},
}
return mock_data.get(city, {"error": "城市未找到"})
# ReAct 系统提示词
SYSTEM_PROMPT = """你是一个能够查询天气并给出穿搭建议的助手。
使用以下格式回答:
Thought: 你的推理过程
Action: 工具名称(参数)
Observation: [由系统填充]
... (可重复多次)
Thought: 最终推理
Answer: 最终答案
可用工具:
- get_weather(city): 查询指定城市的天气,返回温度范围、天气状况和风力
"""
def run_react_agent(user_query: str):
messages = [{"role": "user", "content": user_query}]
for step in range(5): # 最多执行5步防止无限循环
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=messages,
)
output = response.content[0].text
print(f"\n--- Step {step + 1} ---")
print(output)
# 检查是否已给出最终答案
if "Answer:" in output:
return output.split("Answer:")[-1].strip()
# 解析 Action
if "Action:" in output:
action_line = [l for l in output.split("\n") if l.startswith("Action:")][0]
action_str = action_line.replace("Action:", "").strip()
# 执行工具调用
if action_str.startswith("get_weather"):
city = action_str.split("(")[1].rstrip(")")
result = get_weather(city)
observation = json.dumps(result, ensure_ascii=False)
else:
observation = "未知工具"
# 将 Observation 加入上下文
messages.append({"role": "assistant", "content": output})
messages.append({
"role": "user",
"content": f"Observation: {observation}"
})
else:
break
return "Agent 未能完成任务"
# 运行示例
result = run_react_agent("我明天要去北京出差,应该怎么穿?")
print(f"\n最终答案: {result}")
:::tip ReAct 的关键优势 Thought 步骤不仅帮助 LLM 推理,也让开发者能够"观察 LLM 的思维过程"。当 Agent 出错时,我们可以追溯到哪一步 Thought 出现了偏差,大大降低了调试难度。 :::
ReAct 的数学形式
设 Agent 的状态为 ,动作空间为 ,工具返回函数为 (环境)。ReAct 在每一步生成:
其中 是 Thought(思考), 是 Action(动作), 是由 LLM 参数化的策略。
执行动作后,环境返回观察:
下一步的状态更新为:
即将思考、动作、观察全部拼接进上下文,供下一步决策使用。
28.3 记忆系统
问题:Agent 需要"记住"什么?
一个执行多步任务的 Agent 面临记忆管理问题。步骤 1 搜集的信息,步骤 8 可能还需要用到。但 LLM 的上下文窗口是有限的——即使是 128k token 的模型,在处理长文档、多轮搜索后也会被填满。
Agent 的记忆系统通常分为四个层次:
四类记忆
1. 工作记忆(Working Memory)
就是当前的上下文窗口。所有当前步骤的 Thought、Action、Observation 都在这里。容量有限,但访问速度最快(直接作为 prompt 的一部分)。
2. 短期记忆(Short-term Memory)
当工作记忆接近上限时,将早期的对话历史压缩成摘要,保留关键信息,释放 token 空间。
# 压缩早期历史的示例
def compress_history(messages: list) -> str:
old_messages = messages[:-10] # 保留最近10条
summary_prompt = f"请将以下对话历史压缩为简短摘要:\n{old_messages}"
summary = llm.generate(summary_prompt)
return summary
3. 长期记忆(Long-term Memory)
使用向量数据库(如 Pinecone、Chroma)存储历史任务的信息。当新任务开始时,通过语义检索(见第 24 章 RAG)取回相关历史记录。
新任务: "分析苹果公司2024年财报"
↓ 检索
长期记忆: ["上次分析苹果财报时,重点关注了服务业务增长..."]
↓ 注入
工作记忆中作为背景知识
4. 知识记忆(Knowledge Memory)
LLM 在预训练阶段学到的静态知识。这部分不需要显式管理,但也无法更新(知识截止日期问题)。
记忆层次对比
| 记忆类型 | 存储位置 | 容量 | 读写速度 | 持久性 |
|---|---|---|---|---|
| 工作记忆 | Context Window | 小(受限于窗口大小) | 极快 | 会话结束即消失 |
| 短期记忆 | 压缩摘要(仍在 Context 中) | 中 | 快 | 会话内持久 |
| 长期记忆 | 向量数据库 | 大 | 中(需检索) | 跨会话持久 |
| 知识记忆 | 模型权重 | 极大 | 无需检索 | 永久(但静态) |
:::warning 上下文窗口的困境 即使使用了短期记忆压缩,长任务中仍然面临信息损失问题。压缩摘要会丢失细节;过早丢弃的 Observation 可能在后期发现是关键线索。这是当前 Agent 系统的核心挑战之一,没有完美解法。 :::
记忆写入策略
并不是所有信息都值得存入长期记忆。常见的写入策略有:
- 全量写入:每次对话结束后将完整历史存入向量库(高召回,高存储开销)
- 摘要写入:只存储任务摘要和关键发现(低存储,可能丢失细节)
- 选择性写入:由 LLM 判断哪些信息值得记住(灵活,但引入额外 LLM 调用)
28.4 规划能力的演进
问题:如何让 Agent 面对复杂任务不"走弯路"?
ReAct 框架假设任务可以线性分解:步骤 1 → 步骤 2 → 步骤 3。但现实中很多任务有多条可行路径,或者某条路走了一半发现是死路。这时候线性规划就不够用了。
三种规划范式
1. 链式规划(Chain-of-Thought Planning)
最简单的范式:按顺序执行预先规划的步骤。适合结构清晰的任务。
目标: 订一张明天去上海的机票
计划:
步骤1: 查询明天北京→上海的航班
步骤2: 比较价格和时间
步骤3: 选择最优航班
步骤4: 填写乘客信息
步骤5: 确认支付
缺点:任何一步失败,整个链条断裂;无法探索替代路径。
2. 树形搜索:Tree of Thoughts(ToT)
Yao et al.(2023)提出的 Tree of Thoughts 框架,让 LLM 在每步生成多个备选思路,形成树形结构,通过搜索算法(BFS/DFS)找到最优路径。
目标
/ \
方案A 方案B
/ \ \
A-1 A-2 B-1
✓ ✗ ?
每个节点都可以被评分,低分节点被剪枝,高分节点继续展开。
形式化地,设 为对状态 的价值评估函数(由 LLM 打分),ToT 在每步选择:
3. 带反思的规划:Reflexion
Shinn et al.(2023)提出的 Reflexion 框架,在任务失败后让 Agent 自我反思,生成语言形式的经验总结,然后在下次尝试时参考这些经验。
第1次尝试:
行动: 搜索"量子计算2024"
结果: 失败(搜索词太宽泛,结果不相关)
反思: "下次应该用更具体的关键词,例如加上具体技术名称"
第2次尝试(参考反思):
行动: 搜索("量子纠错 surface code 2024")
结果: 成功
Reflexion 的关键洞察:语言形式的反思比数值形式的奖励信号更容易让 LLM 理解和利用。
三种规划范式对比
| 范式 | 探索能力 | 计算开销 | 适用场景 |
|---|---|---|---|
| 链式规划(CoT) | 低(线性) | 低 | 结构清晰的简单任务 |
| 树形搜索(ToT) | 高(多路径) | 高(多次 LLM 调用) | 复杂决策、创意任务 |
| 反思规划(Reflexion) | 中(迭代改进) | 中 | 需要试错的任务 |
:::info 长任务中的错误积累 在多步任务中,早期的错误会传播和放大。步骤 3 的误判会影响步骤 4、5、6 的决策。这是 Agent 系统可靠性的核心挑战。ToT 通过多路径探索降低单点失败的影响;Reflexion 通过事后纠错减少重复犯错。实际系统往往将两者结合使用。 :::
验证与纠错机制
仅靠规划还不够,Agent 还需要能够验证自己的输出是否正确。常见的验证策略有:
- 外部验证器:对于可验证的任务(如代码生成),运行测试用例验证结果
- 自我验证:让 LLM 扮演"批评者"角色,检查自己的输出是否满足要求
- 多 Agent 辩论:多个 Agent 独立完成任务,对比结果,少数服从多数
# 自我验证示例
def self_verify(task: str, result: str) -> tuple[bool, str]:
"""让 LLM 验证自己的输出"""
verify_prompt = f"""
任务要求: {task}
生成的结果: {result}
请评估:这个结果是否满足任务要求?
如果不满足,请说明原因和改进方向。
格式:
PASS/FAIL
原因: ...
"""
response = llm.generate(verify_prompt)
passed = response.startswith("PASS")
return passed, response
本章小结
| 概念 | 关键思想 | 代表方法 |
|---|---|---|
| Agent 循环 | 感知→规划→行动→观察,循环执行 | 基础 Agent 框架 |
| ReAct | 先思考(Thought)再行动(Action),推理过程可见 | Yao et al., 2022 |
| 工作记忆 | 上下文窗口内的当前信息 | Prompt Context |
| 短期记忆 | 历史对话的压缩摘要 | Summary Memory |
| 长期记忆 | 向量数据库存储的跨会话信息 | RAG + Vector DB |
| 链式规划 | 线性执行预定步骤 | CoT Planning |
| 树形搜索 | 探索多条路径,选择最优 | ToT(Yao et al., 2023) |
| 反思规划 | 失败后自我评估,下次改进 | Reflexion(Shinn et al., 2023) |
单个 Agent 在面对复杂任务时仍有局限:能力边界有限、并行执行困难、单点失败影响全局。当任务需要多个专业角色协作时,我们需要更进一步——让多个 Agent 组成团队,分工协作。下一章将介绍 Multi-Agent 系统,探讨如何让多个 Agent 互相通信、分工执行,共同完成超出单个 Agent 能力范围的复杂任务。