Context Engineering:为什么你的 agent 到第 40 轮会变笨
一个事实在仅占窗口 2.6% 的 prompt 中下移三行,检索率就从 84% 跌到 19%。问题从来不是窗口。
本页内容
这里有一个 prompt,被用 greedy decoding 发送给同一个模型 288 次。它长 853 个 token。里面包含一份二十五条支持工单的登记表——城市、队列、优先级、负责人、分机号——以及一个问题:Marta Ferreira 需要就她的工单回电。该工单的直线分机号是多少?
登记表每次都完全相同。模型每次都完全相同。唯一变化的是:二十五行中的哪一行包含答案。
| 答案所在位置 | 命中 | 检索率 | 95 % 区间 |
|---|---|---|---|
| 第 1 / 25 | 27/32 | 84 % | 68–93 % |
| 第 4 / 25 | 6/32 | 19 % | 9–35 % |
| 第 7 / 25 | 6/32 | 19 % | 9–35 % |
| 第 10 / 25 | 9/32 | 28 % | 16–45 % |
| 第 13 / 25 | 8/32 | 25 % | 13–42 % |
| 第 16 / 25 | 6/32 | 19 % | 9–35 % |
| 第 19 / 25 | 6/32 | 19 % | 9–35 % |
| 第 22 / 25 | 3/32 | 9 % | 3–24 % |
| 第 25 / 25 | 7/32 | 22 % | 11–39 % |
每行三十二次试验,每次换一张不同工单;Wilson 区间来自第 4 章,因为二十次里十七次并不能把任何东西和任何东西区分开来。
位置一有 84 % 的时间能答对。其他所有位置都落在 9 % 到 28 % 之间,而且这八个区间全部重叠,所以诚实的读法是:第一,然后是其他所有位置。Liu 等人发现的是一个 U 形——两端高,中间低——但这里的近因端并不明显:最后一个位置的 22 % 落在中间位置的分布范围内。真正不落在任何范围内的是从位置 1 到位置 4 的下跌。三行。
这个模型的 context window 是 32,768 个 token。这个 prompt 用了其中 853 个,2.6 %。没有任何东西溢出,没有被截断,没有触达限制,也没有出现警告。模型开始找不到一行明明交到它手里的内容,只因为那一行在二十五行列表里下移了三个位置。
第 16 章给 context window 定了价,并在结尾提醒:拥有一百万个 token 并不等于用好它们,然后指向这里。这里就是答案。
查看详情
本章需要前文提供的东西。
- 第 9 章 推导了 self-attention 及其 成本。每个 token 都会 attend 到其他每个 token,所以成对关系的数量会随长度平方增长。这个事实下面会用到,不再重新推导。
- 第 16 章 统计了五个可计费的 token 桶,并说明一次对话的账单会按二次方增长。本章讲的是如何处理它,同时不把 agent 弄坏。
- 第 18 章 构建了工具目录,并测得二十个工具不会伤害选择,但会把 prompt 放大六倍。这里是它们的账单。
- 第 19 章 构建了检索。下面的 just-in-time retrieval,是把那一章应用到 agent 自己的历史;chunking 不再解释。
- 第 23 章 构建了 harness。本章所有内容都是在它的循环中运行的一条策略,所以它使用 TypeScript:产物是一个长期运行、持有状态的服务,不是一个持有张量的 notebook。
名字相似的两件事
链接到此部分:名字相似的两件事Anthropic 在 2025 年 9 月划出了界线,这两句话应该并排放在一起。Prompt engineering 是“为获得最佳结果而编写和组织 LLM 指令的方法”。Context engineering 是“在 LLM inference 期间策划和维护最佳 token(信息)集合的一组策略,包括 prompts 之外可能落入其中的所有其他信息”。1
真正的区别在于何时,以及由谁。prompt 由人一次性写好并审阅。context 则在每次调用时由没人盯着看的代码组装出来,材料也不是谁手写的:四十轮历史、六个工具结果、四段检索片段、一个用户资料、十二个 JSON schema。第 15 章测量了更好的指令能买来什么。本章讨论的是另外 90% 的 token,它们会自己到来。
同一份文档指出了它们共同消耗的资源:模型“有一个‘attention budget’,在解析大量 context 时会动用它。引入的每个新 token 都会消耗这个预算的一部分”。它也给出了症状:“随着 context window 中 token 数量增加,模型从该 context 中准确回忆信息的能力会下降”——这就是 context rot。1
最后这句话是关于行为的主张,也就意味着它可以被检验,而本页开头的表格就是检验。
那张表是怎么做出来的
链接到此部分:那张表是怎么做出来的四十行请求打到第 22 章的本地 endpoint——一个小型 Python server,在 CPU 上持有 Qwen2.5-0.5B-Instruct,并使用 chat-completions 形状通信,所以循环留在 TypeScript,张量留在端口另一侧。
const DEPTHS = [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1];
for (const d of DEPTHS) {
const slot = Math.round(d * (N - 1));
let hits = 0, other = 0;
for (let t = 0; t < TRIALS; t++) {
const recs = buildRecords(N, 1000 + t); // 25 unique tickets
const gold = recs[Math.floor(rng(7 + t)() * N)]; // a different one each trial
const rest = recs.filter((x) => x.ticket !== gold.ticket).slice(0, N - 1);
const lines = [...rest.slice(0, slot).map((x) => x.line),
gold.line,
...rest.slice(slot).map((x) => x.line)];
const r = await complete(prompt(lines, ask(gold.owner)), { maxTokens: 12 });
const said = /\d{4}/.exec(r.text)?.[0];
if (said === String(gold.ext)) hits++;
else if (said && recs.some((x) => String(x.ext) === said)) other++;
}
}other 计数器把一个令人失望的结果变成有用结果:当模型答错时,它是迷路了,还是很自信?
答案是很自信。在八个非第一位置中,205 个错误答案里有 136 个是另一张工单的分机号——真实的四位数字,格式正确,只是从错误的行读出来。位置 1 的五次漏答里只有一次是这样;位置 7 的二十六次漏答里有二十一次是这样。
这个区别在生产中才是关键。一个说我找不到的模型,是你会注意到的 bug;一个返回相邻行号码的模型,是你会发布出去的 bug,因为在屏幕上两者看起来一模一样。这是第 19 章用可验证引用防止的那类失败,只不过它不是来自索引,而是来自 prompt 内部。
不只是位置。还有数量。
链接到此部分:不只是位置。还有数量。位置是一条轴。长度是另一条轴,而且更容易测试:把答案放在中间,然后拉长列表。
| 记录数 | prompt tokens | 命中 | 比率 | 95 % 区间 | 错误行 | 两者都不是 |
|---|---|---|---|---|---|---|
| 1 | 97 | 18/20 | 90 % | 70–97 % | 0 | 2 |
| 3 | 159 | 11/20 | 55 % | 34–74 % | 9 | 0 |
| 8 | 315 | 3/20 | 15 % | 5–36 % | 17 | 0 |
| 20 | 695 | 2/20 | 10 % | 3–30 % | 16 | 2 |
| 40 | 1,324 | 3/20 | 15 % | 5–36 % | 15 | 2 |
| 80 | 2,587 | 1/20 | 5 % | 1–24 % | 18 | 1 |
| 140 | 4,477 | 2/20 | 10 % | 3–30 % | 18 | 0 |
一条记录、97 个 token:90%。三条记录、159 个 token:55%。八条记录、315 个 token:15%,从那里开始一路低平,直到 140 条记录、4,477 个 token。整个崩塌发生在列表第一行到第八行之间。
最后一列是既不是正确分机号、也不是另一条记录分机号的一切。在页面只有一条记录时,错误答案只能落在这里。一条记录时的两次漏答值得报告,而不是四舍五入抹掉,因为它们都不是拒答:其中一次在登记表唯一一行写着 5805 时回答了 5806。在 97 个 token、只有一个候选项的情况下,这个模型仍然二十次里两次抄错一位数字,而这就是衡量其他一切的地板。
于是得到两件事。更大的 context 买来的是发送更多内容的权利,不是内容一定会被读到的确定性:这个模型有 32,768-token 的窗口,但在这个任务上的有效工作范围只有几百个 token。并且没有阈值、没有悬崖、没有“context 满了”的状态——退化在第三条记录时已经开始,到第八条记录时已经完成,而那只占窗口的 1%。无论 context limit 是什么,支配这里的都不是它。
为什么
链接到此部分:为什么通常会给出两种机制。第一种是第 9 章的算术,Anthropic 使用的说法和本课程相同:模型“基于 transformer 架构,使每个 token 都能 attend 到整个 context 中的每个其他 token。对于 n 个 token,这会产生 n² 个成对关系”。1 在更长序列上做 attention,并不是把同一个操作应用到更多材料;它是把一份固定的概率质量预算分摊给更多竞争者。第二种是训练:模型见到的短序列远多于长序列,所以长距离位置模式是网络中练习最少的部分。这是一个论证,不是测量,本章无法裁决它。
已经确定的是形状,而且自 2023 年以来就是这样。Liu 等人在不同模型家族和尺寸上测试了多文档问答和键值检索,发现“当相关信息出现在输入 context 的开头或结尾时,性能往往最高;而当模型必须访问长 context 中间的相关信息时,性能会显著下降,即便是明确支持长 context 的模型也是如此”。2 第 15 章从那篇论文拿走了位置规则;第 19 章从中拿走了为什么二十个检索 chunk 可能比四个更差的原因。这个事实的实用形式,是这里唯一值得你采取行动的一句话:用你自己的模型和你自己的数据,五分钟就能测出来;任何已发表曲线都不能替代你自己的曲线。
没有人知道自己的窗口里有什么
链接到此部分:没有人知道自己的窗口里有什么问一个团队,他们的 agent context 里装了什么,你得到的是估计,因为没有 API 会返回答案:响应只给你 prompt_tokens,一个覆盖全部内容的数字。
你可以用四次计数和三次相减恢复拆分——完整渲染后的 prompt、去掉工具定义后的同一 prompt、单独 system message(带和不带工具定义),以及移除工具结果后的全部内容:
async function buckets(messages: Msg[]) {
const sys = messages.slice(0, 1);
const withoutResults = messages.filter((m) => m.role !== "tool");
const [total, sysWithTools, sysNoTools, noResults] = await Promise.all([
countPrompt(messages, CATALOGUE), // everything
countPrompt(sys, CATALOGUE), // system + scaffolding + schemas
countPrompt(sys), // system + scaffolding
countPrompt(withoutResults, CATALOGUE), // everything but tool output
]);
return {
system: sysNoTools,
tools: sysWithTools - sysNoTools,
toolResults: total - noResults,
conversation: total - sysWithTools - (total - noResults),
total,
};
}countPrompt 会在 tokenizing 之前应用模型自己的 chat template,这比听起来更重要:你写下的文本并不是被计数的东西。角色标记、tool-calling preamble 和 schema 渲染,都是你付费但从未手动输入的 token。第 7 章构建了 tokenizer,第 16 章用 js-tiktoken 计数;这里的计数来自同一个即将读取 prompt 的模型,这是唯一完全准确的计数。
现在让一个真实 agent 跑一遍:四十轮事故调查、十二个工具、一个伪造的运维环境,返回逼真的日志转储和指标序列。
| 轮次 | system | 工具定义 | 对话 | 工具结果 | 总 prompt | 本轮计费输入 |
|---|---|---|---|---|---|---|
| 1 | 85 | 1,817 | 155 | 490 | 2,547 | 4,370 |
| 2 | 85 | 1,817 | 282 | 529 | 2,713 | 5,275 |
| 5 | 85 | 1,817 | 647 | 1,870 | 4,419 | 8,093 |
| 10 | 85 | 1,817 | 946 | 2,141 | 4,989 | 4,951 |
| 20 | 85 | 1,817 | 1,500 | 2,943 | 6,345 | 6,316 |
| 30 | 85 | 1,817 | 2,187 | 4,000 | 8,089 | 8,059 |
| 40 | 85 | 1,817 | 3,053 | 5,677 | 10,632 | 21,090 |
把第一行和最后一行对着读。
在第 1 轮,prompt 是 2,547 个 token,其中 71% 是工具定义。system prompt 是 3%。用户输入的是 6%。agent 还什么都没做,就已经背着 1,817 个 token 的 JSON schema。
到第 40 轮,prompt 是 10,632 个 token,占比已经倒过来了:定义 17%,对话 29%,工具结果 53%。工具输出在第 5 轮就超过了定义;对话直到第 25 轮才超过定义,所以在会话前 60% 的时间里,工具目录比已经说过的一切都更大。
再看总数。57 次模型调用合计为一次最终 context 只有 10,632 的运行计费了 370,291 个输入 token——最后一个 prompt 大约被付了三十五遍,这就是第 16 章的二次方,再叠上 agent 的倍增器。在这 370,291 个 token 中,103,569 个,也就是全部计费的 28%,是十二个工具定义,它们在每次调用中都按字节完全相同地重发。
一个工具定义要花多少钱
链接到此部分:一个工具定义要花多少钱工具目录是 agent 中最大的固定成本,而且它是不可见的,因为你从来看不到它:你传入一组对象,provider 替你把它渲染进 prompt。在同一组十二个工具上测量:
system prompt + chat scaffolding, no tools: 85 tokens
all twelve definitions: 1,817 tokens
of which fixed tool-calling scaffolding: 126 tokens
three tools instead of twelve: 605 tokens
same twelve, one-sentence descriptions,
no parameter prose: 1,291 tokens (-29 %)每个工具的边际成本从 80 个 token 的 get_current_time(只接收一个字符串)到 263 个 token 的 search_tickets(有四个参数、一个 enum,并且每个参数都有一句指导)。这就是第 18 章核心建议“描述就是 API”背后的汇率:一个好的描述会在 agent 余生的每次请求上花掉大约一百个 token。三个后果。
不用的工具也会计费。 agent 调用了十二个工具中的七个。另五个在 57 次请求中每次花掉 697 个 token——总计 39,729 个,超过这次运行总计费的十分之一,却是它从未触碰的能力。五个中的一个在 trace 里留下了最刺眼的细节:模型三次尝试调用不存在的 read_log。它想要的工具是 search_logs,工具目录里第二贵的定义,237 个 token。它为那个定义付了 57 次费,从未使用它,也从未找到它的名字。
删减文字是最便宜的优化,而且是一笔交易。 把描述缩短到一句话并移除参数文档,每次调用节省了 526 个 token,即 29%,没有改一行业务逻辑——但让模型更糟地调用工具,这正是第 18 章测到的。重点是这笔交易的两边现在都可以用同一个单位衡量。
到了一定规模,发送定义本身就不再合理。 Anthropic 在 2025 年 11 月给了一个数字:一大组已连接 server 意味着在读取请求前要处理“数十万 token”的定义,而用代码执行来替代——让 agent 只发现并加载它需要的定义——“将 token 使用量从 150,000 个 token 降到 2,000 个 token,时间和成本节省 98.7%”。3 这和本章其他内容是同一个想法,只是应用到 schema 而不是历史:保留索引,按需解析条目。
故意把它弄坏
链接到此部分:故意把它弄坏那段四十轮 transcript 里被埋了两件事。在第 2 轮,任何真正工作开始之前,用户说明了一条常设规则:你打开的任何工单都必须归档到我的员工编号 4417 下。 在第 19 轮,事故中途出现了一个事实:受影响的 shard 是 pay-shard-7,已由 payments 团队确认。 第 40 轮,用户要求 agent 打开事故工单,而这需要两者。每个探针用六种不同措辞询问,并按六分制评分——greedy decoding 是确定性的,所以一次调用只给出一个不可重复的 yes 或 no,六次才给出一个比率。
然后用七种 context 策略重放这段 transcript。刻意选择重放而不是重新运行:七种情况下的消息、工具调用和工具结果都是按字节完全相同的,所以唯一变量是每种策略选择保留什么。第 16 章说明了为什么滑动窗口是糟糕的经济选择,因为它会破坏可缓存前缀。这里看它对行为做了什么:
| context 策略 | 40 轮输入 token | 第 40 轮 prompt | 第 2 轮规则 | 第 19 轮事实 |
|---|---|---|---|---|
| 完整历史 | 370,291 | 10,632 | 6/6 | 5/6 |
| 滑动窗口,最近 12 条消息 | 157,578 | 2,922 | 5/6 | 0/6 |
| 省略 4 轮前的工具结果 | 243,445 | 6,311 | 6/6 | 3/6 |
| 每 6 轮 compaction | 195,515 | 3,220 | 6/6 | 0/6 |
| compaction 加模型写的 notes | 200,849 | 3,286 | 6/6 | 0/6 |
| 固定用户自己的轮次,放在前面 | 168,550 | 3,559 | 6/6 | 5/6 |
| 固定用户自己的轮次,放在后面 | 168,835 | 3,564 | 6/6 | 6/6 |
| 对照:只有这两轮,其他什么都没有 | — | 1,981 | 6/6 | 6/6 |
compaction 行包含了 compaction 本身的成本:七份摘要花了 18,581 个输入 token,note-taker 又花了 3,392 个。对照行存在,是为了把零读成零——只把两条消息放进一个 1,981-token 的 prompt 中,这个模型能完美回答两个探针,所以没有哪一行是任务太难。
完整历史记得住,而且是表里最贵的东西:370,291 个输入 token,只为一次持久内容其实只有两句话的会话。
这回答了开头留下的问题。为什么一个 10,632-token 的 transcript 能保住一个事实,而一个 853-token 的登记表会丢?因为长度是错的变量。登记表里有二十五个四位分机号,写在二十五个相同句子里——对你想要的那个而言,有二十四个几乎完美的诱饵。transcript 里只有一个员工编号和一个 shard 名。Context rot 首先是干扰,其次才是体积,这就是为什么上面 205 个错误答案里有 136 个是邻居的值。关于窗口的有用问题不是它有多长,而是里面有多少东西长得像答案。
滑动窗口便宜了 57%,也丢掉了事故。 员工编号能活下来,只是因为 agent 把它重复进了近期轮次。shard 在第 19 轮只说过一次,不在最近十二条消息中——而模型不会承认这一点。问六次,它回答了 “the affected payment shard is shard 4417”,伸手去拿窗口里剩下的唯一另一个标识符,也就是员工编号;另外两次回答 “pool”,是从日志行里的字符串 pool_exhausted 抠出来的。
Compaction 很便宜,也丢掉了同一个事实。 七份摘要,由模型在明确指令下编写:保留标识符、数字、常设指令和未决问题;但 pay-shard-7 不在任何关键摘要里。六次猜测分别是 shard 1、pay_shard_1 和 pool。Compaction 不会大声失败。它会产生一段流畅、可信、短得多的会话,只是悄悄丢掉了一行。
有三行在第 19 轮事实上得了 0/6——滑动窗口、compaction,以及带 notes 的 compaction。它们合计十八个错误答案,其中没有一个是“我不知道”。
然后是那行本该令人尴尬的结果。逐字保留用户自己的四十条消息,再加上最后四轮完整内容,除此之外什么都不要,成本是 168,550 个 token——比完整历史低 54%——并且两个探针的回答与完整历史一样好,甚至更好。 没有 summariser,没有 note-taker,没有第二个模型:只是对 role === "user" 做一个过滤。用户的话是 agent 窗口中最便宜的高价值 token,而大多数设计会把它们和其他东西一起丢掉。
最后两行是在 agent 内部重演开头的表。相同的固定块,从 system message 移到 prompt 末尾:5/6 变成 6/6。六次试验不足以说明差异显著,这里也不把它当成显著差异——它只是提醒你:位置是一个你正在设置的参数,不管你知不知道。
四种少花窗口的方法
链接到此部分:四种少花窗口的方法下面四种策略来自 Anthropic,并按其顺序排列,不过只有后三种属于它的长期视野列表。1 四者都是同一句指令的变体:能获取的就不要随身带,能压缩携带的就不要原样携带。
Just-in-time retrieval
链接到此部分:Just-in-time retrieval不要预加载内容。保留标识符——文件路径、查询、工单号、工具名及其参数——并在需要时解析。上面那个 agent 中最大的桶,是读过一次、用过一次、然后又随身带了三十多轮的工具输出。把四轮以前的每个结果替换成一个 stub,说明它是什么以及如何取回,只需要六行:
const elide: Policy = (h) => [SYSTEM, ...h.flatMap((turn, ti) =>
turn.map((m) => (ti < h.length - 4 && m.role === "tool"
? { role: "tool", name: m.name,
content: `[${m.name} result from turn ${ti + 1}, ${m.content.length} chars, ` +
`elided; call ${m.name} again with the same arguments to re-read it]` }
: m)))];这就是第 19 章,只是把语料库替换成 agent 自己的过去。检索机制已经在那里了——它就是工具目录。
Compaction
链接到此部分:Compaction当 transcript 超过阈值时,用模型写的摘要替换最旧部分,然后继续。写摘要的 prompt 就是整个设计,也正是在那里决定 compaction 成败:保留标识符、数字、常设指令和未决问题;删除寒暄和可以重新获取的工具输出。
Compaction 在构造上就是有损的,它丢掉什么由一个模型替你选择,而且它选错时不会报错。它也不是免费的:每次 compaction 都是一次额外调用,其输入就是正在被 compact 的内容。
结构化记笔记
链接到此部分:结构化记笔记在 context 之外维护一个小型存储,并在每一轮整体重新注入。与摘要不同,它是 append-only 且可寻址的:第 2 轮写下的规则,到第 400 轮仍然逐字存在。这里测量的版本会在每条用户消息之后询问模型,该消息是否包含任何持久内容:
const r = await complete([
{ role: "system", content:
"You keep a durable note file for a support session. Given one user message, " +
"output one short note ONLY if it states a standing rule, an identifier or a fact " +
"that must survive the rest of the session. Otherwise output exactly NONE." },
{ role: "user", content: `Turn ${i + 1}: ${user}` },
], { maxTokens: 40 });
if (!/^none\b/i.test(r.text.trim())) notes.push(`turn ${i + 1}: ${r.text.trim()}`);这是这里上限最高的策略,也是测量中失败的那一个。四十条用户消息里,note-taker 保留了三条 notes,却没有保留两个关键事实中的任何一个:一行 runbook 建议、一个会话即将结束的通知,以及 Europe/Madrid is currently 13:45——这是它发明出来的时间,因为它正在转述的工具返回的是 09:52 UTC。note-taker 也是一个模型,本章的一切同样适用于它。
Sub-agents
链接到此部分:Sub-agents给一个聚焦任务自己的窗口——自己的 system prompt、自己的小目录、没有父级历史——并返回一个简短答案,而不是一段 transcript。第 23 章把一个 sub-agent 放在一个工具 schema 后面,并把账单留在这里;账单就是:父 agent 永远只为子 agent 窗口中的答案部分付费。
sub-agent 不在上表中,因为它不会运行四十轮:它只在某人替它限定好的窗口里运行一次。给定 system prompt、第 17 到 19 轮,其他什么都不给——2,737 个 token——它对 shard 探针回答 6/6,好过表中每一种策略;对员工编号探针回答 0/6,因为交给它的三轮里没有那个数字。
这就是 sub-agents 的两个数字:干净窗口不是智能,而是范围;而范围预先由代码确定,代码本来就必须知道哪些轮次重要。那些答案里还有一件事值得保留。这是唯一一个回答 “None available” 而不是编造东西的策略。一个拥有小而连贯 context 的模型知道自己缺什么;一个拥有大而嘈杂 context 的模型不知道。
三种记忆
链接到此部分:三种记忆几乎所有关于 agent 记忆的混乱讨论,都是三个机制共用一个词。它们有不同的生命周期、所有者和失败模式;如果一个系统把它们放在同一个地方,它就有一个尚未注意到的问题。
| 对话历史 | 检索 | 持久用户记忆 | |
|---|---|---|---|
| 保存 | 本会话说过什么 | 你拥有的文档 | 关于一个人的事实 |
| 存在期限 | 一个会话 | 直到重新索引 | 跨所有会话,永久 |
| 写入者 | 循环,自动 | ingestion pipeline | 模型,刻意写入 |
| 进入 prompt | 每次调用完整进入 | 查询匹配时进入四段 passage | 每次调用完整进入 |
| 失败方式 | 增长到腐烂 | 检索到错误 chunk | 记错关于你的事情 |
| 构建于 | 第 23 章 | 第 19 章 | 本章 |
学术框架来自 CoALA,它围绕“模块化记忆组件”组织语言 agents,并把 working memory 与 episodic、semantic、procedural stores 分开。4 MemGPT 则把同一个想法字面化,借用了操作系统的虚拟内存:窗口内的快速层、窗口外的慢速层,以及模型自己通过 function calls 在二者之间移动数据。5 两者都迫使产品无论如何都必须回答的问题浮出水面——不是我能保留多少,而是这应该属于哪个 store,以及它什么时候过期。
实用测试是每个事实问一个问题:明天什么仍然应该是真的? 第 12 轮的工具结果,什么都不该。会话摘要,到会话结束为止。用户的员工编号是 4417,直到他们换工作。三个答案,三个 store。
下一步去哪里
链接到此部分:下一步去哪里现在,你可以测量窗口里有什么,决定什么留在里面,并区分一个 agent 是真的忘了某件事,还是带着它却没有看。
四种策略中的最后一种不适合放在这里。sub-agent 不是一条 context 策略,而是第二个 agent;只要有两个 agent,你就必须决定它们之间传递什么,以及谁说了算。第 25 章讲的就是这个:五种编排模式,以及每个名字到底从哪里来;两种经常被混淆的 topology——询问一个 sub-agent 并拿回答回来,和把对话交给它然后不拿回来;以及在那一章计价的任务上测得的结果:更简单的安排胜出——随后是判断它何时停止胜出的测试。
它也继承了本章刚刚测到的东西。sub-agent 返回摘要。摘要是你没有亲手写的 compaction,由一个你看不到窗口的模型生成,而父 agent 没有办法把好摘要和自信但错误的摘要区分开——正是本页开头把 84% 和 19% 分开的同一个区别,也是把十八个缺失事实变成十八个编造事实的区别。所以:当 sub-agent 错了,父 agent 到底能看什么?
来源与方法
链接到此部分:来源与方法这里的每个数字都在这台机器上产生,没有任何一个是估算。模型是 CPU 上 float32 的 Qwen2.5-0.5B-Instruct,使用 greedy decoding,由一个小型 Python endpoint 通过 loopback 提供服务;它使用 chat-completions 形状并暴露 token-count route——再次是第 14 章的接缝,张量在 Python 侧,循环在 TypeScript 侧——所以每个计数都是该模型自己的 tokenizer 应用到它自己的 chat template 上得到的。位置表是 288 次调用,九个位置乘以三十二次试验,每次试验使用不同工单;长度表是 140 次调用;agent 运行是 57 次模型调用,跨 43 分钟真实时间;策略表是同一份 transcript 在七种策略下的重放。区间是 Wilson 区间,来自第 4 章。没有调用付费 API,这也解释了为什么本章没有一个价格:token 计数是精确的,而你会拿来相乘的费率在第 16 章。
参考资料
链接到此部分:参考资料-
Anthropic,Effective context engineering for AI agents,2025 年 9 月 29 日,
anthropic.com/engineering/effective-context-engineering-for-ai-agents,2026 年 9 月 7 日读取。文首引用的两个定义、“attention budget”及每个新 token 都会消耗它的说法、context rot 的描述、n² 成对关系框架,以及作为本章主干的策略均来自该文。其中三种是它的 long-horizon 列表——compaction、结构化记笔记和 multi-agent architectures;just-in-time retrieval 更早出现在同一篇文章中,位于 context retrieval 和 agentic search 下,这里把它与它们归为一组。 ↩ ↩2 ↩3 ↩4 -
Liu, N. F., Lin, K., Hewitt, J., Paranjape, A., Bevilacqua, M., Petroni, F. and Liang, P. Lost in the Middle: How Language Models Use Long Contexts. arXiv:2307.03172(v1 2023 年 7 月,v3 2023 年 11 月)。第 15、16 和 19 章引用过,本文在此测量。引用句来自摘要;论文的两个任务是多文档问答和键值检索,而它发现这种效应在明确支持长 context 的模型中依然存在,这一点对产品决策最重要。 ↩
-
Anthropic,Code execution with MCP: building more efficient agents,2025 年 11 月 4 日,
anthropic.com/engineering/code-execution-with-mcp,2026 年 9 月 7 日读取。150,000 到 2,000 token 的减少、98.7% 的数字,以及预先加载的工具定义会在请求被读取前占据 context 的观察,均来自该文。 ↩ -
Sumers, T. R., Yao, S., Narasimhan, K. and Griffiths, T. L. Cognitive Architectures for Language Agents. arXiv:2309.02427(2023)。它围绕“modular memory components, a structured action space to interact with internal memory and external environments, and a generalized decision-making process to choose actions”组织语言 agents,并把 memory 分成 working、episodic、semantic 和 procedural。第 22 章把它的 taxonomy 用于学习型 agent;上面的三 store 表是它的实践投影。 ↩
-
Packer, C., Wooders, S., Lin, K., Fang, V., Patil, S. G., Stoica, I. and Gonzalez, J. E. MemGPT: Towards LLMs as Operating Systems. arXiv:2310.08560(2023 年 10 月)。提出“virtual context management, a technique drawing inspiration from hierarchical memory systems in traditional operating systems”,由模型自己在窗口内的快速层和窗口外的慢速层之间移动数据。它是说明为什么窗口是 cache 而不是 memory 的最清楚表述。 ↩