H繁中版
文件開發者指南context compression and caching
<!-- Source: https://hermesbible.com/docs/developer-guide/context-compression-and-caching -->

上下文壓縮與快取

Hermes Agent 使用雙重壓縮系統和 Anthropic 提示快取來在長對話中有效管理上下文視窗使用量。

原始碼檔案:agent/context_engine.py(ABC)、agent/context_compressor.py(預設引擎)、 agent/prompt_caching.pygateway/run.py(會話衛生)、run_agent.py(搜尋 _compress_context

可插拔的上下文引擎

上下文管理建立在 ContextEngine ABC(agent/context_engine.py)之上。內建的 ContextCompressor 是預設實作,但外掛可以用替代引擎(例如無損上下文管理)替換它。

context:
  engine: "compressor"    # 預設——內建有損摘要
  engine: "lcm"           # 範例——提供無損上下文的外掛

引擎負責:

  • 決定何時應觸發壓縮(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 │  在 85% 上下文時觸發
  ─────────────────►  │   (前處理,粗略估計)    │  大型會話的安全網
                      └─────────────┬────────────┘
                                    │
                                    ▼
                      ┌──────────────────────────┐
                      │   Agent ContextCompressor │  在 50% 上下文時觸發(預設)
                      │   (迴圈內,真實 token)  │  正常上下文管理
                      └──────────────────────────┘

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 自身壓縮器的會話

閘道器衛生閾值刻意高於 Agent 的壓縮器。將其設為 50%(與 Agent 相同)會在長閘道器會話中導致每輪都過早壓縮。

2. Agent ContextCompressor(50% 閾值,可設定)

位於 agent/context_compressor.py。這是主要壓縮系統,在 Agent 的工具迴圈內運行,可存取準確的、API 回報的 token 數量。

設定

所有壓縮設定從 config.yamlcompression 鍵讀取:

compression:
  enabled: true              # 啟用/停用壓縮(預設:true)
  threshold: 0.50            # 上下文視窗比例(預設:0.50 = 50%)
  target_ratio: 0.20         # 保留為尾部的閾值比例(預設:0.20)
  protect_last_n: 20         # 最小保護的尾部訊息數(預設:20)
  codex_gpt55_autoraise: true  # Codex OAuth 上的 gpt-5.5:將觸發點提高到 85%(預設:true)

# 摘要模型/供應商在 auxiliary 下設定:
auxiliary:
  compression:
    model: null              # 摘要的覆寫模型(預設:自動偵測)
    provider: auto           # 供應商:"auto"、"openrouter"、"nous"、"main" 等
    base_url: null           # 自訂 OpenAI 相容端點

參數詳解

參數預設值範圍描述
threshold0.500.0-1.0當提示 token ≥ threshold × context_length 時觸發壓縮
target_ratio0.200.10-0.80控制尾部保護 token 預算:threshold_tokens × target_ratio
protect_last_n20≥1永遠保留的最近訊息最小數量
protect_first_n3(硬編碼)系統提示 + 第一次交談永遠保留
codex_gpt55_autoraisetruebool在 ChatGPT Codex OAuth 路由上將 gpt-5.5 的觸發點提高到 85%(見下文)。設為 false 以保持全域 threshold

Codex gpt-5.5 閾值自動提升

ChatGPT Codex OAuth 後端將 gpt-5.5 硬性限制在 272K 上下文視窗(同一模型在 OpenAI 直接 API 和 OpenRouter 上暴露 1.05M,在 GitHub Copilot 上為 400K)。在預設的 50% 觸發點下,壓縮會在約 136K 時觸發——僅為模型實際可使用視窗的一半。當活躍路由為 Codex OAuth(provider: openai-codex)且模型為 gpt-5.5 時,Hermes 將觸發點提高到 85%(約 231K)並印出一次性的通知與退出命令。只有此特定路由受影響;其他供應商上的 gpt-5.5 保持你的全域 threshold。要退回全域值:

hermes config set compression.codex_gpt55_autoraise false

計算值(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

注意——閾值源自主模型的上下文視窗

threshold_tokens 始終為 threshold × context_length,其中 context_length主 Agent 模型的上下文視窗——永遠不是輔助/摘要模型的。在 262,144 token 的模型上使用預設的 0.50,閾值為 262,144 × 0.50 = 131,072。該數字接近常見的「128K 上下文」是百分比的巧合,而非輔助模型視窗是觸發點的標誌。輔助模型的上下文視窗是獨立的問題——參見下方的「摘要模型上下文長度」警告,了解它如何影響摘要是否能產生,而非壓縮何時觸發。

壓縮演算法

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

階段 1:修剪舊工具結果(低成本,無 LLM 呼叫)

受保護尾部之外的舊工具結果(>200 字元)被替換為:

[Old tool output cleared to save context space]

這是一個低成本的預掃描,從冗長的工具輸出(檔案內容、終端機輸出、搜尋結果)中節省大量 token。

階段 2:確定邊界

┌─────────────────────────────────────────────────────────────┐
│  Message list                                               │
│                                                             │
│  [0..2]  ← protect_first_n(system + 第一次交談)            │
│  [3..N]  ← 中間輪次 → 已摘要                                │
│  [N..end] ← 尾部(按 token 預算或 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
[使用者想要完成什麼]

## Constraints &amp; Preferences
[使用者偏好、編碼風格、限制、重要決策]

## Progress
### Done
[已完成的工作——具體檔案路徑、執行的命令、結果]
### In Progress
[目前進行中的工作]
### Blocked
[遇到的任何阻礙或問題]

## Key Decisions
[重要的技術決策及原因]

## Relevant Files
[讀取、修改或建立的檔案——附簡要說明]

## Next Steps
[接下來需要做什麼]

## Critical Context
[具體值、錯誤訊息、設定細節]

摘要預算隨被壓縮的內容量成比例:

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

階段 4:組裝壓縮後的訊息

壓縮後的訊息列表為:

  1. 頭部訊息(第一次壓縮時在系統提示上附加註記)
  2. 摘要訊息(選擇角色以避免連續相同角色違反)
  3. 尾部訊息(未修改)

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

  • 引用已移除呼叫的工具結果 → 移除
  • 結果已被移除的工具呼叫 → 注入存根結果

迭代式重新壓縮

在後續壓縮中,先前的摘要被傳遞給 LLM 並附帶更新它的指示,而非從頭摘要。這在多次壓縮之間保留了資訊——項目從「In Progress」移動到「Done」,新增了新進度,過時的資訊被移除。

壓縮器實例上的 _previous_summary 欄位儲存了上次的摘要文字以供此用途。

前/後範例

壓縮前(45 則訊息,約 95K tokens)

[0] system:    "You are a helpful assistant..."(系統提示)
[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 輪的檔案編輯、測試、除錯 ...
[38] assistant: <tool_call> terminal: pytest </tool_call>
[39] tool:      "8 passed, 2 failed\n..."  (5KB 輸出)
[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 tokens)

[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"

提示快取(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           (跨所有輪次穩定)
Breakpoint 2: 倒數第三個非系統訊息    ─┐
Breakpoint 3: 倒數第二個非系統訊息     ├─ 滾動視窗
Breakpoint 4: 最後一個非系統訊息       ─┘

運作方式

apply_anthropic_cache_control() 深複製訊息並注入 cache_control 標記:

# 快取標記格式
marker = {"type": "ephemeral"}
# 或 1 小時 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

啟用提示快取

當以下條件滿足時,提示快取自動啟用:

  • 模型是 Anthropic Claude 模型(透過模型名稱偵測)
  • 供應商支援 cache_control(原生 Anthropic API 或 OpenRouter)
# config.yaml——TTL 可設定(必須為 "5m" 或 "1h")
prompt_caching:
  cache_ttl: "5m"

CLI 在啟動時顯示快取狀態:

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

上下文壓力警告

中間上下文壓力警告已被移除(參見 run_agent.py 中的迭代預算區塊,其中註記:「無中間壓力警告——它們導致模型在複雜任務上過早『放棄』」)。當提示 token 達到設定的 compression.threshold(預設 50%)時觸發壓縮,無之前的警告步驟;閘道器會話衛生作為次要安全網在模型上下文視窗的 85% 時觸發。



閘道器內部