最近不少同学在跟进 Hermes Agent 的版本动态,尤其是 v0.21.0 发布以后,朋友圈和技术社区里讨论最多的两个方向是 Bots Mode 和 Agent 间通信。很多朋友看完更新日志后第一反应是:Bots Mode 到底解决了什么问题?Agent 与 Agent 之间又是怎么通信的?如果只把更新日志当成“新增功能”扫一眼,很容易错过两个特性背后的工程价值。
这篇文章打算以 v0.21.0 的两条主线展开,先讨论多 Agent 应用为什么需要 Bots Mode,再围绕 Agent 间通信梳理协议设计、代码实现和落地注意点,最后给出一个可以本地跑通的“订单查询 + 报表生成”双 Agent 示例。代码示例不绑定某个固定平台的私有 API,而是用贴近实际 Agent 框架的通用方式来表达,方便你迁移到自己的环境。
1. 背景与核心概念:Agent 应用已经到了“分工协作”阶段
1.1 从“单模型对话”到“多 Agent 协作”
如果只是让模型回答问题,我们通常不会用到 Agent 这个概念。Agent 真正发挥作用,是在任务需要“理解意图 → 调用工具 → 读取外部数据 → 自主决定下一步”的场景。
比如用户问“帮我查一下订单 ORD-1001,再生成一份跟单报告”,这看起来是一个请求,但背后其实涉及:
- 识别订单号并调用订单查询接口;
- 判断订单当前状态;
- 将订单状态格式化到报告中;
- 把报告保存成 Markdown/PDF 文件;
- 最后通知用户。
如果单个 Agent 把所有逻辑全部包在内部,工程上很快会遇到几个问题:
- 系统提示词越来越长,容易互相干扰;
- 不同业务工具混在一起,权限不好限制;
- 修改其中一个业务流程,可能影响所有对话;
- 不方便单独扩展或替换某个功能模块。
这也是 Hermes Agent v0.21.0 发布时,Bots Mode 会引起关注的原因。它把“一个通用助手包打天下”的思路,转向“多个 Bot 各自负责一块业务”的模式。
1.2 Hermes Agent 是一个什么样的存在
这里先统一一下概念。Hermes Agent 可以理解为面向 Agent 场景的“运行编排层”工具,它既有交互入口,也有任务执行、工具调用、知识库挂载和模型接入相关能力。实际使用时,用户既可以把它当成一个对话助手,也可以通过配置和代码组合出多个自动化角色。
v0.21.0 的核心更新可以拆成三条线来看:
- 第一条是 Bots Mode。它改变了角色组织方式,让一个实例可以同时管理多个职责不同的 Bot。
- 第二条是 Agent 间通信。它让多个 Agent 不再只是“独立功能模块”,而是可以互相传递任务、回传结果和同步状态。
- 第三条可以归为工程化能力。包括部署、鉴权、知识库挂载、模型账号接入等相关能力,虽然并不是每次更新都会大书特书,但实际影响不小。
对于用 Hermes Agent 做原型验证的开发者来说,最重要的一点是:v0.21.0 还是一个偏快速的迭代版本,协议和配置项可能在后面的版本继续调整。因此,在把 0.x 版本用于生产环境之前,一定要先做小范围验证。
2. 核心变化一:Bots Mode 到底做了什么
2.1 Bots Mode 要解决的问题
早期很多 Agent 框架是“一个 Agent + 一个 Prompt + 一组 Tools”。你可以给这个 Agent 指定角色,比如“你是我的数据分析助手”,然后它调用搜索、计算、数据库查询等工具。
这种结构在单一业务场景下问题不大,但一旦接入真实业务,就会变得很别扭。
举例来说,你希望同一个 Hermes Agent 实例既能做企业客服,又能帮销售写日报,还能定时监控线上接口。如果所有能力放进同一个 Agent,系统提示词里可能需要包含非常多的规则,而这些规则之间还可能互相冲突。
最好的做法是把每个职责拆成独立 Bot:
- 客服 Bot 只负责售后问题查询;
- 数据 Bot 只负责读数据库;
- 报告 Bot 只负责把结构化数据转成文档。
Bots Mode 的核心价值,就是让“对话入口”和“业务角色”解耦。用户看到的是同一个聊天窗口,但请求会根据意图被路由到不同的 Bot 上。
2.2 配置 Bots Mode 的思路
不同版本的配置 Schema 可能不同,但核心字段通常包括:
- Bot 名称和 ID;
- 角色描述;
- 使用的模型名称;
- 允许访问的工具;
- 是否允许读取记忆;
- 是否允许调用知识库;
- 超时时间与并发限制。
下面是一个便于理解的 YAML 示例,字段含义和实际项目基本相通。如果你使用的是 Hermes Agent v0.21.0,需要把这段内容按官方文档映射到对应配置目录中,不要直接复制到任意版本里运行。
mode: bots default_bot: assistant bots: - name: assistant description: 默认助手,负责通用问答和任务路由 model: ${MODEL_NAME} prompt: | 你是一个通用助手。 如果用户请求与订单相关,请转给 order_bot。 如果用户请求生成报告,请转给 report_bot。 tools: - web_search allow_knowledge_base: true - name: order_bot description: 订单查询 Bot,只处理订单相关业务 model: ${MODEL_NAME} prompt: | 你负责查询订单状态。 只接受订单号,不要猜测订单号。 无法确认时,向用户追问完整订单号。 tools: - order_search allow_knowledge_base: false - name: report_bot description: 报告生成 Bot,用于生成 Markdown 报告 model: ${MODEL_NAME} prompt: | 你负责把结构化数据整理成 Markdown 报告。 报告必须包含生成时间、数据来源、主要结论。 tools: - file_writer allow_knowledge_base: false在这个配置中,最关键的地方是default_bot。当用户输入一句话时,入口 Bot 需要先判断由谁来处理,而不是直接把原始问题丢给所有 Bot。
2.3 为什么不能把 Bots 和 Agents 完全混为一谈
Bots Mode 和 Agent 间通信同时出现后,很多人会混淆两个概念。
简单区分:
- Bot 更强调“常驻角色 + 单点职责”,类似一个岗位。
- Agent 更强调“自主规划 + 多步执行”,类似一个能独立完成复杂任务的员工。
在 Hermes Agent 这类工程框架里,一个 Agent 内部可能由多个 Bot 协作完成。例如“订单查询 Agent”听起来像一个完整智能体,但它下面可能有一个负责识别意图的 Bot,一个负责查数据的 Bot,一个负责回复用户的 Bot。
因此在设计阶段,不要急着写代码,而是先把职责边界画清楚。推荐用下面三步来定义:
- 列出业务场景中的真实角色;
- 为每个角色定义输入输出;
- 再考虑角色之间有哪些任务流转关系。
Bots Mode 负责的是第 1 步和第 2 步,Agent 间通信则解决第 3 步。
3. 核心变化二:Agent 间通信的关键设计
3.1 Agent 间通信需要传递什么
Agents 之间通信不是简单地把字符串 A 发给 B。一次真正可控的消息传递,至少需要包含以下信息:
- 发送方 ID;
- 接收方 ID;
- 消息类型;
- 消息体;
- 消息 ID;
- 关联 ID(Correlation ID,用来串起同一任务的多条消息);
- 时间戳;
- 超时策略和重试策略。
下面是一个通用消息头示例:
{ "message_id": "msg_20250214_001", "task_id": "task_order_10086", "from_agent": "order_bot", "to_agent": "report_bot", "message_type": "task.request", "payload": { "action": "generate_report", "order_id": "ORD-1001", "order_status": "shipped", "amount": 199.0 }, "created_at": "2025-02-14T10:30:00Z" }这里面的task_id非常重要。多个 Agent 协作时,一个用户请求会产生多条消息,如果缺少关联 ID,日志阶段很难把一次任务的完整链路串起来。
3.2 三种常见通信模式
根据不同的业务需求,Agent 之间的通信可以分成三类。
| 通信模式 | 适用场景 | 典型特征 |
|---|---|---|
| 同步请求-响应 | 调用方必须拿到最终结果才能继续 | 类似 HTTP 调用,有超时 |
| 异步消息队列 | 任务执行时间长,调用方不需要立刻等待 | 使用 Broker 或 Queue |
| 事件发布-订阅 | 一个事件需要触发多个 Bot 执行副作用 | 解耦发送方和处理方 |
在 Hermes Agent 多 Bot 架构中,最常用的是异步消息队列与事件发布订阅的组合。
例如用户让“订单 Bot”查一个订单,查完后需要“报告 Bot”生成 Markdown 报告,两个操作之间并不需要用户一直盯着。订单 Bot 查询完成后,只需要发送一条消息给报告 Bot,任务就转移过去了。
但如果场景是“订单支付成功后,同时触发短信通知、邮件通知、ERP 同步”,更适合使用发布订阅模式。支付结果作为一个事件被发布,关心该事件的多个 Bot 各自消费。
3.3 上下文和状态不应该全部丢在消息里
很多多 Agent 项目失败,不是通信机制没实现,而是“消息体设计得太随意”。
初学者容易犯一个错误:把 Agent A 的完整上下文全部塞进消息体,发送给 Agent B。这样看似方便,却会导致:
- 消息体过大,传输和存储成本上升;
- Agent B 被大量无关上下文干扰;
- 隐私数据被不必要地传递;
- 链路中的错误难以定位。
更合理的做法是:消息体只包含任务所需的核心数据和数据引用,而具体状态保存在状态服务或数据库中。
比如 Agent A 完成了订单查询,并不需要把 500 条日志全部发给 Agent B。它只需要发送:
{ "from_agent": "order_bot", "to_agent": "report_bot", "message_type": "task.result", "payload": { "order_id": "ORD-1001", "status": "shipped", "total_amount": 199.0, "detail_ref": "storage://order/ORD-1001" } }detail_ref的含义是:如果有必要,报告 Bot 可以按引用地址自行读取详细数据。这样既减少了无效传输,也让消息更容易被理解和追查。
4. 实战示例:实现一个“订单查询 + 报告生成”双 Agent 跑通链路
4.1 项目结构设计
下面用 Python 标准库实现一个最小可运行的 Agent 间通信示例。这里不依赖任何特定第三方 SDK,目的是把消息协议和通信模型讲透。
项目目录如下:
hermes-agent-demo/ ├── broker.py # 消息中心,负责 Agent 间路由 ├── bots.py # 两个 Bot 的业务逻辑 └── main.py # 入口,模拟一次任务流转在这个示例里有两个角色:
order_bot:负责查询订单状态;report_bot:负责把订单状态整理成 Markdown 报告。
用户只需要调用一次main.py,order_bot 查询完成后再把结果发给 report_bot。
4.2 消息结构定义
先创建一个message.py文件,用来定义统一消息体:
# message.py import uuid from dataclasses import dataclass, field from datetime import datetime, timezone @dataclass class Message: task: str payload: dict sender: str receiver: str message_id: str = field(default_factory=lambda: uuid.uuid4().hex[:8]) timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat()) def to_dict(self): return { "message_id": self.message_id, "timestamp": self.timestamp, "task": self.task, "payload": self.payload, "sender": self.sender, "receiver": self.receiver, }这里使用message_id来标识每条消息,用task来区分任务类型,用sender和receiver来确定通信双方。
4.3 消息中转 Broker 实现
broker.py承担消息路由的职责。它不关心业务,只负责:
- 注册 Agent;
- 按接收方 ID 把消息放入对应队列;
- 让 Agent 从自己的队列获取消息。
# broker.py import queue import threading from message import Message class MessageBroker: def __init__(self): self._queues = {} self._lock = threading.Lock() def register(self, agent_id: str, maxsize: int = 100): with self._lock: if agent_id in self._queues: raise ValueError(f"agent already registered: {agent_id}") self._queues[agent_id] = queue.Queue(maxsize=maxsize) def send(self, message: Message): with self._lock: target_queue = self._queues.get(message.receiver) if target_queue is None: raise ValueError(f"unknown receiver: {message.receiver}") target_queue.put(message) def receive(self, agent_id: str, timeout: float = 1.0) -> Message | None: with self._lock: target_queue = self._queues.get(agent_id) if target_queue is None: return None try: return target_queue.get(timeout=timeout) except queue.Empty: return None这里用线程锁保证 Agent 注册和收发消息时队列结构不会被并发修改。实际项目中,建议把 Broker 替换成 Redis Stream、RabbitMQ 或 Kafka 等成熟组件。
4.4 订单查询 Bot 与报告 Bot
bots.py中定义两个 Bot 的处理函数。为了突出通信模型,这里把业务逻辑做了简化。
# bots.py from pathlib import Path def query_order(order_id: str): # 实际项目中这里会调用数据库或外部订单接口 mock_orders = { "ORD-1001": {"status": "shipped", "total_amount": 199.0}, "ORD-1002": {"status": "paid", "total_amount": 59.9}, } if order_id not in mock_orders: raise ValueError(f"order not found: {order_id}") return mock_orders[order_id] def build_markdown_report(order_id: str, order_data: dict) -> str: lines = [ "# 订单报告", "", f"- 订单号:{order_id}", f"- 状态:{order_data['status']}", f"- 金额:{order_data['total_amount']}", "", "## 结论", "", "该订单已完成处理,可继续后续履约动作。", ] return "\n".join(lines) def save_report(content: str, output_path: str): Path(output_path).parent.mkdir(parents=True, exist_ok=True) Path(output_path).write_text(content, encoding="utf-8")在实际的 Hermes Agent 场景中,query_order和build_markdown_report会被封装成 Tool,而不是直接写在 Bot 代码里。这里拆成函数是为了让读者看清执行链路。
4.5 串起完整流程
main.py负责把 Broker、Order Bot、Report Bot 串联起来。
# main.py from broker import MessageBroker from message import Message from bots import query_order, build_markdown_report, save_report def run_demo(): broker = MessageBroker() # 先注册角色 broker.register("order_bot") broker.register("report_bot") broker.register("main") # 模拟用户请求到达 order_bot broker.send( Message( task="query_order", payload={"order_id": "ORD-1001"}, sender="main", receiver="order_bot", ) ) # order_bot 消费消息并执行查询 order_task = broker.receive("order_bot", timeout=2.0) if order_task is None: raise RuntimeError("order_bot 没有收到任务") order_id = order_task.payload["order_id"] order_data = query_order(order_id) print(f"[order_bot] 查询结果: {order_data}") # order_bot 把查询结果作为消息发给 report_bot broker.send( Message( task="build_report", payload={"order_id": order_id, **order_data}, sender="order_bot", receiver="report_bot", ) ) # report_bot 消费消息并生成报告 report_task = broker.receive("report_bot", timeout=2.0) if report_task is None: raise RuntimeError("report_bot 没有收到任务") report_md = build_markdown_report( report_task.payload["order_id"], report_task.payload, ) output_path = "output/report_ORD-1001.md" save_report(report_md, output_path) print(f"[report_bot] 报告生成: {output_path}") if __name__ == "__main__": run_demo()运行命令:
python main.py预期输出类似:
[order_bot] 查询结果: {'status': 'shipped', 'total_amount': 199.0} [report_bot] 报告生成: output/report_ORD-1001.md这个示例看起来很简单,但它已经包含了 Agent 间通信最核心的要素:消息体、消息队列、发送方、接收方和任务流转。真正接入大模型后,多出的只是“意图识别”和“工具调用”,消息骨架不会变化。
5. 从本机 Demo 到生产环境:知识库、模型与部署扩展
5.1 外挂知识库的接入思路
很多用户搜索“Hermes Agent 外挂知识库”,本质上是想让 Agent 回答私有文档问题。知识库接入并不神秘,常见链路是:
- 准备文档目录;
- 对文档做切片;
- 调用 Embedding 模型生成向量;
- 将文档向量写入向量数据库;
- 用户提问时先检索相关片段;
- 把检索结果作为参考信息交给大模型生成回答。
这条链路里最容易出问题的不是模型,而是“切片粒度”和“检索召回”。
如果文档太长直接塞给模型,会导致上下文窗口超限、成本升高、回答不聚焦。如果切片太碎,又会丢失上下文,导致召回内容不完整。
实际落地时建议先按 Markdown 标题、PDF 章节或对话主题切分,每片长度控制在几百字左右,同时保留元数据,例如文件名、标题、页码。这样 Agent 在回答时才能给出可溯源的回答。
5.2 模型账号与密钥管理
无论是接入 OpenAI 兼容接口,还是国内大模型平台,密钥管理都是最常见的问题。
不要直接在配置文件中写入明文 API Key,建议使用环境变量或本机密钥管理工具。例如:
export HERMES_MODEL_API_KEY="your-api-key" export HERMES_MODEL_NAME="your-model-name"然后在配置中通过变量引用:
model: provider: openai_compatible model_name: ${HERMES_MODEL_NAME} api_key: ${HERMES_MODEL_API_KEY}有同学安装某些 Agent 桌面版时被引导到网页登录,很可能就是因为启动后需要获取模型服务授权,或者需要登录账号同步配置。此时应查看应用提示,确认是“产品账号登录”还是“模型服务商授权”。建议使用最小权限原则:临时授权、定期轮换密钥,不要把高权限账号写到本地配置里。
5.3 从本机脚本到正式 Agent 服务
上面示例中的 Broker 是内存实现,进程结束后消息也会丢失。如果要把这套链路放到正式环境,至少要解决三件事:
- 消息持久化:用 Redis Stream、RabbitMQ 或 Kafka 替代内存队列;
- Agent 注册发现:让 Agent 在启动时自动注册,而不是手工维护列表;
- 可观测性:记录每条消息的 message_id、耗时、成功失败状态。
v0.21.0 这类版本迭代通常会完善底层通信能力,但真正决定系统稳定性的还是上层设计。不要在版本号上盲目追求新,而应该关注自己的业务是否需要这些新能力。
6. 常见问题与排查思路
升级到 v0.21.0 或使用多 Agent 功能时,下面这些问题比较高频。
| 问题现象 | 常见原因 | 排查与解决思路 |
|---|---|---|
| 安装后被引导到登录网页 | 需要登录产品账号或获取模型服务授权 | 查看页面要求,完成产品注册或配置 API Key |
| 桌面版安装报错 | 缺少运行库、旧版本残留或安装目录权限不足 | 先卸载旧版本,检查安装日志,以普通用户权限安装 |
| Agent 间消息发不出去 | 目标 Agent ID 拼写错误或者未注册 | 检查日志中的 sender/receiver,确认 Agent 已在 Broker 中注册 |
| 回复内容还是旧模型结果 | 修改 Prompt 或模型配置后未重启会话 | 重启 Agent 进程,清理上下文缓存后重测 |
| 知识库文档检索不到 | 文档未切片、未重建索引或没有 ACL 权限 | 检查 Embedding 是否完成,确认文件类型在支持列表中 |
| 多 Agent 任务无法追踪 | 消息缺少 task_id 或 message_id | 在消息头增加关联 ID,并按任务中心化存储日志 |
回到主页面等命令无效 | 不同版本命令不一致 | 在 UI 中查看帮助菜单或使用 help 命令列出当前版本支持的命令 |
这些排错思路并不只适用于 Hermes Agent,其他 Agent 框架也会遇到类似问题。关键还是先定位“配置层、代码层、权限层、模型层”哪一类出错,再针对性处理。
7. 工程落地最佳实践
7.1 先用角色清单替代代码清单
在实际项目里,不要一开始就写 Agent 代码。先把角色清单列出来:
- 每个 Bot 的名字;
- 输入是什么;
- 输出是什么;
- 能调用哪些工具;
- 什么时候应该拒绝回答;
- 异常情况下交给谁处理。
角色清单确定后,再设计通信消息。
7.2 消息协议要“向上兼容”
Agent 间通信消息一旦被多个模块依赖,后续改字段就要特别谨慎。建议在消息中增加协议版本:
{ "version": "1.0", "message_type": "task.request", "payload": {} }如果协议变更,不要让消息直接变成另一种格式,而是先增加新字段,保留旧字段一段时间,做好兼容和过渡。
7.3 给消息打上全链路追踪 ID
在用户请求刚进入系统时,生成一个trace_id,然后把该 ID 传给所有相关 Agent。
message = Message( task="query_order", payload={ "trace_id": "trace_20250214_001", "order_id": "ORD-1001", }, sender="main", receiver="order_bot", )后续日志、报告和异常都会带上这个 trace_id。否则当系统里有 20 个 Agent 同时工作时,遇到问题根本不知道是哪条链路出了问题。
7.4 知识库权限要单独控制
配置了外挂知识库后,要特别注意权限隔离。不要让所有 Bot 都能访问全部知识库。
例如 HR 知识库只应该由 HR 助手访问,订单知识库只应该由订单助手访问。最小权限原则不仅适用于 API Key,也适用于知识库。
7.5 灰度发布优先
v0.21.0 这类新特性建议先在测试环境验证:
- 新建一个独立测试项目;
- 配置两条简单消息链路;
- 观察消息是否能正常流转;
- 确认没有异常后再接入正式数据。
不要在生产环境直接做大规模配置变更。如果是生产 Agent 服务,升级前仍然要做好备份、回滚预案和最小权限控制。
8. 收尾:把版本升级当成一次架构梳理
Hermes Agent v0.21.0 带来的 Bots Mode 和 Agent 间通信,本质上是在引导开发者做两件事:第一,把大 Agent 拆成小 Bot;第二,让 Bot 之间合理协作。
如果你目前在做的项目只有一个简单对话入口,并不需要强行拆出很多 Agent。但如果业务已经明显膨胀,比如一个 Prompt 要兼顾客服、销售、数据分析,那么 Bots Mode 的设计思路值得参考。
真正稳妥的升级路径不是“看到新版本就无脑更新”,而是先画清楚业务角色链路,设计好消息协议,再在测试环境小范围验证。对于 Agent 类项目,版本号永远只是工具,清晰的边界、可控的权限和完整的日志链路才是长期运维的基础。希望这篇文章里的配置思路、消息模型和排错清单能帮你在升级或学习 Hermes Agent 时少踩一些坑。