serve、talk、local:三个命令分别在什么场景
第一次翻 speech-to-speech 的 README,最容易产生的误解是:serve、talk、local 是同一件事的三种运行方式,随便挑一个都能说上话。真按这个理解去配,多半会在第二天遇到「为什么局域网里另一台机器连不上」「为什么两个人同时连就有一个被踢」这类问题。
这三个命令切的其实是两条正交的线:谁来托管那条 VAD → STT → LLM → TTS 的流水线,以及谁来把麦克风的音频推进去、把合成音频播出来。想清楚这两件事归谁,选命令就是三秒钟的事。
一、README 原表:三个命令各自是什么
| 命令 | 行为 | 什么时候用 |
|---|---|---|
serve | 以 OpenAI Realtime WebSocket 与 WebRTC 运行流水线服务 | 你在对着这个 API 做应用或设备 |
talk --url <完整 realtime url> | 运行自带的麦克风/扬声器客户端 | 你想连上一个已有的 Realtime 服务说话 |
local | 在同一进程内通过环回把 serve 和 talk 组合起来 | 你想一条命令同时起服务并对它说话 |
照这张表反推:serve 只有流水线,没有嘴也没有耳朵;talk 只有嘴和耳朵,没有流水线;local 是把两边塞进同一个进程,中间用环回连起来。
所以「我只是想试试效果」应该用 local,「我要写一个前端/一台设备去连它」应该用 serve,「服务已经跑在另一台机器上、我想拿本机麦克风验证它活着」才轮到 talk。Quickstart 给的两条命令正是最后这种拆法:
pip install speech-to-speech
export OPENAI_API_KEY=...
speech-to-speech serve
speech-to-speech talk --url ws://127.0.0.1:8765/v1/realtime
默认服务地址是 ws://localhost:8765/v1/realtime,--port 默认 8765。
二、判断依据一:绑定地址不一样,这是安全边界
这是三个命令最实质的差别,也是最该记住的一条。
serve默认绑定127.0.0.1。要暴露到网络,必须显式传--host 0.0.0.0。参数 help 原文的措辞是「显式传 0.0.0.0 以把这个未认证的 API 暴露到网络上」。local永远绑定环回,并把自带客户端连到ws://127.0.0.1:<port>/v1/realtime。它的--porthelp 写的就是「本地 Realtime 服务与音频客户端的环回端口」。
判断依据由此非常清楚:只要你的需求里出现「另一台机器」「手机」「同事」「设备」,local 就直接出局,它没有把服务对外开的选项。反过来,如果你只想在这台机器上验证一下链路是否通,用 local 比自己开两个终端更省事,也顺带避免了误把服务开到网络上。
至于 --host 0.0.0.0,README 在 LLM 代理那一节把话说得很直白:服务端自己不做任何认证,也不做任何限流;只在受信网络上启用,或者把它部署在一个由你自己掌管访问控制的网关后面。这句话对 --enable_llm_proxy 成立,对把 Realtime 端口开到 0.0.0.0 同样是该带上的前提。README 举的例子是让计算副本充当网关,只对「用 HF token 创建了会话的客户端」开放这些路径,并按该 token 校验与限流。本文不给「这样配就安全了」的结论——加不加反向代理、怎么做鉴权,属于你自己环境里的运维决策。
三、判断依据二:参数加在哪个命令上才有用
初学者常犯的另一个错,是把回合检测参数加到 talk 上。
按 Realtime Engine 的数据流,客户端只负责把 base64 PCM 用 input_audio_buffer.append 发上来,服务端的 RealtimeService 解码、重采样到 16 kHz、切成 512 采样点的块,再交给 VAD;VAD 在 text_output_queue 上发 speech_started / speech_stopped。也就是说,语音边界与轮次判定全部发生在托管流水线的那一侧。
结论是:--thresh(默认 0.6)、--min_silence_ms(默认 64)、--min_speech_ms(默认 384)这些 VAD 参数,属于 serve 或 local,不属于 talk。同理,--stt、--llm_backend、--tts 这类组件选择器也是给起流水线的那个命令用的。talk 需要的只是一个 --url。
顺带一个查文档的技巧:想看某个组合的默认值与专属参数,用 speech-to-speech serve -h;要看另一种组合,把选择器放在 -h 前面,例如 speech-to-speech serve --stt mlx-audio-whisper -h。这比翻 README 快得多。
四、判断依据三:默认配置里 LLM 是走云端的
这一点和选命令直接相关,但很多人到部署那天才发现。README 给出了「默认等价的完整命令」,其中和后端选择相关的几行是:
speech-to-speech serve \
--thresh 0.6 \
--stt parakeet-tdt \
--llm_backend responses-api \
--tts qwen3 \
--model_name gpt-5.4-mini \
--chat_size 30 \
--responses_api_stream \
--enable_live_transcription
项目的一句话描述是 “Build local voice agents with open-source models”,但默认配置里真正跑在本地的只有 STT 和 TTS,LLM 那一环默认打的是 OpenAI 的 Responses API,模型是 gpt-5.4-mini。所以 Quickstart 第二条才是 export OPENAI_API_KEY=...。
这个默认值会改变你的命令选择:如果你的目标就是「断网也能用」,那么无论 serve 还是 local,都得先把 LLM 换掉。README 给的最省事做法是把 LLM 放进独立的 llama.cpp 进程,然后让 speech-to-speech 指过去:
# 终端 1:llama.cpp 提供 Gemma 4
llama-server -hf ggml-org/gemma-4-E4B-it-GGUF -np 2 -c 65536 -fa on --swa-full
# 终端 2:speech-to-speech 指向这个本地服务
speech-to-speech serve \
--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
想在托管这台机器上直接说话,把上面的 serve 换成 local 即可;如果是 Apple Silicon 或 CUDA/CPU,也可以用进程内后端 --llm_backend mlx-lm / --llm_backend transformers,省掉独立进程。仓库还提供了 docker compose up,compose 文件会同时起一个跑 Gemma 4 的 llama.cpp 服务和实时服务,暴露 8080 与 8765 两个端口——8080 是 llama.cpp,8765 是 Realtime,别记反。
另外提醒一句面向中文场景的读者:默认 STT 是 Parakeet TDT,README 的多语言表里写的是25 种欧洲语言。要做中文语音代理,无论走哪个命令都得换 STT(Whisper 系或 Paraformer)。
五、把它压成一条选择路径
- 只想在本机验证链路通不通 →
local。它永远绑环回,不会误开到网络上;macOS 上还能直接加--mac-optimal-settings。 - 要写前端 / 做设备 / 给同事连 →
serve。默认只绑127.0.0.1,要对外必须显式--host 0.0.0.0,而这一步意味着你要自己处理认证与限流。 - 服务已经在别处跑着,只想用本机麦克风验证一下 →
talk --url <完整 realtime url>。 - 要给多人用 → 一定是
serve,并且第一个要改的参数是--num_pipelines。它默认为 1,help 原文说得很清楚:一个 uvicorn server 监听--port,把每个进来的客户端路由到下一个空闲的 pipeline,每个 pipeline 有自己的 VAD/STT/LM/TTS handler 与会话状态,最大并发 WebSocket 会话数等于num_pipelines,再多的连接会被拒绝。默认值 1 意味着开箱只能承载一个会话——这是我认为整份参数表里最容易在演示当天翻车的一个默认值。
六、两个容易踩空的细节
其一,--mode 已经废弃。 README 说它很快将停止工作。迁移窗口内 --mode realtime 等价于 serve,--mode local 等价于 local,两者都会打印警告;其余所有 mode 取值已被移除,会带着「改用新命令」的提示直接退出。网上大量旧教程用的正是 --mode,跑不通不是你的环境问题,是命令行结构换了。
其二,--mac-optimal-settings 该挂在哪个命令上。 它做四件事:对支持的模型组件使用 MPS 默认值、STT 设为 Parakeet TDT、LLM 后端设为 MLX LM、TTS 设为 Qwen3-TTS 并默认取 6bit 的 MLX 变体。关键是优先级——预设只提供默认值,显式的 --device、组件级设备参数(如 --qwen3_tts_device),以及 --stt、--llm_backend、--model_name、--tts 全部优先于预设。README 还给了一条明确指引:想暴露服务而不启动麦克风客户端时,把它用在 serve 而不是 local 上。很多人默认它只配 local,于是在做 macOS 上的服务端时白白丢掉了这套默认值。
最后补一条排错方向:如果你写了某个后端的专属参数却发现毫无效果,先确认那个后端是不是真被选中了。README 明说 CLI 只为选中的后端构造配置,未激活后端的已知选项仍然被接受,但会带警告地忽略。程序不会报错停下,只会警告后照跑——这类「配了没生效」的问题,答案往往就在启动日志的警告里。
延伸阅读
本文依据 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 的实际输出为准。
以上组合命令为按官方参数语义整理的示例,未逐项实测。
安全相关做法请结合自身环境评估,本文不构成安全方案建议。