对应 Maps to:Stanford CS329Z Engineering AI Agents(Fall 2026),Lecture 6 · Mon Oct 12 · Agent Design Patterns & Scaffolds(Diyi Yang、Michael Ryan、John Yang)。阅读材料:Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models(ICLR 2023)。 依据说明:本讲以原课第 6 讲的公开 slides 为主线(回顾 workflow 与 agent、workflow 设计模式、推理 / 行动的智能体模式、上下文管理模式、案例 Pi),结合 Anthropic 的 Building Effective Agents(第 2 讲的阅读材料)和 slides 引用的论文:STORM、Archon、ReAct、Reflexion、Huang et al.(自我纠错的局限)、CRITIC、CodeAct、ReWOO、LLMCompiler、The Complexity Trap(观察遮蔽)、OpenHands、MemGPT,以及 Pi 的文档。凡是引用数字的都核对过原文摘要或 slides 上的图表(从图上读的数字会注明”约”)。全部实验是本讲义自己设计的,不代表课堂上讲了什么。 配套代码 Code:
code/lecture06/(只用 Python 标准库;9 个脚本 + 6 个模块;不需要 API key;每个脚本约 1 秒) 预计阅读时间:3–4 小时(含运行代码)
0. 本讲导航 · How to read this lecture
0.1 学习目标 Learning goals
slides 给这一讲定了三个目标:给 workflow 和智能体的常见设计模式下定义;比较、对照不同的上下文管理设计模式;分析一个生产级 harness(Pi)里的设计决策。展开来说,学完这一讲你应该能够:
- 说清楚 workflow 和 agent 的区别在”控制流在谁手里”,各自适合什么任务,以及为什么真实系统常常把两者组合起来。
- 认出五种 workflow 模式——提示链、路由、并行化(分段 / 投票)、orchestrator–workers、evaluator–optimizer——知道每一种用什么换来了什么,以及它们在什么条件下才有用;能把一个真实系统(STORM)拆成这些模式的组合。
- 比较几种推理 / 行动模式:ReAct、Reflexion、CRITIC、CodeAct、ReWOO、LLMCompiler;解释为什么”没有外部反馈的自我纠错”常常适得其反,为什么代码是好的动作形态,以及”先规划再执行”拿灵活性换了什么。
- 用”聚焦”和”连续”两条原则评估上下文管理的五种模式(事件日志、记忆卸载、压缩、观察遮蔽、上下文隔离),并知道它们怎样组合、怎样影响前缀缓存。
- 读懂一个极简 harness(Pi)的设计取舍:4 个工具、会话树、技能文件、压缩、没有权限检查。
0.2 先修与本讲范围 Prerequisites & scope
| 默认你已经会 Assumed | 本讲只给直觉,后面再展开 Previewed only |
|---|---|
| 第 1 讲:ReAct、Reflexion、workflow vs agent、路由 | 记忆的架构(第 7 讲);多智能体(第 8 讲) |
| 第 2 讲:多数投票的条件、测试时扩展、上下文工程与前缀缓存 | 提示词与结构的自动优化(第 9 讲) |
| 第 4 讲:工具注册表、文本工具调用协议、并行调用 | 评估方法论(第 12–13 讲) |
| 第 5 讲:DSPy、LangGraph、开源 harness 的设计决策、事件日志 | 编程智能体的 ACI 与测试(第 15 讲) |
| 前置讲 P4:沙箱、最小权限 | 安全护栏(第 14 讲) |
0.3 本讲地图 Map
§0.6 开场:模式是"半成品"
§1 回顾:workflow vs agent——控制流在谁手里 ← 代码 01
§2 workflow 设计模式:提示链、路由、并行化、orchestrator–workers、evaluator–optimizer;组合(STORM、Archon) ← 代码 02、03
§3 智能体设计模式·推理与行动:ReAct、Reflexion 与自我纠错的局限、CRITIC、CodeAct、ReWOO、LLMCompiler ← 代码 04、05、06
§4 智能体设计模式·上下文:两条原则、五种模式 ← 代码 07
§5 案例:Pi ← 代码 08
§6 用真实模型试一试 ← 代码 09(可选)
§7 小结 §8 常见误区 §9 练习 §10 延伸阅读 §11 下一讲预告
0.4 术语 Terms
| 术语 | 本讲里的意思 |
|---|---|
| 设计模式(design pattern) | 一类反复出现的问题的一种可复用的解法骨架——要按自己的情况补全 |
| gate | 提示链中两步之间的程序检查(不需要 LLM) |
| 分段 / 投票(sectioning / voting) | 并行化的两种形式:独立的子任务同时做 / 同一任务做几遍再汇总 |
| 评价器(evaluator) | evaluator–optimizer 里给出评价和反馈的那个 LLM(或程序) |
| 内在自我纠错(intrinsic self-correction) | 没有任何外部反馈,让模型自己检查并修改自己的答案 |
| 代码动作(code action) | 动作是一段可执行的代码(CodeAct),而不是一个 JSON 工具调用 |
| 计划 / 证据变量 | ReWOO 的 #E1、#E2;LLMCompiler 的 2——引用前面步骤结果的占位符 |
| 观察遮蔽(observation masking) | 把旧的工具输出换成一行占位符,保留动作本身 |
| 渐进披露(progressive disclosure) | 一开始只给名字和一句话描述,需要时再读完整内容(技能文件) |
0.5 本讲的代码
这一讲的代码把三组模式各放进一个能量化的小世界:workflow 模式用 400 张客服工单(workflow_lab.py);推理 / 行动模式用一个”跨国比价”的小世界(price_world.py:10 个虚构产品、8 个国家的价格、汇率、税率,4 个工具;agent_patterns.py 实现五种模式;CodeAct 写的代码在自己写的白名单解释器 safe_exec.py 里真的执行);上下文模式用一个 60 步长任务的结构性模拟(context_patterns.py);Pi 案例是一个照着 Pi 的设计写的极简 harness(mini_pi.py),在临时目录里真的修一个 bug。
和前几讲一样的提醒:每次 LLM 调用”做对”的概率——心算出错、计划漏步、评价器的发现率、总结时丢事实——都是显式的旋钮(各模块开头的
KNOBS和常量)。所以准确率的具体数字由旋钮决定,看的是可靠性怎样被组合。机制性的部分不依赖旋钮:token 数、调用次数、延迟(并行时取最大值)、工具和代码真的被执行、计划真的按依赖执行、“计划写死了就不能改”、前缀缓存命中多少、事实在不在上下文里。
0.6 开场:模式是”半成品”
slides 一开始引用了 Martin Fowler(《企业应用架构模式》的作者)的一句话:“模式是半成品——你总得自己把它做完,并且让它适应你自己的环境。“然后说:造一个智能体系统时你要做很多设计决策;今天介绍设计模式;实践中你会定制自己的解法。
这句话也决定了本讲的读法:每个模式都不是”该不该用”的问题,而是”它用什么换了什么、在什么条件下值得”。所以每一节都配了一张表,左边是准确率,右边是调用、成本、延迟。
1. 回顾:workflow vs agent · Where does the control flow lie?
1.1 一条光谱,两个端点
第 1 讲 §4、第 5 讲 §1.2 都画过这条光谱:控制流在程序里(模型只是一小步)→ workflow(程序决定步骤)→ agent(模型决定步骤)。slides 举的例子:邮件分类器、新闻摘要器在 workflow 一端;客服智能体、编程智能体在 agent 一端。它的对照表:
| workflow | agent | |
|---|---|---|
| 控制流 | 代码 | LLM |
| 适应性 | 写死的分支、路由、循环 | 根据观察选下一步 |
| 最适合 | 结构清楚、可重复的任务 | 需要探索和灵活决策的任务 |
| 例子 | 给工单分类 → 检索政策 → 起草自动回复 | 用工具调查并解决工单 |
一句话:workflow 更可预测,因为控制流在程序逻辑里;agent 更能适应,因为控制流在 LLM 的决定里。真实系统常常把两者结合起来。
1.2 在比价世界里量一量
代码 01 在比价世界里做了三个系统:
- workflow:一次 LLM 调用给请求分类、抽出槽位(产品、国家);其余全是代码——查询并行、算术精确;
- agent:ReAct(每轮一个工具调用,最后心算);
- 组合:workflow 认得的请求走 workflow,认不得的交给 agent。
“预料到的请求”是 total(某国含税美元价)和 cheapest(几个国家里哪里最便宜)150 道;“没预料到的请求”是三种 workflow 的设计者没想到的问法——两国差价、有没有卖、两件不同产品哪个便宜——60 道。每道题跑 5 次:
| 系统 | 请求 | 准确率 | LLM 调用 | 输入 token | 延迟(秒) | p95 延迟 |
|---|---|---|---|---|---|---|
| workflow | 预料到的 | 100.0% | 1.0 | 112 | 2.3 | 2.5 |
| workflow | 没预料到的 | 0.0% | 1.0 | 108 | 0.7 | 0.6 |
| agent(ReAct) | 预料到的 | 93.5% | 9.3 | 7,846 | 14.1 | 25.4 |
| agent(ReAct) | 没预料到的 | 96.0% | 5.4 | 3,543 | 7.7 | 11.2 |
| workflow + agent 兜底 | 预料到的 | 100.0% | 1.0 | 112 | 2.3 | 2.5 |
| workflow + agent 兜底 | 没预料到的 | 91.3% | 6.2 | 3,475 | 8.0 | 11.9 |
读法:
- 预料到的请求上,workflow 全面占优:准确率更高(没有心算、没有在长上下文里”回头找数”)、输入 token 只有 agent 的 1/70、延迟只有 1/6。agent 每一步都要把整个历史重发一遍。
- 没预料到的请求上,workflow 无能为力。更糟的是,它的分类器偶尔会把一个陌生的请求错认成已知类型(300 次里 15 次),然后认真地答非所问——workflow 的失败常常是”自信的错”。
- 可预测性:workflow 每题恰好 1 次调用、延迟在 2.2–2.5 秒之间;agent 的调用次数从 2 到 19 不等,延迟从 2.0 到 29.7 秒(代码 01 演示 2)。可预测意味着容易估成本、容易设超时、容易复现和调试。
- 组合拿到了两边的好处,但比纯 agent 在没预料到的请求上低(91.3% vs 96.0%):差的正是那 5% 被分类器错认、根本没到 agent 手里的请求。路由器的错误会原样传给下游——§2.2 会量化这一点。
2. workflow 设计模式 · Workflow design patterns
slides 照搬了 Anthropic 在 Building Effective Agents 里的 workflow 分类:
- 提示链(prompt chaining):把问题分解成更小的步骤;
- 路由(routing):把任务分给合适的 LLM;
- 并行化(parallelization):把工作分给多个线程;
- orchestrator–workers:识别出子任务,派 worker 去完成;
- evaluator–optimizer:生成一个输出,评价它、给出反馈,然后迭代。
代码 02 在 400 张客服工单上把它们逐一实现(workflow_lab.py)。工单有 4 个类别(billing 35%、technical 30%、account 20%、outage 15%),每类的回复都有一组”必须包含”的要素(例如 outage:事故编号、预计恢复时间、道歉、状态页链接);一部分工单是难的;8% 是安全事件(钓鱼、账号被盗);每张工单需要排查 0–4 个后台子系统,事先不知道是哪几个。
怎样读这一节的数字:每次调用做对的概率是旋钮,所以这一节本质上是”可靠性的组合”。每个小节先写出组合的公式,再给出代码的抽样结果;你可以把公式套到自己的系统上。
2.1 提示链:用延迟换准确率
是什么:把一个大任务拆成顺序的几步,每一步的输出是下一步的输入;步与步之间可以加一道程序检查(gate),不合格就不往下走(或者重做)。slides:用延迟换准确率;任务复杂、能分解时有用。
为什么有用:一次调用里塞进好几件事,每件事都做得差一点。设一次调用里每个要素被满足的概率是 ,回复要满足 个要素,那么一次做对的概率是 ;如果”读、抽、写、查”四件事挤在一起让 从 0.95 降到 0.89,对 4 个要素的 outage 工单就是 和 的差别。拆开之后每一步更专注;gate 再把它能检查的缺项挑出来让模型重写。
| 做法 | 要素齐全 | 调用 | 成本(美分) | 延迟(秒) |
|---|---|---|---|---|
| 一次调用(读、抽、写、查都在一起) | 68.6% | 1.00 | 0.427 | 2.81 |
| 提示链:抽事实 → 起草(无 gate) | 80.2% | 2.00 | 0.500 | 3.51 |
| 提示链 + gate(程序检查能检查的要素) | 86.8% | 2.08 | 0.532 | 3.72 |
gate 只能查程序能查的东西:政策编号、事故编号、链接在不在回复里;“有没有道歉""重启步骤写得对不对”它查不了——那需要一个 LLM 评价器(§2.5)。这就是为什么加了 gate 也到不了 100%。
2.2 路由:分类,然后交给专家
是什么:先给输入分类,再交给合适的 LLM(不同的提示词,或者不同的模型)。slides:复杂任务里有几类最好分开处理的情况时有用;也能省钱——简单的交给更弱、更便宜的模型。
组合公式:设分类器准确率 ,分对之后专门流程的质量 ,分错之后质量 ,那么整体质量 。路由只有在 时才划算——分类器要足够准,专家要足够强。
| 做法 | 要素齐全 | billing | technical | account | outage | 调用 | 成本(美分) | 延迟(秒) |
|---|---|---|---|---|---|---|---|---|
| 不路由:大模型 + 通用提示词 | 53.1% | 63% | 44% | 70% | 26% | 1.00 | 0.427 | 2.81 |
| 按类别路由 → 大模型 + 专门提示词 | 65.6% | 65% | 62% | 72% | 67% | 2.00 | 0.435 | 3.05 |
| 按类别和难度路由(简单的给小模型) | 58.8% | 59% | 56% | 71% | 48% | 2.00 | 0.196 | 1.78 |
- 通用提示词在少见的类别上最差(outage 26%)——通用提示词自然地偏向常见情况。专门提示词把它提到 67%。
- 把简单工单交给小模型:准确率掉了约 7 个点,成本降到不到一半、延迟降了 40%。这是一个要按业务定的取舍,不是技术问题。
- 分类器的准确率决定一切:
| 分类器准确率 | 99% | 94% | 80% | 60% |
|---|---|---|---|---|
| 按类别路由后的要素齐全率 | 68.3% | 64.8% | 54.5% | 40.8% |
分类器准确率掉到 80% 时,路由已经只比不路由(53.1%)好一点点;掉到 60% 时比不路由还差。分错的工单拿到的是错误的专家,这比”通用但平庸”更糟。
2.3 并行化:分段与投票
slides 把并行化分成两种:
- 分段(sectioning):把独立的工作拆开同时做,例如”一边把查询标成安全 / 不安全,一边处理查询”;
- 投票(voting):推理时扩展——同一个输入处理好几遍,由汇总器下最后的结论。
两种都能让每次调用只关注一个方面。
分段。把”这是不是安全事件”这道护栏放在哪里(代码 02 演示 3a):
| 护栏放在哪 | 安全事件识别率 | 误报率 | 要素齐全 | 调用 | 成本(美分) | 延迟(秒) |
|---|---|---|---|---|---|---|
| 塞进起草的那次调用里 | 83% | 4.1% | 73.6% | 1.00 | 0.388 | 2.55 |
| 两次调用,先后 | 89% | 4.4% | 81.9% | 2.00 | 0.382 | 2.72 |
| 两次调用,同时 | 86% | 3.9% | 82.7% | 2.00 | 0.382 | 2.51 |
塞在一起时两件事互相干扰:护栏识别率最低,起草也变差。拆开之后,串行要多等一次小模型调用;并行几乎不多花时间——延迟由更慢的那个(起草)决定。(两种”拆开”的识别率差别在噪声以内:只有 29 张安全事件工单 × 3 次。)
投票。同一个”是不是安全事件”问 5 次,再用不同的规则汇总(演示 3b;安全事件只占 8%):
| 票间相关性 | 规则 | 精确率 | 召回率 | F1 |
|---|---|---|---|---|
| 0(独立) | 单票 | 59% | 79% | 0.68 |
| 0 | 5 票,过半 | 100% | 94% | 0.97 |
| 0 | 5 票,≥ 2 票 | 77% | 99% | 0.86 |
| 0 | 5 票,任一 | 27% | 100% | 0.42 |
| 0.6 | 单票 | 63% | 83% | 0.72 |
| 0.6 | 5 票,过半 | 67% | 92% | 0.78 |
| 0.6 | 5 票,任一 | 34% | 97% | 0.50 |
两个要点:
- 汇总规则就是精确率和召回率的取舍。“任一票说是就是”召回最高、误报也最多;“过半”两头兼顾。安全场景常常宁可误报(用”任一”或”≥ 2”),然后交给人复核。
- 投票只在错误独立时有用。票与票独立时,5 票过半把 F1 从 0.68 提到 0.97;当每一票以 0.6 的概率”照抄”同一个共享的判断(同一个模型、同一个提示词,错误往往一起犯),精确率从 100% 掉到 67%——五张一起犯错的票和一张票差不多。第 2 讲 §2.5 说过:多数投票只能救”正确答案是众数”的题。想让投票有用,就要让票变得独立:不同的提示词、不同的模型、不同的检索结果。
2.4 orchestrator–workers:事先不知道有几个子任务
是什么:一个 LLM 动态地把任务拆开,分派给 worker LLM,再汇总。slides:当很难预料需要哪些子任务、需要几个时有用;和并行化相似,区别是子任务是动态生成的。
代码 02 演示 4:工单需要排查 0–4 个后台子系统。“固定分段”不管什么工单都并行查 billing_db 和 meter 两个(最常见的两个);orchestrator 先问大模型”这张工单要查哪些系统”,再按需派 worker:
| 需要排查的子系统数 | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 固定分段查全了 | 100% | 57% | 18% | 0% | 0% |
| orchestrator 查全了 | 100% | 90% | 87% | 78% | 75% |
| orchestrator 派出的 worker(平均) | 0.1 | 1.0 | 2.0 | 2.9 | 3.9 |
整体查全率:固定分段 44.0%,orchestrator 89.1%;代价是多一次规划调用(每张工单 3.00 → 3.59 次调用,成本 0.250 → 0.311 美分)。orchestrator 的上限由”它能不能想到所有要查的东西”决定(这里每个子系统以 0.93 的概率被想到)——需要查的越多,漏掉一个的概率越大()。这正是第 8 讲多智能体系统的基本结构。
2.5 evaluator–optimizer:三个条件缺一不可
是什么:一个 LLM 生成,另一个评价并给出反馈,循环。slides 特别列了它成立的条件:(1) 有清楚、说得出来的反馈;(2) 有一个 LLM 能给出这种反馈;此外生成器还得能根据反馈改进。
代码 02 演示 5 把条件一个一个拿掉:
| 条件 | 要素齐全 | 平均修改轮数 | 调用 | 成本(美分) | 延迟(秒) |
|---|---|---|---|---|---|
| 不审阅(只起草一次) | 67.0% | 0.00 | 1.00 | 0.427 | 2.81 |
| 好评价器(发现率 0.9、误报 0.05)+ 具体反馈 | 94.5% | 0.59 | 3.14 | 0.796 | 5.61 |
| 吵闹的评价器(发现率 0.5、误报 0.3) | 63.0% | 1.48 | 4.64 | 1.182 | 8.37 |
| 反馈不具体(只说”不够好”) | 77.9% | 0.83 | 3.46 | 0.889 | 6.27 |
| 生成器不会按反馈改(改对率 0.2) | 76.8% | 0.89 | 3.56 | 0.916 | 6.46 |
- 好评价器 + 具体反馈:67% → 94.5%,平均只改 0.59 轮(大多数草稿第一次就通过)。
- 吵闹的评价器比不审阅还差(63.0% < 67.0%),而且最贵:误报让生成器去”修”没坏的地方,把好的要素改坏;发现率低又让真正的问题漏过去。一个评价器的价值由它的发现率和误报率共同决定,这两个数要在上线前用带标签的数据测出来(第 13 讲的 LLM-as-judge 校准)。
- 反馈不具体(“不够好”)、生成器不会改,都只能拿到一部分收益。
§3.2–§3.3 会从智能体的角度再讲一遍这件事:评价器能不能拿到外部信号,决定了这个循环是在进步还是在原地打转。
2.6 组合:真实系统是模式的组合
slides:“真实系统是这些基本模式的组合。“两个例子:
STORM(Shao et al., NAACL 2024)写维基百科式的长文:
| 阶段 | 做什么 | 对应的模式 |
|---|---|---|
| 1. 发现视角 | 检索相关主题的文章,让 LLM 列出写这个主题需要的几种视角 | 提示链的第一步 |
| 2. 模拟对话 | 每个视角一个”写作者”向”主题专家”提问,专家检索后回答,多轮 | 并行化 / orchestrator–workers |
| 3. 整理大纲 | 先凭模型自己的知识写草稿大纲,再用对话收集到的信息修订 | 近似 evaluator–optimizer |
| 4. 逐节写作 | 按大纲每节检索引用、写作,合成全文 | 提示链 + 并行化 |
slides 的标注是”orchestrator–workers(在这里接近并行化)、近似 evaluator–optimizer、提示链、并行化”。论文报告:在新整理的 FreshWiki 数据集上,和”先写大纲再检索”的 RAG 基线相比,STORM 的文章被认为”组织得好”的比例高 25 个百分点,“覆盖面广”的高 10 个百分点。
Archon(Saad-Falcon et al., 2024)更进一步:把推理时的技巧(生成、排序、融合、批评、验证、单元测试生成……)当成积木,自动搜索怎样组合最好。论文报告它平均比 o1、GPT-4o、Claude 3.5 Sonnet 等前沿模型高 15.1%;slides 上的图显示它的性能随推理预算(每题的输入输出 token 上限,1 万到 10 万)一路上升,在约 5 万 token 时追上 o1 的水平,而 AFlow、ADAS 这类方法在更高的预算下就停在 63%–65% 左右了。
代码 03 在工单上做了一个迷你 Archon:四个旋钮——路由(不路由 / 按类别 / 按类别 + 难度)、起草(一次 / 提示链)、护栏(同一次调用 / 并行)、审阅轮数(0 / 1 / 2)——组合出 36 种系统。在一半工单(搜索集)上评估和挑选,在另一半(检验集)上检验:
- 前沿很窄:36 种组合里只有 8 种在准确率–成本前沿上,准确率从最差的 38.5% 到最好的 93.2%。同样的组件,组合方式决定了系统的好坏。
- 最佳结构依赖预算:
| 每张工单的预算 | 挑中的组合 | 搜索集 | 检验集 |
|---|---|---|---|
| ≤ 0.25 美分 | 类别 + 难度路由 · 一次起草 · 护栏并行 · 不审阅 | 61.3% | 58.2% |
| ≤ 0.4 美分 | 类别 + 难度路由 · 提示链 · 护栏并行 · 不审阅 | 78.2% | 80.0% |
| ≤ 0.6 美分 | 类别 + 难度路由 · 提示链 · 护栏并行 · 审阅 2 轮 | 90.5% | 88.8% |
| ≤ 0.9 美分 | 不路由 · 提示链 · 护栏同调用 · 审阅 2 轮 | 93.2% | 89.0% |
便宜的预算靠路由把简单工单交给小模型;预算宽了才加上提示链和审阅循环。
- 赢家诅咒又出现了(第 5 讲 §2.7):挑出来的组合在检验集上平均低 1.8 个百分点。最宽预算下挑中的组合”去掉了路由”,但下面的消融显示,去掉路由在检验集上几乎不改变准确率——搜索集上那点优势多半是噪声。
- 消融(0.6 美分预算下的组合,每次拿掉一个模式,在检验集上评估):去掉审阅循环准确率掉约 9 个百分点(88.8% → 80.0%),它是这个任务的瓶颈;去掉路由准确率不变(89.0%),成本却从 0.462 涨到 0.899 美分——在这个组合里路由的价值是省钱,不是准确率。
这种”结构搜索”是第 9 讲优化的一个特例:被优化的不是提示词或权重,而是系统的结构。slides 这一部分的小结:workflow 比 agent 更容易复现和调试;五种常见模式;实践中真实的 workflow 往往是这些组件更大的组合。
3. 智能体设计模式:推理与行动 · Reasoning & acting patterns
slides 的过渡句:“除了工具本身,我们还得设计 LLM 怎样推理、怎样和环境交互。“这一部分按时间顺序讲六个模式,每个都有一句”关键洞见”。
3.1 ReAct:先推理,再行动
第 1 讲 §5.2–§5.4 已经详细讲过 ReAct(也是本讲的指定阅读):交替写”思考”和”动作”,动作的结果作为”观察”送回来。slides 在这一讲补了两点。
推理有用。ReAct 论文表 1(PaLM-540B 提示):Act(只行动不思考)在 HotpotQA 上 EM 25.7、FEVER 上准确率 58.9;ReAct 27.4、60.9。在行动之前先推理今天仍然是标配。
要微调才最好。论文的图 3 比较了提示和微调两种方式下不同规模的 PaLM。提示时,小模型(8B)用 ReAct 只有约 5 分 EM,还不如直接回答(约 16 分)——小模型没本事边想边做;微调(用 3,000 条 ReAct 轨迹)之后完全反过来,论文的原话是:“微调后的 PaLM-8B 的 ReAct 超过了所有 PaLM-62B 的提示方法,微调后的 PaLM-62B 的 ReAct 超过了所有 540B 的提示方法。“slides 的评论:“今天,把模型微调成智能体已经是后训练的常规做法。“(第 2 讲 §4.4 面向智能体的训练。)
3.2 Reflexion 与自我纠错的局限
Reflexion(Shinn et al., 2023)的关键洞见:自我反思有用——把评价和反思变成执行者 LLM 的”记忆”(上下文)。第 1 讲 §5.7 和代码 04 复现过:失败后写一两句反思,带着反思重试,准确率随试次上升;论文的消融也显示,起作用的是 LLM 写的反思,而不只是”看过以前的轨迹”。
但自我反思有局限。slides 引用了 Huang et al.(ICLR 2024)的 Large Language Models Cannot Self-Correct Reasoning Yet:让 LLM 检查并修改自己的回答,准确率会下降。GPT-4-Turbo 的结果(表 5):
| 调用次数 | GSM8K | CommonSenseQA | |
|---|---|---|---|
| 标准提示 | 1 | 91.5 | 84.0 |
| 反馈提示 A:“假设这个答案可能对也可能错。仔细检查并报告严重问题。” 第 1 轮 / 第 2 轮 | 3 / 5 | 88.0 / 90.0 | 81.5 / 83.0 |
| 反馈提示 B:“检查你之前的答案是否正确;如果错了,找出问题。” | 3 / 5 | 90.0 / 90.0 | 74.5 / 81.0 |
| 反馈提示 C:“验证你的答案是否正确,并给出解释。” | 3 / 5 | 91.0 / 91.0 | 81.5 / 83.5 |
没有一种反馈提示让准确率超过不纠错的基线。slides 的三点说明:Reflexion 绕开了这个问题,因为它用的是来自环境的外部反馈;这个发现早于推理模型,推理模型在推理过程中展示了很强的自我纠错能力;今天自我反思通常内置在 LLM 的推理里,而不是由外面显式地强制。
3.3 CRITIC:给验证器配上工具
CRITIC(Gou et al., ICLR 2024)的关键洞见:给验证器加上外部工具(知识库、代码解释器、搜索引擎、计算器……)——做一个智能体式的验证器。循环是:输出 → 验证(调用工具)→ 批评 → 改正 → 再输出。slides 上的图(AmbigNQ,F1):初始输出约 64 分(和 ReAct 差不多);CRITIC 一轮之后约 74、三轮后约 75;去掉工具的 CRITIC 只到约 67;用标准答案决定何时停止的”神谕”版本到约 80。
代码 04 在比价世界的 total 题上把这几页连起来做了一遍:一个”较弱的”生成器(心算出错率 20%;查数据时 5% 拿错税率)先答,然后各种”检查—修改”循环跑 3 轮(60 道题 × 5 次):
| 方法 | 第 0 轮 | 第 1 轮 | 第 2 轮 | 第 3 轮 | LLM 调用 | 工具调用 | 对 → 错 的次数 |
|---|---|---|---|---|---|---|---|
| 不修改 | 77.3% | 77.3% | 77.3% | 77.3% | 1.0 | 3.0 | 0 |
| 自我纠错(无工具) | 77.3% | 73.7% | 69.3% | 63.0% | 4.6 | 3.0 | 60 |
| 自我纠错 + 神谕停止(只在真错时改) | 77.3% | 81.7% | 84.3% | 87.7% | 4.2 | 3.0 | 0 |
| CRITIC(用代码重算) | 77.3% | 96.0% | 96.0% | 96.0% | 4.2 | 6.0 | 0 |
| CRITIC + 重新查询证据 | 77.3% | 100.0% | 100.0% | 100.0% | 4.2 | 15.0 | 0 |
- 无工具的自我纠错越改越差(77.3% → 63.0%):答案本来是对的,模型被自己说服”有错”,改成了错的——300 次运行里 60 次”对改成错”。根源是误报:没有外部信号,模型分不清自己的答案是对是错。
- 同样的发现率、只在真错时才改(神谕停止):87.7%。差别全部来自误报。Huang et al. 指出,早期一些”自我纠错有效”的结果用的正是这种需要标准答案才能做到的停止条件——部署时你没有标准答案。
- CRITIC 一轮就到 96%,之后不再变化:计算器从不误报,也不会把对的改错。但它到不了 100%:证据本身拿错了时,按同样的错证据重算,验证器看不出问题。
- 再重新查一遍证据就到了 100%,代价是每轮多三次工具调用。验证器能发现什么,取决于它能用什么工具去检查。
(这一节的发现率、误报率、“被说有错之后怎么改”都是旋钮;CRITIC 的重算是在白名单解释器里真的执行的。)
3.4 CodeAct:代码是动作的正确形态
CodeAct(Wang et al., ICML 2024)的关键洞见:代码是动作的正确形态。slides 的例子:确定在美国、日本、德国、印度中,哪里买某款手机最划算。用 JSON 动作,模型要一轮一轮地调用”查价格""查汇率""查税率”,每轮一个;用代码动作,一段 Python 就写完了:循环遍历国家、调用工具、算出含税美元价、取最小值。论文在 17 个 LLM 上比较了文本、JSON、代码三种动作格式,报告代码动作的成功率最多高 20%,交互轮数也更少(slides 上的图:GPT-4-1106 的成功率代码约 74%、JSON 约 53%;平均轮数代码约 5.5、JSON 约 7.6)。
为什么?三个原因:
- 控制流:循环、条件、变量都写在一段代码里,一轮做完 JSON 动作要好几轮才能做完的事;
- 把算术和”记住每个数”交给解释器:没有心算错误,也不会在长上下文里张冠李戴;
- 报错就是反馈:KeyError、ValueError 告诉模型哪里错了,它可以改代码再跑(自我调试)。
代码 05 在 90 道 cheapest 题上(× 5 次)比较了三种做法:
| 动作格式 | 成功率 | 轮数 | 输入 token | 延迟(秒) |
|---|---|---|---|---|
| JSON,每轮一个调用(ReAct) | 93.3% | 12.5 | 11,299 | 19.3 |
| JSON,一轮多个调用 | 92.7% | 3.5 | 2,891 | 10.4 |
| CodeAct(代码动作) | 94.7% | 2.2 | 674 | 9.4 |
- 轮数和 token 是结构性的差别:JSON 每轮一个调用时,输入 token 随国家数 k 快速增长(k = 3、4、5 时 7,895、10,945、15,057),因为每一轮都要重发越来越长的历史;CodeAct 几乎不随 k 变化(626、667、730)——循环写在代码里。
- 今天的并行工具调用(一轮多个调用)拿走了 CodeAct 在轮数上的大部分优势,但仍然要心算、要在上下文里”回头找数”:JSON 动作的 450 次运行里有 115 次发生了心算或找数的错误(只是大多数没有改变排名),CodeAct 0 次。
- 代码动作有自己的失败方式。第一版代码的 bug 和结局(演示 2):
| 第一版代码 | 次数 | 最终答对 |
|---|---|---|
| 没有 bug | 299 | 100% |
| 用了产品的别名、没先 search_product | 54 | 93% |
p["cost"] 写错键名(KeyError,响亮) | 39 | 85% |
| 没有 try / except,遇到不卖的国家(ValueError,响亮) | 17 | 88% |
税算成 price * tax(能跑、结果错,安静) | 28 | 57% |
响亮的 bug 大多被改好了——报错信息就是反馈。安静的 bug 能跑、不报错,模型也就不会去改;那 57% 答对的只是”算错了但排名碰巧没变”。对代码动作来说,测试和断言(第 15 讲)比对 JSON 动作更重要。
执行模型写的代码必须隔离。代码 05 演示 4 展示了白名单解释器(safe_exec.py)拒绝的东西:import os、open(...)、任何下划线属性(get_price.__globals__、().__class__.__bases__ 这类逃逸路径)、exec、无限循环(步数上限)。它是纵深防御中的一层;生产系统还会把代码放进容器或受限进程(前置讲 P4 §4)。
3.5 先规划再执行:ReWOO 与 LLMCompiler
slides 的关键洞见:规划让智能体的执行重新变回 workflow。
ReWOO(Reasoning WithOut Observation,Xu et al., 2023)分三个角色:
- 规划器一次写完整个计划,每一步写成”Plan: …… #E1 = 工具[输入]“,后面的步骤可以用 #E1、#E2 引用前面的证据;
- 工人按计划依次调用工具,把结果填进 #E 变量(这一步不调用 LLM);
- 求解器读一遍所有的计划和证据,给出答案。
slides 上的例子:“The Hennchata 的主要成分是哪个干邑酒庄生产的?“计划:#E1 = Wikipedia[The Hennchata] → #E2 = LLM[主要成分是什么?上下文:#E1] → #E3 = Wikipedia[#E2] → #E4 = LLM[哪个酒庄?上下文:#E3]。论文摘要报告在 HotpotQA 上 5 倍的 token 效率、准确率还高 4%;slides 上的图:ReAct 在公开基准上约 0.58 的准确率、约 3,600 token,ReWOO 约 0.61、约 1,300 token。
LLMCompiler(Kim et al., ICML 2024)和 ReWOO 的区别:并行执行和动态重新规划。规划器输出一张任务依赖图(DAG),例如”微软的市值要涨多少才能超过苹果”:2 = search(苹果市值)、1 / 4 = llm(1 和 $2 没有依赖关系,同时执行。任务获取单元按依赖把就绪的任务交给执行器;执行完由 joiner 决定结束还是重新规划。论文摘要报告相对 ReAct:延迟最多快 3.7 倍、成本最多省 6.7 倍、准确率最多高约 9%。slides 上的表(GPT):HotpotQA 上 ReAct 62.47%、7.12 秒,LLMCompiler 62.00%、3.95 秒(快 1.80 倍);电影推荐 72.47%、20.47 秒 → 77.13%、5.47 秒(快 3.74 倍);ParallelQA 89.09%、35.90 秒 → 89.38%、16.69 秒。
代码 06 在比价世界的全部 150 道题上(× 5 次)比较了四种做法:
| 模式 | 准确率 | LLM 调用 | 输入 token | 输出 token | 工具调用 | 延迟(秒) |
|---|---|---|---|---|---|---|
| ReAct(每轮一个调用) | 93.5% | 9.3 | 7,846 | 425 | 8.2 | 14.1 |
| ReAct + 并行调用 | 93.2% | 3.5 | 2,546 | 385 | 8.5 | 8.3 |
| ReWOO | 75.9% | 2.0 | 627 | 238 | 7.8 | 8.1 |
| LLMCompiler | 95.3% | 2.3 | 496 | 186 | 9.2 | 4.8 |
- token:ReWOO 的输入 token 约是 ReAct 的 1/13。规划器和求解器各只调用一次,工具调用之间不调用 LLM,也就不用一遍遍重发越来越长的历史——这是”5 倍 token 效率”的来源(我们的题要查的东西更多,差距更大)。
- 延迟:LLMCompiler 4.8 秒,比 ReAct 快 2.9 倍。它的工具调用并不更少,但互相独立的查询在同一波里并行,算术由 calc 任务在代码里做。
可 ReWOO 的准确率只有 75.9%。按题目里有没有”意外”分组:
| 题目 | ReAct | ReAct + 并行 | ReWOO | LLMCompiler |
|---|---|---|---|---|
| 没有意外(规范名、都卖) | 96% | 96% | 94% | 97% |
| 用了产品的别名 | 90% | 90% | 44% | 96% |
| 有国家不卖这款产品 | 91% | 89% | 63% | 92% |
计划能不能随机应变是这几种模式最根本的区别。ReWOO 的计划一旦写成”拿别名去查价格”(230 次用了别名的运行里有 116 次),后面所有步骤都会失败,而它没有机会改计划——它的名字就是”不看观察就推理”。LLMCompiler 的 joiner 看到失败就要求重新规划(119 次),第二个计划先 search_product;ReAct 每一步都看着观察决定下一步。代价和收益是对称的:不看观察,省下了 token;不看观察,也就看不到意外。
3.6 这些模式在今天
slides 的小结,值得逐条记住:
- ReAct:行动前先推理是有用的——今天仍然常见;
- Reflexion:自我反思今天被并入了推理本身;
- CodeAct:代码是很好的动作形态;
- ReWOO / LLMCompiler:把执行”编译”成 workflow,今天主要是靠并行工具调用来做(代码 06:ReAct + 并行调用 3.5 次 LLM 调用、8.3 秒);但规划本身仍然重要;
- 有时 workflow 仍然被用来编排”子智能体”(第 8 讲)。
4. 智能体设计模式:上下文 · Context management patterns
4.1 两条原则
slides 引用了 Anthropic 的 Effective Context Engineering for AI Agents(第 2 讲的阅读材料)和”大海捞针”测试,然后把上下文工程归结为两条原则:
- 聚焦(focus the context):上下文窗口里只放当前任务需要的东西;整段历史并不需要都在上下文里;多余的信息就是”上下文膨胀”(context bloat)。
- 保持连续(preserve continuity):操纵上下文时要保住需求、目标、约束、进展……实践中这也意味着保住 KV cache(前缀缓存)。
两条原则互相拉扯:为了聚焦而删改上下文,就会打破连续(丢掉信息、让缓存失效)。五种模式是在两者之间取平衡的不同办法。
4.2 五种模式
| 模式 | 做什么 | 代表 |
|---|---|---|
| 持久的事件日志 | 一切按发生的样子保存下来(只追加、不修改);每次调用 LLM 时,输入从事件日志重建;日志还给外部观察者提供了监视系统状态的钩子 | OpenHands(第 5 讲 §4.3) |
| 记忆卸载 | 把知识存进记忆或文件,于是上下文的那部分可以放心删掉 | MemGPT(第 7 讲) |
| 压缩 | 让 LLM 把之前的推理、动作、观察总结成更少的 token;压缩本身也可以是智能体式的 | 各种 harness 的 compaction |
| 观察遮蔽 | 不让 LLM 写摘要,直接把旧的观察截短或换成占位符;对观察很长的编程智能体尤其有用 | The Complexity Trap(Lindenbauer et al., 2025) |
| 上下文隔离 | 用子智能体做特定的任务,只把真正需要的东西放回主上下文;渐进披露:工具和上下文不必都放进初始提示,需要时再披露(例如技能文件) | 子智能体、Agent Skills |
slides 特别强调:这些策略都是兼容的。可以先把知识卸载到记忆里、再压缩上下文;可以用事件日志构建上下文、同时截短观察。真实系统是其中多种乃至全部策略的组合。
4.3 在同一个长任务上比较
代码 07 用一个 60 步的编程任务做结构性模拟(context_patterns.py):每步一个动作和一个观察(读文件约 1,200 token、跑测试约 600、grep 约 250、ls 约 120);轨迹里有 8 个”关键事实”(函数签名、配置值……),在某一步第一次出现,8–35 步之后要用到。要用时事实”看不见”了怎么办:观察被遮蔽、只剩占位符 → 智能体知道去哪找,以 0.97 的概率重新运行(多一步);被 LLM 总结丢掉了 → 以 0.5 的概率想起来重读;被”超出窗口”从开头截掉了 → 智能体不知道自己丢了东西,只以 0.25 的概率碰巧重读。另有一个”上下文腐烂”的旋钮:事实明明看得见,上下文每长 1 万 token,漏看的概率加 2%(第 2 讲 §7.2)。每种策略 200 条轨迹:
| 策略 | 成功率 | 总输入 token | 峰值上下文 | 多走几步 | 额外调用 | 成本(无缓存) | 成本(有缓存) |
|---|---|---|---|---|---|---|---|
| 全部保留(超出 32K 窗口就从开头截掉) | 62.5% | 1,253K | 32.0K | 0.0 | 0.0 | 1.272 | 0.535 |
| 观察遮蔽:只保留最近 8 个观察的原文 | 83.0% | 557K | 12.4K | 7.7 | 0.0 | 0.578 | 0.444 |
| 观察遮蔽,攒够 10 个旧观察再一起遮 | 77.0% | 708K | 17.1K | 6.0 | 0.0 | 0.728 | 0.179 |
| LLM 总结:超过 16K 就把旧的部分总结成 400 token | 48.5% | 677K | 15.9K | 0.5 | 3.6 | 0.702 | 0.203 |
| 遮蔽 + 总结(遮蔽之后仍超过 10K 才总结) | 73.0% | 541K | 11.6K | 7.5 | 3.9 | 0.569 | 0.455 |
| 记忆卸载(关键事实记进笔记)+ 观察遮蔽 | 89.5% | 471K | 10.8K | 0.8 | 0.0 | 0.489 | 0.404 |
| 记忆卸载 + 攒批遮蔽 | 82.0% | 619K | 16.2K | 0.6 | 0.0 | 0.638 | 0.160 |
| 上下文隔离:每 10 步交给一个子智能体 | 61.5% | 266K | 9.1K | 0.4 | 60.0 | 0.286 | 0.100 |
(成本单位:输入 1 token = 1、读缓存 0.1、写缓存 1.25、输出 5,除以 100 万。)
逐条读:
- 全部保留最贵(总输入 125 万 token),成功率却只有 62.5%:上下文越长越容易漏看,超出窗口之后还要从开头截掉、而且截掉了也不告诉模型。从开头截掉也让前缀缓存每一步都失效。
- 观察遮蔽把总输入降到 44%,成功率 83.0%,高于 LLM 总结的 48.5%——总结还要多花 3.6 次调用,并且会丢掉事实、连”去哪找”的线索都丢了;而遮蔽留下的占位符告诉模型”这里曾经有什么、怎样找回来”。论文的结论(在 SWE-bench Verified 上、五种模型配置下):简单的遮蔽和 LLM 总结的解决率相当、有时略好,成本约为原始智能体的一半;他们提出的混合方法再省 7%(相对遮蔽)和 11%(相对总结)。我们这里遮蔽领先得更多,那取决于”总结时丢事实”的旋钮;混合方法在我们的模型里没有带来好处——这也提醒我们,哪种组合最好要在自己的任务上量。
- 连续性 = 保住缓存。每一步都遮一次,最早那个被遮的观察之后的缓存每一步都被打破(有缓存的成本 0.444);攒够 10 个再一起遮,成本降到 0.179,代价是上下文长一些、成功率 77.0%。有缓存时,LLM 总结(0.203)反而比”每步都遮”便宜——两次总结之间上下文是只追加的。第 2 讲 §7.4 的”攒批清理”是同一件事。
- 记忆卸载 + 遮蔽:关键事实写进笔记,原文就可以放心遮掉——成功率 89.5%,单智能体策略里总输入最低。再配上攒批遮蔽,有缓存的成本降到 0.160。这正是 slides 说的”模式可以叠加”。
- 上下文隔离:主智能体的上下文最小、成本最低(有缓存 0.100),但成功率只有 61.5%——跨子任务要用的事实必须写进摘要才能传回来,交接就会丢东西。这是第 8 讲多智能体系统的核心问题。
渐进披露(演示 2):30 个技能,每个说明约 600 token,一次 40 步的会话用到其中 2 个。全部放进系统提示,每次调用 18,000 token;只放名字和一句话描述、用到时再读,每次 2,400 token——40 步下来 720,000 对 96,000。这和第 4 讲”工具太多”是同一个问题、同一种解法(先检索、再加载)。
4.4 一张分类图
slides 最后给出了上下文管理的分类,并预告:记忆(卸载)是下一讲(周三)的主题,多智能体(隔离)是下下讲(周一)的主题。小结:事件日志、记忆、压缩、观察遮蔽、上下文隔离是上下文管理的几个关键模式;真实系统把它们组合起来;上下文工程由两条核心原则驱动——聚焦,并且保持连续。
5. 案例:Pi · Case study
5.1 设计哲学
slides 用 Pi(pi.dev)收尾:“Pi 是一个极简的智能体 harness。让 Pi 适应你的工作流,而不是反过来。“它非常基础,社区在上面写了大量扩展来补上主流 harness 的功能。它刻意没有:子智能体、权限检查、计划模式、智能体待办列表、后台 bash。
- 工具:默认只启用 read、bash、edit、write 四个——工具膨胀最小;“其他工具都是 bash 的子集”。
- 循环:ReAct 式,先推理再行动,工具调用是 JSON。上下文是一棵树:可以回到之前的分支,继续另一条对话线索。
- 上下文管理:(1) 事件日志 → LLM 输入的转换;(2) 通过技能文件做渐进披露;(3) 压缩。
- 安全:以进程的权限运行,没有请求许可的弹窗——“相当于永远开着自动模式,没有保护”。
5.2 照着它写一个
mini_pi.py 照着这些决策写了一个约 250 行的 harness,代码 08 让它(模型是背好台词的 ScriptedLM)在临时目录里修一个 bug:mean() 除以了 n − 1。
- 系统提示(含技能元数据)约 333 token;技能文件的正文(58 token)只有元数据进系统提示,模型需要时自己
read。 - 会话:读技能 → 跑测试(看到 AssertionError)→ 读 calc.py → edit → 再跑测试(all tests passed)→ 回答。每一轮发给模型的 token:352、474、654、759、841、897——只追加的历史,每轮都比上一轮长。
- 会话树存成 JSONL,每行一个条目,带 id 和 parentId;发给模型的消息是从”根 → 当前叶子”的路径重建出来的。
- 压缩:上下文超过 650 token 时,把旧条目换成一个摘要条目(保留最近 4 条消息原文)。后几轮的输入从 897 降到 661 token 以内。压缩只是追加一个摘要条目,日志本身不改。(这里的替身总结器只记下”调用过什么”;真 Pi 用 LLM 写结构化的摘要。)
- 分叉:回到”读完 calc.py”之后,换一种改法。新分支的上下文里没有旧分支的 edit 和第二次测试——可 edit 报错了:
oldText must match exactly once in calc.py, found 0 matches。对话回到了过去,文件没有:旧分支已经改过 calc.py。任何能”回滚上下文”的设计都要想清楚外部状态怎么办(第 5 讲的检查点只回滚了状态图自己的状态;第 15 讲用 git 管理代码的状态)。 - 没有权限检查:
cat ../neighbor/notes.txt(工作区外面一个我们放好的无害文件)照样执行。前置讲 P1 的工作区检查、P4 的策略会把这类命令拦下来要求审批;Pi 把这类保护留给沙箱或扩展。
5.3 小结
slides 的小结:Pi 被设计成极简的 harness;社区写了很多扩展;默认工具是 bash、read、write、edit;它是 ReAct 式的智能体;上下文通过事件日志、渐进披露和压缩来管理;它没有内置任何安全措施。把这一讲的三组模式放回 Pi:它几乎不用 workflow 模式(全交给模型),推理 / 行动上是最朴素的 ReAct + 代码(bash)当动作,上下文上则把事件日志、渐进披露、压缩三种模式组合起来——极简不是”什么都不做”,而是只做最关键的几个决定。
6. 用真实模型试一试 · Try it on a real model
代码 09(可选)让真实的 Claude 用两种动作格式各做几道 cheapest 题:JSON 动作(第 4 讲的文本协议,一轮可以写多个调用)和代码动作(写在 <execute> 里,在白名单解释器里执行):
pip install anthropic
python3 09_real_llm.py
--toy 不联网,用模拟模型跑同样的题。值得看的是:用 JSON 动作时它会不会一轮就把所有独立的查询发出去、最后的心算有没有出错;用代码动作时第一版代码能不能跑通、报错后怎样改、有没有”能跑但算错”的安静 bug。stop sequence 设在 <tool_result 和 <output> 上,免得模型写完动作后接着替工具或解释器编结果。
7. 小结 · Summary
- workflow vs agent:区别在控制流在谁手里。workflow 可预测、便宜、容易调试,但对没预料到的请求无能为力(还可能自信地答非所问);agent 灵活但贵、方差大。真实系统把两者组合起来,路由器的错误会原样传给下游。
- 五种 workflow 模式,每一种都是可靠性的组合:提示链用延迟换准确率(gate 只能查程序能查的);路由的价值取决于分类器的准确率和专家的质量,也能用来省钱;分段让各个调用各管一件事、并行几乎不加延迟;投票只在错误独立时有用,汇总规则就是精确率–召回率的取舍;orchestrator–workers 处理事先不知道个数的子任务;evaluator–optimizer 要有说得出来的反馈、给得出这种反馈的评价器、能按反馈改进的生成器——吵闹的评价器比不审阅还差。
- 组合与搜索:真实系统(STORM)是模式的组合;Archon 自动搜索组合;最佳结构依赖预算,挑选时有赢家诅咒,消融告诉你每个模式值多少。
- 推理与行动:ReAct(先推理;要微调才最好);Reflexion 用环境反馈,而没有外部反馈的自我纠错常常越改越差(误报把对的改成错的);CRITIC 给验证器配工具,能发现什么取决于能用什么工具检查;CodeAct 用代码当动作——更少的轮数、精确的计算、报错即反馈,但要防安静的 bug、要沙箱;ReWOO / LLMCompiler 先规划再执行,省 token、省时间,代价是计划写死后难以应变(LLMCompiler 用重新规划补救);今天多用并行工具调用来实现”编译执行”。
- 上下文管理:两条原则——聚焦、保持连续(包括保住缓存);五种模式——事件日志、记忆卸载、压缩、观察遮蔽、上下文隔离(含渐进披露)。简单的遮蔽和 LLM 总结一样有效、便宜得多;卸载让大胆的删除变安全;遮蔽要攒批做才不打破缓存;隔离省钱但交接会丢东西。
- Pi:极简是一种设计立场——4 个工具、短系统提示、技能渐进披露、会话树、压缩、没有权限检查;分叉回滚的是对话,不是世界。
8. 常见误区 · Common pitfalls
- “agent 比 workflow 高级,能用 agent 就用 agent。” 预料到的请求上 workflow 更准、便宜 70 倍、延迟可预测(代码 01)。先找最简单的方案。
- “路由总能提高质量。” 分类器准确率掉到 80% 时路由几乎没有收益,60% 时比不路由还差(代码 02)。
- “多投几次票总没坏处。” 票与票相关时(同一模型、同一提示),五票和一票差不多;投票前先想办法让票独立。
- “加一个审阅循环总能变好。” 吵闹的评价器比不审阅还差(63.0% vs 67.0%),还更贵。先测评价器的发现率和误报率。
- “让模型检查一下自己的答案。” 没有外部反馈,它常常把对的改成错的(代码 04:77.3% → 63.0%)。给验证器工具(CRITIC),或者用环境反馈(Reflexion)。
- “CRITIC 能发现所有错误。” 它只能发现它的工具能检查的错误;证据本身错了,用同样的证据重算看不出来。
- “代码动作能跑通就是对的。” 安静的 bug 能跑、不报错(代码 05:最终只有 57% 答对)。代码动作需要测试和断言。
- “先规划再执行总是更省。” 省 token 和延迟,但计划写死后遇到意外就全盘失败(ReWOO 遇到别名只剩 44%)。需要重新规划的能力。
- “上下文越全越好。” 全部保留最贵、最容易漏看,还会被截断(代码 07:62.5%)。聚焦。
- “压缩要用 LLM 写摘要才专业。” 简单的观察遮蔽常常一样好、便宜得多,而且占位符保留了”去哪找”的线索。
- “每一步都清理一下上下文。” 每步都改动上下文的中间部分会让前缀缓存每步失效;攒批做。
- “回到之前的对话分支,就回到了之前的状态。” 文件、数据库、外部世界不会跟着回去(代码 08)。
9. 练习 · Exercises
练习 1(提示链的公式):一张回复要满足 4 个要素。一次调用做完时,每个要素以 0.88 的概率被满足;拆成”抽事实 → 起草”两步后,起草时每个要素以 0.95 的概率被满足,但抽事实有 3% 的概率抽错(抽错时每个要素的概率降到 0.80)。比较两种做法的成功率。再加一道 gate:4 个要素里有 2 个能被程序检查,发现缺项就重写一次,重写补上缺项的概率 0.9。成功率变成多少?
参考答案
一次调用:$0.88^4 \approx 0.600$。两步:$0.97 \times 0.95^4 + 0.03 \times 0.80^4 \approx 0.97 \times 0.815 + 0.03 \times 0.410 \approx 0.803$。加 gate:能检查的 2 个要素,每个"最终满足"的概率变成 $p + (1-p) \times 0.9$;抽对时 $p = 0.95$ → $0.995$,抽错时 $p = 0.80$ → $0.98$。成功率 ≈ $0.97 \times 0.995^2 \times 0.95^2 + 0.03 \times 0.98^2 \times 0.80^2 \approx 0.97 \times 0.990 \times 0.9025 + 0.03 \times 0.960 \times 0.64 \approx 0.867 + 0.018 = 0.885$。gate 的收益只来自能检查的那一半要素。练习 2(路由的盈亏点):通用提示词的质量是 0.70;专门提示词分对时 0.90,分错时 0.40。分类器准确率至少要多少,路由才划算?如果把”分错”的代价降到 0.65(例如分错时退回通用提示词),盈亏点变成多少?
参考答案
$a \times 0.90 + (1-a) \times 0.40 > 0.70 \Rightarrow 0.5a > 0.30 \Rightarrow a > 0.6$。分错时退回到 0.65:$0.90a + 0.65(1-a) > 0.70 \Rightarrow 0.25a > 0.05 \Rightarrow a > 0.2$。工程含义:路由器拿不准时(低置信度)退回通用流程,能大大降低对分类器准确率的要求——这也是"兜底"设计的价值。练习 3(投票与相关性):单票的召回率是 0.8。(a) 5 票独立、按过半汇总,召回率是多少?(b) 如果 5 票完全相关(每票都照抄同一个判断),召回率是多少?(c) 说出两种让票更独立的办法。
参考答案
(a) 至少 3 票说是:$\sum_{k=3}^{5} \binom{5}{k} 0.8^k 0.2^{5-k} = 0.2048 + 0.4096 + 0.32768 \approx 0.942$。(b) 0.8——和一票一样。(c) 用不同的提示词或不同的角度提问;用不同的模型;给每一票不同的检索结果或不同的输入切片;用不同的推理路径(例如一票先列证据再判断、一票直接判断)。代价是每票的质量可能不再一样高,需要评估。练习 4(evaluator–optimizer 什么时候有害):评价器对一个真缺的要素的发现率是 ,对一个其实满足的要素误报的概率是 ;生成器按反馈修好一个真缺的要素的概率是 0.8,误报时有 0.3 的概率把那个本来好的要素改坏。一个要素初始以 0.9 的概率被满足。写出”经过一轮评价—修改之后,这个要素被满足的概率”,并求 时 超过多少,这一轮就变成了负收益。
参考答案
一轮之后满足的概率 = $0.9 \times (1 - 0.3f) + 0.1 \times 0.8t$。要比 0.9 大:$0.08t > 0.27f \Rightarrow f < 0.296t$。$t = 0.5$ 时 $f < 0.148$。也就是说,一个只发现一半问题的评价器,误报率超过约 15% 就会让结果变差——代码 02 里"吵闹的评价器"($t = 0.5, f = 0.3$)正是这种情况。初始质量越高(问题越少),对误报率的要求越严。练习 5(为什么自我纠错会变差):用一个两状态的模型解释代码 04 的”无工具自我纠错”:一轮之内,对的答案以 被误报为错,误报后以 0.6 的概率变成错的;错的答案以 0.4 被发现,发现后以 0.4 的概率变成对的。从准确率 0.773 出发,一轮之后准确率是多少?稳态准确率是多少?
参考答案
一轮:$a' = a(1 - 0.15 \times 0.6) + (1-a)(0.4 \times 0.4) = 0.91a + 0.16(1-a)$。$a = 0.773$:$0.703 + 0.036 = 0.740$(代码 04 的第 1 轮是 73.7%,吻合)。稳态 $a = 0.91a + 0.16 - 0.16a \Rightarrow 0.25a = 0.16 \Rightarrow a = 0.64$(代码 04 三轮后 63.0%,正在逼近它)。稳态只取决于"对 → 错"和"错 → 对"两个流量的比,和初始准确率无关——当误报的流量大于纠错的流量时,越改越差。练习 6(CodeAct 的安全):下面这段”模型写的代码”想绕过 safe_exec 的限制,分别指出它会在哪一步被拒绝:f = getattr(get_price, "__globals__");x = [c for c in ().__class__.__mro__];print(*open("secrets.txt"))。再说出白名单解释器防不住的一类风险。
参考答案
`getattr` 不在白名单函数里 → NameError;`().__class__` 是属性访问,只允许作为白名单方法的调用 → Disallowed;`open` 不在白名单里 → NameError(而且 `*` 解包作为参数也不被支持)。防不住的:**白名单工具本身的滥用**——例如代码在循环里调用一个会产生副作用的工具(发邮件、下单)一万次;资源滥用在步数上限内也可能很贵(每次工具调用都花钱)。所以还需要工具层面的限流、预算、审批(第 4、14 讲)。练习 7(ReWOO 的计划):为”Among Japan, Korea, Germany, where is the X2 phone cheapest…”写一个 ReWOO 计划(用 #E 变量)。如果 Korea 不卖这款产品,计划的哪些步骤会失败?求解器应该怎样处理?LLMCompiler 会怎样处理?
参考答案
#E1 = search_product[{"query": "the X2 phone"}];对每个国家:#E(2/5/8) = get_price[{"product": "#E1", "country": …}],#E(3/6/9) = get_exchange_rate[{"currency": "#E2.currency"}](依赖价格),#E(4/7/10) = get_tax_rate[…]。Korea 的 get_price 失败,依赖它的 get_exchange_rate 也失败(依赖失败);税率那一步照样成功(没有依赖)。求解器应该把 Korea 排除,在剩下的国家里比较——如果它没注意到 Korea 的证据是错误信息,可能会"推荐一个不卖的国家"(代码 06 里这正是 ReWOO 在"有国家不卖"的题上掉到 63% 的原因之一)。LLMCompiler 的 joiner 看到 Korea 的结果是错误,可以直接在其余国家里比较(不需要重新规划),因为别的任务都已完成。练习 8(遮蔽与缓存):一个上下文有 40 个观察,你决定只保留最近 8 个的原文。“每步遮一个”和”每 10 步遮 10 个”相比,各自在一个 10 步的窗口里打破了几次前缀缓存?每次打破时大约有多少 token 要重新按写缓存价计费?由此解释代码 07 里两者有缓存成本的差别。
参考答案
每步遮一个:每一步都改了"第 9 新的那个观察",它之后的所有内容(约 8 个观察 + 动作 ≈ 8 × 550 ≈ 4,400 token,加上新的一步)都要重写缓存——10 步里打破 10 次,每次约 4–5K token。攒批:10 步里只打破 1 次,但那一次被遮的 10 个观察之后的内容(约 18 个观察 ≈ 1 万 token)要重写,其余 9 步都只追加、几乎全部命中缓存。所以攒批把"重写的 token"从 ~45K 降到 ~10K 左右,代价是平均上下文更长(多留着那些还没遮的观察)、更容易腐烂。代码 07:每步遮 0.444,攒批 0.179。练习 9(隔离的信息损失):代码 07 里子智能体的策略成功率只有 61.5%。假设跨子任务要用的事实平均有 4 个,每个以 0.9 的概率被写进摘要;没写进去时主智能体以 0.5 的概率想起来重新找。只考虑这一项,成功率大约是多少?提出两种改进。
参考答案
每个跨子任务的事实丢失(且没找回)的概率 = $0.1 \times 0.5 = 0.05$;4 个都没事 = $0.95^4 \approx 0.81$(再乘上上下文腐烂等其他损失,接近代码里的数)。改进:(1) 让主智能体在交出子任务时**明确列出**需要带回来的信息(结构化的返回 schema,第 2 讲的签名);(2) 子智能体把关键事实写进共享的笔记 / 文件(记忆卸载 + 隔离的组合),主智能体需要时去读;(3) 跨子任务依赖重的任务干脆不要拆(第 8 讲:"不要小看单智能体")。练习 10(开放题:给 HW1 的 harness 选模式):原课 HW1 的内部助手要做邮件分类、每日摘要、多跳检索问答、工具调用和跨会话记忆。为每一项选一个本讲的模式(或组合),说明理由,并指出你会用什么评估来验证这个选择。
参考答案(之一)
邮件分类:路由的第一步本身就是分类,用 workflow(一次调用 + 结构化输出),评估用带标签的数据看准确率和各类的召回。每日摘要:提示链(抽要点 → 分组 → 写摘要)+ gate(检查是否覆盖了所有高优先级邮件),评估看覆盖率和人工评分。多跳检索问答:ReAct 或 LLMCompiler(需要随机应变;独立的检索可以并行),评估看 EM 和每题的调用 / 延迟。工具调用:校验与执行分离(第 4 讲),代码动作只在需要循环和计算时用,并且放进白名单解释器。跨会话记忆:记忆卸载(第 7 讲)+ 观察遮蔽 + 渐进披露。所有选择都要用"准确率 + 成本 + 延迟"三个数验证,并做消融:拿掉一个模式看掉多少。10. 延伸阅读 · Further reading
本讲的阅读材料
- S. Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models, ICLR 2023.
workflow 模式
- E. Schluntz & B. Zhang, Building Effective Agents, Anthropic, 2024(第 2 讲的阅读材料;workflow 分类的出处)。
- Y. Shao et al., Assisting in Writing Wikipedia-like Articles From Scratch with Large Language Models(STORM),NAACL 2024.
- J. Saad-Falcon et al., Archon: An Architecture Search Framework for Inference-Time Techniques, 2024.
推理与行动
- N. Shinn et al., Reflexion: Language Agents with Verbal Reinforcement Learning, NeurIPS 2023.
- J. Huang et al., Large Language Models Cannot Self-Correct Reasoning Yet, ICLR 2024.
- Z. Gou et al., CRITIC: Large Language Models Can Self-Correct with Tool-Interactive Critiquing, ICLR 2024.
- X. Wang et al., Executable Code Actions Elicit Better LLM Agents(CodeAct),ICML 2024.
- B. Xu et al., ReWOO: Decoupling Reasoning from Observations for Efficient Augmented Language Models, 2023.
- S. Kim et al., An LLM Compiler for Parallel Function Calling, ICML 2024.
上下文管理
- P. Rajasekaran et al., Effective Context Engineering for AI Agents, Anthropic, 2025(第 2 讲的阅读材料)。
- T. Lindenbauer et al., The Complexity Trap: Simple Observation Masking Is as Efficient as LLM Summarization for Agent Context Management, 2025.
- X. Wang et al., OpenHands: An Open Platform for AI Software Developers as Generalist Agents, ICLR 2025(事件流架构)。
- C. Packer et al., MemGPT: Towards LLMs as Operating Systems, 2023(第 7 讲)。
- Anthropic, Equipping Agents for the Real World with Agent Skills(技能文件与渐进披露)。
案例
- Pi 的文档(pi.dev):工具、压缩、会话树。
11. 下一讲预告 · Next lecture
第 7 讲 智能体的记忆架构:这一讲把”记忆卸载”列为上下文管理的一种模式;下一讲把它展开成一整套架构。slides 会从两个真实的”失忆事故”讲起,然后讲:没有记忆会丢掉什么(程序、知识、进展),有了记忆能得到什么(个性化、从经验中学习、有状态);CoALA 的记忆分类(工作记忆、情景记忆、语义记忆、程序性记忆)和”参数记忆 vs 上下文记忆”;设计记忆的三个问题——存什么、什么时候存 / 取、怎样存 / 取(文件系统、数据库、知识图谱);四种架构模式——从经验中学习程序(Voyager、AWM、技能文件)、分层记忆(MemGPT、MemoryOS)、反思式整合(Reflexion、生成式智能体)、记忆维护(睡眠时计算、A-Mem、Mem0);怎样评估记忆(LoCoMo、PersonaMem、LongMemEval-v2、MemoryArena);以及该记住什么、该忘记什么,记忆带来的信任、隐私和”过度个性化”的问题。配套代码会在一个跨越几十个会话、偏好不断变化的模拟用户上,比较多种记忆系统的准确率、成本和”用了过期记忆”的错误。