从 pip install 到第一次 serve:默认装了些什么
装 speech-to-speech 这件事,表面上只有一行 pip install speech-to-speech。麻烦的地方不在这一行能不能跑通,而在于它跑通之后,你手上究竟拼出了一条什么样的流水线——哪个模型在做转写、哪个后端在出声、语言模型的请求发到了谁家的地址上。这几个问题的答案全都藏在默认值里,而默认值和你脑子里想的多半不一样。
这篇就按官方 README 和 src/speech_to_speech/arguments_classes/ 下的参数定义,把「默认装了些什么」摊开讲一遍。
先交代边界:**我们只读了仓库文件,没有 pip install 过,没有启动过服务,也没有对着麦克风说过一句话。**下文所有毫秒数都是参数定义里的默认值,是流水线自己引入的可配置等待,不含 STT / LLM / TTS 的推理耗时,更不能相加当成端到端延迟。
一、先搞清楚这条流水线由什么组成
README 的定位是一条模块化的语音代理流水线:VAD → STT → LLM → TTS,对外通过一个兼容 OpenAI Realtime 的 WebSocket API 暴露。四个组件各跑在自己的线程里、用队列相连,每一级都有多个可互换后端,用 CLI 参数选。
仓库层面的量级:src/speech_to_speech/ 下 96 个 Python 文件,arguments_classes/ 里 18 个 dataclass 参数文件加起来 131 个 CLI 参数。GitHub API 在 2026-08-09 的快照是 11893 star、1463 fork、136 open issues,仓库标注 Apache-2.0。star 数只是个时间锚点,推不出任何关于稳定性或适用性的结论,这里列出来只是让你知道读到的是哪个时间点的仓库。
二、命令怎么写:三条路径,别混着走
路径 A:直接 pip 装(要求 Python 3.10+)
pip install speech-to-speech
但 Linux + CUDA 的用户先别急着敲这行。README 单独开了小节讲这个坑:Qwen3-TTS 的 GGML 后端来自 faster-qwen3-tts[ggml],它在 PyPI 上的默认 qwentts-cpp-python wheel 是面向 CUDA 12.8 的。如果你机器上没有这个 wheel 期望的 CUDA 12 运行时,正确顺序是先从 Hugging Face 的 wheelhouse 装匹配的 wheel,再装 speech-to-speech:
# 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 兜底
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
每一行为什么在这:版本号后缀 +cu130 / +cu124 / +cpu 就是这批 wheel 区分 CUDA 运行时的方式,选错了等于没绕开原来的错配;-f 指向 wheelhouse 的索引地址,让 pip 从这里而不是 PyPI 取包;最后一行放在末尾,是为了让 speech-to-speech 的依赖解析看到已经装好的那个版本。
如果你不想折腾 GGML 那一路,README 给了另一个出口:传 --qwen3_tts_backend torch,改用之前那套 CUDA-graphs 实现。这个参数的默认值是 ggml。
路径 B:从源码装
git clone https://github.com/huggingface/speech-to-speech.git
cd speech-to-speech
uv sync
README 说这会以可编辑模式安装,并提供 speech-to-speech 命令。要改 handler、要看某个默认值到底写在哪一行,走这条。
路径 C:Docker
装好 NVIDIA Container Toolkit 之后:
docker compose up
README 原文说 compose 文件会起一个跑 Gemma 4 的 llama.cpp 服务和实时服务,暴露端口 8080 与 8765。仓库里另有 Dockerfile 与 Dockerfile.arm64。这条路径的价值在于它顺手把「本地语言模型」那一环也拉起来了,不用你自己先支一个 OpenAI 兼容端点。
关于 Windows
需要说清楚的是:README 的依赖解析只在 pyproject.toml 里按 macOS / 非 macOS 做平台标记分岔,并没有单独的 Windows 章节。所以 Windows 上能参考的只有「非 macOS」这一支——也就是上面 CUDA wheel 那套顺序。至于 Windows 下的音频设备、驱动一类的具体差异,官方文档没写,我们也没跑过,不编。
以上命令均原样引自 README,未逐项验证过,以官方文档与 speech-to-speech serve -h 的实际输出为准。
三、默认装了些什么:三个默认值决定了一切
module_arguments.py 里三个选择器的默认值是这样的:
| 参数 | 默认值 | 含义 |
|---|---|---|
--stt | parakeet-tdt | 默认转写后端 |
--llm_backend | responses-api | 默认语言模型走向 |
--tts | qwen3 | 默认语音输出后端 |
对照 README 的支持矩阵:默认 STT 是 nvidia/parakeet-tdt-0.6b-v3(Apple Silicon 上走 MLX 变体),默认 TTS 是 Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice,非 macOS 用 GGML 后端、Apple Silicon 用 mlx-audio。
有三件事值得单独拎出来说,因为它们跟项目给人的第一印象是反的。
第一,项目描述写着 “Build local voice agents with open-source models”,但默认的 LLM 那一环打的是 OpenAI。--llm_backend 默认 responses-api,而 responses_api_language_model_arguments.py 里 --model_name 默认是 gpt-5.4-mini、--responses_api_base_url 默认 None(help 原文注明 uses OpenAI)。想真正跑本地,你得显式把 base URL 指到自己的 vLLM 或 llama.cpp 上,或者干脆走 Docker compose 那条路。这不是项目在夸口,它说的是 LLM 那一环用的是 OpenAI 兼容协议,能指向托管服务商,也能指向你自己的机器——但默认指向的是前者。
第二,--model_name 这个名字在两个文件里都存在,默认值不一样。language_model_base_arguments.py 里它默认 Qwen/Qwen3-4B-Instruct-2507,responses_api_language_model_arguments.py 里默认 gpt-5.4-mini。哪个生效取决于 --llm_backend 选了谁。看 help 输出时一定要连着后端一起看,不然会拿错默认值。
第三,默认 STT 的语言覆盖面。--parakeet_tdt_language 的 help 原文写的是 “Supports 25 European languages”。中文不在这个描述里。要做中文,README 的矩阵里给的是 Paraformer(paraformer extra,--paraformer_stt_model_name 默认 paraformer-zh)或者 Whisper 系(whisper_stt_arguments.py 的 --language 可选 'zh')。这一条决定了很多人从第一步就得改配置,别等装完了才发现。
另外还有一个非常容易忽略的默认值:--num_pipelines 默认是 1。它的 help 说得很直白——池子里隔离的流水线实例个数,一个 uvicorn 监听 --port,把每个进来的客户端路由到下一个空闲流水线,最大并发 WebSocket 会话数就等于 num_pipelines,再多的连接会被拒绝。也就是说,默认配置下这就是一个单会话服务。
四、可选组件:只装你真要用的那一个
README 给的 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[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 支持
pip install "speech-to-speech[webrtc]" # WebRTC 传输
**这里藏着第二个安装期的坑,而且它是纯依赖层面的死结。**README 原文说:DeepFilterNet(VAD 里可选的音频增强用)需要 numpy<2,Pocket TTS 需要 numpy>=2,两者冲突。结论是:只在不使用 Pocket TTS 的环境里手动安装 DeepFilterNet。对应到 CLI,就是 --audio_enhancement 这个开关,它的默认值是 False——默认不开,所以默认装的时候你不会撞上这个冲突,是你后来想加音频增强的时候才会撞上。
顺带一提:已弃用的实现(含 MeloTTS)都在 archive/ 目录里,不再接入 CLI,别照着老教程去找。
五、产出物长什么样
只说有依据的部分:
- pip 装完之后,会提供一个名为
speech-to-speech的命令;从源码走uv sync同样提供这个命令。 - 服务端监听参数在
realtime_server_arguments.py:--host默认127.0.0.1,--port默认 8765。 - Docker compose 那条路会额外起一个 llama.cpp 服务,端口 8080。
- 除了实时服务,README 还提到本地音频模式,对应
local_audio_arguments.py里的一组参数(如--local_audio_chunk_size默认 1024、--local_audio_print_json默认False,后者用于打印本地音频客户端收到的原始 Realtime 事件)。
至于终端上会滚出哪几行日志、有没有进度条、模型下载到哪个缓存目录——我们没跑过,不写。
六、怎么验收:一条命令,两个陷阱
验收动作只有一条:
speech-to-speech serve -h
它会打印当前这套组合的默认值与专属参数。**关键在于「当前这套组合」这五个字。**README 明确说了:要看另一种组合的参数,把选择器放在 -h 前面,比如
speech-to-speech serve --stt mlx-audio-whisper -h
陷阱一:CLI 只为选中的后端构造配置,未激活后端的已知选项仍然被接受,但会带警告地忽略。JSON 配置同理,多余的非激活后端键会被忽略。翻译成人话——你把某个后端专属参数写错了位置,程序不会报错停下,只会警告一下然后照跑,跑出来的还是默认行为。所以人要检查的第一处,就是你传的每个后端专属参数,前缀是不是跟你选的那个后端对得上(比如你选了 --tts qwen3,就别指望 --kokoro_voice 生效)。
陷阱二:别把 -h 打印的毫秒值当成延迟指标。vad_arguments.py 里跟第一次启动最相关的几个默认值是:--thresh 0.6、--min_silence_ms 64、--min_speech_ms 384、--speech_pad_ms 500。它们是 VAD 切分语音边界用的门限和留白,不是任何形式的耗时测量。同一个文件里还有三个跟轮次重开有关的窗口——--speculative_reopen_ms 800、--unanswered_reopen_ms 7000、--smart_turn_max_wait_ms 2000,以及迟滞用的 --min_speech_continuation_ms 192、Smart Turn 的 --smart_turn_threshold 0.5 和 --smart_turn_incomplete_delay_ms 600。这些数值各自约束的是不同阶段的可配置等待,把它们加起来当成端到端耗时是彻底错误的读法,这些窗口本来就不同时生效,也完全不包含模型推理时间。第一次装完,你只需要知道这些值存在、能改;真要调它们是另一个话题。
如果想看得更细,可以把 --log_level 从默认的 info 调成 debug。
七、什么情况不适用
这套默认组合有几个明确的边界,装之前就该判断清楚:
- 要做中文的,默认 STT 不适用。
--parakeet_tdt_language的 help 写的是 25 种欧洲语言。中文场景请从一开始就规划paraformerextra 或 Whisper 系,别装完再返工。 - 要多人同时接入的,默认配置不适用。
--num_pipelines默认 1,超出的连接会被拒绝。要并发得先想清楚每条流水线各自持有一整套 VAD/STT/LM/TTS handler 和会话状态,这是资源问题不是配置问题。 - **要「完全离线」的,默认配置不适用。**默认
--llm_backend responses-api打的是 OpenAI 兼容端点,--responses_api_base_url默认None。不改这一项,你的对话内容是出网的。 - **既要 Pocket TTS 又要 DeepFilterNet 音频增强的,装不到一个环境里。**numpy 版本硬冲突,只能二选一或者拆环境。
- 要把服务开到公网的,请先停一下。
--host的 help 原文写着:默认 127.0.0.1,显式传0.0.0.0才会把这个未认证的(unauthenticated) API 暴露到网络上。同样地,官方对--enable_llm_proxy(默认False)这条链路的声明是:服务端自身不做认证、不做限流,只应在受信网络内或放在网关后面启用。这里不给「怎么配就安全了」的结论——加反向代理、加防火墙、加鉴权层都属于通用运维做法,不是该项目官方文档的内容,也不构成安全方案建议。 - 密钥别写进命令行历史。
--responses_api_api_key默认None,需要你自己传。写文档时一律用<YOUR_API_KEY>占位,实际使用建议通过$OPENAI_API_KEY、$HF_TOKEN这样的 shell 变量引用(这是通用做法,不是该项目的官方要求)。
最后补一个不算坑但值得知道的事实:README 说这条流水线作为数千台 Reachy Mini 机器人的对话后端在生产环境运行。这是 README 的陈述,可以当作「有人在认真用它」的旁证,但 README 没有给出任何性能指标,所以它推不出「延迟低」或者「很稳」之类的结论,别顺着往下想。
延伸阅读
本文依据 speech-to-speech 官方仓库(github.com/huggingface/speech-to-speech)的 README、
src/speech_to_speech/arguments_classes/ 下的参数定义与 Realtime Engine 架构文档整理,核对日 2026-08-09。
本文内容为仓库源码与文档口径,我们没有安装、部署或调用过该服务,文中毫秒值均为参数默认值而非实测延迟。
参数与默认值随版本变动,请以 speech-to-speech serve -h 的实际输出为准。
许可条款请以官方 LICENSE 原文为准,本文不构成法律意见;安全相关做法请结合自身环境评估,本文不构成安全方案建议。