RSS

第 2 章 / 共 11 章

给窗口做一次盘点

约 14 分钟 更新于

2.1 你打了 14 个字,这一轮送进去了六万 token

回到 shopfront。你新开一个会话,输入框里打的是这么一句:

把 src/checkout/ 迁到 v2 API

十四个字。中文按经验大概十来个 token。你按下回车,脑子里的画面是:模型收到了这十几个 token,开始干活。

实际发生的事情不是这样。这一轮真正送进模型的,是一坨几万 token 的东西,而你打的那十几个 token 排在最后面,占比不到千分之一。

这个落差是上下文工程里最容易被忽略的一件事。第 1 章说窗口是注意力预算,但预算管理有个前提——你得先知道钱花在哪了。大多数人对自己上下文窗口的构成,只有「有个系统提示,有对话历史」这种模糊印象。这一章就是把这坨东西拆开,一项一项摆在桌上,然后给你一张能反复用的盘点表。

不做盘点就去做优化,等于闭着眼睛砍预算。你很可能花半天精简了 CLAUDE.md,省下 800 token,而真正的大头是一次 grep 返回的 12000 token,你根本没看见它。

2.2 一次调用里躺着的八样东西

把一轮请求的输入拆开,通常有这么八类内容。顺序大致就是它们在请求里的排列顺序。

系统提示(system prompt)。 工具厂商写好的那一大段,定义 agent 的行为方式、输出规范、安全边界。你改不了,但它常驻——每一轮都完整地在场。量级上它不小,而且随工具版本变化。

项目指令。 CLAUDE.md 以及同类的项目级规则文件。这是你能完全控制的一块。在 shopfront 里它写着团队约定:用 pnpm 不用 npm、组件必须配 story、API 调用统一走 src/api/client.ts。它也常驻,每一轮都在。第 1 章说的那种「每次翻车加一条」的写法,膨胀的就是这一块。

工具定义(tool definitions)。 每个工具的名字、描述、参数 schema,全部要以文本形式进入上下文,模型才知道自己能调什么。内置的读文件、写文件、执行命令、搜索,加起来已经是一块可观的开销。

MCP 带进来的工具。 这是同一类,但值得单独拎出来,因为它最容易失控。MCP(Model Context Protocol)是连接 AI 应用和外部系统的开放协议,官方的比喻是「AI 应用的 USB-C 口」。它给你能力,但每接一个服务器,那个服务器的全部工具定义就都进了上下文,不管这次任务用不用得上。shopfront 接了一个内部订单系统的 MCP 服务器,它带进来十几个工具——查订单、改状态、退款、对账。而你这次做的是前端 API 迁移,一个都不会调用。它们还是每一轮都在那里。

预载技能的元数据。 如果你用了 Agent Skills,每个技能的 namedescription 会常驻在系统提示里,这样模型才知道有哪些技能可选。单个很小,数量多了就不小。这个设计本身是有意为之的节流手段,第 7 章会讲它背后的渐进式披露。

消息历史。 你说的每一句、模型回的每一段、以及最容易被低估的一项——每一次工具调用的请求和返回都在历史里。第 3 轮读的那个 400 行文件,到第 40 轮还完整地躺着。这一项随轮次单调增长,是长任务里的主要增量来源。

工具返回值。 严格说它属于历史的一部分,但它是最容易失控的一项,必须单独看。一次 grep -r "checkout" src/ 在 1200 个文件的仓库里返回 800 行;一次 npm test 失败输出打了 2000 行堆栈;一次 cat src/api/client.ts 读进来一个 600 行的文件。这些内容进上下文的时候没人拦,它们全额计费,而且永久驻留在历史里直到被压实。

检索结果和压实后的摘要。 前者是你或 agent 主动取回来的外部内容——文档片段、代码搜索命中、MCP 查询结果。后者是上下文接近上限时,系统把之前的对话高保真摘要之后重新起头留下的那段文字。压实(compaction)本身是省 token 的手段,但摘要自己也占位置,第 8 章会讲它的取舍。

2.3 你亲手打的字,是占比最小的那一项

把上面八项摆在一起看,一个反直觉的结论就出来了:你输入的内容通常是整个上下文里占比最小的一部分。

真正吃预算的是两样东西:工具返回值消息历史。前者是单次爆量——一条命令就能塞进上万 token;后者是持续累积——每一轮都比上一轮更沉。而这两样,恰好都不是你直接打字产生的,所以在直觉上它们是隐形的。

这解释了一件很多人困惑的事:为什么精简提示词几乎没有用。你把那句「把 src/checkout/ 迁到 v2 API」反复打磨措辞,省下的是几十个 token,而同一轮里一次没必要的全目录 grep 花掉了一万多。优化的着力点从一开始就放错了地方。

这也解释了为什么第 1 章那三条补救路都失效。它们全都作用在「你能直接看见的那部分」——提示词、贴进去的文件、窗口大小,而问题的主体在你看不见的那部分。

所以盘点的第一价值不是省 token,是把隐形的部分变成可见的。看见之后,你才有资格谈砍哪里。

第2章:一次调用的上下文分层

图 2.1:一次请求送进模型的八层内容,按在请求里的位置从上到下排列。注意每一层的厚度——你亲手输入的那一层薄得几乎看不见,而工具返回值和消息历史两层占据了大半。左侧标注了每一层是「常驻」还是「随轮次变化」:常驻项决定你的固定开销,变化项决定你能跑多久。

2.4 上下文盘点表:一张可以反复填的模板

把八项内容整理成一张表。这张表的列不是随便设的,每一列都对应一个决策:

来源谁控制它典型 token 量级一轮变还是常驻可否缩减
系统提示工具厂商常驻
项目指令(CLAUDE.md常驻
内置工具定义工具厂商 / 你可部分裁剪常驻
MCP 工具定义你(装哪些服务器)常驻
技能元数据常驻
消息历史机制(压实 / 子代理)累积
工具返回值你(怎么发命令)单次爆量后沉入历史
检索结果 / 压实摘要你 + 机制变化

「谁控制它」 决定你有没有着手点。厂商控制的部分(系统提示)你只能接受;你控制的部分(CLAUDE.md、装哪些 MCP、发什么命令)是全部优化空间所在。

「常驻还是一轮变」 决定问题的性质。常驻项是固定成本——它每一轮都收一次费,所以一个 3000 token 的臃肿 CLAUDE.md 在 50 轮任务里被收了 50 次。累积项是跑多久的上限——它决定你在触发压实前还能走几步。这两类要用完全不同的手段治,第 5 章治前者,第 6 到第 8 章治后者。

下面是 shopfront 迁移任务跑到第 20 轮左右时填出来的一份。表里的数字全部是量级估算,用来看比例关系,不是精确测量值——你自己项目的实际数字必须自己量,方法在下一节。

来源谁控制它典型 token 量级(估算)一轮变还是常驻可否缩减
系统提示厂商数千常驻
CLAUDE.md(团队约定 + 历史判例 30 条)两三千常驻能,砍掉与本任务无关的判例
内置工具定义厂商一两千常驻有限
订单系统 MCP 的十几个工具两三千常驻能,本任务不需要,整个摘掉
技能元数据(4 个技能)数百常驻有限
消息历史(20 轮的对话与推理)机制一万上下累积能,靠压实 / 笔记
工具返回值(含第 3 轮的全目录 grep 约 800 行、一次 600 行的 client.ts、一次失败测试的长堆栈)两三万,全场最大单次爆量后沉入历史能,改命令的粒度
检索结果(docs/api-migration.md 片段)一两千变化有限,这是高信号内容
你打的那十四个字约十几一轮变无所谓

这份表一填出来,行动顺序就自己排好了。最大的一块是工具返回值——三万 token 里真正用得上的可能不到一千,这是第一优先级。第二块是那个整个任务都不会调用的 MCP 服务器,它是纯粹的固定成本浪费,摘掉即可,成本几乎为零。而你反复打磨的那句话,在表的最后一行,量级是十几。

对照第 1 章那条原则——找到最小的一组高信号 token——这张表就是找的过程。「典型 token 量级」这一列告诉你哪里有钱,「可否缩减」这一列告诉你哪里能省,两列一交叉就是优先级。

2.5 三种把数字量出来的办法

估算能排优先级,但你迟早需要真数字。有三条路,从粗到细。

看工具自带的用量显示。 大多数 agent 工具会在界面上显示当前上下文占用——用了多少、还剩多少、百分之几。这是最直接的一手数据。养成一个习惯:在任务开始时看一眼,在感觉「它开始跟不上了」的时候再看一眼,把两个数字和中间发生的事对起来。你会很快认出哪种操作是吞噬预算的大户。

用工具提供的上下文查看命令。 有些工具提供了类似 /context 的命令,能给出按来源拆分的占用明细,正好对应上一节那张表的行。这类命令的名字和输出格式在不同工具、不同版本之间不一样,有的叫别的名字,有的干脆没有。请以你手上工具的当前文档为准去核对,不要照抄本书的写法。

手工粗算工具返回值。 这是最土但最有用的一招,因为工具返回值恰恰是最大的一块,而它又最容易在事前估出来。做法是先看行数,再乘平均行长:

# 先数一下这条 grep 会返回多少行
grep -rn "checkout" src/ | wc -l
# 假设输出 800

# 再看看平均每行多长(字符数)
grep -rn "checkout" src/ | awk '{ n += length($0) } END { print n/NR }'
# 假设输出 70

800 行 × 70 字符 ≈ 56000 字符。英文代码大致 4 个字符一个 token,粗算下来接近 14000 token。这一条命令就吃掉了你相当一部分预算,而它的产出可能只是让你知道「checkout 这个词出现在 12 个文件里」。

关键在于这个估算可以在执行前做。把 grep -rn 换成先跑一次 | wc -l,你就在花掉这 14000 token 之前知道了它的价格。命令的粒度是你完全能控制的东西:grep -rl(只列文件名)通常几十行就够,grep -c(每文件计数)更少,加上 head -50 封个顶,都是几秒钟的事。

顺带一提:这类估算不用追求准确。你要的是量级——是几百、几千还是几万。区分「一千和三千」没意义,区分「一千和三万」决定行动。

盘点时最容易踩的三个坑。 第一,只盯着自己打的字。这是最常见的一个,上一节已经说过——你的输入是占比最小的那部分,在这里下功夫收益接近于零。第二,忘了常驻项要乘以轮数。CLAUDE.md 多写 500 token 看着不多,但在一个 50 轮的任务里它被收了 50 次费;判断一条项目指令值不值得留,要问的是「它值不值得在每一轮都占位置」,而不是「它有没有道理」。第三,把 MCP 服务器当成零成本的能力增强。装上去不用就不花钱是错觉——工具定义常驻,装十个服务器等于每一轮都背着十份说明书,哪怕一个都不调用。装之前先问:这个任务真的需要它吗?

2.6 动手:给你的项目填一份盘点表,找出前两名

  1. 复制 2.4 那张空模板,逐行填。厂商控制的行(系统提示、内置工具)填不出精确数字就写「数千」这种量级,不影响使用。你控制的行必须认真填:数一下 CLAUDE.md 的字数、列一下装了哪些 MCP 服务器各带多少工具、翻一遍历史找出返回值最大的那三次工具调用。
  2. 按 token 量级排序,圈出前两名。 大概率其中一名是工具返回值。如果不是,那更值得深究——你的项目结构可能和典型情况不同,而这个不同就是你的优化入口。
  3. 对这两名各写一句处置方案。 格式是「这一项能砍到多少,靠什么手段」。比如:「第 3 轮那次全目录 grep 约 14000 token,改成 grep -rlhead -30 后约 400 token」;「订单系统 MCP 约 2500 token 常驻,本类任务不需要,从配置里摘掉」。

填完的表请留着。第 3 章会用它来做症状归类,第 11 章要用它作为给整个仓库配上下文预算的起点。

答得上来的标准是,你的回答里要说清两件事的代价结构不同:常驻项是固定成本,每一轮乘一次,所以它的治法是一次性删减——把和当前任务无关的项目指令、MCP 工具、技能从配置里拿掉,删一次全程受益。累积项是增长曲线,它决定你在触发压实前还能走多远,所以它的治法是过程中的机制——把状态写到窗口外的文件里、只在需要时检索回来、到点了做压实、让子代理在隔离的窗口里干脏活。如果你的回答是「都少放点东西就行」,那还没抓到区别,回到 2.4 再看一遍那一列。