当前位置: 首页 > news >正文

hermes源码学习8-上下文压缩与缓存

Hermes Agent 使用双重压缩系统和 Anthropic prompt(提示词)缓存,在长对话中高效管理上下文窗口用量。

源文件:agent/context_engine.py(ABC)、agent/context_compressor.py(默认引擎)、agent/prompt_caching.pygateway/run.py(会话清理)、run_agent.py(搜索_compress_context

可插拔上下文引擎

上下文管理基于ContextEngineABC(agent/context_engine.py)构建。内置的ContextCompressor是默认实现,但插件可以用其他引擎替换它(例如无损上下文管理)。

context: engine: "compressor" # default — built-in lossy summarization engine: "lcm" # example — plugin providing lossless context

引擎负责:

  • 决定何时触发压缩(should_compress()
  • 执行压缩(compress()
  • 可选地暴露 agent 可调用的工具(例如lcm_grep
  • 追踪 API 响应中的 token 用量

通过config.yaml中的context.engine进行配置驱动选择。解析顺序:

  1. 检查plugins/context_engine/<name>/目录
  2. 检查通用插件系统(register_context_engine()
  3. 回退到内置ContextCompressor

插件引擎永远不会自动激活——用户必须在context.engine中显式设置插件名称。默认的"compressor"始终使用内置实现。

通过hermes plugins→ Provider Plugins → Context Engine 进行配置,或直接编辑config.yaml

关于构建上下文引擎插件,请参阅 Context Engine 插件。

双重压缩系统

Hermes 有两个独立运行的压缩层:

┌──────────────────────────┐ Incoming message │ Gateway Session Hygiene │ Fires at 85% of context ─────────────────► │ (pre-agent, rough est.) │ Safety net for large sessions └─────────────┬────────────┘ │ ▼ ┌──────────────────────────┐ │ Agent ContextCompressor │ Fires at 50% of context (default) │ (in-loop, real tokens) │ Normal context management └──────────────────────────┘

1. Gateway 会话清理(85% 阈值)

位于gateway/run.py(搜索Session hygiene: auto-compress)。这是一个安全网,在 agent 处理消息之前运行。它防止会话在两次交互之间增长过大时(例如 Telegram/Discord 中的隔夜积累)导致 API 失败。

  • 阈值:固定为模型上下文长度的 85%
  • Token 来源:优先使用上一轮 API 实际报告的 token 数;回退到基于字符的粗略估算(estimate_messages_tokens_rough
  • 触发条件:仅当len(history) >= 4且压缩已启用时
  • 目的:捕获逃过 agent 自身压缩器的会话

Gateway 清理阈值有意高于 agent 压缩器的阈值。将其设置为 50%(与 agent 相同)会导致长 gateway 会话在每一轮都过早触发压缩。

2. Agent ContextCompressor(50% 阈值,可配置)

位于agent/context_compressor.py。这是主要压缩系统,在 agent 的工具循环内运行,可访问准确的 API 报告 token 数。

配置

所有压缩设置从config.yamlcompression键读取:

compression: enabled: true # Enable/disable compression (default: true) threshold: 0.50 # Fraction of context window (default: 0.50 = 50%) target_ratio: 0.20 # How much of threshold to keep as tail (default: 0.20) protect_last_n: 20 # Minimum protected tail messages (default: 20) # Summarization model/provider configured under auxiliary: auxiliary: compression: model: null # Override model for summaries (default: auto-detect) provider: auto # Provider: "auto", "openrouter", "nous", "main", etc. base_url: null # Custom OpenAI-compatible endpoint

参数详情

参数默认值范围描述
threshold0.500.0-1.0当 prompt token 数 ≥threshold × context_length时触发压缩
target_ratio0.200.10-0.80控制尾部保护 token 预算:threshold_tokens × target_ratio
protect_last_n20≥1始终保留的最近消息最小数量
protect_first_n3(硬编码)系统提示词 + 首次交互始终保留

计算值(200K 上下文模型,默认参数)

context_length = 200,000 threshold_tokens = 200,000 × 0.50 = 100,000 tail_token_budget = 100,000 × 0.20 = 20,000 max_summary_tokens = min(200,000 × 0.05, 12,000) = 10,000

压缩算法

ContextCompressor.compress()方法遵循 4 阶段算法:

阶段 1:清除旧工具结果(廉价,无需 LLM 调用)

保护尾部之外的旧工具结果(>200 字符)将被替换为:

[Old tool output cleared to save context space]

这是一个廉价的预处理步骤,可从冗长的工具输出(文件内容、终端输出、搜索结果)中节省大量 token。

阶段 2:确定边界

┌─────────────────────────────────────────────────────────────┐ │ Message list │ │ │ │ [0..2] ← protect_first_n (system + first exchange) │ │ [3..N] ← middle turns → SUMMARIZED │ │ [N..end] ← tail (by token budget OR protect_last_n) │ │ │ └─────────────────────────────────────────────────────────────┘

尾部保护基于token 预算:从末尾向前遍历,累积 token 直到预算耗尽。如果预算保护的消息数少于固定的protect_last_n,则回退到该固定数量。

边界对齐以避免拆分 tool_call/tool_result 组。_align_boundary_backward()方法会跳过连续的工具结果,找到父级 assistant 消息,保持组的完整性。

阶段 3:生成结构化摘要

摘要模型上下文长度

摘要模型的上下文窗口必须至少与主 agent 模型一样大。整个中间部分通过单次call_llm(task="compression")调用发送给摘要模型。如果摘要模型的上下文更小,API 将返回上下文长度错误——_generate_summary()会捕获该错误,记录警告并返回None。压缩器随后会在没有摘要的情况下丢弃中间轮次,静默丢失对话上下文。这是压缩质量下降最常见的原因。

中间轮次使用辅助 LLM 以结构化模板进行摘要:

## Goal [What the user is trying to accomplish] ## Constraints & Preferences [User preferences, coding style, constraints, important decisions] ## Progress ### Done [Completed work — specific file paths, commands run, results] ### In Progress [Work currently underway] ### Blocked [Any blockers or issues encountered] ## Key Decisions [Important technical decisions and why] ## Relevant Files [Files read, modified, or created — with brief note on each] ## Next Steps [What needs to happen next] ## Critical Context [Specific values, error messages, configuration details]

摘要预算随被压缩内容的量动态调整:

  • 公式:content_tokens × 0.20_SUMMARY_RATIO常量)
  • 最小值:2,000 token
  • 最大值:min(context_length × 0.05, 12,000)token

阶段 4:组装压缩后的消息

压缩后的消息列表为:

  1. 头部消息(首次压缩时在系统提示词后追加一条说明)
  2. 摘要消息(角色经过选择以避免连续相同角色违规)
  3. 尾部消息(未修改)

_sanitize_tool_pairs()清理孤立的 tool_call/tool_result 对:

  • 引用已删除调用的工具结果 → 删除
  • 结果已被删除的工具调用 → 注入存根结果

迭代重压缩

在后续压缩中,前一次摘要会连同指令一起传递给 LLM,要求其更新摘要而非从头摘要。这在多次压缩中保留了信息——条目从"进行中"移至"已完成",新进展被添加,过时信息被删除。

压缩器实例上的_previous_summary字段存储最后一次摘要文本以供此用途。

压缩前后示例

压缩前(45 条消息,约 95K token)

[0] system: "You are a helpful assistant..." (system prompt) [1] user: "Help me set up a FastAPI project" [2] assistant: <tool_call> terminal: mkdir project </tool_call> [3] tool: "directory created" [4] assistant: <tool_call> write_file: main.py </tool_call> [5] tool: "file written (2.3KB)" ... 30 more turns of file editing, testing, debugging ... [38] assistant: <tool_call> terminal: pytest </tool_call> [39] tool: "8 passed, 2 failed\n..." (5KB output) [40] user: "Fix the failing tests" [41] assistant: <tool_call> read_file: tests/test_api.py </tool_call> [42] tool: "import pytest\n..." (3KB) [43] assistant: "I see the issue with the test fixtures..." [44] user: "Great, also add error handling"

压缩后(25 条消息,约 45K token)

[0] system: "You are a helpful assistant... [Note: Some earlier conversation turns have been compacted...]" [1] user: "Help me set up a FastAPI project" [2] assistant: "[CONTEXT COMPACTION] Earlier turns were compacted... ## Goal Set up a FastAPI project with tests and error handling ## Progress ### Done - Created project structure: main.py, tests/, requirements.txt - Implemented 5 API endpoints in main.py - Wrote 10 test cases in tests/test_api.py - 8/10 tests passing ### In Progress - Fixing 2 failing tests (test_create_user, test_delete_user) ## Relevant Files - main.py — FastAPI app with 5 endpoints - tests/test_api.py — 10 test cases - requirements.txt — fastapi, pytest, httpx ## Next Steps - Fix failing test fixtures - Add error handling" [3] user: "Fix the failing tests" [4] assistant: <tool_call> read_file: tests/test_api.py </tool_call> [5] tool: "import pytest\n..." [6] assistant: "I see the issue with the test fixtures..." [7] user: "Great, also add error handling"

Prompt 缓存(Anthropic)

来源:agent/prompt_caching.py

通过缓存对话前缀,在多轮对话中将输入 token 成本降低约 75%。使用 Anthropic 的cache_control断点。

策略:system_and_3

Anthropic 每次请求最多允许 4 个cache_control断点。Hermes 使用"system_and_3"策略:

Breakpoint 1: System prompt (stable across all turns) Breakpoint 2: 3rd-to-last non-system message ─┐ Breakpoint 3: 2nd-to-last non-system message ├─ Rolling window Breakpoint 4: Last non-system message ─┘

工作原理

apply_anthropic_cache_control()深拷贝消息并注入cache_control标记:

# Cache marker format marker = {"type": "ephemeral"} # Or for 1-hour TTL: marker = {"type": "ephemeral", "ttl": "1h"}

标记根据内容类型以不同方式应用:

内容类型标记位置
字符串内容转换为[{"type": "text", "text": ..., "cache_control": ...}]
列表内容添加到最后一个元素的字典中
None/空作为msg["cache_control"]添加
工具消息作为msg["cache_control"]添加(仅限原生 Anthropic)

缓存感知设计模式

  1. 稳定的系统提示词:系统提示词是断点 1,在所有轮次中缓存。避免在对话中途修改它(压缩仅在首次压缩时追加一条说明)。

  2. 消息顺序很重要:缓存命中需要前缀匹配。在中间添加或删除消息会使其后所有内容的缓存失效。

  3. 压缩与缓存的交互:压缩后,被压缩区域的缓存失效,但系统提示词缓存保留。滚动 3 消息窗口在 1-2 轮内重新建立缓存。

  4. TTL 选择:默认为5m(5 分钟)。对于用户在轮次之间有较长间隔的长时间会话,使用1h

启用 Prompt 缓存

满足以下条件时,prompt 缓存自动启用:

  • 模型为 Anthropic Claude 模型(通过模型名称检测)
  • 提供商支持cache_control(原生 Anthropic API 或 OpenRouter)
# config.yaml — TTL is configurable (must be "5m" or "1h") prompt_caching: cache_ttl: "5m"

CLI 在启动时显示缓存状态:

💾 Prompt caching: ENABLED (Claude via OpenRouter, 5m TTL)

上下文压力警告

中间上下文压力警告已被移除(参见run_agent.py中的迭代预算块,其中注明:"No intermediate pressure warnings — they caused models to 'give up' prematurely on complex tasks")。压缩在 prompt token 达到配置的compression.threshold(默认 50%)时触发,无需事先警告步骤;gateway 会话清理作为二级安全网在模型上下文窗口的 85% 处触发。

http://www.jsqmd.com/news/995121/

相关文章:

  • 用Python打造你的专属密码生成器:从XKCD风格到命令行工具
  • 企业级数据集成平台架构:基于Kettle的微服务化ETL解决方案
  • 解密FreeBSD 13.2上的OpenMP与ImageMagick问题
  • 2026年杭州GEO优化公司推荐榜:五家主流服务商深度横评,企业选型前建议先看完这篇 - 资讯纵览
  • 3种智能方案:Buzz离线音频转写与翻译完全指南
  • DDrawCompat终极指南:让Windows经典游戏在现代系统上完美运行
  • 通过动态规划优化插电式混合动力电动汽车 (PHEV) 能源管理附Matlab、Simulink代码
  • 干了八年眼镜行业,说点郑州配眼镜不能说的真相 - 配眼镜新资讯
  • 如何在5分钟内掌握Vue Json Pretty:Vue.js JSON数据可视化终极指南
  • 汽车级LCD段码驱动芯片PCA8543:原理、配置与硬件设计实战
  • 微博图片批量下载:无需登录,一键保存高清原图的终极解决方案
  • 技术深度解析:.NET MAUI Community Toolkit - 跨平台开发效率提升的10个实战案例
  • 嵌入式Linux驱动开发 —— 从DTS到代码的桥梁与简单OF系列API(6)
  • 基于multisim的温度测量与控制电路设计
  • MPC8343EA时钟与热管理设计:从PLL配置到散热器选型实战
  • 终极M3U8视频下载指南:如何快速下载和合并HLS流媒体视频
  • 告别鼠标手!用这些Altium Designer 20隐藏快捷键,把你的PCB设计速度提上来
  • MC9S12NE64单芯片以太网微控制器:从硬件设计到低功耗网络节点开发实战
  • 动手实现‘诚实但好奇’云环境下的安全最近邻搜索(Python示例)
  • 【趣解】Tomcat、Nginx、Redis:中间件界的“三剑客“
  • 2026上海GEO优化公司推荐榜:基于真实客户回访数据的深度选型指南 - 资讯纵览
  • 如何实现个性化定制:Mi-Create 为小米穿戴设备打造专属表盘的完整指南
  • 保姆级教程:用Node.js复现拼多多anti_content加密(附完整可运行代码)
  • Figma中文界面汉化插件:5分钟告别英文设计障碍
  • 2026年重庆市场知名小程序开发公司,哪家才是可靠之选? - 资讯纵览
  • 实战指南:5个核心场景深度解析League Toolkit如何提升你的英雄联盟游戏体验
  • 用STC89C52+DS1302+LCD1602做个桌面电子钟,附串口调试和闹钟设置完整代码
  • 云函数平台兼容性探讨
  • OpenCore Legacy Patcher完整指南:4步解决老旧Mac升级难题
  • 终极暗黑3按键助手:D3KeyHelper免费开源工具完整使用指南