[控场AI]
· 5 分钟阅读· 2,822 字

上下文窗口是预算而非记忆:截断账本与Agent设计

上下文窗口是预算而非记忆:截断账本与Agent设计

窗口大小不等于记忆容量,需用截断账本量化锚点丢失并接入断言。

文章纠正了开发者把上下文窗口等同于记忆容量的常见误解,指出窗口是输入与输出的共享硬顶,真实提示预算必须扣除预留输出、工具 schema 和系统提示词。截断可能发生在客户端、框架和服务端三处,HTTP 200 只代表传输成功而非内容完整。删除中间消息的策略最危险,会让早期决策消失而最新错误保留且不报错。摘要会导致精确字符串漂移,决策、ID、错误码等不变量必须用锚点逐字保留。文章主张把窗口当缓存而非存储,用检索替代大窗口粘贴,并介绍了截断账本脚本,通过模拟策略量化锚点丢失,强调必须接入断言才能防止回归。

开发者常把上下文窗口的大小等同于记忆容量,这导致Agent在长对话或复杂工具调用中悄悄丢失关键信息。本文基于一篇讨论截断账本(truncation ledger)的原始文章,重新梳理FAQ框架,并加入可落地的实践建议。

窗口大小不等于记忆容量:预算公式

原文指出,窗口是输入与输出的共享硬顶。正确的预算模型是:

budget = window - reserved_output - schemas - system

也就是说,预留的输出token、每次调用都计费的工具schema、系统提示词,都要先从窗口里扣除。剩下的才是你能用于对话历史和用户输入的“提示预算”。很多团队在设计Agent之后才发现预算不足,此时重构成本更高。先做减法,再设计Agent。

原文强调,窗口是“上限”,不是“内存”,更不是你实际发送的内容。

上下文窗口预算示意图

上下文窗口(context window) 指模型单次调用能接收的最大 token 总数,它同时容纳输入(系统提示、工具 schema、对话历史、用户消息)与模型生成的输出。token 是模型处理文本的最小单位,英文约 4 字符对应 1 token,中文则更密集。“窗口即内存”的类比极具误导性:内存可以换页、可以扩容,而窗口是一次性硬约束,超出部分只能被截断或拒绝。此外,预留输出(reserved_output) 常被忽略——若不给模型留出生成空间,回复会被中途切断。工具 schema 每轮都要随请求发送并计费,工具越多,占用越固定。理解这些组成部分,是做好预算的前提。

HTTP 200不代表内容完整:截断发生在三处

原文FAQ 2指出一个常见误解:调用成功只说明请求合法,不说明内容完整。截断可能发生在三个位置:

  1. 客户端在发送前裁剪历史;
  2. 框架静默修剪工具返回结果;
  3. 服务端按自身策略进行截断。

你只能直接控制前两个,但三个都可以从外部测量。核心结论是:200响应是传输事实,不是内容收据。如果只依赖状态码判断完整性,线上问题将难以复现。

HTTP 200 表示服务器已成功接收并处理请求,属于传输层语义,与响应体内信息的完整性无关。在 Agent 场景中,请求体可能已被客户端或框架提前裁剪,服务端也可能因自身长度策略对超长输入做截断,这些都不会改变状态码。要判断内容是否完整,需要引入独立的完整性信号,例如在提示词中标记锚点、记录发送前后的 token 数与消息条数,或对关键字段做校验和比对。把状态码当作内容收据,是线上问题难以复现的常见根因。

截断策略不只是删除最旧消息

原文FAQ 3提醒,不同框架的截断策略不同:删除最旧、删除中间、优先删除大型工具输出、保留系统提示加最新N个token等。最危险的是“删除中间”——早期决策消失,但最新错误保留,Agent会重新询问已回答过的问题,而系统没有任何报错。

原文给出的修正心智模型是:知道你的策略名称,否则等于不知道策略。

摘要会漂移,锚点必须逐字保留

FAQ 4指出,摘要会丢失精确字符串。标识符、错误码、列名、约束条件必须原样保留。原文建议使用锚点(anchor)机制:

  • 决策用 decision: ... 行固定;
  • 标识符用 id=... 固定;
  • 失败用 ERROR: ... 固定;
  • 其余叙述可以摘要。

原则是:摘要叙述,绝不摘要不变量。

锚点(anchor) 是指在提示词中必须以逐字形式保留的关键信息片段,通常是决策、标识符、错误码或约束条件。之所以需要锚点,是因为摘要模型倾向于改写与压缩,精确字符串最容易在改写中失真,而这类信息一旦出错,下游工具调用或数据库操作就会失败。常见做法是用固定前缀(如 decision:、id=、ERROR:)标记锚点,使截断脚本和断言能够用正则识别并统计其存活情况,从而把“是否丢失关键信息”从主观判断变成可测量的指标。

大窗口不能替代检索:把窗口当缓存

FAQ 5的结论很直接:对于成本和准确率,大窗口并不天然优于检索。粘贴40页文档意味着每一轮都为其付费,而检索只在需要的那一轮付费。更便宜且更易调试的设计通常是:小窗口 + ID + 取数工具。把窗口当缓存,而不是存储;存储应放在提示词之外。

截断账本:量化你会丢失什么

原文提供了一个Python脚本 context_ledger.py,用启发式token计数(约4字符/token)模拟“保留尾部”的截断策略,并报告哪些锚点被丢失。示例中预算为200时无丢失;预算降到40时,decision: use staging-db for the migration dry run 被丢掉,而最新的ERROR保留下来——这正是“错误假设被自信陈述”的典型bug。

原文的使用步骤是:用真实日志替换TURNS;为领域添加锚点(工单ID、货币代码、租户slug);扫描预算直到锚点开始消失;记录该数字作为真实预算;并接入断言,让流水线在锚点丢失时失败。原文强调:没有断言的账本只是日记。

context_ledger.py 的核心是一个启发式计数 + 保留尾部模拟的循环:它按近似 4 字符/token 估算每轮消息的 token 数,从最新消息向前累加,直到触及预算上限,被排除在外的早期消息即视为截断丢失。脚本随后用锚点正则扫描保留区,报告哪些锚点仍在、哪些已消失。这种模拟的价值在于可重复与可比较:同一份 turn 日志在不同预算下运行,能直观看到锚点丢失的临界点。但其 token 估算与真实分词器存在偏差,且只模拟你声明的策略,因此结果应作为预算下限参考,而非服务端行为的精确复现。

策略选择与局限

原文给出了一张策略选择表:短会话直接全发;长聊天固定锚点、摘要叙述;大文档按ID检索;工具密集型循环优先删除旧工具输出。没有免费选项——你只能选择自己能检测到的失败模式。

同时要正视局限:启发式token计数与真实分词器可能相差15-20%;脚本模拟的是你的策略,不是服务商的隐藏策略;它无法衡量注意力质量(模型收到一行也可能忽略);锚点正则只是占位符;它也不涉及延迟和单token成本。如果需要逐字审计记录,应把完整转录保留在外部存储,只把ID传入提示词。

值得问的问题

原文结尾提出了一个直接的问题:你的Agent会先丢失哪个锚点?如果答不上来,你只知道窗口大小,不知道Agent的行为。在下一次重构前运行账本,并把turn文件与提示词一起提交,让每次变更可比较。

短脚本,真实预算,更少自信的错误答案。

#上下文窗口#截断账本#Agent设计#token预算#检索增强#锚点机制#截断策略
分享:

相关推荐