流式 TTS
Hermes 可以在 TTS 音频从提供商到达时即流式播放,而不必等整段音频生成完再播放。这一能力用于语音模式(CLI/TUI 实时对话)、dashboard speak-stream WebSocket,以及——通过网关 StreamingTTSConsumer——任何选择流式音频的平台适配器。语音回复在第一个分句后就开始播报,而非等完整生成 + 合成。
架构 {#architecture}
流式管线有四部分:
- 生产者——LLM 在生成响应时发出文本增量
- 句子分块器——
tools.tts_streaming.SentenceChunker累积增量、剥离<think>块(即使跨增量拆分),并输出完整句子 - TTS 提供商——一个已注册的
StreamingTTSProvider把每个句子转成原始 PCM 块(按提供商声明的sample_rate,int16 单声道) - 音频 sink——本地播放用
sounddevice.OutputStream(tools.tts_tool_speaker.stream_tts_to_speaker),或网关平台适配器的write_streaming_tts接缝(gateway/streaming_tts_consumer.py)
没有分块 API 的提供商仍通过久经考验的同步 text_to_speech_tool 路径获得按句子播放,因此 edge(默认)也是对话式的。所有播报文本由 tools.tts_text_normalize.prepare_spoken_text 清洗(一个清洗器,所有路径共用)。
如何选择提供商 {#how-to-pick-a-provider}
默认情况下,当你已配置的提供商(tts.provider)具备分块 API 时,调度器用它做流式——它绝不会为了获得流式而悄悄把你的声音换成另一家提供商。
要覆盖,在 config.yaml 中设置 tts.streaming.provider:
- 一个提供商名(
elevenlabs、gemini、openai、xai)锁定该流处理器 auto按优先级表elevenlabs → gemini → openai → xai走,使用第一个凭据可解析者——显式选择"可用的最佳分块语音"
tts:
provider: gemini
streaming:
provider: gemini # 或 "auto"
min_len: 20 # 首个独立播报的最短句长(字符);中文环境用约 6
gemini:
model: gemini-2.5-flash-preview-tts
voice: Kore
能力矩阵 {#capability-matrix}
| 提供商 | 传输 | 分块 PCM | 凭据 |
|---|---|---|---|
| elevenlabs | 分块 HTTP(pcm_24000) | 是 | ELEVENLABS_API_KEY / tts.elevenlabs |
| openai | 分块 HTTP(with_streaming_response、pcm) | 是 | tts.openai.api_key → env → 托管网关 |
| gemini | SSE(streamGenerateContent?alt=sse) | 是 | GEMINI_API_KEY / GOOGLE_API_KEY |
| xai | WebSocket(wss://api.x.ai/v1/tts) | 是 | 优先 XAI_API_KEY,否则 xAI OAuth(订阅 bearer 在计量 TTS 上返回 403) |
| edge、piper、kitten、neutts、mistral、minimax、deepinfra…… | — | 否(按句子同步回退) | 同往常 |
所有凭据查找都经 resolve_provider_secret()(配置 > env/.env > 凭据池)——绝不裸读 env。流式正文每句上限 16 MiB,镜像同步提供商有界上游正文的不变量。
新增流式提供商 {#adding-a-new-streaming-provider}
- 在
tools/tts_streaming.py中子类化StreamingTTSProvider - 设置
sample_rate(若不是 int16 单声道,还要设channels/sample_width) - 实现
available()(纯探测——绝不安装任何东西)和stream(self, text) -> Iterator[bytes],产出原始 PCM 块 - 用
@register("yourname")装饰 - 在
tests/tools/test_tts_streaming.py添加测试
ABC 强制契约;注册表让提供商可被发现;调度器(stream_tts_to_speaker)和网关消费者免费处理句子缓冲、停止事件和音频 sink。
网关流式(平台适配器){#gateway-streaming-platform-adapters}
gateway/streaming_tts_consumer.py 把智能体增量桥接到适配器的流式音频接缝。适配器通过在 BasePlatformAdapter 上重写以下方法选择加入:
supports_streaming_tts(chat_id, audio_format) -> boolbegin_streaming_tts / write_streaming_tts / finish_streaming_tts / abort_streaming_tts
全部默认为不支持/no-op,因此既有适配器不受影响。当一轮次的流式音频完成时,该轮次的整文件自动 TTS 回复被抑制(不重复播放);当流式在任何音频可闻之前失败,网关回退到旧的整文件语音回复。