☰
Agent长上下文别只会压缩:context-mode找回丢失的细节,减少幻觉
2026/10/6 12:40:34 网站建设 项目流程

做agent工程的人迟早会遇到同一个坎:上下文窗口不够用。一边是MCP工具动辄把几十KB原始数据灌进窗口(一次Playwright快照就几十KB),一边是会话一长系统自动compaction,把前面的对话压成摘要,细节全丢。两个问题叠加,agent越用越"健忘"还越用越贵。

本文不重复官方宣传,而是先把原理讲清,再说清context-mode到底做了什么、怎么装,最后用真实数据测一遍:压缩到底省了什么、丢了什么,以及"压缩+检索注入"能不能把丢掉的东西捡回来。

一、原理:上下文为什么贵,压缩为什么难

Transformer解码每个token都要算注意力o = softmax(qKᵀ / √d) · V。如果每次都重算全部历史K/V,复杂度是O(L²),L是序列长度。KV Cache把历史K/V缓在显存,decode阶段只算新增token,降到O(L)。但KV占用随L线性涨:以Llama-13B为例,n_layers=40、n_heads=40、d_head=128,fp16下每token KV约 2 × 40 × 40 × 128 × 2 = 819 200 bytes,即约0.78MB(819KB),序列到32K时KV约25GB,到128K时约100GB。根因是不断变长的KV随序列变长,跟模型参数量本身无关。

把这笔账摊开,长上下文的开销其实来自三个方向:一是计算瓶颈,prefill要一次性算完整个输入,输入越长算力吃得越狠;二是储存瓶颈,就是上面这笔KV,它跟序列长度成正比,长会话光KV就能把显存吃掉;三是访存瓶颈,decode每生成一个token都要把权重和整个KV从显存搬到计算单元,batch小的时候带宽比算力更早撞墙。行业里用量化、投机解码、MoE来缓解这几项,但它们解决的是"推得快不快",解决不了"上下文里装不下"这件事。

compaction(压缩)的思路是:把旧对话摘要化,只留骨架,释放窗口。这招省上下文立竿见影,但有个死穴——摘要是不可逆的有损压缩,丢掉的细节再也回不来。纯摘要兜底,agent遇到"前面提到过的错误码是什么"这类问题就只能瞎编。

“检索注入"是对策:compaction前把历史索引进本地FTS5/BM25库,压缩后agent按需检索相关片段再注入。上下文里只放"骨架+当前需要的细节”,既不撑爆窗口,又不丢东西。这是通用工程思路,不是某个工具的专利。

二、context-mode到底做了什么

它是个MCP server,不是一个独立应用。装进客户端后注册11个工具:6个沙箱类(ctx_execute、ctx_batch_execute、ctx_execute_file、ctx_index、ctx_search、ctx_fetch_and_index)和5个meta类(ctx_stats、ctx_doctor、ctx_upgrade、ctx_purge、ctx_insight)。它对外讲的四件事里,跟本文直接相关的是前两件:

第一件是把原始数据挡在窗口外。沙箱工具让模型写脚本去分析数据,只把console.log出来的结果放回上下文,而不是把整个快照读进来。官方给的数字是315KB变成5.4KB。

第二件是会话连续性。每次文件编辑、git操作、任务、报错、用户决策都被记进SQLite,compaction发生时它不把这些倒回上下文,而是索引进FTS5,等agent真的需要时再用BM25检索相关片段注入。这条正是本文要测的。

另外两件也值得一提:它提倡"用代码思考",让模型写脚本去统计而不是把50个文件读进上下文;以及它不强制模型的说话风格——只管数据去哪,不管模型怎么措辞,理由是强行要求简短会拖累编码和推理类benchmark。这两条我这次没测,先说清楚,免得把官方口径当成我的实测结论。

生命周期上有个细节容易踩:会话数据是按会话走的,如果你不带--continue重启,上一次的会话数据会立刻被清掉,想要"接着聊"必须显式续上。hooks方面它在Claude Code上注册了PreToolUse、PostToolUse、UserPromptSubmit、PreCompact、SessionStart、Stop这几处,其中PreCompact是压缩兜底的关键,SessionStart负责在运行时注入路由指令,不会往你的项目里写文件。

三、怎么装上

我用Claude Code这条路最省事,先确认版本不低于1.0.33(claude --version),然后:

/plugin marketplaceaddmksglu/context-mode /plugininstallcontext-mode@context-mode

重启(或/reload-plugins)之后跑/context-mode:ctx-doctor验证,全部显示[x]才算装好,doctor会检查运行时、hooks、FTS5和插件注册状态。想看实时省了多少,可以在~/.claude/settings.json里加一条statusline,命令填context-mode statusline,状态栏会显示本次会话省了多少、累计省了多少、效率百分比。

如果你只是想先试试、不想让hook接管路由,可以走纯MCP方式:

claude mcpaddcontext-mode -- npx-ycontext-mode

这样11个工具都在,但少了自动路由和slash命令,模型不会被"提醒"优先用它们——我的建议是先这么试一天,确认顺手再上完整插件。

其他客户端也支持:npm install -g context-mode之后,Gemini CLI改~/.gemini/settings.json把mcpServer和四个hook(BeforeTool/AfterTool/PreCompress/SessionStart)一起配上;VS Code Copilot建.vscode/mcp.json加.github/hooks/context-mode.json;Codex CLI和OpenCode同样有适配。非hook平台需要手动拷一份路由说明文件给模型看。

四、实测:官方二进制+真实生成

环境:官方 context-mode 1.0.169,Node 24 优先用内置 node:sqlite(避开 better-sqlite3 原生编译);生成侧用硅基流动DeepSeek V3跑问答,key走环境变量不落盘。

4.1 本地检索层(无需LLM)

我一开始把整篇文档当单 section 索引进去,检索无论如何都返回整篇;拆成多文件后,索引才按文件切成4个section,检索ERR_KV_OOM精准命中 doc-err.md,检索Llama-13B KV命中 doc-kv.md。这个坑说明它的chunk单位不是markdown标题,是文件——和RAG里常见的固定token分块不是一回事,后面有用。

索引落在一个本地SQLite库里(FTS5),路径在项目下的隐藏目录content里,按project维度分库,所以不同项目之间不会互相污染。还有个细节:我查Llama-13B KV时,除了doc-kv.md,doc-quant.md也被带回来了——因为它里面写了"KV量化"。BM25是词项匹配,不是语义匹配,同义词和改写会落空,这点跟向量检索的脾气完全不同。

4.2 端到端失真对比

我搭了个可复现的小实验:4 个技术文档当"长会话"语料(全文 601 token),把 compaction 模拟成只保留每个文件的标题行(27 token),再用官方检索注入 top-2 相关片段(平均 +268 token),同一个模型、同样 temperature = 0,回答 4 个只能靠深层细节才能答对的问题,然后数关键事实有没有出现在答案里。token 数是按字符数 / 1.6 估的,中英文混排的近似值,不是真实 tokenizer 结果。

  • 纯压缩:细节召回 1/10,且出现幻觉或拒绝回答(如 Llama-13B 参数那题,模型说"摘要中未提及该信息")。
  • 压缩+检索注入:细节召回 9/10,幻觉消失(FP8 那题模型回答"1.3-1.6 倍",与原文"1.3-1.6x"等价,按字面未计,人工核为命中)。
  • 净效果:上下文占用从 601 降到 27 + 268 约 295,仍比全文省约 51%,细节从几乎全丢变几乎全召回。

    结论很清楚:压缩省的是"骨架外的冗余",检索注入补的是"被压缩牺牲的细节"。两者咬合,才既省又准。

4.3 边界与局限

先说清楚这次没测到什么:沙箱执行那支(官方称 315KB 压到 5.4KB)和"用代码思考"那支我都没测,本文的数字只覆盖"检索注入"这一条路径,别把两支的省幅加在一起当总账。

再说这次测得的边界。样本只有4个问题、10个关键事实,量太小,只能说明机制成立,不能当召回率承诺。检索质量还卡在两个变量上:一是索引粒度,section按文件切,文件太大就会一次注入过多、把省下来的又吐回去,文件太碎又会丢上下文;二是query相关度,BM25认词不认义,问题换个说法就可能召回空。真实会话比我的合成语料脏得多——工具原始输出、报错栈、多轮指代都在里面,召回率大概率更低。真要落地,建议拿自己团队的一段真实长会话重跑一遍我这个脚本,别直接引用9/10这个数。

五、横向:它和谁不一样

很多项目都在解决"长上下文",但范式不同,别混为一谈。

vs 自带compaction(纯摘要):Claude Code、Codex这类客户端内置的compaction就是纯摘要,没有检索兜底,丢即丢。context-mode多一层FTS5,把"丢"变成"暂时收起、按需取回"。

vs 记忆层(本账号写过《Agent记忆层Hindsight》):记忆层通常是core/archive/recall三层抽象——core放常驻的当前状态和偏好,archive归档历史,recall负责检索召回,解决的是跨会话的长期记忆沉淀;context-mode是compaction时的本地FTS5检索注入,解决会话内/跨会话的"细节连续性"。一个管"长期记住你是谁",一个管"刚才聊的细节别漏"。定位不同,不是替代。

verdict:三者按场景选——纯摘要兜底最弱,只适合不在乎细节的短任务;长会话agent的上下文瘦身用context-mode;跨会话长期记忆沉淀用记忆层。我的做法是两样叠着用:记忆层存长期偏好和领域知识,context-mode管当下这一轮压缩后别丢细节;如果你的会话压根跨不了两次compaction,两个都不用开。

六、小结

context-mode的本质是"压缩+检索"双引擎:沙箱执行把工具原始数据挡在窗口外,FTS5把压缩掉的历史按需检回。实测证明它把"省 96% 上下文且细节几乎全丢"扭转为"省 51% 且细节几乎全召回"。原理不神秘,落地也便宜(本地SQLite,装完一条doctor命令就能验)。当然,这次用的是合成语料,真实生产数字会随任务长度和索引质量浮动,但它至少证明机制成立。我个人倾向在长会话agent里默认开着它——如果你的会话不到半小时就结束,开着没差别;一旦跨天或跨compaction,它才真正值。

做agent长期记忆的人,不妨顺手看本账号的《Agent记忆层Hindsight》——和本文是同一道题的两种解法。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询