openai-agents-python 语音管道深度解析:VoicePipeline 如何将 Agent 工作流变为语音应用
在 openai-agents-python 中,VoicePipeline(位于 src/agents/voice/pipeline.py)是把 Agent 型工作流转换为语音应用的核心入口:你只需传入一段要执行的工作流,管道就会自动完成输入语音的转录(STT)、在合适的时机调用你的工作流、并把工作流产出的文本重新合成为语音(TTS)。读完本文,你将掌握管道三大配置项(workflow / STT 与 TTS 模型 / config)的完整参数、两种音频输入方式(AudioInput 与 StreamedAudioInput)的适用场景、StreamedAudioResult 事件流的处理与错误语义,以及基于生命周期事件实现“打断/静音”等最佳实践。
语音管道的三步架构
VoicePipeline 的类文档把它描述为一个“有观点(opinionated)”的三步流程,源码注释与 docs/voice/pipeline.md 的架构图完全一致:
- 将音频输入转录为文本(speech-to-text);
- 运行你提供的
workflow,产生一段文本响应序列; - 将文本响应转换为流式音频输出。
graph LR
%% Input
A["🎤 Audio Input"]
%% Voice Pipeline
subgraph Voice_Pipeline [Voice Pipeline]
direction TB
B["Transcribe (speech-to-text)"]
C["Your Code"]:::highlight
D["Text-to-speech"]
B --> C --> D
end
%% Output
E["🎧 Audio Output"]
%% Flow
A --> Voice_Pipeline
Voice_Pipeline --> E
%% Custom styling
classDef highlight fill:#ffcc66,stroke:#333,stroke-width:1px,font-weight:700;
其中高亮的 “Your Code” 就是工作流(workflow),它是整个管道中唯一由开发者掌控的环节。官方文档 docs/voice/pipeline.md 与 docs/voice/quickstart.md 对这三步有相同表述,可结合快速上手文档中的完整可运行示例(麦克风 I/O 使用第三方库 sounddevice,通过 pip install 'openai-agents[voice]' 安装可选语音依赖)一起理解。
管道配置:workflow、模型与 VoicePipelineConfig
创建 VoicePipeline 时(见 src/agents/voice/pipeline.py)可设置三类内容,全部为关键字参数:
workflow(必填):类型为VoiceWorkflowBase。它是“每次新音频被转录出来时运行的代码”。VoiceWorkflowBase是抽象基类,核心契约只有一个抽象方法run(transcription: str) -> AsyncIterator[str]:接收一段转写文本,产出一段段将被 TTS 朗读的文本。大多数场景下你可以直接使用现成的SingleAgentVoiceWorkflow(见下文),或者继承VoiceWorkflowBase编写多 Agent、自定义消息历史等复杂逻辑。stt_model/tts_model:语音转文本与文本转语音模型,类型是STTModel | str | None与TTSModel | str | None。传字符串时,管道在首次需要模型时通过config.model_provider按名称解析(见 pipeline.py 中的_get_tts_model/_get_stt_model惰性解析逻辑);不传时使用 OpenAI 默认模型——从 openai_model_provider.py 可确认默认值为gpt-4o-transcribe(STT)和gpt-4o-mini-tts(TTS)。config:VoicePipelineConfig | dict[str, Any] | None。注意源码会用coerce_dataclass_config允许直接传字典,缺省时构造默认配置。pipeline_config.py 中定义的完整字段如下:
| 字段 | 类型 / 默认值 | 说明 |
|---|---|---|
model_provider |
VoiceModelProvider,默认 OpenAIVoiceModelProvider() |
模型提供者,负责把模型名映射为 STT/TTS 模型实例 |
tracing_disabled |
bool,默认 False |
是否禁用管道的追踪(tracing) |
tracing |
TracingConfig | None,默认 None |
本管道的追踪配置 |
trace_include_sensitive_data |
bool,默认 True |
追踪中是否包含敏感数据(仅针对管道本身,不含工作流内部) |
trace_include_sensitive_audio_data |
bool,默认 True |
追踪中是否上传音频数据(对应“是否上传音频文件”) |
workflow_name |
str,默认 "Voice Agent" |
追踪中使用的名称 |
group_id |
str,默认随机生成 |
用于把同一会话/进程的多个 trace 关联起来的分组 ID |
trace_metadata |
dict[str, Any] | None |
附加到 trace 的元数据 |
stt_settings |
STTModelSettings |
STT 模型设置 |
tts_settings |
TTSModelSettings |
TTS 模型设置 |
其中 stt_settings / tts_settings 两个子配置同样支持传字典(__post_init__ 中做了类型强制转换)。从 model.py 可以看到其字段:
STTModelSettings:prompt(给模型的指令)、language(音频语言)、temperature、turn_detection(流式输入时的“轮次/活动检测”设置,见下文)、languages(gpt-transcribe/gpt-live-transcribe支持的多语言候选,优先于language)、keywords(引导转写的关键词)。TTSModelSettings:voice(内置音色如alloy、nova等或自定义音色 ID)、buffer_size(流式输出的最小音频块数,默认 120)、dtype(输出音频的 numpy 数据类型,默认np.int16,也可为np.float32)、transform_data(对音频数组的自定义变换函数)、instructions(控制语气的 TTS 指令,默认提示“你收到的是不完整句子,不要补全,照读即可”)、text_splitter(把累积文本切句的函数,默认基于句子边界切分,避免等整段文本才送 TTS)、speed(0.25~4.0)。
运行管道:两种音频输入形式
调用 await pipeline.run(audio_input) 即可运行(pipeline.py)。run() 内部按输入类型分派:AudioInput 走 _run_single_turn,StreamedAudioInput 走 _run_multi_turn;传入其他类型会抛出 UserError。
AudioInput:完整音频,单次结果
AudioInput 用于“已经拥有完整音频输入、只需产出一个结果”的场景——无需检测说话者何时结束,例如预录音频,或用户按完按钮即表示说尽的“按住说话(push-to-talk)”应用。它是一个 dataclass:
buffer:numpy 数组,必须为int16或float32;frame_rate:采样率,默认 24000(模块级常量DEFAULT_SAMPLE_RATE);sample_width:默认 2 字节;channels:默认 1 声道。
它还提供 to_audio_file()(内部把 PCM 数据封装为 WAV 字节流,供 STT 上传)与 to_base64() 方法。quickstart 文档中的最小用法就是:
import numpy as np
from agents.voice import AudioInput
# 实际中来自麦克风;示例用 3 秒静音
buffer = np.zeros(24000 * 3, dtype=np.int16)
audio_input = AudioInput(buffer=buffer)
StreamedAudioInput:流式音频与活动检测
StreamedAudioInput 用于需要检测“用户何时说完了”的场景。它内部是一个 asyncio.Queue,应用侧通过 add_audio(audio) 把检测到的音频块(int16/float32 numpy 数组)依次推入队列;传入 None 表示流结束。
管道会基于“活动检测(activity detection)”在合适的时机自动运行工作流。从源码看,其落地方式是:_run_multi_turn 调用 stt_model.create_session(...) 创建一个 StreamedTranscriptionSession(定义于 model.py),然后 async for input_text in transcription_session.transcribe_turns() 逐轮产出转写文本——每一轮文本都会触发一次 workflow.run(input_text)。检测参数正是上文 STTModelSettings.turn_detection;在 OpenAI 实现中,缺省时会套用 DEFAULT_TURN_DETECTION(见 openai_stt.py),该设置会随 websocket 连接参数下发给转写服务。仓库中的 examples/voice/streamed 提供了基于麦克风的完整流式示例(含 my_workflow.py 自定义工作流)。
从源码结构还可以看到一个细节:多轮流程在开始监听之前会先调用工作流可选的 on_start() 钩子(workflow.py),把开场白文本送进 TTS 输出,并在有产出时立即 _turn_done() 收尾——注释解释了原因:让开场白在启动阶段就结束一个 turn,否则它会一直挂着等待句子终结标点,或错误地与第一个用户 turn 合并。
结果与事件流:StreamedAudioResult 及三类事件
run() 的返回值是 StreamedAudioResult。它是一个“事件随产生即流式推出”的对象,通过 async for event in result.stream() 消费。事件类型定义在 events.py:
VoiceStreamEventAudio(type == "voice_stream_event_audio"):包含一块音频数据(data,numpyint16/float32数组,格式由tts_settings.dtype决定),直接写入播放设备即可;VoiceStreamEventLifecycle(type == "voice_stream_event_lifecycle"):生命周期事件,event字段取值turn_started/turn_ended/session_ended(源码比文档多列出的session_ended表示整个会话结束,见 events.py);VoiceStreamEventError(type == "voice_stream_event_error"):错误事件,携带error异常对象。
文档给出的标准消费模式:
result = await pipeline.run(input)
async for event in result.stream():
if event.type == "voice_stream_event_audio":
# 播放音频
pass
elif event.type == "voice_stream_event_lifecycle":
# 生命周期事件
pass
elif event.type == "voice_stream_event_error":
# 错误
pass
终态错误的语义(重点)
pipeline 文档对错误传播有明确约定,且与 result.py 的实现一一对应:
- 管道的终态错误在应用消费
stream()期间送出/抛出,而不是在run()返回时; - 如果整体运行正常、但 STT 转写会话最终关闭失败,流不会无限期等待,而是把该 close 错误作为错误事件送出——对应
_run_multi_turn中finally块对transcription_session.close()的asyncio.shield包裹与结果检查(pipeline.py); - 如果该 turn 本身已失败,而关闭转写会话又失败,则流保留原始 turn 错误作为主要错误(
primary_exception机制,close 失败只记日志、不覆盖主错误)。
另外从 result.py 的 stream() 实现可以看到更多工程细节:收到 VoiceStreamEventError 或 session_ended 后停止消费并检查内部任务异常;正常退出时还会等待生产者任务收尾,以便活动中的 trace 上下文能先发出 trace_end 再清理;被取消时 _cancel() 会保证终态事件仍然按序送达。
底层工作原理:从文本到 PCM 的流式链路
结合 result.py 源码,管道内部的音频生产链路可以概括为:
_add_text(text):工作流每产出一段文本就累积到_text_buffer,同时用tts_settings.text_splitter按句子边界切出“已完整”的句子;每切出一句就创建一个本地队列 + 一个 TTS 音频任务(_create_audio_task),做到句子一完整就开始合成,而不必等整段回复;_stream_audio(...):每个句子独立调用tts_model.run(text, settings)获取 PCM 字节流,按buffer_size(默认 120 块)攒够后转成 numpy 数组(int16,或按配置转为 float32 并除以 32767),经可选的transform_data变换后入队;_dispatch_audio():一个顺序分发器,按句子入队顺序把各段音频块搬运到最终事件队列,保证输出顺序正确;_turn_done():turn 结束时把剩余未切句的尾巴文本作为最后一段送去合成,并触发turn_ended生命周期事件;_done()则标记会话完成并最终发出session_ended。
追踪方面,_run_single_turn / _run_multi_turn 都用 TraceCtxManager 维持整个处理期的 trace 作用域(携带 workflow_name、group_id、trace_metadata),而 speech_group_span / speech_span 会为每个 turn 与每次 TTS 调用记录输入文本、音色参数、首字节时间(first_content_at)等信息;trace_include_sensitive_data / trace_include_sensitive_audio_data 控制这些敏感内容是否真正写入 span(关闭音频追踪时甚至不会在内存中保留整段 PCM,见 result.py 注释)。
最佳实践:处理打断(Interruptions)
官方文档给出的明确结论是:Agents SDK 目前不为 StreamedAudioInput 提供内置打断处理——每个检测出的 turn 都会独立触发一次工作流运行。若要在应用层实现“用户插话时打断模型播报”,推荐做法是监听 VoiceStreamEventLifecycle 事件:
turn_started:表示新一轮已转写完成、开始处理(在 result.py 的_start_turn()中发出,每个 turn 仅一次);turn_ended:表示该 turn 关联的所有音频已分发完毕。
典型策略:在收到 turn_started 时静音扬声器/麦克风(或停止播放上一轮残留音频),在本应用播完该 turn 的全部音频(收到对应 turn_ended 之后)再解除静音。这套事件驱动的机制与 examples/voice/streamed 中的实时麦克风示例可以配合使用,实现自然的对话式打断体验。
小结与延伸
VoicePipeline 的设计哲学是把 STT、活动检测、TTS 的复杂时序封装成“输入音频、消费事件”的极简接口,而把智能逻辑完全留给工作流:
- 简单单 Agent 场景:
VoicePipeline(workflow=SingleAgentVoiceWorkflow(agent)),SingleAgentVoiceWorkflow会自动维护输入历史、跟随 handoff 更新last_agent(workflow.py),并支持传入context与回调; - 复杂场景:继承
VoiceWorkflowBase自行实现run(),内部可多次调用Runner.run_streamed(),并用VoiceWorkflowHelper.stream_text_from(result)从流事件中提取文本增量; - 实战参考:静态音频示例见 examples/voice/static,流式麦克风示例见 examples/voice/streamed,两者均为可直接运行的完整程序;
- 更多 API 细节可查阅 docs/voice/ 下的 quickstart、tracing 等文档,以及 docs/ref/ 中 voice 模块的自动生成的参考页。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K637- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python310
cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端TypeScript2 K146
hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程Python46467
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go20043
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java33951