核心協調引擎是 run_agent.py 的 AIAgent 類別——一個大型檔案,處理從提示組裝到工具分派到供應商降級的所有事務。
核心職責
AIAgent 負責:
- 透過
prompt_builder.py組裝有效的系統提示和工具 schema - 選擇正確的供應商/API 模式(chat_completions、codex_responses、anthropic_messages)
- 執行可中斷的模型呼叫,支援取消
- 執行工具呼叫(透過執行緒池依序或並行)
- 以 OpenAI 訊息格式維護對話歷史
- 處理壓縮、重試和降級模型切換
- 追蹤父代與子代 Agent 的迭代預算
- 在上下文遺失前將持久化記憶刷新至磁碟
兩個進入點
# 簡單介面——回傳最終回應字串
response = agent.chat("Fix the bug in main.py")
# 完整介面——回傳包含 messages、metadata、usage stats 的字典
result = agent.run_conversation(
user_message="Fix the bug in main.py",
system_message=None, # 若省略則自動建構
conversation_history=None, # 若省略則從會話自動載入
task_id="task_abc123"
)
chat() 是 run_conversation() 的薄包裝,從結果字典中提取 final_response 欄位。
API 模式
Hermes 支援三種 API 執行模式,根據供應商選擇、明確參數和基礎 URL 啟發式規則進行解析:
| API 模式 | 用途 | 用戶端類型 |
|---|---|---|
chat_completions | OpenAI 相容端點(OpenRouter、自訂、大多數供應商) | openai.OpenAI |
codex_responses | OpenAI Codex / Responses API | openai.OpenAI 搭配 Responses 格式 |
anthropic_messages | 原生 Anthropic Messages API | anthropic.Anthropic 透過適配器 |
模式決定了訊息的格式、工具呼叫的結構、回應的解析方式,以及快取/串流的運作方式。三種模式在 API 呼叫前後都收斂為相同的內部訊息格式(OpenAI 風格的 role/content/tool_calls 字典)。
模式解析順序:
- 明確的
api_mode建構子參數(最高優先順序) - 供應商特定偵測(例如
anthropic供應商 →anthropic_messages) - 基礎 URL 啟發式規則(例如
api.anthropic.com→anthropic_messages) - 預設:
chat_completions
一輪生命週期
Agent 迴圈的每次迭代遵循以下序列:
run_conversation()
1. 若未提供則產生 task_id
2. 將使用者訊息附加至對話歷史
3. 建構或重用快取的系統提示(prompt_builder.py)
4. 檢查是否需要預飛壓縮(>50% 上下文)
5. 從對話歷史建構 API 訊息
- chat_completions:OpenAI 格式原樣使用
- codex_responses:轉換為 Responses API 輸入項目
- anthropic_messages:透過 anthropic_adapter.py 轉換
6. 注入臨時提示層(預算警告、上下文壓力)
7. 若使用 Anthropic 則套用提示快取標記
8. 執行可中斷的 API 呼叫(_interruptible_api_call)
9. 解析回應:
- 若有 tool_calls:執行它們、附加結果、回到步驟 5
- 若為文字回應:持久化會話、需要時刷新記憶、回傳
訊息格式
所有訊息在內部使用 OpenAI 相容格式:
{"role": "system", "content": "..."}
{"role": "user", "content": "..."}
{"role": "assistant", "content": "...", "tool_calls": [...]}
{"role": "tool", "tool_call_id": "...", "content": "..."}
推理內容(來自支援擴展思考的模型)儲存在 assistant_msg["reasoning"] 中,並可選擇透過 reasoning_callback 顯示。
訊息交替規則
Agent 迴圈強制嚴格的訊息角色交替:
- 在系統訊息之後:
User → Assistant → User → Assistant → ... - 在工具呼叫期間:
Assistant(帶 tool_calls)→ Tool → Tool → ... → Assistant - 永遠不要連續兩則 assistant 訊息
- 永遠不要連續兩則 user 訊息
- 只有
tool角色可以有連續項目(並行工具結果)
供應商會驗證這些序列,並拒絕格式不正確的歷史。
可中斷的 API 呼叫
API 請求被包裝在 _interruptible_api_call() 中,它在背景執行緒中執行實際的 HTTP 呼叫,同時監聽中斷事件:
┌────────────────────────────────────────────────────┐
│ Main thread API thread │
│ │
│ wait on: HTTP POST │
│ - response ready ───▶ to provider │
│ - interrupt event │
│ - timeout │
└────────────────────────────────────────────────────┘
當被中斷時(使用者傳送新訊息、/stop 命令或訊號):
- API 執行緒被捨棄(回應被丟棄)
- Agent 可以處理新輸入或乾淨地關閉
- 不會有任何部分回應被注入對話歷史
工具執行
依序 vs 並行
當模型回傳工具呼叫時:
- 單一工具呼叫 → 直接在主執行緒中執行
- 多個工具呼叫 → 透過
ThreadPoolExecutor並行執行- 例外:標記為互動式的工具(例如
clarify)強制依序執行 - 結果按原始工具呼叫順序重新插入,不論完成順序
- 例外:標記為互動式的工具(例如
執行流程
for each tool_call in response.tool_calls:
1. 從 tools/registry.py 解析處理器
2. 觸發 pre_tool_call 外掛 hook
3. 檢查是否為危險指令(tools/approval.py)
- 若危險:呼叫 approval_callback,等待使用者
4. 以 args + task_id 執行處理器
5. 觸發 post_tool_call 外掛 hook
6. 將 {"role": "tool", "content": result} 附加至歷史
Agent 層級工具
部分工具在到達 handle_function_call() 之前就被 run_agent.py 攔截:
| 工具 | 攔截原因 |
|---|---|
todo | 讀寫 Agent 本地的任務狀態 |
memory | 寫入有字元限制的持久化記憶檔案 |
session_search | 透過 Agent 的會話資料庫查詢會話歷史 |
delegate_task | 以隔離的上下文產生子代理 |
這些工具直接修改 Agent 狀態,並在不經過註冊表的情況下回傳合成的工具結果。
回呼介面
AIAgent 支援平台特定的回呼,用於在 CLI、閘道器和 ACP 整合中提供即時進度:
| 回呼 | 觸發時機 | 使用者 |
|---|---|---|
tool_progress_callback | 每次工具執行的前後 | CLI 旋轉器、閘道器進度訊息 |
thinking_callback | 模型開始/停止思考時 | CLI「思考中...」指示器 |
reasoning_callback | 模型回傳推理內容時 | CLI 推理顯示、閘道器推理區塊 |
clarify_callback | clarify 工具被呼叫時 | CLI 輸入提示、閘道器互動訊息 |
step_callback | 每次完整 Agent 輪次結束後 | 閘道器步驟追蹤、ACP 進度 |
stream_delta_callback | 每個串流 token(啟用時) | CLI 串流顯示 |
tool_gen_callback | 工具呼叫從串流中被解析時 | CLI 在旋轉器中預覽工具 |
status_callback | 狀態變更(thinking、executing 等) | ACP 狀態更新 |
預算與降級行為
迭代預算
Agent 透過 IterationBudget 追蹤迭代:
- 預設:90 次迭代(可透過
agent.max_turns設定) - 每個 Agent 有自己的預算。子代理獲得獨立的預算,上限為
delegation.max_iterations(預設 50)——父代與子代理的總迭代次數可能超過父代的上限 - 達到 100% 時,Agent 停止並回傳已完成工作的摘要
降級模型
當主模型失敗時(429 速率限制、5xx 伺服器錯誤、401/403 認證錯誤):
- 檢查設定中的
fallback_providers列表 - 按順序嘗試每個降級選項
- 成功後,使用新供應商繼續對話
- 遇到 401/403 時,嘗試刷新憑證後再降級
降級系統也獨立涵蓋輔助任務——視覺、壓縮和網頁提取各自有獨立的降級鏈,透過 auxiliary.* 設定區塊進行配置。
壓縮與持久化
壓縮何時觸發
- 預飛(API 呼叫前):若對話超過模型上下文視窗的 50%
- 閘道器自動壓縮:若對話超過 85%(更積極,在輪次之間執行)
壓縮期間發生的事
- 記憶首先被刷新至磁碟(防止資料遺失)
- 中間對話輪次被摘要為簡潔的摘要
- 最後 N 則訊息被完整保留(
compression.protect_last_n,預設:20) - 工具呼叫/結果訊息對保持在一起(永不拆分)
- 產生新的會話血緣 ID(壓縮建立「子」會話)
會話持久化
每次輪次後:
- 訊息被儲存到會話儲存(透過
hermes_state.py的 SQLite) - 記憶變更被刷新至
MEMORY.md/USER.md - 會話可透過
/resume或hermes chat --resume稍後恢復
關鍵原始碼檔案
| 檔案 | 用途 |
|---|---|
run_agent.py | AIAgent 類別——完整的 Agent 迴圈 |
agent/prompt_builder.py | 從記憶、技能、上下文檔案、個性建構系統提示 |
agent/context_engine.py | ContextEngine ABC——可插拔的上下文管理 |
agent/context_compressor.py | 預設引擎——有損摘要演算法 |
agent/prompt_caching.py | Anthropic 提示快取標記和快取指標 |
agent/auxiliary_client.py | 輔助 LLM 用戶端,處理副任務(視覺、摘要) |
model_tools.py | 工具 schema 收集、handle_function_call() 分派 |