时长 7 小时:§1 智能体是什么(0.5h)· §2 ReAct(1.5h)· §3 错误累积与可靠性(1h)· §4 预算与护栏(1h)· §5 记忆(0.5h)· §6 多智能体(1h)· §7 真实的难点(0.5h)· §A–§C(1h)
对应正课 Lecture 7 的最后一部分:智能体的定义、“传统 → 推理 → 智能体”三个层次、ReAct 框架(输入 → 观察 → 计划 → 行动 → 输出,恒温器与泰迪熊的例子)、多个智能体之间怎么通信(A2A 协议)、安全、Lecture 7 的结语。学习计划点名要会的:“Agent = LLM + 工具 + 记忆 + 循环。ReAct 的 Thought → Action → Observation 循环;Plan-and-Execute 的对比;多智能体编排的收益与开销。""Agent 的真实难点:错误累积(每步 95% 正确,20 步后只剩 36%)、无限循环、成本失控、工具调用幻觉。”
配套代码
w4day4_checks.py(纯 numpy):错误累积、检查与重试(公式与蒙特卡洛)、pass@k 与 pass^k 及其无偏估计、卡住的智能体与最大步数、上下文的增长与压缩、单智能体与多智能体的 token 和延迟§B 参考实现(
code/目录):w4d4_react_agent.py(文本格式与原生函数调用两种 ReAct、五条护栏、记忆压缩、JSONL 追踪、五个模拟场景、在黄金集上跑并导出答案给 Day 6 评测、A2A 风格的 AgentCard)。今天的目标:能说清智能体和”调一次工具”的区别,以及什么时候该用工作流而不是智能体;能解释 ReAct 为什么要把推理和行动交替起来,写出它的循环和解析时的坑;能用公式说明错误累积,算出检查与重试能救回多少,区分 pass@k 与 pass^k;能设计一个智能体的护栏和记忆策略;能讨论多智能体的收益与代价。
§0 今天的主线
Day 3 的工具调用是”一问一答,中间调几次工具”。循环由程序写死:模型调用工具 → 程序执行 → 模型回答。智能体把这个循环的控制权交给模型:给它一个目标,它自己决定要做哪些步骤、按什么顺序、什么时候算完成。
传统 LLM: 问题 ──> LLM ──> 答案
推理模型: 问题 ──> LLM ──> 推理链 ──> 答案 (第 3 周)
智能体: 问题 ──> LLM ──> 调用 ──> LLM ──> 调用 ──> … ──> 答案 (今天)
↑_______ 观察结果 ______|
控制权交出去之后,三个新问题随之而来:怎么让模型会规划(§2 ReAct);长链条上的错误怎么办(§3);怎么防止它失控(§4 护栏、§5 记忆)。最后讨论把一个智能体拆成多个的得与失(§6)。
今天要用到的旧结论:思维链(Week 2 Day 3 §5.1);pass@k 的无偏估计(Week 3 Day 5 §5.1);工具调用的三步与消息格式(Day 3 §2.2);多轮调用的 token 账(Day 3 §2.3);出错时把错误还给模型(Day 3 §2.4);提示注入与最小权限(Day 3 §6);两阶段检索(Day 2 §4)。
§1 智能体是什么
1.1 定义与三个层次
Lecture 7 的定义:“智能体是一个自主地追求目标、代表用户完成任务的系统。“关键词是自主:下一步做什么由模型决定,而不是由程序员事先写好。
上面的图给出三个层次。推理模型已经会”想很久”(第 3 周),但它的思考全在自己脑子里;智能体的每一步思考都可以和外部世界交互:查资料、算数、执行动作,然后根据结果调整下一步。
1.2 LLM + 工具 + 记忆 + 循环
学习计划的公式:智能体 = LLM + 工具 + 记忆 + 循环。
| 部件 | 作用 | 这一周在哪里 |
|---|---|---|
| LLM | 决策:分析现状、决定下一步 | 前三周 |
| 工具 | 感知与行动:获取信息、计算、改变外部状态 | Day 3 |
| 记忆 | 记住做过什么、学到了什么;上下文是短期记忆,外部存储是长期记忆 | §5;RAG 是一种长期记忆(Day 1–2) |
| 循环 | 反复”观察 → 决策 → 行动”,直到完成或放弃 | §2 |
触发智能体的不一定是用户:Lecture 7 指出输入可以是用户手动输入的问题,也可以是一个外部事件(“某个指标超过了阈值”)。后者就是一个在后台常驻、按事件行动的智能体。
1.3 工作流还是智能体
“智能体”很吸引人,但它不是默认选项。Anthropic 的工程文章 “Building effective agents”(2024)做了一个有用的区分:
- 工作流(workflow):LLM 和工具按预先写好的代码路径编排。常见的几种:提示链(上一步的输出是下一步的输入)、路由(先分类,再交给不同的处理流程)、并行(同时做几件事再汇总)、编排者–执行者(一个 LLM 拆任务,几个 LLM 执行)、评审–改写(一个生成,一个评判,循环改进)。
- 智能体(agent):LLM 动态地决定自己的流程和工具使用。
它的核心建议是:先找最简单的方案,只在需要时增加复杂度。很多应用用一次带检索和示例的 LLM 调用就够了;步骤固定、可预测的任务用工作流;只有当步骤数和路径事先无法确定时才用智能体。原因是 §3 的错误累积:自主性越强,链条越长,出错的机会越多,也越难调试。Lecture 7 的结语说的是同一件事:“先从简单的做起,逐步迭代、扩大规模。”
§2 ReAct
2.1 为什么要把推理和行动交替起来
ReAct = Reason + Act(Yao et al. 2022)。它是在对比两种”只做一半”的方法之后提出的:
- 只推理(思维链):模型在脑子里一步步推,但所有事实都来自它自己的记忆。常见的失败是幻觉(编造一个事实)和错误传播(一步错,后面全错)。
- 只行动:模型直接一个接一个地调用工具,不写出推理。它缺少计划和追踪进度的能力:不知道自己已经做到哪一步、下一步为什么要做这个,在长任务里容易迷路。
ReAct 的想法:每一步都先写出思考(“现在知道什么、还缺什么、下一步做什么”),再行动,再根据观察更新思考。推理帮助行动(决定查什么、何时停),行动帮助推理(用真实的观察代替编造的事实)。
论文的结果:在需要查维基百科的问答(HotpotQA)和事实核查(FEVER)上,ReAct 通过调用一个简单的维基百科 API,缓解了思维链的幻觉和错误传播;在两个交互式决策任务(ALFWorld 文本游戏、WebShop 网上购物)上,ReAct 比模仿学习和强化学习的方法在成功率上分别高出 34 和 10 个百分点,而且只用了一两个示范。
2.2 循环:观察 → 计划 → 行动
Lecture 7 用恒温器的例子把循环画成了四个阶段:
输入: 我的泰迪熊很冷,想想办法。
观察: 泰迪熊冷,可能是因为房间温度低,但现在不知道房间温度。 ← 综合已知信息,说清还缺什么(推理最重的一步)
计划: 确定房间温度。 ← 决定要完成什么任务、调用什么工具
行动: get_current_room_temperature() ← 通过 API 执行,或者去文档库里查
观察: 房间现在 65°F,比平均低 5°F。需要把温度调高。
计划: 把温度调高 5°F。
行动: increase_temperature(value=5)
观察: 恒温器现在设在 70°F,应该够暖和了。
输出: 恒温器已调到 70°F,你的泰迪熊很快就会暖和起来。
从外面看,这整个循环就是一个”恒温器智能体”:输入一句话,输出一个结果,中间做了什么都封装在里面。
参考代码的 --demo 场景 1 是同一个结构:
[1] 思考:先在讲义里找 KV Cache 显存的公式在哪一节。
行动:search_notes({"query": "KV Cache 显存 公式 估算", "k": 3})
观察:[{"ref": "Week 1 Day 4 §1.4", "title": "…显存:一个 30 秒的公式", …}, …]
[2] 思考:检索结果指向 Week 1 Day 4 §1.4,读原文确认公式。
行动:read_section({"week": 1, "day": 4, "section": "1.4"})
[3] 思考:公式是 2 × 层数 × KV 头数 × 头维 × 长度 × batch × 字节数……
行动:kv_cache_gib({… "seq_len": 32768, "batch_size": 8}) 观察:{"GiB": 32.0, …}
[4] 行动:calculator({"expression": "32.0 / (80e9 / 2**30)"}) 观察:0.429497
[5] 最终答案:约 32.0 GiB,约等于 0.43 张 80 GB 显卡的容量……公式见 Week 1 Day 4 §1.4。
注意第 2 步:智能体没有直接相信检索结果的片段,而是去读原文。这正是 ReAct 的价值:思考让它意识到”片段可能不完整,要核实”。
2.3 提示格式与解析
ReAct 有两种实现方式:
- 文本格式(原论文的做法):把工具说明写进系统提示,要求模型按”思考 / 行动 / 行动输入”的格式输出,程序用正则解析。任何能跟随指令的模型都能用,而且每一步的思考都清清楚楚。参考代码的
REACT_SYSTEM和parse_react。 - 原生函数调用:思考写在回复的文字部分,行动是
tool_calls(Day 3 §2.2)。格式更可靠,还能一步并行多个调用;需要模型支持。参考代码的--mode native。
文本格式的经典坑:模型自己编观察。模型在训练数据里见过大量”行动 → 观察 → 思考”的文本,写完”行动输入”之后,它常常顺手接着写一段”观察:“,然后基于这段编出来的观察继续推理。场景 4:
模型输出:思考:算一下 2 的 10 次方。
行动:calculator
行动输入:{"expression": "2**10"}
观察:1000 ← 模型编的
最终答案:1000
对策:解析时在”观察:“处截断,后面的一切丢掉(parse_react 会标记 faked_observation);调 API 时设停止词(stop sequence)为”观察:“,让模型写到那里就停(§A.1)。场景 4 里,真实的计算器返回 1024,智能体的最终答案也是 1024。
解析失败怎么办:行动输入写成了 Python 字典(单引号)、格式不对……不要去”猜”模型的意思,把具体的错误还给它(“行动输入不是合法的 JSON 对象,请用双引号”),让它改。这和 Day 3 §2.4 是同一个原则:错误信息越具体,模型越容易改对。
2.4 什么时候停
ReAct 的循环有三种正常的结束方式,和几种异常的:
| 停止原因 | 含义 | 参考代码 |
|---|---|---|
answer | 模型给出了最终答案 | 正常 |
max_steps、max_tokens、timeout | 预算用完 | §4.2 |
repeated_action | 同一个动作重复太多次,判定为卡住 | §4.1 |
too_many_errors | 连续出错太多次 | §3.2 |
把停止原因记下来是调试智能体的第一步:一批任务里 30% 是 max_steps,说明要么步数上限太紧、要么模型在兜圈子,两种情况的修法完全不同。
§3 错误累积与可靠性
3.1 指数衰减
一个任务要连续做对 步,每步独立地以概率 做对,整体成功率是 。w4day4_checks.py 第 1 组:
| 每步正确率 | 5 步 | 10 步 | 20 步 | 50 步 |
|---|---|---|---|---|
| 0.90 | 0.590 | 0.349 | 0.122 | 0.005 |
| 0.95 | 0.774 | 0.599 | 0.358 | 0.077 |
| 0.99 | 0.951 | 0.904 | 0.818 | 0.605 |
每步 95% 正确,20 步后只剩 36%(学习计划的例子)。反过来算:要让 20 步的任务有 90% 的成功率,每步要做到 。长任务对单步可靠性的要求是指数级的。这和 Week 2 Day 3 §5.1 思维链的错误传播、Week 3 Day 6 §3.2 “一步错整题就错”是同一个数学。
3.2 检查与重试
独立同分布的假设太悲观了:错误如果能被发现,就可以重试。设每步以概率 做对;做错时,一个检查器以召回率 发现错误,发现了就重试(最多 次),没发现就带着错误继续。每步的最终成功率满足递推
第 2 组(、,公式与 10 万次模拟吻合):
| 检查器召回 | 不重试 | 重试 1 次 | 重试 3 次 |
|---|---|---|---|
| 0 | 0.358 | 0.358 | 0.358 |
| 0.5 | 0.358 | 0.587 | 0.595 |
| 0.9 | 0.358 | 0.865 | 0.900 |
| 1.0 | 0.358 | 0.951 | 1.000 |
检查器的召回率比重试次数更关键:发现不了错误()时重试多少次都没用; 时重试 1 次就从 0.358 升到 0.865,而每步平均只多花 0.04 次尝试。
设计智能体时的第一个问题因此是:“每一步的错误能不能被自动发现?” 提高 的手段:参数校验(Day 3 validate_args)、工具明确地报错而不是静默返回空(Day 3 §2.4)、代码任务跑单元测试、关键中间结果加断言、让模型自己核对(召回率有限,但不为零)。编程智能体之所以是目前最成功的一类智能体(Lecture 7 的”个人最爱用例”),一个重要原因就是代码有编译器和测试, 天然很高。
3.3 一致性:pass@k 与 pass^k
同一个任务跑 次,单次成功率 :
- pass@k :至少成功一次。适合”有验证器、可以多试几次挑一个”的场景(Week 3 Day 5 §5.1)。
- pass^k : 次全部成功。τ-bench(Yao et al. 2024)提出它来衡量可靠性:面向用户的智能体要对每一个用户都办对,而不是”试 8 次总有一次对”。
第 3 组:三个 pass@1 都约 0.5 的智能体:
| 智能体 | pass@1 | pass@8 | pass^2 | pass^8 |
|---|---|---|---|---|
| A:每道题都是 0.5 的机会 | 0.500 | 0.996 | 0.250 | 0.0039 |
| B:一半题必成、一半题必败 | 0.500 | 0.500 | 0.500 | 0.5000 |
| C:成功率 ~ Beta(2, 2) | 0.506 | 0.949 | 0.306 | 0.0542 |
同样的 pass@1,可靠性可以天差地别。A 每次都在碰运气(pass@8 很高、pass^8 几乎为零),B 会的题一定做对。τ-bench 的结果:最强的函数调用智能体(如 gpt-4o)在它的任务上成功率不到 50%,零售场景的 pass^8 不到 25%。
无偏估计:每个任务跑 次、成功 次,pass^k 的无偏估计是 (无放回地抽 次全是成功)。朴素的 偏高: 是凸函数,由 Jensen 不等式 。第 3 组:真值 0.1441,无偏估计 0.1472,朴素估计 0.1831。Day 5 会把 pass^k 放进基准测试的框架里。
3.4 设计原则
把 §3.1–3.3 翻译成设计原则:
- 减少步数:能用一个工具做完的,不要拆成三个;确定性的部分写成代码(工作流),不要让模型每次都重新决策(§1.3)。
- 让每一步可验证:工具返回明确的成功或失败;关键步骤后面加检查。
- 尽早失败:错误越早发现,浪费的步骤越少;参数不合法就立刻报错,不要等到下游才崩。
- 测可靠性,而不只是准确率:同一批任务跑多次,看 pass^k 和失败的分布。
§4 预算与护栏
4.1 卡住:循环与没有进展
智能体最常见的失控方式不是做了坏事,而是原地打转:用同样的参数反复调用同一个工具、在两个动作之间来回切换、或者一直”再查一次”却从不回答。场景 2(looping_policy)就是这样:同一个检索做了三遍。
参考代码的对策是重复检测:把”工具名 + 参数”规范化成一个键,记录出现次数。第 2 次出现时,在观察前面加一句提醒(“你已经执行过完全相同的动作,结果不会变”);超过上限(默认 2 次)就停止。它比固定的最大步数更早止损,也更精确地告诉你”为什么停”。
第 4 组:把每一步看成一次抽签(以 0.12 完成、0.02 放弃,其余继续),另有 5% 的运行一开始就卡住、永不结束。正常运行成功时的步数,95% 分位是 20:
| 最大步数 | 成功率 | 被截断的正常运行 | 平均每次运行的步数 |
|---|---|---|---|
| 10 | 0.633 | 0.211 | 5.8 |
| 20 | 0.773 | 0.047 | 7.4 |
| 30 | 0.804 | 0.010 | 8.2 |
| 50 | 0.813 | 0.001 | 9.3 |
| 400 | 0.813 | 0.000 | 26.6 |
不设上限时,5% 卡住的运行让平均成本变成无穷大。上限取在”成功运行步数的 95% 分位”附近(这里 20–30 步),几乎不损失成功率,又把成本封顶。太小(10 步)会截掉大量本来能成功的运行;太大(400 步)时,卡住的 5% 吃掉了大部分预算。上限要从数据里定:先不设(或设得很大)跑一批,看成功运行的步数分布。
4.2 五条护栏
参考代码的 Budget:
| 护栏 | 防的是什么 | 怎么定 |
|---|---|---|
| 最大步数 | 兜圈子、过度探索 | 成功运行步数的高分位(第 4 组) |
| token 预算 | 成本失控(上下文随步数平方增长,Day 3 §2.3) | 按单次任务的可接受成本倒推 |
| 时间预算 | 用户等不了;外部 API 卡住 | 按产品的延迟要求 |
| 重复动作上限 | 原地打转(§4.1) | 通常 2–3 次 |
| 连续出错上限 | 模型不会用某个工具、工具坏了 | 通常 3 次;连续出错往往说明是系统问题,不是模型再试一次能解决的 |
护栏触发时要优雅地失败:告诉用户”没能完成,做到了哪一步、卡在哪里”,而不是返回一个半截的、看起来像答案的东西。
4.3 人在回路
有些动作不该让智能体独自决定(Day 3 §6.2):不可逆的(发送、删除、付款)、影响他人的、超出常规权限的。常见的做法是在这些动作之前暂停,请用户确认,其余的自动执行。这是自主性和安全之间的权衡点:确认太多,智能体就退化成了一个需要人一步步点”下一步”的向导。
§5 记忆
5.1 短期记忆:上下文与压缩
智能体的”短期记忆”就是它的上下文窗口:系统提示、问题、以及做过的每一步(思考、动作、观察)。它会一直增长,带来两个问题:成本随步数平方增长;迟早超出窗口。
第 5 组(每步新增思考 100 + 动作 50 + 观察 token):
| 观察长度 | 步数 | 不压缩:累计输入 / 最终上下文 | 压缩(上限 16k,摘要 800):累计输入 / 压缩次数 |
|---|---|---|---|
| 300 | 50 | 651,250 / 24,500 | 422,850 / 1 |
| 1500 | 50 | 2,121,250 / 84,500 | 533,750 / 5 |
压缩:上下文超过上限时,把旧的步骤压成摘要,只保留最近几步的原文。累计成本从平方变回接近线性。
代价:摘要会丢信息。参考代码的场景 5 把记忆上限设得很小:压缩之后,智能体不记得自己已经读过 Week 1 Day 4 §1.4 了,于是又读了一遍,再读一遍,最后被重复检测拦下(repeated_action)。丢掉的恰好是后面要用的,智能体就会重复劳动。
比”事后压缩”更好的是”事前少放”:工具只返回需要的字段(Day 3 §2.3);大的中间结果写进文件或外部存储,上下文里只留一个引用和一句说明;已经用完的信息(比如确认过的中间步骤)主动清掉。这类”决定上下文里放什么”的工作,现在常被称为上下文工程(context engineering),它对智能体表现的影响往往比换提示措辞大得多。
5.2 长期记忆
跨会话的记忆(“用户上周说过他用的是 A100”)需要存到上下文之外:
- 向量记忆:把过去的对话、观察、总结切块嵌入,需要时检索回来。这就是 Day 1–2 的 RAG,只是语料换成了智能体自己的经历。生成式智能体(Park et al. 2023)按”相关性、新近性、重要性”三者的组合来检索记忆。
- 结构化记忆:关键事实写成键值或表格(用户偏好、项目配置),读写都精确。
- 分层管理:MemGPT(Packer et al. 2023)借用操作系统的思路,把上下文当”内存”、外部存储当”磁盘”,让模型自己调用函数在两者之间换入换出。
长期记忆的难点在写什么(全存会被噪声淹没,存得太少又会丢)和什么时候读(每步都检索太贵,不检索又会忘)。
§6 多智能体
6.1 为什么拆成多个
Lecture 7 的例子:一栋智能住宅里可以有恒温器智能体、人员感知智能体、空气质量智能体、能源管理智能体,各管一摊。把一个大智能体拆成几个专门的智能体,好处有三:
- 专门化:每个智能体的提示、工具、权限都更少更聚焦(§4.1、Day 3 §4.1:工具越少选得越准)。
- 并行:互不依赖的子任务同时做,延迟短得多。
- 上下文隔离:每个子智能体在干净的上下文里工作,只把摘要交回;主智能体的上下文不会被子任务的细节撑爆(§5.1)。
代价是 token。第 6 组:任务可以拆成 个互不依赖、各需 步的子任务。单智能体在一个上下文里顺序做完;多智能体由一个编排者写 份说明, 个子智能体各自做完交回摘要:
| 子任务数 × 每个步数 | 单智能体:累计输入 / 顺序步数 | 多智能体:累计输入 / 关键路径步数 |
|---|---|---|
| 2 × 3 | 27,000 / 6 | 31,400 / 5 |
| 4 × 5 | 174,000 / 20 | 99,600 / 7 |
| 8 × 10 | 2,136,000 / 80 | 492,800 / 12 |
子任务少而短时,多智能体更贵(每个子智能体都要重付系统提示和说明);子任务多而长时,单智能体的上下文越滚越大,多智能体反而更省,并行还让延迟短得多。
一个真实系统的数字:Anthropic 的多智能体研究系统报告,智能体用的 token 大约是普通对话的 4 倍,多智能体约 15 倍;在他们内部的研究类评测上,多智能体(Opus 4 当主智能体、Sonnet 4 当子智能体)比单智能体的 Opus 4 好 90.2%;而在 BrowseComp 上,token 用量本身就解释了 80% 的表现差异。他们也明确指出:需要所有智能体共享同一份上下文、或者子任务之间依赖很多的领域(比如大多数编码任务),目前不适合多智能体。多智能体本质上是”用更多的 token 换更好的结果”,只有任务价值足够高、而且真正能并行拆分时才划算。
6.2 A2A:智能体之间怎么通信
几个智能体要协作,就需要一种通信方式。A2A(Agent2Agent,Google 2025 年提出)为此定义了标准(Lecture 7 展示的核心概念):
- AgentCard(名片):智能体的名字、地址、版本,以及它有哪些技能。别的智能体靠它发现”谁能做什么、怎么找到它”。
- AgentSkill(技能):每项技能有 id、描述和例子(“保持舒适温度:比如’我工作时切换到专注模式’”)。
- AgentExecutor(执行器):真正处理请求的代码,至少实现”执行”和”取消”。
参考代码的 --agent-card 打印了一个讲义助教智能体的名片(两项技能:讲义问答、显存估算)。
MCP 与 A2A 的分工:MCP 连接应用和工具(工具是被动的,调用了才执行);A2A 连接智能体和智能体(对方是自主的,会自己规划、可能要花很久、可能中途需要更多信息)。一个智能体可以同时是 MCP 的客户端(用工具)和 A2A 的一端(和别的智能体协作)。
6.3 其他组织方式
- 计划–执行(plan-and-execute):先让一个 LLM 写出完整的计划(步骤列表),再逐步执行,必要时重新规划。和 ReAct”走一步看一步”相比,它更省(不用每步都重新思考全局)、更可控(计划可以先给人审),但对意外的适应性差。
- 反思(Reflexion,Shinn et al. 2023):任务失败后,让模型写一段”哪里做错了、下次怎么改”的反思,存进记忆,下次尝试时读进来。不更新权重,只用语言形式的”经验”改进,适合可以多次尝试的任务。
- 评审–改写(evaluator-optimizer):一个 LLM 生成,另一个按标准评审,给出修改意见,循环直到通过。它把 §3.2 的”检查器”做成了一个 LLM。Day 6 的 LLM 评委是它的评审那一半。
- 代码即行动:让模型直接写一段 Python 来调用工具(可以用循环、变量、条件),而不是一次输出一个 JSON 调用(CodeAct,Wang et al. 2024)。一段代码能完成好几次工具调用,步数更少;代价是必须在沙箱里执行(Day 3 §6.2)。
§7 真实的难点
学习计划列出的四个难点,每一个在今天都有对应的数字或机制:
| 难点 | 本质 | 今天的对策 |
|---|---|---|
| 错误累积 | 的指数衰减(§3.1) | 减少步数、提高检查器召回(§3.2) |
| 无限循环 | 没有进展的动作重复(§4.1) | 重复检测、最大步数(从数据里定) |
| 成本失控 | 上下文平方增长、卡住的运行(§4、§5) | token 预算、压缩、事前少放、并行 |
| 工具调用幻觉 | 编造不存在的工具、编造观察(§2.3) | 明确的报错、在”观察:“处截断、停止词 |
Lecture 7 的结语:
- 幻觉是个大问题;推理能力是瓶颈(微调有帮助但难做,新能力很受欢迎);评测很难(这是 Day 5–6 的主题)。
- 先从简单的做起,逐步迭代、扩大规模;先用能力强的模型把事情做成,之后再考虑换小模型省钱。
- 透明与可观测性帮助建立用户的信任,也让调试成为可能。参考代码把每一步写成一行 JSON(
--ask会写到logs/w4d4_trace.jsonl):问题、每一步的思考、动作、观察、是否出错、停止原因、token 与耗时。一个智能体出了错,第一件事是读它的轨迹,而不是改提示。Day 6 §3 会用 Lecture 8 的失败模式分类来系统地读轨迹。
§A 智能体:API 用法与边界
A.1 停止词与格式
- 停止词:文本格式的 ReAct 一定要设
stop=["观察:", "\nObservation:"](OpenAI 兼容接口的stop参数;transformers 用stop_strings或自定义的StoppingCriteria)。否则模型会自己编观察(§2.3)。参考代码在解析时截断,两道保险更稳。 - 温度:智能体的决策通常用低温度(0–0.3),让同样的状态产生同样的动作,便于复现和调试。需要多样性的地方(头脑风暴、多条路径搜索)再调高。
- 原生函数调用时的思考:有的 API 在返回
tool_calls时content为空。要保留”思考”,可以在系统提示里要求模型先写一句理由再调用,或使用支持推理内容的接口。
A.2 框架
LangGraph、LlamaIndex、smolagents、OpenAI Agents SDK、Claude Agent SDK 等都提供了智能体的骨架(循环、工具注册、记忆、追踪)。用框架之前,先确认你知道它在每一步给模型发了什么:
- 框架的默认系统提示可能很长,而且每一步都重发(Day 3 §2.3 的成本)。
- 默认的最大步数、重试策略、错误处理可能和你想要的不一样(§4.2)。
- 出了问题要能看到原始的消息序列。能打印出每一次发给模型的完整请求,是选框架的一个硬指标。
参考代码只有几百行,没有依赖任何框架,目的就是让每一步都可见。
A.3 追踪与日志
- 每一步记什么:输入(或其哈希)、模型输出原文、解析结果、工具调用与参数、工具返回(截断后)、耗时、token、是否出错。
- 一次运行记什么:问题、停止原因、总步数、总成本、最终答案。
- 不要记什么:密钥、个人敏感信息。工具的返回里可能有,写日志前要过滤。
- 成熟的做法是用 OpenTelemetry 之类的追踪格式,接入专门的可观测性工具;参考代码的 JSONL 是最小版本,但原理一样。
A.4 评测智能体时的坑
- 单次运行的结果不可信:同一个任务跑几次,看 pass^k(§3.3)。
- 按停止原因分组看失败:
max_steps和too_many_errors是完全不同的问题(§2.4)。 - “引用对了”不等于”答对了”:参考代码的
--eval只算了一个便宜的代理指标(回答里引用的小节是否命中黄金答案的小节),回答的对错要交给 Day 6 的评委,最好再人工抽查。
§B 实操
- 跑通
w4day4_checks.py,6 组结论都看懂。第 2 组把 改成 0.9,看要多大的 才能让 20 步任务回到 80%;第 4 组改卡住的比例,看最优的 怎么变。 - 推导(45min,纸笔):(a) 检查与重试的递推与闭式解;(b) pass^k 无偏估计为什么无偏,朴素估计为什么偏高;(c) 第 6 组单智能体和多智能体的 token 公式。
- 读懂智能体的每一条路径(1h):
python code/w4d4_react_agent.py --demo,逐个场景对照 §2–§5。然后自己写一个新的模拟策略:让它在两个动作之间来回切换(A、B、A、B……),看现在的重复检测能不能拦住,不能的话改代码。 - 真模型(1.5h,需要一个 LLM):用 Ollama 或 vLLM 起一个 7B 左右的模型,跑
--ask(文本模式和--mode native各一次),读logs/w4d4_trace.jsonl。再跑--eval --n 20,统计停止原因的分布、平均步数、引用命中率。小模型最常见的失败是什么? - 把
--eval的答案留好:logs/w4d4_answers_text.jsonl是 Day 6 用 LLM 评委打分的输入。 - 读论文:ReAct(Yao et al. 2022)的第 2–3 节;Anthropic 的 “Building effective agents”;挑读 τ-bench(Yao et al. 2024)关于 pass^k 的部分。
参考实现:code/w4d4_react_agent.py。
§C 自测
动机与直觉层
- 智能体和”调用一次工具”的区别是什么?什么时候应该用工作流而不是智能体?
- ReAct 相比”只推理”和”只行动”各解决了什么问题?
- 为什么编程智能体比很多别的智能体更成功?(用 §3.2 的语言回答)
- 多智能体什么时候划算,什么时候不划算?
- MCP 和 A2A 各连接的是什么?
推导与计算层
- 每步 95% 正确,20 步的成功率是多少?要 90% 的成功率,每步要多准?
- 推出”检查器召回 、最多重试 次”时每步的成功率,并说明为什么 比 更重要。
- 定义 pass@k 与 pass^k,举一个 pass@1 相同但 pass^8 相差 100 倍的例子。写出 pass^k 的无偏估计。
- 智能体的上下文每步增加 1000 token,初始 3000 token,50 步的累计输入是多少?压缩能把它降到什么量级?
工程层
- 文本格式的 ReAct 为什么会”自己编观察”?有哪两道防线?
- 设计一个智能体的五条护栏,说明每条的阈值怎么定。
- 记忆压缩会带来什么问题?除了压缩,还有哪些办法控制上下文的增长?
- 你的智能体在一批任务上 30% 停在
max_steps,你会怎么排查?
出关标准
- 能说清智能体的定义、四个部件,以及”先工作流、后智能体”的理由
- 能写出 ReAct 的循环,说出文本格式解析的坑和对策
- 能推出检查与重试的成功率,解释为什么提高检查器召回比多重试更重要
- 能区分 pass@k 与 pass^k,写出 pass^k 的无偏估计
- 能设计护栏(阈值从数据里定)和记忆策略,说出压缩的代价
- 能讨论多智能体的收益与代价,说出 A2A 的核心概念
- 跑通
--demo的五个场景;有模型的话跑通--eval并留下答案文件