老教程里的 --mode 为什么跑不通了
搜索引擎里关于 speech-to-speech 的中文教程,有相当一部分停留在 --mode 那个年代。你复制一条命令过来,敲下回车,出来的东西跟教程截图对不上——这篇就专门处理这一类问题。
先说清楚本文的依据边界:以下内容全部来自官方仓库的 README 与 src/speech_to_speech/arguments_classes/ 下的参数定义,核对日 2026-08-09。我们没有 pip install 过它,没有启动过服务,也没有对着麦克风说过一句话。所以下面不会出现任何延迟数字、音质评价或者「装完界面上会显示什么」的描述。
一、现象长什么样
照抄老教程时,--mode 的失败方式不止一种,这也是它容易被误判的原因:
第一种:命令跑起来了,但多了一行警告。 README 写得很明确,迁移窗口内 --mode realtime 等价于 serve、--mode local 等价于 local,两者都会打印警告。很多人看到服务起来了就跳过了那行警告,直到某次升级后彻底跑不动才回头找原因。
第二种:直接退出,带一句「改用新命令」的提示。 除了上面那两个取值,其余所有 mode 取值都已经被移除了。老教程里如果写的是别的 mode 名字,现在的行为就是带提示退出,不会继续往下走。
第三种:你想查 --mode 到底还支持什么,却在参数定义里翻不到它。 我们把 src/speech_to_speech/arguments_classes/ 下的参数整份导出数过,131 个 CLI 参数里没有 --mode 这一项。这一种最让人困惑——命令还能被接受,但常规参数定义那一层已经查无此项。
README 对这件事的定性是:--mode 已废弃,且很快将停止工作。也就是说,第一种「只是警告」的状态是有保质期的,不能当成长期方案。
二、怎么确认是这个问题
别靠猜,下面三个动作都能在一分钟内做完。
动作一:查帮助输出里还有没有 --mode。
speech-to-speech serve -h
这里有个 README 单独强调过的用法细节:CLI 只为选中的后端构造配置,所以你想看另一种组合的参数,得把选择器放在 -h 前面,例如:
speech-to-speech serve --stt mlx-audio-whisper -h
动作二:核对参数定义那一层。 承接上面第三种现象——src/speech_to_speech/arguments_classes/ 下是 18 个 dataclass 参数文件,131 个参数分散在里面(VAD 19 个、Qwen3-TTS 23 个、模块级 12 个是大头),--mode 不在其中。这个结果本身就是一个判据:一个连常规参数定义里都不再出现的开关,你不该把长期脚本压在它上面。
动作三:看启动的第一屏有没有警告。 如果你的命令里带着 --mode realtime 或 --mode local 且服务确实起来了,那行警告就是本次失败(或未来失败)的唯一预告。想把日志看得更细,可以用 --log_level debug(默认是 info)。
对照下面这张表就能立刻定性:
| 老教程里写的 | 当前行为 | 你该改成 |
|---|---|---|
--mode realtime | 等价于 serve,打印警告 | speech-to-speech serve |
--mode local | 等价于 local,打印警告 | speech-to-speech local |
| 其它任何 mode 取值 | 已移除,带提示直接退出 | 按下一节的三命令模型重写 |
三、按文档语义怎么处置
新的命令模型是三个动词,README 原文给的分工是这样的:
| 命令 | 行为 | 什么时候用 |
|---|---|---|
serve | 以 OpenAI Realtime WebSocket 与 WebRTC 运行流水线服务 | 你在对着这个 API 做应用或设备 |
talk --url <完整 realtime url> | 运行自带的麦克风/扬声器客户端 | 你想连上一个已有的 Realtime 服务说话 |
local | 在同一进程内通过环回把 serve 和 talk 组合起来 | 你想一条命令同时起服务并对它说话 |
老教程里的 --mode realtime 对应的是「只起服务」,改写成 serve;--mode local 对应的是「起服务顺便自己说话」,改写成 local。如果你原来是起了服务再另开一个终端连上去,那么现在的写法是:
speech-to-speech talk --url ws://127.0.0.1:8765/v1/realtime
默认服务地址是 ws://localhost:8765/v1/realtime,端口默认 8765。
改写命令的时候,真正的坑往往不在 --mode 这个词本身,而在它后面跟着的一串老参数上。这里有一条 README 明说的行为必须记住:未激活后端的已知选项仍然被接受,只是会带警告地被忽略,JSON 配置里多余的非激活后端键同理。换句话说,你从老教程里抄来一串属于别的后端的参数,程序不会报错停下,只会警告一声继续跑——你以为配置生效了,实际上它一个字都没读。排查这类「命令跑通了但行为不对」的问题时,这一条是首要嫌疑。
四、处置后怎么验证
第一步,确认默认配置是不是你要的。 README 给出了一段等价展开,export OPENAI_API_KEY=... 之后直接 speech-to-speech serve,等价于:
speech-to-speech serve \
--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
这段值得逐行读一遍,因为它藏着一个很多人换完命令才发现的事实:默认模型是通过 OpenAI Responses API 的 gpt-5.4-mini。项目自我介绍是「用开源模型搭建本地语音代理」,但默认配置里在本地跑的只有 STT 和 TTS 这两环,LLM 那一环默认打的是云端。想全本地就得自己换后端,README 给的最省事做法是把 LLM 放到独立的 llama.cpp 进程,再用 --responses_api_base_url 指过去(http://127.0.0.1:8080/v1)。顺带记一下端口分工:docker compose 里 llama.cpp 是 8080,实时服务是 8765,这两个别写反。
第二步,用 talk 连一次确认服务确实在监听。 用上面那条 talk --url 的命令连本机 8765,这是最直接的连通性验证。
第三步,检查绑定地址有没有被老教程带偏。 serve 默认绑定 127.0.0.1,要暴露到网络必须显式传 --host 0.0.0.0;local 则永远绑定环回,并把自带客户端连到 ws://127.0.0.1:<port>/v1/realtime。如果你抄来的老命令里带着 --host 0.0.0.0,或者顺手开了 --enable_llm_proxy,务必先看清官方自己的声明:服务端不做任何认证,也不做任何限流,只应在受信网络上启用,或者部署在一个你自己掌管访问控制的网关后面。这句话不能弱化,也不存在「照着某个配置写就安全了」的说法;nginx、防火墙、反向代理一类做法属于通用运维手段,不是该项目文档的内容,要不要用、怎么用得结合你自己的环境评估。
以上命令均按官方参数语义组合,我们未逐项实测,以官方文档与 --help 的实际输出为准。
五、什么情况说明不是这个原因
排查文章最容易漏掉的就是这一节。下面几种失败长得很像,但换成 serve 是治不好的:
你的命令里根本没有 --mode。 那就别在这条线上耗了。失败提示里没有出现 mode 相关字样时,直接去看下面几类。
失败发生在安装/导入阶段,服务还没起来。 这类问题通常是依赖矩阵而不是命令语法。README 单列了两个坑:Linux 上 Qwen3-TTS 的 GGML 后端来自 faster-qwen3-tts[ggml],其 PyPI 默认 wheel 面向 CUDA 12.8,机器上没有对应运行时就得先从 Hugging Face wheelhouse 装匹配版本再装本体;另一个是 DeepFilterNet 需要 numpy<2、Pocket TTS 需要 numpy>=2,两者互斥,只能在不用 Pocket TTS 的环境里手动装 DeepFilterNet。
第二个客户端连不上、被拒绝。 看 --num_pipelines,它默认是 1。按参数说明,一个 uvicorn 服务监听 --port,把每个进来的客户端路由到下一个空闲流水线,每条流水线有自己的 VAD/STT/LM/TTS handler 与会话状态;最大并发 WebSocket 会话数等于 num_pipelines,再多的连接会被拒绝。这是并发上限,跟命令怎么写没关系。
断网环境下起不来。 检查是不是只加了 HF_HUB_OFFLINE=1 却没有把 LLM 的 base URL 覆盖到本地——默认的 responses-api 后端会去调远程服务,离线自然就断了。README 的建议是断网前先在联网状态下用完全相同的配置启动一次,让 STT、LLM、TTS、Silero VAD、NLTK 和 Smart Turn 需要的资源都缓存好。
老教程用的组件本身已经被下架了。 已弃用的实现(含 MeloTTS)被放进了 archive/,不再接入 CLI。这种情况下无论 --mode 还是 serve 都救不回来,只能按当前的后端支持矩阵重新选型。
你抄的参数属于没被激活的后端。 回到第三节那条:它会被带警告地忽略而不是报错。命令能跑、行为不对、日志里有警告——这三件事同时出现时,先怀疑参数落空,而不是继续改命令动词。
最后补一句关于版本的态度。这个仓库在 2026-08-09 的快照里有 11893 个 star,最后一次推送就在前一天——更新很勤。star 数说明不了稳定性或者适不适合你,但推送频率至少提示一件事:任何一篇带具体参数的教程(包括这一篇)都有保质期,动手前先跑一次 -h 永远比相信文章便宜。
延伸阅读
本文依据 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 的实际输出为准。
安全相关做法请结合自身环境评估,本文不构成安全方案建议。