Agent Loop 內部機制
核心編排引擎是 run_agent.py 中的 AIAgent 類 —— 約 9,200 行代碼,負責從提示詞組裝到工具分發,再到提供方故障轉移的全部流程。
核心職責
AIAgent 負責以下事項:
- 通過
prompt_builder.py組裝有效系統提示詞和工具模式 - 選擇正確的提供方/API 模式(
chat_completions、codex_responses、anthropic_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_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. 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 次迭代。立即提供最終響應。]
- 達到 70% 以上使用率(警告級別):在最後一個工具結果中追加
- 達到 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 循環(約 9,200 行) |
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 | 工具模式集合,handle_function_call() 分發邏輯 |