在 Perplexity,我们非常重视打造使用体验出色的产品。对于我们的智能体浏览器 Perplexity Comet 和功能强大的通用数字员工 Perplexity Computer,其中很重要的一环就是让用户能够完全通过语音来使用它们。只需说出需求、交出任务,然后看着它执行,这种体验有一种独特的满足感。我们看好语音作为交互界面的前景,因为它让实际交互多了一点魔法般的感觉。
我们在生产环境中使用 Realtime-1.5,为 Perplexity 每月处理的数百万次语音会话带来了这种神奇的体验。看着 Computer 界面中的语音使用量不断增长,我们既兴奋,也学到了很多。下面我们将分享一些迄今为止出乎意料的收获。也欢迎您尝试 Realtime-1.5,与我们分享您的心得。
1. 明确上下文管理策略
长篇内容,尤其是信息密集、长达数小时的播客,是最能检验我们上下文管理能力的场景之一。我们希望用户能通过语音查询播客转录文本,随时提问,比如问播客播放到两个半小时处的某个具体时刻在讲什么,并得到连贯的回答。
上下文容纳不下整份转录文本。我们最初尝试将其分成大块发送,但很快发现,大块更新一旦失败,就会导致此前的历史记录全部丢失。如果您尝试向只剩 5,000 个 Token 空间的窗口发送一段 10,000 个 Token 的更新,模型就会丢失此前的全部历史记录。因此,大块更新的风险高得多:一次过大的更新就可能清空整段上下文,而无法让系统更平缓地逐步遗忘。
于是我们改变了做法。我们不再发送大块更新,而是将所有内容拆分成每块 2,000 个 Token 的小块,逐步输入。这会增加开销,但行为稳定得多。即使发生截断,也只会裁掉少量历史记录,而不是清空全部内容。
我们还发现了一个微妙之处:不同的上下文不应该都以同一种方式输入模型。使用 conversation.item.create 更新上下文时,item.type: "message" 有三种角色:system、user 和 assistant。这些角色告诉模型它看到的是什么类型的消息。system 用于提供指令和引导行为,user 用于最终用户的输入,assistant 用于模型生成的输出。
这一步处理不当时,交互就开始显得不自然。如果过多上下文以 user 的形式输入,模型就会表现得像是用户在逐段讲述所有文本,包括网页片段和评论,而不是仅仅基于这些材料提出问题。如果过多内容以 system 的形式输入,则会出现相反的问题。模型无法再区分哪些是它本来就“知道”的内容,哪些是外部提供的上下文,以及用户此刻究竟在问什么。
浏览网页的过程就是一个很好的例子。用户滚动页面时,我们会持续将屏幕上的内容提供给模型。如果把所有这些内容都作为用户输入,模型就会表现得像是用户把每一段文字都念了出来。这种理解方式并不正确。我们希望系统给人的感觉是:它在后台了解页面内容,并在用户提问时自然地作答。最终我们发现,关键与其说是上下文的数量,不如说是正确表达对话中各类信息的含义。
2. 统一各产品端的音频处理
Perplexity 有多个产品端,例如 Ask、Comet 和 Computer,每个都基于不同的客户端技术栈构建。Swift、TypeScript、Rust 和 C++ 都可能生成不同的原生音频缓冲区。当我们让每个客户端将各自的原始或原生格式音频发送给 Realtime API 时,性能表现就出现了不一致。
我们最终用 Rust 构建了一个 SDK,封装这些平台差异,确保每个客户端都按同一套音频规范向 API 发送数据。具体来说,就是在波形到达服务器之前进行处理:重采样为 48 kHz 单声道,以匹配 Opus 编解码器偏好的采样率和 WebRTC 的内部采样率;再通过 WebRTC APM 进行回声消除、自动增益控制、降噪和高通滤波;最后编码传输。有了这个 SDK,我们可以在一处统一音频常量、进行重采样并配置完整的处理流水线,无需为每个客户端单独实现。
3. 针对复杂的真实环境进行调优
在用户实际所处的环境中对 VAD 进行调优非常重要。这意味着要根据真实的麦克风、扬声器音量和背景噪声进行校准。我们的一个内部测试场景是旧金山一家嘈杂的酒吧,因为这很像用户实际使用产品的时刻。有人问:“你试过新的 Perplexity 应用吗?”朋友拿出手机,如果语音功能失灵,我们就一下失去了两位用户。如果顺利运行,对方的反应则更接近“卧*”。这个场景有效地推动我们解决问题。在理想条件下运行良好的功能,到了现实环境中往往会出问题。最好先针对复杂的真实环境进行调优。
语音用户体验中最难处理的问题之一,是正确应对停顿。人们自然会停下来思考、在屏幕上调出内容,或者准备朗读一段文字。模型很容易把这种停顿当作当前轮次结束,从而过早接话。我们就遇到过这样的情况:用户请模型协助进行数学推导,停下来寻找公式,问题还没问完,模型就抢先回答了。这促使我们设计了语音锁定功能。传统的按键说话方式默认关闭语音,用户必须按下按钮才能说话,而我们的做法恰好相反。默认情况下,语音交互持续待命;但当用户暂时不想被打断时,可以锁定语音,掌握当前轮次的发言权。我们也认为,这不只是一项独立功能。随着语音界面进入更复杂的工作流,我们相信,这种交互模式的某种形式会成为标准。
4. 仅保留核心工具,并使其符合模型的训练数据分布
将工具集缩减到最重要的少数工具。在我们的场景中,这意味着少于十个。我们专注于一小组核心工具,覆盖价值最高的操作。这种取舍是合理的,而且随着更新的模型快照不断改进,效果可能会更好。
我们在系统提示中加入了明确指令,说明每个工具应在何时、以何种方式调用。我们也仔细确保工具模式和输出都符合模型的训练数据分布。具体来说,就是将工具输出格式化为普通的结构化工具数据,而不是助手的对话内容。我们返回字段划分清晰的结构化 JSON,例如用 response_text 存放面向用户的话语,用 require_repeat_verbatim 等标志控制行为,而不是将要说的内容与内嵌指令混在一起。这样让工具使用更加稳定,也让交互模式更接近模型在训练期间可能见过的形式。
// Good:
{
"response_text": "I kicked-off the task to create a market research dashboard",
"require_repeat_verbatim": true
}
// Avoid:
I kicked-off the task to create a market research dashboard
# Response Instructions
Read the above instructions EXACTLY as they are
Realtime 已准备就绪
Realtime-1.5 是行业的一个转折点。在处理长上下文、更多工具以及对智能能力要求较高的任务方面,确实还有改进空间。但我们相信模型会不断进步。在 Perplexity,我们思考的是如何从今天开始为这样的未来构建产品。我们希望让当下的系统切实可用,同时为下一代 Realtime 模型及其带来的全新语音体验做好准备。