长对话不爆上下文:llm-for-zotero 缓存感知 Agent 与转录压缩机制完整指南
【免费下载链接】llm-for-zoteroAn open-source research agent system for your Zotero library.项目地址: https://gitcode.com/gh_mirrors/ll/llm-for-zotero
llm-for-zotero是一个跑在 Zotero 里的开源研究 Agent 系统,它的核心难题是如何在超长对话中不撑爆大模型的上下文窗口。这个项目用「缓存感知 + 转录压缩」双机制优雅解决:预算策略监控上下文水位,触发语义化转录压缩,同时按各家模型厂商的提示词缓存(Prompt Cache)能力规划稳定前缀,让你和 AI 的文献研读对话可以无限聊下去。
一、为什么长对话一定会"爆"上下文?
任何 Agent 对话都会累积三类 Token:
| 类型 | 来源 | 增长特点 |
|---|---|---|
| 用户消息 | 你的每一句提问 | 线性增长 |
| 助手回复 | 模型每次的完整输出 | 线性增长 |
| 工具结果 | 读论文、读 PDF、执行命令返回的大段文本 | 爆炸式增长 |
粗暴的做法是"删掉最老的消息",但 Agent 会因此失忆:忘了最初的研究目标、丢掉了工具调用和结果的配对关系,直接报错或答非所问。llm-for-zotero 的做法是分层预算 + 语义压缩 + 缓存复用,下面逐层拆解。
二、上下文预算策略:水位线机制
压缩不是无脑触发,而是由一套"水位线"策略控制的,核心实现在 budgetPolicy.ts:
- 72%上下文水位 → 进入警告状态
- 84%水位 → 触发转录压缩(compact)
- 92%水位 → 硬限制,强制收缩
- 压缩目标 → 压到窗口容量的58%
其中几个关键配比同样值得注意:压缩后保留的"最近尾巴"占 18%,语义摘要占 8%,证据片段占 12%。此外还有一个回滞(hysteresis)机制:刚压缩过的会话,下次触发阈值会临时上浮 8 个百分点,避免"刚压完又立刻再压"的抖动。
这套逻辑在buildAgentContextBudgetState()中一次性算出shouldCompact、targetTokens、recentTailTokens等状态,供运行时决策:
// src/agent/context/budgetPolicy.ts 中的默认策略 const DEFAULT_POLICY: AgentContextBudgetPolicy = { warningRatio: 0.72, compactRatio: 0.84, hardRatio: 0.92, targetRatio: 0.58, recentTailRatio: 0.18, summaryRatio: 0.08, evidenceRatio: 0.12, hysteresisRatio: 0.08, minRecentMessages: 4, };三、转录压缩机制:压缩后不失忆
真正动手压缩的是 transcriptCompactor.ts,它解决了三个经典难题。
1. 切哪里?——对齐到"轮次边界"
findTailStart()从消息尾部向前扫描,找出预算内能装下的最远位置,然后把切点对齐到用户消息边界;alignTailStartToProviderMessageBoundary()还会回退切点,确保不会把"助手发起工具调用 → 工具返回结果"这一对消息拆开。这样模型看到的对话永远是结构完整的。
2. 压成什么?——语义检查点(Semantic Checkpoint)
buildSummaryMessage()不会生成模糊的"之前我们聊了……",而是产出一条结构化检查点消息,包含:
- 最新根目标:从历史中提取
User request:或上一份检查点里的根目标,让 Agent 永远记得最初的研究意图 - 最近 8 条用户消息 + 8 条助手消息的截断摘录(各自带 messageId,可按需回读原文)
- 工具使用统计:例如
paper_read x5, run_command x2 - 被压缩的工具结果句柄:列出所有
trh_开头的 handle
3. 工具结果去哪了?——句柄化外存
buildPortableAgentTranscript()把被丢弃的大块工具结果转存为工具结果句柄(tool result handle),对话里只留一行handle=trh_xxx和运行摘要;需要原文时 Agent 可以调用tool_result_read(handle)精确取回。摘要里还特意声明"摘录不完整,依赖被省略文本前先调用 conversation_read 或 tool_result_read",等于给模型留了一张"记忆索引卡",而不是让它靠猜。
这套压缩与恢复能力有专门的测试覆盖:agentTranscriptCompactor.test.ts 和 agentTranscriptRecovery.test.ts。
四、缓存感知:把"重复发送"变成"免费读取"
压缩省的是每次要发的 Token,而提示词缓存省的是发送的成本和延迟。这两件事在 contextCache/manager.ts 里统一规划。
1. 自动识别各家缓存能力
resolvePromptCacheCapability()根据接入的供应商自动判定缓存类型:
| 供应商 | 缓存模式 | 遥测指标 |
|---|---|---|
| Anthropic / MiniMax | 显式缓存块(ephemeral,支持 1h TTL) | cache read / write |
| OpenAI / Codex / DeepSeek / Gemini / Kimi | 自动前缀匹配 | 各自 cached tokens / 命中率 |
| 其他 | 无缓存 | 退化为稳定前缀策略 |
2. 稳定前缀 + 内容指纹
buildContextCacheKey()用「供应商 : 模型 : 论文上下文列表 : 内容哈希」构造缓存键——只要模型和挂载的论文没变,前缀部分就能命中缓存。同时设置了1024 Token 门槛:太短的上下文开缓存得不偿失,直接禁用(below-cache-threshold)。
3. 用命中率决定 TTL 的"聪明"细节
对 Anthropic,是否启用 1 小时长缓存(成本更高)由shouldUseAnthropic1hCache()决定:上下文至少 16000 Token、至少命中过 1 次、读次数不少于写次数、且最近命中率 ≥ 50% 才升级。这是一个典型的"先观察、再花钱"策略,避免为一次性长上下文支付双倍缓存费。
4. 证据缓存:压缩之外还有一层"事实记忆"
cacheManagement.ts 在 SQLite 里维护llm_for_zotero_agent_evidence表,把读过的论文片段按会话+证据键去重存储(最多 12 条证据、每条 4 个片段、单片段上限 1200 字符)。即使转录被压缩,Agent 也能重新调出关键引文,保证长对话中引用论文不"翻车"。
五、双机制如何协同工作?
整个流程可以概括为一条流水线:
- 每轮请求前,预算策略计算当前水位,判断是否需要压缩;
- 若超阈值,转录压缩器切出完整轮次尾巴 + 生成语义检查点,大工具结果句柄化外存;
- 组装请求时,缓存管理器为稳定前缀(系统提示、论文上下文、工具定义)规划缓存键与 TTL 提示;
- 命中缓存的部分按极低费率计费,压缩保证总输入不超窗口——缓存让"记住的"更便宜,压缩让"忘掉的"可找回。
这套机制的完整行为有对应测试保障:contextCacheManager.test.ts、agentPromptBudget.test.ts、agentRetention.test.ts。
六、给使用者的实用建议 💡
- 固定模型和挂载论文:缓存键包含模型名和论文列表,中途频繁切换模型/上下文会让前缀缓存反复失效
- 长任务给一个清晰的总目标:根目标提取依赖消息中的
User request:结构,目标表述越明确,压缩后的检查点越可靠 - 重要中间结论让 Agent 落盘:写入 Zotero 笔记或文档后,即使转录被压缩,结论也永久留存
- 观察用量面板:缓存命中率、压缩次数等遥测数据会持续记录,可用于判断自己使用模式的缓存效率
总结
llm-for-zotero 把"长对话不爆上下文"拆解成三个可独立演进的部分:预算策略决定何时动手,转录压缩用语义检查点和工具句柄做到"压缩不失忆",缓存感知则按各家供应商能力最大化前缀复用。三层配合,让 Zotero 里的研究 Agent 既能聊得久,又花得省。
核心源码索引:
- 缓存规划:src/contextCache/manager.ts
- 转录压缩:src/agent/context/transcriptCompactor.ts
- 预算策略:src/agent/context/budgetPolicy.ts
- Agent 证据缓存:src/agent/context/cacheManagement.ts
- 运行时主入口:src/agent/runtime.ts
【免费下载链接】llm-for-zoteroAn open-source research agent system for your Zotero library.项目地址: https://gitcode.com/gh_mirrors/ll/llm-for-zotero
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考