资讯详情

资讯详情

Hermes Agent架构深度解析:执行路径地图与子系统拆解

把 Hermes Agent 的架构总览画成一张执行路径地图是我把这个项目跑通之后做的第一件正经事。这类 Agent 项目在开源社区里不少但像 Hermes Agent 这样把 Web 接入、任务编排、工具调用、记忆管理拆成清晰子系统的并不算多。如果只看 README你会觉得它不过是在大模型外面套了一层 Prompt等真正开始调 bug、加工具、处理并发任务时脑子里没有这张地图基本寸步难行。这篇文章适合两类读者一类是刚把 Hermes Agent 跑起来、准备深入改代码的人另一类是正在设计类似 Agent 架构、想借鉴别人怎么划分子系统边界的人。我会从整体设计、核心子系统、执行路径、部署容器到常见故障排查完整过一遍。1. 从宏观看清 Hermes Agent 的边界1.1 Hermes Agent 到底是什么Hermes Agent 是一个带 Web 接入端的 Agent 运行时核心能力不是“聊天”而是执行多轮任务用户把目标丢进来系统负责拆解目标、调用工具、维护状态、汇总结果最后给出一份可追踪的执行记录。我第一次跑起来时只用了三条容器命令以为它就是一个单体应用。后来翻了源码才发现项目内部被刻意拆成了不同的子系统每个子系统只干一件事。这个设计不是过度工程而是因为 Agent 的执行链路里规划模型调用、工具执行、用户交互、状态存储这几个环节的伸缩方式和故障模式完全不一样。比如工具执行网络超时不能把整个 Web 进程拖死模型服务不稳定也必须把重试和降级隔离在独立模块里。所以看 Hermes Agent 的架构不能只盯着“模型 Prompt 怎么写的”要把它当成一个包含接入层、编排层、执行层、基础依赖层的分布式系统来看。1.2 四层架构划分我习惯把 Hermes Agent 的子系统分成四块层级主要组件职责接入层Web UI、HTTP/WebSocket 网关、会话管理接收用户请求维持实时连接处理认证与历史会话读取编排层规划器、任务调度器、上下文上下文管理器把目标拆成子任务维护任务状态机决定下一步动作执行层工具注册中心、工具执行沙箱、事件总线消费者执行具体工具调用处理参数校验回写结果基础依赖层模型网关、Redis/数据库、向量存储、文件工作区提供模型接入、持久化、记忆检索和临时文件能力这四层之间不是简单的“从上往下调用”。衔接的核心是执行路径地图请求从接入层进入后会在编排层和执行层之间反复往返而不是一条直线走到底。这也是 Agent 系统和传统 Web 后端最大差异——没有固定 Controller 逐层返回而是一个观察、规划、执行、反馈的循环。1.3 为什么用子系统拆分最直接原因是可替换性。模型网关可以随时从本地模型切到云端接口记忆模块可以从 Redis 换成 PostgreSQL 加向量插件工具注册中心可以只开放白名单给特定用户这些如果写在一个大类里改动成本会很高。另一个原因是排查问题。我之前遇到过一次“任务卡住不动”的情况日志里只有一句超时。如果 Web 接入、规划、工具执行都揉在一起我只能全盘打断点。但 Hermes Agent 把任务状态和事件日志分离开之后我只需要查任务队列的消费位点就能判断到底是规划器没产出新任务还是工具执行器挂了。这个收益在实际运维里比任何理论都值钱。2. 核心子系统逐个拆开看2.1 Web 接入与 API 网关子系统接入层是用户接触 Hermes Agent 的第一道门但它几乎不参与 Agent 逻辑。它只负责三件事建立连接、校验身份、把请求转成交互事件。在 Hermes Agent 里Web 端和 Agent 端通过一组轻量 API 通信。Web 页面发起一个任务网关生成唯一的request_id把请求写入消息队列对应的“任务请求”主题然后立刻返回“已受理”。后续任务进展通过 WebSocket 或 Server-Sent Events 推给前端。这种异步设计很关键一个 Agent 任务可能耗时几十秒甚至几分钟HTTP 长连接撑不住必须把请求和响应解耦。会话管理也在这层做。用户刷新页面后网关会从 Redis 里拉取会话摘要重新挂到当前连接上。注意这里拉取的是“摘要”而不是完整上下文完整上下文太大适合放在 Agent 端内部处理不适合每次渲染页面都传一遍。这里有个细节值得学网关在接收入口请求时会把原始输入冻结一份存入审计日志。后面规划器改了用户目标、工具调用拼了参数你都能对照原始需求排查不会被中间加工步骤带偏。2.2 任务规划子系统规划子系统是 Hermes Agent 里最像“大脑”的部分。它的输入是用户目标加上当前上下文输出不是一段文本而是一份结构化的任务清单。我拆过它的源码结构核心是一个planner模块里面维护了一副任务状态机pending、running、completed、failed、blocked。规划器每次迭代时会结合模型输出生成新的子任务并检查旧任务是否允许进入下一阶段。比如“查天气然后订机票”会被拆成两条任务第一条完成第二条才能从blocked变为pending。这个子系统最需要注意的点是“模型输出不可靠”。LLM 生成的任务清单可能是非法格式、可能包含不存在的工具名。所以 Hermes Agent 在规划器外层套了一层校验器任务字段必须通过 JSON Schema 校验工具名必须在注册中心里找得到否则整轮规划会被打回重试。这也是为什么它不像某些 Demo 项目那样经常中途“抽风”。2.3 工具注册与函数调用子系统工具子系统是 Agent 能力边界的物理实现。每个工具在注册中心里有一个唯一的tool_id配上名称、描述、参数 Schema、超时时间、执行模式。我在源码里看到的工具注册结构大致长这样{ tool_id: http_request, description: 发起一次HTTP请求, parameters: { url: { type: string, required: true }, method: { type: string, enum: [GET, POST, PUT], default: GET }, timeout: { type: integer, default: 10 } }, timeout_ms: 30000, execution: sandbox, allowlist: [group:basic] }这里有个必须强调的边界工具执行器只认tool_id和参数不关心这个工具背后的逻辑。它拿到请求后先去注册中心做参数校验再通过执行沙箱调用实际函数最后把返回值包装成统一结构写回任务上下文。这种设计是为了隔离风险——工具可以请求外部网络但本身在受限运行时里内存、文件系统、网络白名单都被约束住。工具调用的 fail 处理也在这层。Hermes Agent 不会因为一个工具报错就让整条任务链崩溃而是把异常包装成工具结果的一部分返回给规划器让模型看到错误信息后决定是换参数重试、换工具还是放弃子任务。2.4 记忆与知识子系统记忆子系统管三种东西短期会话上下文、长期记忆、工作区文件。短期上下文存在 Redis 里键名通常是session:{session_id}保存最近十几轮交互摘要和工具调用记录。这个设计聪明之处在于规划器每次只需加载摘要而不是把冗长的历史对话全部塞进模型Token 消耗能省不少。长期记忆走的是向量存储用户对某个话题的偏好、历史任务的执行结论会被异步写入向量库后续相关任务启动时做相似度召回。工作区则是每个任务独立的一块文件存储。工具在执行过程中产生的中间文件都落在工作区里任务结束后可以选择清理或归档。我遇到过比较头疼的问题就是工具写临时文件污染了全局目录后来发现 Hermes Agent 默认给每个task_id建独立子目录这个坑从一开始就避开了。2.5 模型网关子系统模型网关在所有子系统里最不起眼但决定了整个系统的稳定上限。它负责把规划器、决策器的模型请求发往实际推理服务处理超时、重试、限流和 Token 统计。Hermes Agent 的模型网关支持多 Provider 配置每个模型调用都带一组元信息例如timeout、max_retries、model_name。当模型服务超时网关按指数退避重试连续失败超过阈值就把这轮规划标记为降级改用缓存结果或简单规则兜底。我一开始觉得这一层多余直到有一次底层推理服务发生了慢查询所有任务都卡在“等待模型返回”阶段。虽然我的问题并不常见但不过是受害于某些推理服务没有快速失败——当然真正让我意识到重要的是模型子系统的“可测性”在 Hermes Agent 架构下只要模型网关稳定其他子系统可以独立调试。2.6 执行引擎与事件总线执行引擎是整个架构的地基。它不仅执行调用还维护着一个事件总线。所有子系统的关键行为都会以事件形式发布到总线上例如task_created、tool_called、tool_result、agent_completed。事件总线的出现让各子系统之间不必强耦合。Web 接入层不需要知道规划器是怎么拆任务的规划器也不需要知道工具内部实现。每个模块只需要订阅自己关心的事件类型然后做对应的处理。这个设计在传统后端也很常见但在 Agent 系统里尤其重要因为 Agent 的执行路径是循环且动态的很难用一个同步方法调用链来建模。3. 执行路径地图3.1 一条完整请求的流转路径我自己整理过一张执行路径地图把一次完整任务在 Hermes Agent 内部经过的节点全部列了出来。用户通过 Web 页面提交目标请求网关接收生成request_id和session_id写入审计日志。接入层把请求封装成user_goal事件发布到事件总线。编排层的规划器订阅到事件加载当前会话摘要、短期上下文和相关长期记忆。规划器调用模型网关让模型生成结构化任务清单。校验通过后任务清单写入任务队列。执行引擎消费队列中的第一个可执行任务判断任务类型。如果任务需要工具调用就交给工具注册中心查找对应工具。工具注册中心做参数校验将实际调用送入执行沙箱。工具执行结果返回后执行引擎把结果打包成tool_result事件。规划器收到tool_result后把结果加入上下文并重新调用模型判断下一步继续执行下一个子任务、修改当前任务参数还是直接生成最终回复。当所有任务都进入completed状态聚合器汇总各部分结果经过模型网关生成最终回答。接入层把最终结果推送给前端同时异步更新会话记忆和向量库。这里有三个关键转折点。第一个是第 4 步到第 5 步任务从“规划结果”变成“队列里的实体”标志 Agent 开始真正执行。第二个是第 6 步到第 7 步工具结果回灌上下文后系统进入新一轮决策。第三个是第 8 步的聚合多任务结果合并时顺序不同会让最终回答质量差距很大。3.2 Agent 循环中的核心决策点Hermes Agent 的执行路径本质上是一个“观察-规划-行动-反馈”的循环我在下表里标出了每个环节的输入和处理方式环节数据输入核心处理产物观察会话摘要、记忆召回、环境状态提炼当前目标与约束上下文快照规划上下文快照、可用工具列表生成子任务清单或下一步指令任务项行动任务项、工具 Schema校验参数并调用工具或模型执行结果反馈执行结果、错误信息拼接回上下文触发下一轮循环新一轮上下文快照理解这张表之后很多问题就有了解释。比如为什么任务偶尔会重复执行因为工具结果在回灌时没有做好幂等规划器看到的结果还是“未知”于是又生成了一个一模一样的任务。解决方法是给每个任务加上唯一的execution_id在工具结果里回填原任务的 hash规划器读到重复执行时直接跳到反馈环节。3.3 状态流转与数据格式执行路径地图里最脆弱的部分是状态流转。Hermes Agent 里每个任务的状态变化是pending - running - completed | v failed - retrying - running | v blocked当任务被标记为blocked调度器不会继续往下分配资源。什么情况下会 blocked最常见的是前置任务未完成或工具依赖的外部资源处于锁定状态。Hermes Agent 会把阻塞原因记录在任务的meta字段方便排查。上下文数据的结构大概是这种{ request_id: a3f2..., session_id: s-1024, user_goal: 梳理本季度销售数据并生成摘要, task_list: [ { task_id: t1, status: completed, agent: data_agent, tool_name: sql_query, args: { query: select ... }, result_summary: 901 rows fetched } ], current_focus: generate_summary }这个 JSON 会在每次循环中更新规划器的 Prompt 就是基于这份结构化上下文而不是原始聊天记录。4. 子系统之间的协作机制4.1 事件驱动带来的松耦合Hermes Agent 没有暴露一个“总控接口”让外部按顺序调用。所有子系统的交互基于事件。我在调试时经常把事件总线的日志打开一条请求的完整旅程就能串起来。例举几个关键事件类型user_goal_received接入层发布编排层消费。task_list_generated规划器发布执行引擎消费。tool_result_recorded执行引擎发布规划器消费。agent_session_end聚合器发布接入层、记忆子系统同时消费。这种机制带来的最大好处是并发安全。如果有两个会话同时在跑事件总线能保证执行引擎不会错拿请求 A 的任务结果去更新请求 B 的上下文因为每个事件自带request_id关联字段。4.2 状态存储与并发控制状态存储被独立放在 Redis 和数据库两层。短期状态放 Redis利用它的过期策略和原子操作任务清单和审计记录放数据库保证可查询、可回溯。并发控制是一个必须要讲的技能。当多个事件同时触发同一个任务更新时Hermes Agent 默认用 Redis 分布式锁做临界区保护。锁的 key 是task_lock:{task_id}获取锁失败的一方直接丢弃事件等待下一次重放。我遇到过锁超时设置过短导致任务被误删的情况后来调整到一个合理的值比如一个工具调用通常在 2 秒内完成就锁超时至少给 5 秒同时开启续期机制。4.3 链路追踪与可观测性执行路径地图如果没有可观测性支撑只是一张 PPT。Hermes Agent 在日志里强制要求带上request_id、task_id、tool_id三个字段因此我们可以拼出整条链路。我常用的排查姿势是先按request_id搜出全部分布式日志然后按task_id分组看每个任务从pending到completed的耗时。如果某个任务执行时间接近工具超时上限基本可以断定是工具调用慢而不是规划器问题。链路追踪最终要回答的问题是“每一步花了多久、谁在等谁”这套系统在 Agent 场景下特别合适。5. 部署架构与容器化细节5.1 为什么把 Web 和 Agent 拆成两个容器Hermes Agent 的官方部署推荐里Web 端和 Agent 端是分开的。一开始我觉得多此一举为什么不打成一个镜像直接跑后来我理解了。第一是伸缩模型不同Web 端可以多副本水平扩展而 Agent 端由于依赖会话状态和任务队列更适合按任务量做单实例或多 worker 的分片。第二是故障域隔离Web 容器挂掉Agent 容器还能继续把已在执行的任务跑完反过来Agent 容器重启也不会让接入层直接 502。第三是权限隔离Web 容器只需要暴露给用户Agent 容器则持有密钥、工具配置等敏感资产两者不该放在同一层网络里。5.2 典型三容器拓扑一个常见的简化部署是三个容器加一个网络容器镜像名角色Web 接入层hermes-web页面托管、API 网关、WebSocket 推送Agent 执行节点hermes-agent规划器、执行引擎、工具沙箱入口状态存储redis:7或 postgres会话状态、任务队列、锁配合大概五条命令就能跑起来# 1. 建网络让容器之间用服务名互相访问 docker network create hermes-net # 2. 建数据卷保证 Redis 数据不随容器销毁丢失 docker volume create hermes-redis-data # 3. 启动状态存储 docker run -d --name hermes-redis \ --network hermes-net \ -v hermes-redis-data:/data \ redis:7 # 4. 启动 agent 执行节点链接状态存储 docker run -d --name hermes-agent \ --network hermes-net \ -e REDIS_URLredis://hermes-redis:6379/0 \ -e MODEL_PROVIDERopenai \ -e MODEL_API_KEY${API_KEY} \ hermes-agent # 5. 启动 web 接入层 docker run -d --name hermes-web \ --network hermes-net \ -p 8080:8080 \ -e AGENT_API_URLhttp://hermes-agent:9000 \ hermes-web容器起来之后访问宿主机的8080端口就能打开 Web 界面。注意要先把API_KEY准备好不然 agent 容器会一直报告模型调用失败。上面命令中-d表示后台运行实际用的时候建议先去掉-d跑一次看到日志再转后台。5.3 安装时“拉不到仓库代码”的处理很多人第一次接触 Hermes Agent 是在安装阶段就折了。安装脚本会尝试把官方仓库克隆到本地有时会看到这类错误“failed to download repository, tried git clone ssh, https”。这个问题跟 Hermes Agent 本身的架构没关系纯粹是安装器在下载依赖仓库时遇到网络协议不通。我实践中比较靠谱的逃逸路线是先把安装器的克隆地址拿出来在本地手动执行git clone https://...确认网络能不能通。如果直接连仓库不稳定可以找一个可访问的镜像或托管副本把下载地址替换成可用地址。如果仓库已经下载好只是安装器报错可以直接把目录迁移到安装器预期的路径再重跑安装脚本。安装器通常会检查目录是否存在存在就跳过克隆步骤。不要反复重跑安装脚本而不清理残留。残留目录里的.git锁文件会导致下一次克隆失败先把目录整个删掉再重试。这个阶段最怕的是盲目改全局 git 配置。新协议和端口调整都需要测试成本而 Hermes Agent 的架构里安装环节并不影响后续运行时稳定性不值得在这里浪费太久。差不多能跑起来就直接进到下一阶段。6. 常见故障与排障心得6.1 高频故障速查表症状可能原因处理方式Web 界面能打开但提交任务后一直“排队中”Agent 容器未启动或事件队列消费者掉线检查 Agent 容器日志确认agent-worker在消费任务主题任务一直running然后超时工具调用超时设置过短或外部依赖无响应查看工具详细耗时适当调大timeout_ms检查依赖地址模型回答风格混乱任务拆得乱七八糟上下文里混入太多历史摘要清空该会话上下文或检查记忆召回阈值避免无关长期记忆干扰规划多个会话任务互相影响锁 key 冲突或事件未带request_id升级到较新主版本或者检查自定义工具事件规范补上关联字段Redis 内存暴涨短期上下文过期时间过长 / 任务结果未清理设置合理的SESSION_TTL定期清理已完成任务的工作区文件6.2 调试时我总结的几条套路第一先看事件总线日志不要一上来就查模型输出。Agent 链路里的问题大多数不是模型想不明白而是某个子系统的结果没能正确传到下一步。事件日志能直接指出断点。第二给工具加 Mock 和染色。Hermes Agent 的架构支持工具注册中心可以临时注册一个mock_tool与真实工具返回同样结构的数据然后用它测试规划器是否能正确处理结果。这样可以把外部依赖的不确定性隔离出去。第三控制任务的并发粒度。我在同一个会话里跑过十几个子任务直接导致事件总线瞬时消息量暴涨丢失了一些事件。后面改成每轮最多派发 5 个子任务回退机制才正常。项目本身架构支持更高并发但实际部署要结合 Redis 性能调整消费并发数。第四遇到“神秘问题”先看版本。开源项目迭代很快子系统之间若存在接口变更升级后偶尔会留下不兼容配置。遇到莫名其妙的问题时把 Web 容器和 Agent 容器升级到同一批镜像往往比单独排查更有效。6.3 把架构地图用起来架构总览不是用来看的是拿来定位的。我推荐做三件事给每个子系统写一段包含关键日志关键字的排障手册省得下次再翻代码。在部署环境里把事件总线的指标接上监控任务成功率、工具平均耗时、模型调用错误率都拉出来。把执行路径地图贴在项目文档第一页让新加入的开发者先看地图再进代码。我自己在实践中最受益的一点是每次 Agent 流程异常第一反应不是去猜模型笨不笨而是回到这张地图上判断到底在哪条边、哪个节点断了。这个习惯让排查效率提升了非常多。如果接下来你想自己动手扩展 Hermes Agent建议先挑一个不太核心的工具接入试试。跑通一条完整的“用户目标 - 工具调用 - 反馈 - 最终结果”链路之后你会发现所有子系统之间的关系一下子全串起来了。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →