Hugging Face 推出的模块化语音 agent 架构,VAD/STT/LLM/TTS 每层可替换,支持 OpenAI Realtime API。对语音交互应用开发有重大价值。
一个低延迟、完全模块化的语音AI智能体管道:VAD -> STT -> LLM -> TTS,通过 OpenAI Realtime 兼容的 WebSocket API 暴露。每个组件都可互换。LLM 插槽支持 OpenAI 兼容协议,因此您可以将其指向托管提供商、HF Inference Providers,或在自己的硬件上运行 vLLM 或 llama.cpp 服务器,实现完全本地、完全开源的堆栈。
该管道在生产环境中作为数千个 Reachy Mini 机器人的对话后端运行。
pip install speech-to-speech
export OPENAI_API_KEY=...
speech-to-speech
这启动了一个 OpenAI Realtime 兼容的服务器,监听 ws://localhost:8765/v1/realtime,使用 Parakeet TDT 进行本地 STT,使用 OpenAI 兼容的 LLM,以及 Qwen3-TTS 进行本地语音输出。
从源代码检出,在第二个终端中与其通信:
python scripts/listen_and_play_realtime.py --host 127.0.0.1 --port 8765
想将 LLM 保持在自己的机器上?使用 llama.cpp 部署 Gemma 4:
llama-server -hf ggml-org/gemma-4-E4B-it-GGUF -np 2 -c 65536 -fa on --swa-full
然后将 OpenAI 兼容的 LLM 后端指向它:
speech-to-speech \
--model_name "ggml-org/gemma-4-E4B-it-GGUF" \
--responses_api_base_url "http://127.0.0.1:8080/v1" \
--responses_api_api_key ""
任何 OpenAI Realtime 兼容的客户端都可以连接。有关协议的详细信息,请参阅 Realtime API;有关提供商和本地服务器选项,请参阅 LLM 后端。
管道是一个四个组件的级联,每个组件在自己的线程中运行,通过队列相互连接:
语音活动检测 (VAD):Silero VAD v5 检测语音边界和轮流说话。
语音转文本 (STT):转录用户的发言,支持可选的实时部分转录。
语言模型 (LLM):生成响应,流式传输文本和工具调用。
文本转语音 (TTS):合成音频并将其流式传输回客户端。
每个阶段都有多个可互换的后端,通过 CLI 标志选择。代码设计易于修改,重点关注通过 Transformers 和 Hugging Face Hub 可用的模型。
需要 Python 3.10+。
pip install speech-to-speech
默认安装涵盖标准实时路径:
macOS 和非 macOS 的依赖项通过 pyproject.toml 中的平台标记自动解析。
在 Linux 上,Qwen3-TTS GGML 后端来自 faster-qwen3-tts[ggml]。其在 PyPI 上的默认 qwentts-cpp-python wheel 针对 CUDA 12.8。如果您的机器没有该 wheel 所需的 CUDA 12 运行时,请在安装 speech-to-speech 之前从 Hugging Face wheelhouse 安装匹配的 wheel:
# CUDA 13.x
pip install "qwentts-cpp-python==0.3.1+cu130" \
-f https://huggingface.co/datasets/andito/qwentts-cpp-python-wheels/tree/main/whl/cu130
# CUDA 12.4
pip install "qwentts-cpp-python==0.3.1+cu124" \
-f https://huggingface.co/datasets/andito/qwentts-cpp-python-wheels/tree/main/whl/cu124
# CPU-only fallback
pip install "qwentts-cpp-python==0.3.1+cpu" \
-f https://huggingface.co/datasets/andito/qwentts-cpp-python-wheels/tree/main/whl/cpu
pip install speech-to-speech
要使用之前的 CUDA-graphs 实现而不是 GGML,请传递 --qwen3_tts_backend torch。
额外的后端通过 pip extras 安装:
pip install "speech-to-speech[kokoro]" # 非 macOS 上的 Kokoro-82M TTS
pip install "speech-to-speech[pocket]" # Pocket TTS
pip install "speech-to-speech[chattts]" # ChatTTS
pip install "speech-to-speech[facebook-mms]" # MMS TTS
pip install "speech-to-speech[faster-whisper]" # Faster Whisper STT
pip install "speech-to-speech[whisper-mlx]" # macOS 上的 Lightning Whisper MLX STT
pip install "speech-to-speech[paraformer]" # 通过 FunASR 的 Paraformer STT
pip install "speech-to-speech[mlx-lm]" # macOS 上对视觉模型的 mlx-vlm 支持
已弃用的实现(包括 MeloTTS)位于 archive/ 中,不再连接到 CLI。
关于 DeepFilterNet 的说明:DeepFilterNet(用于 VAD 中的可选音频增强)需要 numpy<2 并与需要 numpy>=2 的 Pocket TTS 冲突。仅在不使用 Pocket TTS 的环境中手动安装。
git clone https://github.com/huggingface/speech-to-speech.git
cd speech-to-speech
uv sync
这在可编辑模式下安装包并使 speech-to-speech CLI 可用。
使用 --stt、--llm_backend 和 --tts 选择实现。运行 speech-to-speech -h 查看确切的值和后端特定的标志。
export OPENAI_API_KEY=...
speech-to-speech
这等同于:
speech-to-speech \
--thresh 0.6 \
--stt parakeet-tdt \
--llm_backend responses-api \
--tts qwen3 \
--qwen3_tts_model_name Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice \
--qwen3_tts_speaker Aiden \
--qwen3_tts_language auto \
--qwen3_tts_backend ggml \
--qwen3_tts_non_streaming_mode True \
--qwen3_tts_mlx_quantization 6bit \
--model_name gpt-5.4-mini \
--chat_size 30 \
--responses_api_stream \
--enable_live_transcription \
--mode realtime
默认模型是通过 OpenAI Responses API 的 gpt-5.4-mini。用 --model_name 覆盖它,用 --responses_api_base_url 设置另一个 OpenAI 兼容提供商或服务器。
speech-to-speech --local_mac_optimal_settings
可选地与特定的 LLM 一起使用:
speech-to-speech \
--local_mac_optimal_settings \
--model_name mlx-community/Qwen3-4B-Instruct-2507-bf16
添加 --device mps 以对所有模型使用 MPS。
为 STT 设置 Parakeet TDT。
将 MLX LM 设置为 LLM 后端。
为 TTS 设置 Qwen3-TTS,默认使用 mlx-audio 的 6bit MLX 变体。
--tts pocket 和 --tts kokoro 在 macOS 上也有效。
要在本地比较 MLX 量化变体:
python scripts/benchmark_tts.py \
--handlers qwen3 \
--iterations 3 \
--qwen3_mlx_quantizations bf16 4bit 6bit 8bit
在 WebSocket 模式下运行管道:
speech-to-speech --mode websocket --ws_host 0.0.0.0 --ws_port 8765
从客户端连接到 ws://<server-ip>:8765。发送原始音频字节(16 kHz、int16、单声道 PCM),接收生成的音频字节。
TCP socket 模式故意设计得很简陋。它流式传输原始 PCM 音频,但不提供完整的 Realtime API 功能集,包括中断处理、实时转录事件或工具调用事件。
在服务器上运行管道:
speech-to-speech --mode socket --recv_host 0.0.0.0 --send_host 0.0.0.0
在本地运行客户端以处理麦克风输入和播放:
python scripts/listen_and_play.py --host <服务器的 IP 地址>
安装 NVIDIA Container Toolkit,然后:
docker compose up
compose 文件启动一个 llama.cpp 服务器(运行 Gemma 4),启动 TCP socket 服务器,并暴露端口 8080、12345 和 12346。
Realtime 模式通过 WebSocket 使用 OpenAI Realtime 协议流式传输音频,支持实时转录和低延迟轮流说话。服务器暴露 /v1/realtime,任何 OpenAI Realtime 兼容的客户端都可以连接:
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8765/v1",
websocket_base_url="ws://localhost:8765/v1",
api_key="not-needed",
)
with client.realtime.connect(model="local") as conn:
conn.send(
{
"type": "session.update",
"session": {
"type": "realtime",
"instructions": "You are a helpful assistant.",
"audio": {
"input": {
"turn_detection": {
"type": "server_vad",
"interrupt_response": True,
}
}
},
},
}
)
for event in conn:
print(event.type)
服务器实现了核心 Realtime 事件集:入站事件包括 input_audio_buffer.append、session.update、conversation.item.create、response.create 和 response.cancel;出站事件包括 speech start/stop、流式转录、音频增量、工具调用和 response.done。完整的事件参考、架构和设计详情位于 Realtime Engine README。
使用 --enable_llm_proxy,实时服务器还会将配置的远程 LLM 作为纯 OpenAI 兼容端点公开,这样客户端可以运行附加任务(摘要、标题、后台 AI 智能体),支持工具和流式传输,完全与语音对话并发,从不被新的语音中断:
运行 --llm_backend chat-completions 时,POST /v1/chat/completions
运行 --llm_backend responses-api 时,POST /v1/responses
服务器本身不执行身份验证和限流。仅在受信网络上启用代理,或将服务器部署在拥有访问控制的网关后面。s2s-endpoint 计算副本就是这样一个网关:它仅向使用 HF token 创建会话的客户端开放这些路径,根据该 token 检查 API 密钥,并对每个用户应用速率限制。将标准 OpenAI SDK 指向你与之交互的任何主机;此服务器忽略 API 密钥(前面的网关决定它必须是什么):
from openai import OpenAI
llm = OpenAI(base_url="http://localhost:8765/v1", api_key="unused")
completion = llm.chat.completions.create(
model="anything", # ignored: the server forces its configured --model_name
messages=[{"role": "user", "content": "Summarize the conversation so far: ..."}],
)
请求是无状态的(每次发送完整的消息列表),并被代理到配置的上游,而密钥由服务器保管,从不到达客户端。模型字段始终被覆盖为服务器配置的 --model_name。默认情况下代理是关闭的,需要远程后端(chat-completions 或 responses-api),否则返回 501 及原因。
LLM 是管道中计算量最大、延迟最高的组件。通过大型模型的单次前向传播可以主导端到端响应时间,因此为你的硬件和延迟预算选择合适的后端很重要。管道支持:
本地推理:CUDA / CPU 上的 transformers,以及 Apple Silicon 上的 mlx-lm。
自托管服务器:responses-api 和 chat-completions 可以指向本地 vLLM 或 llama.cpp 服务器。
提供商 API:同样的后端适用于 OpenAI、HF Inference Providers、OpenRouter 和其他 OpenAI 兼容提供商。
有两个 API 后端可用,共享相同的 --responses_api_* 连接标志:
--llm_backend responses-api(默认)目标是 /v1/responses。
--llm_backend chat-completions 目标是 /v1/chat/completions。
下面的示例将 Parakeet TDT 用于本地 STT、Qwen3-TTS 用于本地 TTS,与不同的 LLM 后端配对。
适用于任何实现 OpenAI Responses API 的提供商或服务器。在 --responses_api_base_url 处指向端点并相应地设置 --model_name:
# OpenAI
speech-to-speech \
--mode local \
--stt parakeet-tdt \
--llm_backend responses-api \
--tts qwen3 \
--qwen3_tts_mlx_quantization 6bit \
--model_name "gpt-4o-mini" \
--responses_api_api_key "$OPENAI_API_KEY" \
--responses_api_stream \
--enable_live_transcription
# HF Inference Providers: Qwen3.5-9B via Together
speech-to-speech \
--mode local \
--stt parakeet-tdt \
--llm_backend responses-api \
--tts qwen3 \
--qwen3_tts_mlx_quantization 6bit \
--model_name "Qwen/Qwen3.5-9B:together" \
--responses_api_base_url "https://router.huggingface.co/v1" \
--responses_api_api_key "$HF_TOKEN" \
--responses_api_stream \
--enable_live_transcription
# HF Inference Providers: GPT-oss-20B via Groq
speech-to-speech \
--stt parakeet-tdt \
--llm_backend responses-api \
--tts qwen3 \
--qwen3_tts_mlx_quantization 6bit \
--model_name "openai/gpt-oss-20b:groq" \
--responses_api_base_url "https://router.huggingface.co/v1" \
--responses_api_api_key "$HF_TOKEN" \
--responses_api_stream \
--enable_live_transcription
与 responses-api 的配置相同,复用相同的 --responses_api_* 连接标志,但与 /v1/responses 不同的是与 /v1/chat/completions 通信。在以下情况下选择此后端:
提供商在 Responses 路径上忽略 chat_template_kwargs.enable_thinking,需要 reasoning_effort 旋钮来禁用推理,或
服务器的 Responses 流式工具调用路径不可靠,而其 Chat Completions 工具调用流式传输很稳健。这对某些 vLLM 构建很有用;参见 #312。
添加 --responses_api_reasoning_effort none 在聊天模板标志无效的提供商上禁用推理:
# vLLM serving a Qwen model with tool calling
speech-to-speech \
--mode realtime \
--stt parakeet-tdt \
--llm_backend chat-completions \
--tts qwen3 \
--model_name "Qwen/Qwen3-4B-Instruct-2507" \
--responses_api_base_url "http://localhost:8000/v1" \
--responses_api_stream
# Gemma 4 31B via the HF router on Cerebras, with reasoning disabled for low voice latency
speech-to-speech \
--mode realtime \
--stt parakeet-tdt \
--llm_backend chat-completions \
--tts qwen3 \
--model_name "google/gemma-4-31B-it:cerebras" \
--responses_api_base_url "https://router.huggingface.co/v1" \
--responses_api_api_key "$HF_TOKEN" \
--responses_api_reasoning_effort none \
--responses_api_stream
在单独的 llama.cpp 进程中运行 LLM 以获得最低摩擦的完全本地设置,如 Reachy Mini 本地对话指南所示:
# Terminal 1: llama.cpp serving Gemma 4
llama-server -hf ggml-org/gemma-4-E4B-it-GGUF -np 2 -c 65536 -fa on --swa-full
# Terminal 2: speech-to-speech using that local LLM server
speech-to-speech \
--mode realtime \
--stt parakeet-tdt \
--llm_backend responses-api \
--tts qwen3 \
--model_name "ggml-org/gemma-4-E4B-it-GGUF" \
--responses_api_base_url "http://127.0.0.1:8080/v1" \
--responses_api_api_key "" \
--responses_api_stream \
--enable_live_transcription
当你想直接通过运行服务器的机器交谈时,可以使用 --mode local 代替 --mode realtime。进程内本地后端仍然可用,Apple Silicon 上使用 --llm_backend mlx-lm,或 CUDA / CPU 上使用 --llm_backend transformers。
语言覆盖取决于你选择的 STT 和 TTS 后端,而不是管道本身:
确保 STT、LLM 和 TTS 配对都覆盖你的目标语言。两种使用模式:
单语言:设置 --language 为目标语言代码。默认是 en。
语言切换:设置 --language auto。STT 检测每个口头提示的语言并将其转发给 LLM。可选地添加 --enable_lang_prompt 以追加"请用...回复我的消息"指令。默认为 False;大型 LLM 通常从上下文推断语言,但显式指令可以帮助较小的模型。
自动语言检测:
speech-to-speech \
--stt parakeet-tdt \
--language auto \
--llm_backend mlx-lm \
--model_name "mlx-community/Qwen3-4B-Instruct-2507-bf16"
单个非英语语言,本例为中文:
speech-to-speech \
--stt whisper-mlx \
--stt_model_name large-v3 \
--language zh \
--llm_backend mlx-lm \
--model_name mlx-community/Qwen3-4B-Instruct-2507-bf16
两个命令也都可以在 --local_mac_optimal_settings 顶部工作;显式 --stt 标志覆盖它设置的默认值。
Kyutai Labs 的 Pocket TTS 提供带声音克隆的流式 TTS:
speech-to-speech \
--tts pocket \
--pocket_tts_voice jean \
--pocket_tts_device cpu
可用的声音预设:alba、marius、javert、jean、fantine、cosette、eponine、azelma。自定义声音文件和 Hugging Face 路径也可以工作。
所有 CLI 参数的参考资料位于参数类和 speech-to-speech -h 中。
见 ModuleArguments。它允许设置:
通用 --device,如果每个部分都应在同一设备上运行
--mode:realtime(默认)、local、socket 或 websocket
STT 实现(--stt)
LLM 后端(--llm_backend:transformers、mlx-lm、responses-api 或 chat-completions)
TTS 实现(--tts)
realtime 管道池大小(--num_pipelines)
见 VADHandlerArguments。值得注意的选项:
--thresh:触发语音活动检测的阈值。
--min_speech_ms:检测到的语音活动被视为语音的最小持续时间。
--min_speech_continuation_ms:在可重新打开的软结尾、未提交的回合重新打开窗口内延续语音的持续条的滞后阈值。默认和推荐配对是 --min_speech_ms 384 --min_speech_continuation_ms 192。
--min_silence_ms:用于分段语音的最小沉默间隔长度。默认为 64 毫秒。
--short_segment_merge_ms:可选的合并窗口,用于拼接相邻的 VAD 段,每个段都短于 --min_speech_ms。
--unanswered_reopen_ms:软结尾推测回合尚未收到任何助手输出且保持可重新打开状态的时间长度的合理性上限。
model_name、torch_dtype 和 device 针对每个 STT、LLM 和 TTS 实现都是公开的。STT 和 TTS 参数使用处理器前缀,例如 --stt_model_name 或 --qwen3_tts_device。LLM 模型选择和聊天设置通过无前缀标志在后端之间共享,例如 --model_name 和 --chat_size;特定于后端的标志对 responses-api 和 chat-completions 后端使用 responses_api_ 前缀,对本地后端使用 llm_ 前缀。
# Local transformers/mlx-lm backend
--model_name google/gemma-2b-it
# OpenAI-compatible backend
--llm_backend responses-api --model_name deepseek-chat --responses_api_base_url https://api.deepseek.com
其他生成参数可以使用处理器前缀加上 gen 来设置,例如 --stt_gen_max_new_tokens 128 或 --llm_gen_temperature 0.7。尚未公开的参数可以添加到相关的 arguments 类中。
欢迎提交 Issue 和 PR。好的起点是开放的 Issue。对于更大的改动,请先开一个 Issue 讨论方法。
对于本地开发:
uv sync
pytest
ruff check
如果您使用此管道,请也引用您运行的组件模型。默认值为:
@misc{SileroVAD,
author = {Silero Team},
title = {Silero VAD: pre-trained enterprise-grade Voice Activity Detector (VAD), Number Detector and Language Classifier},
year = {2021},
publisher = {GitHub},
journal = {GitHub repository},
howpublished = {\url{https://github.com/snakers4/silero-vad}},
email = {hello@silero.ai}
}
@misc{parakeet-tdt,
author = {NVIDIA},
title = {Parakeet TDT 0.6B v3},
publisher = {Hugging Face},
howpublished = {\url{https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3}}
}
@misc{qwen3-tts,
author = {Qwen Team},
title = {Qwen3-TTS},
publisher = {Hugging Face},
howpublished = {\url{https://huggingface.co/Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice}}
}
可选后端(如 Kokoro、Pocket TTS、ChatTTS、Whisper 变体、Paraformer 和 MMS)的引用文献位于各自的组件 README 中。