项目简介
低延迟、模块化语音智能体管线(VAD->STT->LLM->TTS),提供与 OpenAI Realtime 兼容的 WebSocket API。所有组件均可替换,支持本地与云端模型,适合构建交互式语音应用。
README 预览
\n \n \n\n# Speech To Speech: Build voice agents with open-source models\n\n[](https://pypi.org/project/speech-to-speech/)\n[](https://pypi.org/project/speech-to-speech/)\n[](./LICENSE)\n\n\n\nA low-latency, fully modular voice-agent pipeline: **VAD -> STT -> LLM -> TTS**, exposed through an **OpenAI Realtime-compatible WebSocket API**. Every component is swappable. The LLM slot speaks OpenAI-compatible protocols, so you can point it at a hosted provider, at [HF Inference Providers](https://huggingface.co/inference-providers), or at a vLLM or llama.cpp server on your own hardware for a fully local, fully open stack.\n\nThis pipeline runs in production as the conversation backend for thousands of [Reachy Mini](https://huggingface.co/blog/reachy-mini) robots.\n\n\n \n \n \n \n \n\n\n## Quickstart\n\n```bash\npip install speech-to-speech\nexport OPENAI_API_KEY=...\nspeech-to-speech\n```\n\nThis starts an OpenAI Realtime-compatible server at `ws://localhost:8765/v1/realtime` using Parakeet TDT for local STT, an OpenAI-compatible LLM, and Qwen3-TTS for local speech output.\n\nFrom a source checkout, talk to it from a second terminal:\n\n```bash\npython scripts/listen_and_play_realtime.py --host 127.0.0.1 --port 8765\n```\n\nPrefer to keep the LLM on your own machine? Serve Gemma 4 with llama.cpp:\n\n```bash\nllama-server -hf ggml-org/gemma-4-E4B-it-GGUF -np 2 -c 65536 -fa on --swa-full\n```\n\nThen point the OpenAI-compatible LLM backend at it:\n\n```bash\nspeech-to-speech \\n --model_name "ggml-org/gemma-4-E4B-it-GGUF" \\n --responses_api_base_url "http://127.0.0.1:8080/v1" \\n --responses_api_api_key ""\n```\n\nAny OpenAI Realtime-compatible client can connect. See [Realtime API](#realtime-api) for the protocol and [LLM backends](#llm-backends) for provider and local-server options.\n\n## Index\n\n* [How it works](#how-it-works)\n* [Installation](#installation)\n* [Supported components](#supported-components)\n* [Run modes](#run-modes)\n* [R
常见问题 (5)
故障排除在使用 ChatTTS 进行语音转语音(speech-to-speech)时,如何修复 "TypeError: object of type 'numpy.int16' has no len()" 错误?
错误发生在 TTS/chatTTS_handler.py 中,因为流式处理代码假设每个 ChatTTS 音频块的形状都是 (1, samples),并应用了 [0]。当 ChatTTS 返回形状为 (samples,) 的扁平数组时,[0] 会选中单个 numpy.int16 样本,因此 len(audio_chunk) 会报错。
通过移除末尾的 [0] 来修复流式处理分支:
# 修改前
audio_chunk = (audio_chunk * 32768).astype(np.int16)[0]
# 修改后
audio_chunk = (audio_chunk * 32768).astype(np.int16)
要同时支持 (samples,) 和 (1, samples) 两种音频块形状,请使用:
audio_chunk = (audio_chunk * 32768).astype(np.int16).squeeze()
然后重新运行流水线。相关的 while len(audio_chunk) > self.chunk_size: 循环将接收到一个数组,而不是单个整数样本。
故障排除如何修复使用 Open WebUI 实时语音通话连接本地语音到语音服务器时出现的 'Voice provider rejected a request' 错误?
将 speech-to-speech 服务器更新到包含 Open WebUI 兼容性修复的最新版本。出现该问题是因为 Open WebUI 会发送带有 chat_context_1 等 ID 的聊天历史快照,在刷新时删除之前的快照,并且可能先发送 create_response: false,随后再显式发送 response.create;旧版本的服务器会拒绝或忽略这些事件,并断开 WebSocket 会话。
步骤:
1. 拉取最新的 speech-to-speech 代码/发行版本,其中包含针对 Open WebUI Realtime 聊天历史刷新和 create_response=false 的修复。
2. 重新构建并重启服务器容器,例如:
git pull
docker compose up -d --build3. 在 Open WebUI 中,保持 realtime WebSocket 端点设置为
/v1/realtime,并使用你配置的模型,例如 faster-qwen3-tts。4. 发起新的语音通话。此前失败的现有会话可能需要关闭并重新创建。
更新后的服务器可接受 Open WebUI 的快照 ID,能够识别快照删除事件,并在 audio.input.turn_detection.create_response 为 false 时避免自动生成重复响应。如果暂时无法更新,可使用项目自带的 Web 演示作为临时替代方案。
故障排除当 create_response: false 被忽略时,如何修复 Open WebUI Realtime 中重复的语音回复?
升级到包含 audio.input.turn_detection.create_response 标志修复的构建版本。该缺陷位于 RealtimeService._on_transcription_completed:它在收到非空转录结果后无条件地将 GenerateResponseRequest 加入队列,即使会话配置了 create_response: false。随后 Open WebUI 会发送显式的 response.create,导致同一轮对话被语音播放两次。
从源代码构建时,请修补转录完成路径和直接音频路径,使得仅当有效的 create_response 设置启用时才将自动生成加入队列:
{
"type": "session.update",
"session": {
"type": "realtime",
"audio": {
"input": {
"turn_detection": {
"type": "server_vad",
"interrupt_response": true,
"create_response": false
}
}
}
}
}
应用修复后,create_response: false 必须仍然保留转录、历史记录和 VAD 事件,但不得将自动响应加入队列。服务器应等待客户端显式发送的 response.create。使用针对性的复现进行验证:分发 TranscriptionCompletedEvent 后,提示队列应为空:
assert queue.empty()
如果无法升级,可采用临时变通方案:仅使用一种响应机制——要么保持自动生成启用并移除 Open WebUI 显式发送的 response.create,要么保持 create_response: false 并阻止服务器自动将响应加入队列。推荐的解决方案是升级或应用补丁。
故障排除如何修复 Kokoro TTS 在使用另一种语言回复后,将会话中选择的语音还原为 CLI 语音的问题?
升级到包含 PR "Preserve Kokoro session voices across language switches"(即 issue #659 的修复)的构建版本,或应用下面的源代码修改。
覆盖发生在一次 process 调用中:客户端/响应所选的语音被赋值给 self.voice,但当语言切换回设置语言时,_lang_and_voice_for 会用来自 CLI 的 self._initial_voice 将其替换。
所需修改:
1. 将会话/响应所选的语音传递给 Kokoro 处理流程,而不是仅依赖可变的 self.voice:
yield from self._process_kokoro(text, language_code, pinned_voice=voice)
2. 让 _lang_and_voice_for 在应用 CLI 或按语言默认值之前先返回该语音:
def _lang_and_voice_for(..., pinned_voice=None):
if pinned_voice is not None:
return language_code, pinned_voice
# existing language/default-voice resolution不要简单地返回
self.voice:在一条西班牙语回复之后,它可能合法地是西班牙语默认语音 ef_dora,这会错误地延续到英语中。
3. 保留现有的 CLI 回退逻辑:当没有提供响应或会话语音时,继续使用 self._initial_voice。在 on_session_end 中重置为 _initial_voice 也是正确的。
4. 在 MLX 路径上,当解析出的语音发生变化时,同时重新加载 pipeline 和语音,例如先执行 self.model._get_pipeline(new_lang_code),然后执行 load_voice(new_voice)。
验证:使用 --tts kokoro --kokoro_lang_code a --kokoro_voice af_heart 运行,将客户端会话语音设置为 am_puck,先发送一条西班牙语回复,再发送英语。实际发音的语音应先为 ef_dora,然后为 am_puck,而不是 ef_dora 然后 af_heart。
不打补丁的临时变通方法:将 --kokoro_voice 设置为与客户端所选相同的语音,或避免从设置语言切换出去;仅靠配置无法保留每个会话各自不同的语音。
故障排除为什么 Speech-to-Speech 在 AI 静默时仍会为每个 WebRTC 客户端消耗 CPU,我该如何阻止这些空闲的静音音频数据包?
后台 CPU 占用是由 aiortc 造成的:即使传出的 PCM 缓冲区为空,它仍会以每秒约 50 个的速率编码并发送静音 RTP 音频包。请升级到包含 PR #570 的 Speech-to-Speech 版本;如果该 PR 尚未发布,则从该 PR 构建/运行。该修复使出站音频轨道的 recv() 在缓冲区为空时等待一个事件,并在新的 PCM 帧到达或轨道停止时被唤醒。这样既能防止持续的静音帧编码,又无需停止/重建 RTCRtpSender,也无需进行 WebRTC 重新协商;音频恢复时 RTP 时间戳仍保持连续。作为临时解决方案,可在传出队列为空时,在 src/speech_to_speech/api/openai_realtime/webrtc_session.py 中暂停/禁用发送器,并在发送下一帧之前重新启用它。replaceTrack(None) 效果较差,因为 aiortc 仍会以 50 Hz 轮询轨道;而停止/重建发送器会发送 RTCP BYE,并可能带来重启开销。如果仍存在残余的 CPU 占用,请将 _send_loop_for 从 10 毫秒队列轮询改为异步的 await queue.get() 式等待。