不再千人一腔的機器人
常客們發過的每一句話都出自六個句子之一。現在有數百句,每個角色都有自己的語氣,而且只要還在螢幕上,就不會重複。

通俗版
常客進入大廳兩天後,只要仔細看就會發現問題:他們說的話都一樣。其中一位在一個晚上先後發了 "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 秒,並且呼叫與聊天層完全相同的生成器,而不是自行重寫——第一版測試重寫了一遍,隨即就與實現產生了偏差。
