语音 Agent:Pipecat 与 LiveKit 本节摘要:语音 Agent 不是「文本循环外挂个 TTS」那么简单。它的延迟预算极其残酷——生产目标端到端 450600ms,超过 1500ms 用户就觉得「卡坏了」;部分音频(partial audio)是默认状态而非异常(用户边说边出结果);轮次检测本身是个模型(怎么判断用户说完了?);传输从电话 SIP 到 WebRTC 五花八门。
本节摘要:语音 Agent 不是「文本循环外挂个 TTS」那么简单。它的延迟预算极其残酷——生产目标端到端 450~600ms,超过 1500ms 用户就觉得「卡坏了」;部分音频(partial audio)是默认状态而非异常(用户边说边出结果);轮次检测本身是个模型(怎么判断用户说完了?);传输从电话 SIP 到 WebRTC 五花八门。两条路:Pipecat(pipecat-ai/pipecat)给你一个 Python 的、基于帧(frame)的流水线框架——
Frame流经FrameProcessor链,分两个方向:DOWNSTREAM(源→汇,音频进、TTS 出)与 UPSTREAM(反馈与控制:取消、指标、打断 barge-in),典型五段是 VAD(Silero 语音活动检测)→ STT(语音转文字)→ LLM(上下文在用户/助手间交替)→ TTS(文字转语音)→ transport;LiveKit Agents(livekit/agents)把 AI 模型经 WebRTC 桥接给用户,两个语音 Agent 类——MultimodalAgent(OpenAI Realtime 式直连音频)、VoicePipelineAgent(STT→LLM→TTS 级联、给你文本级控制),带语义轮次检测与原生 MCP 集成。商业平台 Vapi 与 Retell 在其上构建,优化后的高端栈能压到 450600ms。本节吃透两套架构、2026 年各环节典型延迟,并用标准库实现一个带 VAD→STT→LLM→TTS→传输五段、含 UPSTREAM 取消帧演示打断的帧式流水线。
对应原课程:Phase 14 · Lesson 22 ·
voice-agents-pipecat-livekit(原英文phases/14-agent-engineering/22-voice-agents-pipecat-livekit/docs/en.md)。前置:第 01 节(Agent 循环)、第 12 节(工作流模式)。
阅读完本节,你应当能够:
文本 Agent 的延迟预算是「秒级」——用户能等。语音 Agent 的延迟预算是「亚秒级」——超过 600ms 用户就觉得迟钝,超过 1.5 秒对话就崩了。这彻底改变了架构:
Frame(帧)→ FrameProcessor(帧处理器)链。PipelineTask 管理生命周期,带事件(on_pipeline_started、on_pipeline_finished、on_idle_timeout)与指标/追踪/RTVI 观察者。典型流水线:
VAD(Silero) → STT → LLM(上下文在用户/助手间交替)→ TTS → transport
传输:Daily、LiveKit、SmallWebRTCTransport、FastAPI WebSocket、WhatsApp。Pipecat Flows 加结构化对话(状态机);Pipecat Cloud 是托管运行时。
Agent、AgentSession、entrypoint、AgentServer。Vapi(优化后的高端栈约 450~600ms)与 Retell(180 次测试电话端到端约 600ms)在其上构建。当你想要托管语音栈、又不想养一个 WebRTC 团队时,选平台。
端到端 450600ms 是高端;8001200ms 常见;超过 1500ms 感觉坏了。每个组件加 50~200ms,上线前先把链路加一遍。
⚠️ 四种失败模式:① 无打断处理——用户插话,Agent 还在说;Pipecat 需 UPSTREAM 取消帧,LiveKit 需等价物。② 忽略 STT 置信度——低置信转录当真理喂给 LLM;应按置信度门控或请求确认。③ TTS 句中截断——流水线在话说到一半取消时,TTS 要知道、或切断音频,否则出杂音。④ 忽视延迟预算——每个组件加 50~200ms,不先加一遍链路就上线,常超 1.5 秒。
原课程 code/main.py 是一个帧式玩具流水线:
Frame 类型(音频、转录、文本、tts_audio、控制)。Processor 接口,带 process(frame)。@dataclass class Frame: kind: str # audio / transcript / text / tts_audio / control payload: Any confidence: float = 1.0 class Processor: def process(self, frame: Frame): ... downstream: "Processor" = None upstream: "Processor" = None
class VAD(Processor): # 语音活动检测 def process(self, f): if f.kind == "audio" and self.is_speech(f.payload): self.downstream.process(Frame("audio", f.payload)) class STT(Processor): # 语音转文字(流式部分转录) def process(self, f): text, conf = self.transcribe(f.payload) self.downstream.process(Frame("transcript", text, conf)) class LLM(Processor): # 上下文交替,首 token 流式 def process(self, f): if f.kind == "transcript": if f.confidence < 0.6: # 置信度门控 self.downstream.process(Frame("text", "能再说一遍吗?")); return for tok in self.generate(f.payload): self.downstream.process(Frame("text", tok)) class TTS(Processor): # 文字转语音,首音频流式 playing = True def process(self, f): if not self.playing: return # 被 UPSTREAM 取消 audio = self.synthesize(f.payload) self.downstream.process(Frame("tts_audio", audio))
def barge_in(pipeline): cancel = Frame("control", {"cmd": "cancel"}) # 用户插话 # 反向传播:TTS 停止、LLM 中断生成 pipeline.tts.playing = False pipeline.llm.stop()
运行 python3 code/main.py 会展示正常流,以及一次打断取消在 TTS 句中停止的轨迹。
💡 设计要点:语音 Agent 的两个流向同等重要——DOWNSTREAM 产出语音,UPSTREAM 让用户能打断。没有 UPSTREAM 的取消路径,Agent 就是个「自说自话不听人」的设备,体验直接崩塌。这与第 01 节的 Agent 循环同源,只是循环的「观察」变成了流式音频帧。
| 方案 | 形态 | 适合 | 延迟 |
|---|---|---|---|
| Pipecat | 帧式流水线(自建) | 要全控制、Python 优先、可插拔提供者 | 由你优化 |
| LiveKit Agents | WebRTC 平台 | WebRTC 优先部署、电话 | 由架构定 |
| Vapi / Retell | 托管语音平台 | 不要 WebRTC 团队、要托管 | 450~600ms |
| OpenAI Realtime / Gemini Live | 直连音频(Multimodal) | 音频进音频出、不要文本中间层 | 最低(省 STT+TTS) |
原课程 outputs/skill-voice-pipeline.md:脚手架一个 Pipecat 形状的语音流水线——VAD + STT + LLM + TTS + transport,带打断处理、置信度门控、指标观察者。
Frame→FrameProcessor 链,DOWNSTREAM(源→汇,音频进 TTS 出)+ UPSTREAM(控制:取消/指标/打断)。下一节,我们进入 Agent 工程的可观测性地基——OpenTelemetry GenAI 语义约定,看如何用统一的标准 span 形状,让 LLM 调用、工具调用、Agent 跨进程的追踪能汇成一条可查询的轨迹。