跳到正文

起步 → 进阶 · 01

在 QQ 里养一条会说话、会唱歌的「鱼」

用 DeepSeek Harness、qq-bridge 和 SnowLuma,从一台空电脑做到「群里有人 @ 它, 它自己决定要不要接话」;再把嗓子装上:发语音、唱准备好的歌、听懂群友的语音。 所有步骤都在 Windows 上跑通过,踩过的坑和上线检查清单在文末。

适用范围 2026-11 的 DSH、qq-bridge 与 SnowLuma;语音部分对应 qq-bridge 的 socialV2.voice、GPT-SoVITS api_v2(9880)与 SenseVoice(9890)。 实测环境 Windows 11、Node.js 22.13+;桥接更新较快,命令以仓库 README 为准。

本页目录

0. 先说清楚这是个什么东西#

它不是「把 ChatGPT 接进群」。它由三块拼起来:

三件套分工
块 干什么 你从哪拿
SnowLuma QQ 网关:把 QQ 消息变成程序能读的数据,把程序写的东西发回 QQ GitHub SnowLuma/SnowLuma releases,解压即用
qq-bridge 调度中枢:决定这次要不要叫醒 AI、叫醒后给多少上下文、AI 说什么就发什么 GitHub Derpyu520/qq-bridge
DSH agent 运行时:真正「想」和「写」的地方,每轮都能调工具(发消息、看图、发表情、唱歌、记东西) DeepSeek 官方渠道,装好能打开 http://127.0.0.1:3080 即可

关键区别

说话这件事是 AI 自己决定的,不是「收到消息 → 自动回复」。 桥接只负责把它叫醒、给它看群里的上下文、把它的动作发出去。所以它能做到 「这轮不接」「只发个图」「过两分钟再回」「自己开个头」。

它做得到的

  • 读群消息并理解上下文
  • 引用回复,看图片和表情包
  • 发表情包、私聊、发语音、唱切好的歌
  • 记住群里的人和梗,学群里的黑话
  • 听懂语音消息,夜里自己写日记

它做不到的

  • 真的「看」视频
  • 唱没切过的歌
  • 记住无限长的历史
  • 免费
  • 7×24 完全无人看管
鲸鱼娘坐在工作台前,把三个零件组装成一个小机器人:聊天网关、黄铜调度中枢和芯片
她面前的三个零件,对应正文里的三件套:QQ 网关、调度中枢、agent 运行时。

1. 你需要准备什么#

  • 一台常开的 Windows 电脑(10/11 都行,4 核 8G 够用;关机的电脑养不出活鱼)
  • 一个 QQ 小号(别拿主号试,风险自担)+ 手机扫码登录
  • DSH 装好并跑起来(http://127.0.0.1:3080 能打开)
  • 一个 DeepSeek API key(按量付费,充 20 块能玩很久)
  • Node.js ≥ 22.13
  • 会复制粘贴命令、会改 JSON 的水平就够了,不需要会写代码
  • 想让她发声时再准备:GPT-SoVITS、ffmpeg、SenseVoice(§5)

费用大概

一个群、一天几百轮,大约 ¥1~5/天;像我这只四个群全开、一天两千来轮, ¥20~30/天(92%~98% 的输入命中缓存,这是便宜的主因)。 工作日 9-12 点、14-18 点是双倍价,能等就等。

2. 最短路径:30 分钟让它回第一句话#

照着做,六步。第 3 步最容易漏,漏了 QQ 上会毫无反应。

bash
# 1) 确认 DSH 在跑:浏览器打开 http://127.0.0.1:3080 能进就行

# 2) 装桥接
git clone https://github.com/Derpyu520/qq-bridge.git
cd qq-bridge
npm install
copy config.example.json config.json     # PowerShell 用 cp config.example.json config.json

# 3) 装 DSH 端(两个聊天 preset + 三个 MCP 服务),装完必须重启 DSH
node scripts/setup-dsh.mjs
#    preset 和 MCP 只在 DSH 启动时加载。这步不做,桥接会拒绝建会话(安全设计),
#    表现就是「QQ 上有消息,但它一个字都不回」。

# 4) SnowLuma:下载 win-x64 完整版,解压,双击 launcher.bat
#    https://github.com/SnowLuma/SnowLuma/releases/latest
#    WebUI 里扫码登录 QQ → 打开「WebSocket 服务端」和「HTTP API」
#    记下端口(默认 WS 3001 / HTTP 3000)和 accessToken

# 5) 填 config.json:snowluma 段 + 白名单
# 6) 启动
npm start

config.json 最少改这几处:

json
{
  "snowluma": {
    "wsUrl": "ws://127.0.0.1:3001",
    "httpUrl": "http://127.0.0.1:3000",
    "accessToken": "WebUI 里配的 token,没设就留空"
  },
  "allow": {
    "private": [你的QQ号],
    "groups": [你的群号]
  },
  "allowAllWhenEmpty": false
}

你的QQ号 和 你的群号 是占位,要换成数字;accessToken 没设就留空。 看到日志里 SnowLuma 已连接 就成了,去群里 @ 它一句试试。

每一步「卡住」长什么样#

启动阶段的常见现象
现象 八成是
启动报 HTTP 426 httpUrl 填成了 WebSocket 端口(3001),应该是 3000
QQ 有消息、它不说话 ① 第 3 步没做 / 做完没重启 DSH;② 群号不在 allow.groups; ③ 白名单空 + allowAllWhenEmpty:false(这是有意的 fail-closed)
控制台打不开 端口 3100 被占,或没带 token(token 在启动日志里,也存在 state/console-token)
连上了但收不到消息 SnowLuma 版本 / 端点问题,看桥接 README「已知行为」那节

3. 从「能回话」到「像个人」——这才是关键#

能回话的机器人和「群友」的差别,90% 在人格卡 (roles/随便起个名.md,在控制台「人格与角色」里选它)。

鲸鱼娘坐在窗边读一张空白人格卡,周围浮着几个气泡,其中一个是问号
人格卡写的不是「要温柔」,是她在什么情况下开口、什么情况下闭嘴。

写人格卡的三条经验#

  1. 长度跟群,不跟模型。群里一条 12 字以内,跟着对方长短走 (不超过对方两倍),一条只放一件事;正事「只给内容」——不铺垫、不补得意、 不写「铺垫 + 正文 + 总结」三段。
  2. 别只写「要温柔」。禁令写多了它就是客服——每句都在检查自己有没有犯错。 要写它想要什么、不要什么:「爱车,聊到轮毂会凑上去问几句」 「被叫醒会哼『再五分钟』」「同一个梗连逗三次会冷一句『行了,过了』」。有偏好才像活人。
  3. 「什么时候不说话」比「怎么说话」重要。给它一个三问的闸门:
    • 这话是在跟我说吗?(名字出现在句子里 ≠ 在说它)
    • 我有没有真东西可说?(没有就别硬接)
    • 现在说是帮忙,还是添乱?(别人在诉苦、在吵架、在说正事,闭嘴)

必须写死的红线

群与群之间要关门——A 群见过的人和事,出了那个群一个字都不许提。 这条不能靠它自觉,要写进卡里。

可以直接抄的骨架#

markdown
# 你是谁
你是群里的一个普通群友,不是助理、不是客服。自称「本鱼」,私聊里用「我」。

# 怎么说
- 群里一条 12 字以内。跟着对方长短走,不超过对方两倍。一条只放一件事。
- 正事只给内容:不先铺垫、不后补得意、答完就收。
- 十句里至少三句是废句(「啊?」「真的假的」「行吧」)。
- 别每句都完美,像打字,不像写文案。

# 什么时候说话
三问都过才开口:①在跟我说吗 ②我有真东西说吗 ③现在说是帮忙还是添乱。

# 什么时候闭嘴
- 别人在诉苦:接一句具体的就收,不当第二个来安慰的人。
- 别人在吵架:不站队,不总结,不劝。
- 有人说「少说两句」:后面半小时当哑巴,被 @ 也只回一句。

# 硬红线
- 群与群之间关门,一个字都不许跨群。
- 不威胁、不骂家人外貌、不装官方、不承诺做不到的事。

# 你有「此刻」
被夸会高兴,被凶会闷。但只从语气和话的长短里漏出来,不许报心情,也不许拿情绪解释自己。

把卡放进 roles/,在控制台选中它,保存即生效,不用重启。

4. 记忆:让它「记得住人」,但别一次全开#

记忆分别存在哪里
记什么 存在哪 注意
人 state/people.json,每人一句印象(50 字以内) 别写小作文,写「话痨、爱发猫图、怕吵」这种能用的
梗 / 话题 state/topics.json 每个群分开
黑话 state/slang.json 全群共享——群内专名、脏话别让它进库
教训 state/learnings.json 被复读 / 被笑 / 被批评 → 每晚蒸馏,按「别再犯 / 有效 / 被当面点过」分节
此刻 state/mood-live.json 会衰减的情绪(半小时到一小时),一句原因就行

两个编码坑,能省你半天

  • 中文 JSON 一律存成「无 BOM」的 UTF-8——带 BOM 的会被程序静默跳过,整段记忆凭空消失。
  • PowerShell 脚本(.ps1)必须带 BOM——不带的话 PowerShell 5 按 GBK 读,满屏乱码报错。

5. 声音和唱歌:先分清三条链路#

到这里,她已经能在群里打字回话了。下面把嗓子装上。 很多人以为「唱歌 = 语音的一种」,其实不是——它们走的是完全不同的路径:

说话、唱歌、听语音的区别
能力 怎么来的 走哪条路 特点
说话(发语音) 现场合成 文字 → GPT-SoVITS → wav → QQ 说什么都行,慢 1~3 秒,字数有限
唱歌 提前切好的文件 直接读 audio/sing/*.mp3 → QQ 要多少有多少,不用合成,但只能是切过的歌
听语音 语音转文字 群友的语音 → SenseVoice → 文本 → 她读 没装这个,等于她「听不见」

所以三件事要分别装:TTS(合成)→ 切片(唱歌)→ ASR(听)。 下面按这个顺序来。

5.1 第一步:装一个本地 TTS(GPT-SoVITS)#

为什么是它:本地跑、免费、能克隆音色,只要 3~10 秒参考音频就能出声。 桥接不挑服务,只要有个 HTTP 接口返回 wav 就行,用别的 TTS 也能接 (改 §5.2 的 baseUrl 和参数名即可)。

准备音色素材#

  • 一段 3~10 秒、干净、没有背景音的说话人声(wav / mp3 都行)
  • 把这段逐字打出来(语气词也打:「嗯」「啊」都算)
  • 声音最好「有情绪」——平淡的参考音频,合成出来也会平

把 API 服务起起来#

GPT-SoVITS 自带 API 服务(api_v2.py),起在 9880:

bash
python api_v2.py -a 127.0.0.1 -p 9880 -c GPT_SoVITS/configs/tts_infer.yaml

看到 Application startup complete 就成了。

注意

别把 9880 暴露到公网。它只应该给本机的桥接调用。

先手动测一下(这一步别跳过)#

桥接调用的就是这个 GET 接口,参数名必须对得上:

bash
curl "http://127.0.0.1:9880/tts?text=你好呀&text_lang=zh&ref_audio_path=参考音频.wav&prompt_text=参考音频的逐字稿&prompt_lang=zh" -o test.wav
  • 出 test.wav、能用播放器放、至少几 KB → 通了
  • 报 not found the model / 500 → 模型没加载好,看 GPT-SoVITS 那边的日志
  • 出 0 字节 → ref_audio_path 路径不对。 这个路径是相对 GPT-SoVITS 目录的,不是相对桥接

5.2 第二步:让桥接接上这套 TTS#

改 config.json 的 socialV2.voice(下面是现在这台机器在用的值, 可以直接抄结构):

json
"voice": {
  "enabled": true,
  "dir": "audio",
  "maxSeconds": 300,
  "maxBytes": 20971520,
  "maxPerMinute": 2,
  "maxPerHour": 10,
  "ffmpegPath": "D:\\你的\\ffmpeg.exe",
  "autoReadback": true,
  "transcribeIncoming": true,
  "localAsr": { "enabled": true, "model": "sensevoice", "baseUrl": "http://127.0.0.1:9890", "timeoutMs": 20000 },
  "tts": {
    "enabled": true,
    "baseUrl": "http://127.0.0.1:9880",
    "refAudio": "参考音频.wav",
    "promptText": "参考音频的逐字稿",
    "promptLang": "zh",
    "textLang": "zh",
    "maxChars": 120,
    "timeoutMs": 120000
  },
  "sing": {
    "enabled": true,
    "deniedKeys": [],
    "fullPlay": ["青藏高原"]
  },
  "textToVoiceProbability": 0.12,
  "replyVoiceProbability": 0
}
voice 字段速查
字段 作用 建议
tts.baseUrl TTS 服务地址 http://127.0.0.1:9880
tts.refAudio / promptText 音色参考 + 逐字稿 路径相对 GPT-SoVITS 目录
tts.maxChars 一次最多合成多少字 120 是硬上限,人格卡里还要再压到 40 字
maxPerMinute / maxPerHour 语音限流 2 / 10,防止刷屏
textToVoiceProbability 「这句本来要打字,改成发语音」的概率 0.12,别太高
replyVoiceProbability 整条回复都改成语音 留 0,很容易变成语音轰炸
sing.deniedKeys 哪些群不许唱歌 空 = 都允许
sing.fullPlay 被点就必须整首连发的歌 只放「A/B/C/D 连着发」的歌
ffmpegPath 切音频 / 调音量用 没有 ffmpeg 会退化(见 §8.2)

改完必须重启桥接

voice 段不在热更新白名单里(热更新的只有 tools / wake / send / wait / proactive / sticker / feedback / context), 进程不重启就一直读旧配置。

重启后去 logs\bridge-out.log 里找 语音合成:… 或 [voice] 开头的行,没有就说明还没被触发过。

5.3 第三步:教她「什么时候该发语音」#

TTS 装好只是「能发」,发不发、发多长,得在人格卡里管。 加在语音那一节(一般是 §六):

markdown
- 想让人听到语气时用 `qq_send_voice`(40 字内,现场合成):
  嘴硬被戳穿、撒娇、被叫醒、晚安。一天最多 2~3 次。
- **少于十个字不发语音**——「嗯」「在呢」「晚安」用字发更有味道。
- 语音不是表演。同一句话打成字已经很有味道,就别再发一遍语音。

三条经验:

  1. 40 字是听觉舒适区。超过就要拆,或者干脆打字—— 语音长了她自己也不好控节奏。
  2. 限次数,别限场景。写成「一天 2~3 次」她反而会挑最重要的时刻发; 写成「只在语音需求时发」,她会每次都觉得这次需要。
  3. 不要让她复述。常见毛病:先打字说一遍,再发语音说一遍。卡里明确禁止。

5.4 第四步:唱歌——这是切片的活,不是合成的活#

核心认知

她不「唱」,她「放」。歌是提前切好的音频文件,她只负责按顺序发。

鲸鱼娘站在复古话筒前闭眼唱歌,海浪潮线、磁带和小音箱从话筒边飘出
唱的部分是「放」:曲子提前切片,她只负责按顺序发出去。

切歌(ffmpeg 一把梭)#

一首歌切成 30~60 秒的段,命名成 歌名-A.mp3、歌名-B.mp3…… 放进 audio/sing/:

bash
# 从 0 秒开始切 45 秒
ffmpeg -i 原曲.mp3 -ss 0 -t 45 -c:a libmp3lame -q:a 4 "audio\sing\远航星的告别-A.mp3"
# 从 45 秒开始切下一段
ffmpeg -i 原曲.mp3 -ss 45 -t 45 -c:a libmp3lame -q:a 4 "audio\sing\远航星的告别-B.mp3"
  • 每段 ≤60 秒:QQ 语音消息太长会被压缩得很难听
  • 命名固定:歌名-A / -B / -C, 她发的时候传 sing/歌名-A,漏掉 sing/ 前缀就找不到文件 (这是最高频的报错)
  • 响度统一:不同歌音量差很多,用 -af loudnorm=I=-16:TP=-1.5 过一遍, 或者靠 config 的 defaultVolume / loudness 兜底

版权

自己听着玩的问题不大,别拿去商用、别公开分发。

配置里两道开关#

json
"sing": {
  "enabled": true,
  "deniedKeys": ["group:某个不想让它唱歌的群"],
  "fullPlay": ["青藏高原"]
}
  • deniedKeys:禁唱名单(在不在名单里,决定「唱歌类语音」发不发得出去)
  • fullPlay:被点这首歌时,必须 A/B/C/D 一口气连发, 不许发两段就停。只放你确定能连着发的歌。

最容易漏的一步:人格卡里的「可唱群白名单」#

白名单不在 config 里

唱歌的群白名单不在 config 里,在人格卡里。 新群不加进去,她会老实说「不会」(哪怕文件都在)。

markdown
- 可唱:车主群、法痛组、XX 群(被点先嘴一句「真要听?行吧」)。一天最多 2 次点歌。
- **说「不会」前先看【你会唱的歌】。**

给新群开唱歌 = 改卡 + 确认群在 allow.groups 里,两件事都要做。

她自己怎么发#

她调 qq_send_voice,voice 参数传相对路径:

json
{ "voice": "sing/远航星的告别-A", "key": "group:123456" }

提示词里会给她一份【你会唱的歌】清单,她照着挑; 不在清单里的歌名她不该说会。

5.5 第五步:让她能「听」语音(ASR)#

没装 ASR,她收到的语音消息只有「[语音] 12秒」,看不见内容—— 群里有人用语音说话,她就接不上。

  • 本机起一个 SenseVoice / FunASR 的 HTTP 服务,默认 9890
  • config.json:socialV2.voice.transcribeIncoming: true、 localAsr.baseUrl: "http://127.0.0.1:9890"、model: "sensevoice"
  • 自测:在群里发一条 5 秒语音,然后看 logs\bridge-out.log 有没有转写出来的文字; 她如果能接上那句话,就是通了

5.6 语音上线测试:五步,别省#

  1. TTS 直连:curl 那个 /tts?... 能出 wav(§5.1)
  2. dryRun 验证:控制台调 POST /api/socialV2/send-voice, body 用 { "key": "private:你的QQ", "text": "测试一下", "dryRun": true } ——会走完解析和限流,但不真发
  3. 真发一条:dryRun: false 给私聊发,能收到、能播、日志有 [voice] 已发送语音
  4. 唱歌 dryRun: { "key": "group:你的群", "voice": "sing/歌名-A", "dryRun": true } → 返回 ok 和体积;再拿一个不在白名单的群试,应该 403「不在白名单内」
  5. 群里点一首:@ 她点歌,看日志有没有 [voice] … (sing/…), 以及她说「不会」还是真发

我们这台机器的实际参数(可以对照)#

本机实际参数
项 值
TTS GPT-SoVITS http://127.0.0.1:9880,maxChars 120
ASR SenseVoice http://127.0.0.1:9890,transcribeIncoming: true
语音库 audio/,合成的临时文件落 audio/_tts/
唱歌素材 audio/sing/,81 个 mp3
限流 2 条/分、10 条/时;单条 ≤300 秒 / ≤20MB
概率 textToVoiceProbability 0.12,replyVoiceProbability 0
整首连发 《青藏高原》(563 人大群例外:先 A/B,等「继续」再 C/D)
歌单注入 每轮提示词里带【你会唱的歌】+【整首连发】提示

6. 别让它刷屏,也别烧穿钱包#

三道闸,缺一道就会出事:

  1. 唤醒分级:@、点名、关键词、问句 → 必醒;普通闲聊 → 按概率(0.1~0.2); 「自己开个头」→ 单独的调度器(20~60 分钟看一次,群安静 15 分钟才考虑,概率 0.3~0.45)。
  2. 群内配速:按「最近 30 分钟里它占了几成话」算——超过目标占比就概率归零。 光调高唤醒概率没用,配速会把它压回去。
  3. 每天预算:dailyBudgetCny 设个上限(比如 15 块), 摸到上限只放「有人叫它」的唤醒。

想让它更活泼

调的是主动搭话的调度器和配速目标,不是把概率拉到 1—— 那只会变成刷屏。语音和唱歌同样有限流,别靠「多发几条」解决冷场。

7. 安全红线(要真上必须看)#

这不是建议

下面每一条都要在开机前逐条确认。尤其是群聊 preset 和白名单, 配置错了就是把电脑和账号一起交出去。

  • 群聊会话绝对不能用带 bash / 文件读写 / 网络工具的 preset。 等于把电脑交给群友。桥接宁可「拒绝建会话」也不回退到默认 preset,这是有意的。
  • 白名单是 fail-closed:allowAllWhenEmpty:false + 空表 = 谁都不许。 加群要显式加号。
  • 控制台 token、SnowLuma accessToken 不进群、不进截图。截图前遮掉。
  • SnowLuma 的 EULA 禁止「用自动化脚本部署它」, 所以桥接只探测、不启动不停止——安装和扫码永远你自己来。
  • 群隔离写进人格卡,别指望它自觉。
  • 别拿它骂人、发广告、刷屏——账号会没,而且找不回来。
  • 语音服务(9880 / 9890)只监听 127.0.0.1,不要暴露到公网; 切歌素材自己听着玩,别商用、别公开分发。

8. 我们踩过的坑(照抄能省半天)#

配置与行为#

真实踩过的坑与原因
坑 真相
改了 config.json 没反应 配置是启动时读的,改完要重启桥接(人格卡除外,热生效)
抬高了唤醒概率还是不吭声 还有「配速」在按话量占比压它,概率只是天花板
新收的表情包从来不用 提示词里只塞「使用次数最高的前 N 个」,新图次数是 0,永远排不上—— 要么提上限,要么单独给「刚收的」一组
群里 @ 它、它说自己「不会唱」 唱歌的可唱群白名单写在人格卡里的,新群要加进去
它偶尔说「我看了下你的图」但其实没看 图片要它有工具去读;没读到就该说「打不开」,这条也要写进卡
夜里 3 点它突然说话 睡眠窗口(比如 02:00-08:00)只让 @、主人私聊叫醒它
它一次回一大段 卡里没写长度上限,或者「正事给全」和「群里 12 字」两条打架

语音与唱歌的报错#

排障表:按错误原文找
现象 原因 / 处理
报 TTS 服务返回 500 GPT-SoVITS 模型没加载完,或 ref_audio_path 相对路径不对
报 合成音频过小,疑似失败 TTS 返回了错误页而不是音频;先按 §5.1 用 curl 试
报 文本太长(N > 120) 超过 tts.maxChars,拆短或调大
发语音 403「这个群不允许唱歌」 该群在 sing.deniedKeys 里
发语音 403「目标 … 不在白名单内」 群号不在 allow.groups(这是会话白名单,不是唱歌白名单,两个不同的闸)
她说「不会唱」但文件都在 卡 §六 的可唱歌单没加这个群(见 §5.4)
报「找不到文件」/ 发不出去 路径少了 sing/ 前缀,或者文件不在 audio/sing/ 下
改完 voice 配置没反应 没重启桥接(voice 段不热更新)
spawn ffprobe ENOENT 没装 ffmpeg / ffprobe:不探测时长,不做时长校验直接发,不算致命; 想修就把 ffmpegPath 指对
日志有合成、群里没消息 QQ 侧被限流或网关没连上,回头看 bridge-err.log 和 SnowLuma 状态

9. 上线检查清单#

勾选状态保存在本机浏览器,刷新不会丢。分两段:先跑通基础,再上语音。

鲸鱼娘站在黄铜控制台旁,指示灯大多为绿色、一盏为琥珀色,她一手拿检查板一手比赞
上线前逐条过一遍:灯全绿再开群,琥珀色那盏留给还没验证的项。

基础:能回话#

语音与唱歌#


顺序别反:先把「能回话」跑通,再上人格和记忆,最后才装嗓子。 语音发不出来时,先确认文字回复是好的——大部分「语音坏了」其实是桥接压根没在跑。