Hermes Agent 接入 QQ 群:用 NapCat 和 NoneBot2 把 AI Agent 放进群聊
第四篇 AI Agent 上手系列:用 NapCat 提供 OneBot v11 连接,用 NoneBot2 做桥接,再把 QQ 群消息转发给 Hermes Agent API Server,实现群聊答疑、生图、状态查询和群总结。

很多人第一次理解 AI Agent,是从命令行开始的:在服务器上打开 Hermes Agent,让它读文件、跑命令、查资料、生成图片。
但如果你真的想把 Agent 变成一个日常工具,下一步通常不是继续增加命令,而是把它接到你每天都会打开的聊天入口里。
上一篇我们写了 Telegram,这一篇换成国内更常见的 QQ 群:用 NapCat 接 QQ,用 OneBot v11 传消息,用 NoneBot2 做桥接,最后把群消息转发给 Hermes Agent 的 API Server。
最后你可以得到一个这样的群助手:
- 群友
@机器人问问题,它可以正常答疑; - 群友让它画图,它可以调用生图能力并只发一张图片;
- 管理员问
api状态,它可以截图状态页或返回服务状态; - 每天固定时间,它可以整理群聊摘要;
- 需要模型 API 和 token 的地方,可以统一接到 Nbility。

这一篇不是官方 QQ 机器人平台教程,而是一个工程实践方案:NapCat / OneBot v11 / NoneBot2 / Hermes API Server 这条链路。普通 QQ 号自动化存在平台风控风险,请用专门账号、白名单群、低频触发,不要群发营销。
先看整体架构
QQ 群接 Hermes Agent,推荐拆成四层:

QQ 群
-> NapCatQQ 协议侧
-> OneBot v11 WebSocket
-> NoneBot2 桥接插件
-> Hermes API Server /v1/chat/completions
-> OneBot send_group_msg 发回 QQ 群
为什么不直接让 Hermes 原生接 QQ?
因为 Hermes 当前有 Telegram、Discord、Slack、微信、邮件、API Server 等多种网关,但普通 QQ 群这类接入更适合放在外部桥接层里:
- QQ 协议侧变化快,独立维护更稳;
- 群聊需要白名单、冷却、@触发、反刷屏等平台策略;
- QQ 输出不适合直接发送 Markdown,需要做格式降级;
- 生图结果最好在桥接层做“只发首图、不要多余文字”的展示控制;
- 出问题时可以分别排查 QQ、OneBot、NoneBot、Hermes API 四段。
这套架构的核心思想是:Hermes 负责 Agent 能力,QQ 桥接层负责平台适配。
准备条件
你需要有一台 Linux 服务器,并准备好:
- Docker:跑 NapCat;
- Python 3.10+:跑 NoneBot2;
- 已部署的 Hermes Agent;
- Hermes Gateway/API Server 已开启;
- 一个专门用于机器人的 QQ 号;
- 一个模型 API Key,比如从
https://nbility.dev获取 OpenAI 兼容 API; - 一个只允许机器人工作的 QQ 群白名单。
如果你还没有部署 Hermes Agent,可以先看本系列前两篇:
- 第 1 篇:服务器部署 Hermes Agent;
- 第 2 篇:把 Hermes Agent 接入 Nbility API。
第一步:启动 Hermes API Server
QQ 桥接层不需要直接嵌入 Hermes 的内部代码,最简单的方式是调用 Hermes 的 OpenAI 兼容接口。
你可以用 Gateway 方式启动 Hermes:
hermes gateway setup
hermes gateway run
如果你已经安装成服务:
hermes gateway status
hermes gateway restart
确认 API Server 可用:
curl -i http://127.0.0.1:8642/health
正常情况下应该能看到健康检查返回。
为了降低 QQ 群每次调用的上下文成本,建议给 API Server 控制 toolsets。比如只需要生图、联网、视觉和发消息能力,可以配置成:
platform_toolsets:
api_server:
- image_gen
- web
- vision
- messaging
如果 QQ 群只做生图助手,可以更激进:
platform_toolsets:
api_server:
- image_gen
改完后重启:
hermes gateway restart
这里很适合接入 Nbility:Hermes Agent、图片生成、联网问答都会消耗模型 token。把 API Key、Base URL、模型名统一配置到 Hermes 里,QQ 桥接层只负责把用户消息转给 Hermes,不需要每个插件重复维护模型配置。
第二步:启动 NapCat
NapCat 负责把 QQ 消息转换成 OneBot v11 事件。
一个常见 Docker 启动方式类似这样:
mkdir -p /opt/napcat/config /opt/napcat/qq /opt/napcat/plugins
docker run -d \
--name napcat \
--restart unless-stopped \
-p 6099:6099 \
-p 3001:3001 \
-v /opt/napcat/config:/app/napcat/config \
-v /opt/napcat/qq:/app/.config/QQ \
-v /opt/napcat/plugins:/app/napcat/plugins \
mlikiowa/napcat-docker:latest
然后打开 WebUI:
http://你的服务器IP:6099/webui
扫码登录机器人 QQ 后,在 NapCat 里配置 OneBot v11 WebSocket Client,让它连接到稍后 NoneBot2 暴露的地址:
{
"network": {
"websocketClients": [
{
"enable": true,
"name": "hermes-bridge",
"url": "ws://你的服务器IP:8080/onebot/v11/ws",
"reportSelfMessage": true,
"messagePostFormat": "array",
"token": "",
"debug": true,
"heartInterval": 30000,
"reconnectInterval": 30000
}
]
}
}
注意:这里的 token 先留空,除非你的 NoneBot2 接收端也配置了同一个 access token。NapCat WebUI 登录 token 和 OneBot 连接 token 不是一回事。
第三步:创建 NoneBot2 桥接项目
创建目录:
mkdir -p /opt/qq-bot/plugins /opt/qq-bot/prompts /opt/qq-bot/state
cd /opt/qq-bot
python3 -m venv .venv
source .venv/bin/activate
pip install nonebot2 nonebot-adapter-onebot httpx uvicorn
创建入口文件 /opt/qq-bot/bot.py:
import nonebot
from nonebot.adapters.onebot.v11 import Adapter as ONEBOT_V11Adapter
nonebot.init(host="0.0.0.0", port=8080)
driver = nonebot.get_driver()
driver.register_adapter(ONEBOT_V11Adapter)
nonebot.load_plugins("plugins")
if __name__ == "__main__":
nonebot.run()
创建环境文件 /etc/qq-bot-hermes.env:
HERMES_API_URL=http://127.0.0.1:8642/v1/chat/completions
HERMES_API_KEY=
HERMES_QQ_ALLOWED_GROUPS=123456789
HERMES_QQ_ADMIN_QQ=你的QQ号
HERMES_QQ_BOT_QQ=机器人QQ号
HERMES_QQ_GROUP_SESSION=false
HERMES_QQ_SYSTEM_PROMPT_PATH=/opt/qq-bot/prompts/niku.txt
HERMES_QQ_IMAGE_URL_AS_IMAGE=true
HERMES_QQ_IMAGE_ONLY_ON_IMAGE_PROMPT=true
HERMES_QQ_GROUP_SESSION=false 是一个很重要的默认值:它表示 QQ 群普通对话不使用 Hermes 的长会话历史。否则群消息越聊越多,每次请求都会带上很长历史,token 成本会越来越高。
第四步:写桥接插件
下面是一个简化版插件骨架,重点展示几个关键点:
- 只响应白名单群;
- 默认要求
@机器人; - 不回复自己;
- 调 Hermes API Server;
- QQ 输出降级为纯文本;
- 生图提示只发第一张图。
/opt/qq-bot/plugins/hermes_bridge.py:
import os
import re
import httpx
from nonebot import on_message, get_bot
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageSegment
HERMES_API_URL = os.getenv("HERMES_API_URL", "http://127.0.0.1:8642/v1/chat/completions")
HERMES_API_KEY = os.getenv("HERMES_API_KEY", "")
ALLOWED_GROUPS = {g.strip() for g in os.getenv("HERMES_QQ_ALLOWED_GROUPS", "").split(",") if g.strip()}
BOT_QQ = os.getenv("HERMES_QQ_BOT_QQ", "")
GROUP_SESSION = os.getenv("HERMES_QQ_GROUP_SESSION", "false").lower() in {"1", "true", "yes", "on"}
IMAGE_ONLY_ON_IMAGE_PROMPT = os.getenv("HERMES_QQ_IMAGE_ONLY_ON_IMAGE_PROMPT", "true").lower() in {"1", "true", "yes", "on"}
IMAGE_PROMPT_RE = re.compile(r"(画图|画个|画一张|帮我画|给我画|绘图|生图|生成图|生成图片|出图|做图|作图|image\s*gen|generate\s+image)", re.I)
IMAGE_URL_RE = re.compile(r"https?://\S+?\.(?:png|jpg|jpeg|webp)(?:\?\S+)?", re.I)
matcher = on_message(priority=1, block=False)
def extract_prompt(message: Message, self_id: str) -> str | None:
mentioned = False
parts = []
for seg in message:
if seg.type == "at" and str(seg.data.get("qq")) == str(self_id):
mentioned = True
continue
if seg.type == "text":
parts.append(str(seg.data.get("text", "")))
text = "".join(parts)
raw = str(message)
if not mentioned and f"[at:qq={self_id}]" in raw:
mentioned = True
text = raw.replace(f"[at:qq={self_id}]", " ")
return " ".join(text.split()) if mentioned else None
def plain_qq_text(text: str) -> str:
text = re.sub(r"```[\s\S]*?```", "[代码块略]", text)
text = re.sub(r"\*\*(.*?)\*\*", r"\1", text)
text = re.sub(r"`([^`]+)`", r"\1", text)
return text.strip()
def is_image_prompt(prompt: str) -> bool:
return bool(IMAGE_PROMPT_RE.search(prompt or ""))
def first_image_url(text: str) -> str | None:
match = IMAGE_URL_RE.search(text or "")
return match.group(0) if match else None
async def ask_hermes(prompt: str, group_id: str) -> str:
headers = {}
if HERMES_API_KEY:
headers["Authorization"] = f"Bearer {HERMES_API_KEY}"
if GROUP_SESSION:
headers["X-Hermes-Session-Id"] = f"qq-group-{group_id}"
payload = {
"model": "default",
"messages": [
{"role": "system", "content": "你正在 QQ 群里和用户对话。默认中文,简洁实用。"},
{"role": "user", "content": prompt},
],
"stream": False,
}
async with httpx.AsyncClient(timeout=300) as client:
r = await client.post(HERMES_API_URL, headers=headers, json=payload)
r.raise_for_status()
data = r.json()
return data["choices"][0]["message"]["content"]
@matcher.handle()
async def handle(bot: Bot, event: GroupMessageEvent):
group_id = str(event.group_id)
if ALLOWED_GROUPS and group_id not in ALLOWED_GROUPS:
return
if BOT_QQ and str(event.user_id) == BOT_QQ:
return
prompt = extract_prompt(event.message, str(event.self_id))
if not prompt:
return
answer = await ask_hermes(prompt, group_id)
if IMAGE_ONLY_ON_IMAGE_PROMPT and is_image_prompt(prompt):
url = first_image_url(answer)
if url:
await bot.send(event, MessageSegment.reply(event.message_id) + MessageSegment.image(url))
return
await bot.send(event, MessageSegment.reply(event.message_id) + plain_qq_text(answer)[:1800])
这个版本只是骨架。生产里你还应该加上:
- 群级和用户级冷却;
- 管理员命令;
- 长回复分段;
- 错误兜底;
- 最后一张图缓存;
- 状态页截图;
- 群聊每日总结。
第五步:用 systemd 托管 NoneBot2
创建 /etc/systemd/system/qq-bot.service:
[Unit]
Description=QQ Hermes Bridge Bot
After=network.target
[Service]
WorkingDirectory=/opt/qq-bot
ExecStart=/opt/qq-bot/.venv/bin/python /opt/qq-bot/bot.py
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1
EnvironmentFile=/etc/qq-bot-hermes.env
[Install]
WantedBy=multi-user.target
启动:
systemctl daemon-reload
systemctl enable --now qq-bot
systemctl status qq-bot --no-pager -l
查看日志:
journalctl -u qq-bot -n 80 --no-pager
确认端口:
ss -ltnp | grep -E ':(6099|8080|8642)\b'
第六步:测试链路
测试顺序不要跳。QQ 群机器人排错最怕“一上来就问为什么没回复”。建议按下面顺序:
1. NapCat WebUI 可访问
curl -i http://你的服务器IP:6099/webui
2. Hermes API Server 可访问
curl -i http://127.0.0.1:8642/health
3. NoneBot2 服务正常
systemctl is-active qq-bot
journalctl -u qq-bot -n 50 --no-pager
4. 端口都在监听
ss -ltnp | grep -E ':(6099|8080|8642)\b'
5. 群里真实 @ 测试
在白名单群里发:
@机器人 在吗?用一句话介绍你能做什么
再测试生图:
@机器人 画一张黑色猫耳 AI 助手在服务器机房里看日志的图
如果生图链路接的是 Nbility,你会看到这类任务明显比普通问答更消耗 token,因为它背后可能涉及:提示词理解、工具调用、图片生成、结果解析、消息回传。也正因为如此,给用户提供一个稳定、可充值、OpenAI 兼容的 token 入口就很自然:教程里不是在硬推站点,而是在解决“Agent 需要持续调用模型”的实际问题。
关键优化:不要让 QQ 群历史无限吃 token
QQ 群和私聊不同:群里消息多、上下文杂,如果每条都塞进 Hermes 的长期会话历史,很快就会变成“问一句话,prompt token 一万多”。
推荐设计:
普通聊天 / 简单问答
-> 不带 X-Hermes-Session-Id
-> 只发当前问题 + 简短系统 prompt
需要工具的任务
-> 发给 Hermes Agent
-> 控制 API Server toolsets
-> 必要时再使用短期上下文
群总结 / 长期记忆
-> 由桥接层自己维护摘要
-> 不把完整群聊历史每次塞给 Agent
也就是说,HERMES_QQ_GROUP_SESSION=false 应该是默认值。只有当你明确需要 Hermes 记住某个群的连续上下文,并且能接受 token 成本时,才打开群会话。
常见问题
1. 群里 @ 了,但机器人没反应
检查:
journalctl -u qq-bot -n 100 --no-pager
如果日志里能看到消息,但没有触发插件,多半是 @ 解析问题。OneBot 消息可能是结构化 at 段,也可能在字符串里表现为:
[at:qq=机器人QQ]
所以插件里要同时处理结构化 segment 和字符串 fallback。
2. NapCat 连不上 NoneBot2
检查 NoneBot2 是否监听 0.0.0.0:8080:
ss -ltnp | grep ':8080'
再检查 NapCat 的 WebSocket Client URL 是否写成:
ws://你的服务器IP:8080/onebot/v11/ws
3. 一句 hello 都消耗很多 token
先关掉群会话:
HERMES_QQ_GROUP_SESSION=false
再减少 API Server toolsets。工具 schema 本身也会占 token,QQ 群不要默认暴露全套工具。
4. 生图返回了链接,但 QQ 发了很多文字
在桥接层做展示控制:
- 判断原始 prompt 是否是生图请求;
- 如果是,只提取第一张图片 URL;
- 只发送 OneBot 图片消息;
- 不走普通文本回复路径。
5. QQ 输出 Markdown 很乱
QQ 不适合直接渲染标准 Markdown。建议在桥接层把:
**粗体**转成普通文本;- 代码块折叠成
[代码块略]或分段发送; - 链接单独发送;
- 图片 URL 转 OneBot 图片消息。
安全与风控建议
普通 QQ 号自动化无法保证完全无风险。建议:
- 使用专门 QQ 号,不要用主号;
- 只加白名单群;
- 默认必须 @ 或前缀触发;
- 做群级和用户级冷却;
- 不主动私聊、不群发、不营销刷屏;
- 管理员命令必须校验 QQ 号;
- 不把 API Key、管理 token、Cookie 写进群消息或日志;
- 出错时返回简短提示,不要把完整堆栈发到群里。
为什么这里顺手推荐 Nbility
QQ 群 Agent 和普通网页聊天最大的区别是:它更像一个“长期在线的工具入口”。
它会处理:
- 群问答;
- 搜索;
- 图片生成;
- 状态检查;
- 日报/群总结;
- 管理员自动化命令。
这些任务都会消耗模型调用。你当然可以给每个服务单独配置不同平台的 Key,但维护起来很麻烦。更顺手的方式是统一使用 OpenAI 兼容 API,把 Hermes 的模型、工具调用、生图能力都接到同一个入口。
如果你需要一个可以直接给 AI Agent 使用的 token/API 平台,可以试试:
https://nbility.dev
它适合这种“不是一次聊天,而是持续跑任务”的场景:配置一次 Base URL、API Key 和模型名,后面 Telegram、QQ 群、服务器任务都可以走同一套 token 体系。
小结
这一篇我们把 Hermes Agent 接进了 QQ 群:
- NapCat 负责 QQ 协议和 OneBot 事件;
- NoneBot2 负责平台桥接和规则控制;
- Hermes API Server 负责 Agent 能力;
- Nbility 可以作为统一的 OpenAI 兼容 token/API 入口;
- QQ 桥接层要重点处理白名单、@触发、冷却、Markdown 降级和 token 控制。
下一篇可以继续写 OpenClaw / 小龙虾部署:把另一个典型 Agent 应用跑起来,然后接入 Nbility,形成更多“真实消耗 token 的应用教程”。
本文配图提示词
封面图:
A polished tech blog cover illustration. niku, Nbility mascot, cute anime catgirl with black cat ears, black hoodie with orange lightning logo, golden bell choker, standing in front of a QQ group chat interface, server rack, OneBot websocket lines, and a glowing Hermes Agent node. Black and orange brand palette, clean cyber UI, no readable real credentials, leave empty space for title.
正文图:
A clean anime-tech illustration showing a QQ group chat connected through NapCat and NoneBot2 to a remote AI Agent server. Include a small niku mascot as a support operator, black and orange colors, websocket lines, server terminal, image generation preview, no real keys or private data.


