首页
/ Goose 上下文压缩机制解析:compaction 提示词、结构化摘要与超窗重试策略

Goose 上下文压缩机制解析:compaction 提示词、结构化摘要与超窗重试策略

2026-09-05 10:06:24作者:昌雅子Ethen

当 Goose 会话触及模型上下文窗口上限时,系统并不会直接报错终止,而是通过 goose-context-management crate 将整段对话历史压缩(compaction)为一条结构化摘要消息,让会话可以跨越上下文窗口继续工作。本篇以该 crate 内嵌的核心提示词 compaction.md 为主体,完整拆解其任务定义、摘要 JSON Schema 与输出规则,并结合 summarize.rsstructured.rstemplates.rs 等源码,说明这条提示词在 Goose 压缩流水线中的真实调用位置、容错解析与超窗降级策略。

compaction.md:压缩提示词的任务设定

compaction.md 是压缩流程的“第一提示词”,它由 templates.rs 中的 include_dir! 宏在编译期打包进二进制(static PROMPTS: Dir = include_dir!("$CARGO_MANIFEST_DIR/src/prompts")),运行时由 builtin_template(COMPACTION_TEMPLATE) 取出。其内容可拆为三部分:

任务上下文(Task Context),原文完整保留了以下设定:

  • An llm context limit was reached when a user was in a working session with an agent (you)
  • Distill the conversation below into a structured summary with only the most verbose parts removed
  • Include user requests, your responses, all technical content, and as much of the original context as possible
  • This will be used to let the user continue the working session
  • The summary will be read by an agent (you) on a next exchange to allow for continuation of the session

要点在于“只删最啰嗦的部分,而不是重写”——摘要的第一读者是 Agent 本身(“you”),而非人类用户,因此它可以保留远比给人看的摘要更多的原始细节。

对话历史注入点:模板中的 {{ messages }} 占位符由 summarize.rs 填充——SummarizeContext { messages } 结构体携带序列化后的历史文本,经 minijinja 渲染(render(&templates.compaction, &context))后作为系统提示词发出,而用户侧消息只有一句固定的 SUMMARIZE_REQUEST_TEXT:"Please summarize the conversation history provided in the system prompt."。

推理草稿区(scratchpad):提示词要求模型先用 <analysis> 标签写时间线复盘(用户目标、方法、关键决策、文件、错误与修复),并明确警告"analysis 会被丢弃,所以保持简短,它只是清单而非细节的存放地"。这个设计与后文的 JSON 提取逻辑强耦合——structured.rsjson_candidates 正是以 </analysis> 终止符为锚点向后搜索摘要 JSON。

摘要 JSON Schema 与九个字段的完整语义

提示词要求:在 </analysis> 之后只输出一个 ```json 代码块,严格匹配以下 schema(此处为 compaction.md 原文的完整保留):

{
  "user_intent": ["every user goal and request, most important first"],
  "technical_concepts": ["all discussed tools, methods, and concepts"],
  "files": [
    {
      "path": "path of a file that was viewed or edited",
      "summary": "what was done to it and why",
      "key_code": "important code, signatures, or diffs from this file (omit if none)"
    }
  ],
  "errors_and_fixes": ["bugs hit, their resolutions, and user-driven changes"],
  "problem_solving": ["issues solved or in progress, and key decisions: what was chosen, what was rejected, and why"],
  "user_messages": ["all user messages, truncating long tool call arguments or results"],
  "pending_tasks": ["all unresolved user requests, most important first"],
  "current_work": "active work at summary request time: filenames, code, alignment to latest instruction",
  "next_step": "include only if it directly continues a user instruction, otherwise omit"
}

各字段的设计意图(从 structured.rsStructuredSummary 定义与注释可以印证):

字段 类型 设计意图
user_intent 字符串列表 每个用户目标,最重要的在前——“最重要的在前”意味着消费端可以安全地从尾部截断
technical_concepts 字符串列表 讨论过的所有工具、方法与概念
files FileActivity 对象列表 唯一允许嵌套对象的字段:路径、做了什么及为什么、关键代码/签名/diff
errors_and_fixes 字符串列表 遇到的 bug 与解决方式;提示词要求逐字引用错误信息、panic 文本与失败测试输出
problem_solving 字符串列表 已解决/进行中的问题与关键决策:选了什么、拒绝了什么、为什么
user_messages 字符串列表 全部用户消息,可截断冗长的工具调用参数与结果
pending_tasks 字符串列表 未完成的请求,最重要的在前
current_work 可选字符串 发起压缩时的活跃工作:文件名、代码、与最新指令的对应关系
next_step 可选字符串 仅在直接延续用户指令时包含,否则省略

提示词还给出了一组硬性规则,值得完整引用:

  • <analysis> 块是会被丢弃的草稿纸:只有 JSON 存活,因此 JSON 必须自包含,重复分析中所有对续作有意义的细节;
  • 每个列表按重要性从高到低排序;
  • files(对象数组)外,所有列表条目必须是纯字符串而非嵌套对象;
  • errors_and_fixes 中逐字引用错误消息、panic 文本与失败测试输出——包括数字、标识符与路径的精确字符串,不许意译;
  • 摘要只会被 Agent 自己读取,因此可以比给人看的摘要长得多:把整个长度预算花在 JSON 字段上,大量引用——完整输出块、完整代码片段、用户原话;
  • 不要省略任何对继续会话可能重要的信息;
  • 字段宁缺毋滥,不要为字段编造内容;
  • 除非用户确认,不要引入新想法。

消息如何进入提示词:format_message_for_compacting

进入 {{ messages }} 之前,每条 Message 先经 format.rsformat_message_for_compacting 压成纯文本。从源码看,其映射规则是:

  • 文本内容原样保留;图片渲染为 [image: {mime}];文档渲染为 [document: name (mime)]
  • 工具调用渲染为 tool_request({name}): {arguments_json}(参数序列化失败时回退为 <<invalid json>>);
  • 工具结果渲染为 tool_response: {文本拼接},无文本内容时为 tool_response: [non-text content]
  • 权限确认、elicitation 等 action_required 状态、系统通知与错误消息各自有对应的文本形式;
  • ThinkingRedactedThinking 内容被直接丢弃MessageContent::Thinking(_) => None)——推理链对续作会话没有可复用价值,去掉可显著节省 token。

最终每条消息形如 [user]: ...[assistant]: ...,空消息渲染为 [role]: <empty message>,全部以换行拼接后注入模板。

摘要的解析与容错:lenient 反序列化 + 无损失回退

模型并非总能严格遵守 schema。structured.rs 用两层防线保证"压缩永不丢信息":

第一层:宽松反序列化。 StructuredSummary 的每个字段都挂有自定义 deserializer:lenient_string_listlenient_stringlenient_string_optlenient_file_list。注释解释了动机——"模型经常会扩充 schema(例如把 errors_and_fixes 写成 {"error": .., "fix": ..} 对象),单个字段如此不应导致整个好摘要被丢弃"。对象被拼接为 k: v; k: v 形式的字符串、数组被分号连接、数字转字符串;files 里如果模型"过度遵守纯字符串规则"输出了字符串,则降级为只有 path 的 FileActivity 而不是丢弃。未知顶层字段通过 #[serde(flatten)] extra 保留,以便用户自定义提示词新增的字段仍能到达自定义渲染模板。

第二层:JSON 提取策略。 json_candidates 按优先级生成候选序列:以每个 </analysis> 终止符(从后往前)为切点,其后每个 json 围栏(从后往前)再到起始对象;候选必须"紧贴标记且花括号配平"。源码注释解释了为什么不用简单的 `rfind`:摘要 JSON 本身可能引用 `</analysis>` 字符串(例如会话正在编辑压缩提示词的场景),这种候选只有在包含其后所有终止符出现时才被接受。提取用花括号配平而非围栏边界,是因为字符串值里可以合法地包含 ;而被截断的半成品 JSON 永不修复——修复会丢掉结尾的、对续作最关键的章节,而保留原始文本的回退不会。

兜底:raw-text 回退。 summarize.rsapply_structured_summary 的注释直白写明:当模型没遵守结构化输出格式(不认 schema 的模型、用户自定义提示词)时,StructuredSummary::parse 返回 None,原始响应文本被原样保留作为摘要——即无损回退。测试 unusable_responses_fall_back_to_raw_text 覆盖了自由文体、空对象 {}、仅含未知字段、JSON 中途截断、JSON 仅被引用于散文、草稿区内嵌示例 JSON 等 9 种不可用响应,全部断言回退为 raw text。

压缩主流程:summarize 的超窗重试阶梯

summarize.rs 中的 summarize 是整条流水线的核心,返回 Summary { message, usage }。其流程:

  1. 构造请求:用户侧消息只含 SUMMARIZE_REQUEST_TEXT,全部历史经 format_message_for_compacting 序列化后渲染进系统提示词;
  2. 超窗重试阶梯:定义 REMOVAL_PERCENTAGES: [u32; 5] = [0, 10, 20, 50, 100]。若模型返回 ProviderError::ContextLengthExceeded(连摘要器自己都超窗),则依次移除 10%、20%、50%、100% 的工具响应后重试。filter_tool_responses 的删除策略是从中间向两端交替摘除工具响应消息——"从上下文最不可能重要的中间位置开始删",且每轮至少删 1 条;
  3. 失败出口:若历史中根本没有工具响应可删,第一次失败即快速返回,错误信息会给出可操作建议——"换用可用上下文更大的模型或配置、禁用部分 extension 以减小工具 schema 负载、或开新会话";若有工具响应但删完 100% 仍超窗,则报 "context limit exceeded even after removing all tool responses"。summarize.rs 的单元测试 用一个永远报 ContextLengthExceededOverflowingModel 验证了这两条路径:无工具响应时只发 1 次请求即失败,有工具响应时恰好发 5 次(REMOVAL_PERCENTAGES.len())请求后耗尽。
  4. usage 归一化:压缩调用本身也消耗 token,ensure_usage_tokens 在响应被改写为更小的渲染摘要之前TokenEstimator 补齐 input/output tokens——注释强调 usage 必须反映"可计费的原始模型输出"。

成功后,摘要消息的 role 被强制改写为 Userresponse.role = Role::User),以便它以一条用户消息的身份接回原会话历史。

渲染层:compaction_summary.md 与 code_fence 过滤器

结构化摘要最终要变成一条人类可读、Agent 可续读的 Markdown。这一步由 compaction_summary.md 模板完成——它是 minijinja 模板(启用 trim_blocks/lstrip_blocks),按九个字段依次渲染 ## User Intent## Technical Concepts## Files + Code## Errors + Fixes## Problem Solving## User Messages## Pending Tasks## Current Work## Next Step 各节,空字段整节省略。

模板头部注释明确说明它是用户可覆盖的:把修改后的副本放到 ~/.config/goose/prompts/compaction_summary.md 即可实验压缩后上下文的内容(例如用 user_intent[:3] 只保留前三条最重要目标),无需重新构建 goose。模板中 key_code 字段经过 code_fence 过滤器处理,该过滤器(templates.rscode_fence 函数)统计代码串内最长反引号连续段,生成比其长 1(且至少 3 个)反引号的围栏,防止嵌入的代码块"逃逸"破坏外层围栏。测试 render_fences_exceed_backtick_runs_in_key_code 验证了含 ```` 的 key_code 会被五反引号围栏包裹。

上层 API:compact、CompactingProvider 与压缩阈值

lib.rs 暴露三层 API,compaction.md 提示词贯穿始终:

  • summarize:最小层——给定模型与消息切片,产出一条摘要消息;
  • compact:trait 层,供拥有自己会话表示的调用方使用。CompactionInput 负责提供消息(可选覆盖 Templates),CompactionOutput 负责接收摘要与 usage;Vec<Message> 已内置实现 CompactionInput,简单场景无需包装类型。此 trait API 为 Rust 独有;
  • CompactingProviderprovider.rs 中的 Provider 装饰器,声明 manages_own_context() == true。它在 streamcomplete 中拦截 ProviderError::ContextLengthExceeded,自动调用 summarize 把整段历史压成一条摘要消息,然后用摘要替换原历史重试一次,而不是把错误抛给上层。

此外 README 说明了默认压缩阈值:DEFAULT_COMPACTION_THRESHOLD(0.8)是调用方开始压缩时上下文窗口的默认占比。Python 与 Kotlin 侧通过 goose-sdk 的 UniFFI 绑定访问压缩能力,Templatescompaction + summary 两段提示词文本)允许调用方整体替换内置提示词——这正是 compaction.md 与 compaction_summary.md 分离成两个独立字段的原因:前者控制"怎么总结",后者控制"总结之后长什么样"。

小结

Goose 的上下文压缩机制中,compaction.md 定义了"面向 Agent 自身"的信息保全型摘要契约:九字段 JSON、重要性排序、逐字引用错误、analysis 仅作草稿;而 structured.rs 的宽松解析与 raw-text 回退、summarize.rs 的 0/10/20/50/100% 工具响应重试阶梯、provider.rs 的超窗自动重试,共同保证这条提示词在模型不守格式或历史超长时依然不会丢失会话信息。阅读这两份提示词模板(以及 ~/.config/goose/prompts/compaction_summary.md 的用户覆盖位)是理解并定制 Goose 长会话延续行为的最直接入口。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384