不再千人一腔的机器人
常客们发过的每一句话都出自六个句子之一。现在有数百句,每个角色都有自己的语气,而且只要还在屏幕上,就不会重复。

通俗版
常客进入大厅两天后,只要仔细看就会发现问题:他们说的话都一样。其中一位在一个晚上先后发了 "quick game anyone"、"lets play"、"quick game anyone"、"need one more"、"need one more"。另一位在九十分钟里说了三次 "anyone up for a game"。
数据库说得很直白:他们发过的每一句话都是那六句之一。游戏内也是同样的情形——某个昵称把 "had to have it" 说了九遍。还有第三个没人提到的破绽:十二个人写的全是同一种腔调——小写、简短、没有标点——而同一个房间里的真人却在打 "Hi guys!"、"Someone wanne play?" 和 "Let’s goooooooooo!"。
现在每位常客都有自己的语气,它取决于这个角色是谁,而不是每次随机抽取。放在整个阵容里,这意味着:
- 有人全用小写,有人写完整的句子——而且始终如此;
- 有人标点很多,有人从不用标点;
- 每个人都有两三个真正会用的表情符号,而不是共用一个池子;
- 有人喜欢把词拉长,有人爱用缩写,有人偶尔打错字;
- 他们说的话与房间状况相符:没人会在还空着四个座位时喊"再来一个"。
他们在任何地方都只用英语书写,这与游戏中其他机器人行为遵循的决定一致。
技术细节
语料按意图组织——打招呼、找对局、还差一个人、刚加入、评论模式、等待、闲聊、鼓励、再来一局——其中两种意图的内容取自真实房间,因此句子不可能与玩家眼前看到的情况相矛盾。
角色的语气由昵称推导而来,而不是存储下来,因此不需要在角色类型上新增字段,并且进程重启后保持不变:
// botChat.voice.ts — the voice is a pure function of the nickname
const rng = createRng(`voice:${persona.nickname}`);
return {
sentenceCase: rng.float() < 0.34,
punctuation: pick(rng, ['none', 'light', 'heavy']),
emoji: pickN(rng, EMOJI_POOL, rng.int(2, 3)), // habitual, not shared
elongation: rng.float() * 0.2,
typoRate: rng.float() * 0.06,
};重复问题由一小块记忆负责,其作用范围同时绑定房间与角色,因此同一句话在仍显示于屏幕上时不会再次出现,同一房间里的两个角色也不会互相复读。
真正有意思的缺陷出在"错别字"变换上。机器人聊天是以服务角色写入的,会直接绕过真人消息必须经过的脏词过滤——因此一个"逼真"的错别字有可能拼出被屏蔽的词(把 "and" 打成 "nad",而它就在屏蔽列表里)。一个 2600 次采样的测试轻松通过;这个问题大约要抽取 68000 次才会浮现。现在的测试改为对约 260000 个可能字符串做穷尽枚举,耗时 1.4 秒,并且调用与聊天层完全相同的生成器,而不是自行重写——第一版测试重写了一遍,随即就与实现产生了偏差。
