主题
上下文管理与压缩
本文引用的源码文件
本文引用的文件
- src/background/context-compactor.js
- src/background/context-summarizer.js
- src/shared/token-counter.js
- src/side_panel/auto-compaction.js
- src/storage/session-store.js
- src/side_panel/context-indicator.js
- src/shared/context-usage.js
- src/background/token-recorder.js
- src/side_panel/token-stats-panel.js
- agent/src/memory/tokens.js
目录
引言
本文件聚焦“对话上下文管理与压缩”的完整实现,覆盖以下关键主题:
- 对话上下文的构建、维护与优化策略
- Token 估算与校准算法、上下文窗口管理
- 自动压缩触发条件、历史消息裁剪与结构化摘要生成
- 长对话场景下的内存优化、上下文恢复与会话状态持久化
- 不同模型上下文窗口的适配策略与性能调优方法
- 上下文生命周期图、Token 使用统计与压缩效果评估
本项目采用双进程架构:Chrome 扩展端(Manifest V3)与独立本地代理端 Agent。上下文压缩既包含扩展端的轻量估算与 UI 决策,也包含后台调用 LLM 生成结构化摘要的能力;会话状态(含压缩记录)通过 IndexedDB 持久化,支持切换、归档、分叉与恢复。
项目结构
围绕上下文管理的代码主要分布在以下模块:
- 共享层:Token 估算、上下文预算、模型窗口映射、内容截断等
- 背景层:手动压缩摘要生成、批量 ReAct 轮次压缩
- 侧边栏:自动压缩决策、上下文指示器、Token 统计面板
- 存储层:会话持久化、压缩记录恢复
- 代理端:记忆与 RAG 中的 Token 统计(用于跨进程对齐)
graph TB subgraph "扩展端" A["侧边栏<br/>auto-compaction.js"] B["上下文指示器<br/>context-indicator.js"] C["Token 统计面板<br/>token-stats-panel.js"] D["共享工具<br/>token-counter.js"] E["共享上下文用量<br/>context-usage.js"] F["背景服务<br/>context-compactor.js / context-summarizer.js"] G["会话存储<br/>session-store.js"] end subgraph "代理端" H["记忆 Token 统计<br/>memory/tokens.js"] end A --> B B --> F B --> G C --> D E --> D F --> D F --> H
图表来源
- src/side_panel/auto-compaction.js#L1-L123
- src/side_panel/context-indicator.js
- src/side_panel/token-stats-panel.js
- src/shared/token-counter.js#L1-L760
- src/shared/context-usage.js
- src/background/context-compactor.js#L1-L81
- src/background/context-summarizer.js#L1-L246
- src/storage/session-store.js#L1-L662
- agent/src/memory/tokens.js
章节来源
- src/shared/token-counter.js#L327-L407
- src/background/context-compactor.js#L1-L81
- src/background/context-summarizer.js#L1-L246
- src/side_panel/auto-compaction.js#L1-L123
- src/storage/session-store.js#L129-L178
核心组件
- Token 估算与上下文预算:提供字符到 Token 的估算、图片 Token 估算、EWMA 校准、上下文压力评估、按预算裁剪历史消息、引用内容压缩等。
- 手动压缩摘要生成:将用户选定的素材交给当前模型生成结构化摘要,失败不静默降级。
- 批量 ReAct 轮次压缩:从消息中识别完整轮次(assistant(tool_calls)+tool),一次 LLM 调用压缩多轮旧对话为结构化摘要,并带输入封顶与快速失败降级。
- 自动压缩决策:纯函数判定是否触发阈值驱动的半自动压缩,发送前钩子保证失败不阻断用户发送,具备连续失败退避。
- 会话持久化:保存/恢复会话级上下文压缩记录,支持切换、归档、分叉与定时任务宿主会话。
章节来源
- src/shared/token-counter.js#L146-L192
- src/shared/token-counter.js#L443-L505
- src/background/context-compactor.js#L24-L80
- src/background/context-summarizer.js#L116-L180
- src/side_panel/auto-compaction.js#L23-L49
- src/storage/session-store.js#L129-L178
架构总览
上下文管理贯穿“估算—决策—压缩—持久化—恢复”全链路:
sequenceDiagram
participant UI as "侧边栏界面"
participant Auto as "自动压缩决策<br/>auto-compaction.js"
participant Indicator as "上下文指示器<br/>context-indicator.js"
participant BG as "背景服务<br/>context-compactor.js / context-summarizer.js"
participant Store as "会话存储<br/>session-store.js"
participant Stats as "Token 统计<br/>token-stats-panel.js"
UI->>Auto : 发送前检查
Auto->>Indicator : runCompaction({silent})
Indicator->>BG : COMPACT_CONTEXT_SUMMARY
BG-->>Indicator : {success, summary, memoryId}
Indicator->>Store : saveCurrentSession()
Store-->>UI : 会话已持久化
BG->>Stats : recordTokenUsage(...)
Stats-->>UI : 更新 Token 统计
图表来源
- src/side_panel/auto-compaction.js#L68-L122
- src/side_panel/context-indicator.js
- src/background/context-compactor.js#L32-L80
- src/storage/session-store.js#L129-L178
- src/side_panel/token-stats-panel.js
详细组件分析
Token 估算与上下文预算
- 估算策略:中文约 1.5 字符/token,英文约 4 字符/token;图片按分辨率 tiles 公式估算,避免 Base64 字符串高估。
- EWMA 校准:基于 API 返回的实际 prompt_tokens 修正估算偏差,样本不足时不生效。
- 上下文预算:上下文窗口 - 系统提示词预留 - 工具定义预留 - 输出预留 = 消息历史可用预算。
- 压力等级:ratio < 0.7 安全,0.7~0.9 警告,≥0.9 临界。
- 历史裁剪:按预算从旧到新移除消息,保持 assistant(tool_calls)/tool 配对一致性,并可生成规则式摘要。
- 引用压缩:超过阈值的引用/选中内容自动压缩,防止长期占用上下文。
flowchart TD
Start(["开始"]) --> Calc["计算消息 Token 估算"]
Calc --> Calib{"是否需要 EWMA 校准?"}
Calib --> |是| ApplyCalib["应用校准因子"]
Calib --> |否| KeepRaw["保留原始估算"]
ApplyCalib --> Budget["计算消息历史预算"]
KeepRaw --> Budget
Budget --> Pressure["评估上下文压力等级"]
Pressure --> Trim{"是否超预算?"}
Trim --> |否| End(["结束"])
Trim --> |是| Cut["按预算裁剪历史消息"]
Cut --> Summary["生成被裁剪消息的规则摘要"]
Summary --> End
图表来源
- src/shared/token-counter.js#L82-L127
- src/shared/token-counter.js#L146-L192
- src/shared/token-counter.js#L424-L505
- src/shared/token-counter.js#L750-L759
章节来源
- src/shared/token-counter.js#L37-L54
- src/shared/token-counter.js#L82-L127
- src/shared/token-counter.js#L146-L192
- src/shared/token-counter.js#L327-L407
- src/shared/token-counter.js#L424-L505
- src/shared/token-counter.js#L750-L759
手动压缩摘要生成
- 入口:由侧边栏收集清洗后的素材后调用 background 接口。
- 行为:构造结构化摘要 Prompt,限制最大输出 Token,低温度稳定输出,失败明确报错。
- 统计:成功后记录 Token 使用,便于统计面板展示。
sequenceDiagram
participant UI as "上下文指示器"
participant Compactor as "手动压缩摘要生成"
participant API as "LLM 接口"
participant Recorder as "Token 记录器"
UI->>Compactor : generateCompactionSummary(material, model, sessionId)
Compactor->>API : POST /chat/completions (非流式)
API-->>Compactor : {choices[0].message.content}
Compactor->>Recorder : recordTokenUsage(sessionId, model, usage)
Compactor-->>UI : {success, summary}
图表来源
章节来源
批量 ReAct 轮次压缩
- 轮次识别:从 assistant(tool_calls) 开始,收集其后连续的 tool 消息作为一轮。
- 转写构建:提取工具名称、关键参数与结果片段,拼接成紧凑 transcript。
- 输入封顶:transcript 超过上限时保留头部 70% + 尾部 30%,避免压缩请求自身膨胀。
- 结构化摘要:要求输出“任务目标/已完成事项/关键事实/未决问题”,温度更低以保证稳定性。
- 失败降级:异常或空响应直接返回 null,调用方回退到权重裁剪。
flowchart TD
S(["进入 summarizeRoundsBatch"]) --> Extract["extractAllRounds 提取轮次"]
Extract --> Transcript["buildBatchTranscript 构建转写"]
Transcript --> Cap{"是否超过 transcriptMaxTokens?"}
Cap --> |是| Trunc["truncateByTokens 头尾保留"]
Cap --> |否| Prompt["组装结构化摘要 Prompt"]
Trunc --> Prompt
Prompt --> Call["POST /chat/completions (低温度)"]
Call --> Check{"是否成功且非空?"}
Check --> |是| Return["返回结构化摘要"]
Check --> |否| Fail["返回 null调用方降级"]
图表来源
- src/background/context-summarizer.js#L116-L180
- src/background/context-summarizer.js#L181-L245
- src/shared/token-counter.js#L200-L217
章节来源
- src/background/context-summarizer.js#L11-L23
- src/background/context-summarizer.js#L37-L53
- src/background/context-summarizer.js#L89-L107
- src/background/context-summarizer.js#L116-L180
- src/background/context-summarizer.js#L181-L245
自动压缩决策与发送前置钩子
- 决策函数:shouldAutoCompact 为纯函数,依据开关、阈值比、待压缩条数、是否生成中、是否开启记忆等判断。
- 发送前置钩子:runAutoCompactionBeforeSend 在发送前尝试自动压缩;任何失败均放行本次发送,不阻断用户。
- 退避机制:连续失败达到上限后进入退避,手动压缩成功后重置计数。
flowchart TD
Start(["发送前钩子"]) --> ReadCfg["读取 chatConfig.autoCompaction"]
ReadCfg --> Backoff{"连续失败是否达上限?"}
Backoff --> |是| Skip["跳过自动压缩"]
Backoff --> |否| Ratio["getRatio() 获取占用比"]
Ratio --> Pending["countPendingSinceCompaction 计算新消息数"]
Pending --> Decide{"shouldAutoCompact 判定"}
Decide --> |否| SendContinue["继续发送"]
Decide --> |是| Compact["compact({silent:true})"]
Compact --> Result{"是否成功?"}
Result --> |是| Reset["重置退避计数"]
Result --> |否| IncFail["增加失败计数"]
Reset --> SendContinue
IncFail --> SendContinue
图表来源
- src/side_panel/auto-compaction.js#L23-L49
- src/side_panel/auto-compaction.js#L51-L58
- src/side_panel/auto-compaction.js#L68-L122
章节来源
- src/side_panel/auto-compaction.js#L6-L11
- src/side_panel/auto-compaction.js#L23-L49
- src/side_panel/auto-compaction.js#L68-L122
上下文指示器与手动压缩流程
- 入口封装:triggerCompaction 转为薄壳,内核 runCompaction 复用手动与自动两条路径。
- 素材组装:assembleCompactionMaterial 根据 maxMemoryMessages 与已有压缩记录选择可压缩素材。
- 结果处理:更新 activeCompaction(摘要、锚点消息 ID、压缩数量、节省 Token、模型、时间戳、版本号、记忆锚点),持久化会话并刷新 UI。
sequenceDiagram
participant Panel as "上下文指示器"
participant Material as "素材组装"
participant BG as "背景压缩摘要"
participant Store as "会话存储"
Panel->>Material : assembleCompactionMaterial(history, config)
Material-->>Panel : {material, includedCount, hasOldSummary}
Panel->>BG : COMPACT_CONTEXT_SUMMARY
BG-->>Panel : {summary, memoryId}
Panel->>Store : saveCurrentSession()
Store-->>Panel : 完成
Panel-->>Panel : 渲染分隔条/更新指示器
图表来源
- src/side_panel/context-indicator.js
- src/background/context-compactor.js#L32-L80
- src/storage/session-store.js#L129-L178
章节来源
- src/side_panel/context-indicator.js
- src/background/context-compactor.js#L32-L80
- src/storage/session-store.js#L129-L178
会话状态持久化与上下文恢复
- 压缩记录:会话对象中包含 contextCompaction 字段,保存 activeCompaction 快照。
- 切换恢复:switchToSession 恢复 messageHistory、activeCompaction、model/temperature/topP 等。
- 归档与分叉:archiveCurrentSession/duplicateSession 支持归档与消息级分叉,分叉元数据记录来源与分叉点。
- 定时任务宿主会话:createScheduledSession 创建专属宿主会话,便于任务绑定与清理。
classDiagram
class Session {
+string id
+string title
+string model
+boolean useTools
+Array enabledTools
+string agentId
+number temperature
+number topP
+Array messageHistory
+object contextCompaction
+boolean isGenerating
+string createdAt
+string updatedAt
}
class ActiveCompaction {
+string summary
+string upToMessageId
+number compressedCount
+number summaryTokens
+number savedTokens
+string model
+number createdAt
+number revision
+string memoryId
}
Session --> ActiveCompaction : "包含"
图表来源
- src/storage/session-store.js#L129-L178
- src/storage/session-store.js#L302-L389
- src/storage/session-store.js#L574-L627
章节来源
- src/storage/session-store.js#L129-L178
- src/storage/session-store.js#L302-L389
- src/storage/session-store.js#L574-L627
依赖关系分析
- 共享层 token-counter.js 被多个模块依赖:自动压缩决策、批量压缩、手动压缩、上下文指示器等。
- 背景层 context-compactor.js 与 context-summarizer.js 依赖 fetchWithRetry、i18n、logger、token-counter。
- 侧边栏 auto-compaction.js 依赖 state、logger,并通过注入的 compact 函数复用上下文指示器的内核。
- 会话存储 session-store.js 依赖 IndexedDB 适配器与 side_panel/state,负责持久化与恢复。
graph LR TC["token-counter.js"] --> AC["auto-compaction.js"] TC --> CS["context-summarizer.js"] TC --> CC["context-compactor.js"] CC --> TR["token-recorder.js"] CI["context-indicator.js"] --> CC CI --> SS["session-store.js"] AC --> CI
图表来源
- src/shared/token-counter.js#L1-L760
- src/background/context-compactor.js#L1-L81
- src/background/context-summarizer.js#L1-L246
- src/side_panel/auto-compaction.js#L1-L123
- src/side_panel/context-indicator.js
- src/storage/session-store.js#L1-L662
章节来源
- src/shared/token-counter.js#L1-L760
- src/background/context-compactor.js#L1-L81
- src/background/context-summarizer.js#L1-L246
- src/side_panel/auto-compaction.js#L1-L123
- src/side_panel/context-indicator.js
- src/storage/session-store.js#L1-L662
性能与容量规划
- Token 估算开销:estimateMessagesTokens 对每条消息做一次估算,配合 contributions 增量扣减,避免 O(n²) 全量重估。
- 图片 Token 估算:优先使用 width/height 按 tiles 计算,避免 Base64 字符串导致的高估。
- 批量压缩输入封顶:transcriptMaxTokens 默认 30000,超出时头尾保留,降低单次压缩请求成本。
- 模型上下文窗口:内置映射 + 自定义模型配置 + 全局覆盖,优先级清晰,避免误判窗口大小。
- 自动压缩退避:连续失败达到上限后暂停自动压缩,减少额外 LLM 调用。
- 会话持久化:saveCurrentSession 仅写入必要字段,避免冗余数据;切换时并行发起保存与读取,提升交互流畅度。
[本节为通用性能建议,不直接分析具体文件]
故障排查指南
- 压缩失败无摘要:检查 background 接口返回 success 与 summary 是否为空;确认 apiBase/apiKey 配置有效。
- 自动压缩未触发:确认 chatConfig.autoCompaction 为 true,ratio 达到 thresholdRatio,pendingCount ≥ minPending,且不在生成中。
- 连续失败退避:查看 consecutiveFailures 是否达到 maxFailures;手动压缩成功后会重置。
- 会话恢复异常:检查 switchToSession 是否正确恢复 activeCompaction 与 messageHistory;确认 IndexedDB 迁移与读写正常。
- Token 统计不一致:核对 recordTokenUsage 是否被调用;对比 estimateMessagesTokens 与 API 实际 prompt_tokens 的校准情况。
章节来源
- src/background/context-compactor.js#L61-L79
- src/side_panel/auto-compaction.js#L81-L122
- src/storage/session-store.js#L302-L389
- src/shared/token-counter.js#L82-L127
结论
该项目的上下文管理与压缩体系以“估算—决策—压缩—持久化—恢复”为主线,兼顾准确性与鲁棒性:
- Token 估算结合 EWMA 校准,贴近真实 API 行为;
- 自动压缩以纯函数决策,失败不阻断用户发送;
- 批量 ReAct 轮次压缩结构化输出,提高信息密度;
- 会话持久化确保压缩记录可恢复,支持复杂工作流(切换、归档、分叉、定时任务)。
在实际使用中,建议:
- 合理设置 autoCompactionThreshold 与 minPending,平衡额外调用与上下文节省;
- 关注 Token 统计面板,结合校准信息调整估算偏差;
- 针对大模型窗口差异,使用自定义模型配置精确控制上下文窗口。
[本节为总结性内容,不直接分析具体文件]
附录:指标与评估
- 上下文生命周期图:从消息产生、估算、压力评估、压缩触发、摘要沉淀到会话恢复的全链路。
- Token 使用统计:每次 LLM 调用后记录 usage,侧边栏面板展示会话累计、平均每轮、每日统计与近 7 天历史。
- 压缩效果评估:比较 materialTokens 与 summaryTokens,计算 savedTokens;观察 ratio 变化与压力等级改善。
stateDiagram-v2 [*] --> 空闲 空闲 --> 估算中 : "新增消息" 估算中 --> 安全 : "ratio < 0.7" 估算中 --> 警告 : "0.7 ≤ ratio < 0.9" 估算中 --> 临界 : "ratio ≥ 0.9" 警告 --> 压缩中 : "手动/自动触发" 临界 --> 压缩中 : "自动触发" 压缩中 --> 已压缩 : "摘要生成成功" 压缩中 --> 裁剪降级 : "摘要失败" 已压缩 --> 恢复中 : "切换会话" 恢复中 --> 空闲 : "恢复完成" 裁剪降级 --> 空闲 : "继续发送"
图表来源
- src/shared/token-counter.js#L443-L505
- src/background/context-summarizer.js#L181-L245
- src/storage/session-store.js#L302-L389
章节来源