AIAgent 才是 Hermes Agent 的“总调度器”:run_agent.py 在系统里到底负责什么?

0 阅读10分钟

一、先给结论:AIAgent 不是“大模型”,而是“任务总控台”

很多人第一次看 Hermes Agent,容易把核心误解成“调用某个大模型的代码”。但从官方文档和源码结构看,真正的核心不是模型本身,而是 run_agent.py 里的 AIAgent。它像一个任务总控台:先把用户问题、项目上下文、长期记忆、技能、工具说明组织成模型能理解的输入;再选择合适的 Provider/API Mode 调用模型;如果模型要求调用工具,就调度工具执行;如果上下文太长,就压缩;如果主模型失败,就切换备用模型;如果任务跑太久,就用预算机制刹车。

二、普通调用大模型 VS AIAgent 调度 Agent,有什么本质区别?

普通大模型调用通常是“给模型一段文本,等模型返回一段文本”。AIAgent 要处理的是“让模型在真实环境里完成任务”:它需要读文件、查网页、调用终端、管理会话、记住历史、处理异常、在不同平台展示进度。

对比项

普通 Chat / LLM API

Hermes AIAgent

输入

用户消息

用户消息 + 系统 Prompt + Memory + Skills + Context Files + Tool Schemas

输出

文本回复

文本回复,或多轮工具调用后的最终结果

执行能力

模型只能生成内容

AIAgent 调度文件、终端、浏览器、MCP 等工具

状态管理

通常由外部系统维护

AIAgent 维护 OpenAI-style conversation history 和 session

容错能力

调用失败就报错

支持 retry、fallback provider、credential refresh 等

成本控制

通常靠外部限制

IterationBudget、上下文压缩、辅助任务 fallback

三、两个入口:chat() 是简洁版,run_conversation() 是完整版

AIAgent 对外有两个主要入口。chat() 更像“省心模式”:传入一个问题,拿回最终文本;run_conversation() 更像“工程模式”:它返回包含最终回答、完整消息历史、元数据和使用统计的结构化结果。官方文档也说明,chat() 本质上是 run_conversation() 的薄封装,只是把 final_response 抽出来返回。

这意味着:如果你只是把 Hermes 当命令行助手,用 chat() 就够;如果你要把 Hermes 嵌到 Web 系统、自动化平台、任务平台、企业内部工具中,就应该重点看 run_conversation()。

四、初始化阶段:AIAgent 先把运行时“装备”装好

看 run_agent.py 的构造函数,会发现 AIAgent 初始化参数非常多。GitHub 的项目说明里也提到,真实的 AIAgent.__init__ 大约有 60 个参数,覆盖凭证、路由、回调、session context、预算、credential pool 等。这些参数不是“堆功能”,而是在描述一个 Agent 运行时需要的所有资源。

可以把初始化理解成“任务开始前的装备检查”:模型用哪个、工具开哪些、当前平台是谁、会话 ID 是什么、最大循环次数是多少、有没有进度回调、是否跳过记忆和上下文文件。

五、run_conversation() 主循环:一次任务真正怎么跑起来?

官方文档把每一轮 Turn Lifecycle 拆成清晰步骤:生成 task_id、追加用户消息、构建或复用系统 Prompt、检查是否需要预检压缩、按 API mode 构造消息、注入预算/上下文压力提示、应用缓存标记、发起可中断模型调用、解析模型响应。如果返回 tool_calls,就执行工具并把结果追加回历史,然后回到构造 API 消息继续循环;如果返回文本,就持久化 session、刷新 memory 并返回。

这套流程说明了一个关键事实:Agent 不是“一次模型调用”,而是“模型思考 → 工具执行 → 结果回填 → 模型再思考”的循环。AIAgent 就是这个循环的控制器。

六、Prompt 组装:AIAgent 不是自己写 Prompt,而是调度 Prompt Builder

AIAgent 并不是把用户输入原封不动丢给模型。它会通过 prompt_builder.py 组装有效系统提示词,把 Memory、Skills、Context Files、Personality、工具 schema 等内容组织起来。官方关键源码文件说明中也把 agent/prompt_builder.py 定义为“从 memory、skills、context files、personality 组装系统提示词”的模块。

通俗理解:Prompt Builder 负责“把菜配齐”,AIAgent 负责“决定什么时候上菜、上给哪个模型、用哪种接口格式”。

七、Provider 与 API Mode:同一套 Agent,要能切换不同模型接口

Hermes 不是只绑定一个模型。官方文档说明它支持 chat_completions、codex_responses、anthropic_messages 三种 API 执行模式,并按显式 api_mode、provider 检测、base_url 推断、默认 chat_completions 的顺序解析。GitHub README 也强调 Hermes 可以使用 Nous Portal、OpenRouter、OpenAI、Anthropic 或自定义端点,并可通过 hermes model 切换模型。

这就是 AIAgent 的另一个价值:业务层不必关心每个模型厂商的消息格式细节,AIAgent 把内部消息统一成 OpenAI-style,再按目标 Provider 转换。

八、可中断模型调用:长任务 Agent 必须允许用户打断

官方文档提到,API 请求会被包装在 _interruptible_api_call() 里,实际 HTTP 调用运行在后台线程,主线程监听响应完成、interrupt event 或 timeout。如果用户发送新消息、执行 /stop 或系统信号打断,API 线程结果会被丢弃,不会把半截回复写进历史。

这对真实产品很重要。用户可能在 Agent 跑任务时发现方向错了,需要立刻改口;如果系统不能打断,就会浪费 token、时间和工具调用成本。

九、工具执行:模型负责“提议”,AIAgent 负责“审批与落地”

当模型返回 tool_calls 时,AIAgent 会解析工具调用,单个工具直接执行,多个工具可通过 ThreadPoolExecutor 并发执行;交互式工具会强制顺序执行;结果会按原始 tool_call 顺序重新插回历史。这意味着模型并不是直接操作终端或文件系统,它只是提出“我要调用哪个工具、参数是什么”,真正的执行由 AIAgent 和工具注册表完成。

工具执行链路通常包括:从 tools/registry.py 查找 handler、触发 pre_tool_call hook、检查危险命令、等待用户审批、执行 handler、触发 post_tool_call hook、把工具结果追加为 role=tool 消息。

十、Agent-Level Tools:有些工具必须由 AIAgent 自己拦截

官方文档特别指出,有些工具会被 run_agent.py 在进入普通 handle_function_call() 之前拦截,例如 todo、memory、session_search、delegate_task。原因是这些工具会修改 Agent 自身状态:todo 读写任务状态,memory 写长期记忆,session_search 查会话库,delegate_task 启动子 Agent。

这类设计很关键:普通工具更多是“对外部世界做动作”,Agent-Level Tools 则是“改变 Agent 自身的运行状态”。所以它们不能完全当作普通外部函数处理。

十一、消息历史守门人:为什么要维护统一 Message Format?

AIAgent 内部统一使用 OpenAI 兼容消息格式:system、user、assistant、tool。官方文档还强调消息角色必须严格交替:system 后是 User → Assistant → User → Assistant;工具调用时是 Assistant(with tool_calls) → Tool → Tool → Assistant;不能连续两个 assistant,也不能连续两个 user,只有 tool 可以连续出现。

这个“守门”动作看起来不起眼,但非常重要。很多模型供应商会拒绝格式错乱的历史消息;工具调用如果不成对回填,下一轮模型就可能看不懂自己刚刚做了什么。

十二、预算、压缩、Fallback:AIAgent 的三套刹车系统

能连续行动的 Agent 必须有“刹车”。官方文档说明,AIAgent 通过 IterationBudget 跟踪循环次数,默认 90 次;子 Agent 有独立预算,受 delegation.max_iterations 控制;预算到 100% 时停止并返回已完成工作的总结。

上下文方面,AIAgent 在 API 调用前发现会话超过模型上下文 50% 时会触发预检压缩;Gateway 层在 85% 时也会更激进地自动压缩。压缩前会先 flush memory,然后摘要中间消息、保留最近 N 条消息、保证 tool call/result 成对不被拆开。

模型失败方面,遇到 429、5xx、401/403 等情况时,AIAgent 会检查 fallback_providers,按顺序尝试备用 provider/model;辅助任务如 vision、compression、web extraction 也可以有独立 fallback 链。

十三、Callback Surface:为什么同一个 AIAgent 能接 CLI、Gateway、ACP?

AIAgent 支持多种平台回调,例如 tool_progress_callback、thinking_callback、reasoning_callback、clarify_callback、step_callback、stream_delta_callback、tool_gen_callback、status_callback。官方文档说明,这些回调用于 CLI、Gateway 和 ACP 集成中的实时进度展示。

这也是 Hermes 和普通命令行脚本的区别:它不是把结果憋到最后一次性吐出来,而是让不同平台能看到“正在思考、正在调用工具、需要澄清、已完成一步、正在流式输出”等状态。

十四、持久化与记忆刷新:一次任务结束不代表信息丢失

官方 Agent Loop 文档说明,在每轮结束后,消息会保存到 SQLite session store(由 hermes_state.py 管理),Memory 变更会刷新到 MEMORY.md / USER.md,用户之后可以通过 /resume 或 hermes chat --resume 恢复会话。[1] GitHub README 也强调 Hermes 会搜索过去对话、构建跨会话用户模型,并通过内置学习闭环创建和改进 skills。

所以 AIAgent 不只是把这次请求跑完,还要把“这次执行过什么、用户偏好是什么、哪些信息值得长期保存”沉淀下来,为下一次任务提供上下文。

十五、源码协作关系:run_agent.py 很大,但不是孤岛

官方文档在关键源码文件列表中明确写到:run_agent.py 是完整 agent loop;agent/prompt_builder.py 负责系统 Prompt 组装;agent/context_compressor.py 负责默认有损摘要算法;agent/prompt_caching.py 负责 Anthropic prompt caching;agent/auxiliary_client.py 负责视觉、摘要等辅助 LLM 任务;model_tools.py 负责工具 schema 收集和 handle_function_call() 调度。

因此看源码时,不建议从 run_agent.py 第 1 行硬啃到最后。更好的方式是围绕一次任务执行链路,顺藤摸瓜去看它调用的模块。

十六、推荐源码阅读路线:别硬啃,按职责切片

先看 AIAgent.__init__:知道它运行时需要哪些配置、凭证、工具、回调、会话和预算。

再看 chat():理解简单入口为什么只是 run_conversation() 的封装。

重点看 run_conversation():把官方 9 步 Turn Lifecycle 和源码结构对上。

看 Provider/API Mode 解析:理解为什么同一个 Agent 能接不同模型。

看工具调用处理:理解 registry、审批、并发、结果回填。

看压缩、Fallback 和预算:理解长期任务为什么不容易失控。

最后看持久化:理解 session、memory、resume 如何让任务跨平台延续。

十七、用一句话总结 AIAgent

AIAgent 是 Hermes Agent 的“运行时大脑外壳”:模型负责生成判断和行动意图,AIAgent 负责把意图变成可控、可追踪、可恢复、可压缩、可切换模型、可跨平台延续的真实执行过程。

如果你要学习 Hermes Agent 架构,run_agent.py 不只是一个文件,而是一条主线。沿着它,你会看到 Agent 工程最核心的几个问题:上下文怎么拼、模型怎么选、工具怎么调、危险动作怎么审批、失败怎么降级、长任务怎么压缩、历史怎么保存、经验怎么沉淀。