先说结论在 Ubuntu 服务器上把 QQ 做成 AI 智能回复机器人这件事现在已经没有想象中那么复杂了。我最近基于 NapCat 这个 QQNT 生态的协议框架搭配 DeepSeek 开放平台的 API做了这套叫 QQSafeChat 的自动回复机器人跑了大概一个多月群里自动答疑、私聊陪聊、关键词触发这些场景都稳定工作。整体链路就是“QQ 消息进来 → NapCat 把事件推给机器人程序 → 程序带着上下文调 DeepSeek → 生成内容后由 NapCat 发回聊天窗口”。这个项目适合谁如果你需要给社群做个 24 小时在线的自动回复助手或者想研究大模型 API 怎么和即时通讯平台打通又不想去啃 QQ 官方开放平台那些冗长的资质审核那么这套方案就是一条成本很低、见效很快的路子。我写这篇文章的目的是把整个搭建过程、核心代码、参数调校、踩过的坑全部摊开讲清楚包括为什么选 NapCat、为什么用 DeepSeek、上下文窗口该怎么管理、进程崩溃了怎么自动拉起让你照着抄也能复现一套能跑的生产级机器人。1. 整体设计思路与技术选型为什么这么定做这类机器人最难的不是写代码而是选对底座。市面上能对接 QQ 消息的机器人方案并不少但能在 Linux 无头服务器上长期稳定运行的靠谱的真不多。传统的老牌框架要么依赖 Windows 图形界面登录要么协议早就跟不上官方升级一段时间就会掉线或者触发限制。相比之下NapCat 基于新版 QQ 客户端生态做协议适配提供 OneBot 标准接口可以直接跑在纯命令行的 Ubuntu 环境里没有图形界面依赖扫码登录一次后就能长期挂机。这一点对于服务器部署来说几乎是决定性的。第二个核心决策是语言模型选型。我对比过好几个大模型 API最终选了 DeepSeek原因有三条一是它的开放 API 价格非常友好个人项目或者小社群完全烧得起跑一个月基本也就一杯咖啡钱二是上下文长度和支持能力足够日常对话使用通用问答、内容摘要、角色扮演这类需求都能覆盖三是它提供了兼容 OpenAI 格式的接口这意味着可以用成熟的 Python SDK 直接调用不需要额外写一套复杂的协议封装。它还支持通过参数控制随机性和回复长度适合把回复风格调得稳重一些。整体架构上我把它设计成完全解耦的三层接入层NapCat 负责和 QQ 服务端通信把收到的群消息、私聊消息转换成标准 OneBot JSON 事件。业务层一个常驻的 Python 进程通过 WebSocket 订阅 NapCat 的事件流过滤、分流、构造 AI 请求。生成层DeepSeek API 接收带上下文的对话记录返回生成的回复文本再由业务层交回 NapCat 发出去。这么拆的好处很明显任何一层出问题都不会拖垮整体比如 DeepSeek API 超时了机器人顶多不回复不会导致 QQ 进程崩溃NapCat 掉线了AI 服务本身还是健康的重连后马上恢复。如果你以后想把底层模型换成别的或者再加一个 Telegram 机器人只需要在业务层加一个适配器接入层不用动。我还特意把回复策略设计成了“双通道”群里默认打扰模式被 才回复避免机器人刷屏私聊则是完整对话模式有上下文记忆可以连续沟通。这样一个机器人既能当群助手又能当私人陪聊各种需求都能同时满足。2. Ubuntu 环境准备与 NapCat 部署细节2.1 先备好运行环境这一步偷懒后面全是坑NapCat 是基于 Node.js 生态构建的所以第一件事是确认 Ubuntu 上有可用的 Node 环境。我建议不要在旧版本上挣扎直接用 Node.js 的长期支持版。Ubuntu 自带的 apt 源里 Node 版本经常偏老容易遇到依赖兼容问题我踩过一次坑后来是用 nvm 装的 v20 LTS 版本一路顺畅。安装命令很简单但有几个细节需要留意。nvm 安装完要重新登录 shell 或者 source 一下配置文件才会生效这个经常被人忽略。装完记得验证版本node -v和npm -v都要有输出。如果服务器之前装过旧版 Node建议先把残留的全局包清掉避免版本冲突。另外系统里最好装一下git、build-essential后面拉代码、编译原生模块都可能用到避免临时安装时环境不干净。# 安装 nvm curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash # 重新加载环境变量 export NVM_DIR$HOME/.nvm [ -s $NVM_DIR/nvm.sh ] \. $NVM_DIR/nvm.sh # 安装 Node.js LTS 版 nvm install 20 nvm alias default 20这里多说一句不要图方便用apt install nodejs尤其不要用 Ubuntu 默认源。我实测过默认源里的 Node 版本跑 NapCat 会出现 WebSocket 握手异常排查了半天才发现是 Node 版本太旧底层 API 行为不一致。用 nvm 多花三分钟后面能省三个小时。2.2 NapCat 安装与启动核心就三步环境准备好之后安装 NapCat 本身不算复杂。去官方仓库下载对应架构的 release 包解压到你计划存放的目录比如/opt/napcat然后直接执行启动脚本就行。服务器是 x86_64 就选 x64 的包是 ARM 的小主机就选 arm64这个别选错。cd /opt/napcat unzip NapCat.Shell.zip chmod x napcat.sh ./napcat.sh首次启动会在终端里打印出一个超大二维码这时候需要用手机 QQ 扫码登录。注意扫码登录时手机和服务器最好在同一个网络环境下异地登录容易被风控拦截。扫码成功后终端会显示登录成功、当前 QQ 号等基本信息到这一步 NapCat 本身就算跑起来了。这里有一个很多人会忽略的点启动方式决定稳定性。如果直接在前台跑./napcat.shSSH 一断开进程就没了。我后面会在第五节专门讲怎么用 systemd 把它注册成后台守护服务让它在崩溃或重启后自动拉起。但在调试阶段前台跑反而方便能看到完整日志出问题也好定位。2.3 OneBot 配置正向 WebSocket 还是 HTTP 轮询NapCat 支持多种与上层应用的通信方式我推荐用正向 WebSocket。它的特点是 NapCat 作为服务端监听一个端口你的机器人程序作为客户端主动连上去然后事件就会源源不断地推过来不需要 HTTP 轮询实时性和效率都更好。HTTP 模式是机器人程序作为服务端、NapCat 回调过来虽然也能用但涉及到回调地址配置和公网可达性问题在服务器环境里多一层麻烦。在 NapCat 的配置界面里进到网络配置添加一个 WebSocket 服务端监听地址填0.0.0.0端口用一个不冲突的比如3001。这里给出我实际使用的关键参数配置项我的取值说明连接类型正向 WebSocket机器人程序主动连接 NapCat监听地址0.0.0.0允许本机所有网卡访问监听端口3001避免与常用服务端口冲突消息格式原始消息(raw)保留 CQ 码文本方便提取 和图片事件上报全部开启群消息、私聊消息、系统通知都上报配置完记得重启 NapCat 让配置生效。验证方式很简单随便写一个 WebSocket 客户端连上ws://127.0.0.1:3001然后用另一个 QQ 号往机器人号发条消息看终端是否能收到 JSON 事件。能收到说明底层链路已经通了。2.4 登录态与多 QQ 实例管理NapCat 支持同时挂多个 QQ 实例每个实例独立配置。如果你有多个业务群分别用不同机器人管理可以分别启动多个实例端口错开就行。不过我这里建议刚开始不要贪多一个实例跑通了再加否则出问题的时候要同时排查多个进程内耗太大。还有一点关于登录态QQ 的登录凭证是有有效期的长时间运行后可能会掉线。我自己的策略是每天用 crontab 检查一次进程存活和登录状态如果掉了就在凌晨低峰期自动重启一个登录脚本重新扫码。二维码扫码这种动作没法完全自动化所以我的做法是机器人号绑定的手机装一个远程推送扫码画面的工具但具体工具因环境而异就不展开说了。核心思路是不要让登录态问题成为你上线的拦路虎提前想好掉线后的恢复流程。3. 机器人程序设计与 DeepSeek 接入的实操要点3.1 事件订阅与消息分流OneBot 协议到底在传什么NapCat 推到 WebSocket 的数据是标准的 OneBot JSON 事件结构非常规整。我贴一段实际收到的群消息事件示例你感受一下{ post_type: message, message_type: group, time: 1735000000, group_id: 123456789, user_id: 987654321, message_id: 5500112233, raw_message: 你好, sender: { user_id: 987654321, nickname: 张三, card: 群名片 } }这里面post_type是事件类型message_type区分群消息还是私聊消息group_id是群号user_id是发送者 QQraw_message是文本内容。机器人程序要做的第一件事就是解析这个 JSON、认出“这是谁在哪个群说了什么”。我的设计里分流逻辑是这样的群消息检查消息文本里有没有 机器人或者是否包含设定的关键词。如果有就响应没有就静默避免刷屏。如果群消息带了关键词但是高频重复要做频率限制。私聊消息凡是 QQ 好友发来的消息一律进入 AI 对话流程。私聊场景默认带 10 轮上下文记忆可以实现连续对话。后续我还会加一个“免打扰模式”通过命令切换防止机器人长时间不说话导致用户以为它挂了。系统通知比如有人进群、退群可以选择回复欢迎语但这个功能初期先关掉集中精力把对话做稳。值得注意的是消息文本里如果包含 CQ 码比如[CQ:at,qq123]或者[CQ:image,filexxx]会被一起塞进raw_message。AI 模型不认识这种格式直接发给它会产生奇怪回复。所以我在构造请求前会写一个正则清洗函数把 CQ 码剥掉只保留纯文本。这个细节虽然小但是直接决定了回复质量。3.2 DeepSeek API 接入一个兼容层打天下DeepSeek 的 API 是 OpenAI 兼容格式的所以直接用openaiPython SDK 就能调不需要自己写 HTTP 请求。只需要改两个参数base_url指向 DeepSeek 官方端点api_key换成你自己的密钥。模型名用deepseek-chat就行这是通用的对话模型。核心调用代码非常简单from openai import OpenAI client OpenAI( api_keysk-你的密钥, base_urlhttps://api.deepseek.com ) resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: system_prompt}, {role: user, content: 你好请介绍一下你自己} ], temperature0.7, max_tokens512, streamFalse ) print(resp.choices[0].message.content)参数上有几个值得说的地方max_tokens我设在 256 到 512 之间因为 QQ 聊天的场景下回复太长反而像机器人在写作文短小精悍更自然。512 的上限已经足够覆盖绝大多数回答。temperature决定了回复的随机性0.7 是个比较折中的值。如果是客服型机器人、要求严谨降到 0.3 左右更合适如果是闲聊陪聊可以调到 1.0 让它更活泼。streamFalse是最省事的做法等完整回复一次性拿到。流式输出虽然体验更顺滑但需要额外处理状态在 QQ 这种异步场景下收益不大。密钥管理这里我要专门强调不要把 API Key 直接硬编码在源码里更不要提交到公开仓库。我见过有人把密钥写在代码里推到 GitHub 上几分钟内就被爬虫扫走盗刷。正确做法是从环境变量读取或者在项目根目录放一个.env文件用python-dotenv加载然后把这个文件加进.gitignore。就算项目只是自己用也养成好习惯后面部署到别的机器不用改代码。3.3 上下文管理AI 对话的“记忆”是怎么实现的DeepSeek 模型本身没有记忆每一次调用都是无状态的。所谓上下文记忆其实是你的程序把之前的对话历史拼接进请求里一并发给模型模型才知道“你刚才说了什么”。这就是为什么很多聊天机器人聊着聊着突然“失忆”因为程序没带历史。我的上下文管理策略是给每个用户或者每个群维护一个环形队列只保留最近 N 轮消息。私聊场景保留最近 10 轮群场景保留最近 6 轮因为群聊人多、话题散乱保留太多反而会让模型困惑于“哪句话是谁说的”。在构造请求时把历史消息映射成 OpenAI 的 messages 格式角色按“user”和“assistant”交替标记。messages [{role: system, content: system_prompt}] for item in history: messages.append({role: item[role], content: item[content]}) messages.append({role: user, content: current_message}) resp client.chat.completions.create( modeldeepseek-chat, messagesmessages, temperature0.7, max_tokens512 )这里有一个成本细节上下文越长消耗的 token 越多单次请求费用越高、响应也越慢。所以不要无脑把整个群的所有聊天记录都喂给模型只保留跟机器人对话相关的轮次就够。我踩过坑刚开始图省事把群里最近 50 条所有人都算进去结果模型总是被无关的闲聊带偏而且账单涨得很快。3.4 Prompt 系统设计决定机器人“人设”和边界接入模型之后最关键的一步是写好 system prompt。DeepSeek 这类通用大模型如果没有系统约束回复风格会偏“通用 AI 味”放在 QQ 里显得非常违和。我的 system_prompt 这么写你是 QQSafeChat一个运行在 QQ 群和私聊中的智能助手。 你的风格: 简洁、友好、口语化像熟悉的朋友一样说话不要用作为AI模型这类话开头。 你的能力: 支持知识问答、内容总结、灵感头脑风暴。 边界: 如果被问到无法确认或需要承担责任的问题(医疗、法律、投资决策等)建议用户咨询专业人士不给绝对结论。 注意: 回答控制在100字以内除非用户明确要求详细解释。设计这个 prompt 有几个小心机明确口语化和长度限制模型就不容易长篇大论。给出责任边界模型在敏感问题上不会乱给结论这对面向公开群聊的场景尤其重要。“如有必要追问澄清”这类引导性语句可以加在 prompt 末尾让它在遇到模糊提问时主动反问而不是瞎猜一个答案。你可以根据自己的场景替换人设词。比如做学习社群prompt 里加“你是学习辅导助手擅长拆解问题引导用户思考”做电商客服就加“你是售后客服优先安抚情绪再引导解决流程”。prompt 是免费无限次修改的建议上线后根据实际回复效果多迭代几版。4. 完整实操复现从零搭起一个能跑的机器人4.1 项目结构规划一个清晰的项目目录可以让你后面维护少掉很多头发。我的实际布局是这样/opt/qqsafechat/ ├── bot.py # 主程序: WebSocket监听 事件分发 ├── ai.py # 模型调用封装 上下文管理 ├── config.json # 配置: 端口、管理员、关键词、模型参数 ├── .env # 环境变量: API Key等敏感信息 ├── requirements.txt # Python依赖清单 └── logs/ # 运行日志目录主程序和 AI 封装分开好处是如果想换模型供应商只需要改ai.py主程序根本不用动。config.json用纯文本存非敏感配置管理员 QQ 号、启用关键词、单日发言上限这些都可以放这里不用改代码就能调整策略。4.2 机器人主程序实现细节完整代码我会拆开讲解。先看主程序bot.py核心任务是建立 WebSocket 连接、接收事件、分发到不同处理函数。这里使用 Python 的websocket-client库长连接自动重连的机制自己写。import json import ssl import websocket import ai WS_URL ws://127.0.0.1:3001 def on_message(ws, raw_message): try: event json.loads(raw_message) except json.JSONDecodeError: return if event.get(post_type) ! message: return msg_type event.get(message_type) text clean_cq_code(event.get(raw_message, )) user_id event.get(user_id) if msg_type group: group_id event.get(group_id) should_reply is_at_bot(text) or has_keyword(text) if should_reply: reply ai.get_reply_group(user_id, text) if reply: ws.send(json.dumps({ action: send_group_msg, params: {group_id: group_id, message: reply} }, ensure_asciiFalse)) elif msg_type private: reply ai.get_reply_private(user_id, text) if reply: ws.send(json.dumps({ action: send_private_msg, params: {user_id: user_id, message: reply} }, ensure_asciiFalse)) def clean_cq_code(text): import re return re.sub(r\[CQ:[^\]]\], , text).strip() def is_at_bot(text): return [CQ:at,qq机器人QQ号] in text def has_keyword(text): keywords [小助手, help, 帮助] return any(k in text for k in keywords) def on_open(ws): print(WebSocket 连接已建立) def on_error(ws, error): print(连接错误:, error) def on_close(ws, status, msg): print(连接关闭准备重连) if __name__ __main__: while True: ws websocket.WebSocketApp( WS_URL, on_messageon_message, on_openon_open, on_erroron_error, on_closeon_close ) ws.run_forever(ping_interval30, ping_timeout10) time.sleep(5)这里几个实用细节ping_interval30是让底层每 30 秒发一个心跳包长连接不会因为空闲被服务端断开。run_forever一旦退出就说明连接断了外层套一个while True5 秒后自动重连实现简单粗暴但实测可靠。发送消息时json.dumps里必须加ensure_asciiFalse否则中文会变成\uXXXX转义序列发出去全是乱码和反斜杠。clean_cq_code是前面提到的防 CQ 码污染必须放在消息处理最前面。4.3 AI 调用封装上下文队列与安全过滤再看ai.py重点在于用队列保存上下文以及做基础的安全过滤。from openai import OpenAI from collections import defaultdict, deque client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlhttps://api.deepseek.com ) history_cache defaultdict(lambda: deque(maxlen10)) system_prompt 你是 QQSafeChat... def get_reply_private(user_id, text): if not is_safe_text(text): return 这个话题我不太方便聊咱们换个别的吧。 history_cache[user_id].append({role: user, content: text}) messages [{role: system, content: system_prompt}] list(history_cache[user_id]) reply request_model(messages) history_cache[user_id].append({role: assistant, content: reply}) return reply def request_model(messages): try: resp client.chat.completions.create( modeldeepseek-chat, messagesmessages, temperature0.7, max_tokens512, timeout30 ) return resp.choices[0].message.content except Exception as e: print(AI 请求失败:, e) return 系统忙不过来了稍等一下再试。 def is_safe_text(text): block_words [违法关键词1, 违法关键词2] return not any(w in text for w in block_words)defaultdict(deque(maxlen10))实现自动按用户隔离的上下文队列每个用户最多保留 10 轮超过自动挤掉最早的消息。这么做省心、内存占用也低是个人项目里性价比很高的方案。timeout30很重要。DeepSeek API 偶尔会因为网络波动变慢如果不设超时请求可能一直挂着导致机器人像死了一样。设了 30 秒超时后最坏情况也就是等半分钟收到一个“系统忙”的兜底回复不会无限挂起。4.4 开机自启与守护systemd 让机器人真正“无人值守”前面说过SSH 断开前台进程就会死这不是一个合格的服务。我用 systemd 把机器人封装成系统服务实现开机自启、崩溃自动重启。在/etc/systemd/system/qqsafechat.service里写入[Unit] DescriptionQQSafeChat AI Robot Afternetwork-online.target [Service] WorkingDirectory/opt/qqsafechat EnvironmentFile/opt/qqsafechat/.env ExecStart/usr/bin/python3 /opt/qqsafechat/bot.py Restartalways RestartSec10 [Install] WantedBymulti-user.targetEnvironmentFile是特意写的让 systemd 帮我们把.env里的DEEPSEEK_API_KEY加载进进程环境Python 里os.getenv就能直接读密钥也不会出现在命令行参数里。然后执行sudo systemctl daemon-reload sudo systemctl enable qqsafechat sudo systemctl start qqsafechat sudo systemctl status qqsafechat到这里一个完整跑通的 QQ AI 机器人就真正上线了。之后基本无需人工干预断线自动重连崩溃自动拉起重启服务器也自动恢复。5. 常见问题排查与避坑实录5.1 高频问题速查表我整理了这段时间实际遇到的典型问题按“症状 / 可能原因 / 解决办法”列了个表新手直接对着查就行症状可能原因解决办法NapCat 启动后没有二维码旧 Node 版本导致初始化失败换 nvm 安装 Node 20 LTS 后重试扫码后提示登录失败或风控异地登录、频繁切换登录设备手机和服务器尽量同网络环境降低登录频率WebSocket 客户端连不上 3001端口没监听 / 防火墙拦截 / 配置没生效netstat -tlnp检查端口确认 NapCat 配置已重启群消息不触发机器人没加关键词且没被 确认触发规则消息里带关键词或 机器人机器人回复了一串转义字符发送消息时没写ensure_asciiFalse检查 json.dumps 参数中文必须原样发送AI 回复一直“系统忙”API 超时或余额不足检查 API Key 额度调大 timeout 或换网络环境重试机器人聊几次就“失忆”上下文队列容量太小或没存 assistant 回复检查 deque 的 maxlen确认 assistant 回复也进队列机器人回复离谱答非所问上下文里混入了无关消息或 CQ 码强化 clean_cq_code群场景下减少历史轮数5.2 三个最容易忽略的隐形坑第一个隐藏坑是API 账单与 token 消耗失控。上下文队列越长越容易把成本推高。我上线第一周没做 token 统计月末一看调用量远超预期。后来在request_model里加了resp.usage的日志输出每次请求记录 token 数并设定了每日预算上限和报警阈值。这里强烈建议你把usage数据用起来哪怕只是每天看一眼也能避免意外超支。第二个隐藏坑是机器人被高频刷屏导致 API 并发堆积。群里如果有人恶意连续发几十条 程序会瞬间发几十个 API 请求延迟暴涨还可能触发频率限制。我的解决办法是在主程序里加一层简单节流同一个用户或同一个群5 秒内最多处理一条消息超出的直接忽略。这不算高明但足够挡住 99% 的刷屏场景。第三个隐藏坑是进程“假死”尤其程序连接正常、日志正常、但就是不回复。我排查到的原因包括Python 某个第三方库在特定网络环境下阻塞了主线程以及 API 返回了超长内容导致 json 解析卡顿。最终解决方案是在 AI 请求中加入timeout同时把日志打全——每一条消息进来、每一条回复出去左右加时间戳。有了日志任何卡点都逃不过排查。5.3 账号稳定性和使用边界的经验建议关于 QQ 机器人有一个绕不开的话题账号稳定性。我的经验是控制频率、模拟真人行为比任何技术手段都管用。具体来说不要在短时间内对同一个账号或群连续发送大量消息频率高于真人会被识别为异常。不要用机器人做营销轰炸、违法的群发、搜集他人隐私等操作这既违反平台规则也违背使用 AI 服务的道德边界。敏感内容过滤不能省。我在is_safe_text里维护了一个动态敏感词列表同时让 system prompt 带上责任边界双保险。模型输出不可控这件事你必须有预期所以过滤逻辑放在请求前和回复后各一道比只拦一边更稳。很多人在“QQ 机器人写好了能不能卖钱”这个问题上心动但我劝你冷静。自己做社群工具、做学习助手完全没问题一旦涉及到规模化商业化账号资源、内容合规、服务稳定性都是很重的担子不是加几个关键词就能解决的。先让它安安静静服务好一个群再想扩大这才是合理的节奏。5.4 性能与观察日志是左手统计是右手上线之后不要什么都不管。我的做法是每天抽 5 分钟看两个东西一是logs/下的运行日志确认没有异常报错二是简单的消息统计比如当天处理了多少条消息、平均响应延迟是多少。这些数据不需要复杂的监控系统自己写个脚本跑一下就行。日志格式我统一为[时间] [类型] [用户] [摘要]例如[2025-01-12 10:23:45] [GROUP] [987654321] [bot 介绍一下什么是量子计算]。有了这个查找问题不需要翻山越岭。如果你嫌手写脚本麻烦也可以直接把日志输出到 journaldjournalctl -u qqsafechat随时查看但注意保存策略别让日志文件无限堆大。写在最后的一点个人体会整套系统跑顺之后我最大的感受是协议层反而是最稳定的真正需要花心思的是模型调用策略和上下文管理。很多人一上来就在纠结框架能不能跑通结果折腾了一周还没上线我反而建议先花半小时把 WebSocket 链路跑通哪怕回复内容是写死的“你好”先把“消息进来能回消息”这个闭环做出来再逐步接入 AI、加上下文、加权限控制。这个由简到繁的节奏能让你每天都有可见的进度心态也不容易崩。另外一个小技巧是DeepSeek 这类模型的回复质量跟你给的 system prompt 高度相关别急着调参数先反复打磨 prompt。同一个问题在 prompt 里加一句“控制在 100 字以内”和没加这句体验完全像两个机器人。我的项目 logo、名字都改了好几轮但 prompt 迭代了十几版每一版都能感受到肉眼可见的变化。如果你也想搭这么一套系统我建议从复制我的代码开始先跑通再说然后再一步步替换成你自己的场景。等你在实操中遇到新的问题大概率我也在某个深夜遇到过欢迎留言交流我知无不言。