《深入理解 AI Agent:设计原理与工程实践》第 2 章「上下文工程」精读笔记
原书信息:李博杰著,第 2 章,p35–p77。全书 311 页,本章 43 页,是篇幅最大、作者自称”全书最关键”的一章。原书开源发布,配套代码仓库:
--
阅读定位:本章是
Agent = LLM + 上下文 + 工具公式里”上下文”这一支柱的完整展开。作者在引言里给的建议是——时间有限就只读第 1 章和第 2 章。
关于本文:逐页精读整理,所有引文逐字来自原文,页码为原书 PDF 页码。文中提到的”图 2-6""图 2-16”等编号指原书插图,本文未转载,需要看图请对照原书。文末有批判性分析,那部分是我的判断,不代表原作者观点。
一、一句话抓住这一章
上下文窗口是一台只有一半的检索引擎。(p64)
这句话是整章的枢纽。模型”检索”那一半极强——你问什么,注意力就能从上万 token 里把原始记录捞出来;但它缺”提炼”那一半——上下文里的东西从来不会被自动数一遍、建个索引、或就地总结成一条结论(p65)。
于是整章所有技术,本质上都在做同一件事:替模型把它自己算不出来(或算起来很贵)的那部分先算好,再放进上下文。 章末小结原样重复了这条纲领(p72 与 p76 出现两次):
不要让模型被动地在海量信息中检索,而要主动为模型提供经过提炼的结构化知识。
二、全章骨架:一张结构图 + 一条逻辑链
2.1 结构图:所有技术都挂在”静态前缀 + 轨迹”上
作者在 2.2.5(p45)给出了本章唯一真正的地基图:
┌─────────────────────────────────────────┐
│ 静态前缀(每轮不变,KV Cache 缓存) │ ← 2.3 KV Cache 约束
│ ├─ System Prompt │ ← 2.4 提示工程
│ └─ Tool Definitions │ ← 2.4.6 工具定义设计
├─────────────────────────────────────────┤
│ 轨迹(随交互增长,可压缩) │
│ user / assistant / tool / user / ... │ ← 2.7 上下文压缩
│ ├─ Skill 元数据(末尾注入,emit-once) │ ← 2.5 Agent Skills
│ └─ <agent_status>(末尾注入,每轮更新) │ ← 2.6 Agent 状态栏
└─────────────────────────────────────────┘
↑ 模型从这里开始生成记住一句话就能推导出全章大半结论:前面不能动,后面可以压。(p45 原话:“理解了这个结构,就能理解为什么’前面不能动、后面可以压缩’。“)
2.2 逻辑链:七节之间的因果关系
2.1 为什么上下文决定能力上限(动机)
↓
2.2 上下文在 API 层长什么样(地基:messages 列表 + 静态前缀/轨迹)
↓
2.3 这个结构在推理层的硬约束(KV Cache:前缀改一字节全废)
↓
├─→ 2.4 静态前缀里放什么、怎么写(提示工程 + 工具定义 + 提示注入)
│ ↓ 提示词膨胀 → 注意力稀释
│ 2.5 静态放不下了怎么办(Skills 渐进式披露:按需加载)
↓
2.6 轨迹里模型看不见的东西怎么补(状态栏:把隐式状态显式化)
↓
2.7 轨迹太长了怎么办(压缩;以及更彻底的方案:隔离)注意 2.5 → 2.6 的衔接是作者刻意设计的:Skills 的”方式三”(元数据以 user 角色注入上下文末尾)就是状态栏机制的一个特例(p61、p64)。两节共用同一条注入通道。
三、逐节精讲
2.1 上下文:决定 Agent 能力上限的关键(p35–36)
核心主张:「模型本身的智力只是基础,上下文的质量才是 Agent 能力的真正上限。」(p36)
作者给了一个 Coding Agent 的三分法,说明”最低信息需求”是什么:
| 类别 | 内容 | 缺失后果 |
|---|---|---|
| 实时代码上下文 | 目录结构、模块职责、数据结构、代码规范 | 语法正确但风格格格不入,甚至引入架构冲突 |
| 流程规范 | Git 分支策略、提交规范、CR 流程、CI/CD | 可能直接往主分支提交未测试代码 |
| 环境信息 | 开发环境配置、测试库地址、staging 部署、密钥管理 | 本地跑通,测试环境立刻崩 |
这一节最值得注意的是一个”越界”的论断——作者把上下文工程从技术问题拔高到组织问题:
上下文工程首先是一个技术问题,但更根本的是一个组织问题。……如果团队本身就是一个信息黑洞,再好的 AI Agent 也无计可施。(p36)
所以构建 AI 原生团队,首先是一场文档化运动,而不只是部署新工具。(p36)
他举 Linux 内核为例:三十多年全球协作,靠的是”高度透明、文档驱动”,这种工作方式天然创造了对 AI 友好的环境。并引 OpenAI 研究员翁家翌:「人和模型一样,最重要的是 Context」「AI 短时间内无法取代人的最大原因也是 context——因为 AI 跟人并不在同一个环境里面」。
评注:这是全章唯一一处非技术论断,但它的隐含推论很强——远程友好度 ≈ AI 友好度。可以当成一个可操作的组织诊断指标:如果你们团队的关键决策只存在于会议口头和私聊里,那么无论买什么 Agent 产品,天花板都在那里。
2.2 API 的上下文结构(p36–46)——本章地基,别跳过
这一节看似”讲 API 基础”,实际上后面每一节都在回指它。四个必须内化的点:
① 四种角色(p36)
| role | 谁写的 | 作用 |
|---|---|---|
system | 开发者 | 身份、规则、约束。模型视为最高优先级,通常只有一条、放最前 |
user | 终端用户 | 待响应的请求 |
assistant | 模型 | 之前的回复,含工具调用请求。原样放回让模型”看到自己的决策” |
tool | Agent 框架 | 工具执行结果,靠 tool_call_id 关联 |
tools 字段是请求的独立字段而非消息——这个细节决定了它随请求一起进静态前缀。
② API 是无状态的(p41)
每次调用都是无状态的——模型不会”记住”上一次的对话,Agent 框架必须每次都把完整历史送回去。
③ 决策与执行分离(p40)
模型负责决策(调用什么工具、传什么参数),Agent 框架负责执行(实际调用 API、运行代码)。
④ Agent 核心循环就是一个 while(p43)
while True:
response = client.chat.completions.create(model=..., messages=messages, tools=tools)
msg = response.choices[0].message
messages.append(msg)
if not msg.tool_calls: # 没有工具调用 → 完成
print(msg.content); break
for tc in msg.tool_calls: # 执行工具,结果追加回 messages
messages.append({"role": "tool", "tool_call_id": tc.id,
"content": execute_tool(tc.function.name, tc.function.arguments)})作者在代码注释里埋了一条生产提醒:这里必须加 max_iterations 上限,因为 Agent 会陷入无限重复同一工具调用(后文 2.3、2.6 会解释为什么)。
一句话总结这一节:「Agent 框架的核心工作就是管理这个 messages 列表……本章后续所有的上下文工程技术,本质上都是在优化这个列表的内容和结构。」(p44)
实验 2-1(local_llm_serving)的结论很有意思:0.6B 的 Qwen3 在合理提示词下也能可靠完成工具调用,M2 芯片上 >100 token/s。作者的推论是「端侧 Agent 的时代比大多数人预期的更近」。
2.3 KV Cache 友好的上下文设计(p46–54)——全章技术密度最高
三条铁律(p47,作者说跳过原理也必须记住)
- 系统提示词和工具定义一旦确定就不要改。 任何改动,哪怕多一个空格,都会导致缓存全部失效。
- 动态信息永远追加到末尾——时间戳、用户状态作为新消息追加到对话末尾,而不是修改已有的系统提示词。
- 使用标准 API 格式,不要自行拼接消息。
第 3 条作者做了一个重要且容易被忽略的澄清:自行拼接 "USER: ... ASSISTANT: ..." 的根本问题不是缓存——“缓存只认 token 字节序列,只要拼出的前缀字节级稳定,照样能命中”——真正的问题是偏离了模型训练时见过的格式,削弱多步思考能力(p47、p52)。很多二手总结把这条讲错了。
开场那个事故(p46)
日均 10 万次对话的客服 Agent,工程师在系统提示词加了一行 Current time: {{now}}:
- TTFT:0.5 秒 → 3–5 秒
- 月度推理账单:几乎翻倍
代码没问题、模型没换。这个例子的价值在于展示”无形成本”——一行看似无害的代码,让整条推理链路慢一个量级。
原理(选读,但值得懂)
注意力机制三向量(表 2-1,p47):Query = 当前词的”搜索请求”,Key = 每个词的”标签”,Value = 每个词的”内容”。
实验 2-2(注意力可视化)揭示的四个模式(p49):
- 注意力储存池(Attention Sink):序列第一个 token 常吸收超过总注意力 70% 的权重。原因是 softmax 强制所有权重加起来 = 100%,模型无法表达”不关注任何东西”,于是把剩余权重倾倒到开头这个固定位置——「就像一个公共的回收站」。这是系统性现象,不是缺陷。
- 思考的三角形模式(
<think>内频繁回看之前思考与工具定义) - 输出的三角形模式
- 位置偏好(Position Bias):开头和结尾回忆精度高,中间容易被忽略 → 关键信息放开头或结尾
为什么改前缀会全废(p51–52):大模型是数十到上百层 Transformer 串联,每层独立生成自己的 KV 缓存,且层间是流水线关系。改第 1 个 token → 第 1 层输出变 → 第 2 层输入变 → 逐层传导 → 所有层的缓存都必须重算。
Chat Template(2.3.1)是被低估的一节。它是”信封格式”——把结构化 JSON 翻译成 <|im_start|>system ... <|im_end|> 这样的线性 token 流。两个实用价值:
- 解释了为什么必须用标准格式:工具结果若被塞进
user消息,Chat Template 会误判”用户换了话题”,清理掉之前的思维链——「相当于模型正算到一半,草稿纸被人收走了」(p50)。 - 注意各家策略差异很大:DeepSeek 剥离全部历史思考;Claude 要求客户端在工具循环中把带签名的 thinking block 原样回传,新用户轮次后服务端才忽略历史 thinking。用前查模板文档。
实验 2-3:五种有害的上下文管理模式(p52)
| 反模式 | 危害 |
|---|---|
| 动态系统提示词(塞时间戳) | 缓存 100% 失效 |
| 动态用户配置(余额、剩余调用数写进前缀) | 同上 |
| 工具定义动态排序 | 实验表明:固定顺序对工具选择能力几乎无影响,但性能提升显著 |
| 滑动窗口对话历史 | ①破坏前缀一致性 ②丢失关键工具结果。实验中「使用滑动窗口的 Agent 经常陷入循环,反复执行相同的工具调用」 |
文本格式化(自拼 USER:/ASSISTANT:) | 不是缓存问题,是模型能力问题 |
KV Cache vs Prompt Cache(2.3.3,p52)
| KV Cache | Prompt Cache | |
|---|---|---|
| 层级 | 模型内部 / 推理引擎 | API 服务层 |
| 作用 | 加速单次请求内的 token 生成 | 减少跨请求的重复计算成本 |
| 经济影响 | 间接 | 直接影响 API 计费 |
计费细节差异不小:Anthropic / DeepSeek 缓存读取约为首次计算的 1/10,OpenAI 约五折;Anthropic 需显式设 cache_control 断点(并非自动命中)、缓存写入有约 1.25 倍加价、最小 1024 token、TTL 默认约 5 分钟;OpenAI 是自动前缀缓存。
2.3.4 缓存作为架构约束——本章最有分量的一节
这一节是全章我认为最值得反复读的部分。作者用 Claude Code 的三个设计决策,论证了一个远超”性能优化”范畴的结论:
在设计 Agent 架构时,缓存经济性不是事后优化,而是前置约束。(p53)
提示词的排列顺序首先由缓存的经济性决定,其次才是语义逻辑。(p53)
三个具体表现:
- 提示词结构由缓存边界决定:系统提示词被一个缓存边界一分为二,之前可跨用户跨会话全局缓存,之后放用户/会话特定信息。每个运行时二值条件若放在边界之前,缓存键变体数翻倍——3 个条件(macOS/Linux × 普通/调试 × 中/英)就是 2³ = 8 种缓存键。提示词片段在类型层面被区分为”可缓存”和”会破坏缓存”,后者命名带显式警告标记。
- 子 Agent 必须与父 Agent 字节级对齐:提示词、工具定义、模型配置、消息前缀、思考配置逐字节匹配,才能命中同一份 Prompt Cache。这个约束从缓存层向上传导,影响了 Agent 的生成方式和参数传递机制。
- 工具结果的替换字符串首次出现即冻结:即使会话重启也用完全相同的替换串,保证恢复后的字节流与缓存一致。
评注:这是全章从”技巧”跃迁到”架构”的地方。它说的其实是一个反直觉的工程秩序——在有 Prompt Cache 的系统里,“字节稳定性”是一个和”正确性""可读性”并列的一等公民约束。第 3 点尤其极端:为了缓存一致性,宁可持久化一个可能已经过时的字符串。这在传统软件工程里几乎是坏味道,但在这里是刚需。
2.3.5 铁律其实可以松动(深水区选读,p53–54)
作者自己的研究(arXiv
.17107)给了一个反直觉观察:prefill 阶段模型其实在”做笔记”。读到”用户所在城市:北京”时,它并不是把字段原样缓存,而是把”这意味着什么”的结论写进了下游每一层的 KV 状态。测量发现,字段自身那几个 token 的 KV 对最终决策的贡献往往不到 1%。由此打开两种操作:
- 编辑(Editing):改一个字段后,只要模型有显式 CoT,改动能顺着已缓存的思考传播,用约 1% 算力得到与整段重算一致的结果。(边界条件:没有 CoT 时孤立改字段会被忽略,因为结论早已烘焙进下游状态却没有思考路径去更新它。)
- 组合(Composition):用 RoPE 重定位把预算好的”技能”缓存块直接拼进另一段上下文,
O(L²)重算 →O(L)拼接。
vLLM 实测:p90 TTFT 最高降几十到几百倍,前缀命中率约 98.5%,跨 12 个模型 logit 余弦 0.90–0.999。
作者自己划了界:这仍属研究阶段,前面三条实践结论在当前生产系统中依然是默认原则。
2.4 提示工程:优化系统提示词(p54–59)
总的检验标准(p54,我认为是本节最实用的一句):
大语言模型是一位聪明的新员工,能力出众,但对你们的具体工作流程和内部约定一无所知。如果一个聪明的新员工读完你的系统提示词还不知道该怎么做,Agent 也一样不知道。
五个维度:
| 小节 | 维度 | 关键结论 |
|---|---|---|
| 2.4.1 | 语气与风格(“人格”) | 大写 NEVER do X 比 Please avoid X 更引起注意,但过度使用会稀释,保留给真正关键的约束 |
| 2.4.2 | 结构化(“格式”) | XML 标签名自带语义(<working_directory> 优于”当前目录:”);XML 负责机器可解析的精确语义,Markdown 负责人机共读的组织逻辑 |
| 2.4.3 | 流程 vs 规则(“组织方式”) | 流程驱动(SOP)优于规则堆砌。模型能随时知道”我在哪一步”,而不是遍历所有规则找匹配项 |
| 2.4.4 | 业务规则细化(“内容”) | 这不是技术问题,是产品设计问题 |
| 2.4.5 | Few-shot 示例 | 何时给、给几个、放哪里 |
2.4.4 是本节的重头戏(p55–56)
作者用 Pine AI 的计费系统做案例。三种计费模式(按省钱提成 20% / 按服务收 tip / 特别难办的预收款),而模糊规则会导致行为不稳定:
- “帮我退掉上个月买的衣服”——是”帮用户省钱”还是”取回本属于他的钱”?
- “帮我取消 Netflix 订阅”——取消让用户未来不再付费,这算”省钱”吗?
必须细化到可执行:
NEVER use percentage_based_one_time for refunds and service cancellations.
Use fixed_fee instead.以及:成功率按固定流程分步评估,高于 60% 用可退款模式、低于 30% 直接拒绝任务;通话每分钟 180,我维持 30”,把避免未来涨价也算成省钱。
设计哲学(p56):
大语言模型的优势在于遵循复杂指令和从长上下文中提取信息,但不应该在业务规则制定上被赋予过多的自由裁量权。
组织含义(值得单独抄出来):
在优秀的 Agent 公司里,提示词一般由产品经理来设计……工程师的角色是将规则准确地编码到提示词中,确保格式正确、结构清晰,但不应擅自决定业务逻辑。
2.4.5 Few-shot:两个决策点(p56)
- 放哪里:放 system → 成为静态前缀,对所有请求生效;伪造一组 user/assistant 消息放首轮 → 适合按会话类型选不同示例集。
- 对缓存的影响:无论放哪,示例都在靠前区域,一旦确定就应字节级稳定。「如果按请求动态检索’最相关’的示例,等于每次都改写前缀,缓存会持续失效。」→ 生产系统为每类任务准备固定示例集,而不是逐请求挑选。
数量:「两三个精心挑选、覆盖边界情况的示例,通常胜过十个大同小异的示例。」
2.4.6 工具定义:一个重要的时效性更新(p57)
Claude Code 的工具描述包含四类内容:使用边界(NEVER invoke grep or rg as a Bash command)、具体示例(timezone: 'America/New_York')、性能提示(Batch your tool calls together)、工具间协作(Use the Read tool at least once before editing)。
但作者明确指出:“工具定义与系统提示词一起构成静态前缀”只是基础模式。 2026 年以来工具定义本身也在向 Skills 式渐进披露演进,而且已经是 API 层原生能力:
- OpenAI Responses API:
tool_search+defer_loading: true,走tool_search_call → tool_search_output - Anthropic:Tool Search(
tool_referenceblocks);Claude Code 对 MCP 工具默认延迟加载 - Codex CLI:
tool_search(BM25)不是可选特性,而是默认开启的架构
为什么追加到末尾不破坏缓存? 因果注意力决定每个 token 的 KV 只依赖它之前的 token,末尾追加不改变任何已缓存 token 的 K、V。这不是”预编译”,而是**“只增不改”的追加式注入**。
一个极易误解的点(作者专门澄清,p57):“追加到末尾”只发生在工具被发现的那一轮。此后 schema 块固定在轨迹原位置,成为普通历史消息,不是每轮被重新搬到最新末尾(真那样每轮都要重新 prefill,缓存就没意义了)。真正导致重算的只有两种:TTL 过期、修改/移除/重排已加载工具集。
约束:模型必须在训练中见过”工具定义出现在对话中间”这种模式 → 目前只有 GPT-5.4+、Claude 4.5 系列等较新模型支持。
实验 2-4:提示工程消融实验(基于 Tau-Bench,p57–58)
| 维度 | 操作 | 结果 |
|---|---|---|
| 语气与风格 | 商务中立 / Trump 风格 / Casual 风格 | 对任务完成率影响相对有限——模型风格适应能力强 |
| 信息组织 | 保留全部规则内容,但打乱结构、去掉标题层次 | 任务成功率下降 >30%,“先验证身份再处理退款”被拆散后 Agent 会跳过身份验证 |
| 工具描述 | 保留签名和参数,移除描述性文本 | 工具调用错误率 +45% |
这印证了:对人类友好的信息组织方式,对模型同样友好。(p58)
作者自评:“消融实验的结论本身并不意外……更有价值的是方法论本身——当 Agent 表现不佳时,与其全面重写提示词,不如先做消融实验。这比凭感觉猜测要可靠得多。“
2.4.7 提示注入(p58–59)
本质:攻击者通过 Agent 处理的外部内容(网页、邮件、文档),将伪装成系统指令的文本混入上下文。
为什么比聊天机器人危险:聊天机器人最坏输出不当内容,Agent 拥有工具调用能力——可能删文件、发邮件、泄露数据,不可逆。攻击面随能力增长:每一个感知工具都是潜在注入入口(网页不可见元素、PDF 元数据、甚至图片 EXIF)。
上下文层三道防御:
| 手段 | 做法 | 强度 |
|---|---|---|
| 来源标记 | <external_content source="webpage">...</external_content> | 中 |
| 结构化角色 | 严格用 system/user/assistant/tool 角色体系 | 中——这也是”不要自行拼接消息”的又一个理由:把工具结果混入 user 消息,等于亲手抹掉了模型辨别来源的依据 |
| 输入清洗 | 过滤”忽略之前的指令”等模式 | 弱,容易被措辞变体绕过,只能作为辅助 |
作者特别警示:本章介绍的机制本身构成新的注入面。
- Skills:「Skill 的本质是’把外部内容当作指令加载’的制度化形式」——第三方 Skill 以很高执行倾向进入上下文,比网页隐藏文本更直接。→ 安装来源不明的 Skill 前必须审查,如同审查将要执行的代码。
- 状态栏:状态栏信息被模型高度信任(这正是它有效的原因),一旦内容来自可被污染的数据源,这种信任就会被反向利用。
清醒的结论(p59):
上下文层的防御……只是第一道防线,它只能降低攻击成功率,无法做到万无一失。
执行层防御(权限控制、沙盒、独立审查)在第 4、5 章;检索投毒在第 3 章。
2.5 动态提示词与 Agent Skills(p59–63)
动机:提示词膨胀的两个代价(p60)
- 浪费 token:大部分内容与当前任务无关
- 注意力被稀释(后文以”上下文腐化”展开)
渐进式披露的三层结构(p60)
| 层 | 内容 | 体积 | 加载时机 |
|---|---|---|---|
| 一 | SKILL.md 的 YAML frontmatter(name + description) | ~300 tokens | 启动时全部注入 |
| 二 | SKILL.md 正文(核心流程) | ~2K tokens | 模型判断需要时,通过专用 Skill 工具加载 |
| 三 | 子文档(html2pptx.md、reference.md、scripts/*.py) | 不定 | 按需选择性深入 |
比喻:「你不会把公司所有部门的操作手册都堆到新员工桌上,而是先给一份总目录,需要哪本再去取。」
description 的写法:本节最实操的一条(p60)
元数据中的 description 字段是路由决策的关键……写法要像路由条件而非功能介绍。 最直接的写法是”Use when / Don’t use when”加上几条反例。 反例不是可选项,而是 Skill 路由能否准确触发的关键。 描述太宽泛(如 “help with backend”)等于任何后端相关工作都能触发。 真正有效的描述是路由条件——“何时该用我”比”我能做什么”重要得多。
2.5.2 三种注入方式的三难(p61)——本节的技术核心
| 方式 | 做法 | 指令遵循 | KV Cache | 结论 |
|---|---|---|---|---|
| 一 | 追加进 system 消息 | 最强(训练时大量使用该位置) | 每次加载新 Skill 都失效前缀 | ✗ 频繁切换 Skill 时代价巨大 |
| 二 | 通用文件读取工具,内容落在上下文中间 | 打折——模型可能只当成”参考”而非”指令”;Claude 因训练数据充足最可靠,其他模型差异很大 | 完全不影响 | ✗ 模型依赖强 |
| 三 | 元数据注入末尾 + 完整内容按需工具加载(Claude Code 生产实现) | 强——模型对”自己刚刚主动调用的工具的输出”有更强的执行倾向 | 友好 | ✓ |
方式三的两个巧思:
- 元数据以 user 角色的 meta 消息注入末尾,外层用
<system-reminder>包裹。既不改 system(不破前缀),又在末尾(注意力最优)。采用增量发送:每个 skill 只在首次出现时发送,稳态下每轮元数据增量为零。 - 完整内容通过
Skill(skill: "pdf")这样的专用工具加载,结果作为 tool result 落在轨迹中。
作者自己点出的一个诚实的局限(p61):
“末尾”的注意力优势只在注入的当轮成立——增量发送的元数据永久留在轨迹中,随着会话增长它会逐渐滞留到上下文中部,位置优势随之衰减。 这是**“只发一次、节省缓存”与”每轮置底、保住注意力”之间的权衡**,下一节状态栏讨论持久追加式更新时会再次遇到同一个取舍。
KV Cache 演化的实测(图 2-13,p62)
| 轮次 | 内容 | 本轮 cache_creation |
|---|---|---|
| Turn 1 | 首次加载 PPTX skill | ≈ 2.5k tokens |
| Turn 2 | 读取 PDF | ≈ 0.5k tokens |
| Turn 3 | 写出 HTML | ≈ 0.4k tokens |
必须厘清的误解(p63):
“对 KV Cache 友好”并非”零成本”——首次 emit 那几百到几千 token 终归要付一次写入代价(缓存写入还是加价计费的)。 它的准确含义是一次性写入、永久受益。
对比方案(塞进 system prompt)每次更新会让其下游整条 trajectory 进入 cache_creation,量级是数万到数十万 token。
一条容易被忽略的元原则(p61)
选择 Agent 交互模式时应对齐模型厂商的训练方法论。 基础模型公司推行的 Agent 用法,本质上是它们专门训练过的模式,这使得同一生态内的模型天然具有最优表现。
2.6 Agent 状态栏(p63–71)——本章最有原创性的一节
要解决的问题(p64)
系统提示词要求”拨打每个商家不超过 3 次”,但打了 3 次之后,Agent 经常数不清打了几次,又打了第 4 次,甚至陷入循环。
根源:关于”已经打了几次”的知识没有被自动提炼出来,而是以原始通话记录的形式分散在 KV Cache 的向量表示中。模型每次决策都要花额外思考 token 去扫描重新统计——效率极低、错误率高,而且代价随上下文里堆积的内容量 N 一起涨。
理论基础:那句核心论断(p64)
上下文学习更像检索而非推理——模型擅长从已有内容中查找信息,但不擅长主动归纳和总结。
作者做了一个重要限定,避免被误读:「这里说的是模型在单次前向传播中如何消费已经在上下文里的信息,并不否定模型可以通过生成思维链来完成多步思考。」
于是有了那个比喻:上下文窗口是一台只有一半的检索引擎——检索那一半极强(相当于把 RAG 内置进每次前向传播),但没有”提炼层”。
两个作用机制
- 把隐式状态提炼为显式知识(p65):原始轨迹信息高度冗余,状态栏”以极低的额外 token 成本,呈现出原本需要扫描数千个 token 才能获得的信息”。
- 显式地操纵注意力分配(p65):结构化元信息放上下文末尾 → 空间上更接近即将生成的新 token → 获得更高注意力权重。作者称之为**“强制性的注意力引导”**。
大规模证据:上下文蒸馏基准(p65–66)
作者与合作者的专门基准(Distill, Don’t Retrieve):3 类任务(计数、规则归纳、状态跟踪)× 11 个模型(前沿 API 到笔记本可跑的 2B)× 近 2.4 万次评测。
| 对象 | 收益 |
|---|---|
| 弱模型 | 准确率 +40 ~ +54 个百分点;一个 2B 本地小模型追平了不带状态栏的前沿大模型 |
| 强模型 | 思考量 / 延迟 / 花费各降约一个数量级(思考 token 砍掉八九成以上) |
| 最本质的变化 | 不带状态栏时每次查询的思考量随上下文变长而持续增长;带上后基本恒定 |
一个格式细节:状态栏必须写成 衣物: 9 件(合格 7、次品 2) 这样能一眼定位的键值对,而不是散文——论文里散文版效果明显更差,因为模型还得先读一遍解析出来,等于又回到了”扫描”。
三条可以直接照做的经验(p66)——本章最实用的一段
① 状态栏要用代码维护,别拿大模型去维护
一个自然念头是”叫一个 LLM 去读历史帮我总结状态栏”——结果恰恰相反:
- 一个 20 行的正则函数就能达到”标准答案”级准确度
- 让前沿大模型一次性读完整段历史吐出统计,反而在大多数格子上出错,把下游准确率拖得比”根本不用状态栏”还低
原因:“让 LLM 批量统计长历史,等于把’扫描整段上下文’这个原始难题原封不动搬了个家。”
替代方案:能用代码算就用代码算;实在要用 LLM,也要逐条抽取、再由代码汇总,绝不要让它一次性批量统计。
② 想删原始上下文之前,先确认状态栏覆盖了所有会被问到的维度
状态栏是对原始上下文的有损投影。极端测试:状态栏只存”两两组合”计数,却去问”三者交叉”——连 Claude 都从 100% 掉到 7.6%。
一条看着很像样、实则答非所问的状态栏,会变成一个理直气壮把模型带偏的”假权威”。
实践建议:把”新增一种问法”当成一次数据库改表结构来对待——要么先给状态栏加字段,要么这次就别删原文。
另有一类任务(如大段散文里的多跳推理)本就没有干净的结构化摘要,别指望状态栏提高准确率,它顶多帮你省点 token。
③ 把状态栏的准确率当成一线生产指标
模型几乎无条件地相信状态栏——你写”打了 3 次”,它就当真是 3 次,既不会偷偷去核对,也不会自己重算。
容错空间:数错 10% 以内收益还能保住大半,越过这条线,带着错状态栏可能比不带还糟。
深水区:状态栏为什么”根子上”有效(p66–67)
这是本章我认为思想密度最高的一段。作者提出:让模型变强通常有两条路——想得更久(更长 CoT)和试得更多(多采样挑最好)。但两条路有共同天花板:
它们都只在模型”自己的脑子里”打转,用的都是同一套固定权重和同一段固定上下文,因此变不出上下文里原本没有的新信息,只能把已有信息重新排列组合。
真正突破天花板的是第三条路:交互(Interaction Scaling,arXiv
.11598)——让外部”仪器”去观察模型的产出在真实世界里的表现,再写回上下文。关键在于,这个观察是模型光靠想想不出来的:代码到底有没有通过测试、网页渲染出来那个按钮有没有跑出屏幕、这一步操作后系统状态变成了什么样。
所以状态栏最有价值的,往往不是模型本可以自己数出来的东西(那只是替它省力气),而是它根本无从推断的外部事实。
状态栏把”闭卷考试”变成了”随时能查一眼真实世界”。
配套的一条附带发现,很尖锐:
衡量改进的”尺子”本身也必须扎根于真实观测:若用一个只瞄一眼截图的视觉模型来打分,它连自己刚修好的缺陷都测不出来,整个循环就会悄无声息地空转。
这与业界”循环的瓶颈在验证器,而不在模型”是同一件事。
2.6.2 状态栏的四类内容(p67)
| 类型 | 内容 | 变化频率 | 缓存友好性 |
|---|---|---|---|
| 任务规划 | TODO 列表 | 动态 | 需追加更新 |
| 事件侧信道信息 | 精确时间、地理位置、距上次回复的间隔 | 一经添加不再变 | 友好 |
| 环境当前状态 | 系统时间、工作目录、“该工具已被重复调用 N 次” | 动态 | 需追加更新 |
| 可用能力清单 | 已安装 Skill 元数据 | 最低(仅安装/卸载时) | 友好 |
2.6.3 位置:一个重要的协议层澄清(p68)
状态栏在 API 层面是一条 user 角色的消息插入到上下文末尾,而不是修改 system。
这里的 user 角色只是 API 协议层面的技术选择,并不等同于第一章定义的”来自终端用户的输入”。Harness 是在借用 user 角色这个消息槽位,向模型注入由 Agent 框架自动生成的系统状态信息。
用 <agent_status> 标签包裹以便模型识别其特殊性质。
2.6.4 更新方式的两种实现(p69)
| 实现一:每轮替换 | 实现二:持久追加 | |
|---|---|---|
| 做法 | 移除上一轮状态消息,末尾追加最新 | 一旦注入永久留在轨迹,每轮末尾追加新的 |
| 优点 | 上下文只有一份状态,永远最新、无歧义 | 对缓存完全友好(只追加不修改) |
| 代价 | 移除旧状态使其后所有缓存失效(与”动态时间戳”是同一失效机制,只是范围限于最近几轮) | 陈旧状态累积:占 token,且要求模型自己关注”最新一条” |
| 生产实例 | — | Claude Code 的 <system-reminder> |
经验法则:状态更新频繁 + 轨迹很长 → 实现二;轨迹较短 or 单条状态消息很大(完整 TODO + 环境快照)→ 实现一。
实验 2-8:五种状态栏技术(p69–70)
| 技术 | 做法 | 实测效果 |
|---|---|---|
| 时间戳跟踪 | [2025-09-14 10:30:45] 前缀加到 user 消息和 tool 响应(注意:不是加在系统提示词里,否则破坏 KV Cache) | 理解时序关系,支持调试审计 |
| 工具调用计数器 | 全局字典 + Tool call #3 for 'read_file' | 触发模式识别:一次失败查路径、两次列目录、三次主动放弃找替代。深层价值是隐式成本感知 |
| TODO 列表管理 | 借鉴 Manus”通过复述操纵注意力”;rewrite_todo_list / update_todo_status | 启用 15 次迭代完成;禁用需 21 次且常遗漏子任务 |
| 详细错误信息 | 四层:错误类型描述 + 完整参数 JSON + 调用栈 + 针对性修复建议 | 找到替代方案成功率 60% → 95%,从盲目重试转为分析性问题解决 |
| 系统状态感知 | 时间、工作目录(执行 cd 后自动更新)、OS 类型、Shell、Python 版本 | 支持平台相关决策(Linux 用 apt、macOS 用 brew) |
涌现效应:单独用效果有限,组合起来超出预期。时间戳 + 计数器 → 理解操作频率与时间分布;TODO + 系统状态 → 按环境调整策略;错误信息 + 计数器 → 多次失败后不仅改变策略,还能理解失败原因。
2.6.5 从读数到策略:时间感(p70–71)——本节最反直觉的发现
作者提出 Agent 缺失的一项能力:时间感(time sense),拆成三轴:
| 轴 | 名称 | 含义 | 失败模式 |
|---|---|---|---|
| 预算轴 | 紧迫度 urgency | 把投入的力气匹配到时钟上。双向的——低紧迫度不等于”少干点”,而是”别急着停,接着做” | 三分钟和三十分钟交出的东西没区别 |
| 终点轴 | 坚持度 persistence | 分清真墙和假墙,也知道活儿干完了没有 | 对着已 410 Gone 的接口重试五次;或搜了两次就断言”查无此信息” |
| 监控轴 | 警觉度 vigilance | 把时间异常升级成值得追查的假设 | 本该 500ms 却跑了 5 秒;1 毫秒就”成功”返回但 body 为空 |
关键实验(4 种条件对照):
| 条件 | 通过率 |
|---|---|
| 什么都不给 | 一成出头 |
| 只给原始时间戳 | 与什么都不给几乎没区别(差 2–3 个百分点) |
| 时间戳 + “这些读数该怎么用”的操作手册 | +19 ~ +49 个百分点,拉到四五成 |
光把读数摆到模型面前,并不足以改变它的行为。……它缺的不是读数,而是拿这个读数该怎么办的策略。
这补上了前面留的缺口:工具计数器之所以只靠”第 3 次(3/3)“一行读数就能纠偏,是因为决策规则太显然——“到顶了就停”;而”力气该花多少""这堵墙要不要绕”这类判断规则并不显然。
一条真正管用的”节奏状态栏”,必须把读数(用了多久、这工具慢不慢、这墙撞了几次)和一小段操作策略(时间紧就交付、慢调用要诊断、真墙就绕开)成对地给出去,缺一不可。
四个厂商家族六个模型(Claude / Gemini / GPT / Qwen)不加手册时通过率无一例外趴在一成出头的地板上——说明这是当前后训练普遍漏掉的一项控制,而不是某个模型不够聪明。
(伏笔:第 7 章会看到一个对照——同样教这套节奏感,稀疏的结果奖励怎么都学不会,换成逐 token 的稠密信号才终于学会。)
2.6.6 设计哲学(p71)
- 所有元信息人类可读,开发者随时可检查 Agent 拿到了什么、做了什么决定
- 对模型无侵入性——不需要微调,任何模型上都能起效,可以一项项叠加尝试
2.7 上下文压缩策略(p71–76)
两个截然不同的动机(p71)
- 长度约束和成本约束——最直观:窗口有限、工具结果动辄数万字符、token 越多成本越高延迟越大
- 提升思考质量——「总结后的知识比原始形式更利于模型使用」。即使窗口足够大,把所有原始信息堆在上下文里也不是最优选择。
第二个动机是本节的灵魂:状态栏是把算好的结论加进上下文,压缩是把臃肿的原始记录换成算好的结论——「两者是同一枚硬币的两面,都在给那台’只有一半’的检索引擎补上缺失的’提炼’」(p72)。区别只在于:状态栏往往由代码每一步确定性维护,压缩则更多是用一次 LLM 调用把大段原文蒸馏掉。
100 只猫的例子(p72)
上下文里有 100 个笼子的巡查记录(90 黑猫、10 白猫),问”各有多少只”:
| 条件 | 结果 |
|---|---|
| 不启用思维链 | 很难直接给出正确答案——注意力擅长查找(“笼子 37 里是什么猫”),不擅长统计归纳 |
| 启用思维链 | 能数对,但每次被问都要重新从头数一遍,产生大量思考 token |
| 提前写入”当前统计:黑猫 90 只,白猫 10 只” | 立即检索到结论,无需重新思考 |
这就是压缩的第二个价值:把需要思考才能得到的结论变成可以直接检索的知识。
上下文腐化 Context Rot(p72)
溢出是”装不下了”,腐化是”装得下但找不到了”——后者更隐蔽,因为 Agent 表面上还在正常工作,只是决策质量悄然下降。
实践中最常见的失效模式不是窗口不够长,而是信息密度不对——偶尔才用到的知识每次都加载、稳定的规则和动态的状态混在一起。
Karpathy 的洞察(p72):模型的”记忆差”在某种程度上是特性而非缺陷——窗口有限性迫使模型从大量细节中抽象出一般性模式。
另一条理论支撑(p72):上下文学习更像快速适配机制而非真正的学习——「不是真的改变了模型参数,但效果类似于做了一次小小的专项训练」。这解释了 few-shot 为什么有效,也解释了为什么这种改善不会跨会话累积。
2.7.3 压缩与 KV Cache:看似矛盾,实则互补(p73)
关键在于压缩发生的时机和位置——不是在单次 API 调用过程中改上下文,而是在两次调用之间由 Agent 框架预处理:
- System Prompt 和 Tool Definitions 永远不动
- 压缩对象是对话历史中的 tool results——替换位置之后的缓存失效,之前的仍有效
- 这是一个有意识的权衡:不压缩就溢出、任务直接失败;压缩虽损失部分缓存,但长度可控、密度更高
推论:「压缩的频次需要权衡——频繁压缩会频繁破坏缓存,最好在上下文接近阈值时批量压缩,而不是每轮都压。」
实验 2-9:六种压缩策略(p73–74)
任务:追踪 OpenAI 联合创始人的职业状态。模型:Kimi K3,刻意把上下文预算限制在 128K 以触发压缩。
| 策略 | 机制 | 结果 |
|---|---|---|
| ① 无压缩 | 全量保留 | 7 次工具调用累计约 367,000 字符(平均约 52,000/次),第 5 次迭代溢出,任务失败 |
| ② 个体摘要 | 每个结果独立生成 2–3 段摘要 | 成功,但信息碎片化——多页面重复描述同一事件 |
| ③ 组合摘要 | 所有结果合并后生成综合摘要 | 成功,但输入超长时必须截断,可能丢末尾信息 |
| ④ 上下文感知压缩 | 把当前查询意图 + 已积累信息纳入压缩决策(Given the search query: {query} / Current context: {context}) | 最优。一次压缩把 147,877 字符压到 1,963 字符(约 1.3%)仍保留关键信息 |
| ⑤ 感知 + 引用 | 每条事实附带来源 URL | 有损压缩 + 无损索引的结合,可回溯 |
| ⑥ 自适应窗口 | 超过窗口 80%(128K 即 102,400 token,实测约 135,600 触发)才批量压缩全部未标记工具结果,[COMPRESSED] 防重复 | 总 token 大,但前几次迭代保持完整原始信息,初期信息收集灵活性最大 |
策略④成功的洞察(值得单独记):
多步骤任务中,不同阶段需要的信息密度和类型是不同的——初期需要广泛的信息收集,中期需要精确的事实核验,后期需要综合的信息整合。
注意 · 原文数据不一致:图 2-16/2-17 与正文的数字对不上。例如”上下文感知”图中为 25,198 token / 0.9% / 15 次迭代,正文写 40,157 token / 约 3.0% / 7 次迭代;“个体摘要”图中 123,205 / 6.8% / 24 次,正文 276,608 / 10.9% / 12 次。图注称 token 减少 77%,章末小结称 75% 以上(p76)。引用这组数字前建议以配套仓库实测为准。
2.7.4 生产级五层压缩(以 Claude Code 为参照,p74–75)
不同类型的信息有不同的保质期,压缩策略应当与信息的预期生命周期匹配。
| 层 | 机制 | 特点 |
|---|---|---|
| 1 | 工具结果预算控制 | 大输出存磁盘,模型只看摘要预览;替换决策一旦做出就被冻结以保证缓存一致 |
| 2 | 噪声直接删除 | 低价值内容直接移除、不做摘要——对噪声做摘要只是在浪费 token |
| 3 | API 层微压缩 | 服务端从前缀移除指定工具结果,本地消息不变。零本地实现成本,但移除点之后缓存同样失效 → 适合在反正要付重建代价时用,不宜频繁触发 |
| 4 | 归档式摘要 | 逐轮结构化摘要——像 git log 那样保留每轮独立记录,而非像 git squash 合并成一条 |
| 5 | 全量压缩 | LLM 驱动,最后手段。分两阶段(先压会话记忆,不行再全量),配连续失败熔断器——生产数据表明大量会话会困在反复压缩失败的循环中烧钱 |
排列顺序有意义:前三层实现成本最低、缓存扰动可控,优先使用;后两层成本高但效果强,作兜底。
2.7.5 四条设计原则(p75)
- 信息价值非均匀分布:关键决策点(人员名单)> 支撑性证据(新闻细节)> 冗余噪声(导航栏、页脚广告)
- 语义完整性:「Sutskever 于 2024 年 5 月离开 OpenAI」不能压成「Sutskever 离开」——时间和公司名不可丢
- 任务相关性:同样内容在”查找创始人名单”和”了解个人背景”下应产生不同压缩结果
- 压缩即理解:需要深层语义理解;且显式压缩的结果是可审查的、可跨会话复用的
保留优先级清单(p75–76)——建议直接抄进你的系统
压缩最容易丢失的不是细节本身,而是早期的架构决策、约束背后的理由和失败的路径——LLM 通常会优先删除那些看起来还可以重新获取的信息。
- 架构决策和关键约束:不得摘要
- 已修改的文件列表和关键变更记录:完整保留
- 验证状态(pass/fail):必须保留
- 未解决的 TODO 和回滚笔记:必须保留
- 工具输出:可以删除,仅保留 pass/fail 结论
另:UUID、hash、IP、端口、URL、文件名等标识符必须原样保留——「一旦把 PR 编号或 commit hash 改错一位,后续的工具调用就会直接失效」。
2.7.7 隔离优于压缩(p76)——本章的收束
压缩是在信息已经进入上下文之后做减法,而一个更釜底抽薪的思路是:让大体积的中间信息根本不进入主上下文。
对比同一任务”在代码库中找到处理支付回调的函数”:
| 做法 | 主上下文增量 |
|---|---|
| 主 Agent 亲自搜索 | 十几个文件、数万 token 原始代码进入主上下文,绝大部分在找到目标后就沦为永久占据窗口的噪声 |
| 委派搜索子 Agent | 只增加两条消息:一条任务描述,一条结论(“函数位于 src/payment/callbacks.py 的 handle_callback,另有两处调用点”) |
这本质上是用隔离代替压缩:压缩是有损的、需要额外 LLM 调用的事后补救;隔离则让噪声从一开始就与主上下文绝缘,主 Agent 的 KV Cache 前缀也完全不受影响。
代价(作者诚实地指出):子 Agent 看不到主 Agent 的完整上下文,任务描述必须自包含、目标明确——「这又回到了本章的主题:上下文的质量决定能力上限,对子 Agent 同样成立。」
生产实例:Claude Code 的 Task 工具、各类 Deep Research 系统的检索子 Agent。
四、贯穿全章的三条暗线
读完之后回头看,会发现七节之间有三条反复出现的线索。
暗线 A:模型只会检索,不会归纳 → 所有”提炼”都要外置
| 出现位置 | 表现 |
|---|---|
| 2.6.1 | 状态栏的全部理论基础:「上下文窗口是一台只有一半的检索引擎」 |
| 2.7.2 | 压缩的第二动机:「把需要思考才能得到的结论变成可以直接检索的知识」 |
| 2.4.5 | few-shot 有效的原因(上下文学习”像被临时定制过”),也解释了它不跨会话累积 |
| 2.8 | 章末纲领:「不要让模型被动地在海量信息中检索,而要主动为模型提供经过提炼的结构化知识」 |
推论:状态栏(加法)和压缩(减法)不是两件事,是同一件事的两面。
暗线 B:稳定前缀是前置约束,它反复制造同一个两难
同一个取舍在本章至少出现四次,作者自己在 p61 明确点名过一次:
| 位置 | 两难的两端 |
|---|---|
| 2.4.5 few-shot | 动态检索最相关示例(效果好)vs 固定示例集(缓存稳) |
| 2.5.2 Skills 注入 | ”只发一次、节省缓存” vs “每轮置底、保住注意力” |
| 2.6.4 状态更新 | 每轮替换(无歧义)vs 持久追加(缓存友好) |
| 2.7.3 压缩时机 | 频繁压缩(长度可控)vs 批量压缩(少破缓存) |
推论:这不是四个独立的工程选择题,是同一个约束在四个位置的投影。理解了 2.3.4 “缓存作为架构前置约束”,四道题的答案都能自己推出来。
暗线 C:一切进入上下文的外部文本,都是不可信输入
| 位置 | 注入面 |
|---|---|
| 2.4.7 | 网页、邮件、PDF 元数据、图片 EXIF |
| 2.4.7 | Skills 本身——“把外部内容当作指令加载”的制度化形式,比网页隐藏文本更直接 |
| 2.4.7 / 2.6.1 | 状态栏本身——正因为模型高度信任它,污染它的收益才最大 |
推论:本章介绍的每一个”提升能力”的机制,同时也是一个”扩大攻击面”的机制。作者反复强调上下文层防御只是第一道防线。
五、工程规则速查表
可以直接当 checklist 用。
KV Cache
- 系统提示词和工具定义定下就不改,一个空格都不改(p47)
- 动态信息(时间戳、用户状态)永远追加到末尾,不改前缀(p47)
- 用标准 API 格式,不自行拼接
USER:/ASSISTANT:(p47) - 不要动态排序工具定义——固定顺序对选择能力几乎无影响,对性能提升显著(p52)
- 不要用滑动窗口裁历史——破坏前缀 + 丢关键工具结果 + Agent 陷入循环(p52)
- 不要把工具结果伪装成 user 消息(毁思维链 + 抹掉来源辨别依据)(p50 / p58)
- 把运行时二值条件全部归到缓存边界之后(N 个条件 = 2^N 种缓存键)(p53)
- 子 Agent / 旁路调用的前缀与父 Agent 字节级对齐(p53)
提示工程
- 检验标准:聪明的新员工读完还不知道怎么做,Agent 也不知道(p54)
- 流程驱动(SOP)优于规则堆砌(p55)
- 业务规则细化到可执行——由产品经理设计,工程师只负责准确编码,不擅自决定业务逻辑(p56)
NEVER全大写保留给真正关键的约束,过度使用会稀释(p55)- few-shot:2–3 个覆盖边界情况 > 10 个大同小异;一旦确定字节级稳定,不要逐请求动态检索(p56)
Skills
description写成路由条件(Use when / Don’t use when + 反例),不是功能介绍(p60)- 元数据注入末尾 + 完整内容按需工具加载(方式三)(p61)
- 元数据增量发送:每个 skill 只在首次出现时发(p61)
- 来源不明的 Skill 必须先审查,如同审查将要执行的代码(p58)
- 交互模式对齐模型厂商的训练方法论(p61)
状态栏
- 用代码维护,别拿 LLM 批量统计长历史(20 行正则 > 前沿大模型)(p66)
- 写成键值对
衣物: 9 件(合格 7、次品 2),不要写成散文(p66) - 删原文前先确认状态栏覆盖了所有会被问到的维度;新增问法当成一次改表结构(p66)
- 把状态栏准确率当一线生产指标盯(容错约 10%)(p66)
- 信息越是来自对外部世界的真实观测,价值越高;绝不能来自可被污染的数据源(p67)
- 读数 + 操作策略成对给出,光给读数模型不会改变行为(p70)
- 更新方式:长轨迹高频更新 → 持久追加;短轨迹或大状态块 → 每轮替换(p69)
压缩
- 接近阈值时批量压缩,不要每轮都压(p73)
- 压缩决策纳入当前查询意图 + 已积累信息(上下文感知压缩效果最好)(p74)
- 对噪声直接删除,不做摘要(p75)
- 全量压缩配连续失败熔断器(p75)
- 保留优先级:架构决策/关键约束不得摘要;验证状态、未解决 TODO 必须保留;UUID/hash/URL/文件名原样保留(p75–76)
- 大体积中间信息优先考虑子 Agent 隔离而非事后压缩(p76)
六、批判性分析:这一章的强项、张力与可追问处
强项
- 理论—证据—工程三层闭合。 每个机制都给了”为什么有效”(注意力/缓存原理)、“多有效”(自建基准的量化数字)、“怎么做”(可照抄的规则)。这在 Agent 类书籍里不常见——多数只有第三层。
- 作者诚实地标注了自己论断的边界。 例如状态栏”只对有干净结构化摘要的任务有效,对大段散文多跳推理别指望”;KV Cache 可编辑”仍属研究阶段,三条铁律依然是默认原则”;“追加到末尾”的注意力优势”只在注入的当轮成立”。这种自我设限提高了可信度。
- 2.3.4 是全章的思想制高点。 “缓存经济性是前置约束而非事后优化”这个判断,把一个性能话题提升成了架构话题,并给出了三个具体的传导路径(提示词排序、子 Agent 对齐、替换串冻结)。
值得注意的张力
① “上下文学习是检索而非推理”这个论断,作者自己的限定和它的使用范围之间有轻微张力。
作者严谨地限定为”单次前向传播内消费已有上下文”,并明确”不否定模型可以通过生成 CoT 完成多步思考”。但整章从这个论断推出的工程结论(状态栏、压缩、蒸馏),实际隐含的是一个更强的版本:用 CoT 现算的代价高到不可接受。这个更强版本在计数、状态跟踪类任务上有 2.4 万次评测支撑,但推广到”所有归纳类任务”时证据就弱了。作者在思考题第 6 题(★★★)里其实自己抛出了这个问题。
② 状态栏与提示注入之间有一个未完全解决的循环。
状态栏之所以有效,是因为模型几乎无条件相信它(p66);而这恰恰是它作为攻击面最危险的地方(p58)。作者给的缓解是”信息要来自可靠的真实观测”——但在一个 Agent 要处理网页、邮件、第三方 API 的系统里,“可靠观测”和”外部输入”的边界往往并不清晰(比如状态栏里的”用户当前订单状态”来自一个第三方 API)。这一层防御的具体工程方案,本章没有给出,被推给了第 4、5 章的执行层防御。
③ 状态栏的”有损投影”问题,在生产中比作者呈现的更棘手。
“新增一种问法当成一次改表结构”这条建议在原理上正确,但它假设你能预先枚举会被问到的维度。在开放域 Agent 里这恰恰是最难的部分。“连 Claude 都从 100% 掉到 7.6%“这个数字应该被当成警告而非注脚——它意味着状态栏 + 删原文这个组合,在维度覆盖不全时是一个悬崖式失效,而不是平滑退化。保守做法应该是:除非任务维度封闭(计数、状态机跟踪),否则保留原文。
④ 实验数据的图文不一致(p73–76)削弱了压缩一节的说服力。
这是全章唯一明显的编辑问题。六种策略的核心排序(上下文感知最优)在两版数据下都成立,所以结论不受影响,但具体数字不宜直接引用。
我认为最被低估的两段
- 2.4.6 结尾关于”工具定义也在渐进式披露化”的更新(p57)。这段实际上宣告了”工具定义必须在上下文最前面”这条经典铁律的松动,且已是 API 原生能力(不是框架补丁)。很多人对 Agent 上下文布局的心智模型还停留在松动之前。
- 2.6.5 的”读数 ≠ 策略”发现(p70)。“只给原始时间戳与什么都不给几乎没区别(差 2–3 个百分点)“这个结果,对所有做可观测性注入的人都是一记警钟:你往上下文里塞的指标,如果没有配套的”该怎么用”的说明,很可能完全没起作用而你还以为起了。
七、关键数据索引
| 数据 | 数值 | 出处 |
|---|---|---|
| 上下文窗口 | Qwen3 32K / Claude 200K / Gemini 2M | p35 |
| 客服 Agent 事故 | TTFT 0.5s → 3–5s,月账单近翻倍 | p46 |
| Attention Sink | 首 token 有时吸收 >70% 注意力 | p49 |
| 0.6B 模型速度 | M2 上 >100 token/s | p45 |
| Prompt Cache 读取成本 | Anthropic/DeepSeek 约 1/10,OpenAI 约五折;写入约 1.25× 加价;最小 1024 token;TTL 约 5 分钟 | p52 |
| 缓存键组合爆炸 | 3 个二值条件 = 8 种缓存键 | p53 |
| KV Cache 可编辑 | 约 1% 算力;p90 TTFT 降几十至几百倍;命中率约 98.5%;12 模型 logit 余弦 0.90–0.999 | p53–54 |
| 消融:信息组织打乱 | 成功率下降 >30% | p58 |
| 消融:移除工具描述 | 错误率 +45% | p58 |
| Skills 体积 | 元数据 ~300 tokens;SKILL.md ~2K tokens | p59–60 |
| Skills 缓存演化 | Turn1 ≈2.5k → Turn2 ≈0.5k → Turn3 ≈0.4k cache_creation | p62 |
| 上下文蒸馏基准 | 3 类任务 × 11 模型 × 近 2.4 万次评测 | p66 |
| 状态栏:弱模型 | 准确率 +40 ~ +54 个百分点 | p66 |
| 状态栏:强模型 | 思考量/延迟/花费各降约一个数量级 | p66 |
| 状态栏覆盖不全 | Claude 从 100% → 7.6% | p66 |
| 状态栏容错 | 数错 10% 以内收益还能保住大半 | p66 |
| TODO 列表 | 启用 15 次迭代 vs 禁用 21 次 | p69 |
| 详细错误信息 | 替代方案成功率 60% → 95% | p70 |
| 时间感:只给读数 | 与什么都不给差 2–3 个百分点 | p70 |
| 时间感:加操作手册 | +19 ~ +49 个百分点 | p70 |
| 压缩实验:无压缩 | 7 次调用约 367,000 字符,第 5 次迭代溢出失败 | p73 |
| 压缩实验:单次压缩比 | 147,877 → 1,963 字符(约 1.3%) | p74 |
| 自适应窗口阈值 | 窗口 80%(128K → 102,400 token),实测约 135,600 触发 | p74 |
八、实验与思考题清单
9 个配套实验(代码仓库:https://github.com/bojieli/ai-agent-book)
| 编号 | 难度 | 名称 | 项目目录 |
|---|---|---|---|
| 2-1 | ★ | 本地 LLM 服务部署与工具调用 | local_llm_serving |
| 2-2 | ★ | 注意力机制可视化 | attention_visualization |
| 2-3 | ★★ | 常见的错误上下文管理模式 | kv-cache |
| 2-4 | ★★ | 提示工程的消融实验 | prompt-engineering(基于 Tau-Bench) |
| 2-5 | ★★ | 提示注入攻防实验 | — |
| 2-6 | ★★ | 用 Agent Skills 从论文生成 PPT | — |
| 2-7 | ★★ | 注意力可视化验证状态栏效果 | attention_visualization |
| 2-8 | ★★ | 五种 Agent 状态栏技术 | agent-status-bar |
| 2-9 | ★★★ | 六种压缩策略对比 | — |
9 道思考题,其中最值得动手想的三道(★★★):
- 第 1 题:设计一种既避免信息丢失、又控制长度、且不破坏 KV Cache 前缀的策略。(提示:本章其实给了答案的两半——2.7.4 的分层压缩 + 2.7.7 的子 Agent 隔离)
- 第 6 题:若”上下文学习本质是检索而非推理”成立,当前所有基于”把更多信息塞进上下文”的优化方向都需要重新审视。怎么突破?
- 第 7 题:Skills 渐进式披露依赖模型判断”我需要哪个 Skill”——如果模型不知道自己不知道什么,就无法正确触发加载。这个”元认知”问题怎么解?
九、这一章通向哪里
| 本章埋的伏笔 | 在哪展开 |
|---|---|
| 工具定义的设计原则 | 第 4 章 |
| 子 Agent 作为协作工具的完整设计 | 第 4 章 |
| 执行层防御(权限、沙盒、独立审查) | 第 4、5 章 |
| 检索内容的注入风险(知识库投毒) | 第 3 章 |
| ”仅需七个核心工具”的 Skill + 通用执行器模式 | 第 5 章 |
| 时间感蒸馏进权重(稀疏 vs 稠密奖励的对照) | 第 7 章 |
| Claude Code 的周期性离线记忆整合 | 第 8 章 |
| 让 Agent 自己设计知识结构 | 第 8 章 |
| Loop 工程与多 Agent 上下文架构 | 第 10 章 |
下一章(第 3 章)把上下文管理从窗口内延伸到跨会话持久化:用户记忆与知识库。
版权说明
本文为读书笔记,其中标注页码的引文逐字摘自李博杰《深入理解 AI Agent:设计原理与工程实践》第 2 章,版权归原作者所有。原书选择开源发布,配套代码见 github.com/bojieli/ai-agent-book。
本文的结构梳理、暗线归纳、速查表与第六节的批判性分析为笔记作者整理,如与原书原意有出入,以原书为准。推荐直接读原书——本文再详细也只是索引。