这篇文章记录一条已经实际跑通的路线:QQ 用户发送消息后,腾讯的机器人网关通过 WebSocket 把事件推送给 Ubuntu 上的 Python 程序,程序调用阿里云百炼 Qwen,再把回答发回 QQ。最终效果同时覆盖群聊 @机器人 和私聊两种场景。
本文使用 qq-botpy,因为它是本次部署验证过的实现。腾讯目前还提供较新的 qqbot-agent-sdk;如果你准备新建复杂的 Agent 项目,可以再评估新 SDK,但不要在排错阶段混用两套 API。
本文测试日期为 2026-08-27。示例中的
qwen3.8-max是本次实际跑通的模型,但模型权限、名称和计费策略可能调整,重新部署时请先在阿里云控制台确认。
整体架构与完成标准
一次完整请求会依次经过以下链路:
QQ 私聊 / 群聊 @机器人
↓
QQ 官方机器人 WebSocket 网关
↓
腾讯云 Ubuntu 上的 qq-botpy
↓
消息路由与指令处理
↓
Qwen OpenAI 兼容接口
↓
生成答案并通过 QQ 回复

各层职责如下:
| 层级 | 组件 | 职责 |
|---|---|---|
| 接入层 | QQ 开放平台 | 创建机器人、分配 AppID/AppSecret、管理测试用户 |
| 传输层 | WebSocket | 接收 QQ 事件,无需自己暴露 HTTP 回调端口 |
| 运行层 | Ubuntu + Python | 常驻运行机器人进程 |
| 路由层 | router.py |
识别帮助、状态和普通问答 |
| 模型层 | ai.py |
调用 Qwen 的 OpenAI 兼容接口 |
| 运维层 | systemd | 后台运行、自动重启、开机启动和日志查看 |
完成部署后,应满足以下标准:
- 群聊中
@机器人 你好能收到回复; - 测试用户私聊机器人能收到回复;
帮助、状态等本地命令不依赖模型也能工作;- 服务重启后能自动恢复,日志可通过 systemd 查询。
准备账号与凭证
开始前准备以下资源:
| 项目 | 要求 |
|---|---|
| QQ 开放平台 | 已创建的官方机器人,以及可用的 AppID、AppSecret |
| 腾讯云服务器 | Ubuntu 22.04 或 24.04,建议至少 2 核 2 GB |
| 网络 | 服务器可访问 QQ 网关与阿里云百炼接口 |
| 阿里云百炼 | 已开通模型服务、创建 Workspace、取得 API Key |
| 测试账号 | 已加入机器人体验名单的 QQ 账号 |
| 本地客户端 | 任意 SSH 客户端,例如 Windows Terminal |
本文会用到四个关键配置:
QQ_APPID
QQ_SECRET
QWEN_API_KEY
QWEN_BASE_URL
QQ_APPID 和 QQ_SECRET 来自 QQ 开放平台,QWEN_API_KEY 来自阿里云百炼。北京地域默认使用 Workspace 专属兼容端点:
https://<YOUR_WORKSPACE_ID>.cn-beijing.maas.aliyuncs.com/compatible-mode/v1
这里的 Workspace ID 和 API Key 不是同一个值。若使用其他地域,应以控制台和阿里云官方文档给出的 Base URL 为准。
配置 QQ 机器人开放平台
创建机器人并取得凭证
登录 QQ 开放平台,新建机器人应用,进入开发设置页面,记录 AppID,并生成 AppSecret。

截图只用于说明入口位置。不同时间的控制台布局可能略有差异,以实际页面为准。
配置服务器公网 IP
在机器人开发设置中,把腾讯云服务器的公网 IPv4 加入 IP 白名单。可以在服务器执行:
curl -4 ifconfig.me
若服务器绑定了弹性公网 IP,应填写该固定地址。使用 NAT、代理或多出口网络时,要确认 QQ 平台实际看到的出口 IP。
添加体验用户
机器人未正式发布前,只允许体验名单中的账号测试。把自己的 QQ 号添加为体验用户,并在手机或桌面 QQ 中完成授权。群聊测试时,还要按平台提示把机器人加入允许测试的群。
选择事件接收方式
本文使用 qq-botpy 主动建立 WebSocket 长连接,不需要额外部署公网 HTTP 回调服务。代码会监听:
on_group_at_message_create:群聊中用户@机器人;on_c2c_message_create:用户与机器人的单聊消息。
创建客户端时必须开启 botpy.Intents(public_messages=True),否则相关公开消息事件不会进入程序。
准备腾讯云 Ubuntu 环境
先通过 SSH 登录服务器:
ssh root@<YOUR_SERVER_IP>
确认系统和 Python 版本:
cat /etc/os-release
python3 --version
安装基础环境:
apt update
apt install -y python3 python3-venv python3-pip curl ca-certificates
实验阶段先在 /root/qqbot 中操作,便于快速定位问题:
mkdir -p /root/qqbot
cd /root/qqbot
python3 -m venv venv
source venv/bin/activate
python -m pip install --upgrade pip
看到终端前出现 (venv),说明虚拟环境已激活。之后安装依赖:
pip install qq-botpy openai python-dotenv
/root/qqbot只适合实验。正式运行时会先把源码迁移到/opt/qqbot,再交给普通用户管理。普通用户无法穿过/root目录,仅对/root/qqbot执行chown并不能解决访问问题。
项目目录与文件职责
项目最终结构如下:
qqbot/
├── .env
├── .env.example
├── .gitignore
├── requirements.txt
├── config.py
├── ai.py
├── router.py
├── bot.py
└── venv/
| 文件 | 作用 |
|---|---|
.env |
保存运行时配置 |
.env.example |
配置模板 |
config.py |
加载并检查配置 |
ai.py |
创建 Qwen 客户端并完成对话请求 |
router.py |
处理本地指令与 AI 问答 |
bot.py |
连接 QQ、接收事件、发送回复 |
requirements.txt |
固化最小依赖 |
从零创建全部配置与代码
以下文件可直接复制。建议逐个创建,遇到问题时更容易定位是哪一层出错。
.env
QQ_APPID=<YOUR_QQ_APPID>
QQ_SECRET=<YOUR_QQ_APP_SECRET>
QWEN_API_KEY=<YOUR_QWEN_API_KEY>
QWEN_BASE_URL=https://<YOUR_WORKSPACE_ID>.cn-beijing.maas.aliyuncs.com/compatible-mode/v1
QWEN_MODEL=qwen3.8-max
保存后立即限制权限:
chmod 600 .env
config.py
import os
from dotenv import load_dotenv
load_dotenv()
QQ_APPID = os.getenv("QQ_APPID", "").strip()
QQ_SECRET = os.getenv("QQ_SECRET", "").strip()
QWEN_API_KEY = os.getenv("QWEN_API_KEY", "").strip()
QWEN_BASE_URL = os.getenv("QWEN_BASE_URL", "").strip()
QWEN_MODEL = os.getenv("QWEN_MODEL", "qwen3.8-max").strip()
def require_config() -> None:
required = {
"QQ_APPID": QQ_APPID,
"QQ_SECRET": QQ_SECRET,
"QWEN_API_KEY": QWEN_API_KEY,
"QWEN_BASE_URL": QWEN_BASE_URL,
"QWEN_MODEL": QWEN_MODEL,
}
missing = [name for name, value in required.items() if not value]
if missing:
raise RuntimeError(f"缺少环境变量: {', '.join(missing)}")
程序只判断配置是否存在,不打印任何真实值。
ai.py
from openai import AsyncOpenAI
from config import QWEN_API_KEY, QWEN_BASE_URL, QWEN_MODEL
client = AsyncOpenAI(
api_key=QWEN_API_KEY,
base_url=QWEN_BASE_URL,
)
SYSTEM_PROMPT = """你是一个运行在 QQ 官方机器人中的中文助手。
回答要准确、简洁、友好;不确定时明确说明,不要编造事实。
"""
async def ask_qwen(user_text: str) -> str:
response = await client.chat.completions.create(
model=QWEN_MODEL,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_text},
],
temperature=0.7,
)
answer = response.choices[0].message.content
if not answer:
return "模型没有返回有效内容,请稍后重试。"
return answer.strip()
这里使用 OpenAI 兼容客户端,但请求实际发往 QWEN_BASE_URL。qwen3.8-max 是本次跑通配置;若收到模型不存在或无权限错误,应先回到百炼控制台检查 Workspace、地域和模型授权,而不是盲目改代码。
router.py
from ai import ask_qwen
from config import QWEN_MODEL
HELP_TEXT = """可用指令:
- 帮助:查看使用说明
- 状态:查看机器人状态
- 其他内容:交给 Qwen 回答
"""
async def route_message(text: str) -> str:
clean_text = text.strip()
if not clean_text:
return "请发送你想咨询的内容。"
if clean_text in {"帮助", "/help", "help"}:
return HELP_TEXT
if clean_text in {"状态", "/status", "status"}:
return f"机器人运行正常,当前模型:{QWEN_MODEL}"
return await ask_qwen(clean_text)
本地指令不调用 Qwen。因此当 帮助 可以回复、普通问题不能回复时,问题大概率位于模型配置或网络层,而不是 QQ 接入层。
bot.py
import botpy
from botpy.message import C2CMessage, GroupMessage
from config import QQ_APPID, QQ_SECRET, require_config
from router import route_message
async def process_message(text: str) -> str:
try:
return await route_message(text)
except Exception as exc:
print(f"处理消息失败: {type(exc).__name__}: {exc}")
return "服务暂时不可用,请稍后再试。"
class MyClient(botpy.Client):
async def on_ready(self):
print(f"机器人已登录: {self.robot.name}")
async def on_group_at_message_create(self, message: GroupMessage):
user_id = message.member_openid
text = message.content.strip()
print(f"收到群聊消息 user={user_id}, length={len(text)}")
answer = await process_message(text)
await message.reply(msg_type=0, content=answer[:1900])
async def on_c2c_message_create(self, message: C2CMessage):
user_id = message.author.user_openid
text = message.content.strip()
print(f"收到私聊消息 user={user_id}, length={len(text)}")
answer = await process_message(text)
await message.reply(msg_type=0, content=answer[:1900])
if __name__ == "__main__":
require_config()
intents = botpy.Intents(public_messages=True)
client = MyClient(intents=intents)
client.run(appid=QQ_APPID, secret=QQ_SECRET)
这里保留了 on_group_at_message_create、on_c2c_message_create 和 public_messages=True。日志记录用户 OpenID 与消息长度。回复暂时截断到 1900 个字符,避免超过消息长度限制;正式产品可扩展为分段回复。
requirements.txt
qq-botpy
openai
python-dotenv
确认功能稳定后,可以用当前环境生成带版本号的锁定文件:
pip freeze > requirements.lock.txt
.env.example
QQ_APPID=<YOUR_QQ_APPID>
QQ_SECRET=<YOUR_QQ_APP_SECRET>
QWEN_API_KEY=<YOUR_QWEN_API_KEY>
QWEN_BASE_URL=https://<YOUR_WORKSPACE_ID>.cn-beijing.maas.aliyuncs.com/compatible-mode/v1
QWEN_MODEL=qwen3.8-max
.gitignore
.env
venv/
__pycache__/
*.py[cod]
*.log
.idea/
.vscode/
启动与功能测试
不要一上来就启动完整机器人。按配置、网络、模型、代码、QQ 的顺序验证,能显著减少排错范围。
只检查配置是否加载
python - <<'PY'
from config import QQ_APPID, QQ_SECRET, QWEN_API_KEY, QWEN_BASE_URL, QWEN_MODEL
print("QQ_APPID:", bool(QQ_APPID))
print("QQ_SECRET:", bool(QQ_SECRET))
print("QWEN_API_KEY:", bool(QWEN_API_KEY))
print("QWEN_BASE_URL:", bool(QWEN_BASE_URL))
print("QWEN_MODEL:", QWEN_MODEL)
PY
输出包含配置是否已加载的布尔值和当前模型名。
检查基础网络
curl -I https://api.sgroup.qq.com
curl -I "https://<YOUR_WORKSPACE_ID>.cn-beijing.maas.aliyuncs.com"
curl -I 可能收到 404 或 405,这不一定代表网络失败;只要能完成 DNS、TLS 并收到 HTTP 响应,就说明基础链路通常可达。
单独调用 Qwen
python - <<'PY'
import asyncio
from ai import ask_qwen
print(asyncio.run(ask_qwen("只回复:Qwen 连接成功")))
PY
这一步成功,说明 API Key、Base URL、模型名与 Python 客户端组合正确。
检查语法与导入
python -m py_compile config.py ai.py router.py bot.py
python -c "import botpy, openai, dotenv; print('imports ok')"
启动机器人
python bot.py
看到“机器人已登录”后,在 QQ 中按下表测试:
| 场景 | 输入 | 预期结果 |
|---|---|---|
| 群聊 | @机器人 帮助 |
返回本地帮助文字 |
| 群聊 | @机器人 用一句话解释 WebSocket |
返回 Qwen 答案 |
| 私聊 | 状态 |
返回当前模型与运行状态 |
| 私聊 | 任意正常问题 | 返回 Qwen 答案 |
| 空消息 | 只 @ 不提问 | 提示发送咨询内容 |
常见报错与分层排查
排错时不要同时改多个文件。先根据现象确定层级,再只处理该层。
| 现象或报错 | 常见原因 | 处理方式 |
|---|---|---|
python: command not found |
未进入虚拟环境或系统只有 python3 |
执行 source venv/bin/activate,或使用 python3 |
No module named botpy |
依赖装到了错误的 Python | 对比 which python 与 python -m pip -V,再重新安装 |
No module named dotenv |
缺少 python-dotenv |
执行 python -m pip install python-dotenv |
配置布尔值为 False |
.env 不在当前目录、变量名错误或格式错误 |
确认工作目录、文件名和等号两侧内容 |
QQ 登录返回 100007 |
AppID/AppSecret 错误、应用状态或环境不匹配 | 回到 QQ 平台重新确认凭证和机器人配置 |
| 一直连接不上 QQ | 出口 IP、DNS、时间或防火墙异常 | 核对公网 IP 白名单,执行 timedatectl 与网络检查 |
curl 根路径返回 404 |
请求的是服务根路径而非具体 API | 只把它作为网络可达性参考,不据此判定 API 不可用 |
| Qwen 返回 401 | API Key 无效、过期或属于其他 Workspace | 重新创建 Key,并确认 Key 与 Workspace 对应 |
| Qwen 返回 404 | Base URL、地域、模型名错误或模型未授权 | 使用控制台显示的专属端点,核对 QWEN_MODEL |
| Qwen 返回 429 | 触发限流或余额/配额不足 | 降低并发、稍后重试,并检查模型额度 |
ImportError: set_user_model |
把旧版示例和当前 SDK 混用 | 删除旧接口调用,按本文的 AsyncOpenAI 实现 |
IndentationError |
粘贴时混入 Tab 或缩进损坏 | 用 python -m py_compile 定位行号并统一四空格 |
| 机器人在线但群聊无回复 | 未 @、未开启公开消息 Intent、体验群未配置 | 检查事件函数名与 public_messages=True |
| 私聊无回复 | 测试账号不在体验名单或事件未触发 | 先检查平台权限,再看终端是否打印收到消息 |
| 帮助能回、普通问题不能回 | QQ 链路正常,Qwen 调用失败 | 单独运行 ask_qwen 测试并查看错误码 |
| 回复发送失败或被截断 | 内容过长或包含平台不接受的格式 | 保持截断,后续实现分段和格式清理 |
可以用这个顺序快速定位:
- 终端完全没有收到事件:查 QQ 平台、体验用户、群和 Intents;
- 收到事件,但
帮助也失败:查 botpy 回复接口和程序异常; 帮助成功,普通问答失败:查百炼端点、Key、模型权限和网络;- Qwen 测试成功,但 QQ 回复失败:查消息长度、内容格式与平台限制。
systemd 后台运行
先迁移到正式目录
实验完成后创建普通用户和正式目录:
adduser --system --group --home /opt/qqbot qqbot
mkdir -p /opt/qqbot
cp /root/qqbot/config.py /opt/qqbot/
cp /root/qqbot/ai.py /opt/qqbot/
cp /root/qqbot/router.py /opt/qqbot/
cp /root/qqbot/bot.py /opt/qqbot/
cp /root/qqbot/requirements.txt /opt/qqbot/
cp /root/qqbot/.env /opt/qqbot/
cp /root/qqbot/.env.example /opt/qqbot/
cp /root/qqbot/.gitignore /opt/qqbot/
chown -R qqbot:qqbot /opt/qqbot
chmod 600 /opt/qqbot/.env
不要复制旧虚拟环境,直接以运行用户重新创建:
sudo -u qqbot python3 -m venv /opt/qqbot/venv
sudo -u qqbot /opt/qqbot/venv/bin/python -m pip install --upgrade pip
sudo -u qqbot /opt/qqbot/venv/bin/python -m pip install -r /opt/qqbot/requirements.txt
先以前台方式验证正式目录:
cd /opt/qqbot
sudo -u qqbot /opt/qqbot/venv/bin/python bot.py
确认能登录后按 Ctrl+C 停止,再配置 systemd。
创建 systemd 服务
新建 /etc/systemd/system/qqbot.service:
[Unit]
Description=QQ Official Bot with Qwen
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=qqbot
Group=qqbot
WorkingDirectory=/opt/qqbot
ExecStart=/opt/qqbot/venv/bin/python /opt/qqbot/bot.py
Restart=on-failure
RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
加载并启动:
systemctl daemon-reload
systemctl enable --now qqbot
systemctl status qqbot --no-pager
持续查看日志:
journalctl -u qqbot -f
查看最近 100 行:
journalctl -u qqbot -n 100 --no-pager
更新代码后的标准操作是先检查语法,再重启服务:
cd /opt/qqbot
/opt/qqbot/venv/bin/python -m py_compile config.py ai.py router.py bot.py
systemctl restart qqbot
systemctl status qqbot --no-pager
后续扩展路线
基础版本跑通后,可以按优先级逐步增强:
| 优先级 | 扩展 | 实现建议 |
|---|---|---|
| P0 | 超时与重试 | 为模型请求增加超时;只对短暂网络错误和 429 做指数退避 |
| P0 | 并发保护 | 用信号量限制同时调用模型的数量,防止瞬间打满额度 |
| P1 | 分段回复 | 按安全长度切分长答案,逐段发送并保留顺序 |
| P1 | 多轮上下文 | 以用户 OpenID 为键保存最近若干轮,并设置过期时间 |
| P1 | 限流与黑名单 | 按用户和群设置每分钟请求次数,阻止滥用 |
| P2 | 工具调用 | 接入天气、知识库或业务 API,并明确权限边界 |
| P2 | 可观测性 | 增加请求 ID、耗时、错误类型和模型用量,不记录敏感正文 |
| P2 | 内容安全 | 根据业务场景增加输入输出审核与人工兜底 |
| P3 | SDK 迁移评估 | 对比 qqbot-agent-sdk 的 Agent、插件和部署能力后再迁移 |
多轮对话不要无限累积。建议保留最近 6~10 轮,并为每个会话设置 30 分钟左右的过期时间;否则上下文会越来越长,延迟和费用也会持续上升。
重建检查清单
如果需要在一台新服务器上重建,可以照下面的顺序执行:
- 创建 QQ 官方机器人,保存 AppID/AppSecret;
- 设置公网出口 IP、体验用户和体验群;
- 开通阿里云百炼 Workspace、API Key 与目标模型权限;
- 安装 Python、venv、pip、curl 和 CA 证书;
- 在实验目录创建虚拟环境并安装依赖;
- 创建
.env、config.py、ai.py、router.py、bot.py; - 依次完成配置、网络、Qwen、语法和 QQ 消息测试;
- 把源码迁移到
/opt/qqbot,重新创建虚拟环境; - 使用普通用户运行服务;
- 配置 systemd、自动重启与开机启动;
- 设置费用告警和日志策略。
官方资料
到这里,一个可维护的 QQ 官方机器人就完成了。最关键的经验不是把所有代码一次粘贴完,而是始终按“平台事件 → 本地路由 → 模型接口 → QQ 回复”的层次验证。这样无论将来替换模型、增加知识库,还是迁移到新的 SDK,都能快速判断问题真正发生在哪一层。