Hermes Agent 源码解析(三):Agent 主循环 —— run_agent.py 的核心秘密

Hermes Agent 源码解析(三):Agent 主循环 ------ run_agent.py 的核心秘密

run_agent.py 4123 行,是 AIAgent 类的主场。构造器 60 个参数、同步循环、budget tracking、provider 无关设计------这篇把心脏拆开看。

AIAgent 构造

构造器签名有约 60 个参数 (通过 agent/agent_init.pyinit_agent() 转发器实现),核心分类:

python 复制代码
class AIAgent:
    def __init__(self,
        # 模型连接 / 模式选择 / 运行边界
        base_url=None, provider=None, model="",
        api_mode="chat_completions",
        max_iterations=90,
        enabled_toolsets=None,
        session_id=None,
        # 预算 / 回调 / credential_pool / checkpoints ...
        iteration_budget=None,
        tool_progress_callback=None,
    ): ...

构造器实际是代理模式 ------ __init__ 把所有参数转发给 agent.agent_init.init_agent(),这样 agent_init 可以在不干扰 AIAgent 类签名的情况下,自由重组初始化逻辑。

run_conversation() ------ 核心循环

这是 Agent 的主循环,完全同步(Synchronous),设计非常精炼:

python 复制代码
while (api_call_count < self.max_iterations
       and self.iteration_budget.remaining > 0) \
        or self._budget_grace_call:
    if self._interrupt_requested:
        break
    response = client.chat.completions.create(
        model=model, messages=messages, tools=tool_schemas,
    )
    if response.tool_calls:
        for tc in response.tool_calls:
            messages.append(tool_result_message(
                handle_function_call(tc.name, tc.args, task_id)))
        api_call_count += 1
    else:
        return response.content

循环的三个关键机制

1. Budget Trackingiteration_budget 跟踪已消耗的 token 和调用次数,防止无限循环。_budget_grace_call 留出一轮的"宽限" ------ 当 budget 刚好耗尽但模型正要输出有用结果时不强行中断。

这里有个反直觉的设计点:grace call 不加 budget。如果是无限循环,最后一步也会耗完 budget,不存在逃逸。这比在后处理阶段处理边界情况要干净得多。

2. Interrupt 机制_interrupt_requested 标志由外部线程设置(CLI 的 Ctrl+C 或 Gateway 的 /stop 命令),循环在下一次迭代开始时干净退出,不留僵尸进程。

3. Tool Call 批处理 :一次 LLM 响应可能包含多个 tool_calls(parallel tool calling),循环逐个执行并追加结果,再发回模型继续推理。

消息格式

遵循 OpenAI 标准消息格式:

python 复制代码
# system → 系统提示词 / user → 用户输入
# assistant → 模型回复(可能含 tool_calls)
# tool → 工具执行结果
# reasoning → 推理内容存储在 assistant_msg["reasoning"]

三对接口

AIAgent 暴露三种使用方式:

python 复制代码
# 1. 简单:response = agent.chat("你好")
# 2. 完整:result = agent.run_conversation("写个脚本")
# 3. 低层:agent.add_user_message("开始"); agent.run()

chat()run_conversation() 的薄封装;run_conversation() 是核心循环;低层接口供 Gateway 等需要精细控制会话生命周期的调用者使用。

延迟导入优化

run_agent.py 顶部有一个值得注意的优化 ------ OpenAI SDK 不在模块顶层 import:

python 复制代码
# NOTE: `from openai import OpenAI` 故意不在模块顶层
# SDK 导入耗时约 240ms,通过薄代理对象在首次调用时做真正 import

这个模式保证了 hermes --help 等不需要 LLM 的操作不会被 SDK 加载拖慢。

Provider 无关的设计

run_conversation() 不关心背后是哪个 provider。_create_openai_client() 根据运行时配置多态构造:

ini 复制代码
provider → resolution → client
  ├─ OpenAI    → openai.OpenAI(base_url=...)
  ├─ Anthropic → proxy-compatible client
  ├─ Gemini / Bedrock / Azure ...
  └─ 10+ plugin providers → 统一接口

所有 provider 都暴露同一套 client.chat.completions.create() 接口 ------ 这是架构上最优雅的设计之一。

这个统一接口意味着:换 provider 对 run_conversation() 完全透明。逻辑层不关心你是走 OpenAI 还是 Anthropic,甚至不关心你是 REST 还是 gRPC------适配器层把差异消化干净了。

完整源码见 NousResearch/hermes-agent 下的 run_agent.py


欢迎一起讨论! 60 个参数的构造器你觉得是必要之恶还是应该拆解?

📢 关注我,获取最新开源更新日志和开发者工具动态。如果觉得这篇文章有用,欢迎转发给身边的开发者朋友。

#源码解析 #HermesAgent #Agent循环 #开源项目

下一篇我们聚焦工具系统 ------ 70+ 工具如何自注册、自动发现、按需加载。

相关推荐
爱写代码的小任2 小时前
Hermes Agent 源码解析(二):入口与 CLI —— 从 `hermes` 命令到交互式 REPL
源码阅读
坐吃山猪9 天前
【Hanako】README08_LEVEL4_插件系统架构
python·架构·agent·源码阅读
LienJack14 天前
《Claude Code 源码解析系列》第7章|Skill
claude·源码阅读
LienJack14 天前
《Claude Code 源码解析系列》第8章|Agent 协作
claude·源码阅读
LienJack14 天前
《Claude Code 源码解析系列》第3章|Prompt 编写
claude·源码阅读
LienJack14 天前
《Claude Code 源码解析》第2章|ReAct 主循环
claude·源码阅读
LienJack15 天前
《Claude Code 源码解析系列》第一章-工程架构
人工智能·源码阅读
倾颜23 天前
React 19 源码主线拆解 04:Fiber 到底是什么,React 为什么需要 Fiber?
前端·react.js·源码阅读
Bigger1 个月前
第十章:我是如何剖析 CLI 里的终极 Agent 能力的(电脑控制与浏览器接管)
前端·claude·源码阅读