流式 TTS

Hermes 可以在 TTS 音频从提供商到达时即流式播放,而不必等整段音频生成完再播放。这一能力用于语音模式(CLI/TUI 实时对话)、dashboard speak-stream WebSocket,以及——通过网关 StreamingTTSConsumer——任何选择流式音频的平台适配器。语音回复在第一个分句后就开始播报,而非等完整生成 + 合成。

架构 {#architecture}

流式管线有四部分:

  1. 生产者——LLM 在生成响应时发出文本增量
  2. 句子分块器——tools.tts_streaming.SentenceChunker 累积增量、剥离 <think> 块(即使跨增量拆分),并输出完整句子
  3. TTS 提供商——一个已注册的 StreamingTTSProvider 把每个句子转成原始 PCM 块(按提供商声明的 sample_rate,int16 单声道)
  4. 音频 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 → 托管网关
geminiSSE(streamGenerateContent?alt=sse)是GEMINI_API_KEY / GOOGLE_API_KEY
xaiWebSocket(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}

  1. 在 tools/tts_streaming.py 中子类化 StreamingTTSProvider
  2. 设置 sample_rate(若不是 int16 单声道,还要设 channels / sample_width)
  3. 实现 available()(纯探测——绝不安装任何东西)和 stream(self, text) -> Iterator[bytes],产出原始 PCM 块
  4. 用 @register("yourname") 装饰
  5. 在 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) -> bool
  • begin_streaming_tts / write_streaming_tts / finish_streaming_tts / abort_streaming_tts

全部默认为不支持/no-op,因此既有适配器不受影响。当一轮次的流式音频完成时,该轮次的整文件自动 TTS 回复被抑制(不重复播放);当流式在任何音频可闻之前失败,网关回退到旧的整文件语音回复。