hermes解读

Hermes Agent Loop 深度解读

一、整体架构

run_agent.py 是 hermes-agent 的核心文件(约 10,500 行),实现了 AIAgent 类------一个支持多模型、多 API 协议、多工具调用的 AI Agent 编排器。

复制代码
用户输入 → run_conversation() → 主循环(while) → LLM API 调用 → 工具执行 → 循环/终止

其中Thought 藏在 "LLM API 调用" 这一步里面。

一次 API 调用,模型同时返回了 Thought + Action:

assistant_message = response.choices0.message

.content → Thought(推理文本)

.reasoning → Thought(结构化推理)

.tool_calls → Action(工具调用)

所以之前的流程图没有错,只是粒度不够细------LLM API 调用 = Thought + Action,它们不是两步,而是同一个 response 的不同字段。

主循环(while)

├── ① LLM API 调用 → 同时返回 Thought + Action

├── ② 工具执行 → 产生 Observation

└── ③ 回到循环顶部 → 带着 Observation 继续


二、Agent Loop 核心流程

2.1 入口:run_conversation() (行 7506)

这是 Agent Loop 的唯一入口,接收用户消息并返回完整对话结果。核心流程如下:

复制代码
run_conversation(user_message)
│
├── 1. 初始化与预处理
│   ├── 安全 stdio 替换(防管道断裂)
│   ├── 恢复主运行时(如上一轮激活了 fallback)
│   ├── 清理 surrogates 字符(防止 JSON 序列化崩溃)
│   ├── 重置各类重试计数器
│   ├── 清理失效 TCP 连接
│   └── 重建 iteration budget
│
├── 2. 构建/恢复 System Prompt
│   ├── 首次会话:从零构建(_build_system_prompt)
│   └── 续接会话:从 SQLite 恢复(保持 cache prefix 一致)
│
├── 3. Preflight 上下文压缩
│   └── 若历史消息已超阈值 → 主动压缩(最多 3 轮)
│
├── 4. Plugin 钩子:pre_llm_call
│   └── 将插件上下文注入 user message(非 system prompt,保护 cache prefix)
│
└── 5. 主循环 while (api_call_count < max_iterations)
    │
    ├── 检查中断请求
    ├── 消耗 iteration budget
    ├── 准备 API 消息(注入 memory、plugin 上下文)
    ├── 应用 Anthropic prompt caching
    ├── 消息规范化(JSON 排序、去除无效字段)
    │
    ├── 内层 retry 循环 (最多 3 次)
    │   ├── 构建 API 参数(_build_api_kwargs)
    │   ├── 执行 API 调用(优先 streaming)
    │   ├── 处理各类异常
    │   │   ├── 429 限流 → 指数退避 + fallback
    │   │   ├── 上下文长度溢出 → 压缩后重试
    │   │   ├── 认证失败 → 特定 provider 重试
    │   │   └── 空响应 → 重试 + fallback
    │   └── 成功 → break 退出 retry 循环
    │
    ├── 处理 API 响应
    │   ├── 规范化响应(支持 chat_completions / codex_responses / anthropic_messages)
    │   └── 标准化 content 为 string
    │
    ├── 分支判断
    │   ├── 【有 tool_calls】→ 工具执行路径
    │   └── 【无 tool_calls】→ 最终响应路径
    │
    ├── 工具执行路径
    │   ├── 校验工具名称(自动修复 → 3 次无效则中止)
    │   ├── 校验 JSON 参数(无效则重试 → 注入错误让模型自纠)
    │   ├── 去重 + 限制 delegate_task 调用数
    │   ├── 构建 assistant message 并追加到 messages
    │   ├── 执行工具(_execute_tool_calls)
    │   │   ├── 判断是否可并行(_should_parallelize_tool_batch)
    │   │   ├── 并行路径:ThreadPoolExecutor
    │   │   └── 串行路径:逐个执行
    │   ├── 追加 tool results 到 messages
    │   ├── 上下文压力预警(85% 橙色、95% 红色)
    │   ├── 判断是否需要上下文压缩
    │   └── continue → 回到主循环顶部
    │
    └── 最终响应路径
        ├── 检查空内容(优先使用前一轮附带内容)
        ├── thinking-only 响应 → prefill 继续最多 2 次
        ├── 真正空响应 → 重试 3 次 → 尝试 fallback
        ├── Codex 中间确认 → 继续推送
        ├── 截断续接(truncated_response_prefix)
        ├── 清理 think blocks
        └── break → 退出主循环

2.2 循环退出条件

退出原因 触发条件 处理方式
text_response 模型返回纯文本(无 tool_calls) 正常结束,返回最终响应
interrupted_by_user 用户发送中断信号 保存会话,返回中断状态
budget_exhausted iteration budget 用完 注入 grace 消息请求总结
max_iterations_reached API 调用次数达上限 调用 _handle_max_iterations 生成摘要
all_retries_exhausted 3 次重试全失败 返回错误
empty_response_exhausted 多次空响应 + fallback 耗尽 返回 "(empty)"
error_near_max_iterations 接近上限时出错 返回错误消息
各种 truncation 输出被截断且无法恢复 返回 partial 结果

2.3 Grace Call 机制

当 iteration budget 耗尽时,不是直接退出,而是注入一条用户消息让模型总结:

复制代码
"Your tool budget ran out. Please give me the information or actions you've completed so far."

给模型一次额外的 API 调用机会产出文本回复。


三、三大 API 协议适配

hermes 支持三种 API 协议,通过 api_mode 自动检测和切换:

api_mode 触发条件 响应解析方式
chat_completions 默认;OpenAI 兼容端点 response.choices[0].message
codex_responses OpenAI GPT-5.x、直接 OpenAI URL _normalize_codex_response()
anthropic_messages api.anthropic.com、URL 以 /anthropic 结尾 normalize_anthropic_response()

自动检测逻辑在 __init__ 中,优先级:显式指定 > provider 名称 > URL 特征


四、工具执行系统

4.1 工具分发 (_execute_tool_calls)

复制代码
_execute_tool_calls(assistant_message, messages, ...)
│
├── 判断是否可并行化 (_should_parallelize_tool_batch)
│   ├── 只读工具 → 允许并行
│   ├── 读/写工具路径不重叠 → 允许并行
│   └── 否则 → 串行执行
│
├── 并行路径 (_execute_tool_calls_concurrent)
│   └── ThreadPoolExecutor(max_workers=min(num_tools, _MAX_TOOL_WORKERS))
│       └── 每个工具一个 _run_tool 线程
│           └── _invoke_tool(name, args, task_id, call_id)
│
└── 串行路径 (_execute_tool_calls_sequential)
    └── 逐个调用 _invoke_tool + 逐个追加结果

4.2 工具路由 (_invoke_tool)

工具名称 路由目标 说明
todo todo_tool 内置 todo 管理
session_search session_search 会话搜索
memory memory_tool 内置记忆 + 通知外部 memory provider
clarify clarify_tool 交互式澄清(需 callback)
delegate_task delegate_task 子 Agent 委派
外部 memory 工具 memory_manager.handle_tool_call 外部记忆 Provider
其他 handle_function_call 通用注册工具(文件、终端、浏览器等)

4.3 Checkpoint 机制

对文件变更工具(write_file, patch)和破坏性终端命令,在执行前自动创建 checkpoint:

python 复制代码
if function_name in ("write_file", "patch") and self._checkpoint_mgr.enabled:
    self._checkpoint_mgr.ensure_checkpoint(work_dir, f"before {function_name}")

五、上下文管理

5.1 上下文压缩

ContextCompressor 驱动,在多个时机触发:

触发时机 条件 说明
Preflight 压缩 进入主循环前,历史消息已超阈值 最多 3 轮压缩
运行中压缩 工具执行后 should_compress() 返回 True 基于真实 token 计数
API 错误触发 收到 context_length 错误 自动压缩后重试

压缩策略:保护最早 N 条 + 最近 N 条消息,中间部分被摘要替换。

5.2 Prompt Caching

对 Claude 模型通过 OpenRouter 使用时,自动注入 cache_control 断点(system + 最近 3 条消息),缓存命中率可达 ~75%。

关键设计:Plugin 上下文注入 user message 而非 system prompt,保持 system prompt 稳定以利用 prefix cache。

5.3 上下文压力预警

进度 等级 表现
≥ 85% 🟠 橙色警告 提醒用户上下文即将满
≥ 95% 🔴 红色严重 强烈警告,压缩即将触发

同一会话 300 秒冷却期,不重复警告。


六、容错与恢复

6.1 多层重试机制

错误类型 最大重试 恢复策略
API 限流 (429) 3 次 指数退避 + jitter (5s~120s)
上下文溢出 3 次 压缩消息后重试
无效工具名 3 次 返回可用工具列表让模型自纠
无效 JSON 参数 3 次 重试 → 注入错误让模型自纠
空响应 3 次 重试 → fallback provider
不完整 scratchpad 2 次 不追加消息,直接重试
Codex incomplete 3 次 追加中间消息后继续
Thinking-only 2 次 prefill 继续让模型产出文本
截断 (length) 3 次 续接指令继续输出

6.2 Fallback Provider Chain

当主 provider 持续失败时,自动切换到 fallback 链中的下一个:

python 复制代码
self._try_activate_fallback()    # 切换到下一个 provider
self._restore_primary_runtime()  # 下一轮恢复主 provider

触发场景:空响应、429 限流、上下文溢出无法压缩、认证失败等。

6.3 连接健康检查

每次对话开始前清理失效 TCP 连接:

python 复制代码
self._cleanup_dead_connections()  # 检测并清理僵尸 socket

七、Iteration Budget 管理

属性/方法 类型 说明
max_total int 总预算(默认 90)
used int 已消耗次数
remaining int 剩余次数
consume() 方法 消耗 1 次,返回是否成功
refund() 方法 退还 1 次(execute_code 调用免费)
  • 父 Agent 创建 budget,子 Agent(delegate_task)共享同一个实例
  • execute_code 工具调用自动 refund(成本极低)
  • Budget 耗尽时触发 Grace Call 机制

八、记忆系统

8.1 内置记忆

工具 功能 说明
memory add / replace / read 基于文件存储,写入时同步通知外部 provider
session_search 搜索历史对话 在会话数据库中检索
定期 nudge 每隔 N 轮提醒 提醒模型检查记忆

8.2 外部记忆 Provider

通过 MemoryManager 接入:

操作 时机 说明
Prefetch 每轮开始前 基于用户查询预取相关记忆,注入到 user message
Write-back 内置 memory 工具写入时 同步通知外部 provider

九、插件系统

通过 hermes_cli.plugins.invoke_hook 实现:

钩子名称 触发时机 用途
on_session_start 新会话创建时 初始化会话状态
pre_llm_call 每轮 LLM 调用前 注入额外上下文到 user message
pre_api_request 每次 API 请求前 监控/计量
post_api_request 每次 API 请求后 监控/计量

十、总结:Agent Loop 设计亮点

# 设计亮点 详细说明
1 🔵 三协议统一 用 api_mode 抽象层统一处理 OpenAI Chat Completions / Codex Responses / Anthropic Messages,上层逻辑无感知
2 🟢 防御性编程极强 几乎每个可能失败的操作都有 try/except、重试、fallback,从管道断裂到 JSON 解析失败到连接失效全覆盖
3 🟠 上下文生命周期 Preflight 压缩 → 运行中压缩 → 压力预警 → Grace Call,形成完整的上下文溢出防护链
4 🔵 工具并行化 智能判断只读/读写工具的路径依赖,自动选择并行或串行执行
5 🟢 Cache-friendly System prompt 只在首次构建后缓存复用,Plugin 上下文注入 user message,最大化 prefix cache 命中率
6 🟠 Budget 共享 父子 Agent 共享 iteration budget,子 Agent 不会无限消耗资源
相关推荐
过期的秋刀鱼!1 小时前
使用都热编码的分类特征
人工智能·算法·决策树·机器学习·分类·数据挖掘
wabs6661 小时前
关于哈希表【力扣383.赎金信的思考】
算法·leetcode·散列表
love_muming2 小时前
二叉树操作全解析:从递归到层序遍历
java·数据结构·算法·二叉树
.道阻且长.10 小时前
2.LeetCode算法习题讲解--双指针--复写零
算法·leetcode·职场和发展
To_OC12 小时前
LC 438 找到所有字母异位词:暴力超时后,我靠滑动窗口一招搞定
javascript·算法·leetcode
Forever Nore15 小时前
学完C语言力扣第一题做不来正常吗
数据结构·算法
hansang_IR15 小时前
【题解】LC:倍增 / 区间并查集(Range Parallel Unionfind)
c++·算法·并查集
Tisfy17 小时前
LeetCode 3731.找出缺失的元素:哈希 / 排序
算法·leetcode·哈希算法·排序·哈希表