低延迟不是更快地猜:EOU / Barge-in / Turn Protocol 必须统一(4 种结束 + Generation Fencing + 9 类可复现场景)

低延迟不是更快地猜:EOU / Barge-in / Turn Protocol 必须统一(4 种结束 + Generation Fencing + 9 类可复现场景) 低延迟不是更快地猜EOU、Barge-in 与 Turn Protocol 为什么必须统一TL;DR场景语音 Agent 把停顿 → 提交工具 → 用户打断误判为用户取消但旧工具请求已越过网络并最终修改生产环境运行时把成功结果写进历史。问题不是单点失败而是 EOU、Barge-in、提交、撤销没有共享同一时间线和身份。结论EOU 不是阈值而是事务Barge-in 是一组取消而不是一个静音按钮Turn ID 还不够必须有 Generation ID 与 cancel_epoch 做 fencing。延迟指标必须和误截断、误延长、陈旧结果共用同一时间锚点否则所谓低延迟只会让错误更早并发。产出OPEN/SPECULATIVE/COMMITTED/REVOKED 四态用户轮状态机session_id/turn_id/generation_id/task_id/cancel_epoch 五键事件信封可落地的事件信封 JSON六条全局不变量九种故障注入场景的可复现测试矩阵并把取消、撤销、提交和延迟绑到同一时钟。版本矩阵功能状态说明Deepgram FluxEagerEndOfTurn/TurnResumed/EndOfTurn/turn_index状态机✅ 已验证Deepgram 官方 Flux launch 文档明确EagerEndOfTurn 用于medium confidence for speculative response generationTurnResumed 表示用户续说需取消已准备响应EndOfTurn 为高置信度提交Deepgram Fluxeot_threshold默认 0.7eot_silence_threshold_ms默认 5000ms✅ 已验证官方 launch 文档原话“developers only need to set: eot_threshold: confidence level required to trigger EndOfTurn (default 0.7). eot_silence_threshold_ms: fallback silence duration to force turn end (default 5000ms)”Deepgram Flux eager 模式会多 50-70% LLM 调用✅ 已验证官方 launch 文档原话“With eager_eot_threshold set to 0.3–0.5, you can expect EagerEndOfTurn 150–250ms earlier than EndOfTurn, at the cost of 50–70% more LLM calls”Deepgram Flux EndOfTurn 95% 在 1.5s 内触发✅ 已验证官方 launch 文档原话“Flux typically achieves a p90 (p95) latency of 1 second (1.5 seconds)”OpenAI Realtimeserver_vad/semantic_vadturn detection✅ 已验证官方文档 turn_detection 配置支持 typeserver_vad按静音分块与 semantic_vad按语义估计是否完成并支持 eagernessOpenAI Realtimeconversation.item.truncate截断未听音频✅ 已验证官方 Twilio DemosessionManager.ts代码interrupt 时计算audio_end_ms elapsedMs发送conversation.item.truncate强制服务端上下文与用户实际听到时长匹配OpenAI RealtimecancelResponse(id, sampleCount)行为✅ 已验证官方 reference client 文档原文“This method will cause the model to immediately cease generation, but also truncate the item being played by removing all audio after sampleCount and clearing the text response”OpenAI Realtime WebRTC/SIP vs WebSocket 缓冲归属不同✅ 已验证官方 Realtime 文档WebRTC/SIP 由服务端掌握输出缓冲可在用户打断时自动截断WebSocket 由客户端管理播放必须发送conversation.item.truncateTwilio Media Streamsclear清空缓冲 /mark双重触发✅ 已验证官方 Twilio Media Streams 文档clear清空服务端缓冲音频mark既可能因音频正常播完返回也可能因缓冲被 clear 返回不能一律解释为用户听完LiveKit 默认中断路径停止讲话 截历史到用户听到部分✅ 已验证LiveKit 官方 Turns 文档默认中断路径会停止讲话并把历史截到用户实际听到的部分这是框架语义不是 WebRTC 本身提供的事务保证gRPC 取消语义 取消前修改不回滚✅ 已验证gRPC 官方 Cancellation 文档原文“A cancellation terminates the RPC immediately so that no further work is done. Changes made before a cancellation are not rolled back”gRPC 库不能直接中断应用层 handler✅ 已验证gRPC 官方 Cancellation 文档“the client cancellation operation should propagate to the whole call chain so each end can cancel in time. For server-side, the server can check whether the specific RPC has timed out, or how much time is left”Web AudioAudioScheduledSourceNode.stop()只控制本地音频节点⚠️ 待验证本文引用了规范层面的stop() 后该 source 输出静音但本轮 search 未直接命中 W3C Web Audio 1.1 规范原文建议直接核对 W3C TRWebRTCremoveTrack/replaceTrack(null)不会传播为 Agent 取消⚠️ 待验证本文按 WebRTC 规范语义给出该结论本轮 search 未直接命中 W3C WebRTC 规范原文建议核对 W3C TR 原文LiveKit STT 模式min_delay会在 STT end-of-speech 后再加一次等待⚠️ 待验证本文按 LiveKit Turn Detector 文档引用该行为本轮 search 未直接命中 livekit.io 官方原页建议核对 livekit.io/agents/logic/turns/turn-detector 原页取消契约的精细分类cancel_observed/cancel_completed/cancel_unsupported/too_late/side_effect_committed⚠️ 本文方法这是本文给出的协议设计要求不是某家供应商现有事件发布边界协议、状态机和预算属于工程设计供应商事件名只在对应产品边界内成立不冒充作者已完成的线上实测。摘要语音 Agent 的低延迟不只是缩短静音阈值。EOU 决定何时允许一轮产生后果Barge-in 决定旧一代工作何时失去提交资格延迟指标必须证明取消、静音和结果丢弃发生在同一时间线上。本文给出统一 Turn Protocol、Generation fencing、已听前缀和可复现实验方法。关键词EOU、Barge-in、Turn Protocol、Generation ID、语音 Agent目录先把四种“结束”拆开EOU 不是一个阈值而是一笔事务Turn ID 还不够必须有 Generation IDBarge-in 是一组取消不是一个静音按钮状态提交必须以“用户实际听到什么”为准延迟必须和正确性共用时间锚点可复现测试矩阵结论先定义失效再谈快用户说“把客厅空调调到二十度……”系统在停顿处判定一轮结束LLM 发出工具调用TTS 开始播“好的正在为你……”。用户立刻打断“算了先别动。”音频前端做对了表面上的事停掉扬声器清空播放队列。用户听不到旧回答了界面也显示 Agent 正在听。但旧工具请求已经越过网络。随后它返回成功设备真的被调到二十度运行时还把结果写进会话历史。下一轮模型看到的是“操作已完成”用户感知到的却是“我已经打断”。这不是单纯的 Barge-in 失败也不是 TTS 停得不够快而是旧一轮的执行权没有随着打断一起失效。这类竞态说明EOUEnd of Utterance话语结束、Barge-in 和端到端延迟不是三个可以分别调参的模块。EOU 事件只能提出“用户可能说完”的候选应用层 EOTEnd of Turn或 turn commit 才把输入从“仍可能修订”提升为“允许执行”Barge-in 决定哪一代工作从此失去提交资格延迟指标则必须证明这次提交、撤销、静音和结果丢弃发生在同一条时间线上。三者若不共享 Turn ID、取消契约、状态提交规则和时间锚点所谓低延迟只会让错误更早并发、更快越过不可逆边界。先把四种“结束”拆开工程中最常见的误区是把所有结束事件都压成一个布尔值finaltrue。实际系统至少存在四类信号它们的输入、输出和可证明的事实不同。机制主要输入典型输出能证明什么不能证明什么VAD波形、能量、频谱及声学特征speech_started、speech_stopped或 speech/non-speech 状态能说明声学活动发生变化不能说明一句话、一个意图或一轮对话已经完整EndpointingVAD 状态加静音持续时间部分实现再结合 STT 状态停顿边界、供应商特定的结束标记能说明出现了足够长的停顿不能证明用户不再续说也不能自动等价为语义提交UtteranceEnd已转写词及其时间戳、词间空档词间空档达到阈值后的事件能绕开部分非语音噪声造成的 VAD 问题仍是基于转写空档的启发式不是语义完成证明语义 EOU转写文本、上下文有时结合声学特征完成概率、EndOfTurn或动态等待决策能利用句法、语义和上下文判断“是否像说完了”仍受模型、语言、领域和阈值约束不是跨供应商标准Deepgram 的文档把这些边界写得很清楚。其 Endpointing 基于 VAD 检测从语音到静音的转换达到配置时长后返回speech_finaltrueUtteranceEnd则查看 finalized 与 interim 结果中的词时间在词间空档足够长时独立发事件。两者可以同时启用也可能先后或只出现其一。12因此把UtteranceEnd当成更高级的speech_final或者把两者做 OR 后直接执行高风险工具都会丢掉原始语义。is_final也不是整轮结束。在 Deepgram 的流式转写中is_finaltrue表示某个音频片段的文本不再修订长输入可能先后产生多个 finalized 片段而speech_final仍为 false。完整 utterance 需要累计 finalized 片段再以结束信号决定何时提交。官方示例甚至出现is_finalfalse、speech_finaltrue的组合说明两个字段本来就在不同轴上。13“转写分段完成”和“用户整轮完成”必须使用不同状态字段。供应商命名也不能拼成虚构标准。OpenAI Realtime 把server_vad和semantic_vad都放在 turn detection 配置下前者按静音分块后者按用户所说内容估计是否完成并通过 eagerness 调整等待策略。4Deepgram Flux 使用EagerEndOfTurn、TurnResumed、EndOfTurn和turn_index其eot_threshold、eager_eot_threshold、eot_timeout_ms只适用于 Flux/v2并非通用参数。5LiveKit 又把 VAD、STT endpointing、语义 turn detector、realtime model 内建检测和手动提交列为不同模式。67设计内部协议时可以把它们归一到“候选结束”“恢复说话”“确认结束”等抽象事件但必须保留provider、provider_event、置信度和原始载荷不能假设字段同义。EOU 不是一个阈值而是一笔事务EOU 的正确抽象不是“何时调用 LLM”而是“何时允许这一轮产生可提交后果”。建议把用户轮建模为状态机OPEN - SPECULATIVE - COMMITTED - COMPLETED其中任何尚未完成的状态都可以进入REVOKEDSPECULATIVE还可以因用户续说回到OPEN。候选 EOU 只允许系统做可丢弃的工作例如启动草稿生成、预取只读数据、准备 TTS 文本切分。确认 EOU 才授予正式提交权把用户消息写入权威历史、允许有副作用的工具进入 commit 阶段、把可播放回复绑定到当前 generation。这正是 eager EOT 的真实成本。Deepgram 明确说明降低 eager 阈值会更早触发也会产生更多 false starts若随后收到TurnResumed在途回复应被取消或丢弃且 eager 模式会增加 LLM 请求。8因而“降低阈值减少等待”只描述了收益的一半。另一半是误截断、无效模型调用、错误工具规划和更多待撤销音频。若运行时没有 speculative/committed 边界提前几十或几百毫秒启动的草稿就会越过工具与状态提交点优化立即变成一致性缺陷。确认也不能只看事件名。一个稳健的提交动作至少应校验当前turn_id仍处于开放状态候选所依据的transcript_revision未被后续转写替换当前 generation 未被打断供应商事件满足本产品的提交策略硬超时只作为降级路径而非“语义正确”的证明。Deepgram Flux 保证同一生命周期中EndOfTurn文本与紧邻的EagerEndOfTurn文本匹配并在续说时先发TurnResumed这是该供应商的状态机契约不应外推到其他 STT。6还有一个常被忽略的尾延迟来源重复 endpointing。LiveKit 文档说明在 STT 模式下运行时的min_delay会加在 STT 供应商的 end-of-speech 信号之后。9如果 STT 已等一次静音Agent Runtime 又无条件再等一次P95/P99 会出现并非模型慢、而是两个结束策略串联造成的“误延长”。所有 EOU 延迟都应分解为供应商检测等待、网络传输、运行时二次等待和提交排队而不是统称“ASR latency”。Turn ID 还不够必须有 Generation ID一轮用户输入可能触发多次候选提交第一次短停顿启动草稿用户续说后撤销第二次候选又启动新草稿最终确认才成为正式响应。它们属于同一个turn_id却不是同一代工作。因此协议至少要有以下关联键session_id会话范围turn_id一轮用户意图的逻辑身份generation_id该轮的一次推理/执行尝试task_idLLM、工具、TTS、播放等子任务tool_call_id、audio_segment_id外部调用与可播放片段身份cancel_epoch或 fencing token用于拒绝旧代迟到结果。一个可落地的事件信封如下。字段名不是标准关键是所有层共享同一组身份和时间锚点。{session_id:s-7f,turn_id:t-18,generation_id:g-18.2,cancel_epoch:4,event_id:e-991,parent_event_id:e-977,kind:tool.result,phase:speculative|committed|revoked|completed,transcript_revision:12,provider:internal-or-vendor,provider_event:raw-event-name,seq:1842,ts_mono_ns:4829910020031,ts_wall_utc:2026-07-23T20:15:31.842Z,payload:{}}generation_id的核心作用是 fencing而不只是日志关联。任何异步结果进入 reducer、会话历史、设备状态或播放队列之前都必须验证“这个 generation 是否仍是该 turn 的有效持有者且当前 phase 是否允许这种提交”验证失败的结果进入stale_result_dropped审计流不得靠调用方“尽量别返回”来保证安全。Barge-in 到来时正确顺序是先在本地原子撤销旧 generation 的提交资格再向各下游发送取消。伪代码可简化为on_interrupt(new_user_audio): old active_generation atomic: old.phase REVOKED old.cancel_epoch 1 deny_future_commits(old) active_generation open_new_turn_or_generation() fanout_cancel(old) playback_drop(old)这个顺序解决最危险的竞态即使工具结果在fanout_cancel之前或期间到达它也因 fencing 校验失败而不能污染权威状态。反过来若先发网络取消、后标记本地失效迟到结果可能正好穿过窗口。Barge-in 是一组取消不是一个静音按钮完整 Barge-in 至少包含五条链路。第一检测链路确认用户开始说话并区分真实语音、回声、环境噪声和短促非语音。检测事件只负责提出 interrupt不直接证明新一轮已完整。第二运行时撤销旧 generation停止接受其模型 token、工具计划、工具结果和历史写入。对于已经开始的 LLM 请求发送 provider 支持的 cancel但本地必须继续丢弃取消后的 token因为“已发送取消”不等于“远端已经停止”。第三工具链路按副作用等级处理。只读工具可以在撤销后直接丢结果幂等写操作必须携带 idempotency key、turn/generation metadata 和可查询终态不可逆动作不应在 speculative 阶段发出必要时采用 prepare/commit、显式确认或补偿事务。gRPC 的官方取消指南指出库通常不能直接中断应用层 handler服务端需要主动检查取消并停止处理向上游的取消传播在不同语言实现中也不完全相同。10因此客户端的cancel_requested只是意图不是远端停止执行、停止计费或撤销副作用的证明。协议应区分cancel_observed、cancel_completed、cancel_unsupported、too_late和side_effect_committed。第四TTS 生成链路停止继续合成。Deepgram TTS 的Clear清除其服务端内部文本与音频生成缓冲并尽快停止发送新音频块它不代表浏览器、移动端、电话网关或声卡里的已收音频被清除。11第五播放链路立即停当前 source清掉所有属于旧 generation 的排队 chunk并拒绝取消后迟到的音频。Deepgram Voice Agent 的UserStartedSpeaking也明确要求客户端停播并丢弃缓冲但这仍是客户端侧动作。12Web Audio 规范规定AudioScheduledSourceNode.stop()后该 source 输出静音但这只描述本地音频节点不会替你取消远端模型、工具或 TTS。13即使在 WebRTC 层调用removeTrack或replaceTrack(null)规范定义的也是停止媒体发送不会自动传播成 Agent Runtime 的模型或工具取消。14Deepgram 的AgentAudioDone也只表示服务端发完最后一个音频块不表示用户已经听完客户端仍需观察本地输出队列。15供应商在这里有不同责任边界。OpenAI Realtime 的 WebRTC/SIP 连接由服务端掌握输出缓冲可在用户打断时自动截断未播放音频WebSocket 连接则由客户端管理播放客户端必须停播、记录已播放时长并发送conversation.item.truncate删除未听部分。16Twilio 双向 Media Streams 的clear会清空其缓冲音频mark既可能因音频正常播完返回也可能因缓冲被 clear 返回所以应用仍要记录清空原因与 generation 状态不能把收到 mark 一律解释为“用户听完”。17LiveKit 的默认中断路径会停止讲话并把历史截到用户实际听到的部分这是框架语义不是 WebRTC 本身提供的事务保证。7由此可得一个硬规则停止声音、停止生成、停止执行、停止提交是四个独立动作。Barge-in 必须把它们绑定到同一个取消域但不能把任一动作的成功当作其他动作已经成功。状态提交必须以“用户实际听到什么”为准语音 Agent 的会话历史不应只记录“模型生成了什么”而应区分模型生成文本TTS 已生成音频音频已进入播放队列音频实际播放到哪个 sample 或毫秒位置哪一部分因打断被截断。否则下一轮模型会以为用户听过整段旧回答产生“如我刚才所说”之类的错位。OpenAI 的 WebSocket 截断流程要求客户端记录已播放时长正是因为服务端不知道耳端进度。16Deepgram 也明确区分服务端发完音频与用户听完音频。15建议把 assistant 输出拆成generated_content、delivered_prefix和committed_history。模型 token 可以持续写入临时缓冲只有可映射到已播放音频的前缀进入 delivered 记录。发生中断时权威历史提交 heard prefix未听部分标为 revoked。文本与音频无法精确对齐时不要伪造逐字截断保留音频播放游标、原始文本、对齐置信度和截断策略让后续模型知道这是近似历史。工具状态也要分层tool_planned、tool_dispatched、tool_side_effect_committed、tool_result_received、tool_result_applied。撤销后收到结果可以阻止applied却未必能逆转side_effect_committed。把两个状态混成一个tool_done正是开篇竞态无法审计的原因。延迟必须和正确性共用时间锚点端到端延迟不能只测“用户停说到首包音频”。那会奖励提前误判 EOU、提前启动无效 LLM、提前播出随后被截断的回答。Deepgram 将 transcript latency 与 EOT latency 分开并提醒 finalized 结果会混入 endpoint 等待精确 EOT 还需要真实 speech-end 锚点。其文档同时建议看代表性样本上的 P50、P95、P99而不是单次或平均值。18一条可重放时间线至少记录audio.user_speech_start_ground_truth audio.user_speech_end_ground_truth vad.speech_started / vad.speech_stopped eou.candidate_received turn.speculative_started turn.resumed turn.committed llm.request_sent / first_token / cancel_sent / cancel_ack / terminal tool.dispatched / cancel_sent / side_effect_committed / result_received / result_applied tts.request_sent / first_audio_received / clear_sent / cleared playback.enqueued / first_sample / last_sample / queue_cleared / output_silent barge_in.ground_truth_start / detected / generation_revoked stale_result_dropped同一进程的时长用 monotonic clock 计算跨节点关联保留 UTC wall clock、时钟同步状态和单调seq。不要直接拿供应商词时间戳当毫秒级墙钟Deepgram 明确说明其 transcriptstart、duration不保证适合精确延迟测量。18其 Voice Agent 可观测性指南也建议给每个收发帧加自有时间戳和序列号并保存 function call、barge-in 与 latency 事件。19核心指标应成组发布EOU commit latencyturn.committed - speech_end_ground_truthresponse-to-earplayback.first_sample - speech_end_ground_truthbarge detection latencybarge_in.detected - barge_in.ground_truth_startresidual playbackplayback.output_silent - barge_in.ground_truth_start并另报从 detection 起算的系统处置时长cancel propagation从 generation revoked 到 LLM、tool、TTS、playback 各自 terminal/ackfalse truncation rate标注仍属同一用户轮却已提交或开始不可逆执行的比例false extension rate标注已结束但提交超过产品自定 SLO 的比例speculative waste被撤销的模型请求、token、TTS 音频和未播放字节stale tool executiongeneration 撤销后仍开始或完成副作用的次数stale result drop迟到结果被 fencing 拒绝的次数。P50 说明常态P95/P99 暴露抖动、排队、网络与重复 endpointing误截断、误延长、残留播放和 stale tool execution 则约束“快但错”。这些指标必须按语言、噪声、回声、设备、网络、句式和打断位置分层不能用一个平均延迟掩盖尾部竞态。可复现测试矩阵测试不需要虚构“线上二百轮”。更可靠的做法是用双轨音频语料、确定性 fake service 和故障注入构造可重复实验一轨是用户近讲另一轨是 Agent 扬声器回灌语料带人工标注的 speech start、speech end、意图边界和 barge-in 起点。Fake LLM、tool、TTS 可配置首包延迟、取消是否协作、结果乱序、重复投递和“副作用已提交后才收到取消”。网络层注入 jitter、丢包、重连与跨通道乱序。场景注入方式必须成立的协议断言主要观测句中自然停顿后续说在一个意图中插入不同长度静音候选 EOU 可启动草稿但不得提前提交不可逆工具续说后旧 generation 被撤销false truncation、speculative waste真正结束但有背景噪声叠加音乐、敲击或电话铃声VAD、UtteranceEnd、语义 EOU 的原始事件分别留存最终只提交一次EOU P50/P95/P99、false extensionLLM 生成中打断首 token 前后分别触发 barge-in本地先 revoke取消后 token 全部被丢弃新轮不继承未听旧文本cancel propagation、stale token drop工具请求在途时打断工具设置可协作取消、不可取消、too-late 三种模式迟到结果不得 apply副作用终态可审计重复请求由 idempotency key 抑制stale execution、side-effect committedTTS 已生成但未播放延长客户端队列TTS clear 与本地 queue clear 都执行旧 generation 后续音频不得入队未播放字节、queue clear latency正在播放时打断在不同播放游标触发output 进入静音历史只提交 heard prefix旧 chunk 全部失效residual playback、截断游标回声造成假打断提高扬声器回灌并切换 AEC不得无条件撤销检测置信度和恢复路径可追踪false barge-in、恢复延迟取消与结果乱序让 result 先于或后于 cancel ack 到达fencing 决定能否提交消息到达顺序不得改变权威结果stale result drop、终态唯一性断线重连与重复投递重放事件、复用 tool_call_id每个 turn/generation 只有一个终态副作用不重复duplicate suppression、审计完整率除场景断言外还应做六条全局不变量测试被撤销 generation 的结果永不进入权威历史不可逆工具默认不得在 speculative phase 发出旧 generation 音频在 queue clear 后不能再次入队每个 generation 只有一个终态每次副作用都有幂等键和可查询终态用户历史与实际听到的前缀一致。只要其中一条不成立低延迟数据就不具备发布价值。结论先定义失效再谈快实时语音系统的关键单位不是 ASR 请求、LLM 请求或一段 TTS 音频而是带身份、阶段和提交权的一代 turn execution。EOU 提出或确认一次提交Barge-in 撤销旧代提交权LLM、工具、TTS 和播放器通过同一个 generation fencing 决定结果是否仍可进入系统可观测性用同一组时间锚点证明撤销是否及时、尾部是否稳定、旧结果是否被隔离。没有这层 Turn Protocol团队会得到一组彼此“优化成功”的局部指标ASR 更早 finalLLM 更早发请求TTS 更早出首包播放器更快静音。与此同时误截断增加、无效调用增加、旧工具继续执行、历史记录与用户耳中内容分叉。协议的作用不是让系统变慢而是把推测、提交、撤销和交付变成可验证的状态变化。只有在旧工作能够被可靠失效之后提前计算才是真正的低延迟而不是更快地产生竞态。FAQVAD 检测到静音后能不能直接调用工具不建议。VAD 只证明声学活动变化应先进入可撤销候选阶段有副作用工具需要确认提交和代际校验。收到取消请求是否代表远端任务已经停止不代表。取消可能只是表达不再需要结果远端 handler 仍可能执行因此还需要 fencing 和提交前有效性检查。低延迟最重要的单一指标是什么没有单一指标。结束延迟必须与误截断、误延长、打断静音和陈旧结果一起观察。参考资料错误速查卡症状根因定位修复用户听不到旧回答但旧工具已修改生产Barge-in 只停了扬声器工具结果仍按原 generation apply 写历史检查 generation phase 是否被原子置为 REVOKED、cancel_epoch 是否单调递增先原子撤销提交权 → fanout_cancel → playback_dropfencing 校验失败必须进 stale_result_dropped误把用户说完了提交给 LLM续说后整段失效EOU 候选事件被当作提交信号缺少 SPECULATIVE/COMMITTED 边界检查 EOU commit 是否校验 turn_id open transcript_revision 未被替换把 user turn 建模为 OPEN/SPECULATIVE/COMMITTED/REVOKED 状态机工具重复执行同一条写操作不可逆操作在 speculative 阶段发出没有 idempotency key检查 tool_call_id idempotency_key 是否唯一并由 generation 持有不可逆动作必须 prepare/commit带幂等键可查询终态旧 generation 的 token 写入对话历史“已发送取消被当作远端已停止”本地继续接 token检查 cancel_observed/cancel_completed/cancel_unsupported 三个事件是否区分协议应区分 cancel_observed、cancel_completed、cancel_unsupported、too_late、side_effect_committed下一轮模型引用如我刚才所说但用户没听到历史只记录了 generated_content没有 delivered_prefix检查 committed_history 是否只含 heard prefix拆 generated_content / delivered_prefix / committed_history中断时只提交 heard prefixP95 偏高但 P50 正常被误归因为网络慢重复 endpointingSTT 内部 min_delay 运行时 EOU 等待拆解 latency供应商检测等待 网络传输 运行时二次等待 提交排队取消运行时 min_delay 或允许运行时基于 STT end-of-speech 直接提交同一段 LLM 输出被记录两次取消后迟到 token 仍进入 reducer检查 generation_id cancel_epoch 校验reducer 入口做 fencing迟到结果进 stale_result_dropped 审计流Twilio mark 一律被解释为用户听完mark 既可能因音频正常播完返回也可能因 clear 返回检查 mark 事件与 clear 事件是否同时记录及原因字段应用必须记录 mark 触发原因normal_end / cleared与 generation 状态Web Audio stop() 后以为远端也停了AudioScheduledSourceNode.stop()只控制本地音频节点检查本地 source 是否停了但远端 LLM/TTS 未撤销同步撤销 Playback / Gateway / TTS / LLM-Tool / History 五条链路WebRTC removeTrack 以为能取消模型/工具规范定义的 removeTrack/replaceTrack 只停止媒体发送检查 RtpSender.setTrack(null) 与 Agent Runtime 取消是否独立Agent Runtime 必须独立撤销 generation不能依赖 WebRTC track 状态传播cancel_requested当作远端已停止客户端意图被当作远端执行保证检查远端是否到达 cancel_observed / cancel_completed协议必须区分 cancel_requested意图与 cancel_observed远端观察和 cancel_completed已停止VAD 检测到静音就直接调用工具VAD 误当作整轮结束检查 VAD 与 EOU commit 之间是否有 SPECULATIVE 阶段VAD 只能进 SPECULATIVECOMMIT 必须在 turn.committed 之后is_finaltrue当作整轮完成文档已说明is_final是片段级而非 utterance 级检查 speech_final 与 is_final 是否同时使用累计 finalized 片段 完整结束信号才提交区分 is_final / speech_final / EndOfTurnUtteranceEnd OR speech_final 当作更高置信度两者语义不同轴OR 后丢失原始信息检查 OR 之前是否分别记录了原始事件与 confidence保留 provider / provider_event / confidence不私自 OR取消前已做的副作用无法回滚却被承诺已撤销gRPC 官方明确Changes made before a cancellation are not rolled back检查 side_effect_committed 事件是否与 cancel 分离记录协议必须区分 too_late 与 side_effect_committed幂等键 可查询终态是底线回声造成假打断旧 generation 被错误撤销AEC 未开或置信度低时仍按 interrupt 撤销检查 interrupt 事件是否带 AEC 置信度不无条件撤销保留检测置信度与恢复路径可回滚eot_threshold调得很低但 false start 暴增eager 阈值降低会多 50-70% LLM 调用false starts 也增加检查 speculative_waste / false_truncation 是否同步上升把 eot_threshold 调整到 P95 EOT latency 与 false_truncation 联合最优点同一 turn 多次 candidate 提交互相污染只有 turn_id 没有 generation_id检查事件信封是否带 generation_id cancel_epoch五键事件信封session_id / turn_id / generation_id / task_id / cancel_epochOpenAI WebSocket 模式没发 conversation.item.truncate客户端以为 WebSocket 也会自动截断检查 sessionManager 是否发送 truncateWebSocket 模式客户端必须记录已播放时长并显式发送 conversation.item.truncate网络 jitter 大时取消乱序导致权威状态被旧 generation 覆盖先发网络 cancel后置本地 REVOKED检查 on_interrupt 的原子顺序顺序必须为原子置 REVOKED cancel_epoch → fanout_cancel → playback_drop取消与 LLM token 到达 race 导致 fence 失效reducer 在 fence 校验前已写历史检查 reducer 入口的 generation_id cancel_epoch 校验顺序所有异步结果必须先过 fencing 才进 reducer / 历史 / 设备状态Endpointing官方或第一方资料访问日期 2026-07-23。 ↩︎ ↩︎End of Speech Detection While Live Streaming官方或第一方资料访问日期 2026-07-23。 ↩︎Configure Endpointing and Interim Results官方或第一方资料访问日期 2026-07-23。 ↩︎Voice Activity Detection (VAD)官方或第一方资料访问日期 2026-07-23。 ↩︎Configure the Voice Agent官方或第一方资料访问日期 2026-07-23。 ↩︎Understanding the Flux State Machine官方或第一方资料访问日期 2026-07-23。 ↩︎ ↩︎LiveKit Turns Overview官方或第一方资料访问日期 2026-07-23。 ↩︎ ↩︎Optimize Voice Agent Latency with Eager End of Turn官方或第一方资料访问日期 2026-07-23。 ↩︎LiveKit Turn Detector官方或第一方资料访问日期 2026-07-23。 ↩︎gRPC Cancellation官方或第一方资料访问日期 2026-07-23。 ↩︎TTS WebSocket Clear官方或第一方资料访问日期 2026-07-23。 ↩︎User Started Speaking官方或第一方资料访问日期 2026-07-23。 ↩︎Web Audio API 1.1官方或第一方资料访问日期 2026-07-23。 ↩︎WebRTC: Real-Time Communication in Browsers官方或第一方资料访问日期 2026-07-23。 ↩︎Agent Audio Done官方或第一方资料访问日期 2026-07-23。 ↩︎ ↩︎Realtime Conversations: Interruption and Truncation官方或第一方资料访问日期 2026-07-23。 ↩︎ ↩︎Twilio Media Streams: WebSocket Messages官方或第一方资料访问日期 2026-07-23。 ↩︎Measuring STT Latency官方或第一方资料访问日期 2026-07-23。 ↩︎ ↩︎Session Observability官方或第一方资料访问日期 2026-07-23。 ↩︎