上下文工程:把有限的注意力花在刀刃上 · Lesson 3 of 6

第 3 课:即时检索:让 Agent 自己去找上下文

学习目标:

  • 能用「注意力预算」算清预加载的隐性成本,判断一份材料该不该放进初始上下文
  • 能描述即时检索的运作方式:轻量标识符、元数据信号、靠探索渐进发现
  • 能为一个具体的 Agent 划出混合策略:哪些信息预载、哪些留标识符运行时再取

前置要求:读完第 1、2 课,手边留着本系列第 7 门课写好的 harness 循环 | 上一课 第 2 课 << | 下一课 第 4 课 >>

先摁住「全塞进去」的冲动

假设你要做一个代码库问答 Agent:仓库里有 200 个源文件,用户会问「这个函数在哪儿定义的」「改这个配置会影响什么」。最顺手的想法是:把 200 个文件全部读出来,拼进初始上下文——反正现在模型的上下文窗口很大,塞得下。

塞得下,不等于该塞。第 1 课讲过,模型解析大体量上下文时靠的是一份「注意力预算」,每引入一个新 token 都会消耗掉一部分1。而且这笔账越到后面越难看:上下文里的 token 越多,模型从中准确召回信息的能力越差1。好在这是一条渐变的性能坡道,不是悬崖——有的模型退化得缓一些,但这个特征在所有模型上都存在1。所以第 1 课的结论在这里要再念一遍:上下文必须当成一种边际收益递减的有限资源来对待1

回到那 200 个文件。用户问一个具体问题,真正相关的可能就两三个文件;剩下 197 个文件的十几万 token 不是无害的背景板,它们在同一份注意力预算里跟关键内容抢注意力。更麻烦的是,Agent 是在循环里跑的:每一轮都会产生新的、可能对下一轮推理有用的数据1——如果初始上下文就已经七八成满,循环没跑几轮就得撞墙。

于是问题变成:什么时候把资料直接递到模型眼前,什么时候只告诉它「资料在哪儿,自己去拿」?这就是本课的全部内容。

两种策略摆在一起看

先把两种做法各自说清楚。

预加载:推理开始之前,把可能用到的资料全部放进初始上下文。模型第一轮就能看到所有东西,不需要任何额外的检索动作。

即时检索:初始上下文里不放原文,只保留轻量标识符——文件路径、存好的查询、网页链接这一类1;运行时 Agent 用工具按需把内容加载进来。

预加载即时检索
初始上下文
拿到资料的时机第一轮就在眼前要先花一到几轮工具调用
token 花在哪儿大量花在「可能用得上」的内容花在「当下确实要用」的内容
典型的翻车方式注意力被稀释,关键内容被淹没检索绕路、空转,多烧轮次和预算

想想你自己是怎么干活的:你不会把整个代码库背下来。你记得的是「鉴权逻辑在 auth 目录」「配置解析好像在 config.js」——一套指向内容的索引,需要细节的时候才打开文件看。即时检索就是把这套工作方式交给 Agent。

但请注意表格的最后一格:检索本身不是免费的。每次即时检索都是一轮完整的工具调用往返——模型发起调用、harness 执行、结果回填、再推理一次。你在本系列第 7 门课给 harness 装过最大轮次和预算这两道阀,检索花的正是这两道阀管着的东西。所以「永远即时」不是标准答案,这是一笔要算的账,我们在后面的取舍框架里细算。

即时检索是怎么运作的

让即时检索转起来,需要三样东西:一套标识符(让模型知道「有什么、大概在哪儿」)、几个检索工具、一个允许多轮探索的循环。合在一起,Agent 就能靠探索一步步渐进地发现相关上下文1

这里有个容易被低估的关键点:标识符的元数据本身就是信号。文件名、目录结构,都在提示内容的相关性和用途1tests/refund.test.js 这个路径你不用打开就知道里面是什么;一个两年没动过的 legacy/ 目录,多半不用先看。Anthropic 在多 Agent 研究系统的复盘里有一句说得很透:「搜索的本质是压缩:从庞大的语料中蒸馏出洞察。」2即时检索的每一步——看目录、搜关键词、挑文件——都是在做这种压缩:把「可能相关的一大片」收窄成「确实要读的一小块」。

落到代码上。给上面那个代码库问答 Agent 配三个工具,实现全都很短:

再按第 2 课的标准写工具定义——自含、用途极其明确、功能之间重叠最小1

系统提示里只放稳定的部分:职责、引用要求、检索的行为约定。注意,一行文件内容都没有:

假设把这套东西接到一个小型电商仓库上,问一句「订单退款金额是在哪儿算的?」,一次典型的运行轨迹长这样(仓库内容是示意,每一步的调用形式和返回格式都由上面的实现决定;第 3 轮的文件正文太长,此处用一行括注代替原文):

第 1 轮  list_files({ path: "." })      →  README.md         src/         tests/第 2 轮  grep({ pattern: "Refund", path: "src" })      →  src/payments/refund.js:12:export function calculateRefundAmount(order, policy) {         src/orders/service.js:6:import { calculateRefundAmount } from "../payments/refund.js";         src/orders/service.js:88:  const amount = calculateRefundAmount(order, policy);第 3 轮  read_file({ path: "src/payments/refund.js" })      →  (该文件共 60 行,全文返回)第 4 轮  模型不再调用工具,直接作答:         「退款金额在 src/payments/refund.js 第 12 行的 calculateRefundAmount         里计算;orders 模块在 src/orders/service.js:88 调用它。」

留意第 2 轮到第 3 轮之间发生了什么:grep 返回了三行匹配,模型没有把两个文件都读一遍,而是从行内容看出定义在 refund.jsservice.js 只是导入和调用方,于是只读了一个文件。文件路径加匹配行这些元数据,先替模型完成了一轮筛选1。整条轨迹里进入上下文的,只有一份目录列表、三行 grep 结果和一个 60 行的文件——而不是 200 个文件。

还有两处值得回味。一是两个工具实现里的截断:grep 最多返回 50 行,read_file 最多 400 行。第 2 课说过,工具返回的信息要讲究 token 效率1——检索工具是上下文的供货商,供货商先把量控制住,循环才活得久。二是失败情形:如果 grep 一直搜不到,模型可能换着关键词反复搜。这正是本系列第 7 门课的空转检测和预算阀要兜底的场景——探索是好事,无限探索不是。

混合策略才是常态

讲到这里你可能以为结论是「即时检索赢了」。不是。真实系统很少站在两个极端上,常见的形态是混合:一部分数据预先取回,图的是速度;其余交给模型,让它在运行时相机自主探索1

你天天在用的 Claude Code 就是活例子。CLAUDE.md 在会话开场被整体放进上下文,而 glob、grep 这类原语支撑运行时的即时探索1。官方文档说 CLAUDE.md 是「Claude 在每次会话开始时都会读的特殊文件」3——正因为每次都加载,文档建议里面只放广泛适用的内容,并对每一行都问一句:「删掉这行,Claude 会犯错吗?」3答案是否定的行就该删。文档的原话相当不客气:臃肿的 CLAUDE.md 文件会让 Claude 忽略你真正的指令3

技能(skills)走的是第三条路:按需加载,不让每次会话都背着它们3。把这三样摆在一起,正好是一张混合策略的分层图:

  • CLAUDE.md:稳定、每轮都受它约束 → 开场预加载;
  • 技能:成套的专项能力,特定任务才用 → 按需加载;
  • 代码库本身:体量大、每次只用到一角 → 只靠 glob/grep 即时探索。

你给自己的 Agent 做设计时,画的就是这张图的翻版:哪些内容坐进「CLAUDE.md 的位置」,哪些内容坐进「代码库的位置」。

一个能直接上手的取舍框架

面对每一份候选材料,问两个问题:

  1. 它稳定吗? 内容是不是长期不变,跟具体问题无关?
  2. 每一轮(或几乎每一轮)都用得上吗?

两个都是「是」→ 预加载。典型:编码规范、核心业务约束、Agent 的行为守则、目录顶层结构。这类内容通常也不大——如果一份「每轮都要用」的材料大得吓人,先怀疑它是不是真的每轮都要用。

只要有一个「否」→ 留标识符,即时检索。典型:某个模块的完整源码(只有涉及该模块的问题才需要)、历史工单(只有排查特定故障才查)、长篇设计文档(只有做方案对齐时才翻)。

然后把检索的成本放进天平再校一遍:每次检索多一轮往返、多一次延迟、多花一笔预算。所以对「小而常用」的材料别赌气搞即时检索——为省下 600 字的预载空间,换来每个会话都多跑一轮 list_files,是笔亏本买卖。反过来,预载一份大概率用不到的 2000 行脚本,就是在纯烧注意力预算1

Claude Code 的文档把这件事的分量说得很直接:上下文窗口是「最需要管理的资源」3。预加载和即时检索不是路线之争,它们是你管理这份资源的两只手。

这一课不讲 RAG,这是故意的

一说「检索」,很多人第一反应是向量库、embedding、RAG 流水线。本课刻意不碰这些——课程 README 划过边界,这门课里的「即时检索」专指一件更朴素的事:Agent 拿着文件系统和搜索类工具,按需拉取内容。

这不只是教学上的偷懒。文件路径天然自带层级和命名语义,grep 的结果精确、可解释,这套组合已经足以支撑「靠探索渐进发现上下文」的完整循环1。Anthropic 谈构建 Agent 时给过一条分寸建议:应当考虑只在复杂度能被证明确实改善结果时才引入它4——注意原文用的是「考虑」(consider),这是权衡的姿态,不是禁令。对一个代码库问答 Agent 来说,先把「文件系统 + grep」这条最简单的路走通、量出它到底哪里不够用,比一上来就架向量检索更符合这个姿态。

最后留个扣子:就算你把即时检索做得再克制,Agent 在长任务里一轮轮跑下去,工具结果仍然会持续累积1,上下文窗口迟早逼近上限。到那时候,光会「少拿」就不够了,还得会「扔」和「记」——这是第 4 课的主题。

小结

  • 「装得下」不是预加载的理由:每个新 token 都在消耗注意力预算,token 越多,模型对上下文的准确召回越差——这是一条渐变的坡道而非悬崖;上下文要当成边际收益递减的有限资源来管1
  • 即时检索的做法:上下文里只留轻量标识符(文件路径、存好的查询、链接),运行时用工具按需加载;标识符的元数据——文件名、目录结构——本身就在提示相关性,让 Agent 靠探索渐进地发现上下文1
  • 检索不是免费的:每次即时检索都是一轮工具调用往返,花的是延迟,以及本系列第 7 门课那几道控制阀管着的轮次与预算。
  • 混合策略是常态:一部分数据预先取回图速度,其余交给模型自主探索1。Claude Code 是现成参照:CLAUDE.md 开场整体载入、技能按需加载、代码库靠 glob/grep 现场探索13
  • 取舍框架就两问:稳定吗?每轮都用吗?双「是」预载,否则留标识符;小而常用的别硬拆去检索,大而低频的别硬塞进预载。
  • 本课的「即时检索」指文件系统与搜索工具的按需拉取,不涉及向量库;在引入更重的检索设施之前,记住那条分寸建议:考虑只在复杂度确实改善结果时才增加它4

>> 第 4 课:压缩与笔记:长任务的上下文管理

Footnotes

  1. Effective context engineering for AI agents — Anthropic Engineering — https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

  2. How we built our multi-agent research system — Anthropic Engineering — https://www.anthropic.com/engineering/multi-agent-research-system

  3. Best practices for Claude Code — Claude Code Docs — https://code.claude.com/docs/en/best-practices 2 3 4 5 6

  4. Building Effective AI Agents — Anthropic Engineering — https://www.anthropic.com/engineering/building-effective-agents 2

Exercises

01

你在给公司内部的「运维值班问答 Agent」设计上下文,手上有五份候选材料:

Level 1:给上下文清单做分拣
  1. 值班守则(约 500 字,每次回答都要遵守的红线,比如不得直接给出生产库密码);
  2. 全部 800 篇历史故障工单(每篇几百到几千字);
  3. 服务目录顶层结构(40 个服务的名字和一句话职责,约 600 字);
  4. 某个服务的完整部署脚本(约 2000 行,只有部署类问题才用得上);
  5. 一份「常见告警关键词 → 工单搜索查询」的映射表(约 30 行)。

用本课的取舍框架,把每一项归入「预加载」或「留标识符即时检索」,并各用一句话说明理由。

Done criteria · checked locally
02

把本系列第 7 门课写好的 harness 循环拿出来,注册本课的三个工具(list_files、grep、read_file),对准你自己的一个真实代码仓库,做成问答 Agent。要求:

Level 2:给你的 harness 加上检索工具
  • 系统提示只预载稳定信息:职责、引用要求、检索的行为约定,不放任何文件内容;
  • 三个工具的描述自含、互不重叠,返回结果有截断;
  • 跑一个真实问题,把工具调用轨迹记下来,检查它是否呈现「由粗到细」的渐进发现。
Done criteria · checked locally

My note

Jot down thoughts, sticking points, things you didn't get. Written to this course's appendix only — the lesson file is never touched.