Lecture 6 · 智能体设计模式与脚手架 · Agent Design Patterns & Scaffolds

Engineering AI Agents · Lecture 06。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

个人整理的学习笔记,非官方讲义;课程来源与整理说明见课程介绍。配套示例未逐一运行验证。

本篇目录

对应 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)里的设计决策。展开来说,学完这一讲你应该能够:

  1. 说清楚 workflow 和 agent 的区别在”控制流在谁手里”,各自适合什么任务,以及为什么真实系统常常把两者组合起来。
  2. 认出五种 workflow 模式——提示链、路由、并行化(分段 / 投票)、orchestrator–workers、evaluator–optimizer——知道每一种用什么换来了什么,以及它们在什么条件下才有用;能把一个真实系统(STORM)拆成这些模式的组合。
  3. 比较几种推理 / 行动模式:ReAct、Reflexion、CRITIC、CodeAct、ReWOO、LLMCompiler;解释为什么”没有外部反馈的自我纠错”常常适得其反,为什么代码是好的动作形态,以及”先规划再执行”拿灵活性换了什么。
  4. 用”聚焦”和”连续”两条原则评估上下文管理的五种模式(事件日志、记忆卸载、压缩、观察遮蔽、上下文隔离),并知道它们怎样组合、怎样影响前缀缓存。
  5. 读懂一个极简 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 的 1、1、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 一端。它的对照表:

workflowagent
控制流代码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.01122.32.5
workflow没预料到的0.0%1.01080.70.6
agent(ReAct)预料到的93.5%9.37,84614.125.4
agent(ReAct)没预料到的96.0%5.43,5437.711.2
workflow + agent 兜底预料到的100.0%1.01122.32.5
workflow + agent 兜底没预料到的91.3%6.23,4758.011.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:用延迟换准确率;任务复杂、能分解时有用。

为什么有用:一次调用里塞进好几件事,每件事都做得差一点。设一次调用里每个要素被满足的概率是 pp,回复要满足 rr 个要素,那么一次做对的概率是 prp^r;如果”读、抽、写、查”四件事挤在一起让 pp 从 0.95 降到 0.89,对 4 个要素的 outage 工单就是 0.954=0.810.95^4 = 0.81 和 0.894=0.630.89^4 = 0.63 的差别。拆开之后每一步更专注;gate 再把它能检查的缺项挑出来让模型重写。

做法要素齐全调用成本(美分)延迟(秒)
一次调用(读、抽、写、查都在一起)68.6%1.000.4272.81
提示链:抽事实 → 起草(无 gate)80.2%2.000.5003.51
提示链 + gate(程序检查能检查的要素)86.8%2.080.5323.72

gate 只能查程序能查的东西:政策编号、事故编号、链接在不在回复里;“有没有道歉""重启步骤写得对不对”它查不了——那需要一个 LLM 评价器(§2.5)。这就是为什么加了 gate 也到不了 100%。

2.2 路由:分类,然后交给专家

是什么:先给输入分类,再交给合适的 LLM(不同的提示词,或者不同的模型)。slides:复杂任务里有几类最好分开处理的情况时有用;也能省钱——简单的交给更弱、更便宜的模型。

组合公式:设分类器准确率 aa,分对之后专门流程的质量 qspecq_{\text{spec}},分错之后质量 qwrongq_{\text{wrong}},那么整体质量 ≈a⋅qspec+(1−a) qwrong\approx a \cdot q_{\text{spec}} + (1 - a)\, q_{\text{wrong}}。路由只有在 a⋅qspec+(1−a)qwrong>qgenerica \cdot q_{\text{spec}} + (1-a) q_{\text{wrong}} > q_{\text{generic}} 时才划算——分类器要足够准,专家要足够强。

做法要素齐全billingtechnicalaccountoutage调用成本(美分)延迟(秒)
不路由:大模型 + 通用提示词53.1%63%44%70%26%1.000.4272.81
按类别路由 → 大模型 + 专门提示词65.6%65%62%72%67%2.000.4353.05
按类别和难度路由(简单的给小模型)58.8%59%56%71%48%2.000.1961.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.000.3882.55
两次调用,先后89%4.4%81.9%2.000.3822.72
两次调用,同时86%3.9%82.7%2.000.3822.51

塞在一起时两件事互相干扰:护栏识别率最低,起草也变差。拆开之后,串行要多等一次小模型调用;并行几乎不多花时间——延迟由更慢的那个(起草)决定。(两种”拆开”的识别率差别在噪声以内:只有 29 张安全事件工单 × 3 次。)

投票。同一个”是不是安全事件”问 5 次,再用不同的规则汇总(演示 3b;安全事件只占 8%):

票间相关性规则精确率召回率F1
0(独立)单票59%79%0.68
05 票,过半100%94%0.97
05 票,≥ 2 票77%99%0.86
05 票,任一27%100%0.42
0.6单票63%83%0.72
0.65 票,过半67%92%0.78
0.65 票,任一34%97%0.50

两个要点:

  1. 汇总规则就是精确率和召回率的取舍。“任一票说是就是”召回最高、误报也最多;“过半”两头兼顾。安全场景常常宁可误报(用”任一”或”≥ 2”),然后交给人复核。
  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:

需要排查的子系统数01234
固定分段查全了100%57%18%0%0%
orchestrator 查全了100%90%87%78%75%
orchestrator 派出的 worker(平均)0.11.02.02.93.9

整体查全率:固定分段 44.0%,orchestrator 89.1%;代价是多一次规划调用(每张工单 3.00 → 3.59 次调用,成本 0.250 → 0.311 美分)。orchestrator 的上限由”它能不能想到所有要查的东西”决定(这里每个子系统以 0.93 的概率被想到)——需要查的越多,漏掉一个的概率越大(1−0.93k1 - 0.93^k)。这正是第 8 讲多智能体系统的基本结构。

2.5 evaluator–optimizer:三个条件缺一不可

是什么:一个 LLM 生成,另一个评价并给出反馈,循环。slides 特别列了它成立的条件:(1) 有清楚、说得出来的反馈;(2) 有一个 LLM 能给出这种反馈;此外生成器还得能根据反馈改进。

代码 02 演示 5 把条件一个一个拿掉:

条件要素齐全平均修改轮数调用成本(美分)延迟(秒)
不审阅(只起草一次)67.0%0.001.000.4272.81
好评价器(发现率 0.9、误报 0.05)+ 具体反馈94.5%0.593.140.7965.61
吵闹的评价器(发现率 0.5、误报 0.3)63.0%1.484.641.1828.37
反馈不具体(只说”不够好”)77.9%0.833.460.8896.27
生成器不会按反馈改(改对率 0.2)76.8%0.893.560.9166.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):

调用次数GSM8KCommonSenseQA
标准提示191.584.0
反馈提示 A:“假设这个答案可能对也可能错。仔细检查并报告严重问题。” 第 1 轮 / 第 2 轮3 / 588.0 / 90.081.5 / 83.0
反馈提示 B:“检查你之前的答案是否正确;如果错了,找出问题。”3 / 590.0 / 90.074.5 / 81.0
反馈提示 C:“验证你的答案是否正确,并给出解释。”3 / 591.0 / 91.081.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.03.00
自我纠错(无工具)77.3%73.7%69.3%63.0%4.63.060
自我纠错 + 神谕停止(只在真错时改)77.3%81.7%84.3%87.7%4.23.00
CRITIC(用代码重算)77.3%96.0%96.0%96.0%4.26.00
CRITIC + 重新查询证据77.3%100.0%100.0%100.0%4.215.00
  • 无工具的自我纠错越改越差(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)。

为什么?三个原因:

  1. 控制流:循环、条件、变量都写在一段代码里,一轮做完 JSON 动作要好几轮才能做完的事;
  2. 把算术和”记住每个数”交给解释器:没有心算错误,也不会在长上下文里张冠李戴;
  3. 报错就是反馈:KeyError、ValueError 告诉模型哪里错了,它可以改代码再跑(自我调试)。

代码 05 在 90 道 cheapest 题上(× 5 次)比较了三种做法:

动作格式成功率轮数输入 token延迟(秒)
JSON,每轮一个调用(ReAct)93.3%12.511,29919.3
JSON,一轮多个调用92.7%3.52,89110.4
CodeAct(代码动作)94.7%2.26749.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):
第一版代码次数最终答对
没有 bug299100%
用了产品的别名、没先 search_product5493%
p["cost"] 写错键名(KeyError,响亮)3985%
没有 try / except,遇到不卖的国家(ValueError,响亮)1788%
税算成 price * tax(能跑、结果错,安静)2857%

响亮的 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),例如”微软的市值要涨多少才能超过苹果”:1=search(微软市值)、1 = search(微软市值)、2 = search(苹果市值)、3=math(3 = math(1 / 2)、2)、4 = llm(3)——3)——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.37,8464258.214.1
ReAct + 并行调用93.2%3.52,5463858.58.3
ReWOO75.9%2.06272387.88.1
LLMCompiler95.3%2.34961869.24.8
  • token:ReWOO 的输入 token 约是 ReAct 的 1/13。规划器和求解器各只调用一次,工具调用之间不调用 LLM,也就不用一遍遍重发越来越长的历史——这是”5 倍 token 效率”的来源(我们的题要查的东西更多,差距更大)。
  • 延迟:LLMCompiler 4.8 秒,比 ReAct 快 2.9 倍。它的工具调用并不更少,但互相独立的查询在同一波里并行,算术由 calc 任务在代码里做。

可 ReWOO 的准确率只有 75.9%。按题目里有没有”意外”分组:

题目ReActReAct + 并行ReWOOLLMCompiler
没有意外(规范名、都卖)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,253K32.0K0.00.01.2720.535
观察遮蔽:只保留最近 8 个观察的原文83.0%557K12.4K7.70.00.5780.444
观察遮蔽,攒够 10 个旧观察再一起遮77.0%708K17.1K6.00.00.7280.179
LLM 总结:超过 16K 就把旧的部分总结成 400 token48.5%677K15.9K0.53.60.7020.203
遮蔽 + 总结(遮蔽之后仍超过 10K 才总结)73.0%541K11.6K7.53.90.5690.455
记忆卸载(关键事实记进笔记)+ 观察遮蔽89.5%471K10.8K0.80.00.4890.404
记忆卸载 + 攒批遮蔽82.0%619K16.2K0.60.00.6380.160
上下文隔离:每 10 步交给一个子智能体61.5%266K9.1K0.460.00.2860.100

(成本单位:输入 1 token = 1、读缓存 0.1、写缓存 1.25、输出 5,除以 100 万。)

逐条读:

  1. 全部保留最贵(总输入 125 万 token),成功率却只有 62.5%:上下文越长越容易漏看,超出窗口之后还要从开头截掉、而且截掉了也不告诉模型。从开头截掉也让前缀缓存每一步都失效。
  2. 观察遮蔽把总输入降到 44%,成功率 83.0%,高于 LLM 总结的 48.5%——总结还要多花 3.6 次调用,并且会丢掉事实、连”去哪找”的线索都丢了;而遮蔽留下的占位符告诉模型”这里曾经有什么、怎样找回来”。论文的结论(在 SWE-bench Verified 上、五种模型配置下):简单的遮蔽和 LLM 总结的解决率相当、有时略好,成本约为原始智能体的一半;他们提出的混合方法再省 7%(相对遮蔽)和 11%(相对总结)。我们这里遮蔽领先得更多,那取决于”总结时丢事实”的旋钮;混合方法在我们的模型里没有带来好处——这也提醒我们,哪种组合最好要在自己的任务上量。
  3. 连续性 = 保住缓存。每一步都遮一次,最早那个被遮的观察之后的缓存每一步都被打破(有缓存的成本 0.444);攒够 10 个再一起遮,成本降到 0.179,代价是上下文长一些、成功率 77.0%。有缓存时,LLM 总结(0.203)反而比”每步都遮”便宜——两次总结之间上下文是只追加的。第 2 讲 §7.4 的”攒批清理”是同一件事。
  4. 记忆卸载 + 遮蔽:关键事实写进笔记,原文就可以放心遮掉——成功率 89.5%,单智能体策略里总输入最低。再配上攒批遮蔽,有缓存的成本降到 0.160。这正是 slides 说的”模式可以叠加”。
  5. 上下文隔离:主智能体的上下文最小、成本最低(有缓存 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

  1. workflow vs agent:区别在控制流在谁手里。workflow 可预测、便宜、容易调试,但对没预料到的请求无能为力(还可能自信地答非所问);agent 灵活但贵、方差大。真实系统把两者组合起来,路由器的错误会原样传给下游。
  2. 五种 workflow 模式,每一种都是可靠性的组合:提示链用延迟换准确率(gate 只能查程序能查的);路由的价值取决于分类器的准确率和专家的质量,也能用来省钱;分段让各个调用各管一件事、并行几乎不加延迟;投票只在错误独立时有用,汇总规则就是精确率–召回率的取舍;orchestrator–workers 处理事先不知道个数的子任务;evaluator–optimizer 要有说得出来的反馈、给得出这种反馈的评价器、能按反馈改进的生成器——吵闹的评价器比不审阅还差。
  3. 组合与搜索:真实系统(STORM)是模式的组合;Archon 自动搜索组合;最佳结构依赖预算,挑选时有赢家诅咒,消融告诉你每个模式值多少。
  4. 推理与行动:ReAct(先推理;要微调才最好);Reflexion 用环境反馈,而没有外部反馈的自我纠错常常越改越差(误报把对的改成错的);CRITIC 给验证器配工具,能发现什么取决于能用什么工具检查;CodeAct 用代码当动作——更少的轮数、精确的计算、报错即反馈,但要防安静的 bug、要沙箱;ReWOO / LLMCompiler 先规划再执行,省 token、省时间,代价是计划写死后难以应变(LLMCompiler 用重新规划补救);今天多用并行工具调用来实现”编译执行”。
  5. 上下文管理:两条原则——聚焦、保持连续(包括保住缓存);五种模式——事件日志、记忆卸载、压缩、观察遮蔽、上下文隔离(含渐进披露)。简单的遮蔽和 LLM 总结一样有效、便宜得多;卸载让大胆的删除变安全;遮蔽要攒批做才不打破缓存;隔离省钱但交接会丢东西。
  6. 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 什么时候有害):评价器对一个真缺的要素的发现率是 tt,对一个其实满足的要素误报的概率是 ff;生成器按反馈修好一个真缺的要素的概率是 0.8,误报时有 0.3 的概率把那个本来好的要素改坏。一个要素初始以 0.9 的概率被满足。写出”经过一轮评价—修改之后,这个要素被满足的概率”,并求 t=0.5t = 0.5 时 ff 超过多少,这一轮就变成了负收益。

参考答案 一轮之后满足的概率 = $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 的”无工具自我纠错”:一轮之内,对的答案以 pFA=0.15p_{FA} = 0.15 被误报为错,误报后以 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);以及该记住什么、该忘记什么,记忆带来的信任、隐私和”过度个性化”的问题。配套代码会在一个跨越几十个会话、偏好不断变化的模拟用户上,比较多种记忆系统的准确率、成本和”用了过期记忆”的错误。

← 返回课程目录