☰
forgecode 智能体上下文自动压缩(Agent Context Compaction)实现方案与源码剖析
2026/9/28 2:32:30 网站建设 项目流程
  • 人工智能
  • AI Agent
  • 代码智能体
  • AI 应用
  • CLI
  • 开发工具

【免费下载链接】forgecode

AI enabled pair programmer for Claude, GPT, O Series, Grok, Deepseek, Gemini and 300+ models

项目地址:https://gitcode.com/gh_mirrors/forge39/forgecode
点击查看免费下载

本文以 plans/agent-context-compaction-2025-03-24.md 实现方案为骨架,结合 forgecode(AI enabled pair programmer for Claude、GPT、Grok、Deepseek、Gemini 等 300+ 模型)仓库中的实际源码与测试,完整讲解上下文自动压缩的设计目标、配置结构、触发判定、执行链路与迁移路径。读完本文,你将掌握如何通过Agent.compact配置为智能体开启基于 token 数、轮数、消息数的自动上下文压缩,并理解其底层实现原理与调试手段。

一、背景与目标:为什么需要上下文自动压缩

在长会话的 AI Agent 应用中,每一轮对话都会把历史消息、工具调用与工具结果持续追加进上下文。随着会话推进,上下文不断膨胀,会带来三个直接后果:

  1. Token 成本持续上升:每轮请求都会把全部历史发送给模型,输入 token 线性增长;
  2. 超出模型上下文窗口:一旦超过模型 context window,会触发context_length_exceeded类错误,导致会话中断;
  3. 指令遵循质量下降:过长的上下文中,早期系统约束与用户意图容易被淹没。

为此,该实现方案提出:在Agent结构上新增一个字段,使上下文压缩能够基于可配置的触发条件(token 数量、轮数、消息数量)自动执行,并受一个最大 token 上限约束。目标是以一种“灵活、自动”的方式替代原先更复杂的基于 transform 的压缩方案。

从当前仓库代码看,这一设计已经落地为核心配置类型Compact(位于 crates/forge_domain/src/compact/compact_config.rs),并被Agent结构持有(见 crates/forge_domain/src/agent.rs)。

二、配置结构设计:Compaction / Compact 字段全解

2.1 方案文档中的原型设计

方案文档首先定义了Compaction结构体,包含三个可选触发阈值与一个必填的压缩后上限:

/// Configuration for automatic context compaction #[derive(Debug, Clone, Serialize, Deserialize, Setters)] #[setters(strip_option, into)] pub struct Compaction { /// Maximum token count before compaction is triggered #[serde(skip_serializing_if = "Option::is_none")] pub token_threshold: Option<usize>, /// Maximum number of turns before compaction is triggered #[serde(skip_serializing_if = "Option::is_none")] pub turn_threshold: Option<usize>, /// Maximum number of messages before compaction is triggered #[serde(skip_serializing_if = "Option::is_none")] pub message_threshold: Option<usize>, /// Maximum allowed token count after compaction pub max_tokens: usize, }

并配套提供new(max_tokens)构造函数与should_compact(context, turn_count, message_count)判定方法——任一阈值被满足即返回true,否则返回false。

2.2 仓库中的最终实现:Compact结构体

实际仓库将配置结构命名为Compact,字段比方案原型更丰富,增加了eviction_window、retention_window、token_threshold_percentage、model、on_turn_end等控制项:

字段类型默认值说明
retention_windowusize0压缩时保留最近 N 条消息,不参与摘要;与eviction_window取更保守值(可压缩消息更少者优先)
eviction_windowf640.2可被摘要化的上下文比例上限,取值范围0.0~1.0,0.0表示不压缩,1.0允许全部摘要
max_tokensOption<usize>None压缩后保留的最大 token 数
token_thresholdOption<usize>None触发压缩的绝对 token 上限,与token_threshold_percentage取较小值
token_threshold_percentageOption<f64>None以模型上下文窗口百分比推导的触发阈值,与token_threshold取较小值
turn_thresholdOption<usize>None触发压缩的最大对话轮数
message_thresholdOption<usize>None触发压缩的最大消息数
modelOption<ModelId>None执行压缩使用的模型 ID(可用更便宜/更快的模型);不设置则回退到 Agent 根级模型
on_turn_endOption<bool>None当最后一条消息来自用户时触发压缩

值得注意的默认行为:Compact::new()将eviction_window初始化为0.2(即默认压缩 20% 的上下文),其余触发阈值均为None(表示不触发)。百分比字段在反序列化时会被校验,非法值(超出0.0~1.0)会直接报错,例如"percentage must be between 0.0 and 1.0, got 1.5"——这一校验逻辑见compact_config.rs中的deserialize_percentage与deserialize_optional_percentage。

2.3 触发判定:should_compact的四个维度

实际实现中should_compact的签名与方案原型不同:不再由调用方传入turn_count和message_count,而是直接从Context内部推导(源码见 compact_config.rs):

pub fn should_compact(&self, context: &Context, token_count: usize) -> bool { self.should_compact_due_to_tokens(token_count) || self.should_compact_due_to_turns(context) || self.should_compact_due_to_messages(context) || self.should_compact_on_turn_end(context) }

四个维度为“或”关系,任一满足即触发:

  • Token 维度:token_count >= token_threshold(注意是>=,等于阈值即触发;未配置阈值则不触发);
  • 轮数维度:统计上下文中角色为 User 的消息条数>= turn_threshold——也就是说“轮数”按用户消息数计,系统消息与助手消息不计入;
  • 消息维度:context.messages.len() >= message_threshold,直接按总消息条数判定;
  • 回合结束维度:当on_turn_end == true且最后一条消息来自用户时触发,适用于“用户提问后立即压缩”的场景。

三、Agent 结构扩展:compact 字段与安全阈值

3.1 字段挂载

方案文档要求为Agent增加compact字段,并遵循已有的序列化与合并模式:

/// Configuration for automatic context compaction #[serde(skip_serializing_if = "Option::is_none")] #[merge(strategy = crate::merge::option)] pub compact: Option<Compaction>,

实际仓库中该字段已落地为非可选类型,默认值为Compact::default()(见 agent.rs 与Agent::new中的compact: Compact::default()):

/// Configuration for automatic context compaction pub compact: Compact,

Compact本身派生Merge,其中retention_window、eviction_window使用merge::std::overwrite策略,其余可选字段使用merge::option策略——即“对方有值则覆盖,对方无值则保留本值”,与方案测试中预期的合并语义一致。

3.2 安全阈值兜底:compaction_threshold

仅靠用户配置的绝对token_threshold并不安全:如果阈值与模型上下文窗口之间预留余量不足,上下文可能先于压缩触发而撑爆窗口。仓库在Agent上提供了compaction_threshold(selected_model)方法(agent.rs)做兜底:

  • 绝对上限来自compact.token_threshold,未配置时默认100,000tokens;
  • 上下文窗口上限来自compact.token_threshold_percentage,未配置时默认取模型上下文窗口的70%;模型元数据缺失时使用默认上下文窗口128K;
  • 最终取两者较小值写入compact.token_threshold,为工具输出与后续消息预留安全余量。

例如模型上下文窗口为 80K 时,即使配置了token_threshold: 100_000,实际阈值也会被压到80_000 × 0.7 = 56_000。该逻辑在 agent.rs 中有 9 个针对性测试用例覆盖,其中 3 个是修复历史缺陷的回归测试(见下文“测试验证”一节)。

3.3 Token 估算与模型回退

  • Agent::set_compact_model_if_none():若compact.model未设置,则复制 Agent 根级模型作为压缩模型(agent.rs);
  • estimate_token_count(count):按“约 4 个字符 ≈ 1 个 token”做粗略估算(count / 4),并注明生产环境应使用对应 LLM 的 tokenizer(agent.rs)。

四、触发与执行链路:从 Orchestrator 到 Hook + Compactor

4.1 方案文档中的 Orchestrator 设计

方案文档计划在Orchestrator中新增compact_context方法,并改造init_agent_with_event,在把上下文发送给 provider 之前完成压缩判定与执行:

async fn compact_context( &self, agent: &Agent, context: &mut Context ) -> anyhow::Result<()> { if let Some(config) = &agent.compact { let max_tokens = config.max_tokens; let mut summarize = Summarize::new(context, max_tokens); while let Some(mut summary) = summarize.summarize() { let content_to_summarize = summary.get(); // TODO: 未来增强可用 AI 模型生成更好的摘要 summary.set("Summary of previous conversation"); } } Ok(()) }

即:先在init_agent_with_event中读取会话turn_count与context.messages.len(),调用config.should_compact(...)判定,需要压缩则执行compact_context并把压缩后的上下文回写。

4.2 仓库中的实际实现:Hook + Compactor 服务

从当前源码结构看,最终实现把“触发”与“执行”解耦为两层:

第一层:触发钩子CompactionHandler(crates/forge_app/src/hooks/compaction.rs)

它实现EventHandle<EventData<ResponsePayload>>,在响应事件到达时检查会话上下文:

if let Some(context) = &conversation.context { let token_count = context.token_count(); if self.agent.compact.should_compact(context, *token_count) { info!(agent_id = %self.agent.id, "Compaction triggered by hook"); let compacted = Compactor::new(self.agent.compact.clone(), self.environment.clone()) .compact(context.clone(), false)?; conversation.context = Some(compacted); } else { debug!(agent_id = %self.agent.id, "Compaction not needed"); } }

第二层:执行器Compactor(crates/forge_app/src/compact.rs)

Compactor::compact(context, max)的核心流程为:

  1. 由eviction_window构造CompactionStrategy::evict(percentage),由retention_window构造CompactionStrategy::retain(n);
  2. 非强制模式(max == false)取两者较小值eviction.min(retention)(更保守);强制模式取retention;
  3. 调用strategy.eviction_range(&context)定位需要压缩的消息区间(start, end);
  4. 对区间内的消息(跳过可丢弃消息)生成ContextSummary,经SummaryTransformer清洗后,渲染进模板 templates/forge-partial-summary-frame.md,替换原区间。

CompactionStrategy定义在 crates/forge_domain/src/compact/strategy.rs,包括Evict(f64)、Retain(usize)、Min、Max四种变体:百分比策略会先把 token 预算换算成“保留前 N 条消息”的等价形式(to_fixed),再交由find_sequence_preserving_last_n找出从第一条助手消息开始、止于保留窗口之前的连续区间。

4.3 摘要生成与两个关键保护

compress_single_sequence中值得关注的三个细节:

  • 清洗管线:SummaryTransformer(来自 crates/forge_app/src/transformers/)会依次执行“丢弃 system 角色消息 → 去重连续用户消息 → 每个文件路径只保留最后一次操作 → 去重连续助手内容块 → 剥离工作目录前缀”;
  • 推理链保持:若压缩区间的消息带有reasoning_details,会取最近一条的推理细节注入压缩后第一条助手消息,避免 extended thinking 场景下推理链断裂(代码注释给出了示例:[U, A+r, U, A+r, U, A] → compact → [U-summary, A+r, U, A]);
  • 用量累积:压缩前会把区间内所有消息的usage累加并转交给摘要消息,保证 token 用量统计不因压缩而丢失。

摘要结果以一条User 角色的文本消息(内容为 summary frame)替换原消息区间,模板forge-partial-summary-frame.md会把历史中的文件操作、搜索模式、shell 命令、Skill、MCP 调用、任务清单等以结构化 Markdown 呈现给模型,让后续轮次仍能“记住”关键操作。

五、Token 计数机制

方案文档要求增强 token 计数函数:

fn token_count(text: &str) -> usize { text.split_whitespace().count() * 75 / 100 }

并注明这是占位实现,生产环境应使用真正的 tokenizer。仓库的最终实现比该占位函数更完善(见 crates/forge_domain/src/context.rs):

pub fn token_count(&self) -> TokenCount { let actual = self.messages.last().as_ref() .and_then(|u| u.usage) .map(|u| u.total_tokens) .unwrap_or_default(); match actual { TokenCount::Actual(actual) if actual > 0 => TokenCount::Actual(actual), _ => TokenCount::Approx(self.token_count_approx()), } }

即:优先使用 provider 返回的最后一条消息的真实usage.total_tokens(TokenCount::Actual);不可用时才回退到逐消息近似估算token_count_approx()(各消息的token_count_approx之和)。ContextMessage::token_count_approx对文本、工具调用、工具结果、推理内容、图片等不同类型分别估算,相关单测覆盖了 Unicode、空内容、图片消息等边界情况(context.rs)。

六、旧方案迁移:Transform::Assistant 的弃用

方案文档明确要求淘汰基于 transform 的压缩方式,步骤包括:

  1. 为Transform::Assistant变体增加弃用注释:
pub enum Transform { /// Compresses multiple assistant messages into a single message /// /// DEPRECATED: Use the new `compact` field on the Agent struct instead. #[deprecated( since = "next_version", note = "Use the compact field on Agent instead" )] Assistant { // existing fields... }, // Other variants... }
  1. 在execute_transform中对该变体输出警告日志:
Transform::Assistant { .. } => { tracing::warn!( "Transform::Assistant is deprecated. Use the compact field on Agent instead." ); // Existing implementation... }
  1. 在文档中说明迁移路径:新配置集中在Agent.compact,由 Hook/Compactor 统一处理,无需再手写 transform 链。从当前仓库的 transformers 目录(crates/forge_app/src/transformers/)看,该目录仍保留着compaction.rs、dedupe_role.rs、trim_context_summary.rs等转换器——其中SummaryTransformer已被Compactor内部复用,也就是说压缩能力从“外部 transform 管线”收敛为“Agent 配置驱动的内置机制”,但清洗转换器本身得到了复用,而不是被简单删除。

七、测试验证:从方案测试到仓库回归测试

7.1 方案文档规划的测试

方案文档在crates/forge_domain/src/agent.rs中规划了三类测试:

#[test] fn compact_config() { let config = Compaction::new(1000) .token_threshold(2000) .turn_threshold(5) .message_threshold(20); assert_eq!(config.max_tokens, 1000); assert_eq!(config.token_threshold, Some(2000)); assert_eq!(config.turn_threshold, Some(5)); assert_eq!(config.message_threshold, Some(20)); }
  • compact_config:验证 setter 链式构造正确写入各字段;
  • test_should_compact:构造超过 token 阈值的上下文断言触发,构造极高阈值断言不触发;
  • compact:验证Agent.merge对compact字段的三态合并语义(Base 无值取 Other、双方有值被覆盖、Other 无值保留 Base)。

7.2 仓库中的实际测试

方案中的测试思路在仓库中演化为两套更系统的用例:

Compact触发判定测试(compact_config.rs)使用MessagePattern工具用字符串模式快速构造上下文('u'=用户消息、'a'=助手消息、's'=系统消息,例如ctx("uau")),逐一验证:

  • token 维度:超过/等于阈值触发、低于阈值与未配置不触发;
  • 轮数维度:ctx("uauau")对turn_threshold=2触发,且只统计用户消息(ctx("uasa")不算轮数);
  • 消息维度:超过/等于阈值触发、低于阈值不触发;
  • on_turn_end:最后一条为用户时触发,助手/系统消息结尾不触发;
  • 组合场景:多阈值“任一满足即触发”与“全部未达不触发”、空上下文不触发。

compaction_threshold安全阈值回归测试(agent.rs)记录了 3 个已修复缺陷:

  1. BUG 1:token_threshold为None时旧逻辑提前返回,导致压缩永不触发、最终出现context_length_exceeded;修复后无配置也会默认设为上下文窗口的 70%(如 128K 窗口 → 89_600);
  2. BUG 2:codex-spark(128K 窗口)默认阈值 100K 时余量仅 28K,上下文涨到约 110K 时未触发压缩,而 API 请求叠加工具输出后超过 128K 上限;修复后阈值被压到 70% 安全线;
  3. BUG 3:模型元数据缺失时旧逻辑不设默认阈值导致上下文无界增长;修复后即使context_length未知也会设置合理默认阈值。

CompactionResult指标测试(crates/forge_domain/src/compact/result.rs)验证压缩后 token/消息的缩减百分比计算,以及原始或压缩后数量为 0 的边界情况(返回0.0)。

CompactionResult结构本身记录了压缩前后original_tokens、compacted_tokens、original_messages、compacted_messages四项指标,并可由token_reduction_percentage()得到压缩率,可用于观测与埋点。

八、配置示例:forge.yaml 中的 compact 用法

方案文档给出的最小示例(可直接写入forge.yaml):

# Example in forge.yaml agents: myAgent: id: myAgent model: gpt-4-turbo system_prompt: "You are a helpful assistant." compact: max_tokens: 4000 token_threshold: 6000 turn_threshold: 10 message_threshold: 30

结合仓库实现的完整字段,一个更贴近生产实践的配置可以是:

agents: myAgent: id: myAgent model: gpt-4-turbo system_prompt: "You are a helpful assistant." compact: model: gpt-4o-mini # 用更便宜的模型生成摘要 max_tokens: 4000 # 压缩后保留约 4000 tokens token_threshold: 6000 # 上下文超过 6000 tokens 时触发 token_threshold_percentage: 0.7 # 同时按窗口 70% 兜底,取较小值 turn_threshold: 10 # 超过 10 个用户轮次时触发 message_threshold: 30 # 超过 30 条消息时触发 retention_window: 5 # 保留最近 5 条消息不压缩 eviction_window: 0.2 # 最多摘要 20% 的上下文 on_turn_end: true # 用户发完消息即压缩

配置要点归纳:

  • 触发条件为或关系,任一阈值达到即触发,未配置的维度不参与判定;
  • token_threshold与token_threshold_percentage同时配置时取较小者,且最终会被compaction_threshold按模型上下文窗口二次收敛;
  • eviction_window与retention_window同时生效时取更保守的(可压缩消息更少者);
  • model不配置时回退到 Agent 根级模型;on_turn_end适合交互型场景,turn_threshold适合长任务场景。

九、验证标准与实施步骤

方案文档给出了 6 条验证标准,可作为任何新压缩实现的自检清单:

  1. Compaction结构体正确定义,包含全部必需字段与方法;
  2. Agent结构体新增compact字段并遵循既有模式;
  3. Orchestrator 在配置条件满足时自动执行上下文压缩;
  4. 基于 transform 的旧方案被正确弃用并给出警告;
  5. 全部测试通过,证明压缩逻辑符合预期;
  6. 文档与示例清楚说明新特性的用法。

对应实施步骤为:

  1. 在agent.rs中添加Compaction/Compact结构体;
  2. 为Agent添加compact字段;
  3. 在 Orchestrator/Hook 中实现压缩方法;
  4. 改造init_agent_with_event(或等价触发点)加入压缩检查;
  5. 增强 token 计数机制;
  6. 弃用Transform::Assistant变体并输出警告;
  7. 为新功能补充测试;
  8. 更新文档与示例。

十、从方案到实现的演进小结

对照方案文档与当前仓库代码,可以梳理出这条演进脉络:

  • 配置结构:原型Compaction(4 个字段)→ 落地Compact(9 个字段),新增eviction_window/retention_window控制“压缩多少”、token_threshold_percentage做窗口比例兜底、model支持低成本摘要模型、on_turn_end支持回合结束触发;
  • 触发判定:由调用方传入turn_count/message_count→ 直接从Context内部统计,签名收敛为should_compact(&Context, token_count);
  • 执行链路:Orchestrator 内联compact_context→ 独立的CompactionHandler(事件钩子)+Compactor(服务)分层,CompactionStrategy统一百分比/固定保留/取最值策略;
  • 摘要质量:占位符"Summary of previous conversation"→SummaryTransformer清洗管线 +forge-partial-summary-frame.md结构化模板,并保留推理链与用量统计;
  • 安全兜底:新增compaction_threshold按模型上下文窗口收敛触发阈值,配套 3 个 BUG 回归测试防止上下文撑爆窗口。

对于需要在 forgecode 中自定义 Agent 压缩策略的开发者,核心落地位置就是 crates/forge_domain/src/compact/compact_config.rs(配置与判定)、crates/forge_app/src/hooks/compaction.rs(触发)、crates/forge_app/src/compact.rs(执行),而 crates/forge_domain/src/compact/strategy.rs 与 crates/forge_domain/src/compact/result.rs 则分别承载压缩策略与结果指标——理解了这四个文件的配合方式,就掌握了整个上下文压缩子系统的全貌。

  • 人工智能
  • AI Agent
  • 代码智能体
  • AI 应用
  • CLI
  • 开发工具

【免费下载链接】forgecode

AI enabled pair programmer for Claude, GPT, O Series, Grok, Deepseek, Gemini and 300+ models

项目地址:https://gitcode.com/gh_mirrors/forge39/forgecode
点击查看免费下载

相关推荐

上一篇:Element Plus常见问题:疑难杂症解决方案汇总
下一篇:告别传统复制粘贴!yanky.nvim的剪贴板历史管理功能详解

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询