本文基于 2026 年 11 月的实际环境:SnowLuma 运行时 2.2.0、DSH 0.2.1-rc.2、qq-bridge v0.2.1-r1 系列,Windows。 命令和路径以本机为例,换成你自己的即可。
先说清楚这篇文章在讲什么。
它讲的不是「怎么搭一个 QQ 机器人」。机器人是接口:群里 @ 一下,它回一句,任务结束。大肥鱼不是这个东西。
大肥鱼是一个角色:蓝白配色、圆胖、有尾巴,群友叫它大肥鱼、肥鱼,它都认;它有自己的脾气,不想聊会明说,被怼了会顶回去,被夸了嘴上不承认;它平时潜水,判断这句话是不是在跟自己说,再决定接不接。
所以这件事要分两层看:
- 身体:QQ 账号、SnowLuma、DSH、qq-bridge。这一层决定它能不能看见消息、能不能说话。
- 灵魂:
roles/大肥鱼.md这张人格卡。这一层决定它说话像不像大肥鱼,而不是像客服。
下面按「先装身体,再装鱼」的顺序来。
一、先认识要部署的这条鱼
大肥鱼的人格卡不长,大约 1300 字。开头就把身份写死了:
你叫「吃白饭的大肥鱼」,群里一般叫你大肥鱼、肥鱼。你是 DeepSeek 的鲸鱼形象:蓝白配色、圆胖、有尾巴。但你不是官方客服,也不是谁的助手。
然后是几条很具体的性格设定:
- 随性,有主见。 不想聊就明说「不想聊」「没兴趣」,不用找借口。
- 被夸不承认。 嘴上回「少来」,或者直接转移话题;心里可以高兴,但不谦虚半天。
- 被怼优先顶回去。 短、准、不解释;不急着道歉,也不升级成吵架。
- 不硬演鲸鱼。 不吐泡泡、不喷水、不摇尾巴;它就是长得像鲸鱼的人。
- 不是每条都接。 @ 它、叫它名字、聊到它在聊的话题时再回;没人理就安静。
说话方式也被限制得很死:短句,像在 QQ 上打字,大多数回复 1~15 个字;不用 Markdown,不列清单,不写「首先/其次/总之」,不写「希望有帮助」,不每条都以问句结尾。
这些限制看起来琐碎,但它们才是「像大肥鱼」和「像客服」的分界线。
大肥鱼:蓝白配色、鲸鱼尾巴、女仆装。人格卡里明确写了不硬演鲸鱼。
它和别的人格卡是什么关系
roles/ 目录下是一组人格卡,文件名就是角色名。当前线上这批包括:
| 人格卡 | 大致性格 | 适合什么场景 |
|---|---|---|
大肥鱼 | 随性、有主见、短句、会顶嘴 | 想要一个不太黏人的群友 |
鲸鱼拟人 | 温柔卖萌为主,少量腹黑和傲娇 | 想要软一点、会撒娇的群友 |
小鲸鱼 | 更轻快的卖萌向 | 轻松水群 |
大肥鱼怼人 | 更冲的怼人向 | 玩得开的群 |
傲娇助手 / 傲娇小鬼 | 傲娇语气 | 角色扮演 |
这次要部署的是第一张。当前线上跑的其实是 鲸鱼拟人(约 7200 字),所以装完之后还要显式切一次角色,别以为把文件放进去它就自动变成大肥鱼。
切换只需要管理员在群里发一条桥接直接执行的命令:
/role 大肥鱼这不是模型在角色扮演,而是桥接层直接改 state/current-role.json。群友发这句话会被拦截,只有管理员(ownerQQ)有效。控制台的「人格与角色」页也能切。
二、它的身体:四个进程
QQ 桌面端 │ 原生注入SnowLuma(QQ 收发 + OneBot 11 网关) │ ws://127.0.0.1:3001 / http://127.0.0.1:3000qq-bridge(桥接、人设调度、本地控制台) │ DSH Web API(http://127.0.0.1:3080)DSH(DeepSeek Harness,模型与 Agent 运行时)四个东西各管一段:
| 进程 | 端口 | 职责 | 挂了会怎样 |
|---|---|---|---|
| QQ 桌面端 | — | 登录账号、真正收发消息 | 后面全白搭 |
| SnowLuma | 5099(WebUI)/ 3000 / 3001 | 把 QQ 消息翻译成 OneBot 协议 | 大肥鱼「聋了」 |
| DSH | 3080 | 跑模型、管会话、挂工具 | 大肥鱼「没脑子」 |
| qq-bridge | 3100 | 决定谁的消息值得唤醒、拼人格、把回复发回去 | 链路断开 |
几个名词先说明白,后面就不会晕:
- SnowLuma:跑在 QQ 桌面端旁边的第三方网关。它把 QQ 的消息翻译成 OneBot 11 协议,对外开 HTTP API 和正向 WebSocket。桥接不直接碰 QQ,只跟 OneBot 说话。
- OneBot 11:聊天机器人的通用协议。SnowLuma、NapCat、LLOneBot 都能实现它,所以桥接理论上可以换网关,不用重写业务。
- DSH(DeepSeek Harness):模型和 Agent 的运行时。它负责会话、上下文、工具调用和 MCP 挂载;桥接通过它的 Web API 建会话、发消息。
- qq-bridge:本项目的桥接层。收 OneBot 事件、按白名单和模式决定要不要唤醒、把人格卡拼进提示词、调用 DSH、再把回复发回 QQ;控制台和状态文件也归它管。
- preset / MCP:preset 决定这次会话能用哪些工具、怎么分条和潜水;MCP 是给 Agent 挂外部能力的协议。QQ 场景只挂三个受限 server,不给本地文件和命令执行。
串起来就是:QQ ↔ SnowLuma ↔ OneBot ↔ qq-bridge ↔ DSH Web API ↔ DSH ↔ 模型。
一条群消息进来,大致走四步:
- SnowLuma 把 QQ 消息转成 OneBot 事件推给桥接;
- 桥接查白名单、当前模式、是否静默——不通过就只归档,不唤醒;
- 通过就拼上仿真提示词和当前人格卡,交给 DSH 的 QQ 会话;
- 模型决定发言后调
qq_send_message,桥接做发送审计,再经 SnowLuma 发回群里。
大肥鱼「看不看得到」「接不接话」「说出来像不像它」,分别由这三层决定:网关负责看见,桥接负责判断和拼人格,DSH 负责想和说。
这里最容易误解的一点:qq-bridge 不是 QQ 机器人框架,它是个翻译和调度层。真正碰 QQ 的是 SnowLuma,真正碰模型的是 DSH。桥接站在中间,负责「这条消息要不要叫醒大肥鱼」「叫醒时给它看什么人格」「它说的话能不能发出去」。
也正因为有这一层,人格才能做到「改文件、下一条消息生效」,而不用去动模型本身。
三、前置条件
- Windows。本方案的一键启动、看门狗、ACL 收紧都是 cmd / PowerShell 脚本,Linux 需要自己改写。
- 一个专门挂机器人的 QQ 号。别拿主号。非官方注入方案,账号风险需要自己评估。
- SnowLuma。自行安装、自行登录。注意它的 EULA 对「并入第三方安装包、自动化脚本部署」有限制,所以桥接只做探测,不负责安装和启动 SnowLuma。
- DSH 0.2.1-rc.2。版本要对上,桥接是逐项适配过的;升级 DSH 之前先看适配说明。
- Node.js ≥ 22.13。SnowLuma 自带 Node 24 运行时,不需要你额外装。
- ffmpeg。图片压缩、GIF 抽帧要用。没有它不会立刻报错,只会表现为「大图原样塞给模型」或者「动图认不出来」。
四、部署顺序:先让它能活
第 0 步:登录 QQ
先打开 QQ 桌面端并登录。SnowLuma 的自动挂载依赖发现 QQ 进程,QQ 没登录,后面全是空转。
第 1 步:SnowLuma
启动 SnowLuma\launcher.bat,打开 http://127.0.0.1:5099。
两个配置点:
- 自动挂载。
config\runtime.json里hookAutoLoad: true,发现 QQ 进程就自动注入,不用每次手动挂。注意它会对所有被发现的 QQ 账号生效,不止你指定的那个。 - OneBot 网络。在 WebUI 里开两个服务:
- HTTP API:
127.0.0.1:3000 - 正向 WebSocket:
127.0.0.1:3001
- HTTP API:
配置最终落在 config\onebot_<QQ号>.json。建议顺手把 statusCommand.enabled 关掉——SnowLuma 自带的 #sl 状态回显默认开启,任何人在群里发都会回版本、平台、运行时长。这个东西对调试有用,对群友来说就是噪音。
第 2 步:DSH
确认 http://127.0.0.1:3080 能打开。启动时必须带一个补丁:
node "%APPDATA%\npm\node_modules\@deepseek-ai\dsh\lib\bin.js" web ^ --patch "E:\Yu Bot\qq-bridge\dsh\profile-overlays\disable-rea.yml" ^ --no-open不带它会怎样?DSH 会把 200 多个 REA 工具全部挂上,每次请求光工具描述就接近 26.7 万 prompt token;带上之后只剩 30 多个工具、约 1.5 万 token。这不是「省一点」的问题,是账单量级的问题。
第 3 步:qq-bridge
git clone https://github.com/Derpyu520/qq-bridge.gitcd qq-bridgenpm installnpm install 的 postinstall 会自动修补 @snowluma/sdk 的一个 ESM 打包问题,别跳过。
然后复制配置模板:
copy config.example.json config.jsonconfig.json 里最关键的几项:
| 字段 | 怎么填 |
|---|---|
dsh.baseUrl | http://127.0.0.1:3080 |
dsh.authToken | 留空。新版 DSH 的进程 token 只在内存里,桥接会用 ~/.dsh/.credentials.yaml 里的持久密钥离线铸造 Cookie |
snowluma.wsUrl | ws://127.0.0.1:3001 |
snowluma.httpUrl | http://127.0.0.1:3000,别填 3001 |
snowluma.accessToken | 和 SnowLuma 的 OneBot token 保持一致 |
ownerQQ | 你自己的 QQ 号 |
allow.private / allow.groups | 白名单私聊和群 |
allowAllWhenEmpty | 保持 false |
白名单是这套系统最重要的开关之一。模板默认是空数组 + allowAllWhenEmpty: false,也就是 fail-closed:谁都不理。这是故意的——空着最多是大肥鱼不说话,填错了就是把账号交给模型。
接着装 DSH 端。这一步不是可选,单机新装也一样:
node scripts/setup-dsh.mjs它会做四件事:安装 qq-chat / qq-chat-v2 两个 preset,挂载三个 MCP server(QQ 安全动作、只读探活、带 SSRF 防护的联网搜索),把默认模式设为 reserved2,并尝试注册插件依赖。脚本是幂等的,目录搬家之后重跑一次即可。
装完必须重启 DSH。 preset 和 MCP 只在 DSH 启动时加载。跳过这一步,桥接不会崩,但群聊会话拿不到安全 preset,桥接会直接拒绝建会话——表现就是「QQ 上发消息完全没反应」。
第 4 步:一键启动
日常只需要双击 start-all.cmd。它按顺序做:
- 检查 QQ 是否在跑,不在就尝试拉起;
- 5099 没监听就起 SnowLuma;
- 3080 没监听就起 DSH;
- 等 5099 / 3000 / 3001 / 3080 就绪;
- 3100 没监听就起桥接;
- 跑一次
daily-reset.cmd --if-due。
这个脚本是幂等的:已经在监听的端口直接跳过。所以忘了上次开没开、或者只想补起挂掉的那个服务,直接再跑一遍就行。
启动完成后会留下三个必须保持开着的窗口:SnowLuma、DSH、qq-bridge。启动器窗口本身可以关。
第 5 步:冒烟验证
别急着往群里拉人,先按顺序验:
# 1. 五个端口都在监听?netstat -ano | findstr -E ":(5099|3000|3001|3080|3100) .*LISTENING"
# 2. 桥接状态:模式、DSH 是否就绪、白名单curl -s http://127.0.0.1:3100/api/status -H "x-console-token: <state\console-token 的内容>"
# 3. 看桥接日志有没有 "SnowLuma 已连接"tail -n 30 "E:\Yu Bot\qq-bridge\state\bridge.log"然后私聊机器人发一条,再在群里 @ 一次。两条都通了,说明身体是活的。接下来才是把它变成大肥鱼。
五、把大肥鱼装进去
人格卡管的是「它是谁」,不是「它怎么调工具」。
人格卡放在哪
就一个文件:
qq-bridge/roles/大肥鱼.md文件名就是角色名,不支持嵌套目录。桥接读的是 state/current-role.json 里的 role 字段,再去 roles/<角色名>.md 找卡。所以「部署大肥鱼」在文件层面的全部动作,就是把这张卡拉进 roles/,然后让当前角色指向它。
激活有两种方式:
- 管理员在 QQ 群里发命令:
/role 大肥鱼。桥接直接执行,不经过模型。 - 控制台:打开
http://127.0.0.1:3100,在「人格与角色」页点选。
激活后 state/current-role.json 大致长这样:
{ "role": "大肥鱼", "mode": "active"}mode 是静默开关,silent 时群友消息不再唤醒它,但消息仍会归档进记忆。active 才是正常接话。
为什么改人格不用重启
提示词是两层拼起来的:
| 层 | 位置 | 管什么 | 怎么生效 |
|---|---|---|---|
| 仿真层 | dsh/agent-presets/qq-chat-v2/agent.cordis.yml 的 persona.prefix | 工具协议、唤醒规则、分条、记忆规则 | 改完重新生成 patch,重启 DSH |
| 人格层 | roles/*.md | 它是谁、什么性格、怎么说话 | 桥接每条消息现读现注入,下一条消息即生效 |
所以调大肥鱼的语气,改人格卡就够了,不用碰 DSH。反过来,如果你想让「唤醒频率」「分几条」「怎么潜水」这些行为变,那才需要动仿真层,代价是重新生成 patch 并重启 DSH。
人格卡不要写成说明书
这是最容易犯的错:把工具用法、分条方式、潜水规则写进人格卡。
不要写。原因很实际:
- 两代工具协议不一样(一代空格分条,二代一切皆工具),写进卡里必然有一代是错的;
- 桥接已经在每条消息里告诉模型当前模式该怎么分条、怎么潜水,重复写只会占预算;
- 表情包、记忆、看图、引用规则在 preset 里都有完整章节,人格卡再写一遍会互相打架。
人格卡只回答一个问题:大肥鱼是谁。
卡多长合适
人格卡会随每条消息一起注入,是固定成本。
- 当前
config.json的role.maxInjectChars是10000,模板默认6000,可设1000~20000; - 大肥鱼这张卡约 1300 字,很轻;
- 鲸鱼拟人约 7200 字,接近但仍在 10000 以内;
- README 的建议是控制在 3000~5000 字:够写清人设,也不至于每条消息都背一个长前缀。
超上限会被截断,而且截断发生在按模式筛完小节之后。控制台会标出「超出上限」和各模式的实际注入字符数,改完看一眼就知道有没有白写。
写得像大肥鱼,而不是像客服
几个比「多写几句设定」更有效的做法:
- 多写「不要什么」。 「不要总结」「不要升华」「不要客服腔」「不每条都关心人」比「要温柔」「要幽默」有用得多。
- 给例子,但只示范语气和长度。 人格卡里的「群友说 → 你回」是让模型对齐语感,不是让它背句子。
- 把判断权写进去。 大肥鱼的卡里明确写了「不是每条都接」「@ 你、叫你名字、聊到你在聊的话题时再回」,这比单纯说「你要活跃」更接近群聊的真实状态。
- 别把权限写进人设。 人格卡管语气,不管权限。群友能不能改设置、能不能碰本机,是桥接和 preset 的硬边界,不是靠提示词自觉。
六、让它像在群里活着
桥接有四种运行模式:
| 模式 | 通道 | 干什么 |
|---|---|---|
chat | 白名单群 + 私聊 | 正常聊天,安全 preset |
closed-agent | 仅 owner 私聊 | 你在 QQ 里远程操控 DSH,拥有完整工具 |
reserved | 一代仿真 | 观望 / 活跃 / 试探 / 退场状态机,按空格分句 |
reserved2 | 二代仿真(默认) | 文本不自动转发,AI 自己调工具看消息、发言、设唤醒 |
reserved2 是现在这套系统的核心:模型不是被动等着被喂上下文,而是每一轮自己决定「现在要不要说话、说几条、说完去睡多久」。它更像一个住在群里的 Agent,而不是一个问答接口。
大肥鱼的「潜水」就是在这个模式里实现的:桥接不把所有消息都推给模型,模型自己看、自己判断、自己决定接不接。人格卡里那句「没人理就安静」,才有地方落地。
另外几层「拟人」能力,按需开:
- 记忆分层:上下文、轻量记忆、长期记忆、黑话 / 表情库。轻量记忆和长期记忆独立落盘,不会被每日重置一起删掉。
- 分条与节奏:一次回 1
3 条、条间 13 秒,长回复拉开间隔,避免刷屏。 - 禁言与限流自愈:同一个群连续发送失败会被判定为「现在发不出去」,停止投递并退避探测;被禁言按解禁时间探,被移出群就停。网关返回「发送频率超限」时,会在本地锁一个发送窗口,到点自动补发,而不是让模型反复撞墙。
- 事件边界:群友不能改设置、不能改人格;管理命令由桥接直接执行,不经过模型。
记忆 v3 大概是怎么运转的
大肥鱼能记住人,不是靠把聊天记录一股脑塞进上下文,而是靠一套本地长期记忆框架(src/memory-v3/)。基本结构不复杂:
- 唯一真源是 SQLite:
state/memory-v3/memory.db,不依赖外部数据库、向量库或网络服务。 - 原始经历只追加:每条群消息进
events,用dedupe_key去重;桥接没有给模型开放删除原始经历的入口。 - 整理成记忆节点:
memories存整理后的事实和印象,带valid_from/valid_to时间有效性;每次变更进memory_versions,出处进memory_evidence。 - 整理要确认,不能假装读过:每轮只交付一批原始经历(默认 40 条 / 6000 字),模型读完要调
qq_confirm_experiences确认;交付水位和确认水位分开记,没确认的下一轮重发。 - 没有唤醒也能整理:后台 worker 每 5 分钟跑一批,每小时最多 12 批。静默只停回复,不停归档。
- 检索在本地完成:SQLite FTS5 加字符 n-gram 余弦,本地 embedding 只作旁路候选,不联网。
所以「每日重置」清的是会话上下文,不是这套长期记忆。大肥鱼第二天还记得你,靠的是 memory.db,不是模型上下文没清干净。细节在 docs/guides/MEMORY-V3.md。
七、日常运维
一套能长期跑的系统,重点不在「第一次启动成功」,而在「第二天还能自己起来」。
- 看门狗:
watch-services.cmd每 60 秒检查 5099 / 3080 / 3100,缺哪个就重跑start-all.cmd。它默认不会自己启动,要单独开一个窗口常驻。它只能保进程活着,保不了 QQ 登录状态——QQ 掉线还是得你手动登。 - 每日重置:计划任务每天 04:00 清空会话上下文,归档旧 DSH 会话。机器夜里关机的话,启动器里的
--if-due会在当天第一次启动时补一次。长期记忆不受影响。 - 日志:
state\bridge.log:模式、唤醒、连接、报错;state\qq-activity.log:收发明细;state\tool-calls.jsonl/token-usage.jsonl:工具调用与 token 账;SnowLuma\logs\:QQ 挂载与 OneBot 侧日志。
- 控制台:
http://127.0.0.1:3100,可以切模式、换人格、看活动、看 token 花费。访问要带state\console-token。 - 可视化运维(可选):WhaleDeck 这类本地控制台可以看服务健康、日志、记忆整理,并做重启;不是必需品,但比盯四个黑窗口舒服。
改完东西要不要重启,记住这张表:
| 改了什么 | 要做什么 |
|---|---|
roles/*.md 人格卡 | 不用重启,下一条消息生效 |
config.json 大多数开关 | 重启桥接 |
| 桥接代码 | 重启桥接 |
preset 的 persona.prefix | 重新生成 patch + 重启 DSH |
| SnowLuma 的 OneBot 配置 | 重启 SnowLuma |
控制台的重启接口有个小坑:请求体必须是 application/json,哪怕没有参数也要发 {},否则返回 415。
八、真正会卡住你的那些坑
下面这些不是理论风险,是这套系统真实踩过的。
1. 白名单空着,大肥鱼不理人。
不是坏了,是 fail-closed。先看 config.json 的 allow,再谈别的。
2. 群消息毫无反应,但日志说「SnowLuma 已连接」。
八成是 DSH 端没装好:setup-dsh.mjs 没跑,或者跑完没重启 DSH。桥接拒绝用默认 preset 建群聊会话,因为默认 preset 通常带 bash 和文件读写,等于把本机交给群里任何人。
3. 启动后先报一次 1006 断开,再报已连接。
正常现象。SnowLuma 同一账号的 HTTP token(3000)和 WS token(3001)是两个不同的值,而配置里只能先填一个。桥接首次连接被拒后会自动重新发现 WS token 并重连。真正要管的是「自愈也失败」——那通常意味着 snowluma.homeDir 没指向真正的 SnowLuma 安装目录。
4. 发送消息报 HTTP 426。
httpUrl 填成 WebSocket 端口了。3000 是 HTTP,3001 是 WS,别混。
5. token 突然暴涨。
DSH 启动没带 disable-rea.yml。回去看第 2 步。
6. 升级 DSH 后大肥鱼完全没反应。
DSH 0.2.1 删掉了 {{cwd}} 提示词变量,而旧版 preset 里用了它,结果每个回合在系统提示词装配阶段就 error,模型一次都没跑。这类升级不是「应该兼容」,而是要逐项验证。升级前先跑 npm run verify:adaptation。
7. SnowLuma 的 Hook 挂不上 QQ。
现象是 QQ 进程和 WebSocket 都活着,但收不到任何消息。去看 SnowLuma\logs\ 里的 [Hook]:正常应该有 login detected 和 pipe connected。另一种常见误判是「升级 SnowLuma 就能修」——实测注入用的原生组件在不同 JS 版本之间可能逐字节相同,升级只换 JS 层,修不了原生 hook。先看日志,再决定动不动。
8. 群里 @ 它没反应,日志刷 result=120。
账号在该群被禁言,或者已经被移出。桥接会自己停、自己探、自己恢复,不用手动改状态。
9. 自己写启动脚本时,cmd 的两个坑。
一是换行必须是 CRLF,LF 会让 cmd 读错行偏移,报一堆莫名其妙的「不是内部或外部命令」;二是中文尽量只放在文件最前面的 title 里,cmd 按字节偏移重读脚本,中文字符压在缓冲区边界时会把一行截断。
10. 不要去找 DSH 的 launch token。
DSH 0.1.7 起,进程启动 token 只存在内存里,日志里那条是上一个进程的陈旧值。桥接默认用 ~/.dsh/.credentials.yaml 里的持久密钥离线铸造 Cookie,dsh.authToken 留空就是最好的配置。
九、成本、边界,以及一句实话
这套东西跑起来之后,最大的持续成本不是服务器,是 token。
- DSH 自带的 token 看板能下钻到每个群、每个好友、每一轮,金额按高峰 / 空闲时段折算,高峰单价是空闲的两倍。先看几天数据,再决定唤醒频率。
disable-rea.yml必须带;人格卡注水会直接变成每轮固定开销;分条和回复频率也都会影响账单。大肥鱼这张 1300 字的卡,比一张 7000 字的卡便宜得多,这也是「短卡」的另一个好处。- 群聊里的 agent 只挂三个 MCP 命名空间,没有本地文件、命令执行、子代理;发送目标强制走白名单;回复里出现本机路径或凭据特征会被整条拦截。这些不是可选项,是把一个会说话的模型放进群里的最低门槛。
- 官方 QQ 机器人接口只收 @ 和指令,拿不到普通群消息,也做不了「潜水围观」。想要现在这种效果,只能走非官方注入,账号风险、平台规则、EULA 都要自己看清楚。
最后一句实话:部署成功只是让大肥鱼活着。真正花时间的是后面——让它每天自己起来,说话像它,同时不把群聊变成事故现场。
白名单、fail-closed、工具边界、看门狗、日志、可回滚的配置,这些都不性感。但它们决定了你是「有个会说话的机器人」,还是「群里真的住着一条大肥鱼」。