Solon v4.0.6

react - 上下文压缩(compression)

</> markdown
2026年8月3日 下午5:41:49

在智能体(Agent)的长期运行中,上下文窗口(Context Window)的限制是开发者面临的最大挑战。随着对话轮次的增加,Token 消耗不仅会带来高昂的成本,更会导致模型因为信息过载而变得迟钝甚至失忆。

Solon AI 通过 ContextCompressionInterceptor 拦截器与多维摘要策略,为智能体提供了类似人类的"长短期记忆"管理机制。

1、 核心组件:ContextCompressionInterceptor

ContextCompressionInterceptor 实现 ReActInterceptor,挂载在 ReAct 推理流程上,负责在推理开始前onReasonStart)监控工作记忆(Working Memory)的规模;当服务端因上下文超限(PTL, Prompt-Too-Long)返回错误触发重试时,还会在 onReasonRetry 中执行强制收紧模式。

触发时机与工作原理

  • 双维度触发:消息数量(非初心消息数 > maxMessages × messageTriggerFactor)或 Token 数(估算值 > contextLength × maxContextLengthRatio,默认 75%)任一超过阈值即触发压缩。
  • 执行策略:调用配置的 SummarizationStrategy 对判定为"过期"的消息段进行加工(LLM 摘要 / 关键信息提取 / 向量归档 / 滚动压缩)。
  • 注入摘要:将加工后的摘要消息(统一带 META_COMPRESSED 标记)重新注入上下文头部,并物理移除原始明细。
  • 强制模式(PTL):onReasonRetry 检测到 Prompt-Too-Long 错误时,按失败次数逐级收紧预算(0.8 → 0.6 → 0.4 → 0.25),在保证返回可用的同时尽量少丢历史。

核心保护机制(压缩不会"拆家")

  • 初心链保护:标记为 META_FIRST 的消息(system prompt、用户原始问题)永不参与压缩与删除。
  • Tool-use 原子对保护Assistant(with tool_calls)ToolMessage 的调用-结果配对不会被拆散;截断点落在工具结果上时会向前回溯到源头调用。
  • 孤立结果清理:压缩后无配对源头的 ToolMessage / Observation 会被自动移除,避免 LLM 感知到错位消息。
  • 保留窗口下限minReservedMessages(默认按 Token 预算动态推导,区间 [3, 20])保证 Token 维度过度截断时至少保留最近几轮交互。
  • 单条消息硬上限perMessageCap(默认自动推导)对超大的单条消息(如工具读取超大文件)做头尾截断,防止单条顶爆请求。
  • 压缩效果监控:压缩后消息数仍 ≥ 原始 90% 时输出告警,提示调大 maxMessages 或调整 minReservedMessages

构造器与关键配置

// 双参构造:maxMessages + 策略(maxContextRatio 默认 0.75)
new ContextCompressionInterceptor(int maxMessages, CompressionStrategy strategy)

// 三参构造:显式指定上下文窗口使用比例((0,1])
new ContextCompressionInterceptor(int maxMessages, double maxContextRatio, CompressionStrategy strategy)

// 四参构造:额外指定摘要重试次数
new ContextCompressionInterceptor(int maxMessages, double maxContextRatio, int maxRetries, CompressionStrategy strategy)

// 无参构造:maxMessages 默认 15
new ContextCompressionInterceptor()

// 链式配置(均提供 setter)
.setMaxMessages(int)          // 消息数阈值(下限 10)
.setMessageTriggerFactor(1.5) // 触发滞后系数(≥1.0,调高可避免临界抖动)
.setMaxContextLengthRatio(0.8)// Token 阈值 = contextLength × 该比例
.setDefaultContextLength(128_000L) // 模型未提供 contextLength 时的回退值
.setMinReservedMessages(8)    // 保留窗口消息数下限(显式覆盖)
.setPerMessageCap(8000)       // 单条消息 Token 硬上限
.setMaxRetries(3)             // 策略生成摘要的重试次数

Token 阈值始终以模型配置的 contextLength 为准(finalTokenThreshold),模型未配置时回退到 defaultContextLength(默认 128k)。拦截器内部用 GPT-4o 编码器(jtokkit)估算 Token,对主流模型偏差 < 5%。

2、 内置摘要策略全家桶

Solon AI 提供了四个开箱即用的策略(构造器均为无参,模型实例由拦截器在运行时传入),满足从"简单压缩"到"无限续航"的不同业务场景。

A. 基础语义压缩 (LLMCompressionStrategy)

  • 职能:调用轻量级模型对过期的对话段落进行一次性概括。
  • 场景:通用场景,对历史细节要求不高。
  • 特点:精简、准确,自带视觉标记(--- [执行进度总结] ---);内置 PTL 自动重试,待压缩历史过大时逐步收窄范围后重试(最多 3 次)。
  • 可定制:systemInstruction(...) 链式替换默认系统指令。

B. 关键信息看板 (KeyInfoExtractionStrategy)

  • 职能:作为"信息审计专家",只提取事实、参数、结论和已验证的失败尝试。
  • 场景:垂直领域任务(如 SQL 生成、自动化运维),需要防止核心参数丢失。
  • 特点:过滤掉冗长的思考过程,只保留"硬干货",输出为 Markdown 列表(--- [已确认的关键信息] ---)。
  • 可定制:systemInstruction(...) 链式替换默认提取协议。

C. 层级滚动摘要 (HierarchicalCompressionStrategy)

  • 职能:将"旧摘要"与"新消息"递归合并。(Summary_N-1 + History_New) -> Summary_N。
  • 场景:超长任务流。
  • 特点:支持无限续航。旧摘要通过 Trace 的 agent:summary:hierarchical 槽位持久保留,记忆链条永不断裂。
  • 工程细节:maxSummaryLength 默认 500 Token(按 Token 计量截断);单次压缩最多调用模型 3 次,遇 PTL 时执行两段式降级(先合并较旧分块,再以中间摘要合并较新分块);所有分块成功后才提交新摘要,避免中间状态污染。

D. 冷记忆归档 (VectorStoreCompressionStrategy)

  • 职能:将原始明细同步存入向量数据库(RepositoryStorable),仅在上下文中留下一个"检索锚点"。
  • 场景:合规审计、需要回溯原始细节的复杂推理。
  • 特点:物理存盘 + 内置取回工具。该策略同时是 AbsTalent 子类,注册为 Talent 后注入 recall_history 工具(按 sessionId 过滤,带白名单防注入校验),Agent 看到 --- [历史细节已归档] --- 标记即可主动调用回溯,具备"翻阅档案"的能力。

3. 级联编排:CompositeCompressionStrategy

在生产环境下,单一策略往往不够。你可以通过 CompositeCompressionStrategy 将多个策略串联起来,构建多层级记忆体系。

  • 执行语义:按添加顺序逐个执行所有子策略,各策略输出以 Markdown 分割线(---)拼接为一条摘要消息;子策略的 metadata 合并(putIfAbsent,先策略优先);合并结果统一强制标记 META_COMPRESSED
  • 容错:单个子策略抛异常不影响其它策略,被吞掉并记录错误日志。

最佳实践建议顺序:

  • 先通过 VectorStore 存盘(保证原始数据不丢)。
  • 再通过 KeyInfo 提纯(保证硬核数据在看板上)。
  • 最后通过 Hierarchical 压缩(保证全局进度不丢失)。

4、 快速上手

以下示例展示了如何为 ReAct 智能体配置一个"永不失忆"的记忆模型:

// 1. 选择并组合策略(策略类均为无参构造,chatModel 由拦截器运行时注入)
VectorStoreCompressionStrategy vectorStoreCompression = new VectorStoreCompressionStrategy(vectorRepo);

CompressionStrategy myStrategy = new CompositeCompressionStrategy()
 .addStrategy(vectorStoreCompression) // 冷归档
 .addStrategy(new KeyInfoExtractionStrategy()) // 事实看板
 .addStrategy(new HierarchicalCompressionStrategy()); // 滚动摘要

// 2. 注入拦截器(消息超过 15 条,或 Token 超过模型 contextLength 的 75% 时触发)
ContextCompressionInterceptor memoryGuard = new ContextCompressionInterceptor(15, myStrategy);
memoryGuard.setDefaultContextLength(128_000L);

// 3. 构建 Agent
Agent agent = ReActAgent.of(chatModel)
 .defaultInterceptorAdd(memoryGuard) // 挂载记忆守卫
 .defaultTalentAdd(vectorStoreCompression) // 注册 recall_history 取回工具
 .build();

5、压缩参数配置参考

Token 阈值由模型 contextLength × maxContextLengthRatio 推导,因此配置重点是消息数阈值上下文窗口比例,而非绝对值。

模型上下文窗口maxMessages 建议maxContextLengthRatio适用场景与策略建议
20k10 - 150.7 - 0.8紧凑型: 必须频繁触发压缩。由于容量有限,需严格控制工作内存,建议配合 perMessageCap 防止单条大消息顶爆。
100k30 - 400.75(默认)均衡型: 能够容纳较长的代码段或多轮 ReAct 思考。75% 是目前大多数模型保持高召回率(Recall)的黄金线。
200k50 - 600.75 - 0.85扩展型: 适合复杂任务编排。利用长上下文减少摘要频率,保持原始 Tool Call 链路的完整性。
1m (100万)100 - 1500.85 - 0.9海量型: 此时 Token 比例更多是为了控制成本和响应延迟。大窗口下建议调高 messageTriggerFactor(如 1.5)降低触发频率。

说明:maxMessages 有下限保护(Math.max(10, v)),设置过小会被钳到 10;messageTriggerFactor 默认 1.0(无滞后),触发阈值 = maxMessages × factor,压缩后保留窗口仍收敛到 maxMessages

重要提醒

  • 摘要窗口越大,有助于 llm 理解上下文;但是,上下文的 token 也会越大(越费钱)
  • 摘要窗口太小,llm 不好施展拳脚(还可能处处受制。比如:读取的文件,很快就被压缩了)
  • 若未配置任何策略(构造器传 null),压缩退化为"零成本物理裁剪":过期消息直接丢弃,同时自动执行原子序列追溯,保留最后一个完整的工具调用组,避免上下文错位。
  • 策略生成摘要失败(LLM 调用异常 / 返回"无显著进度" / 摘要超预算)时返回 null,拦截器会保留已有旧摘要(META_COMPRESSED 标记消息)兜底,不会静默丢状态。
  • 每次压缩都会向流式接收端推送 ContextSizeEvent(消息数、Token 数、压缩前后对比),前端可据此展示"上下文规模"与"记忆压缩"状态。
  • 静态上下文(system prompt + tools 定义)会标记边界,配合模型 Prompt Caching(cache_control / prompt_cache_key)自动获得缓存收益,压缩不会破坏可缓存块的结构完整性。