跳到主要内容

第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 的执行轨迹可能是:

  1. 规划:这个任务需要搜索近期论文、阅读摘要、整合关键进展、撰写报告
  2. 行动:调用搜索工具,查询"Transformer architecture 2024 advances"
  3. 观察:获得 10 条搜索结果,包含标题和摘要
  4. 规划:筛选出 3 篇最相关的论文,分别获取详细内容
  5. 行动:调用网页读取工具,获取论文详情(重复 3 次)
  6. 观察:得到三篇论文的关键内容
  7. 行动:整合信息,生成报告草稿
  8. 规划:报告是否满足要求?是 → 结束

整个过程中,人类只在开始时给出目标,在结束时接收结果。

:::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 的状态为 sts_t,动作空间为 A\mathcal{A},工具返回函数为 E\mathcal{E}(环境)。ReAct 在每一步生成:

(tht,at)πθ(st)(th_t, a_t) \sim \pi_\theta(\cdot \mid s_t)

其中 thtth_t 是 Thought(思考),ata_t 是 Action(动作),πθ\pi_\theta 是由 LLM 参数化的策略。

执行动作后,环境返回观察:

ot=E(at)o_t = \mathcal{E}(a_t)

下一步的状态更新为:

st+1=[st,tht,at,ot]s_{t+1} = [s_t, th_t, a_t, o_t]

即将思考、动作、观察全部拼接进上下文,供下一步决策使用。

28.3 记忆系统

问题:Agent 需要"记住"什么?

一个执行多步任务的 Agent 面临记忆管理问题。步骤 1 搜集的信息,步骤 8 可能还需要用到。但 LLM 的上下文窗口是有限的——即使是 128k token 的模型,在处理长文档、多轮搜索后也会被填满。

Agent 的记忆系统通常分为四个层次:

四类记忆

1. 工作记忆(Working Memory)

就是当前的上下文窗口。所有当前步骤的 Thought、Action、Observation 都在这里。容量有限,但访问速度最快(直接作为 prompt 的一部分)。

WorkingMemory=[s0,th1,a1,o1,,tht,at,ot]\text{WorkingMemory} = [s_0, th_1, a_1, o_1, \ldots, th_t, a_t, o_t]

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
✓ ✗ ?

每个节点都可以被评分,低分节点被剪枝,高分节点继续展开。

形式化地,设 V(s)V(s) 为对状态 ss 的价值评估函数(由 LLM 打分),ToT 在每步选择:

st+1=argmaxsexpand(st)V(s)s_{t+1} = \arg\max_{s \in \text{expand}(s_t)} V(s)

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 能力范围的复杂任务。