上下文压缩 · 满了怎么办
上下文窗口会满。grok 的压缩不是「留个尾巴」,而是把整盘对话总结成一份九段式结构化摘要、再重建一份全新历史。这一章看它的触发、摘要提示、清洗、重组,以及那道防「压不动」的输入梯子。
上下文压缩 · 满了怎么办
再长的上下文窗口也会满。满了还硬塞,要么报错要么烧钱。grok 的应对是压缩——策略颇为激进,值得细看。
触发:用量过 85%
策略在 xai-grok-agent/src/compaction.rs 的 CompactionPolicy:
auto_compact_threshold_percent默认 85——估算用量(总 token / 上下文窗口)达到 85% 就自动压(这是代码默认值,环境变量、用户配置或远端按模型下发的设置都能覆盖);wall_clock_budget_secs默认 300——单次压缩的墙钟预算,超了就掐掉重试(防推理跑飞);memory_flush_enabled、two_pass_enabled、compact_model——见下。
主循环在每圈采样前调 check_auto_compact_needed(),工具执行后再调一次 check_preflight_overflow() 兜底(两者都在 xai-grok-shell/src/session/compaction.rs)。
策略:全量替换,不留尾巴
多数 agent 压缩是「留最近 N 条 + 摘要前面」。grok 走的是 full-replace(xai-grok-compaction/src/code_compaction/):把整盘对话总结掉,重建一份全新历史。流程:
构建提示 → 采样(重试+分类) → 清洗摘要 → 重组历史九段式结构化摘要
摘要提示(templates/full_replace_summary_prompt.txt)不是「随便总结一下」,而是要模型产出九个固定段落:
- 主要请求与意图 · 2. 关键技术概念 · 3. 文件与代码片段(最近的编辑贴全) · 4. 错误与修复 · 5. 问题求解 · 6. 全部用户消息(明确排除「总结指令」这条本身) · 7. 待办任务 · 8. 当前工作 · 9. 可选下一步(带原话引用防漂移)。
还有条硬规矩:把之前的压缩摘要也带进来,保证跨多次压缩不丢信息。模型要先在私下推理里想清楚、不单独输出分析草稿,最终只交一个 <summary>…</summary> 块。
眼熟吗?这套九段式结构,和很多长会话 agent(包括你正在用的这类工具)「续上一段对话」时的摘要格式几乎同构——它是把「一盘长对话压成不丢关键信息的短摘要」这件事工程化的成熟答案。
清洗与重组
模型的输出不能直接用,summary.rs 要清洗:剥掉开头的 <analysis> 草稿(提示词虽然不让写,还是得防)、把外层 <summary> 转成 Summary: 前缀、给回显的控制标记后插零宽空格(防摘要「教」下一轮再吐一个块)。还有个 is_degenerate_summary 检查:清洗后不足 500 字符就当成瞬时失败重试(源码注释说,线上见过最短的健康摘要也有约 3,200 字符)。
重组(assemble.rs)把历史拼回一个规范形状:
[System, UserMeta(前缀), ProjectInstructions(AGENTS.md), User(最后的 query), 最近尾巴…, UserMeta(摘要+回捞指针), SystemReminder]注意 AGENTS.md 是原文重新注入的(不交给摘要器转述),最近的尾巴放在摘要前——既有「全局记忆」又有「近期工作」。
回捞指针:压掉的历史去哪了
摘要末尾那段「回捞指针」指向哪,由压缩模式决定(features.compaction_mode 或环境变量 GROK_COMPACTION_MODE):默认的 segments 会把被压掉的历史按段写成会话目录下的 compaction/segment_*.md,另附一份 INDEX.md 目录(markdown 渲染在新拆出的 xai-compaction-transcript crate),并在摘要里提示模型:需要细节就用 read_file、grep 回查,别改这些文件;transcript 模式改为指向原始的 updates.jsonl;summary 模式则不留指针。
防「压不动」的输入梯子
一个魔鬼细节:如果对话已经大到连总结请求本身都超上下文怎么办?grok 有一道输入梯子(InputStage,在 xai-grok-shell/src/session/compaction.rs):Verbatim(逐字)→ VerbatimFitted(裁到预算)→ Lossy(有损)。压缩请求因上下文溢出失败时,host 不是放弃,而是降一级重建更小的输入再试——这就是防止会话进入「压不动的死状态」的机关。
另外两招:压缩前先 memory flush(memory_flush_enabled,把该记的持久知识先存下再丢历史,见记忆);两遍预压缩(two_pass_enabled,用量接近阈值时先在后台把历史前缀总结好,真压缩时只补最近尾巴)。
和《从 LLM 到 Coding Agent》对照
对照 context-compaction 与 auto-compaction:最小实现讲清了「为什么要压、怎么触发」。grok 的增量是把它做成了一条带结构化摘要、退化检测、控制符清洗、输入梯子、记忆联动、两遍预压的完整产线——每一环都在回答「怎么压得又不丢信息、又不会自己卡死」。
上一篇:system prompt 怎么组装 · 下一篇:记忆 · 跨会话记住东西