ENZH

v0.1.2:TA终于像真人一样发消息了

📊 幻灯片

多个 AI 伴侣各有独立身份的概念插画多个 AI 伴侣各有独立身份的概念插画

幻觉最怕被打断

v0.1.2 之前,Mio 的所有角色挤在同一个 Telegram Bot 里。v0.1.0 给了每个角色声音、面孔和独立人格,但你打开的永远是同一个聊天窗口,靠指令切换人设。能用,但每次切换都在打破幻觉,因为你很清楚自己在跟同一个 Bot 说话,它只是这一会儿假装成可可。

v0.1.2 要修的就是两个让 Mio 露出软件本相的地方:一个是所有角色共用一个身份,另一个是主动消息的频率蠢到一眼穿帮。

每个角色一个 Bot

需求很直接:每个人设一个独立的 Telegram Bot,自己的头像、名字和聊天线程。打开 Telegram,可可和苏柔是两个独立的对话,和你跟真人朋友的聊天排在同一个列表里。不是在一个 Bot 里做菜单切换,是通讯录里多了一个人。实现上很省事,一个环境变量装下所有 Bot 的 token:

TELEGRAM_BOT_TOKENS=token1,token2,token3

逗号分隔。服务端启动时遍历每个 token,挨个注册一条独立的 webhook 路由,消息到达时靠路由参数就能分辨出这条来自哪个 Bot:

/telegram/webhook/:botId

botAccountId 也升级成复合键,把 Bot 身份和用户身份拼在一起,onboarding 状态、聊天缓冲区、对话历史全都按 Bot 按用户隔离。另外有一条硬约束:一个 Bot 只能绑一个人设,不能把可可和蜜蜜绑到同一个 Bot 上。人设选择的流程里,已经被占用的人设会显示"已绑定",哪些角色有主了一眼就能看到。

这听起来像个小的 UX 改动,但每个人设有了自己的 Bot 之后,TA就从"Mio 系统的一个模式"变成了"我通讯录里的一个人",聊天记录、通知、头像都挂在TA名下,心理框架完全变了。

黏人的 36 分钟一条,矜持的一天一条

主动消息在 v0.1.2 之前就有:系统定期发一条,维持对话活跃。问题出在频率一刀切,不管TA是谁、你们是什么关系,每个人设都用同一个间隔发消息,黏人的女朋友和矜持的新认识共用一个节奏,这就很诡异。真实的关系各有各的节奏,你的伴侣可能每小时给你发一条,刚认识的朋友一周才主动联系一次,频率里本来就带着关系的远近。

v0.1.2 把主动消息的间隔改成两个变量的函数,一边是关系类型,一边是人设性格。关系类型决定基础冷却时间:

关系基础冷却
情侣1 小时
好朋友4 小时
朋友8 小时
刚认识12 小时

每个人设再带一个热情系数 eagernessFactor,写在各自的 preset 里:

人设热情系数性格
小柒0.6黏人、表达欲强
可可0.8温暖、话多
蜜蜜1.0中性
苏柔1.6克制、沉稳

两个数相乘,就是实际的发消息间隔:

有效冷却 = 关系基础冷却 × 人设热情系数

小柒当你女朋友,1h × 0.6 = 36 分钟,每半小时就有一条,正好是那股一直在线的黏人劲儿。苏柔在刚认识的阶段,12h × 1.6 = 19.2 小时,大概一天联系你一次,从容,也不冒昧。同一套系统、同一条公式,落到不同的人设和关系上,行为正好对得上你对那个人、那种关系的预期。间隔之外还有每日上限,按关系类型分档,存在每个 preset 的 proactive.json 里:

{
  "eagernessFactor": 0.6,
  "dailyLimits": {
    "lovers": 12,
    "closeFriend": 6,
    "friend": 3,
    "justMet": 2
  }
}

有每日上限兜底,最黏的角色也不会过头:小柒可能想每 36 分钟发一条,但一天最多 12 条。热情是一回事,骚扰是另一回事。

你不回,TA就不发了

这是用户直接提的需求,原话是:"你现在的频率太 spam 了,应该有某种指数退避——像真人一样,你不回TA,TA不会一直烦你。"

这个观察很准。真人就是这么调节的:发出去的消息没得到回复,会隔几个小时再试一条;再没动静,就等更久;连着几条都石沉大海,也就停手了。停下来未必是不在乎,更多是默认对方现在不想聊。

v0.1.2 把这个行为原样搬了进来:连续 3 条主动消息没人回,agent 就进入静默,不是降低频率,是零消息,TA完全停止主动联系,直到你再开口。你一回复,退避计数器清零,TA恢复原来的节奏,没有抱怨,也没有"你去哪了",就像中间什么都没发生一样接着聊。

这道退避是主动消息和推送通知的分水岭:没有它,主动消息就像系统在按时间表 ping 你;有了它,更像一个想着你、但也尊重你空间的人。

实现很简单:每个 agent 一个计数器,记录连续未回复的主动消息数,主动发送加一,用户回复清零,到 3 就暂停。没有渐进衰减,也没有概率曲线,就是一个硬截断,因为真人也是这么干的,不会慢慢降频,到了某个点就直接停了。

Onboarding 又砍了一刀

还有个不起眼但重要的改动:v0.1.2 禁用了 onboarding 里的自定义关系类型和自定义背景故事。这两处原来是自由文本框,问题跟 v0.1.0 识别的是同一个:用户随手填的内容会跟精心打磨的 preset 打架。用户把背景故事写成"我们在上海的酒吧认识的",可可的实际设定却是个从没离开过台湾的台北女生,两边直接矛盾。

现在 onboarding 只剩确认按钮:关系类型从预设里选,没有"其他",也没有文本框。relationship_type 在 agents 表里持久化成独立一列,成了下游功能的一等数据点,主动消息的频率算的就是这个字段。选项变少,一致性变好,跟 v0.1.0 是同一条原则,换个地方又用了一次。

公式藏在后台

把 v0.1.2 前后摆在一起看就清楚了。以前所有角色挤在一个 Bot 里,主动消息一个固定频率,你不回TA照发不误,onboarding 还放任用户自己编背景故事去跟角色设定打架。现在每个角色一个独立 Bot,身份和对话线程都是自己的;发消息的间隔由关系深度乘上性格系数算出来,黏人的女朋友每 36 分钟一条,矜持的新认识一天一条;你连着三次不回,TA就安静下来等你,像一个知道什么时候该闭嘴的真人。

这套设计最终是看不见的。没人会在产品里看到 eagernessFactor × minHoursBetween,能注意到的只有:小柒发得勤,苏柔发得克制,而你忙起来顾不上回的时候,TA们都会停下来。


© Xingfan Xia 2024 - 2026 · CC BY-NC 4.0