大多数聊天机器人是“被 @ 了就回一句”的助手。你发一句,它回一句;你关掉窗口,它就停在原地,既不知道过了多久,也不关心你这几天发生了什么。它可以很聪明,却很难让人觉得“对面有人”。

ai-life 想做的是另一件事:让 AI 像一个人那样待在你身边——有自己的性格和日子,懂分寸,记得每一个人。 QQ 私聊里有一对一陪你的有希;QQ 群、微信群里还有一个把你和朋友们当老熟人的她。“像人”不只是说话像人,还包括什么时候开口、什么时候闭嘴、怎么对不同的人、被逗了怎么办、记不记得你上周说过的事、会不会慢慢变得更懂你。

这篇文章不按版本顺序写,而是按功能拆开:每一块能做什么、是怎么做出来的、踩过哪些坑。文中截图都来自 18.1 代码真实运行的小屋网页(数据是虚构的示例)。

项目地址:GitHub · ai-life

项目 现状
当前版本 18.1(仓库发布号;代码里有希是 13.0、群聊是 6.0)
代码 ailife/(有希)约 1.5 万行,aigroup/(群聊,QQ 和微信共用)约 2 万行
测试 566 个自动测试全部通过(有希 139、群聊 254、QQ 接入 173),另有离线自检、人设回归评测和每周人格一致性评测
运行环境 Windows 电脑,或 2 核 4G 的 Linux 轻量服务器;不需要显卡
大模型 任意 OpenAI 兼容接口,全部由你自己添加

一、先看它能做什么

三个角色,一份代码

角色 是谁 渠道 启动命令 小屋网页
yuki 有希,一对一陪你,有记忆、有情绪、有自己的生活,会主动找你 QQ 私聊(NapCat) python run.py yuki 127.0.0.1:8765
qq 群里的她,会看气氛接话、记得每个群友、会玩梗 QQ 群(NapCat) python run.py qq 127.0.0.1:8767
wechat 同一个群聊大脑,接到微信群 微信群(Windows 微信 / Docker 里的 Linux 微信) python run.py wechat 127.0.0.1:8768

代码只有一份:ailife/ 是有希,aigroup/ 是群里的她,QQ 和微信的差别只在最外面的接收、发送层(aigroup/qq、aigroup/wechat)。每个角色有自己的“家”——home/yuki、home/qq、home/wechat,里面放她自己的配置、密钥、SQLite 数据库、NapCat、立绘和私人人设,都不进 git。更新代码只换代码,家里的东西不动。

像人的五件事

做了什么
有自己的人格 不用一串形容词,而是用心理学的人格模型写:特质剖面(大五 30 facet)、“如果……就……”的情境签名、价值排序、幽默分寸;每一轮按当下的情境提醒她“你一贯是这样的”
懂每一个人 用同一套办法给每个群友建画像;知道谁和谁常聊、每个人“吃哪一套”;回话前先判断对方这句想要什么
懂分寸、会看时机 不是每条都接:先算这时候插话自不自然,拿不准再问一个便宜模型;有人连着逗她会越回越短;没人接就安静;每天主动开口有预算
真的看 B 站视频 读字幕或听一遍,再在几个时间点截画面给看图模型看,把声音和画面对上;有人追问“第三分钟那段”能翻出原话和画面
会慢慢长大 记住群里的梗、约好的事到点提醒;每周回想、每月整理“我是怎样的人”(要证据);性格要改得你点头;每周自动评测她有没有走样

有希这边还有:像真人一样分条发消息、先查再合并的长期记忆、四个会衰减的情绪值、作息和校历、只解锁到今天的故事线、内心独白、日记 / 周信 / 季度回忆录、有分寸的主动消息,以及打卡、愿望清单、时间胶囊这些“一起做的事”。

小屋里的她

每个角色自带一个只在本机能打开的网页“小屋”。18.0 把它换成了暖纸色、奶油白卡片的样子:首屏是她此刻在做什么和她的立绘,颜色跟着真实时间走。

有希的小屋首页:首屏只说她此刻在做什么、心情一句话;下面是她的今天、一起的记忆、快到的日子、还没聊完的、她在想和正在经历的故事线

截图说明:人设用的是仓库自带的示例角色林汐和小满,人名、群名、聊天、日记、评测分数都是编的;大模型接的是本机的假接口,没有连真实 QQ 或微信;没有放立绘,所以立绘位置显示的是名字的最后一个字。

二、整体架构

有希这边按职责分包,依赖只往下走:最底下的 core/ 放配置、数据库、密钥、自检、备份和更新;上面是 llm/、persona/、media/;再上面是她的“脑子” mind/(brain 拼提示词,memory、retrieval 管记忆,emotion 管情绪,story、herworld、inner、rhythm 负责她自己的生活);最外层是和渠道无关的 chat/、接 QQ 的 channels/qq/、主动消息 proactive/ 和小屋 web/。群聊这边的大脑在 aigroup/chat/(门控、开口、人格、懂人)、aigroup/memory/(画像、事实、群梗)、aigroup/eval/(评测和标注)。

一次私聊回复大致这样走:

  1. NapCat 通过 WebSocket 把你的消息交给 channels/qq;
  2. chat/companion.py 等你连发的几条说完(默认 4 秒),合成一次输入;
  3. mind/brain.py 把人设、当前情绪、时间感、今天的安排和故事进展、检索到的记忆拼成提示词;
  4. 大模型路由按 chat 用途挑模型生成回复;
  5. mind/quality.py 检查重复、开头雷同、称呼超额,有问题就带着问题重写一次;
  6. mind/output.py 解析 [心情:…]、[提醒:08:00|…] 这类标签记账,再从正文里去掉;
  7. chat/delivery.py 把回复按 ||| 拆成几条,算好打字停顿,一条条发出去。

三、人格:形容词不是人格

这是 14.0 以后项目最核心的部分。早期的人设就是一段 Markdown:“嘴贫、傲娇、心软”。这种写法有两个问题:模型会执行过头(被逗时回个不停、毒舌变刻薄);长对话里还会被稀释,越聊越像对面的人——研究里叫 Persona Drift。

六层人格

现在人格拆成六层,写在人设旁边的一个 YAML 里(persona/xxx.traits.yaml,代码在 aigroup/traits.py):

层 写什么 依据 谁能改
核心身份 一两句:她是谁、和这个群什么关系 — 你
特质剖面 只写和她有关的十来个 facet,每个 1~5 档 + 一句说明 大五 / IPIP-NEO 你(她只能提建议)
情境签名 什么时候 → 她会怎样 → 她不会怎样 Mischel & Shoda 的 CAPS 你
价值与动机 冲突时的裁决顺序;想要的、真正需要的、毛病、矛盾;四种幽默的配比 Schwartz、依恋理论、Martin 幽默风格 你
声音 句长、口头禅配额、绝不说的话、按情境挑的对话示例 — 你
叙事身份 这一阵在意什么、怎么应对、一个关键场景、故事往哪走 McAdams 她自己写,要有证据

写法上有几条经验(都写在 docs/人格刻画.md 里):“毒舌”不是一个特质,要拆成“坦率高 + 顺从低 + 温柔高”——嘴上不让人、心里软;每条签名都要写“不会怎样”;写具体场合,别写口号。

1
2
3
4
5
6
7
8
9
10
11
facets:
A2: {level: 4, note: 说话直,不懂就说不懂} # 坦率高
A4: {level: 2, note: 被怼先呛回一句,但真错了就认} # 顺从低
A6: {level: 4, note: 见人真难过立刻软下来} # 温柔高
humor:
mix: {亲和: 50, 调侃: 30, 自嘲: 20, 攻击: 0}
signatures:
- when: 同一个人连着逗你、让你复述骂人的话
flags: [pestered]
do: 回一次就够,后面越回越短,或者不回
not: 跟着骂别人;同一个问题追问第二遍

运行时怎么用

  • 稳定的部分放最前面:人格文件渲染成“# 你的人格”,放在人设正文最前,提示词前缀不变,能命中大模型服务的前缀缓存;
  • 当下的部分放最后面:程序先认出 13 种情境(被叫到、被连着逗、没人接、被夸、被损、有人难过、问推荐、鸽了、被纠正、深夜、冒泡、新人、聊 AI),按“2 × 命中情境数 + 1 × 命中触发词数”给每条签名打分,挑前 1~3 条签名和 1~2 段对话示例,放在提示词最后、离生成最近的地方——这就是对抗人设漂移的做法。顺带一提,在这之前群聊其实根本没用到人设里的“# 对话示例”,这次才接上;
  • 心情也是条件:CAPS 理论的原意是“if 情境 且 内部状态 then 行为”。她在群里的心情是两个数(愉悦度 valence、激活度 arousal,半衰期 120 分钟),程序据此认出“有点不服气 / 有点低落 / 心情很好”三种状态,人格文件可以给它们写专门的签名。同一个人,心情不同反应不同;
  • 知识边界:人格文件里写她熟的、只知道个大概的(说话带“好像”“我记得”)、不懂的(说不懂、去查,别硬编)、不感兴趣的。依据是 Jones & Bergen 2025 的图灵测试研究:“承认不知道”是最像人的信号之一;
  • 想法池(参考 Inner Thoughts,Liu et al. 2025):输出格式里多了一个 hold——想说但时机不对的一句先留着,最多 5 句、6 小时过期,聊到相关的或者群冷了再拿出来考虑,说出口了就划掉。

她会慢慢长大,但性格要你点头

每周回想一次“这一周的我”,每月整理一次“我是怎样的人”,按 McAdams 的第二、三层写:在意什么、怎么应对、一个带日期的关键场景、故事往哪走,每条都要给证据。她觉得性格该改一档时,只能记成一条建议,在小屋“她的日子 → 她是怎样的人”下面由你采纳或不改。

每周检查她有没有走样

  • 人格一致性:每周一,评测模型不看人设、只读她这一周说的话,给每个 facet 打 1~5 分,和人格文件里的目标比:平均差几档、哪一项跑偏了 ≥ 2 档、四种幽默各说了几句、有没有真伤人的话。第一次跑基线时,一个群平均差 0.25 档,另一个差 0.70 档(“愤怒敌意”高了 2 档),这就是要调的地方;
  • 定向测试(参考 CharacterBench:稀疏的特质要专门出题测):给每条签名、每个“不懂 / 不感兴趣”的话题,让模型编一段会触发它的群聊,她回一句,评测模型按那条规则打分;同时看程序有没有挑中那条签名。场景编一次就缓存,周与周可比;
  • 签名效果:每次用了哪几条签名都记下来,和“这句话后来有没有人接”对上。近两周用了 5 次以上、一半以上冷场的签名,每周自动变成一条“建议改写”。

性格开关:18.0 加了搜索和分组目录;下面的每周评测里能看到人格一致性、定向测试和每条签名用上以后的效果(截图里的分数是示例数据)

四、私聊:怎样让有希“像个人”

像真人一样发消息

“像真人”首先体现在消息的形状上,而不是内容上。真人用手机聊天很少一次发一大段:会分成两三条,中间有停顿;会引用对方的某句话;偶尔打错字再补一句改正。

chat/delivery.py 在输出层模拟了这些细节:

  • 模型用 ||| 分条,每条之间按 typing_chars_per_sec: 6 算停顿(单条最多 6 秒),QQ 上会显示“对方正在输入…”;
  • 可以引用你的话,按 typo_rate: 0.03 偶尔打错一个字,再补一句“*正确的字”,说出口又后悔会撤回;
  • 表情包按优先级选:她从你这里学来的表情包 → QQ 小黄脸 → emoji;你发来的表情包会被理解成某种情绪存下来(17.1 起存之前会验证是不是一张完整的图)。

另一个容易被忽略的细节是等你说完:你连发几条时,companion 会等一会儿,把这几条合在一起理解。人设末尾的“对话示例”按话题挑 3 段给模型参考,只学语气和分条方式,照抄会被质检发现。

长期记忆:先查再合并

最朴素的做法是把对话摘要不断追加进数据库,很快就会出现两个问题:同一件事被反复记好几遍;新信息(“我换工作了”)和旧信息(“我在某公司上班”)同时存在。

mind/memory.py 的整理流程是“先查再合并”:聊天告一段落后用模型抽取新事实;对每条新事实,先用向量在已有记忆里找“是不是同一件事”(余弦相似度 ≥ 0.92 视为同一件);是就更新旧条目,不是才新增。记忆分成三栏:关于你、你们俩、她自己的设定——她随口编出的设定(“我小时候学过三年钢琴”)也会记下来,之后前后一致。

检索是关键词 + 向量的混合方案(mind/retrieval.py):BM25(k1 = 1.4,b = 0.75)擅长命中人名、作品名;向量用 embed 用途接任意 OpenAI 兼容的 /embeddings(推荐硅基流动免费的 BAAI/bge-m3,不用装本地模型),相似度低于 0.35 的丢掉;两路用 RRF(k = 60)融合,向量接口不可用时自动退回纯关键词。

此外还有几种“软记忆”:每周整理一段“她心里的你”;还没聊完的事(14 天过期)、纪念日、你的作息从聊天里自动抽取;你引用她的话回一个 👎,这句话会记进“她会避开的说法”。

记忆页:每周整理的“她心里的你”是一封写给你的信,记忆按三栏排成瀑布流,可以点星调重要度

情绪与时间感

情绪不是一个“开心 / 难过”的标签,而是 mind/emotion.py 里四个 0~100 的数,各自被事件推动,再按半衰期指数衰减回基线:

维度 基线 半衰期 变化规律
开心 60 6 小时 聊得开心会升,过几个小时回落
想念 20 + 3.5 ×(离你上次说话的小时数 − 0.5) 1.5 小时 你越久没来越高,你一说话就大幅下降
委屈 4 3 小时 被已读不回、被冷落时升高
疲惫 按小时查表 2 小时 跟着她自己的作息走,凌晨两三点最高

模型在回复里顺带写一个 [心情:…] 标记,程序把里面的词映射成数值(“开心、雀跃”开心 +12、委屈 −6;“委屈、吃醋”委屈 +12、开心 −10;“困、累”疲惫 +15)。这样情绪由模型“感受”、由程序“记账”,不会剧烈跳变。

有一个刻意的设计:好感度永远是满的(代码里就是一个常量 AFFECTION = 100)。我不希望 AI 伴侣用冷战、疏远来制造“攻略感”——她可以委屈、可以想你,但不会因为你忙了几天就不理你。

17.0 以后,小屋首页只用一句话讲她的心情(“心情很好,偶尔会想你在干嘛”),四个数值、7 天曲线和好感都挪进了“设置”——数字是给调试用的,不该是看她时的第一眼。

她自己的生活

她的一天:日程、内心独白、感知与日记如何串成一天

  • 作息与校历(mind/rhythm.py):学生按真实日期推算学期、第几周、考试周和寒暑假;上班族按工作日上班。每学年升一级,每过一次生日长一岁。每天早上排好“今天的安排”,小屋首屏那句“林汐和编辑开选题会,会后一起吃面”就是从这里认出来的;
  • 身边的人(mind/herworld.py):第一次运行时按人设固定好她的同事、朋友、家人,名字在日程、日记、聊天里保持一致;
  • 故事线:提前写好结局,只让她知道今天(mind/story.py)。开线时模型一次写好全部步骤和结局存进数据库,但聊天和小屋里只放出今天及以前的部分。每天即兴编,事件之间没有连续性;把整条故事告诉她,她又会“剧透”自己的未来。预先规划、按日解锁,两全其美;
  • 长期目标:一两个持续几个月的目标(比如日语 N2),同样提前排好,有进展那天会想跟你分享;
  • 内心独白(mind/inner.py):清醒时每 3 小时“一个人想一想”,输出想法、心情、要不要找你、想说什么。决定找你时,主动消息就从这个想法出发;
  • 日记、来信、纪念册、回忆录:每晚日记,周日一封信,每月一份纪念册,每季度一本回忆录(只许用真实的聊天、日记和纪念日当材料,聊天少于 20 条就不写);
  • 画她自己(17.0):配一个能带参考图的画图模型(routes.image_ref,例如 Qwen-Image-Edit 一类),她发自拍时会照着家里 data/avatar/ 的立绘画,长相前后一致;图上的中文由 Pillow 叠字,不让画图模型自己写字。

她的日子分成“她写的 / 她的生活 / 她知道的”三块:日记、来信、回忆录书架;相册、长期目标、故事线、身边的人;说过的喜好和查过的事

主动找你,但要有分寸

proactive/scheduler.py 每 10 分钟判断一次要不要找你,规则是一组明确的数字:

  • 平均每天随机主动 2 次,你越久不说话,每小时多 3% 的概率;
  • 两次至少隔 90 分钟,你 30 分钟内说过话就不插嘴;
  • 一天最多 8 次(到点提醒和纪念日不算);连续 3 次没回就停;
  • 免打扰 00:30~08:00;看你最近 14 天回主动消息的比例自动调频率。

主动消息也不是随机说话:早安带一份晨间小报(天气、提醒、纪念日、今天更新的番);内心独白里决定找你;手机快捷指令发来“起床 / 到家”信号(往一个私密的 ntfy 频道 POST 一句话),她就顺势说一句“到家啦”。程序关掉时她会发一句提前写好的道别;离开超过两小时再开机,会按当天的安排说说“刚才在干嘛”,错过的提醒合并成一次补上。

别越聊越像模板

mind/quality.py 用“生成前提醒 + 生成后检查”两道关处理,重写时原回复里定下的提醒、纪念日不会丢。还有一套人设回归评测:python run.py yuki eval 用一组固定对话在临时数据库里跑一遍,统计重复率、开头重复、称呼超额、出戏、回复长度,并和上次比较;完整模式还有 20 轮长对话和“隔开 50 条消息后还记不记得”的记忆测试。线上每周一出一份周报,在小屋设置页显示 14 项指标和上周的对比。

五、群聊:什么时候开口,比说什么更难

一条群消息走过的路

提示词按变化频率分层:system 部分按“几乎不变(人格、规矩)→ 每天才变(今天的安排、群笔记、关系网)→ 每批才变(在场的人的画像和相处经验)”排,user 部分放每次都变的聊天内容和当轮挑中的签名。这样前缀缓存能命中最长的一段,DeepSeek 一类接口上能省下不少钱。

门控:不调大模型,先算要不要说

群里的她最常犯的错不是说错话,而是话太多。chat/gate.py 在调用大模型之前,用纯计算决定这一批消息要不要开口:

  • 开口概率:一个 logistic 回归,logit = 偏置 + Σ 权重 × 特征。特征包括有没有叫她的名字、是不是 @ 了别人、有没有问号、是不是只有表情、最近 5 分钟她说话的占比等。初始权重一部分来自开源项目 MaiBot 用自己运行日志拟合的结果(偏置 −1.20、叫了名字 +1.59、@ 了别人 −1.12、有问号 +0.48、只有表情 −1.03……),其余是我加的;
  • 阈值不是固定的:把最近 1 小时这个群每批消息的开口概率从高到低累加,累加到“话痨度(默认 0.3)× 总和”时的那个概率就是当前阈值。也就是说,她大致只拿走一个群三成的“发言份额”,群越热闹门槛越高;
  • 注意力和精力:她回了谁,那人的注意力 +0.4、其他人 −0.1,半衰期 5 分钟;她每主动说一次精力 −0.1,慢慢恢复;连续选“不说”时下次判断的间隔指数退避;
  • 每天主动开口有预算:插话、冒泡、分享共用每天 40 次;同一个人 15 分钟内叫她 4 次以上,这一轮最多一条,也可以不回;
  • 自己学:每次判断都写进门控日志,5 分钟后看有没有人接她的话,每晚用这些数据重新拟合权重。

拿不准时:问一下,再训练一个小判断器

门控离阈值只差一点的时候,最容易出错。13.0 加了“拿不准问一下”:这种时候问一个便宜模型,它只回一个字——说 / 等 / 不,“等”就等那个人再开口;每群每小时最多问 20 次,结果当作弱标签进每晚的重新拟合。

但最可靠的标签来自人。小屋“学到的”页最上面是这一刻她该不该说:程序优先挑“她说了没人接 / 问过模型 / 离阈值不到 0.1”的时刻,给你看那一刻之前的十来句和她后来说了什么,你按键盘 Y(该说)/ N(不该说)/ S(看不出来)。页面显示你的判断和她的一致率、精确率、召回率;这些标注在每晚重新拟合里权重最高。

16.0 再往前走一步(参考 DiscussLLM:先让小分类器判):两类标注各够 30 条,就用它们训练一个朴素贝叶斯小判断器——特征是那一刻之前 4 条消息的字二元组(区分她和群友、区分位置)加上门控特征,拉普拉斯平滑;随机留出 20% 测准确率,≥ 0.75 才启用;启用后,它有把握的时候(≥ 0.85 或 ≤ 0.15)就不再去问大模型。18.1 把标注进度改成实时计数,并写清楚“够数以后当晚训练、第二天开始用”。

工作台 → 学到的:最上面是“这一刻她该不该说”,你判的直接进每晚重新拟合,两类各够 30 条就训练小判断器;下面是她学到的黑话、说话方式,以及她记的每件事是谁说的、有效到哪天

像真人一样慢半拍

  • 被叫到时 2~12 秒才“看到”,刷到想插话时 5~40 秒;human_latency 还会从这个群群友自己的回复间隔里抽样,群里大家回得慢,她也慢;
  • 她按自己的日程正在忙时被叫到,过一两分钟才偷空回一句。这里修过一个很典型的问题:以前等待期间群里每来一条新消息,她就把这批合起来从头再等,群越热闹回得越晚,最多能拖到 10 分钟。18.1 改成第一次被叫到时就定好回复的时刻,后来的消息只合进这一批一起回;
  • 新消息来了会重新想一下(最多 2 次);聊天记录里隔了一小时以上会写“过了一个多小时”,她不会把几小时前的话当成刚说的;
  • 半夜被叫到先记下,早上补一句“才看到”;两个人以上在复读时跟一句;不想说话时给某条消息点个表情。

懂每一个人

画像:每个群友一份,按群分开存(在 A 群知道的事不拿到 B 群说)。字段按和她自己的人格同一套分层:性格、说话风格、口头禅;他的反应(“被损时……;累了时……”,也就是他的 if-then);在意(他在群里图什么);看法、兴趣;带日期的近况和记得的事;称呼、群里关系、怎么聊、别碰的话题。每人多说 15 句就更新一次;只写有证据的,别人损他的话不当成他的性格;画像有历史版本,每人每群留 20 份,可以退回。

熟悉度和说话指纹:熟悉度是每晚结算、慢慢涨的分数,刚认识的人先客气,很熟的才互损;每晚还统计每个人 30 天的“说话指纹”:平均字数、表情包和图片比例、问句多少、常在几点出现。这些不靠模型猜。

关系网和相处经验(16.0,aigroup/chat/insight.py):

  • 数两周里谁回复谁、谁 @ 谁、谁 90 秒内紧接着谁说话,按“2 × 回复 + 1.5 × @ + 0.5 × 紧接着”打分,得出“谁和谁常聊”,放进每天的背景里;
  • 参考 LIFELONG-SOTOPIA 的经验总结:她对某个人说过的话里,哪些被接了(后续得分 ≥ 0.6)、哪些冷场了(≤ 0.2),每晚让模型总结成 3~5 条“他吃哪一套、什么会冷场”,和他说话时放进提示;
  • 参考 FANToM(谁知道什么):一个人隔了 2 小时以上又出现,而这期间群里聊了不少,提示她“他不在的时候聊了这些,别默认他看过”;
  • “今天的状态”由模型判断:关键词命中“累 / 崩溃 / 忙”以后,让便宜模型读他今天的话,判断真实状态(很好到很糟、忙不忙、依据),分得清“累死了哈哈哈”和真累。

工作台 → 群友:最上面是关系网和相处经验——谁和谁常聊,以及她和每个人相处的经验;下面是画像卡片和 24 小时发言分布,你可以抽查每一项画像对不对

回话前先读心思:输出格式里的 think 先判断对方这句想要什么——逗她、认真问、求安慰、分享、吐槽还是随口一说,再决定怎么接。

她记的事分清是谁说的、到哪天为止(15.0):每条事实带来源——他自己说的 / 听别人说的 / 她猜的;开玩笑、互损、转述别人的事不算到他头上(依据 PersonaMem-v2:模型分不清“别人的偏好”和“用户自己的”)。“这周在赶论文”“明天考试”写到哪天为止,到点自动过期,不会过了好几天还问“论文赶完没”。猜的事只出现一次时先不拿来用,第二次出现才用。

你可以抽查:小屋群友页每个画像字段旁边有“对 / 不对”,统计每个字段的准确率;标了“不对”的字段,下次重写时会被要求按证据重写。

此外她还记得群里的事:群笔记和群梗(命中关键词时才告诉她意思);“周四开黑”“下周二面试”到点提醒、考前几小时打个气、事后问结果;群友生日送一张生日卡;有人说“在珠海出差”,她记下这几天他在哪,那边下大雨时提醒一句。

发出去之前的质检

aigroup/chat/quality.py 分硬规则和软规则。硬规则直接不发:思考过程外泄、说提示词、助手腔、做不到的媒介动作(“我刚拍了张照片发你”)、链接、Markdown 残留、碰了某人“别碰”的话题。软规则管“AI 腔”:“希望对你有帮助”、凡事列三点、滥用破折号……参考了 humanizer-zh、AnthroDial 和 Computational Turing Test 等研究。模型每次写两种说法,挑扣分少、条数少的那条。每周还会做一次她自己的风格审计:和群友比句长、口头禅、客服腔。

值得一提的是,现在两个群里的她都是公开的 AI(ai_open: true):大家都知道她是做出来的,所以她不再假装在上课、出门吃饭,“今天的安排”只写线上的事(刷比赛、查资料、整理小本本);“自称 AI”也不再扣分,只拦助手腔和说提示词。

拿真实群聊改一轮

微信群版刚接进两个真实的朋友群,第一个早上就暴露了一串问题:一进群就叫出别人的真名,说“听说你今天上最后一堂课”;被拉去“帮我上课”越说越真;问她中午吃什么,她反问“你们学校附近有啥好吃的”;同一个梗一个早上说了三遍;群友讨论她是怎么做出来的,她顺着说“代码还没传完呢”;有人没 @ 她、紧接着她的话追问,她当没看见。

修的时候我给自己定了一个要求:不针对这两个群打补丁,每一条都改成对任何群、任何人设都成立的规则:

问题 改成的通用规则
刚认识就说私事 事先导入过画像、但真正说上话不到 5 次的人算“刚认识”,他的近况和私事先不放进提示词;被问“怎么知道”就照实说是谁讲的
乱叫名字 每个群可以单独关“叫人名字”;没设的群看群友习惯,最近 300 句里互相叫名字不到 4% 就不叫、也不 @
没 @ 的追问不接 她刚说完,同一个人接着说“你……”或接了她的词,就当成在跟她说
一个梗连着玩 程序找出她这几个小时用过的“招牌说法”,提示词里列出来“这次别用”,质检也扣分
说幕后、反问 不说幕后的事;问什么就给个具体答案
重启丢 @ 重启前 20 分钟里有人叫她、她还没回的,重启后补回

改完以后,我把那个早上出问题的几句话原样喂给新版本重放了一遍(不发群,只看她会怎么说;朋友的称呼换成了“某某”):

群友说 当时 现在
@你是谁 昨晚被某某拉进来测试的那个……你是在验货吗 我叫小满,某某做的那个,先来打个招呼
@你怎么知道我是谁 某某代码还没传完呢 某某跟我讲过你们,说群里最会带节奏的就是你
@hello 某某好,久仰久仰(叫了真名) 来了来了,@一遍就够,我又不聋
@你说我中午吃什么 你们学校附近有啥好吃的,我帮你挑 吃潮汕粿条吧
你小本本上都记啥了(没 @) 没回 谁让我叫爷爷、谁约饭放鸽子……今晚开始一条条对账
感觉它不聪明(没 @) 没回 人机能有我这么会记仇?

这一轮最大的体会是:聊天记录比我自己想出来的测试用例有用得多。 为了让这件事可以重复做,仓库里有 replay(拿真实聊天回放)和 calibrate(看质检会不会误伤)两个工具。

进群前先做功课

“刚认识的人”的规则,配套的是一个进群前的准备流程(scripts\onboard.bat):导出群里以前的聊天记录(只读)→ 大模型整理成群笔记、群梗、每个常说话的人的画像草稿 → 你看过、删掉不想让她知道的 → 再灌进去。灌进去的人都算“事先听说的”,真正说上话以前,她不会把听来的私事说出来。

隐私做成机制,而不是一句承诺

13.0 把隐私从“注意一下”改成了程序里的机制:消息入库前就脱敏(手机号、身份证、银行卡、验证码、邮箱换成占位符);带正文的日志只写进她家里的日志文件;聊天记录按 keep_days 到期清理;forget-person / forget-group 能让她把某个人或某个群从所有表里忘掉,最后 VACUUM;仓库提交前还有一个隐私扫描钩子,防止私人内容进 git。

六、看得懂、听得懂、知道去查

  • B 站视频真的“看”(15.0,net/bili.py):有字幕读字幕,没字幕就取最低音质的音频做语音识别(只转前 20 分钟);再拿最低清晰度的视频流,用 ffmpeg 在“均匀撒点 + 弹幕最热闹的几段”各截一帧(默认 8 张),连同封面一次交给看图模型,写出“每个时间点画面上是什么”;总结时把声音和画面对上(“05:20 画面上是……这时候他在说……”)。只按需读那几秒,不下整个视频——服务器上实测一个 3 分半的 MV,13 秒看完。有人追问“3:12 那里”“画面上那个是谁”,就翻出那附近的原话和截到的画面;
  • 看图:图片按内容去重只看一次,分清截图 / 表情包 / 照片,摘出图上的字,写出把握,把握低时不当真。17.1 修过一个隐蔽的问题:下载图片时只读到了“已经到了的那一块”,大一点的图只存下开头几 KB,发出去是半张图、看图接口回 400;而看图和聊天用同一个模型时,这个 400 会让聊天模型冷却 60 秒,她那一分钟说不了话。现在读到头为止、发之前先认格式,图片本身的错误也不再让接口冷却;
  • 听语音(群聊):QQ 语音转成 mp3 交给 SenseVoice 识别,连情绪和笑声一起译成“[语音 8 秒,带笑……]”;被要求时她也能用本地的 Kokoro 发语音(不克隆真人),每天有条数上限。有希这边仍然只用文字——声音一旦不像,出戏比文字严重得多;
  • 不知道的先搜:问到作品、比赛、新闻这类她可能不知道的事,先查(Tavily 或国内搜索引擎),要点记成知识,常用到的慢慢变成她的兴趣;分享“刷到的”新闻只要最近 3 天的,程序会再对一遍发布日期,避免把旧闻当新闻;
  • 链接:B 站、YouTube、公众号和普通网页先在后台点开读完再回你,只访问公网地址、最多读 2MB。

七、小屋:在聊天之外看见她

小屋是 aiohttp + 单页前端写的本机网页,两个页面共用一份 tokens.css。它只听 127.0.0.1,中间件校验 Host(防 DNS 重绑定),并拒绝跨站来源的请求;跑在服务器上时,在自己电脑上双击 scripts\open_home.bat,它用 SSH 隧道把三个小屋接到本机。

小屋的样子改过三次:14.1 换成安静的蓝白;17.0 换成“窗边日常”——首屏是她此刻和她的立绘;18.0 又换成现在暖色的“小屋”版。后两次都只改样子和排版,接口、数据库、提示词、群聊逻辑一个没动,原来的功能和字段一个没删。18.0 的几条设计规则:

  • 一份 tokens:暖纸色底、奶油白卡片、深棕文字、蜂蜜色小图标;深色模式是暖棕的“台灯”色。字号、间距、圆角只用 tokens 里的几档,两个页面原来各自几百行样式全删了;
  • 颜色跟着真实时间走:早上暖白(5~16 点)、傍晚玫瑰(16~21 点)、深夜深紫(21~5 点),每分钟对一次;
  • 一行顶栏:头像、名字、页面和一个“…”菜单(深浅色、刷新、使用说明、管理),手机上页面放到底部一排;
  • 首页只放“她”:有希首页按 12 列排“她的今天 / 一起的记忆 + 快到的日子 / 还没聊完的 + 她在想 / 故事线”,没有内容的卡片就不显示,空卡片也不占一整行;
  • 群小屋分出“工作台”:首页只有今天的群——四个大数字和昨天比、消息图(今天 / 两周切换)、她的安排;群聊、群友、学到的、性格开关、大模型、日志放进带侧栏的工作台,更紧凑;
  • 在 1440 / 1024 / 768 / 390 / 360 宽、深浅色、早上 / 傍晚 / 深夜都检查过,没有横向滚动。

群聊小屋首页:首屏是小满此刻,四个大数字和昨天比;每小时消息里紫色是她说的话,旁边是她今天的安排

首屏颜色跟着真实时间走:白天暖白、深夜深紫;深色模式是暖棕的“台灯”色

手机宽度:立绘矮一点,页面导航挪到底部

最常用的是群小屋的性格开关:插话概率、回复节奏、复读、点表情、冒泡、人格签名、懂人这些开关拖动马上生效,值存在她自己的数据库里,重启也在;可以只调某一个群;18.0 加了搜索、“只看已调”和分组目录。以前想让她话少一点要改 YAML 再重启,现在拖一下就能看出效果。

小屋顶上的连接状态只用四种颜色:绿是好好的,蓝是装好了在等第一次,黄是要你看一下,灰是没开。这条规矩写在 docs/DESIGN.md 里:整个界面里只有她本人是鲜活的,其余保持安静、清楚、诚实。

八、大模型:自己加,按用途分,花钱有上限

不内置任何接口。 程序里一个地址都不写,加模型的流程只有一条(小屋、命令行、配置向导共用一套代码):粘贴接口地址和 Key → 程序请求 /models 列出能用的模型,按名字猜是聊天、看图、画图还是向量模型 → 选一个,真的试聊一句、显示耗时(推理模型回复为空时,依次试几种常见的“关掉思考”参数)→ 选它用来干什么。

按用途路由(llm/roles.py):聊天、后台小活、整理记忆、夜里写日记、看图、画图、画她自己(带参考图)、记忆检索。每种用途排几个模型,前一个出错换下一个并让它冷却(限流 30 秒,一般错误 60 秒,额度耗尽 1 小时);后台类用途没配就跟着上一级,看图、画图、检索没配就干脆不做。仓库推荐的组合大致是:DeepSeek 聊天和看图(一张图不到 1 厘钱)、硅基流动的 bge-m3 做向量、Z-Image-Turbo 画日常、Qwen-Image-Edit 画她自己。

设置页:心情的具体数值、连接状态都在这里;大模型按用途排顺序,17.0 起多了“画她自己”

聊天优先:整理记忆、写日记这些后台任务一次只跑一个,你刚聊完的 25 秒内先让一让。所有模型都连不上时,她先回一句人设里写好的“信号好差,等我一下下”(不经过大模型),1、3、10 分钟后自动重试补上。

每天的花费上限(llm/budget.py):按接口返回的 token 估算人民币,画图按张算,三个角色共用一本账。到了上限,后台的活先停;聊天再多给一成,超了也停;第二天恢复。用到八成时在 QQ 上提醒。现在三个角色加起来每天 5 块钱以内。

九、部署:从一行命令到 24 小时在线

Windows 一键部署

1
irm https://raw.githubusercontent.com/star031104/ai-life/main/get.ps1 | iex

它会准备 Python 环境(有 conda 就用,没有就建 .venv,连 Python 都没有就用 winget 装)、用清华镜像装依赖和 ffmpeg、问你先配哪一个角色,然后打开配置向导。选了 QQ 的话,自动下载 NapCat、写好 WebSocket 和 token、弹出二维码,用小号扫一次码,之后自动登录。

装完双击 start.vbs,配好的角色全部在后台启动,每个角色一个托盘图标(粉色 L、绿色 G、微信绿 W)。程序意外退出 10 秒后自动重开;退出码 3 表示配置有问题,就不反复重开。托盘和程序之间不走端口,而是约定一个文件 data/tray_cmd:点“重启”时托盘往里写一个词,程序每秒看一眼,看到就自己收尾——有希还会先跟你道个别——再退出。

踩过的坑:最初每次开机都要扫码。查下来是程序把小号号码直接跟在 NapCat 后面,而 NapCat 只认 -q 号码,光秃秃的号码被悄悄忽略。原来的测试用的是一个假启动脚本,正好绕过了它——测试也换成了真实的 napcat.bat 内容。

微信是怎么接上的

微信没有官方机器人接口,aigroup/wechat/ 走的是“读本地数据库收消息 + 模拟操作发消息”:

  • 收消息:微信本地库是 SQLCipher 4 加密的。程序只读地从微信进程内存里找候选密钥(Windows 用 ReadProcessMemory,Linux 读 /proc/<pid>/mem),每个候选都先拿第 1 页的 HMAC 验过才用;之后只解密有变化的页,每秒看一眼;
  • 发消息:切到这个群的单独窗口 → 粘贴 → 回车 → 还原你原来的前台窗口和剪贴板;你在用键鼠就等一等。Linux 服务器上,微信跑在 Docker 的虚拟显示里,用 xdotool + xclip 做同样的事。图片按图片格式放进剪贴板(17.1 以前是当文件粘贴,群里看到的是一个文件)。发完要在库里看到自己那条才算发出去。

搬上服务器

到 10.0 为止,ai-life 一直“住在你电脑上”。用久了发现最常见的场景是:你在外面用手机找她,家里的电脑正好关着。11.0 先做减法(删掉 Telegram、对公网开放的手机小屋、桌宠、电脑感知),再把三个角色搬上一台腾讯云轻量(2 核 4G、60G 盘、5M 带宽,Ubuntu 24.04 + Docker):

  • 一份代码、一个 .venv、三个 systemd 服务(Restart=always,退出码 3 不重启);
  • NapCat 和 Linux 版微信跑在 Docker 里;两个 NapCat 容器要各自固定 machine-id、主机名和 MAC,否则会互相把对方踢下线;
  • 所有端口只绑 127.0.0.1,对外只开 SSH;
  • 小管家 deploy/server/ops.py 用 cron 每 5 分钟看一次门、汇总花费,每天备份、出日报;QQ 被踢下线时用存好的密码重启 NapCat,要短信验证就在 QQ 上提醒。

三个角色、两个 NapCat、一个微信全部起来后,内存用了约 1.2 GB(总共 3.6 GB),平均负载 0.3 左右,硬盘不到 10 GB,大头是 Docker 镜像。大模型都走云上接口,服务器本身不做推理。

自动更新与自检

core/update.py 每天看一次 GitHub 上的版本号,有新版本就在小屋亮黄点,你点“更新”才装。下载的 zip 要过四项检查(CRC、版本号、关键文件、没有奇怪的路径),任何一项不对就一个文件都不动;config*.yaml、.env、data/ 和你已有的人设永远不碰;新版跑满两分钟才算成功,连续两次刚启动就退出就自动换回旧版。

命令 做什么
python run.py yuki selftest 不联网、不碰真实数据,用假模型把核心流程跑一遍,并检查新版本能否打开你数据库的副本
python run.py qq check / wxdiag 群聊体检:接口、NapCat / 微信、数据库
python run.py yuki eval 有希的人设回归评测
replay / calibrate 群聊回放和质检校准
/diag(在 QQ 私聊里发) 一键检查每个接口、QQ、数据库和备份
1
2
3
4
5
6
7
8
9
10
11
ai-life 13.0 离线自检
✅ 依赖:齐全
✅ 配置文件:聊天模型 2 个
✅ 人设文件:林汐,对话示例 4 段
✅ 大脑:回复 + 标签:回了 2 条,提醒和愿望都记上了
✅ 记忆整理 + 作息学习:1 条记忆,作息 1 条
✅ 提示词各部分:1929 字
✅ 主动消息 + 调度:能正常发出
✅ 小屋网页数据:正常
✅ 周报:正常
全部正常,可以放心重启。

十、版本历程

版本 做了什么
9.0 删掉语音条、打电话、桌宠说话(近千行代码、四百多 MB 本地模型);Windows 一键部署
10.0 不加功能的重构:五十多个平铺文件拆成分层包;不再内置任何大模型地址
11.0 删掉 Telegram、手机小屋、桌宠、电脑感知,三个角色搬上服务器
12.0 三个仓库合成一个;照着真实群聊把规则改了一轮
13.0 修好“群聊事实表一直是空的”这个大 bug;向量改接 /embeddings;门控拿不准时问一下;看图、听语音、画她自己;隐私做成机制
14.0 结构化人格(六层)、情境签名放在提示词最后、每周人格一致性评测
15.0 B 站视频截画面“看”;知识边界;想法池;记忆分来源和有效期
16.0 心情改变反应;关系网、相处经验、谁刚回来;画像抽查;定向测试;朴素贝叶斯小判断器
17.0 小屋“窗边日常”版:首屏是她此刻和立绘、颜色跟着时间走;有希也能带参考图画自己
17.1 / 17.2 图片下载只存半张的问题;微信发图按图片发;“发张照片”真的发照片
18.0 小屋暖色“小屋”版:一行顶栏、空卡片不占地方、群小屋分出工作台
18.1 被叫到不再越等越久;“该不该说”的进度实时计数

回头看,ai-life 有好几次是靠删东西变好的:9.0 删语音,11.0 删 Telegram、桌宠和电脑感知(每 30 秒看一眼你的前台程序,这本来就是最越界的功能),17.0 把心情数值和连接状态从首页挪走,只留一句话。少一个功能,就少一类要操心的事;首页少一个数字,她就更像一个人而不是一块仪表盘。

十一、边界:AI 伴侣不能替代真实的人

陪伴类产品天然带着风险,所以 ai-life 在内容上写了明确的准则:

  • 角色都是成年人,不写露骨内容;
  • 不用冷战、威胁或让人愧疚的方式绑住你(这也是好感度恒满的原因);
  • 你连着两天情绪低落,她会收起捉弄和小脾气,多陪你、多听你说;
  • 你说起伤害自己的念头时,她会认真对待,并鼓励你找身边信任的人或专业帮助。

AI 伴侣可以让人在某些时刻感到被惦记,但它不能代替现实中的人际关系和专业支持。这句话写在 README 里,也是我做这个项目时一直提醒自己的。

十二、总结

ai-life 里真正花时间的不是“接一个大模型”,而是围绕它补齐一个“人”需要的东西:性格、时间、生活、记忆、情绪,以及对每一个人的理解。很多功能单看都很小——只解锁到今天的故事线、只拿三成发言份额的阈值、放在提示词最后的那一两条签名、“他不在的时候聊了这些”的一句提醒——但正是这些细节决定了它是工具还是一个“在场的人”。

另一半是工程上的克制和诚实:在不稳定的接口上做路由、冷却和花费上限;用离线自检、回归评测和每周的人格一致性评测保证每次更新不会让她“变了个人”;把隐私做成入库即脱敏、可以让她忘掉某个人的机制。

还有一半是真实的聊天和你自己的判断:规则写得再全,也要放进真的群里跑一个早上才知道漏了什么;小屋里你按下的每一个 Y 和 N,最后都会变成她判断“这时候该不该说话”的依据。

如果你也想拥有一个有自己性格和日子的 AI 伙伴,欢迎试试 ai-life。更详细的说明见仓库的 docs/人格刻画.md、docs/小屋.md、docs/YUKI.md、docs/GROUP_QQ.md、docs/GROUP_WECHAT.md,也欢迎提 Issue 和 PR。

系列文章

  1. 基于大模型的移动应用合规检测系统开发记录
  2. 浅梦 AI 助手 Runtime 开发报告:一个本地语音桌面 Agent 的工程化实现
  3. 砚火 InkForge Local 开发报告:从创作困境到本地 AI 长篇生产系统
  4. 映章 YingZhang 开发报告:从材料到可编辑演示的 AI PPT 生产系统
  5. ai-life 开发报告:让 AI 像一个人那样待在你身边(私聊、QQ 群、微信群)