LLM 天生话多,我加了两层机制把回复压短了
AI学会适时沉默的概念插画
LLM 天生话多,这个跟训练方式有关系:它是被训练成「有帮助」的,而「有帮助」在训练数据里基本就等于「详细」。你发一个「嗯」,真人朋友会回「干嘛」,两个字。LLM 会回一整段,问你今天过得怎么样,又讲自己最近在忙什么,末了还要抛一个走心的提问过来。做客服助手这样没什么问题,做伴侣就不行了,真人发消息不是这个节奏。
我拉了一下数据。做长度控制之前,Mio 的 5 个人设在 realistic 模式下,回复和用户输入的长度比平均是 5.02x,也就是回复是用户输入的 5 倍长。短消息场景更夸张,用户只发一个字的时候,这个比率能到 6.60x。你说一句它回五句,反正整场对话基本就是 AI 一个人在讲。
一开始试的两条路都不行
第一反应是暴力截断:设个 max_tokens 上限,硬砍输出。问题立刻就暴露了,截在句子中间比话多更糟。「我在想你昨天说那——」,这种断了尾巴的回复比小作文还难受,用户会以为 app 出了 bug。
接着试的是纯提示词控制,在 system prompt 里写死「回复要简短,不超过 X 字」。有点用,但很不稳定。LLM 把长度指令当成建议,不当成硬约束,或者说它觉得这条是可以看情况执行的:话多的人设(小柒,大学生小奶狗性格)照样写小作文;安静的人设(苏柔,文艺编辑)本来就简短,这条提示词等于白写。
两条路都试完,我退一步想了想:把回复长度定成一个固定值,这个方向本身就是错的。长度至少应该随三个维度变化:
- 模式:realistic(短信风格,模拟真人节奏)vs companion(更有表达欲,允许更长的回复)
- 关系亲密度:distant→neutral→close,参考 v0.1.4 关系演化系统
- 人设话多程度:quiet(苏柔、陈哥)vs neutral(可可、蜜蜜)vs chatty(小柒)
安静人设、刚认识的关系、回一条短消息,合理的长度大概就是 2 到 4 个字;话多人设、情侣关系、聊深度话题,可以到 12 到 18 字。想清楚这三个维度之后,方案就自然分成了两层。
回复长度预算由「模式」「关系亲密度」「话多程度」三个维度共同算出,不是一个固定值。
第一层:每轮生成前注入长度合约
具体做法是这样的:每次生成之前,动态往 system prompt 里注入三个东西。1. 字符上限,根据模式、关系、人设算出来;2. 气泡数上限,companion 模式允许多个气泡模拟连续发消息,realistic 一般只给 1 个;3. 一条通用原则:简短、温暖、把空间留给用户。
举几个 realistic 模式的预算:distant + 短消息,4 字 1 气泡;neutral + 日常聊天,8 字 1 气泡;close + 深度话题,84 字 3 气泡。话多人设在这个基础上加 15-40%,安静人设减 10%。
第二层:确定性的后处理裁剪器
这一层有个关键决定:我没有再调一次 LLM,写的是一个纯代码函数 normalizeAssistantReplyLength(),做确定性裁剪,没有任何模型参与。按句子边界切分(中英文标点都检测),裁到字符和气泡预算以内,只保留完整句子,绝不切半句。它在持久化和发送之前执行,所以数据库里存的和用户看到的是同一份文本。
不让第二个 LLM 去「帮我缩短这段话」,主要是成本和延迟。每条消息已经有一次 LLM 调用的开销了,再加一轮缩写,成本直接翻倍,延迟还要多 500ms 以上。聊天产品用户对响应速度非常敏感,发完消息等 2 秒和等 3 秒,感受完全不一样。
确定性函数零成本、零延迟,行为完全可预测。代价是它不会「智能缩写」,只能按句子切。但实测下来,把多余的句子整句砍掉,比试图去压缩每一句效果更好:LLM 生成的回复一般前两句就是核心,后面基本都是展开和补充。把后半段砍掉,留下来的反而更像真人说话。
两层机制:先用长度合约引导生成,再用确定性裁剪器按整句把回复砍到预算内。
第一版压过头了
第一轮调参把 realistic + distant 的回复压得太狠。用户问「在干嘛」,LLM 回「嗯。」够短是够短,但这就是句废话,根本没回答问题。
修的方式是加一个针对活动类问题的确定性守卫,专门挡这种「为短而短」的情况:检查用户是不是在问「你在做什么」「在忙吗」这类需要实际内容的问题,是的话就给回复设一个下限,必须给出实际回答。「在忙」两个字,又短又回答了问题,合格;「嗯」,短但没回答,不合格。这套判断不需要模型参与,几行正则就能搞定。
裁剪既要有字数上限也要有内容下限,太短到答非所问就被下限守卫挡回。
调了几轮参数之后发现,简短跟温暖其实不冲突。真人回话也短,但你不会觉得他冷漠,差别在语气,还有对 context 的把握。
拿最短的输入「嗯」举例,可可这个人设在 realistic 模式下:distant 关系回「嗯。」,2 个字,陌生人之间的礼貌回应;neutral 回「幹嘛啦。」,4 个字,朋友间随意的反问,带点好奇;close 回「幹嘛,句點我喔?」,8 个字,情侣间的撒娇吐槽,短但有情绪。同一个输入,三档回复各自贴着当时的关系状态。
A/B 测试结果
两层系统上线后跑了一轮完整的 A/B 测试,下面所有数字都是回复/输入长度比:
| 场景 | 控制前 | 控制后 | 变化 |
|---|---|---|---|
| 总体 realistic | 5.02 | 2.78 | -45% |
| 短消息 realistic | 6.60 | 5.00 | -24% |
| 日常聊天 realistic | 1.95 | 0.70 | -64% |
| 深度话题 realistic | 6.49 | 2.63 | -59% |
| Distant 平均 | 3.83 | 1.83 | -52% |
| Neutral 平均 | 3.97 | 2.28 | -43% |
短消息场景改善最小(-24%),这个也正常,用户只发 1-2 个字,回复再短也得有内容。日常聊天改善最大(-64%),控制前 LLM 在这类场景里最爱展开,现在后半段直接被裁剪器砍掉了。Distant 关系的比率从 3.83 降到 1.83,基本接近真人水平了:刚认识的人之间,回复本来就该和输入差不多长,甚至更短。
感觉话痨这个问题,在 AI 伴侣这个领域一直是被低估的。行业里一般优化的是 engagement,回复越长用户停留越久,指标越漂亮,回复太长这件事没什么人当成问题来管。我自己做下来的感受是,真实的关系里本来就有沉默,真人不会把脑子里的想法全说出来,很多时候不说话也没什么问题。
把 LLM 的回复砍短这件事本身没什么难度,砍 token 就行。真正花时间的是让砍完的结果还合理:短消息场景短得有内容,亲密关系短得有情绪,不能短得像敷衍。这个需要同时看 context、关系状态和人设性格,三个维度的每一种组合都得单独调,没有一个全局参数能一次搞定。
Mio 在这些问题上的更多取舍,都写在 Mio 宣言 里。
