房间里没有「正在输入…」

文字界面能无声吸收的对话延迟,在说话的人有了身体之后,就变成了一个表演问题。

约 10 分钟

延迟没变,变的是契约

把一个语言模型放在聊天框后面,两秒的等待毫不起眼。这个界面有一套已经确立的「正在处理」说法 —— 转圈、三个点、流式光标 —— 而用户花了好几年学会怎么读它。这段等待不需要任何解释。

把同一个模型放在一个距你两米、戴着头显能看见的角色后面,这两秒就变成了一个后端再怎么优化也解决不掉的问题,因为问题不在时长。一个在场却沉默的人,本身就在传达某种东西。取决于沉默持续了多久、以及它前面发生了什么,这个人是在思考、在犹豫、在拒绝回答,或者坏掉了。用户会从中挑一个解读,而如果你的角色没有任何办法表明是哪一个,他挑中的通常是最后那个。

人类对话实际跑在什么速度上

这里的计时参考异常扎实。一项覆盖十种互不相关语言的轮次转换研究发现,从一个人说完到下一个人开口之间的间隔,紧密地聚集在 200 毫秒上下,而且文化差异小得惊人。这比「规划一个回答」所需的时间还短 —— 这恰恰是重点:听者在对方还在说的时候就已经在准备自己的一轮,并且是踩着一个预测出来的结束点开口的。

这件事的意义在于「延迟意味着什么」,而不在于「延迟花了多少」。正因为基线这么紧,高于它的沉默就不是中性的。半秒的间隙已经足够被察觉。到一秒左右,听者开始把它读成不情愿或者不同意 —— 人们如何解读一个被延迟的回应,在邀约和请求的场合里有充分的记录。再往后,解读就完全取决于说话者看上去在做什么。

一条 800 毫秒给出回答的模型管线,按工程标准算是干得不错,而它正好落在「人类听者预期这个停顿有含义」的区间里。这一仗靠压延迟是赢不了的。问题是这个角色拿这段时间在做什么。

这段延迟是由哪几块组成的

值得拆开,因为它们的解法不同,而且只有一部分在你这侧的网络里。

断句(endpointing)
判定用户说完了。设得激进会把人话说一半截断;设得保守就给每一轮都加上一笔固定的税。这一块经常是最大的单项贡献者,也是团队最后才去看的一块 —— 因为有意思的技术不在这儿。
转写
流式模型在用户说话的同时就产出部分结果,也就是说这一块大部分可以和说话重叠掉,而不是在之后才付。一个等用户停下来才开始的批量转写,等于为同一段话付了两遍时间。
首个 token 的延迟
取决于模型大小、部署方式,以及你的提示有多长。提示长度是你能控制的那部分,也是会悄悄长大的那部分:一段无上限累积的对话历史会让每一轮都比上一轮更慢,而用户体验到的是「这个角色累了」。
语音合成
流式语音可以在第一个分句就开口,而不用等到最后一个 token —— 这把「第一声」和「完整答案」解耦了。这是能拿到的最大的一笔感知收益,因为用户反应的是角色什么时候开始说,不是什么时候说完。
动画派发
口型和手势必须由还在陆续到达的音频驱动。那些要等完整音频缓冲区才开始做动画的系统,把流式语音刚省下来的全都还回去了。

方差比均值更伤人

一条稳定在 900 毫秒的管线,聊起来比一条均值 600、但在 200 到 2000 之间跳的更舒服。从指标看板上看这很反直觉,因为第二个系统的数字更好看。

原因是稳定的延迟可以被吸收进人设。一个总要顿一下才回答的人,是沉稳、是谨慎、是年纪大了;三个来回之后用户已经调整了自己的节奏去配合它,然后就不再注意了。而一个忽快忽慢的延迟没法被读成性格,因为性格是稳定的。它会被读成它实际是的那个东西:一个响应时间取决于用户看不见的某件事的系统。

由此得到的设计推论是:有时候钳位比优化更值钱。把快的那些回答压住,统一拖到 700 毫秒左右的地板上,让快慢两种情况看起来一样 —— 这不会让你损失任何用户在意的东西,却拿掉了那份被读成「不可靠」的抖动。工程师往往抵触这件事,因为这等于故意把好的情况做差;而它通常仍然是对的选择。

用什么去填那段沉默

既然沉默一定会被解读,务实的做法就是给它一个身份。一个移开视线、往上方看去的角色,是看得出在思考的,而且这个解读能撑住的时间长得出人意料 —— 比一个干站着不动的角色长得多,后者大约一秒就会被读成卡死。

陷阱在于:任何单一的填充动画,一旦重复就是破绽。用户识别出一个循环的思考姿态,比设计师预期的要快得多;而一旦被识别出来,它就不再是思考姿态,而变回了它一直以来的那个加载转圈。一个小型动作库、采样时避免紧邻重复,能撑得住;单独一个手势活不过一场十分钟的对话。

一个即时的非语言确认,比一个更好的填充动作更值钱。一次点头、一个轻微前倾、一次重心转移,在断句触发的那一瞬间就派发出去、在模型还什么都没产出之前 —— 它告诉用户「你被听到了」。那才是这段空隙里真正的焦虑,而它可以在本地、一百毫秒之内被回答掉,与此同时真正的回答还在别处生成着。

有些最便宜的收益在写作里。一个本来就有理由慢的角色 —— 在读什么东西、手上在忙、习惯出声思考 —— 买来的秒数是一个笔直站着待命的角色买不到的。这也是这篇里最不通用的一条建议,因为它约束了你的角色能是什么,而那是个产品决定,不是工程决定。

真正难的是打断

上面讲的全都是「怎么让一段延迟可以忍受」。更难的问题是:真实对话是可以被打断的,而多数已上线的管线不能。

人在对方开口时会把一个词说到一半就停下,而打断本身是带含义的:它表示不同意、不耐烦,或者「意思我懂了,不用展开」。一个在用户已经压着它说话时还要把自己那句说完的系统,不只是慢。它在传达「我没在听」,而这比传达一个停顿要糟糕得多。

要处理它,意味着能在一句话中途取消合成、停掉动画且不让它突兀地弹回、丢弃已生成回答的剩余部分,并且带着那段没说完的话作为上下文重新进入聆听。每一块单拎出来都可解,而这套组合很少有人做 —— 因为它做对了是隐形的,只有在用户测试里、当有人用对人说话的方式而不是用对机器说话的方式开口时,它才暴露出来。

而这也正是「身处 VR」把赌注抬高的地方。一个对着聊天窗口说话的用户知道自己在操作软件,会自行调整。一个和一个有身体的角色同处一室的用户不会以同样的方式调整,因为在场感正在起作用;于是这个系统被按一套「界面自己招来的」标准去衡量。

相关页面