2.1. 三层骨架解决了调用方式与执行逻辑的耦合

三层骨架解决了调用方式与执行逻辑的耦合

2026年4月,Hermes Agent 在 v0.16 版本中同时发布了命令行工具 hermes、桌面应用 Gateway 和 Telegram/Discord/Slack 等多平台接入能力。翻开源码你会发现,这些完全不同的交互方式最终都指向同一个对象——agent/run_agent.py 中的 AIAgent。这意味着你在终端里敲下的每一行字,和老板在 WhatsApp 里发的每一条语音,走的是同一套执行逻辑。Hermes 的做法很直接:把“谁来调用”和“如何执行”做成两层独立的骨架,中间用一层动态模型路由隔开。这就是被社区广泛讨论的三层骨架,也恰好是本章要拆解的 Entry Points、Agent Core、Provider Resolution 三条分界线。

现象:一个 Agent,四种入口,同一套大脑

这种设计一开始并不显眼。在 Hermes 的早期中,CLI 是唯一入口,所有对话逻辑堆在一个文件里。发展到 v0.16 时,仓库里已清晰分出三条调用路径:

入口 项目文件 典型使用场景 消息封装 启动方式
CLI cli.py 开发者本地调试 终端文本流 hermes chathermes TUI
Gateway gateway/run.py 多平台聊天 平台事件 → 统一事件格式 长期运行后台进程
ACP 适配器 acp/ 模块 企业内集成 Agent Communication Protocol 可选的服务模块
Python 库 agent/run_agent.py 直接调用 其他工具链嵌入 原生 Python 对象 AIAgent().run()

这些入口的启动方式、协议、消息格式完全不同,但它们复用的是同一个 AIAgent 实例的 run() 方法。以 Gateway 为例:当你从 Telegram 发一条消息,Gateway 会把它打包成标准文本事件,推送给 Agent Core;Agent Core 完全不感知 Telegram 的 bot token 或聊天 ID,它只看到一个“用户输入字符串”。同样,ACP 适配器通过 gRPC 进来的一段结构化指令,在进入核心循环前也会被统一序列化为内部消息体。

这种“入口归一”的好处是显而易见的:新增一个平台不会碰核心执行逻辑。2026 年 5 月社区贡献者为 Hermes 增加了 Microsoft Teams 接入,据当时的 Pull Request 信息,仅需在 Gateway 中新增不到 200 行适配代码,就完成了全部接入——整个 agent/ 目录一个字节都没改。

Entry Points 统一接入层:将协议差异隔离在外

三类入口的核心工作都落在三件事上:建立连接、序列化消息、消费响应。Hermes 对此有一套清晰的分工。

协议适配与消息标准化

  • CLI 最简单:input() 读取字符串,print() 输出响应,连会话状态都直接存在内存里。它是单用户的,没有并发问题。
  • Gateway 要处理多平台 Webhook:Telegram 通过 HTTP POST,Discord 走 WebSocket,Slack 用 RTM。Gateway 为每个平台维护一个 PlatformAdapter,核心方法就是 normalize_message(),把不同平台的事件转成统一的 IncomingMessage 对象。
  • ACP 适配器 面向企业级的标准化通信,它同时支持 HTTP 和 gRPC,消息体用 protobuf 定义。但同样,在打入 Agent 之前,protobuf 会被拆解成纯文本或命令参数。

Gateway 和多用户场景还必须解决一个问题:如何把返回响应正确路由回对应聊天窗口。为此,Gateway 在调用 AIAgent.run() 时携带一个 session_id,Agent 执行完毕后,响应会携带同样的 session_id,再由 Gateway 的 Router 根据之前的映射将响应投递到正确的平台和对话中。这个机制让 Agent 内核依然保持“单会话循环”的认知,完全不需要理解多租户概念。

可操作总结

入口 消息进入方式 内部统一格式 对 Agent 的调用 结论与最佳实践
CLI 标准输入 str agent.run(user_input) 同步调用 适合快速测试和单会话
Gateway Webhook/WS IncomingMessage 对象 dispatch_to_agent() 通过事件循环异步调用 生产多平台推荐,网关单独进程
ACP gRPC/HTTP protobuf → str/dict 异步 agent.run() 包装成服务 需要与内部系统集成时使用
Library 函数调用 Python 原生对象 agent.run() 直接调用 嵌入其他脚本或 Agent 工作流

记住一条规律:入口再怎么变,Agent Core 的输入永远是一个字符串和一组元数据。 这是 Hermes 入口层设计的核心约束。

Agent Core 轻量级运行时:单一对话循环的状态机

在所有入口之下,实际的“思考”发生在 agent/run_agent.pyAIAgent 类中。尽管这个文件在 v0.16 时仍被注释为“大文件”,但其内部逻辑已经被清晰地拆解成一个单线程异步事件循环 + 状态机

对话循环的七个步骤

agent.run(user_input) 被调用时,Hermes 会启动一个 async 循环,依次走过七个状态:

[WAIT_INPUT] → [BUILD_PROMPT] → [RESOLVE_PROVIDER] → [EXECUTE] → [DISPATCH_TOOLS] → [COMPRESS] → [WRITE_RESPONSE]
  • WAIT_INPUT:入口输入已就绪,触发循环。
  • BUILD_PROMPTPromptBuilder 从 SQLite 读取对话历史、系统提示(SOUL.md)、外部知识片段,拼接成完整的 prompt。
  • RESOLVE_PROVIDER:根据用户配置、历史调用情况和当前可用性,动态选择模型提供商(见下一节)。
  • EXECUTE:调用提供商的 chat_completion API,流式获取推理结果。
  • DISPATCH_TOOLS:如果模型输出决定调用工具,ToolDispatch 会匹配注册表中的工具,触发执行并收集结果。
  • COMPRESS:当对话上下文接近 token 上限,Compression 模块自动汇总早期对话,防止溢出。
  • WRITE_RESPONSE:将最终文本或工具结果写回给入口层。

这个循环是 Hermes Agent 的灵魂。入口层无论来自 CLI 还是 Gateway,触发的都是这套完全一致的序列。为了确保这个循环的轻量,Hermes 刻意保持其为单线程协程模型——所有外部调用(API 请求、工具执行)都是异步 I/O,不会阻塞事件循环。这让一个 Agent 实例可以同时处理多个会话(Gateway 场景),每个会话在同一个事件循环中以协程形式交替执行。

协程模型与并发

调研资料显示,Hermes 使用 Python 的 asyncio 作为并发框架,但没有引入多线程或多进程。它的并发策略是将每个对话 session 映射为一个 asyncio.Task,所有 task 共享同一个 AIAgent 实例,但各自持有独立的上下文栈。SQLite 写入通过 aiosqlite 实现非阻塞,避免会话间互相干扰。

这种设计使得 Hermes 可以被部署在一个很小的资源里(文档提到在树莓派上运行 Gateway 的案例),同时保持高吞吐。缺点也明显:一个 task 内的 CPU 密集操作(如 prompt 本地处理)仍会短暂影响事件循环,不过这在实践中可以通过将重型任务委托给工具后端来规避。

关键对比:传统循环 vs Hermes 事件循环

特征 传统顺序脚本 Hermes Agent Core
调用方式 入口代码中直接写逻辑 入口只负责触发状态机
多平台支持 每个平台一套复制粘贴的逻辑 一个循环,多种入口复用
模型切换 硬编码 API key 和模型名 动态解析,运行时切换
工具调用 函数调用散落各处 统一 ToolDispatch 分发
上下文管理 手动拼接字符串 PromptBuilder 自动拉取

可以看到,Agent Core 做了两件关键的事:把执行逻辑收敛到一个可靠的状态机里,把所有外部不确定性(模型、工具、上下文)都变成可替换的模块。这就是轻量级运行时的真正含义——不是功能少,而是高层控制逻辑足够薄。

Provider Resolution 动态模型路由:解耦模型与业务

三层骨架的第三层位于 Agent Core 内部,但设计上足够独立,值得我们单独拆解。它就是 Provider Resolution——Hermes 在运行时决定“用哪个模型”的机制。

为什么需要一个解析层

Hermes 支持至少 11 种模型家族,而不同提供者的 API 风格差异很大:有的用 OpenAI 兼容的 /v1/chat/completions,有的走 Anthropic 的 Messages API,还有的用自定义端点。如果让 Agent Core 直接记住这些差异,代码会迅速腐化。Provider Resolution 层的作用就是把这些差异收敛到一个接口后面chat_completion(prompt, model, **params)

选择流程与策略

当 Agent 进入 RESOLVE_PROVIDER 状态时,解析器会按以下顺序查找可用的提供者:

  1. 检查用户覆盖:是否通过 hermes model use 临时指定了模型?是 → 验证其凭据(auth.json 中有无对应 key 或 OAuth token)和可用性。
  2. 读取配置优先级:从 config.yaml 中读取 model_priority 列表,依次检查每个提供者的状态。
  3. 故障转移:如果首选提供者不可用(网络超时、额度耗尽等),自动尝试下一个。重试策略为指数退避,最多 3 次。
  4. 选用默认提供者:如果以上均失败,使用随 Hermes 分发的默认提供者(如本地 Ollama 实例,如果已启动)。

所有解析结果会缓存在内存中一个会话的生命周期内,因此同一轮对话不会反复 ping 提供者健康检查。

三种认证路径的解耦

认证方式 配置位置 典型提供者 适用场景 注意点
API Key auth.json OpenAI, Groq 个人开发,直接调用 不要提交到 Git
OAuth 2.0 auth.json + 系统浏览器认证 Google GenAI, Azure 企业 SSO 集成 需在 Gateway 环境配置回调
自定义端点 config.yaml 内部部署的 vLLM, Ollama 私有机房,代理 必须指定 base_url

三层隔离后,Agent 核心只知道“我有一个 chat completion 方法”,而不知道背后是调用 OpenAI 还是 Anthropic。同样,入口层 CLI 或 Gateway 完全不用关心模型选择逻辑,它们只把用户输入丢给 Agent Core;Provider 层的变更(比如添加一个新模型)对它们透明。

社区踩坑:解析失败的头号原因

根据 Blake Crosley 在 v0.16 Reference 中的提醒,新手第一次运行 hermes chat 时常遇到模型不可用的报错,原因有三:

  • 未运行 hermes model 交互配置,使用了不存在的默认模型;
  • auth.json 格式错误或未放置在 ~/.hermes/
  • 公司网络代理未在环境变量中设置,导致自定义端点不可达。

建议:读完本章后,不妨先跑一次 hermes model 完成提供者初始化,再继续探索 Gateway 部署。

先给结论:三层如何在工程上解耦

如果我们把 Hermes 看作一个三明治:

  • 上层 Entry Points 负责“谁来吃”—— CLI、Gateway、ACP,分别对应本地用户、消息平台、企业系统。
  • 中层 Agent Core 负责“怎么吃”——统一的状态机,保持对话节奏,决定何时调用工具、何时压缩上下文。
  • 底层 Provider Resolution 负责“吃什么”——决定用哪个模型的大脑,处理所有 API 细节和故障转移。

三层的耦合点被压缩成两个标准接口:消息输入接口(入口 → 核心)、chat completion 接口(核心 → 提供者)。任何一层的替换或扩展都不跨层影响。

传统 Monolithic Agent 与 Hermes 三层骨架对比

维度 单体 Agent Hermes 三层骨架 解耦价值(作者结论)
新增一个聊天平台 整个重新实现一遍对话循环 增加 Gateway 适配器,约 200 行代码 开发成本降低 90%(基于社区 PR 统计)
切换模型提供商 需要修改核心代码里的 API 调用 运行 hermes model 或修改 config.yaml 风险极低,可在线热切换
逻辑 Bug 修复 影响所有入口 只需改 agent/run_agent.py,一次修复全平台受益 回归测试集大大缩小
部署形态 通常是重量级服务 支持本地 CLI、轻量级 Gateway、企业 ACP 三种形态 符合从开发到生产的演进路径

这个结论的可操作性很强:如果你现在的 Agent 还在入口代码里 hard-code 模型调用逻辑,那么按照三层骨架重构是性价比最高的架构升级路径。

按场景推荐:你该怎么用这三层

场景一:单机研究和原型开发

直接使用 hermes chat CLI。入口就是终端,Agent Core 会在你的机器上单会话运行。通过 hermes model 绑定一个或两个提供者,不需要网关。所有对话历史保存在 ~/.hermes/MEMORY.md 和 SQLite 里,随时可查阅。

场景二:为团队部署聊天机器人

启动 hermes gateway 后台进程,在 config.yaml 中启用 Telegram、Discord 等平台。Gateway 会负责从多个平台接收消息,路由给同一个 Agent 实例。必须提前在 auth.json 中配置好平台 Bot token。这个模式下,你只需要管理一个 Gateway 进程,所有平台享受一致的 Agent 行为。

场景三:将 Hermes 嵌入产品后端

如果你已有自己的产品系统和用户界面,可以使用 Python 库方式直接 import agent,或者启动 ACP 适配器,通过 HTTP/gRPC 暴露 Agent 能力。这样,你的产品后端只需发送一条标准化消息,就能获得推理结果,而 Hermes 的状态管理和模型切换完全由它自己接管。

无论哪种场景,记得将 auth.json.env 排除在版本控制之外,这是 Hermes 社区贡献者中最常见的隐私事故。

从三层骨架到 Agent 操作系统内核

Entry Points、Agent Core、Provider Resolution 构成了 Hermes 的“外层骨骼”。它们解决的是宏观层面的调用与执行解耦。但一个能自主复盘、不断进化的 Agent,显然不能只靠单次对话循环。在下一章《六大子系统构成 Agent 操作系统的内核》中,我们会走进 Agent Core 的内部,去看消息总线、提示构建器、工具调度、压缩模块等六个子系统是如何像操作系统的内核服务一样,支撑起这个会成长的自主推理系统。你刚刚在 CLI 里看到的每一次上下文自压缩、每一次工具调度,都源于这些子系统之间的精密协作。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~