nest
自己写的多平台 AI bot 应用:一个后台进程管三个性格不同的 QQ 群聊 bot。项目答疑按评测结果设计——本地重排判断「资料里有没有」,大模型挑读哪几节,真实问题答对率从 79% 升到 98%。
一个进程,三个性格不同的群聊 bot
nest 是我自己写的多平台 AI bot 应用,目前接 QQ。一个后台进程同时管三个 bot,在一台 Mac mini 上 24 小时运行:每个 bot 是一个 QQ 号加一个角色形象,有自己的名字、人设和模型——大肥鲸用 DeepSeek,彩虹小马用 Grok,小龙女用 ChatGPT。它们会判断什么时候该接话、认真回答正经问题、发群友收藏的表情包、学群里的黑话,还能盯着订阅和网页,有变化就按人设改写后推送。配一个网页控制台,能看到每条消息「回了什么、为什么没回」。
10 个页面:实时消息与判断理由、人设和模型、用量、表情包、黑话、监控、项目资料、QQ 容器与存储;远程访问走 Tailscale + 密码。
让 bot 只照一个软件的帮助资料答疑,答不上的转给维护资料的人。
OpenAI 兼容 Chat Completions、OpenAI Responses、ChatGPT;连续失败 5 次自动暂停 60 秒。
点名必回、偶尔插话、几个 bot 轮流;先分辨是闲聊、正经问题、问以前的聊天还是问项目,再决定怎么答。
所有账号的事件进同一个队列,SQLite 只有一个写入者;断线指数退避重连,并补收断线期间的消息。
先定原则再写代码:每条都写明它防的是什么
| 原则 | 防的是什么 |
|---|---|
| 一个进程跑所有账号,每个账号一个独立上下文 | 多开一份进程就多付一份内存;几个 bot 轮流接话这类协调,也只有在一个进程里才做得到 |
| 后台服务和界面分开 | 界面只在打开网页时占资源,24 小时常驻的只有后台 |
| 事件驱动,不轮询 | 没人说话时 CPU≈0 |
| 断线自动重连,并补收断线期间的消息 | 掉线那几分钟群里说的话不会丢 |
| 每个「没回」都有记录和原因,控制台可见 | 不用猜 bot 为什么沉默;发送结果不确定时不当成功、也不盲目重发 |
| 一个数据库、单一写入者、版本化迁移、定期清理 | 并发写坏数据、记录表无限增长 |
| 从第一天起有黑盒测试 | 改一处坏一处;测试全部离线,不连真 QQ、不调真模型 |
上线也没停过服务:nest 先以「只看不说」模式连上同样的账号,和原来的 bot 并行跑,在控制台对比「本来会回」的内容,没问题再一个一个切到正式发言。从第一行代码到三个 bot 全部上线用了两天,三个 bot 合计内存约 80MB,空闲时 CPU 接近 0。
像群友一样接话:什么时候说、说几条、说多快
- 群里来了消息 安静 2 秒再处理,最多等 8 秒
- 该不该接话 点名必回;没被点名时,攒够 5 条新消息、隔 120 秒或有人向全群提问才考虑插话
- 轮到谁 bot 之间最多连聊 4 轮就等真人;同一群 30 秒内只放一个 bot 主动插话
- 分辨问题 没有问句直接当闲聊,不花模型调用;其余分成正经问题、问旧聊天、问项目
- 生成回复 可联网查、翻聊天记录、查项目资料;带一张表情包
- 分条发送 最多 3 条、每条不超 80 字,按打字速度停顿
其余能力都守一条原则:模型的产出要经程序核对。表情包只收表情、不收群友的普通照片,按内容去重,由看图模型写描述,回复时从候选里挑一张;黑话由模型从聊天里找词条,程序再核对每个词至少真实出现过 2 次;截图读字时,像密钥的长串先打码;订阅、网页和价格监控第一次检查只记基线、不推送。每个 bot、每个群都可以先「试运行」,只记录本来会发的消息。
只照资料回答,资料里没有就老实说、转给作者
小龙女负责 CicaTavern(我的另一个项目)的玩家交流群。群友常问「某个功能有没有、在哪开」「这个报错怎么办」,她以前只能凭印象答,常常编出不存在的界面路径——错的路径会让人去找不存在的按钮,比不答更糟。于是给她接上 CicaTavern 的帮助资料:405 节,约 15 万 token,整份塞进提示词不现实;检索模型还得和三个 QQ 客户端挤在同一台 16GB 的 Mac mini 上。
- 截图读字 报错截图先由看图模型抄出字
- 三路找候选 关键词 BM25 · 向量 bge-m3 · 报错原文,RRF 融合取 30 节
- bge 重排 最高分低于 0.17 = 资料里没有,转给作者
- 模型挑资料 回答的模型从 30 节里挑最多 6 节;出错退回重排前 6 节
- 照资料作答 界面路径照原文说,没写的直说不确定;一题多问逐件答
- 答不上就转交 私聊维护资料的人;补的回答马上能被检索到
每个设计选择都先量再改
评测集 298 题:43 道真实问题(群里一周内问 CicaTavern 本身的,人工标出能回答它的小节)、22 道无答案题(问资料里根本没有的功能,先确认这些词在资料里一次都没出现),以及 233 道照资料写成群友口吻的合成题,覆盖常说的词、只说需求、描述现象、粘贴报错四种风格。检索看正确小节排第一的比例和「无答案拒掉」;回答由小龙女用线上的模型和提示词作答,再交给另一家的 Claude Opus 5.5 评分,避免模型给自己打分。
| 方案 | 真实题:正确小节排第一 | 无答案题:判断为没有 | 运行 |
|---|---|---|---|
| A 只用关键词(BM25) | 54% | 45% | 本机 |
| B 只用向量(bge-m3) | 70% | 59% | 本机 |
| C 关键词 + 向量融合 | 67% | 59% | 本机 |
| D 融合 → bge 重排,带上下文头 | 58% | 73% | 本机 |
| E 融合 → bge 重排(上线) | 72% | 91% | 本机,0.85 秒 |
| F 大模型看整份目录挑 | 93% | 55% | 每题约 1.6 万 token |
| H 大模型给 E 的候选排序 | 95% | 59% | 每题约 3.7 千 token |
第一轮回答评测里答不好的 9 道真实题,对照每题交给她的资料,5 道的正确小节根本没交到她手上——正好是全部 2 道答错和 3 道部分对。问题不在她不会答,在于「交哪几节」。改进前先拿第一轮记下的排序离线模拟了五种策略,一次模型都没调,就看出按分数设门槛救不回排在第 8、9 名的正确小节,于是改成每道项目问题都让回答的模型挑资料,「有没有」仍由 bge 判断。
技术亮点
一个人八天,从第一行代码做到带评测的项目答疑,工程上主要解决了这几件事。
一个进程,单一写入者
每个 QQ 号一个 OneBot 适配器实例,所有事件进同一个队列、同一个分发任务,SQLite 只有一个写入者;断线指数退避重连并补收消息,发送结果不确定时不盲目重发。多 bot 轮流这类协调,只有在一个进程里才做得到。
检索在本机,不出 Mac
bge-m3 向量和 bge-reranker-v2-m3 重排由 llama.cpp 在 Mac mini 上跑(Q8、Metal,两个约 1.5GB):提问不出本机、不要密钥;几百节资料直接在内存里比,不用向量数据库;模型服务挂了,检索降级继续而不是失败。
离线测试与多轮审查
609 项测试全部离线:假 OneBot、假模型,不连真 QQ、不调真模型。控制台上线前先由三位评审挑设计问题、再从六个角度审代码,每条问题先由独立验证者复现再修,21 条确认的问题全部修复并复核。
24 小时托管与省内存
nest 和两个模型服务都是 launchd 服务,开机自启;QQ 一个号一个 NapCat 容器,每个号的 QQ 从约 800MB 降到约 260MB,每周一凌晨逐个重启;存储按天自动清理并压缩数据库。