← 返回作品

nest

自己写的多平台 AI bot 应用:一个后台进程管三个性格不同的 QQ 群聊 bot。项目答疑按评测结果设计——本地重排判断「资料里有没有」,大模型挑读哪几节,真实问题答对率从 79% 升到 98%。

角色 · 独立开发(AI 协作) 2026 AI 应用 / 后台服务 + 控制台 技术栈 · Python / FastAPI / SQLite / React / llama.cpp / LLM
nest
PRODUCT · 这是什么

一个进程,三个性格不同的群聊 bot

nest 是我自己写的多平台 AI bot 应用,目前接 QQ。一个后台进程同时管三个 bot,在一台 Mac mini 上 24 小时运行:每个 bot 是一个 QQ 号加一个角色形象,有自己的名字、人设和模型——大肥鲸用 DeepSeek,彩虹小马用 Grok,小龙女用 ChatGPT。它们会判断什么时候该接话、认真回答正经问题、发群友收藏的表情包、学群里的黑话,还能盯着订阅和网页,有变化就按人设改写后推送。配一个网页控制台,能看到每条消息「回了什么、为什么没回」。

L5
控制台
React · SSE 实时推送

10 个页面:实时消息与判断理由、人设和模型、用量、表情包、黑话、监控、项目资料、QQ 容器与存储;远程访问走 Tailscale + 密码。

L4
项目资料检索
BM25 + bge-m3 + bge 重排 · 本机 llama.cpp

让 bot 只照一个软件的帮助资料答疑,答不上的转给维护资料的人。

L3
模型层
3 种接口

OpenAI 兼容 Chat Completions、OpenAI Responses、ChatGPT;连续失败 5 次自动暂停 60 秒。

L2
brain:什么时候说话
仲裁 · 限额 · 分类

点名必回、偶尔插话、几个 bot 轮流;先分辨是闲聊、正经问题、问以前的聊天还是问项目,再决定怎么答。

L1
接入层
OneBot 11 · 一个号一个 NapCat 容器

所有账号的事件进同一个队列,SQLite 只有一个写入者;断线指数退避重连,并补收断线期间的消息。

自底向上:多个 QQ 号的事件汇进一个进程,brain 决定谁说、说什么,控制台看得见每一步。
DESIGN · 设计原则

先定原则再写代码:每条都写明它防的是什么

设计原则
原则防的是什么
一个进程跑所有账号,每个账号一个独立上下文多开一份进程就多付一份内存;几个 bot 轮流接话这类协调,也只有在一个进程里才做得到
后台服务和界面分开界面只在打开网页时占资源,24 小时常驻的只有后台
事件驱动,不轮询没人说话时 CPU≈0
断线自动重连,并补收断线期间的消息掉线那几分钟群里说的话不会丢
每个「没回」都有记录和原因,控制台可见不用猜 bot 为什么沉默;发送结果不确定时不当成功、也不盲目重发
一个数据库、单一写入者、版本化迁移、定期清理并发写坏数据、记录表无限增长
从第一天起有黑盒测试改一处坏一处;测试全部离线,不连真 QQ、不调真模型

上线也没停过服务:nest 先以「只看不说」模式连上同样的账号,和原来的 bot 并行跑,在控制台对比「本来会回」的内容,没问题再一个一个切到正式发言。从第一行代码到三个 bot 全部上线用了两天,三个 bot 合计内存约 80MB,空闲时 CPU 接近 0。

CHAT · 群聊

像群友一样接话:什么时候说、说几条、说多快

  1. 群里来了消息 安静 2 秒再处理,最多等 8 秒
  2. 该不该接话 点名必回;没被点名时,攒够 5 条新消息、隔 120 秒或有人向全群提问才考虑插话
  3. 轮到谁 bot 之间最多连聊 4 轮就等真人;同一群 30 秒内只放一个 bot 主动插话
  4. 分辨问题 没有问句直接当闲聊,不花模型调用;其余分成正经问题、问旧聊天、问项目
  5. 生成回复 可联网查、翻聊天记录、查项目资料;带一张表情包
  6. 分条发送 最多 3 条、每条不超 80 字,按打字速度停顿
每一步的判断都写进控制台:哪条消息没回、为什么没回,一眼能查到。限额兜底:每个 bot 每小时 30 句,全局每分钟 10 句。

其余能力都守一条原则:模型的产出要经程序核对。表情包只收表情、不收群友的普通照片,按内容去重,由看图模型写描述,回复时从候选里挑一张;黑话由模型从聊天里找词条,程序再核对每个词至少真实出现过 2 次;截图读字时,像密钥的长串先打码;订阅、网页和价格监控第一次检查只记基线、不推送。每个 bot、每个群都可以先「试运行」,只记录本来会发的消息。

KNOWLEDGE · 项目答疑

只照资料回答,资料里没有就老实说、转给作者

小龙女负责 CicaTavern(我的另一个项目)的玩家交流群。群友常问「某个功能有没有、在哪开」「这个报错怎么办」,她以前只能凭印象答,常常编出不存在的界面路径——错的路径会让人去找不存在的按钮,比不答更糟。于是给她接上 CicaTavern 的帮助资料:405 节,约 15 万 token,整份塞进提示词不现实;检索模型还得和三个 QQ 客户端挤在同一台 16GB 的 Mac mini 上。

  1. 截图读字 报错截图先由看图模型抄出字
  2. 三路找候选 关键词 BM25 · 向量 bge-m3 · 报错原文,RRF 融合取 30 节
  3. bge 重排 最高分低于 0.17 = 资料里没有,转给作者
  4. 模型挑资料 回答的模型从 30 节里挑最多 6 节;出错退回重排前 6 节
  5. 照资料作答 界面路径照原文说,没写的直说不确定;一题多问逐件答
  6. 答不上就转交 私聊维护资料的人;补的回答马上能被检索到
两个模型各管一件:本地重排模型决定「资料里有没有」,回答的大模型决定「读哪几节」。
EVAL · 评测

每个设计选择都先量再改

评测集 298 题:43 道真实问题(群里一周内问 CicaTavern 本身的,人工标出能回答它的小节)、22 道无答案题(问资料里根本没有的功能,先确认这些词在资料里一次都没出现),以及 233 道照资料写成群友口吻的合成题,覆盖常说的词、只说需求、描述现象、粘贴报错四种风格。检索看正确小节排第一的比例和「无答案拒掉」;回答由小龙女用线上的模型和提示词作答,再交给另一家的 Claude Opus 5.5 评分,避免模型给自己打分。

检索方案对比(16 种组合中的代表)
方案真实题:正确小节排第一无答案题:判断为没有运行
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 判断。

真实问题答对率(同样 43 题、同一个评委)
第 1 轮:bge 决定交哪几节
79%
第 2 轮:大模型挑资料
98%
答错 2 道 → 0,正确资料交到手上 38/43 → 43/43,两轮都没有编造。
代价也写清楚:每道项目问题多一次模型调用(约 3.7 千 token),查资料从 0.85 秒变成 4.2 秒;22 道无答案题里有 1 道被相近的资料带偏。三轮评分共花约 5 美元。
Engineering

技术亮点

一个人八天,从第一行代码做到带评测的项目答疑,工程上主要解决了这几件事。

一个进程,单一写入者

每个 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,每周一凌晨逐个重启;存储按天自动清理并压缩数据库。

98%
项目答疑:真实问题答对(第 1 轮 79%)
80MB
三个 bot 合计内存,空闲 CPU≈0
609
离线自动化测试全绿
8 天
135 次提交,从第一行代码到评测完成
下一个项目
PodEcho →