
把 DeepSeek 接进 QQ 群很多人的第一版做法都差不多写个脚本群里发一句话脚本调一次 API再把返回结果发回群里。跑通不难但用两天你就会发现这个机器人非常“AI”——回复动辄几百字群里聊天被它刷成一片语气像在写工作报告别人问一句它回一篇小论文。问题出在模型吗并不是。DeepSeek 的中文对话能力本身足够好问题出在接入方式上你只是把 API 的输出原样转发到了 QQ中间没有做任何“拟人化处理”。真正能让一个 QQ 机器人“像人”的不是选多强的模型而是回复编排层。其中最关键的一环就是本文要重点讲的多段回复逻辑。同一个问题模型一次性输出 200 字和它分三次、隔一两秒逐句说完用户的感受是完全不同的。前者是接口返回后者是“有人在回我”。这篇文章会从零开始带你完整搭建一条链路QQ 群消息 → NapCatOneBot 11→ Python 程序 → 多段回复逻辑 → DeepSeek 官方 API。读完你可以自己跑起一个会分段说话、有性格、能记住上下文的 QQ 机器人也能理解为什么“多段回复”才是拟人化聊天体验的骨干逻辑而不是附属功能。1. 为什么要把 DeepSeek 接入 QQ 机器人先说需求场景。QQ 机器人最常见的用途有三类个人助手私聊场景下陪聊、写文案、做翻译、解释概念相当于随身带一个 API 助手。群聊吉祥物在技术群、兴趣群、游戏群里被 之后回答问题承担“群友”的角色。社区自动客服让用户私聊机器人完成查资讯、查攻略、基础问答等操作。为什么选择 DeepSeek抛开模型能力对比不谈单从接入成本看DeepSeek 的 API 兼容 OpenAI 格式官方给出base_url和 API Key 之后用现成的 OpenAI SDK 就能直接调用迁移成本非常低。中文对话质量在同类开源模型中属于第一梯队对 QQ 这种强中文场景很契合。但大多数人在接入时会遇到几个非常具体的痛点第一回复长度失控。模型不设置max_tokens或设置得很大一条回复几百字是常态。QQ 群聊里突然刷屏很容易让群成员反感还可能触发平台风控。第二回复节奏完全没有人味。真人聊天是“一句、停顿、再一句”有呼吸感。API 返回是“一次性把所有话都怼给你”读起来像粘贴复制。第三上下文管理缺失。直接调 API 时如果不保存历史消息机器人每次回复都是“失忆”的如果保存全部历史几十轮之后 token 消耗会非常大费用和延迟都不可控。第四群里消息没有触发过滤。如果不做“只在被 时回复”的限制机器人会对群内每条消息都有反应既刷屏又费钱。这篇文章要解决的就是这些问题。你可以把多段回复逻辑理解为在模型输出和用户看到的消息之间插入一个“拟人化调度层”。它不会让模型变聪明但会让模型看起来更像一个真实的人在跟你聊天。2. 核心概念DeepSeek API、OneBot 协议与多段回复逻辑2.1 DeepSeek API 的基本认知DeepSeek 开放平台提供两种常用模型deepseek-chat通用对话模型响应快适合日常聊天。deepseek-reasoner深度思考模型会先输出推理过程再输出回答适合复杂逻辑问题但响应更慢。API 地址是 OpenAI 兼容格式。使用官方 Python SDK 时配置方式是from openai import OpenAI client OpenAI( api_keysk-xxxxxxxx, base_urlhttps://api.deepseek.com )这意味着你在很多原本面向 OpenAI 的工具、脚本里只需要替换base_url和api_key就能把后端切换成 DeepSeek。这是 DeepSeek 生态能快速扩散的一个重要原因。需要留意的是使用deepseek-reasoner时接口返回里会包含reasoning_content推理字段。如果把这个字段原样塞回历史消息再请求部分代理场景下会报错。官方 API 的处理建议是历史消息中的 assistant 消息只保留content字段不要在上下文中携带reasoning_content。2.2 QQ 机器人接入链路OneBot 协议与 NapCat传统上接入 QQ 机器人的方案有很多但大多数第三方框架都基于 OneBot 协议。OneBot 11 是一套聊天机器人通信标准把 QQ 客户端上的消息、事件、群信息抽象成统一的 JSON 格式通过 HTTP 或 WebSocket 与机器人进程通信。社区里常用的实现包括框架状态说明go-cqhttp已停止维护早期使用最多的框架基于 WebQQ 协议NapCat活跃维护基于 NTQQ 的模块化实现配置简单社区活跃Lagrange.OneBot活跃维护基于 NTQQ 的另一种实现NoneBot2 / Koishi机器人框架在 OneBot 之上封装了插件系统和调度能力本文选择NapCat 手写 Python 客户端的组合。为什么不用 NoneBot2因为本文的重点是讲明白多段回复逻辑的底层实现。用现成框架可以很快跑通但“为什么分段、在哪里切段、延迟怎么控制”这些关键点会被框架封装掉。手写一遍理解会更扎实。等你理解了再去用框架扩展插件也不迟。2.3 多段回复逻辑人是怎么说话的先看两个回复方式的对比。假设用户问“你能帮我分析一下为什么我最近总失眠吗”一次长大段回复是这样失眠的原因通常包括心理因素、生理因素、环境因素和生活习惯等多个方面。从心理角度看长期焦虑和精神压力会导致大脑皮层兴奋从生理角度看咖啡因摄入过量、睡前剧烈运动都会影响入睡环境因素比如噪音和光线也会干扰褪黑素分泌……多段节奏回复是这样失眠这件事得从几个角度分开看。 你先回想一下睡前一两个小时有没有喝咖啡或者玩高强度游戏 如果有那多半是大脑一直处于兴奋状态不是身体不想睡。 另外睡前刷手机也是一个很容易被忽略的原因蓝光会抑制褪黑素分泌。第二种方式为什么更像人因为它有停顿、有反问、有递进关系符合人类“边想边说”的表达习惯。多段回复逻辑要做的就是把模型生成的文本按照语义断点或长度阈值切分成多个短片段再按一个接近真人打字的速度逐条发送。实现层面分三步文本切割按中文句末标点。…切分再把碎片拼接成合适长度的片段。异步发送对同一个触发消息依次发送多个短消息而不是一次性发长消息。延迟模拟片段之间加入 0.8 到 2 秒的随机间隔模拟真人打字和思考的时间。这三步加起来就是标题里说的“多段回复逻辑”。3. 环境准备与前置条件开始之前需要准备如下环境一台可以运行 Python 的机器Windows / Linux / macOS 均可。Python 3.9 或更高版本。一个备用 QQ 小号建议不要使用常用主号因为第三方协议存在账号风控风险。DeepSeek 开放平台账号并创建一个 API Key。NapCat 框架本体。依赖库方面本文只需要两个pip install websockets openaiwebsockets用来和 NapCat 的 OneBot WebSocket 通信openai用来调用 DeepSeek 的 OpenAI 兼容接口。如果你打算完全按教程走建议先把 NapCat 下载解压并确保能启动。Windows 下通常直接双击运行脚本即可Linux 服务器上需要提前安装好unzip和基础运行库。具体安装包以官方发布页为准这里不写死下载地址因为不同系统、不同架构对应的包名不同。4. 搭建 QQ 机器人框架NapCat OneBot 114.1 启动 NapCat 并登录 QQ启动 NapCat 之后它会引导你配置 QQ 登录信息。登录步骤一般如下启动 NapCat 主程序。在终端或 WebUI 中获取登录二维码。用小号扫码登录。登录成功之后NapCat 会保持 QQ 在线状态。这时你已经拥有一个“可以被程序控制的 QQ 客户端”。4.2 配置正向 WebSocket 服务NapCat 中需要通过它的 WebUI 来配置 OneBot 服务。打开 WebUI 后找到 OneBot 相关配置项开启“正向 WebSocket 服务器”并设置监听地址和端口。常见配置如下监听地址0.0.0.0或127.0.0.1本机调试用后者更安全监听端口3001事件订阅勾选所有消息事件尤其是群消息和私聊消息这里的端口设置要和后面 Python 代码里的ws_url保持一致。如果你本机只有这一个服务推荐直接使用ws://127.0.0.1:30014.3 验证 OneBot 连接是否就绪Python 代码还没写但你可以先用一个最简脚本来探测 NapCat 的 WebSocket 是否能连通。新建一个test_ws.pyimport asyncio import websockets async def test(): async with websockets.connect(ws://127.0.0.1:3001) as ws: print(WebSocket 连接成功等待 NapCat 推送事件……) while True: raw await ws.recv() print(收到事件, raw[:200]) if __name__ __main__: asyncio.run(test())运行这个脚本时如果 NapCat 已经启动并开启了正向 WebSocket那么脚本会持续输出 QQ 收到的新消息事件 JSON。如果没有任何输出优先排查端口是不是写错了NapCat 里 OneBot 服务是不是没启动是不是登录的是另一个 QQ。这一步跑通后面的工作就踏实多了。5. DeepSeek API 接入配置登录 DeepSeek 开放平台在控制台创建 API Key。创建之后先不要写完整程序用一段最简代码确认 API 能通。from openai import OpenAI client OpenAI( api_keysk-你的实际key, base_urlhttps://api.deepseek.com ) resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: 你是一个话不多的朋友。}, {role: user, content: 你好介绍一下你自己。} ], max_tokens100 ) print(resp.choices[0].message.content)预期是打印一段简短的自我介绍。如果报AuthenticationError检查 API Key 是否复制完整如果报连接超时检查网络环境和base_url是否正确。这里有个要注意的地方API Key 不应写死在公开仓库或博客里。本文为了演示方便写在配置文件中你实际使用时建议通过环境变量或密钥管理服务来加载。6. 完整代码DeepSeek 拟人化 QQ 机器人下面进入核心环节。完整代码分为三个文件qq_bot/ ├── config.json ├── segment.py └── bot.py6.1 配置文件 config.json{ ws_url: ws://127.0.0.1:3001, deepseek_api_key: sk-你的实际key, base_url: https://api.deepseek.com, model: deepseek-chat, max_tokens: 500, temperature: 0.9, session_max_rounds: 10, segment_max_len: 40, segment_delay_min: 0.8, segment_delay_max: 1.8, bot_name: 小深 }参数说明参数含义ws_urlNapCat 正向 WebSocket 地址segment_max_len单个回复片段的最大字符数segment_delay_min/segment_delay_max片段间随机延迟范围秒session_max_rounds每个会话最多保留多少轮消息bot_name机器人名字作为群聊触发词6.2 多段回复切割模块 segment.py# 文件路径segment.py import re MIN_SEGMENT_LEN 4 def split_reply(text: str, max_len: int 40) - list[str]: 将模型回复按句子切分再组合成合适长度的片段。 优先按中文句末标点切单句过长时再硬切。 # 去掉换行按句末标点切分 parts re.split(r(?[。!?…;]), text.replace(\n, )) segments [] buf for part in parts: part part.strip() if not part: continue # 如果单句话超过 max_len说明这句话很长需要硬切 while len(part) max_len: if buf: segments.append(buf) buf segments.append(part[:max_len]) part part[max_len:] # 把短句拼接成接近 max_len 的片段 if len(buf) len(part) max_len: buf part else: if buf: segments.append(buf) buf part if buf: segments.append(buf) # 过滤过短碎片避免出现“嗯。”“对。”这种零碎消息 result [s.strip() for s in segments if len(s.strip()) MIN_SEGMENT_LEN] return result if result else [text]这段代码的要点是用正则(?[。!?…;])做零宽断言切分保留标点在句子末尾。短句会先拼进缓冲区拼到接近max_len再作为一个片段输出。如果某句话本身特别长会硬切成多段避免单条消息过长。过滤掉 4 个字符以内的碎片避免发送“嗯。”“对。”这样过于零碎的消息。6.3 主程序 bot.py# 文件路径bot.py import asyncio import json import random import re import websockets from openai import OpenAI from segment import split_reply # 读取配置 with open(config.json, r, encodingutf-8) as f: config json.load(f) client OpenAI( api_keyconfig[deepseek_api_key], base_urlconfig.get(base_url, https://api.deepseek.com), ) MODEL config.get(model, deepseek-chat) SESSION {} SYSTEM_PROMPT ( 你是一个住在QQ群里的朋友名字叫 config.get(bot_name, 小深) 。 你说话简短、口语化、有温度偶尔使用一点语气词。 不要输出长篇大论不要用 Markdown不要用列表不要用代码块。 同一个意思尽量控制在三句话以内。 ) def build_messages(user_id: str) - list[dict]: 基于当前会话历史构建请求消息列表。 history SESSION.get(user_id, []) messages [{role: system, content: SYSTEM_PROMPT}] messages.extend(history[-config.get(session_max_rounds, 10) * 2:]) return messages async def send_segments(ws, target: dict, text: str): 将文本切成多段按随机延迟逐条发送。 segments split_reply(text, config.get(segment_max_len, 40)) for i, seg in enumerate(segments): if target[type] group: payload { action: send_group_msg, params: { group_id: target[group_id], message: seg, }, } else: payload { action: send_private_msg, params: { user_id: target[user_id], message: seg, }, } await ws.send(json.dumps(payload)) # 片段之间加入随机延迟模拟真人打字节奏 if i len(segments) - 1: delay random.uniform( config.get(segment_delay_min, 0.8), config.get(segment_delay_max, 1.8), ) await asyncio.sleep(delay) def get_reply(user_id: str, user_message: str) - str: 调用 DeepSeek 获取回复并保存到会话上下文。 history SESSION.setdefault(user_id, []) history.append({role: user, content: user_message}) messages build_messages(user_id) # 说明这里为演示方便使用了同步调用。 # 生产环境中建议放到线程池或改用异步 HTTP 客户端避免阻塞事件循环。 resp client.chat.completions.create( modelMODEL, messagesmessages, max_tokensconfig.get(max_tokens, 500), temperatureconfig.get(temperature, 0.9), ) reply resp.choices[0].message.content.strip() history.append({role: assistant, content: reply}) # 控制上下文长度只保留最近 N 轮 max_history config.get(session_max_rounds, 10) * 2 if len(history) max_history: history[:] history[-max_history:] return reply def should_reply_group(raw_message: str) - bool: 群聊中只回复被 或包含机器人名字的消息。 if [CQ:at,qq in raw_message: return True bot_name config.get(bot_name, 小深) return bot_name in raw_message def clean_group_message(raw_message: str) - str: 去掉消息中的 前缀 CQ 码只保留文本内容。 cleaned re.sub(r\[CQ:at,qq\d\], , raw_message).strip() return cleaned async def handle_messages(ws): 处理 NapCat 推送过来的所有事件。 async for raw in ws: msg json.loads(raw) # 只处理消息事件 if msg.get(post_type) ! message: continue message_type msg.get(message_type) raw_message msg.get(raw_message, ) user_id msg.get(user_id) group_id msg.get(group_id) # 私聊直接回复群聊需要触发词 if message_type group: if not should_reply_group(raw_message): continue user_message clean_group_message(raw_message) target {type: group, group_id: group_id} session_key fgroup_{group_id} else: user_message raw_message target {type: private, user_id: user_id} session_key fprivate_{user_id} if not user_message: continue print(f[{message_type}] {session_key}: {user_message}) try: reply get_reply(session_key, user_message) await send_segments(ws, target, reply) except Exception as e: print(处理消息失败:, e) # 出错时发送一条简短提示避免群友以为机器人卡死 await send_segments(ws, target, 我刚走神了你再说一遍) async def main(): async with websockets.connect(config[ws_url]) as ws: print(已连接 NapCat OneBot WebSocket:, config[ws_url]) await handle_messages(ws) if __name__ __main__: asyncio.run(main())6.4 代码逻辑拆解这段主程序有几个关键设计。会话隔离。群聊使用group_群号作为会话 key私聊使用private_QQ号作为 key。这样不同群、不同人之间的上下文不会互相污染。如果某个群聊里有多个用户持续对话所有消息都会进入同一个群上下文这在入门阶段是可以接受的如果要按人隔离可以把 key 改成group_{group_id}_user_{user_id}。触发策略。群聊中机器人只会对被 的消息、或者消息里包含“小深”两个字的消息做出回应。这样不会出现“群里每句话它都接”的灾难场景。私聊则全部响应。多段发送。send_segments把模型回复切段后逐条通过 WebSocket 发送。发送间隔是0.8到1.8秒内的随机值而不是固定值。固定间隔容易产生机械感随机间隔更接近真人。异常兜底。如果 API 调用失败或网络异常机器人会发送一句“我刚走神了你再说一遍”而不是让群友看到一行堆栈报错。准备完成后启动方式python bot.py看到下面这行日志就说明程序已经连上 NapCat已连接 NapCat OneBot WebSocket: ws://127.0.0.1:30017. 多段回复逻辑进阶结合流式输出边生成边发送上面的方案是“等 DeepSeek 生成完整回复再分段发送”。优点是简单、稳定缺点是有感知延迟尤其当问题比较复杂时DeepSeek 可能要生成 5 到 10 秒群友会觉得“机器人怎么半天没反应”。进阶方案是使用流式输出streamTrue在模型生成过程中边积累边发送。达到一个片段长度就先发出去不必等完整回复结束。def stream_reply_messages(session_key: str, user_message: str) - list[str]: 流式获取回复并按固定长度切段返回。 history SESSION.setdefault(session_key, []) history.append({role: user, content: user_message}) messages build_messages(session_key) stream client.chat.completions.create( modelMODEL, messagesmessages, max_tokensconfig.get(max_tokens, 500), temperatureconfig.get(temperature, 0.9), streamTrue, ) full_reply for chunk in stream: delta chunk.choices[0].delta.content if delta: full_reply delta history.append({role: assistant, content: full_reply.strip()}) return split_reply(full_reply.strip(), config.get(segment_max_len, 40))真正的生产级流式玩法更复杂一边生成一边按标点找安全切点找到就立刻发送同时继续生成。但这种做法对消息顺序控制、频率控制要求较高而且容易在群聊里产生“机器人话说到一半突然不说了”的观感。入门阶段我更建议先用“完整回复再切段”把多段回复逻辑跑稳再逐步优化。8. 运行结果与效果验证启动程序后用小号在群里发一条 机器人的消息例如小深 你好呀今天心情不太好预期效果是机器人不会一次性刷出一大段而是在几秒内分成 2 到 3 段消息回复内容类似怎么了听你这语气是遇到什么烦心事了 可以先跟我说说反正群友也不一定会认真看。 不过说真的每次看到你们在群里吐槽我都觉得这才是群聊的灵魂。验证多段回复是否生效可以从三方面判断消息条数一次触发对应 2 条以上连续消息而不是 1 条长消息。发送间隔每条消息之间有 1 秒左右的停顿而不是几乎同时发出。上下文连续接着追问“你还记得我刚才说了什么吗”机器人能根据历史记录回答。如果机器人在私聊里也正常回复、群里却毫无反应优先检查触发条件。可能是消息里没有 机器人也可能 CQ 码清理逻辑把消息文本删空了。9. 常见问题与排查方法问题现象可能原因排查方式解决方案Python 连接不上 NapCatWebSocket 地址或端口错误检查 NapCat WebUI 中端口设置统一ws_url与监听端口日志无任何事件输出消息事件订阅未勾选检查 NapCat 事件订阅配置勾选群消息和私聊消息事件群聊不回复触发条件未满足确认消息是否包含 或机器人名字修改should_reply_group逻辑API 报401认证错误API Key 错误检查配置文件重新复制 Key确认没有多余空格API 报超时模型响应慢或网络不稳定查看请求耗时和日志改用deepseek-chat调小max_tokens出现reasoning_content相关报错深度思考模型的历史消息携带了推理字段检查保存历史的代码只保存content字段过滤reasoning_content回复仍然一次性刷屏切段函数未被调用在send_segments中打印切段结果确认split_reply生效QQ 账号收到风控提示发送频率过高或内容触发平台规则检查账号安全中心降低频率增加延迟建议使用小号测试多群上下文串到一起会话 key 设置不严谨查看 SESSION key 命名使用group_{gid}_user_{uid}粒度10. 拟人化调优与工程最佳实践跑通多段回复只是第一步。真正让机器人“耐看”还需要做几轮拟人化调优。第一提示词里写清楚“人设”和“说话禁忌”。很多机器人一眼假不是因为模型不好而是因为提示词里只写了“你是 AI 助手”于是模型默认进入客服模式。推荐在人设中明确约束你是一个QQ群里的朋友不是助手你说话简短不用 Markdown同一个意思尽量三句话内讲完不要输出代码块、列表、引用等结构化文本。第二把延迟做的更自然。固定 1 秒延迟和随机 0.8 到 1.8 秒延迟用户的感受完全不同。更进一步可以根据问题的复杂度动态调整延迟短问题延迟短长问题延迟长。第三控制单条消息长度。QQ 对超长文本有限制更重要的是单条消息过长在手机端阅读体验很差。建议segment_max_len设置为 30 到 50 之间。太短会出现“碎片感”太长又会回到刷屏问题。第四对话上下文的成本控制。当前代码用SESSION在内存中保存历史进程重启后清空。生产环境建议把上下文存到 Redis并按用户设置 TTL。请求时只组装最近 N 轮消息既能省 token也能降低延迟。第五注意非官方协议的风险。NapCat 这类基于 NTQQ 的非官方方案仅建议用于个人学习和技术研究。如果要做对外发布的产品级 QQ 机器人请参考腾讯官方 QQ 开放平台的机器人接入方案遵守平台规则避免账号风险。第六生产化部署。如果机器人需要 7x24 小时运行不要只靠一个前台python bot.py。可以使用systemd、supervisor或 Docker 来托管进程并配置日志轮转。同时建议加一个简单的健康检查接口定期探测 DeepSeek API 和 NapCat WebSocket 的连接状态。11. 总结这篇文章的核心点可以概括成一句话DeepSeek 接入 QQ 机器人的难点不在 API 调用而在于回复编排。多段回复逻辑通过“文本切分 分批发送 随机延迟”三个动作把模型的长输出改造成符合人类聊天节奏的短片段。再配上一个有性格的 system prompt、一个合理的触发策略、一组严格控制长度的配置项机器人就会从“接口搬运工”变成“群聊里的一个活人”。下一步你可以继续研究的方向有三个接入更多能力在触发词中识别“ /画图 ”“ /翻译 ”等命令让机器人不是只能聊天。优化上下文策略用摘要压缩历史消息而不是简单截断让长对话仍然保持记忆。增加权限与审核群管理可以给机器人设定黑名单、白名单、敏感词过滤生产环境必须做这一层。如果你现在正打算做 QQ 机器人建议先把这篇文章里的完整代码跑通再动手改人设和触发词。多段回复逻辑的调参segment_max_len、延迟范围、max_tokens直接决定聊天观感值得反复调试。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。