Skip to content

Token的压缩策略

ContextManager 中的压缩机制是一个复杂的智能系统,远不止"判断是否需要压缩,需要的话就压缩"这么简单。它包括:

  1. 多种压缩策略:根据对话特征选择最适合的压缩方法
  2. 消息优先级系统:基于消息角色、内容、位置等因素分配优先级
  3. 压缩历史记录:保留最近10次压缩操作的详细记录,用于监控、分析和优化
  4. 压缩验证机制:确保压缩结果质量,不会移除关键信息
  5. 统计和监控:提供丰富的压缩统计信息,帮助了解系统性能

思路流程图

Excalidraw 文件:https://gcntfv628ebr.feishu.cn/file/MjkHbXHWjopTSQxpXDccVDUznHf

Token压缩策略

一、压缩策略

系统实现了三种压缩策略,每种策略适用于不同的场景:

4.1 中间移除策略 (MiddleRemovalStrategy)

  • 原理:保留对话的开始和结束部分,移除中间的消息
  • 适用场景:保持对话流程和最近上下文最重要的情况
  • 实现特点
    • 保留前 preserveStart 条和后 preserveEnd 条消息
    • 从中间部分开始移除消息,直到达到目标令牌数
    • 确保不删除系统消息和标记为保留的消息

4.2 最旧移除策略 (OldestRemovalStrategy)

  • 原理:优先移除最早的消息,保留较新的消息
  • 适用场景:长对话中最近上下文最重要的情况
  • 实现特点
    • 按时间戳排序,优先移除旧消息
    • 考虑消息优先级,优先移除低优先级消息
    • 确保保留最小消息数量

4.3 混合策略 (HybridStrategy)

  • 原理:智能结合中间移除和最旧移除策略
  • 适用场景:根据对话特征自动选择最佳策略
  • 实现特点
    • 分析对话特征(长度、消息分布、压缩严重程度)
    • 根据分析结果选择最适合的策略
    • 在不确定时尝试两种策略并选择效果更好的

二、压缩触发机制

压缩通过 checkAndCompress() 方法触发:

  1. 检查条件:检查机制的初始化
    • 确保压缩已启用
    • 确保有分词器和压缩策略
    • 确保距离上次检查已超过配置的时间间隔
  2. 利用率计算
    • 公式:utilization = currentTokenCount / maxTokens
    • 用途:监控当前令牌使用情况,提供警告和统计信息
  3. 压缩决策
    • 逻辑:
    • 默认阈值)为 0.9(90%)
    • 用途:决定何时开始压缩操作

压缩触发:

  • maxToken:是根据 LLM 提供商和模型动态计算得来的,而不是写死的值。它的确定过程如下:
  • CurrentToken:是调用updateTokenCount() 获取的,updateTokenCount()方法会遍历整个消息数组,从头开始计算所有消息的令牌数:
  • 使用 lastCompressCheck:lastCompressionCheck 变量是用于控制压缩检查频率的关键机制。它通过记录上次检查时间,并与配置的检查间隔进行比较,来决定是否需要执行新的压缩检查,简单来说就是:这个方法检查当前时间与上次压缩检查时间的差值是否超过了配置的检查间隔(默认 5 秒)。
  • 每次执行**更新 Token 总数(updateTokenCount) **的时候,会进行全量重新计算,全量重新计算的原因:
    1. 压缩操作:压缩操作会删除或修改消息,导致令牌数发生变化
    2. 消息优先级调整:系统可能会根据对话内容动态调整消息优先级
    3. 准确性要求:确保令牌计数的准确性,避免累积误差

三、压缩策略的选择

压缩策略的选择有三层筛选,选择模式是一层一层往下

  1. 第一层:基于供应商/模型的策略选择 - 在初始化时根据LLM提供商和模型选择基础策略
  2. 第二层:基于对话特征的策略选择 - 仅当基础策略为hybrid时才执行对话特征分析
  3. 第三层:置信度判断 - 如果置信度≥0.6,选择置信度最高的策略;如果置信度<0.6,触发自适应方法

1、根据供应商|模型的选择策略

提供商模型推荐策略原因
OpenAIGPT-4hybrid平衡的开始和结束保留,适合通用对话
OpenAIO1middle-removal更高的保留数量,适合需要更多上下文的模型
Anthropic所有oldest-removal保留更多结束消息,适合 Anthropic 的对话风格
Google1.5middle-removal大上下文模型,需要更保守的压缩
LMStudio/Ollama所有hybrid本地模型通常有较小上下文,需要更激进的压缩

2、根据对话特征选择策略

🌟🌟 当第一步输出的压缩策略是混合策略的时候,就会进入这一步,选择真正合适的策略:中间移除策略或者最旧移除策略

3、使用自适应方法选择策略

🌟🌟 当上面在进行根据对话特征选择的时候,输出置信度低于 0.6 的时候,就会启动系统的自适应的方法

具体流程的处理:

  1. 系统将推荐策略设置为null
  2. 触发自适应方法adaptiveCompress
  3. 自适应方法会分别执行两种压缩策略(middle-removal和oldest-removal)
  4. 计算每种策略结果的效率分数(综合考虑令牌减少和消息保留)
  5. 选择效率分数更高的策略的结果作为最终结果

效率计算方法

效率 = 令牌减少(60%权重) + 消息保留(40%权重)

  • 令牌减少占 60% 的权重(更重要的目标)
  • 消息保留占 40% 的权重(次要但重要的目标)

实例场景:

假设有一个包含 15 条消息的对话,总令牌数为 9000,需要压缩到 6000 个令牌。

1. 中间移除策略的结果:

  • 压缩后令牌数:6200
  • 保留消息数:12
  • 移除消息数:3

2. 最旧移除策略的结果:

  • 压缩后令牌数:5800
  • 保留消息数:10
  • 移除消息数:5
效率计算过程
  1. 中间移除策略的效率计算
typescript
// 计算压缩比
const compressionRatio = 6200 / 9000 = 0.689;

// 计算令牌减少率
const tokenReduction = 1 - 0.689 = 0.311;

// 计算消息保留率
const messagePreservation = 12 / 15 = 0.8;

// 计算效率分数
const efficiency = 0.311 * 0.6 + 0.8 * 0.4 = 0.1866 + 0.32 = 0.5066;
  1. 最旧移除策略的效率计算
typescript
// 计算压缩比
const compressionRatio = 5800 / 9000 = 0.644;

// 计算令牌减少率
const tokenReduction = 1 - 0.644 = 0.356;

// 计算消息保留率
const messagePreservation = 10 / 15 = 0.667;

// 计算效率分数
const efficiency = 0.356 * 0.6 + 0.667 * 0.4 = 0.2136 + 0.2668 = 0.4804;
结果选择
typescript
// 比较效率分数
if (middleEfficiency >= oldestEfficiency) {  // 0.5066 >= 0.4804
    return middleResult;  // 选择中间移除策略的结果
} else {
    return oldestResult;  // 选择最旧移除策略的结果
}

在这个例子中,虽然最旧移除策略减少了更多的令牌(35.6% vs 31.1%),但中间移除策略保留了更多的消息(80% vs 66.7%)。由于系统综合考虑了令牌减少和消息保留,并且消息保留的权重较高,因此最终选择了中间移除策略的结果。

四、 压缩策略的执行

4.1、消息分配优先级

系统按以下流程为每条消息分配优先级:

  1. 检查消息是否已有优先级,如果有则保持不变
  2. 检查是否是系统消息,如果是则分配 CRITICAL 优先级
  3. 检查是否是工具消息,如果是则分配 HIGH 优先级
  4. 检查是否在对话的开始或结束位置,如果是则分配 HIGH 优先级
  5. 检查是否是长消息(>800 tokens),如果是则分配 HIGH 优先级
  6. 检查是否是短消息(<20 tokens)且不包含问号,如果是则分配 LOW 优先级
  7. 检查是否包含函数调用,如果是则分配 HIGH 优先级
  8. 如果以上条件都不满足,分配 NORMAL 优先级

4.2、压缩策略的执行流程

4.2.1、中间移除策略 (MiddleRemovalStrategy)

执行流程:

第一步:消息分组

  • 将可移除消息按原始位置分为三组:
    • 开始组:前 N 条消息(N = preserveStart 配置值)
    • 结束组:后 M 条消息(M = preserveEnd 配置值)
    • 中间组:剩余的消息

第二步:构建压缩消息列表

  • 保留所有可保留消息
  • 保留开始组和结束组的所有消息
  • 将中间组按优先级排序(低优先级在前)

第三步:执行压缩

  • 从中间组开始,按优先级从低到高移除消息
  • 直到达到目标令牌数或无法再移除消息
优先级使用方式:
  • 在中间组内部,优先移除低优先级消息
  • 开始组和结束组的消息无论优先级如何都会被保留
  • 如果中间组移除后仍未达到目标,系统会考虑从开始组或结束组移除低优先级消息
矛盾处理:

当出现"低优先级消息在末尾,但策略要求保留末尾"的矛盾时:

  • 系统优先保留末尾的配置数量消息
  • 主要从中间部分移除消息,优先移除中间部分的低优先级消息
  • 只有在中间部分移除后仍未达到目标时,才会考虑从末尾移除低优先级消息

4.2.2、最旧移除策略 (OldestRemovalStrategy)

执行流程:

第一步:消息排序

  • 将可移除消息按优先级和时间戳排序
  • 排序规则:先按优先级(低→高),同优先级按时间戳(旧→新)

第二步:构建压缩消息列表

  • 保留所有可保留消息
  • 添加所有可移除消息(已排序)

第三步:执行压缩

  • 从压缩列表中找到最旧的可移除消息
  • 移除该消息
  • 重复直到达到目标令牌数或无法再移除消息
优先级使用方式:
  • 在选择移除消息时,优先选择低优先级的消息
  • 只有当低优先级消息都被移除后,才会考虑移除高优先级消息
  • 时间戳只在相同优先级的消息中起作用
矛盾处理:

当出现"旧消息是高优先级,新消息是低优先级"的矛盾时:

  • 系统优先考虑优先级,不会移除高优先级的旧消息
  • 只有当所有低优先级消息(无论新旧)都被移除后,才会考虑移除高优先级消息
  • 这确保了重要的高优先级消息不会被过早删除

五、 完整流程总结

  1. 初始化阶段
    • 配置压缩时,根据供应商和模型选择压缩策略
    • 创建对应的策略实例(MiddleRemovalStrategy、OldestRemovalStrategy或HybridStrategy)
  2. 触发阶段
    • 每次添加消息时,检查是否需要压缩
    • 如果当前令牌数超过阈值,触发压缩
  3. 执行阶段
    • 所有策略都会先执行消息优先级分配(通过assignMessagePriorities函数)
    • 根据不同策略的特定逻辑执行压缩:
      • MiddleRemovalStrategy:保留开始和结束消息,移除中间消息
      • OldestRemovalStrategy:优先移除最旧的消息
      • HybridStrategy:先分析对话特征,然后选择最适合的子策略或使用自适应方法
  4. 结果应用
    • 验证压缩结果
    • 应用压缩后的消息列表
    • 更新令牌计数和压缩历史

重要发现:无论使用哪种压缩策略(包括middle-removal),系统都会执行消息优先级分配。这不是混合策略特有的功能,而是所有压缩策略的共同步骤。消息优先级分配确保了在压缩过程中,重要的消息(如系统消息、工具消息、长消息等)会被优先保留。