从零写一个 Hermes Agent 的 QQ 插件,顺便开源了

Shi Yi2026/10/26devaionebotlinux

想在自己的服务器上跑一个 Hermes Agent,然后通过 QQ 控制它。听起来一件事,结果从头到尾折腾了一整天——最后不是装完了,而是自己写了一个开源插件。

这篇文章记录整个过程:从选型、部署、踩坑,到被逼着造轮子,最后发布到 GitHub。


📌 背景:Hermes 官方不支持 QQ

Hermes Agent 是 Nous Research 出的开源自主智能体,能持久记忆、自动创建技能、跑定时任务,功能很顶。但它的消息网关只支持 Telegram、Discord、Slack 那一票国外平台——没有 QQ。

INFO

关于 OneBot 扫描事件的坑,我之前踩过一次,可以看这篇:关于onebot扫描事件

先说结论,QQ 接入有三条路:

方案思路问题
官方 QQ Bot API腾讯官方机器人消息审核、主动推送限制多,不自由
现成开源桥接NapCat + 桥接项目要么 star 个位数不敢用,要么只解决入站
自己写插件以真插件形式接进 Hermes工作量大,但架构最干净

我最后选了第三条。中间的过程比较曲折,往下看。


🛠️ 部署:三个容器的连环坑

服务器是国内的,国际出口基本残废。记录几个印象深刻的坑:

GitHub 拉不动。一开始想源码构建 Hermes 镜像,git clone 卡 15 分钟不动。测了一圈镜像站,git clone 全被 403 或超时。最后发现——官方根本就有 Docker 镜像 nousresearch/hermes-agent,白折腾了。

TIP

遇到"下载不动"先怀疑人生,再怀疑方案。也许根本不需要下载这玩意。

Docker Hub 也拉不动。docker pull 停在 Pulling fs layer 一动不动。解法是镜像站前缀直拉:

docker pull docker.1ms.run/mlikiowa/napcat-docker:latest
docker tag docker.1ms.run/mlikiowa/napcat-docker:latest mlikiowa/napcat-docker:latest

同一个镜像换几个站轮着重试,断点续传几次就下来了。

最终架构是三个容器:

QQ ⇄ NapCat(OneBot v11) ⇄ AstrBot ⇄ Hermes Agent ⇄ GLM

AstrBot(3.9 万 star)当对话前端,Hermes 当大脑,GLM Coding Plan 出算力。跑通的那一刻还挺爽的。


🤔 但是:Hermes 在 QQ 里是个"残疾人"

跑起来才发现问题:AstrBot 只是把 QQ 消息转成 API 请求发给 Hermes,Hermes 返回的只能是文本。

它不知道 QQ 的存在。不能发文件、不能发图片、不能主动推送消息——一个能执行命令的强力 agent,在 QQ 里只能动嘴不能动手。

去 NapCat 社区转了一圈:官方有个 PR 给 Hermes 加 NapCat 支持,挂了三个月没人理,review 意见没人改;第三方项目要么 patch 源码(升级就炸),要么 star 只有十几个。

行吧,自己写。


🧐 开发:对着官方文档逐行核对

Hermes 的插件系统是正经设计过的:放 ~/.hermes/plugins/ 目录,实现 BasePlatformAdapter 的三个方法(connect / disconnect / send),通过 register(ctx) 注册,就能获得和 Telegram/Discord 同级的网关待遇——完全不用改源码。

我找了官方 IRC 插件当参考,照着写了:

hermes-napcat/
├── plugin.yaml    # 插件清单
├── __init__.py    # register(ctx) 注册入口
├── onebot.py      # OneBot v11 WS 客户端(重连、API 调用)
├── adapter.py     # 平台适配器(消息收发、白名单)
├── tools.py       # 7 个 QQ 工具
└── schemas.py     # 工具的 JSON Schema

设计上花心思的点:

  • 双重白名单取交集:群白名单(插件层过滤,名单外的群连配对提示都不发)∩ 用户白名单(网关层),群里只有指定的人能指挥
  • 按需可见:平时完全不读群聊(零 token 消耗、零隐私记录),你问"群里聊了什么"它才调 get_group_msg_history 拉记录——"我让他看见他才看见"
  • 零额外依赖:只用 Hermes 自带的 aiohttp

🐛 实测:五个 bug 的连续剧

代码写完只是开始。上服务器实测,bug 接连有冒出来,一个比一个隐蔽:

#Bug教训
1connect() 多了个 is_reconnect 参数不认识接口签名要看真实调用,文档不一定全
2工具 handler 写成 async def,返回的是协程对象文档说了 handler 必须是同步函数,我没仔细读
3消息里的 [CQ:at,qq=xxx] 原样透传给模型收消息要解析 CQ 码,发消息也要把 CQ 转回结构化段
4闭包里 message = cq_to_segments(message) 直接 UnboundLocalError赋值让 Python 判定局部变量,右侧读时还没赋值
5发文件报 NapCat 错误 1200「识别URL失败」Hermes 和 NapCat 是两个容器,路径根本不可达,最后用 base64 内联解决

DANGER

最痛的领悟:只测函数不测调用链,等于没测。bug 2 和 bug 4 都是"函数单元是对的、串起来就炸",后来我把测试升级成全链路(同步入口 → 线程桥 → 事件循环 → mock 客户端)才锁住。

有意思的是 bug 3 的发端问题——是 Hermes 自己读了我的插件源码定位到的,它指出了 send() 里没做 CQ 转换。agent 帮我 debug 我写的、用来驱动它的插件,套娃了属于是。


✅ 结果:v0.2.0 已开源

最终版本 9 个 commit,五组回归测试全绿,发布了 v0.2.0:

地址:https://github.com/ShiYioo/hermes-napcatopen in new window

功能一览:

  • QQ 私聊 / 群聊对话(群聊 @ 门控 + 双重白名单)
  • 7 个工具:主动发消息、发文件(base64 跨容器投递)、群列表、按需拉群聊记录、成员查询、禁言、踢人
  • 断线自动重连(指数退避 + 幂等启动防双连接)
  • cron 定时任务结果直接推送到 QQ

现在的完整架构:

入站(对话):QQ → NapCat → AstrBot → Hermes
出站(动作):Hermes → napcat 工具集 → NapCat → QQ

AstrBot 管人设和插件生态,hermes-napcat 给 agent 一双能主动干活的手。


📝 总结

  1. 先找官方镜像再源码构建,能省半天
  2. 插件开发前,把参考插件的真实调用方式核一遍,比读十遍文档有用
  3. 交付前跑全链路测试,函数级测试会漏掉协程、闭包、线程桥这类"串联才炸"的问题
  4. 跨容器传文件,路径都是虚的,base64 内联才是通用解
  5. 生态缺什么就写什么,写完就是你的开源项目

从"想通过 QQ 聊天"到"给这个生态填了个空位",一天时间。挺值的。

Last Updated 10/26/2026, 3:27:27 PM