给 QQ 机器人接上大模型|官方 WebSocket 接入 + 零依赖 Python(附完整源码)

自己在 QQ 上跟 AI 聊天,其实不难——难的是把那些坑趟平。这份源码就是趟完坑的版本,直接能用。

先说清楚它是什么:收到 QQ 消息 → 拼上下文 → 调用任意 OpenAI 兼容接口 → 把回复发回 QQ。 它不是带工具能力的 Agent,读不了你电脑上的文件、跑不了命令,就是个聊天桥。这一点先讲明白,免得预期错位。

两种接入方式

对比项 官方机器人 NapCat + OneBot
账号 独立机器人账号,带「机器人」标识 你的小号
封号风险 无 有
公网 IP 不需要 不需要
群里回话 只在 @ 机器人时 默认 @ 时,可关
门槛 注册开发者、创建机器人 扫码即用

推荐官方。网上很多教程默认你要有公网服务器配 Webhook,其实官方支持 WebSocket 长连接, 官方文档原话是「不依赖公网服务器部署,适合个人单机使用」。也就是说一台普通 Windows 电脑就能跑。

零依赖是怎么做到的

整个项目不需要 pip install 任何东西,Python 3.10+ 自带的标准库就够。

唯一的障碍是官方网关走 WebSocket,而 Python 标准库没有 WS 客户端。常见做法是装 botpy, 但它会拖进 aiohttp 一串依赖。这里的做法是手写了一个约 200 行的 RFC6455 客户端 ws_client.py: 握手校验 Sec-WebSocket-Accept、掩码帧收发、ping/pong 自动应答、分片重组。

好处不只是省依赖——你完全知道它做了什么。坏处是自己要测得够狠,所以自测里单独验了 125 字节边界、 126 和 127 两种扩展长度、以及 90KB 大帧的收发往返。

快速开始

1. 开通官方机器人

  1. 登录 QQ 开放平台(q.qq.com)注册并完成主体认证,个人可以。
  2. 「创建机器人」,填头像、昵称、简介。
  3. 进机器人管理页 →「开发设置」,记下 AppID 和 AppSecret。
  4. 同一页的「事件订阅与回调地址」,连接方式选 WebSocket。
  5. 「内部体验号码」加上你自己的 QQ 号——这一栏最多 20 个号,不受服务范围限制,

等于绕过审核先自用,比等服务范围审批快得多。

2. 配置

copy .env.example .env

编辑 .env,至少填五项:

LLM_BASE_URL=https://api.deepseek.com/v1
LLM_API_KEY=sk-你的key
LLM_MODEL=deepseek-flash
QQ_APP_ID=你的AppID
QQ_CLIENT_SECRET=你的AppSecret

模型端只要兼容 OpenAI 的 /chat/completions 就行,DeepSeek、各类中转、本地 Ollama 都可以。

3. 启动

python qq_official.py

看到这两行就成了:

鉴权成功,机器人「你的机器人名」,session=...
网关已连接 wss://api.sgroup.qq.com/websocket/,心跳 41s

然后给机器人发条私聊。

4. 锁定白名单

第一轮白名单是空的,谁找到这个机器人都能用你的额度。跑起来后日志会打印:

首次见到 private 会话 openid=ABC123DEF... (你的昵称)—— 想锁定就把这个 id 填进 ALLOW_PRIVATE

把这个 openid 填进 .env 的 ALLOW_PRIVATE,群消息填 ALLOW_GROUPS,重启生效。

注意 openid 不是 QQ 号,是机器人和用户之间的映射,每个机器人都不一样,只能从日志里拿。 这也是为什么白名单不能预先填——装好先用一轮,把日志里的值抄进去。

三个实际踩过的坑

这部分是本文最值钱的地方,都是真撞过并定位了原因的。

坑一:群里 @ 它不回,但私聊正常

不是代码问题。日志里能看到 GROUP_ADD_ROBOT,说明机器人确实进群了,但之后一条消息事件都没有。

原因:群消息权限不是「拉进群」就自动有的。必须由群主用最新版手机 QQ 把机器人 移除再重新添加,添加过程中会弹「设置权限」,点进去勾上接收消息。单纯拉进群不算授权。

补充两个小细节:手打的 @机器人名 只是一段普通文本,平台不会当成真 @,必须从成员列表里点选; 只发一个空 @ 不带文字也收不到,脚本会当作没内容忽略。

坑二:msg_id + msg_seq 会被去重

官方的被动回复机制是带 msg_id 回复,同一条消息有次数上限(单聊 4 次、群聊 5 次),而且 相同的 msg_id + msg_seq 重复发送会被去重,报错 40054005。

这意味着:如果你先发一条「收到,正在思考…」占位,再发正式回复,两者都从 msg_seq=1 开始的话, 第二条会被直接丢掉。正确做法是同一条消息的所有回复连续递增 msg_seq——占位吃 1,正文从 2 开始。

源码里 send_reply() 返回下一个可用序号,就是这个原因。

坑三:推理模型会返回空内容,而且不报错

换成 deepseek-flash 之后踩到的。这个模型会把 max_tokens 预算先花在 reasoning_content(思考过程)上, 再输出 content。实测 max_tokens=16 时,16 个 token 全被思考吃光,返回:

{
  "content": "",
  "reasoning_content": "我们需要回答中文。用户问……",
  "finish_reason": "length"
}

HTTP 200,没有报错,但内容是空的。 如果代码不判断这点,就会把它当成「模型没响应」。

源码里的处理:默认预算提到 2000,并且第一次拿到空内容时把预算翻倍重试一次,日志里带上 finish_reason 和当时的预算,方便判断是不是又被思考吃光了。

顺便说成本:这类模型的思考 token 一样计费。实测一句「在吗」,输出 7 个 token,思考用了 114 个—— 费用大头在思考上。

顺带一个判断中转端点的技巧

市面上的 API 中转很多,模型名往往只是标签。想知道背后到底是谁,看两个地方:

一、看 usage 字段。 DeepSeek 原生只会吐 prompt_cache_hit_tokens / prompt_cache_miss_tokens。 如果同时出现 cache_read_input_tokens / cache_creation_input_tokens(这是 Anthropic 的命名), 说明网关在混多套字段,大概率不是单一上游。

二、看报错体。 如果错误里出现 account(s) cooling down this model 这类字样, 说明它背后是账号池轮换——同一个模型名可能轮到不同账号甚至不同上游,那模型标签就完全不可信了。

最后提醒一句:模型自报身份毫无参考价值。 实测 DeepSeek 官方 deepseek-reasoner 被问 「你是不是 DeepSeek」时,回答「我是 OpenAI 开发的 ChatGPT」。模型没有可靠的自我认知, 答案是顺着上下文编的——你自己试试,前面对话说它是某某模型,它多半就顺着认了。

其他能力

  • 上下文记忆:默认记最近 8 轮,存 memory.json,重启不丢;/reset 清空当前会话
  • 引用解析:群里引用某条消息跟它说话时,会把被引用原文一起喂给模型
  • Markdown 转纯文本:QQ 不渲染 Markdown,链接保留成 文字 (url) 形式,QQ 里可点
  • 长回复分段:按换行边界切,段间隔 300ms 防触发频率限制
  • CQ 码转义:文本里的 [CQ: 转成全角冒号,防止模型被诱导输出 CQ 码去 @全体成员
  • 单实例锁:重复启动会被拒绝并退出,避免两个进程同时连网关、同一条消息各回一遍

下载

压缩包内含全部源码、配置模板、MIT 许可与致谢说明(NOTICE.md),解压即用。

# 解压后
copy .env.example .env
# 编辑 .env 填 AppID / AppSecret / 模型 key
python qq_official.py
# 想先确认环境没问题,跑自测(不碰真 QQ、不需要真 AppID)
python test_official.py

免责与合规

  • QQ 机器人服务由腾讯提供,本项目与其无隶属关系。使用前请阅读 QQ 开放平台运营规范与《QQ 用户协议》。
  • NapCat 方案属于非官方协议,存在账号风控风险,建议只用小号,且不要用于群发、自动加好友等行为。
  • 本项目采用 MIT 许可开源。其中 QQ 侧文本处理部分移植自开源项目

Derpyu520/qq-bridge(MIT), 已在 NOTICE.md 中保留原作者版权声明。

  • 代码不收集、不上传任何数据,memory.json 与日志都只存在本地。

源码下载:qq-bridge-1.0.0.zip(约 35KB,MIT 许可,解压即用)

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容