跳到主要內容

Agent Loop 內部機制

核心編排引擎是 run_agent.py 中的 AIAgent 類 —— 約 9,200 行代碼,負責從提示詞組裝到工具分發,再到提供方故障轉移的全部流程。

核心職責

AIAgent 負責以下事項:

  • 通過 prompt_builder.py 組裝有效系統提示詞和工具模式
  • 選擇正確的提供方/API 模式(chat_completionscodex_responsesanthropic_messages
  • 支持中斷的模型調用,具備取消支持
  • 執行工具調用(通過線程池實現串行或併發執行)
  • 以 OpenAI 消息格式維護對話歷史
  • 處理壓縮、重試以及備用模型切換
  • 跟蹤父 Agent 和子 Agent 之間的迭代預算
  • 在上下文丟失前刷新持久記憶

兩個入口點

# 簡潔接口——返回最終響應字符串
response = agent.chat("Fix the bug in main.py")

# 完整接口——返回包含消息、元數據和用量統計的字典
result = agent.run_conversation(
user_message="Fix the bug in main.py",
system_message=None, # 如果省略則自動構建
conversation_history=None, # 如果省略則從 session 自動加載
task_id="task_abc123"
)

chat()run_conversation() 的輕量封裝,從結果字典中提取 final_response 字段。

API 模式

Hermes 支持三種 API 執行模式,由提供方選擇、顯式參數和基礎 URL 推斷共同決定:

API 模式用途客戶端類型
chat_completionsOpenAI 兼容端點(OpenRouter、自定義、大多數提供方)openai.OpenAI
codex_responsesOpenAI Codex / Responses APIopenai.OpenAI(使用 Responses 格式)
anthropic_messages原生 Anthropic Messages APIanthropic.Anthropic 通過適配器

模式決定了消息格式、工具調用結構、響應解析方式以及緩存/流式處理機制。三種模式在 API 調用前後均統一為相同的內部消息格式(OpenAI 風格的 role/content/tool_calls 字典)。

模式解析順序:

  1. 顯式 api_mode 構造函數參數(優先級最高)
  2. 提供方特定檢測(例如 anthropic 提供方 → anthropic_messages
  3. 基礎 URL 推斷(例如 api.anthropic.comanthropic_messages
  4. 默認值:chat_completions

輪次生命週期

Agent 循環的每次迭代遵循以下流程:

run_conversation()
1. Generate task_id if not provided
2. Append user message to conversation history
3. Build or reuse cached system prompt (prompt_builder.py)
4. Check if preflight compression is needed (>50% context)
5. Build API messages from conversation history
- chat_completions: OpenAI format as-is
- codex_responses: convert to Responses API input items
- anthropic_messages: convert via anthropic_adapter.py
6. Inject ephemeral prompt layers (budget warnings, context pressure)
7. Apply prompt caching markers if on Anthropic
8. Make interruptible API call (_api_call_with_interrupt)
9. Parse response:
- If tool_calls: execute them, append results, loop back to step 5
- If text response: persist session, flush memory if needed, return

消息格式

所有消息在內部均使用 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
  • 絕不允許連續兩個助理消息
  • 絕不允許連續兩個用戶消息
  • 僅允許 tool 角色擁有連續條目(並行工具結果)

提供方會驗證這些序列,拒絕格式錯誤的歷史記錄。

可中斷的 API 調用

API 請求被封裝在 _api_call_with_interrupt() 中,該函數在後臺線程中運行實際的 HTTP 調用,同時監控中斷事件:

┌──────────────────────┐     ┌──────────────┐
│ Main thread │ │ API thread │
│ wait on: │────▶│ HTTP POST │
│ - response ready │ │ to provider │
│ - interrupt event │ └──────────────┘
│ - timeout │
└──────────────────────┘

當發生中斷時(用戶發送新消息、執行 /stop 命令或接收信號):

  • API 線程被放棄(響應被丟棄)
  • Agent 可處理新輸入或乾淨關閉
  • 不會將部分響應注入對話歷史

工具執行

串行與併發

當模型返回工具調用時:

  • 單個工具調用 → 在主線程中直接執行
  • 多個工具調用 → 通過 ThreadPoolExecutor 併發執行
    • 特例:標記為交互式(如 clarify)的工具強制串行執行
    • 無論完成順序如何,結果均按原始工具調用順序重新插入

執行流程

for each tool_call in response.tool_calls:
1. Resolve handler from tools/registry.py
2. Fire pre_tool_call plugin hook
3. Check if dangerous command (tools/approval.py)
- If dangerous: invoke approval_callback, wait for user
4. Execute handler with args + task_id
5. Fire post_tool_call plugin hook
6. Append {"role": "tool", "content": result} to history

Agent 級工具

某些工具在到達 handle_function_call() 之前由 run_agent.py 攔截:

工具攔截原因
todo讀取/寫入 Agent 本地任務狀態
memory向持久記憶文件寫入,帶字符限制
session_search通過 Agent 的會話數據庫查詢會話歷史
delegate_task啟動子 Agent(s),擁有隔離上下文

這些工具直接修改 Agent 狀態,並返回合成的工具結果,不經過註冊表。

回調接口

AIAgent 支持平臺特定的回調,以在 CLI、網關和 ACP 集成中實現實時進度反饋:

回調函數觸發時機使用方
tool_progress_callback每個工具執行前後CLI 進度條,網關進度消息
thinking_callback模型開始/停止思考時CLI “thinking...” 指示器
reasoning_callback模型返回推理內容時CLI 推理顯示,網關推理塊
clarify_callback調用 clarify 工具時CLI 輸入提示,網關交互消息
step_callback每次完整的 Agent 回合結束後網關步驟追蹤,ACP 進度
stream_delta_callback每次流式傳輸的 token(啟用時)CLI 流式顯示
tool_gen_callback從流中解析出工具調用時CLI 進度條中的工具預覽
status_callback狀態變化時(思考、執行等)ACP 狀態更新

預算與回退行為

迭代預算

Agent 通過 IterationBudget 跟蹤迭代次數:

  • 默認值:90 次迭代(可通過 agent.max_turns 配置)
  • 父 Agent 與子 Agent 共享預算 —— 子 Agent 會消耗父 Agent 的預算
  • 兩級預算壓力機制通過 _get_budget_warning() 實現:
    • 達到 70% 以上使用率(警告級別):在最後一個工具結果中追加 [BUDGET: 迭代 X/Y。剩餘 N 次迭代。開始整合你的工作。]
    • 達到 90% 以上使用率(嚴重警告級別):在最後一個工具結果中追加 [BUDGET WARNING: 迭代 X/Y。僅剩 N 次迭代。立即提供最終響應。]
  • 達到 100% 時,Agent 停止並返回已完成工作的摘要

回退模型

當主模型失敗時(429 速率限制、5xx 服務器錯誤、401/403 認證錯誤):

  1. 檢查配置中的 fallback_providers 列表
  2. 按順序嘗試每個回退提供方
  3. 成功後,使用新提供方繼續對話
  4. 對於 401/403 錯誤,在切換前嘗試刷新憑證

回退系統也獨立覆蓋輔助任務 —— 視覺、壓縮、網頁提取和會話搜索各自擁有可配置的獨立回退鏈,通過 auxiliary.* 配置節進行設置。

壓縮與持久化

壓縮觸發時機

  • 預檢(API 調用前):當對話超過模型上下文窗口的 50%
  • 網關自動壓縮:當對話超過 85%(更激進,運行於回合之間)

壓縮期間發生的情況

  1. 首先將記憶刷新到磁盤(防止數據丟失)
  2. 將中間對話回合總結為緊湊摘要
  3. 保留最後 N 條消息完整(compression.protect_last_n,默認值:20)
  4. 工具調用/結果消息對保持完整(從不拆分)
  5. 生成新的會話譜系 ID(壓縮創建了一個“子”會話)

會話持久化

每次回合結束後:

  • 消息保存到會話存儲(通過 hermes_state.py 使用 SQLite)
  • 記憶更改刷新到 MEMORY.md / USER.md
  • 可通過 /resumehermes chat --resume 重新啟動會話

關鍵源文件

文件用途
run_agent.pyAIAgent 類 —— 完整的 Agent 循環(約 9,200 行)
agent/prompt_builder.py從記憶、技能、上下文文件、個性等組裝系統提示
agent/context_engine.pyContextEngine ABC —— 可插拔的上下文管理
agent/context_compressor.py默認引擎 —— 有損摘要算法
agent/prompt_caching.pyAnthropic 提示緩存標記與緩存指標
agent/auxiliary_client.py輔助 LLM 客戶端,用於輔助任務(視覺、摘要)
model_tools.py工具模式集合,handle_function_call() 分發邏輯