把 Voice Agent 做成可交付的系统
从端到端与级联出发,复盘话轮、工具、记忆、录音和交付边界。
博客本文目录
在实时语音系统的开发中,我逐渐形成了一个判断:交付能力,取决于系统能否把一次对话的时间、状态和结果对齐。
模型已经生成了回答,用户可能还没听见;用户补了一句话,未必是在取消任务;Agent 说“我来查一下”,工具可能根本没有执行。只看聊天记录,这些问题很容易被归成“模型不稳定”。沿着音频和事件往下查,才会看到具体断在了哪里。
从流式级联到原生实时语音,从浏览器到嵌入式终端,再到工具、记忆和录音,链路每增加一层,轮次之间的衔接、异步任务的取消和设备端的实际播放都会变得更重要。
01 / 两种语音架构,共用一套交付底座
先分清两个经常混用的“端到端”。
模型侧的端到端,指应用接入一个原生实时语音会话,由供应商统一处理输入语音、回复音频及轮次事件。这是应用接口的统一,供应商内部仍可能包含多个处理模块。
产品侧的端到端,则从用户开口一直到扬声器发声,包含采集、回声消除、传输、模型、播放、工具执行,以及通话结束后的数据收尾。选用了原生语音模型,这条工程链路仍然存在。
两种路径可以放在同一个 Agent 底座上。终端通过实时媒体层传输音频,Agent 连接供应商;会话身份、授权、配置快照、录音和历史共用。LiveKit 是这类媒体与会话编排的一种开源选择。
级联路径是 ASR → 轮次确认 → LLM → TTS。它的价值在于可拆解:识别、语言模型、音色能独立选择,出问题也能逐段测量。代价是应用需要处理文本增量、断句、取消传播和组件间等待。
原生实时路径把这些内部步骤交给供应商。应用主要维护持续音频流、协议事件、工具桥接和本地播放。它提供了另一种组织听说与打断的方式,但也减少了应用对内部阶段的可见性。
所以,我们没有把原生模型伪装成四个可混搭的下拉框,也没有给它补出“ASR 耗时”“TTS 耗时”。接口没有暴露的内部事实,应当保留为未知。
选型时,需要独立换组件、细分测量和控制,可以考虑级联;需要评估供应商原生听说行为,可以考虑实时语音。最终比较相同场景下的任务完成、误打断、延迟和成本,架构名称本身不构成质量结论。
02 / 级联的速度,来自流式重叠与有边界的提前准备
把四个组件连通,只完成了级联的第一步。接下来要决定:哪些工作可以提前做,哪些结果必须等待确认。
一种有效的做法是允许在用户轮次最终确认前准备 LLM,关闭提前 TTS。这样能够重叠部分等待,又把声音留在确认之后。用户继续说、改口或否定时,尚未播出的推测仍有机会被取消。
这条原则同样适用于流式 TTS:文字要尽早送出,但切得过碎会伤害停顿和韵律,攒得过长又推迟首声。测量“模型首个输出到实际提交 TTS 文本”的时间,可以区分模型慢与文本交接慢。
流式链路还需要明确音频合同。采样率、声道数、编码格式和分帧大小必须在每个边界确定;不同实时供应商的输入要求并不相同。把重采样等协议差异放在适配器中,可以避免让每一种终端各自实现供应商细节。
浏览器开启回声消除、自动增益和一层降噪。是否再叠加服务端降噪,要经过声学 A/B:失真的人声会影响识别和轮次判断,多加一个处理模块本身不代表体验更好。
提前准备要可撤销,进入播放要有依据。 这是级联优化里值得保留的边界。
03 / “有声音”“说完了”“要插话”,是三件事
语音自然度最容易被阈值讨论带偏。想回答快,就缩短静音等待;担心抢话,又把等待拉长。没有拆开问题时,调参只是在两个坏体验之间移动。
我们需要分别回答四个问题:VAD 判断有没有语音;EOU 判断这一轮是否结束;Barge-in 判断是否要让出话权;Backchannel 判断是否适合给一个短促的倾听反馈。
“嗯”可能是用户在听,也可能是思考中的延续;停顿可能发生在回忆人名时;扬声器回采也可能被当成新声音。单个声学事件不足以解释全部意图。LiveKit 的官方文档也区分轮次结束与打断处理,并为实时模型提供独立控制方式。轮次与打断说明
级联可以组合本地 VAD、轮次检测器与 SDK 的打断和误打断恢复,但组合这些组件,并不自动获得完整的语义打断能力。Smart Turn 判断用户是否说完,打断策略决定何时让出话权。Smart Turn 项目说明
原生实时路径采用供应商轮次控制时,一个容易出现的问题是:适配器收到 speech_started 就取消回答,把误触发直接放大成停播。可行的处理方式是先核对当前输入的持续声学证据;不足时等待同一输入项的有效识别,再取消回复并通知本地停播。
这道确认门校验供应商事件,轮次结束仍由原有状态机决定。旧声学片段和迟到字幕不能替当前输入作证。验证时需要覆盖故障片段、正常插话和低音量插话,并进一步检查终端扬声器缓冲和不同回声环境。
真正接受打断后,还要同步处理生成任务、下行播放和对话历史。模型吐出了整段文本,不能据此认为用户已经听完;被取消的尾句也不能继续作为双方共享上下文。
回答前的 filler 与用户讲述中的附和同样要分开。前者缓解回答等待,后者参与话轮中的听说节奏。filler 需要内容门槛、冷却和新话轮取消;话轮内附和还需要判断语境和插入时机。两者都需要单独验证触发率和听感,不能靠一句“嗯,我在听”证明正式回答变快了。
04 / 延迟先统一口径,再讨论优化
用户问“为什么这么慢”,工程上至少存在三个不同终点:供应商产生首帧、客户端软件开始播放、扬声器真实发声。
云端指标与设备播放回执各自有用,但覆盖范围不同。播放回执能说明软件消费了多少音频帧;声学首声仍需要终端采集。原生实时模型的首次回应还可能包含短应答,不能自动当作实质答案。
级联可观察 EOU、最终转写、LLM 首输出、TTS 首音频和文本交接。它们有的重叠,有的来自不同生成尝试,直接相加可能重复计时。工具并行时,等待时间按区间并集计算,也不能再机械加到已经测得的 E2E 上。
因此,指标采集首先要做好关联:把同一轮的事件对齐到话轮标识,用序号和修订防止迟到消息覆盖当前显示,把推测性生成、filler 和工具续答与首段回答区分开。缺失数据保持未知。
一次排查中,用户经历了长时间等待,但查询工具本身很快就返回了结果。主要等待发生在工具被调用之前。此时更换查询接口或延长 HTTP 超时,都没有对准已观察到的故障边界。
基准测试也应先固定模型配置、输入、网络和终端,再分别统计普通轮、工具轮、打断轮的分位数、成功率与样本量,并单独记录失败和超时。延迟目标需要在这些条件明确后,通过真实语音测试验收。
05 / 工具调用要形成闭环,取消也要保留真实结果
语音会把工具问题掩盖得很自然。Agent 说得流畅,用户就容易以为事情正在进行。我们需要核对完整链条:模型产生函数调用,适配器接纳,权限检查通过,执行器执行,结果返回,必要时设备确认,最后生成对应的语音反馈。
如果一次行动请求只有口头承诺,没有工具执行审计,排查应先定位到执行器之前。单凭“没有审计”,无法断定是模型没调用还是适配器丢了事件。
可以使用相同音频做受控对照,记录供应商原始函数事件、适配器接纳事件和执行器调用。如果原始响应里就没有函数调用,应继续检查模型的工具选择行为;如果原始事件存在而执行链缺失,再检查适配与派发。短提示词与好示例值得尝试,但修复必须通过完整任务流程验收。供应商协议支持工具,并不保证每次对话都正确选择工具。
工具续接还有一种常见风险:用户提出任务后补了一句话,系统因为话轮代际变化,直接丢弃后续续答,导致任务没有最终反馈。
这里的关键是给取消分类。只读查询可以保留结果;尚未执行、参数可能过期的写操作应暂缓,并明确返回“输入已变化、尚未执行”;已经提交的外部操作必须保留真实结果。用户开口不能被统一解释为取消,更不能让一个已经发生的操作在历史里凭空消失。
语音挂断则需要更具体的完成条件:确认语生成、播放完成、命令发送、设备确认。不同供应商的桥接路径分别验收,不能用文本中的“再见”代替整条动作链。
每个异步结果都要说明:它属于哪轮、是否仍有效、究竟执行到了哪一步。
06 / 记忆和复杂推理,都需要等待预算
长期记忆能提高连续性,也会给实时语音增加网络请求和过期结果。
级联可以利用稳定的 ASR 中间结果提前检索,再以最终文本校正;每轮限制检索次数,为进入生成前的额外等待设置上限。用户否定或改口后,不能沿用旧前缀查到的语义结果。低相关结果允许返回空,不用无关画像强行填满上下文。
这里有一个实际取舍:Embedding 请求可能超时,检索也可能漏召回。需要把召回率与首声延迟一起评估;达到等待上限时,允许部分轮次不使用长期记忆,避免让增强能力持续阻塞对话。
记忆还必须携带归属、来源与修订。账号、智能体和设备之间是否共享记忆,需要显式定义,注入前再次核对。记忆作为参考资料,用户当前的纠正应当优先;共享一个智能体,也不意味着不同用户可以混用个人记忆。
复杂推理可以采用类似思路:前台维持交互节奏,困难问题按需委托给后台处理。它的代价也明确:多一次推理和续答,增加耗时与费用;是否提高任务质量,需要结合真实任务验证。
这里可复用的判断是:为增强能力分配明确预算,超时、迟到和无结果都要有可接受的行为。
07 / 录音是证据系统,也是一套独立生命周期
录音可以采用“云端双声道主档+异步逐轮切片”的结构。单用户会话中,把 Agent 与用户分别放在左右声道,便于回看重叠说话、等待和打断。多人会话则需要更细的音轨归属,避免把多人的声音混成同一角色。
录音需要显式同意;关闭录音后,基本通话、文字和指标仍然可用。选择录音的会话先等待采集就绪,再开放对话;通话后的下载、转码、切片和上传由后台任务完成,不挤进每一轮实时推理。
录音服务也有版本兼容性问题:协议类型里出现某个请求,不代表所用服务版本真的能处理它。应固定经过验证的镜像,并把真实音频文件的格式、双声道能量、首尾完整性、网页播放与删除列为独立验收项。
删除也有顺序。先清理对象存储,再删除数据库业务记录;对象删除失败时保留记录和失败状态,允许重试。否则数据库里看似删干净了,音频可能仍留在远端,而且失去定位依据。
设备播放回执进一步限制 AI 切片尾点,但它仍然只能证明软件消费。录音、回执与外部声学采集共同帮助定位问题,各有边界。
08 / 让工程经验变成一套能重复执行的 SOP
经过这些迭代,我们逐渐形成两条固定流程。
排障从一条真实会话开始:先锁定设备、时间、配置修订和会话,再沿着上行音频、识别或供应商事件、话轮状态、回复生成、工具审计、下行播放、设备回执逐段核对。找到最早偏离预期的边界,再设计最小复现,每次只改变一个关键变量。
验证按层推进:协议模拟验证状态机;真实供应商验证接口行为;浏览器验证交互与播放;物理电话验证声学和弱网;录音验证文件及删除;容量测试验证并发下是否仍成立。每一层都留下输入条件、版本、失败样本和结论范围。
发布流程从冻结范围开始。并行开发可能让发布源夹杂未经本次验收的改动,因此候选制品基于已确认的线上清单构建,只合入这次经过验证的差异,并记录文件哈希。
切换前暂停新接入,让已有通话自然结束。通话数之外,还检查许可、房间、Agent 作业、录音收尾与其他相关任务。归零后完成必要迁移,切换受影响的服务,核对实际版本、配置和健康,再恢复接入。回滚同样经过空闲门禁,也要保留已经上线的其他有效修复。
多节点能提高维护和接入弹性,但不能据此承诺正在进行的模型会话无缝迁移。容量也要分开计算通话、录音启动和后台处理;设计了准入机制之后,仍需通过压力测试确定安全容量。
开发和上线前,我们会逐项检查五个问题:
- 谁负责决定这一轮结束、何时让出话权?
- 这个事件描述的是生成、播放,还是用户实际听见?
- 用户继续说话后,哪些工作可取消,哪些结果必须保留?
- 增强能力超时或失败时,当前通话还能怎样继续?
- 这项结论通过了哪一层验证,下一层还缺什么?
这些问题让模型选择、延迟优化、工具执行和发布变得可以讨论、可以验证。下一阶段,我更想用完整的真人通话去检验它们:用户长时间回忆时是否被催促,插话后能否自然接回,承诺执行的事情是否真的完成。
对话真正发生在人的耳朵和生活里。我们的工程闭环,也应该一直走到那里。
相关项目:Story Voice。