起步 → 进阶 · 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 完全无人看管
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 上会毫无反应。
# 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 最少改这几处:
{
"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,在控制台「人格与角色」里选它)。
写人格卡的三条经验#
- 长度跟群,不跟模型。群里一条 12 字以内,跟着对方长短走 (不超过对方两倍),一条只放一件事;正事「只给内容」——不铺垫、不补得意、 不写「铺垫 + 正文 + 总结」三段。
- 别只写「要温柔」。禁令写多了它就是客服——每句都在检查自己有没有犯错。 要写它想要什么、不要什么:「爱车,聊到轮毂会凑上去问几句」 「被叫醒会哼『再五分钟』」「同一个梗连逗三次会冷一句『行了,过了』」。有偏好才像活人。
-
「什么时候不说话」比「怎么说话」重要。给它一个三问的闸门:
- 这话是在跟我说吗?(名字出现在句子里 ≠ 在说它)
- 我有没有真东西可说?(没有就别硬接)
- 现在说是帮忙,还是添乱?(别人在诉苦、在吵架、在说正事,闭嘴)
必须写死的红线
群与群之间要关门——A 群见过的人和事,出了那个群一个字都不许提。 这条不能靠它自觉,要写进卡里。
可以直接抄的骨架#
# 你是谁
你是群里的一个普通群友,不是助理、不是客服。自称「本鱼」,私聊里用「我」。
# 怎么说
- 群里一条 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:
python api_v2.py -a 127.0.0.1 -p 9880 -c GPT_SoVITS/configs/tts_infer.yaml
看到 Application startup complete 就成了。
注意
别把 9880 暴露到公网。它只应该给本机的桥接调用。
先手动测一下(这一步别跳过)#
桥接调用的就是这个 GET 接口,参数名必须对得上:
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(下面是现在这台机器在用的值,
可以直接抄结构):
"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
}
| 字段 | 作用 | 建议 |
|---|---|---|
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 装好只是「能发」,发不发、发多长,得在人格卡里管。 加在语音那一节(一般是 §六):
- 想让人听到语气时用 `qq_send_voice`(40 字内,现场合成):
嘴硬被戳穿、撒娇、被叫醒、晚安。一天最多 2~3 次。
- **少于十个字不发语音**——「嗯」「在呢」「晚安」用字发更有味道。
- 语音不是表演。同一句话打成字已经很有味道,就别再发一遍语音。
三条经验:
- 40 字是听觉舒适区。超过就要拆,或者干脆打字—— 语音长了她自己也不好控节奏。
- 限次数,别限场景。写成「一天 2~3 次」她反而会挑最重要的时刻发; 写成「只在语音需求时发」,她会每次都觉得这次需要。
- 不要让她复述。常见毛病:先打字说一遍,再发语音说一遍。卡里明确禁止。
5.4 第四步:唱歌——这是切片的活,不是合成的活#
核心认知
她不「唱」,她「放」。歌是提前切好的音频文件,她只负责按顺序发。
切歌(ffmpeg 一把梭)#
一首歌切成 30~60 秒的段,命名成 歌名-A.mp3、歌名-B.mp3……
放进 audio/sing/:
# 从 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兜底
版权
自己听着玩的问题不大,别拿去商用、别公开分发。
配置里两道开关#
"sing": {
"enabled": true,
"deniedKeys": ["group:某个不想让它唱歌的群"],
"fullPlay": ["青藏高原"]
}
deniedKeys:禁唱名单(在不在名单里,决定「唱歌类语音」发不发得出去)-
fullPlay:被点这首歌时,必须 A/B/C/D 一口气连发, 不许发两段就停。只放你确定能连着发的歌。
最容易漏的一步:人格卡里的「可唱群白名单」#
白名单不在 config 里
唱歌的群白名单不在 config 里,在人格卡里。 新群不加进去,她会老实说「不会」(哪怕文件都在)。
- 可唱:车主群、法痛组、XX 群(被点先嘴一句「真要听?行吧」)。一天最多 2 次点歌。
- **说「不会」前先看【你会唱的歌】。**
给新群开唱歌 = 改卡 + 确认群在 allow.groups 里,两件事都要做。
她自己怎么发#
她调 qq_send_voice,voice 参数传相对路径:
{ "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 语音上线测试:五步,别省#
-
TTS 直连:
curl那个/tts?...能出 wav(§5.1) -
dryRun 验证:控制台调
POST /api/socialV2/send-voice, body 用{ "key": "private:你的QQ", "text": "测试一下", "dryRun": true }——会走完解析和限流,但不真发 -
真发一条:
dryRun: false给私聊发,能收到、能播、日志有[voice] 已发送语音 -
唱歌 dryRun:
{ "key": "group:你的群", "voice": "sing/歌名-A", "dryRun": true }→ 返回 ok 和体积;再拿一个不在白名单的群试,应该 403「不在白名单内」 -
群里点一首:@ 她点歌,看日志有没有
[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. 别让它刷屏,也别烧穿钱包#
三道闸,缺一道就会出事:
- 唤醒分级:@、点名、关键词、问句 → 必醒;普通闲聊 → 按概率(0.1~0.2); 「自己开个头」→ 单独的调度器(20~60 分钟看一次,群安静 15 分钟才考虑,概率 0.3~0.45)。
- 群内配速:按「最近 30 分钟里它占了几成话」算——超过目标占比就概率归零。 光调高唤醒概率没用,配速会把它压回去。
-
每天预算:
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. 上线检查清单#
勾选状态保存在本机浏览器,刷新不会丢。分两段:先跑通基础,再上语音。
基础:能回话#
语音与唱歌#
顺序别反:先把「能回话」跑通,再上人格和记忆,最后才装嗓子。 语音发不出来时,先确认文字回复是好的——大部分「语音坏了」其实是桥接压根没在跑。