goose-context-management:为 Goose 打造的分层会话压缩与长对话续写方案
发布时间:2026/9/10 6:47:42来源:尧图网络
goose-context-management为 Goose 打造的分层会话压缩与长对话续写方案【免费下载链接】goosean open source, extensible AI agent that goes beyond code suggestions - install, execute, edit, and test with any LLM项目地址: https://gitcode.com/GitHub_Trending/goose3/goosecrates/goose-context-management是开源 AI 智能体项目 Goose仓库根目录中专门负责**会话压缩Conversation Compaction**的独立 crate。它的目标非常聚焦把一长段消息历史归纳为一条摘要消息让 Agent 的对话能够在单个模型上下文窗口被占满之后继续下去。本文以该 crate 的官方 README 为主体结合其源码与集成代码讲解其分层 API、结构化摘要格式、自动重试机制以及在 Goose 会话中的真实落地方式。读完本文你将能理解 Goose 会话“越过上下文窗口续跑”的完整原理并能在自己的 Rust 项目中直接使用summarize、compact或CompactingProvider三层接口之一。问题背景为什么要“压缩”而不是“丢弃”大语言模型的每次推理都有上下文窗口上限而一个真实的工作会话例如让 Goose 修改代码、执行命令、读写多个文件会产生大量消息用户指令、Agent 的思考与回复、成批的工具调用及其结果、错误与修复过程等。这些消息会持续累积很快逼近甚至超过窗口上限。直接的解决办法是丢弃旧消息但代价是丢失关键上下文用户意图、文件改动、排错结论后续会话无法自然延续。goose-context-management采用另一种思路把历史对话“蒸馏”成一条承载所有关键信息的摘要消息由 Agent 在下一轮交流中阅读这条摘要来续接会话。从 crate 源码的第一段文档注释即可看到这一设计意图//! Conversation compaction: summarizing a message history down to a single //! message so a conversation can continue past a models context window.该 crate 的一个设计原则是按层划分、从小到大layered, smallest first需要哪一层能力就用哪一层不必引入多余抽象。分层后的全部公共 API 都在 lib.rs 中导出pub use format::format_message_for_compacting; pub use model::{CompactionModel, ProviderModel, TokenEstimator}; pub use provider::CompactingProvider; pub use structured::{FileActivity, StructuredSummary}; pub use summarize::{summarize, Summary}; pub use templates::Templates;下面依次剖析这三层。第一层summarize—— 一次调用产出摘要summarize是最小、最直接的 API给定一个压缩模型和一批消息产出一条摘要消息。README 给出了完整用法use goose_context_management::{summarize, Templates}; let summary summarize(model, None, Templates::default(), messages).await?; // summary.message, summary.usage返回的Summary结构体定义在 summarize.rs包含两个字段pub struct Summary { pub message: Message, // 摘要消息角色被改写为 user pub usage: ProviderUsage, // 本次摘要调用的 token 用量 }从源码可以确认几个值得注意的实现细节摘要请求的用户消息是固定的Please summarize the conversation history provided in the system prompt.而真正的历史内容放在**系统提示system prompt**里由模板引擎渲染。摘要消息的 role 会被强制改为Role::User这样它可以作为下一条普通对话消息进入后续轮次。usage 统计先于摘要改写模型原始输出可计费 token被记录下来之后摘要正文才会被改写为渲染后的结构化版本从而保证usage反映真实的可计费 token 数见 summarize.rs。超长历史的自动分级重试summarize的调用本身也需要消耗上下文——历史太长时连摘要模型都可能超窗。源码为此内置了一套渐进式重试策略const REMOVAL_PERCENTAGES: [u32; 5] [0, 10, 20, 50, 100];流程summarize.rs第一次尝试用完整历史移除 0%发起摘要请求。若模型返回ProviderError::ContextLengthExceeded则按10% → 20% → 50% → 100%的比例逐步移除**工具响应tool response**后重试。移除策略是“从中间向外”middle outwards逐条剔除工具响应消息因为历史中部的工具响应是最不可能影响续接的信息实现见filter_tool_responses。如果历史中根本没有工具响应可移除则直接快速失败并给出可操作建议换用更大的可用上下文、禁用部分扩展以减少工具 schema 体积或开启新会话。若移除全部工具响应后仍然超窗返回“即使移除所有工具响应仍超限”的错误。对应测试位于同一文件的mod testssummarize_without_tool_responses_fails_fast验证无工具响应时只发一次请求并快速失败summarize_with_tool_responses_preserves_exhausted_removal_error验证有工具响应时会完整走完 5 次重试summarize.rs。第二层compact—— trait 化的抽象 APIcompact是面向**自持会话表示own conversation representation**的调用方设计 trait API。调用方只需让自己的会话类型实现两个 trait就可以把“读取历史、回写摘要”的细节交给 crate 处理。两个 trait 定义在 lib.rspub trait CompactionInput { fn messages(self) - VecMessage; fn templates(self) - Templates { Templates::default() } } pub trait CompactionOutput { fn set_summary(mut self, summary: Message); fn set_usage(mut self, usage: ProviderUsage); }CompactionInput告诉压缩器“从哪读”。messages()提供完整历史templates()可覆盖默认提示模板有默认实现无需强制覆盖。CompactionOutput告诉压缩器“往哪写”。压缩完成后set_summary收到摘要消息、set_usage收到用量信息。compact的泛型实现lib.rs只是把输入取出、调用summarize、再把结果写回输出pub async fn compactI, O(model, estimator, input, output) - Result() where I: CompactionInput ?Sized, O: CompactionOutput ?Sized,为方便最简单场景crate 已经为VecMessage免费实现了CompactionInputimpl CompactionInput for VecMessage { fn messages(self) - VecMessage { self.clone() } }因此如果调用方恰好就是用VecMessage存历史直接把它当作输入即可无需包装类型。注意compact是基于 trait 的 API目前仅限 Rust 使用。其他核心导出模型抽象、Token 估算与 Provider 包装除两层主 API 外crate 还导出了一组可独立使用的构件。CompactionModel与ProviderModelCompactionModel是压缩运行所依赖的模型抽象model.rs只要求实现一次对话补全#[async_trait] pub trait CompactionModel: Send Sync { async fn complete( self, system: str, messages: [Message], ) - Result(Message, ProviderUsage), ProviderError; }实现方自己决定模型选择、fallback 与会话管道session plumbing——这意味着压缩可以复用 Goose 主程序里的会话记账与容错逻辑。ProviderModel是这个 trait 的开箱即用实现它把任何实现了goose-providers中Providertrait 的 provider 适配为CompactionModelpub struct ProviderModel { provider: Arcdyn Provider, model_config: ModelConfig, }其complete实现只是把调用转发给底层Provider::completemodel.rs。TokenEstimatorTokenEstimator用于可选的 token 计数model.rs它回答“应该把多少历史喂给摘要器”。提供两个异步方法pub trait TokenEstimator: Send Sync { async fn count_chat_tokens(self, system: str, messages: [Message]) - usize; async fn count_text_tokens(self, text: str) - usize; }在summarize内部当 provider 返回的 usage 缺少输入/输出 token 时会调用 estimator 补全ensure_usage_tokens见 summarize.rs。也就是说即使 provider 不报告 token 用量调用方也能通过 estimator 获得完整、可计费的用量统计。CompactingProvider—— 自动压缩的 Provider 包装器CompactingProvider是更上层的“无人值守”方案包装一个Provider一旦底层补全因ContextLengthExceeded失败就自动压缩历史并带摘要重试一次provider.rspub struct CompactingProvider { inner: Arcdyn Provider, templates: Templates, }其Provider实现覆盖stream与complete两条路径核心逻辑一致provider.rsmatch self.inner.stream(model_config, system, messages, tools).await { Err(ProviderError::ContextLengthExceeded(_)) { let compacted self.compacted_messages(model_config, messages).await?; self.inner.stream(model_config, system, compacted, tools).await } other other, }也就是说正常调用直接透传只有超窗错误才触发“先summarize成单条摘要再以摘要替换原历史重试”。值得注意的一点CompactingProvider::manages_own_context()固定返回true向调用方声明“上下文由我自理”从而避免上层再做额外的阈值判断。提示模板与结构化摘要Templates与StructuredSummary压缩质量的好坏很大程度上取决于提示词与摘要格式的设计这也是该 crate 最有特色的部分。Templates可替换的提示模板Templates结构包含两个模板字符串templates.rspub struct Templates { pub compaction: String, // 压缩系统提示compaction.md pub summary: String, // 摘要渲染模板compaction_summary.md }内置模板通过include_dir!在编译期打包进二进制路径为 src/prompts/compaction.md 与 src/prompts/compaction_summary.md因此运行时无需外部文件。渲染引擎是minijinjaJinja 语法的 Rust 实现并注册了code_fence过滤器用于把代码片段包进“反引号长度自动加一”的安全围栏防止key_code中嵌套的反引号破坏 Markdown 结构见 templates.rs。compaction.md是发给摘要模型的系统提示它把上下文压缩任务定义为“按给定 JSON schema 输出一条可续接会话的摘要”并明确要求先在analysis标签内按时间顺序梳理用户目标、方法、关键决策、文件、错误与修复这段 scratchpad最终会被丢弃只用于引导思考在/analysis之后只输出一个 json 代码块严格匹配给定字段 schema列表按“重要度从高到低”排序errors_and_fixes中的报错文本、panic 内容、失败测试输出必须逐字引用而非转述摘要仅供 Agent 自己阅读因此可以远超给人看的普通摘要长度把整个长度预算花在 JSON 字段上。compaction_summary.md则是把结构化 JSON渲染为易读 Markdown 摘要的模板输出包含## User Intent、## Files Code、## Errors Fixes、## Pending Tasks、## Current Work等分节。它在文件头部注释里说明了重要的可扩展性设计This template is user-overridable: place a modified copy at ~/.config/goose/prompts/compaction_summary.md to experiment with what the post-compaction context contains (e.g. user_intent[:3] to keep only the three most important goals) without rebuilding goose.即用户可以在不重新编译 goose 的前提下把修改版模板放到~/.config/goose/prompts/compaction_summary.md从而控制压缩后上下文里保留什么例如只保留最重要的三个用户目标。StructuredSummary容忍模型“不听话”的宽松解析StructuredSummary定义了结构化摘要的数据模型structured.rs。每个列表字段都遵循“最重要在前”的排序约定以便消费方可以从尾部截断。字段如下字段类型含义user_intentVecString每个用户目标与请求最重要在前technical_conceptsVecString讨论到的工具、方法与概念filesVecFileActivity查看或编辑过的文件活动errors_and_fixesVecString遇到的 bug、解决方式与用户驱动的改动problem_solvingVecString已解决/进行中的问题与关键决策user_messagesVecString所有用户消息超长工具参数可截断pending_tasksVecString所有未解决的用户请求最重要在前current_workOptionString摘要请求时刻进行中的工作next_stepOptionString直接延续用户指令的下一步否则省略FileActivity包含path、summary与可选的key_code重要代码、签名或 diff。设计上对“模型不按 schema 输出”的情况非常宽容所有字段宽松反序列化——缺省字段为空、对象或数字被转成字符串也不报错因为模型经常在字段里塞入{error: ..., fix: ...}这类富化结构不能因单个字段不合规就丢弃整个好摘要。files字段若模型输出成纯字符串也会被当作 path-only 活动处理而非丢弃相关测试见file_entries_parse_leniently与lenient_shapes_are_stringified_not_rejected。源码中还有一个极为考究的细节模型响应里的 JSON 需要被可靠提取但响应可能包含analysisscratchpad、被引用的示例 JSON、字符串值内嵌的代码围栏、甚至被摘要内容自己引用的/analysis字样。json_candidates采用“按多个候选依次尝试 花括号配平”的提取策略structured.rs并在任何候选都不可用时回退为保留模型原始文本无损 fallback绝不为了追求结构化而丢弃信息。文件内大量测试如unusable_responses_fall_back_to_raw_text、quoted_terminator_inside_summary_json_does_not_hide_it、embedded_fences_in_string_values_do_not_break_extraction逐条验证了这些边界场景。apply_structured_summarysummarize.rs负责执行“解析 → 渲染”这一步只有当结构化解析成功、且渲染结果非空时才用渲染文本覆盖原始响应任何失败模型未按 schema、模板被改坏、渲染出错都只会告警并保留原始输出保证信息零丢失。在 Goose 主程序中的落地阈值、可见性与续接消息goose-context-management不只是独立的可复用 crate它已被 Goose 主会话逻辑深度集成。集成点集中在 crates/goose/src/context_mgmt/mod.rs几个关键事实如下。默认压缩阈值crate 导出DEFAULT_COMPACTION_THRESHOLD 0.8lib.rs表示当会话 token 用量达到上下文窗口的 80% 时触发自动压缩。Goose 主程序将其再导出并允许通过环境变量GOOSE_AUTO_COMPACT_THRESHOLD覆盖check_if_compaction_needed见 context_mgmt/mod.rs当阈值小于等于 0 或大于等于 1 时自动压缩被禁用。若 provider 声明manages_own_context()则跳过阈值判断直接返回不需要压缩。压缩后会话的“可见性”分层compact_messagescontext_mgmt/mod.rs执行完整压缩后会重建会话消息列表并做精细的可见性划分原始历史消息变为user 可见但 agent 不可见with_agent_invisible()保留给用户回看摘要消息与一条“续接引导”assistant 消息变为agent-only紧邻的用户消息被原样保留在会话中自动压缩时确保用户当前正在进行的请求不丢失续接消息文案随场景切换普通对话续接、工具循环续接、用户手动压缩manual compact各有对应的提示文本见CONVERSATION_CONTINUATION_TEXT、TOOL_LOOP_CONTINUATION_TEXT、MANUAL_COMPACT_CONTINUATION_TEXT。usage 与 retained contextCompactionResult同时返回usage摘要调用真实可计费 token即使输出被改写为渲染版也不打折与retained_context_tokens压缩后 agent 可见上下文的估算 token通常远小于可计费输出。相关单元测试如test_structured_summary_is_rendered断言渲染后的摘要不再包含json与analysisscratchpad 片段同时output_tokens仍然存在。主程序还提供了format_message_for_compacting与Templates的替换路径compaction_templates通过crate::prompt_template::template_source加载使 goose 运行时也能应用用户自定义模板。消息如何被“压扁”给摘要模型format_message_for_compacting在把历史喂给摘要模型之前每条消息都要被规整为便于模型读取的纯文本形式这个职责由format_message_for_compacting承担format.rs。它的转换规则如下文本Text→ 原文图片 →[image: {mime_type}]不传像素只传类型标记文档 →[document: {name} ({mime_type})]工具请求 →tool_request({name}): {参数 JSON}工具响应 →tool_response: {文本内容}无文本时标记[non-text content]出错时标记[error]工具确认请求、action_requiredtool_confirmation / elicitation / 对应响应、系统通知、错误消息均有各自的紧凑文本Thinking与RedactedThinking推理过程被直接丢弃因为压缩不需要推理草稿。每条消息最终被格式化为[{role}]: {内容}role只有user/assistant两种多条消息以换行拼接后注入摘要系统提示的{{ messages }}占位处。这保证了摘要模型看到的输入是“扁平、文本化、信息完整”的会话记录。跨语言访问Python 与 Kotlingoose-context-management本身是纯 Rust crate但 Python 与 Kotlin 的调用方通过goose-sdk的UniFFI 绑定访问压缩能力见 lib.rs 与 README“Cross-language access”一节。需要特别说明的是基于 trait 的compactAPI仅限 Rust跨语言暴露的是更简单的函数式入口summarize及其返回结构。相关绑定源码位于 crates/goose-sdk/src/bindings.rs其余依赖定义在该 crate 的 Cargo.toml依赖goose-providers、minijinja、rmcp、serde、anyhow等。如何在自己的项目中使用goose-context-management作为 workspace 成员发布版本为0.1.0-alpha.7见 Cargo.toml。选用哪一层取决于你的集成深度你只需一次摘要调用summarize(model, estimator, Templates::default(), messages)从返回的Summary中取message与usage。你拥有自己的会话表示让你的类型实现CompactionInput/CompactionOutput再调用compact(...)由 crate 完成“读取历史 → 生成摘要 → 回写结果”的全流程。你不关心压缩时机只要“别让我超窗”用CompactingProvider::new(inner)必要时.with_templates(templates)包一层 provider超窗自动压缩重试。你想调节行为通过Templates { compaction, summary }自定义模板内置模板打包在 src/prompts或提供TokenEstimator让 usage 统计不依赖 provider 上报。作为参考Goose 主程序选择的是最彻底的方案实现CompactionModel/TokenEstimator复用会话管道与 token 计数器、按GOOSE_AUTO_COMPACT_THRESHOLD默认 0.8判断是否需要压缩、压缩后精细管理消息可见性并追加续接引导消息。整个调用链贯穿 context_mgmt/mod.rs 与goose-context-management内部实现两者共同构成了 Goose “长会话不停摆”的底层保障。小结goose-context-management的价值在于把“越过上下文窗口续跑会话”这一复杂工程问题收敛为三个边界清晰、可逐层选用的抽象函数层summarize一次调用产出摘要与用量trait 层compact解耦“读历史/写回”与会话表示provider 层CompactingProvider透明拦截超窗错误并自动重试。支撑它们的是高质量的提示工程内置 minijinja 模板、可用户覆盖、对模型输出高度宽容的结构化解析多候选 JSON 提取 无损原文回退、以及按比例“从中间向外”剔除工具响应的分级重试策略。这些细节共同保证了压缩不只是“省 token”更是以最小信息损失延续工作会话——这也正是 Goose 作为可长时间自主工作的 AI Agent 的关键技术底座之一。【免费下载链接】goosean open source, extensible AI agent that goes beyond code suggestions - install, execute, edit, and test with any LLM项目地址: https://gitcode.com/GitHub_Trending/goose3/goose创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网