Skip to main content

Agent 循环内部机制

核心编排引擎是 run_agent.pyAIAgent 类——大约 9,200 行代码,处理从提示词组装到工具分发再到提供商故障转移的所有事务。

核心职责

AIAgent 负责:

  • 通过 prompt_builder.py 组装有效的系统提示词和工具 schema
  • 选择正确的提供商/API 模式(chat_completions, codex_responses, anthropic_messages)
  • 进行支持取消的可中断模型调用
  • 执行工具调用(通过线程池顺序或并发执行)
  • 以 OpenAI 消息格式维护对话历史
  • 处理压缩、重试和 fallback 模型切换
  • 跟踪父 agent 和子 agent 之间的迭代预算
  • 在上下文丢失之前刷新持久化内存

两个入口点

# Simple interface — returns final response string
response = agent.chat("Fix the bug in main.py")

# Full interface — returns dict with messages, metadata, usage stats
result = agent.run_conversation(
user_message="Fix the bug in main.py",
system_message=None, # auto-built if omitted
conversation_history=None, # auto-loaded from session if omitted
task_id="task_abc123"
)

chat()run_conversation() 的一个薄包装器,用于从结果 dict 中提取 final_response 字段。

API 模式

Hermes 支持三种 API 执行模式,通过提供商选择、显式参数和 base URL 启发式方法解析:

API 模式用途客户端类型
chat_completionsOpenAI 兼容端点(OpenRouter, custom, 大多数提供商)openai.OpenAI
codex_responsesOpenAI Codex / Responses API带有 Responses 格式的 openai.OpenAI
anthropic_messages原生 Anthropic Messages API通过 adapter 的 anthropic.Anthropic

该模式决定消息如何格式化、工具调用如何结构化、响应如何解析以及缓存/流式传输如何工作。三者都在 API 调用前后汇聚到相同的内部消息格式(OpenAI 风格的 role/content/tool_calls dicts)。

模式解析顺序:

  1. 显式 api_mode 构造函数参数(最高优先级)
  2. 特定提供商检测(例如 anthropic 提供商 → anthropic_messages
  3. Base 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 (with tool_calls) → Tool → Tool → ... → Assistant
  • 绝不连续两条 assistant 消息
  • 绝不连续两条 user 消息
  • 只有 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 可以处理新输入或干净地关闭
  • 没有部分响应被注入到对话历史中

工具执行

顺序 vs 并发

当模型返回工具调用时:

  • 单个工具调用 → 直接在主线程中执行
  • 多个工具调用 → 通过 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 的 session DB 查询会话历史
delegate_task生成具有隔离上下文的子 agent

这些工具直接修改 agent 状态并返回合成工具结果,无需经过 registry。

回调接口

AIAgent 支持特定平台的回调,从而在 CLI、gateway 和 ACP 集成中实现实时进度:

回调触发时机使用者
tool_progress_callback每次工具执行前/后CLI spinner, gateway 进度消息
thinking_callback模型开始/停止思考时CLI "thinking..." 指示器
reasoning_callback模型返回推理内容时CLI 推理显示,gateway 推理块
clarify_callbackclarify 工具被调用时CLI 输入提示,gateway 交互式消息
step_callback每次完整 agent 回合后Gateway 步骤跟踪,ACP 进度
stream_delta_callback每个流式 token(启用时)CLI 流式显示
tool_gen_callback当从流中解析工具调用时CLI spinner 中的工具预览
status_callback状态变更(思考、执行等)ACP 状态更新

预算和 Fallback 行为

迭代预算

agent 通过 IterationBudget 跟踪迭代:

  • 默认:90 次迭代(可通过 agent.max_turns 配置)
  • 在父 agent 和子 agent 之间共享——子 agent 消耗父 agent 的预算
  • 通过 _get_budget_warning() 的两级预算压力:
    • 使用率 70%+(caution 级):将 [BUDGET: Iteration X/Y. N iterations left. Start consolidating your work.] 附加到最后一个工具结果
    • 使用率 90%+(warning 级):附加 [BUDGET WARNING: Iteration X/Y. Only N iteration(s) left. Provide your final response NOW.]
  • 达到 100% 时,agent 停止并返回已完成工作的摘要

Fallback 模型

当主模型失败时(429 速率限制,5xx 服务器错误,401/403 认证错误):

  1. 检查 config 中的 fallback_providers 列表
  2. 按顺序尝试每个 fallback
  3. 成功后,与新提供商继续对话
  4. 遇到 401/403 时,在故障转移之前尝试刷新凭证

fallback 系统还独立覆盖辅助任务——vision、compression、web extraction 和 session search 各自拥有自己的 fallback 链,可通过 auxiliary.* config 部分配置。

压缩和持久化

压缩触发时机

  • Preflight(API 调用前):如果对话超过模型 context window 的 50%
  • Gateway 自动压缩:如果对话超过 85%(更激进,在回合之间运行)

压缩期间发生什么

  1. 内存首先刷新到磁盘(防止数据丢失)
  2. 中间对话回合被总结为紧凑的摘要
  3. 最后 N 条消息保持完整(compression.protect_last_n,默认:20)
  4. 工具调用/结果消息对保持在一起(绝不拆分)
  5. 生成新的 session lineage ID(压缩创建“子”session)

Session 持久化

每个回合后:

  • 消息保存到 session store(通过 hermes_state.py 的 SQLite)
  • 内存变更刷新到 MEMORY.md / USER.md
  • session 稍后可通过 /resumehermes chat --resume 恢复

关键源文件

文件用途
run_agent.pyAIAgent 类——完整的 agent 循环(~9,200 行)
agent/prompt_builder.py从 memory、skills、context 文件、personality 组装系统提示词
agent/context_engine.pyContextEngine ABC——可插拔上下文管理
agent/context_compressor.py默认引擎——有损摘要算法
agent/prompt_caching.pyAnthropic 提示词缓存标记和缓存指标
agent/auxiliary_client.py用于侧边任务(vision, summarization)的辅助 LLM 客户端
model_tools.py工具 schema 收集,handle_function_call() 分发

相关文档