主题
上下文管理
本文引用的源码文件
本文引用的文件
目录
引言
本文件面向“上下文管理系统”的完整技术文档,聚焦以下目标:
- 解释上下文压缩算法:truncateByTokens、truncateContentSmart、assessContextPressure。
- 说明 Token 计数与优化机制:estimateMessagesTokens、getCalibratedTokens、trimMessagesByBudget。
- 描述记忆存储机制:短期记忆(会话历史)、长期记忆(Agent 端)与 scratchpad 便签系统。
- 解释会话状态管理:生命周期、持久化与恢复。
- 说明上下文摘要生成:summarizeRoundsBatch 批量摘要与 extractAllRounds 轮次提取。
- 提供可操作的优化建议、自定义策略思路与内存泄漏防护要点。
本项目为双进程架构:Chrome 扩展端(Manifest V3,src/)与独立本地代理端 Agent(Node.js)。扩展侧负责 UI、上下文估算、压缩触发与消息组装;Agent 侧负责长期记忆、RAG、工具执行与安全控制。两端通过本地 HTTP/WebSocket 通信。
项目结构
上下文管理相关代码横跨共享层、后台服务层与侧边栏 UI 层:
- 共享层 token-counter.js:Token 估算、截断、预算分配、压力评估、消息过滤与清洗。
- 共享层 context-usage.js:发送路径的历史选择、压缩注入与占用计算。
- 后台层 context-compactor.js:手动压缩摘要生成。
- 后台层 context-summarizer.js:批量对话轮次摘要生成。
- 后台层 react-loop.js:ReAct 主循环中的上下文压力检测、动态裁剪与摘要降级。
- 侧边栏 context-indicator.js:上下文指示器、手动压缩流程与摘要查看。
- 侧边栏 session-manager.js / session-manager-ui.js:会话创建、切换、保存、删除与 UI 交互。
- 工具定义 agent-tools.js:take_note 便签工具定义,支撑 scratchpad。
graph TB subgraph "共享层" TC["token-counter.js<br/>Token 估算/截断/预算/压力"] CU["context-usage.js<br/>历史选择/压缩注入/占用计算"] end subgraph "后台层" CC["context-compactor.js<br/>手动压缩摘要"] CS["context-summarizer.js<br/>批量摘要/轮次提取"] RL["react-loop.js<br/>ReAct 主循环/压力检测/动态裁剪"] end subgraph "侧边栏" CI["context-indicator.js<br/>指示器/手动压缩流程"] SM["session-manager.js<br/>会话 API"] SUI["session-manager-ui.js<br/>会话标签 UI"] AT["agent-tools.js<br/>take_note 便签工具"] end CI --> CC CI --> CU RL --> CS RL --> TC RL --> CU CC --> TC CS --> TC CU --> TC SUI --> SM AT --> RL
图表来源
- src/shared/token-counter.js#L146-L554
- src/shared/context-usage.js#L39-L239
- src/background/context-compactor.js#L32-L80
- src/background/context-summarizer.js#L116-L245
- src/background/react-loop.js#L1248-L1337
- src/side_panel/context-indicator.js#L296-L395
- src/side_panel/session-manager.js#L69-L177
- src/side_panel/session-manager-ui.js#L305-L317
- src/background/tools/agent-tools.js#L170-L186
章节来源
- src/shared/token-counter.js#L146-L554
- src/shared/context-usage.js#L39-L239
- src/background/context-compactor.js#L32-L80
- src/background/context-summarizer.js#L116-L245
- src/background/react-loop.js#L1248-L1337
- src/side_panel/context-indicator.js#L296-L395
- src/side_panel/session-manager.js#L69-L177
- src/side_panel/session-manager-ui.js#L305-L317
- src/background/tools/agent-tools.js#L170-L186
核心组件
- Token 估算与截断:estimateTokens、estimateMessagesTokens、truncateByTokens、truncateContentSmart、filterApiMessages、sanitizeImageUrlsForApi、stripImagesFromContent。
- 预算与压力:getMessageBudget、assessContextPressure、getContextWindow、inferContextWindow。
- 消息裁剪:trimMessagesByBudget、generateMessagesSummary。
- 校准:updateCalibration、getCalibratedTokens、getCalibrationInfo。
- 上下文使用计算:computeContextUsage、selectHistoryForSend、appendCompactionToSystemPrompt、applyCompactionInjection、assembleCompactionMaterial。
- 摘要生成:generateCompactionSummary(手动)、summarizeRoundsBatch(批量)、extractAllRounds(轮次提取)。
- 会话管理:createSession、switchToSession、saveCurrentSession、deleteSession、duplicateSession 等。
- Scratchpad 便签:take_note 工具定义与 ReAct 循环内 notesState 维护。
章节来源
- src/shared/token-counter.js#L81-L139
- src/shared/token-counter.js#L146-L192
- src/shared/token-counter.js#L200-L321
- src/shared/token-counter.js#L327-L407
- src/shared/token-counter.js#L413-L505
- src/shared/token-counter.js#L507-L554
- src/shared/token-counter.js#L580-L675
- src/shared/token-counter.js#L684-L713
- src/shared/context-usage.js#L39-L239
- src/background/context-compactor.js#L32-L80
- src/background/context-summarizer.js#L116-L245
- src/side_panel/session-manager.js#L69-L177
- src/background/tools/agent-tools.js#L170-L186
架构总览
下图展示一次“上下文接近上限时的自动压缩与动态裁剪”的关键调用链:
sequenceDiagram participant UI as "侧边栏 context-indicator.js" participant BG as "后台 react-loop.js" participant SUM as "context-summarizer.js" participant COMP as "context-compactor.js" participant SH as "shared token-counter.js / context-usage.js" UI->>BG : 用户触发或自动触发压缩 BG->>SH : assessContextPressure(totalEstimate, contextWindow) alt 压力非安全 BG->>SH : trimMessagesByBudget(currentMessages, targetBudget) BG->>SUM : summarizeRoundsBatch(messages, priorSummary, config, model) SUM-->>BG : 结构化摘要或 null失败降级 BG->>BG : 若摘要失败则回退权重裁剪 else 安全 BG->>BG : 继续正常推理 end UI->>COMP : generateCompactionSummary(material) COMP-->>UI : summary + savedTokens
图表来源
- src/background/react-loop.js#L1305-L1337
- src/background/context-summarizer.js#L181-L245
- src/background/context-compactor.js#L32-L80
- src/shared/token-counter.js#L443-L505
- src/side_panel/context-indicator.js#L296-L395
详细组件分析
Token 估算与截断
- estimateTokens:按中文/英文字符比例估算单段文本 token。
- estimateMessagesTokens:支持字符串与 multipart content(含 image_url),避免 Base64 高估;附加 tool_calls/tool_call_id/reasoning_content 字段估算。
- truncateByTokens:保留头部 70% + 尾部 30%,插入省略提示。
- truncateContentSmart:智能类型识别(HTML/JSON/文本),分别采用 body 优先、顶层 key 摘要、固定头尾策略。
- filterApiMessages:剥离内部字段、修复 assistant(tool_calls)+tool 配对、清理图片 URL 内部字段、清洗非法 Unicode。
- sanitizeImageUrlsForApi:仅保留标准 url 字段,避免非标准字段进入请求体。
- stripImagesFromContent:移除多模态 content 中的图片,仅保留文本。
flowchart TD
Start(["输入消息数组"]) --> Est["estimateMessagesTokens 估算"]
Est --> Type{"content 类型?"}
Type --> |string| T1["estimateTokens(text)"]
Type --> |array| T2["遍历 part:<br/>text → estimateTokens<br/>image_url → estimateImageTokens<br/>其他 → JSON.stringify"]
Type --> |other| T3["JSON.stringify(content)"]
T1 --> Extra["附加字段估算<br/>tool_calls/tool_call_id/reasoning_content"]
T2 --> Extra
T3 --> Extra
Extra --> Overhead["每条加 MESSAGE_OVERHEAD"]
Overhead --> Sum["累加得到总 tokens"]
Sum --> End(["返回估算值"])
图表来源
章节来源
- src/shared/token-counter.js#L146-L192
- src/shared/token-counter.js#L200-L321
- src/shared/token-counter.js#L580-L675
- src/shared/token-counter.js#L684-L713
预算分配与压力评估
- getContextWindow:优先级为自定义模型映射 > 全局覆盖 > 内置映射 > default。
- getMessageBudget:contextWindow - SYSTEM_PROMPT_BUDGET - estimateToolsTokens(toolCount) - OUTPUT_BUDGET。
- assessContextPressure:ratio < 0.7 safe;< 0.9 warning;否则 critical。
flowchart TD
A["获取 contextWindow"] --> B["计算 messageBudget = window - system - tools - output"]
B --> C["计算 usedTokenssystem+tools+history+input"]
C --> D["ratio = usedTokens / budget"]
D --> E{"ratio < 0.7 ?"}
E --> |是| F["safe"]
E --> |否| G{"ratio < 0.9 ?"}
G --> |是| H["warning"]
G --> |否| I["critical"]
图表来源
章节来源
消息预算裁剪与规则摘要
- trimMessagesByBudget:从旧到新移除消息,保证 assistant(tool_calls)+tool 配对连丢;预估算逐条贡献并增量扣减,避免 O(n²)。
- generateMessagesSummary:从被裁消息中提取最近 user 问题与使用的工具名,生成结构化摘要。
flowchart TD
S(["输入 messages + budget"]) --> Split["分离 system可选与 rest"]
Split --> Pre["预估算每条贡献 contributions"]
Pre --> Running["runningTotal = sum(system) + sum(rest)"]
Running --> Check{"runningTotal <= budget ?"}
Check --> |是| Ret["返回原消息"]
Check --> |否| Loop["while rest.length > 0 && runningTotal > budget"]
Loop --> Remove["shift 一条 removed,加入 trimmedMessages"]
Remove --> Pair{"removed.role === assistant && tool_calls ?"}
Pair --> |是| DropTool["连续丢弃后续 tool 消息"]
Pair --> |否| Next["继续"]
DropTool --> Next
Next --> Update["runningTotal -= contributions[head++]"]
Update --> Loop
Loop --> Build["result = system + rest"]
Build --> Summary["generateMessagesSummary(trimmedMessages)"]
Summary --> Out(["返回 {messages, trimmedCount, summary}"])
图表来源
章节来源
上下文使用计算与发送路径
- selectHistoryForSend:记忆层(isolateChat=false 不携带历史)→ 窗口层(maxMemoryMessages)→ 压缩层(upToMessageId 之后参与发送)→ 预算层(从后往前保留至 historyBudget)。
- appendCompactionToSystemPrompt / applyCompactionInjection:将压缩摘要注入 system prompt,受 preserveSystem 保护。
- computeContextUsage:与发送路径同源的计算口径,输出 usedTokens、contextWindow、ratio、level 与 breakdown。
- assembleCompactionMaterial:点击“压缩上下文”时,按记忆设置窗口选出全部携带内容作为素材,不经发送预算裁剪。
flowchart TD
In["history + currentMessage"] --> Mem{"isolateChat ?"}
Mem --> |否| OnlyCur["仅当前消息"]
Mem --> |是| Win["按 maxMemoryMessages 取最近 N 条"]
Win --> Comp{"存在有效 compaction.upToMessageId ?"}
Comp --> |是| Filter["过滤 upToMessageId 之前的消息"]
Comp --> |否| KeepAll["保留窗口内全部"]
Filter --> Budget["从后往前保留至 historyBudget"]
KeepAll --> Budget
Budget --> Trim["统计被裁消息数"]
Trim --> Summary["生成规则摘要可选"]
Summary --> Out(["keptHistory + currentMessage"])
图表来源
- src/shared/context-usage.js#L39-L114
- src/shared/context-usage.js#L120-L145
- src/shared/context-usage.js#L152-L212
- src/shared/context-usage.js#L220-L239
章节来源
手动压缩与批量摘要
- 手动压缩:context-indicator.runCompaction 收集素材 → 调 background COMPACT_CONTEXT_SUMMARY → 写入 state.activeCompaction → 持久化。
- 批量摘要:context-summarizer.summarizeRoundsBatch 构建 transcript(priorSummary + rounds),对超长 transcript 做 truncateByTokens 封顶,调用 LLM 输出结构化摘要;失败返回 null,由上层降级到权重裁剪。
- 轮次提取:extractAllRounds 扫描 assistant(tool_calls) 起始的完整轮次,包含其后所有 tool 消息。
sequenceDiagram
participant UI as "context-indicator.js"
participant BG as "context-compactor.js"
participant RL as "react-loop.js"
participant SUM as "context-summarizer.js"
UI->>BG : COMPACT_CONTEXT_SUMMARY(material, model, sessionId)
BG-->>UI : {success, summary, memoryId?}
UI->>UI : state.activeCompaction = {summary, upToMessageId, ...}
UI->>UI : saveCurrentSession()
RL->>RL : assessContextPressure(...)
alt 压力临界
RL->>SUM : summarizeRoundsBatch(messages, priorSummary, config, model)
SUM-->>RL : summary 或 null
RL->>RL : 若 null 则走 trimMessagesByBudget 降级
end
图表来源
- src/side_panel/context-indicator.js#L296-L395
- src/background/context-compactor.js#L32-L80
- src/background/context-summarizer.js#L116-L245
- src/background/react-loop.js#L1305-L1337
章节来源
- src/side_panel/context-indicator.js#L296-L395
- src/background/context-compactor.js#L32-L80
- src/background/context-summarizer.js#L116-L245
- src/background/react-loop.js#L1305-L1337
记忆存储机制
- 短期记忆:浏览器侧 side panel 的 state.messageHistory 与 IndexedDB 会话数据(通过 session-manager.js 封装)。
- 长期记忆:Agent 端 memory/global-memory.json,跨会话持久化;扩展侧通过工具调用与上下文压缩沉淀 summary 条目(tags 含 compaction + session:<id>)。
- Scratchpad 便签:take_note 工具在 ReAct 循环中维护 notesState(FIFO 限制长度),并在每轮末尾以锚点消息形式注入 filteredMessages,使关键发现跨越压缩存活。
graph TB SP["Scratchpad notesState<br/>ReAct 循环维护"] --> FM["filteredMessages<br/>每轮末尾注入锚点"] FM --> API["API 调用"] API --> RL["react-loop.js"] RL --> SUM["context-summarizer.js<br/>批量摘要"] SUM --> MEM["Agent 长期记忆<br/>global-memory.json"] UIH["Side Panel 历史<br/>state.messageHistory"] --> DB["IndexedDB 会话存储"]
图表来源
- src/background/tools/agent-tools.js#L170-L186
- src/background/react-loop.js#L1921-L1932
- src/background/context-summarizer.js#L181-L245
- src/side_panel/session-manager.js#L69-L177
章节来源
- src/background/tools/agent-tools.js#L170-L186
- src/background/react-loop.js#L1921-L1932
- src/background/context-summarizer.js#L181-L245
- src/side_panel/session-manager.js#L69-L177
会话状态管理
- 生命周期:newSession → switchToSession → saveCurrentSession → deleteSession/archive/restore/duplicate。
- 持久化:chrome.storage.local 用于 completedSessionIds;IndexedDB 用于会话数据(load/save/switch/delete/reorder/import/export)。
- 恢复:restoreCompletedSessions 在 Side Panel 初始化时恢复完成标记;switchToSession 恢复 activeSessionId 与对应历史。
stateDiagram-v2 [*] --> 新建 新建 --> 活跃 : "switchToSession" 活跃 --> 归档 : "archiveCurrentSession" 归档 --> 活跃 : "restoreArchivedSession" 活跃 --> 关闭 : "deleteSession" 活跃 --> 复制 : "duplicateSession" 复制 --> 活跃 : "switchToSession(新会话)"
图表来源
章节来源
依赖关系分析
- token-counter.js 被多处消费:react-loop.js、context-usage.js、context-indicator.js、context-summarizer.js、agent-dispatcher.js。
- context-usage.js 统一了“显示值与发送值同源”,避免 UI 与发送链路漂移。
- react-loop.js 聚合上下文压力检测、动态裁剪、摘要降级与 scratchpad 注入。
- context-indicator.js 驱动手动压缩流程,并与 session-manager.js 协作持久化。
graph LR TC["token-counter.js"] --> RL["react-loop.js"] TC --> CU["context-usage.js"] TC --> CI["context-indicator.js"] TC --> CS["context-summarizer.js"] CU --> CI CS --> RL CI --> CC["context-compactor.js"] CI --> SM["session-manager.js"]
图表来源
- src/background/react-loop.js#L6
- src/shared/context-usage.js#L1-L11
- src/side_panel/context-indicator.js#L1-L11
- src/background/context-summarizer.js#L1-L10
章节来源
- src/background/react-loop.js#L6
- src/shared/context-usage.js#L1-L11
- src/side_panel/context-indicator.js#L1-L11
- src/background/context-summarizer.js#L1-L10
性能与内存优化
- 工具定义 token 缓存:rawToolTokens 在 ReAct 主循环外只算一次,getCalibratedTokens 仍逐轮应用(因校准因子随实际用量演进)。
- trimMessagesByBudget 已优化为逐条贡献预估算 + 增量扣减,避免 O(n²)。
- 图片 token 估算按分辨率 tile 公式,避免 Base64 字符串高估约 20 倍。
- 实时校准 updateCalibration 使用 EWMA 平滑,样本不足时不生效,防止异常波动。
- 长任务中 stripImagesFromContent 历史消息剥离,减少不必要传输。
- 建议:
- 合理设置 maxMemoryMessages,避免过长历史常驻内存。
- 开启 persistSummaryToMemory,让压缩摘要沉淀为长期记忆,降低重复压缩成本。
- 控制 take_note 数量(NOTES_MAX_STORED FIFO),避免便签无限增长。
- 定期归档/删除会话,释放 IndexedDB 空间。
章节来源
- src/background/react-loop.js#L1248-L1254
- src/background/react-loop.js#L1285-L1289
- src/shared/token-counter.js#L81-L139
- src/shared/token-counter.js#L460-L505
- src/shared/token-counter.js#L63-L79
故障排查指南
- 压缩失败:
- 检查 API 配置是否缺失(config_missing)。
- 确认 isolateChat 是否为 true(memory_off)。
- 确认正在生成回复时不可压缩(generating)。
- 压缩点失效:
- 当 upToMessageId 对应的消息被删除,本次发送会忽略压缩;可在详情弹窗中移除失效记录。
- 上下文超限:
- 观察 assessContextPressure 等级;critical 或超过 85% 会触发 trimMessagesByBudget。
- 若批量摘要失败(summarizeRoundsBatch 返回 null),会回退到权重裁剪。
- 工具调用配对错误:
- filterApiMessages 会校验 assistant(tool_calls).id 与 tool.tool_call_id 严格匹配,不匹配会清除或剔除未匹配的 tool_calls。
- 图片导致的高估:
- 确保 stripImagesFromContent 在历史消息中正确剥离 Base64。
- 使用 sanitizeImageUrlsForApi 清理非标准字段。
章节来源
- src/side_panel/context-indicator.js#L296-L395
- src/shared/context-usage.js#L138-L145
- src/background/react-loop.js#L1305-L1337
- src/shared/token-counter.js#L580-L675
- src/shared/token-counter.js#L684-L713
结论
本系统的上下文管理围绕“精准估算 + 智能截断 + 预算控制 + 摘要降级”展开:
- 通过 estimateMessagesTokens 与 getCalibratedTokens 提升估算准确度。
- 通过 truncateByTokens/truncateContentSmart 实现可控的内容保留。
- 通过 assessContextPressure 与 trimMessagesByBudget 实现主动防御。
- 通过 summarizeRoundsBatch 与 extractAllRounds 实现高效批量摘要。
- 通过 session-manager 与 IndexedDB 保障会话状态持久化与恢复。
- 通过 scratchpad 便签与 ReAct 锚点注入,使关键信息在压缩中存活。
附录:实践示例与最佳实践
- 优化上下文使用:
- 调整 maxMemoryMessages 限制历史长度。
- 启用 persistSummaryToMemory,沉淀压缩摘要到长期记忆。
- 使用 compressQuotedContext 压缩引用内容,避免大段内容永久占据上下文。
- 自定义压缩策略:
- 基于 truncateContentSmart 的 contentType 参数,扩展新的类型处理逻辑(如 markdown、CSV)。
- 在 generateMessagesSummary 基础上定制摘要模板,突出业务关键字段。
- 管理会话状态:
- 使用 duplicateSession 进行分支探索,结合 upToMessageId 精确分叉。
- 使用 archiveCurrentSession/restoreArchivedSession 管理冷/热会话。
- 性能调优建议:
- 保持 rawToolTokens 在循环外计算,避免重复序列化与估算。
- 利用 stripImagesFromContent 剥离历史图片,减少传输体积。
- 监控 updateCalibration 的 ratio 与 samples,必要时重置校准因子。
- 内存泄漏防护:
- 及时 removeCompactionDivider 与 closeContextUsagePopup,避免 DOM 节点残留。
- 控制 notesState 长度(FIFO),避免便签无限增长。
- 定期清理 completedSessionIds 与过期会话,释放 chrome.storage.local 与 IndexedDB。
章节来源