Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ StreamCore starts one layer below prompt-and-tool frameworks: the media path. Yo

Details and code: [Bring your own agent](./docs/bring-your-own-agent.md) · [Agent runtime](./docs/agent-runtime.md).

Providers: Deepgram, AssemblyAI, OpenAI, Cartesia, ElevenLabs, MiniMax, Speechify, Ollama, VibeVoice (local), xAI Grok Voice (speech-to-speech), pgvector/Supabase for retrieval. See [Providers](./docs/providers.md).
Providers: Deepgram, AssemblyAI, OpenAI, Cartesia, ElevenLabs, MiniMax, Speechify, Telnyx, Ollama, VibeVoice (local), xAI Grok Voice (speech-to-speech), pgvector/Supabase for retrieval. See [Providers](./docs/providers.md).

OpenAI STT supports `whisper-1`, `gpt-4o-transcribe`, and `gpt-4o-mini-transcribe` through the independent `openai.stt_model` setting.

Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ StreamCore 位于「提示词 + 工具」类框架的下一层:媒体链路。

详情与代码:[接入你自己的智能体](./docs/bring-your-own-agent.zh-CN.md) · [智能体运行时](./docs/agent-runtime.zh-CN.md)。

服务商:Deepgram、AssemblyAI、OpenAI、Cartesia、ElevenLabs、MiniMax、Speechify、Ollama、VibeVoice(本地)、xAI Grok Voice(语音到语音),检索支持 pgvector / Supabase。见[服务商](./docs/providers.zh-CN.md)。
服务商:Deepgram、AssemblyAI、OpenAI、Cartesia、ElevenLabs、MiniMax、Speechify、Telnyx、Ollama、VibeVoice(本地)、xAI Grok Voice(语音到语音),检索支持 pgvector / Supabase。见[服务商](./docs/providers.zh-CN.md)。

OpenAI STT 可通过独立的 `openai.stt_model` 配置选择 `whisper-1`、`gpt-4o-transcribe` 或 `gpt-4o-mini-transcribe`。

Expand Down
22 changes: 15 additions & 7 deletions config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ provider = "deepgram" # Supported: aliyun, assemblyai, deepgram, openai, vibev
provider = "openai" # Supported: openai, ollama, agent

[tts]
provider = "cartesia" # Supported: aliyun, cartesia, deepgram, elevenlabs, mimo, minimax, speechify, vibevoice, volcengine
provider = "cartesia" # Supported: aliyun, cartesia, deepgram, elevenlabs, mimo, minimax, speechify, telnyx, vibevoice, volcengine

# Provider credentials

Expand Down Expand Up @@ -101,12 +101,13 @@ model = "gpt-4o-mini"
stt_model = "whisper-1" # STT model: whisper-1, gpt-4o-transcribe, or gpt-4o-mini-transcribe
system_prompt = "You are a helpful AI voice assistant. Keep your responses concise and conversational."
base_url = "" # Optional. Any endpoint speaking OpenAI's protocol — DeepSeek
# (https://api.deepseek.com/v1, model deepseek-chat), Moonshot
# (https://api.moonshot.cn/v1), Qwen, MiniMax. Empty targets OpenAI.
# Prefer a non-reasoning model here: reasoning models emit their
# thinking before the answer, and on a voice call that is dead air.
# Note this is unrelated to [ollama] base_url, which speaks Ollama's
# own protocol — the two are not interchangeable
# (https://api.deepseek.com/v1, model deepseek-chat), Moonshot
# (https://api.moonshot.cn/v1), Qwen, MiniMax, Telnyx
# (https://api.telnyx.com/v2/ai, e.g. model glm-5.3). Empty targets OpenAI.
# Prefer a non-reasoning model here: reasoning models emit their
# thinking before the answer, and on a voice call that is dead air.
# Note this is unrelated to [ollama] base_url, which speaks Ollama's
# own protocol — the two are not interchangeable

[ollama]
base_url = "http://localhost:11434" # Ollama server URL
Expand Down Expand Up @@ -141,6 +142,13 @@ api_key = "" # Required if tts.provider = "speechify"
voice_id = "" # Optional; defaults to Geffen (geffen_32). Simba 3.2 uses its curated voice set.
model = "" # Optional; defaults to simba-3.2

[telnyx]
api_key = "" # Required if tts.provider = "telnyx"
voice = "Telnyx.Qwen3TTS.d9348e0d-988a-42cc-a64e-18093fe45c03" # Any catalog voice from GET /v2/text-to-speech/voices
# (Qwen3TTS voices use UUID ids). Availability varies by account; a voice your
# key is not provisioned for fails the dial with HTTP 403
voice_speed = 1.0 # Playback-rate multiplier, clamped to 0.8-1.2 like the per-utterance delivery tags

[mimo]
api_key = "" # Required if tts.provider = "mimo"
voice = "" # Optional; defaults to mimo_default. Chinese: 茉莉 冰糖 苏打 白桦; English: Mia Chloe Milo Dean
Expand Down
8 changes: 7 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ provider = "deepgram" # aliyun | assemblyai | deepgram | openai |
provider = "openai" # openai | ollama | agent

[tts]
provider = "cartesia" # cartesia | deepgram | elevenlabs | mimo | minimax | speechify | vibevoice
provider = "cartesia" # cartesia | deepgram | elevenlabs | mimo | minimax | speechify | telnyx | vibevoice

# [grok] # Used when realtime.provider = "grok"
# api_key = ""
Expand Down Expand Up @@ -119,6 +119,11 @@ api_key = ""
voice_id = ""
model = ""

[telnyx] # Telnyx hosted synthesis, used when tts.provider = "telnyx"
api_key = ""
voice = "Telnyx.Qwen3TTS.d9348e0d-988a-42cc-a64e-18093fe45c03" # Any catalog voice from GET /v2/text-to-speech/voices; availability varies by account
voice_speed = 1.0 # Playback-rate multiplier, clamped to 0.8-1.2

[minimax]
api_key = ""
voice_id = "" # Defaults to English_Graceful_Lady; 40+ languages available
Expand Down Expand Up @@ -198,6 +203,7 @@ Provider keys use each provider's conventional variable name; secrets owned by t
| `CARTESIA_API_KEY` | `cartesia.api_key` |
| `ELEVENLABS_API_KEY` | `elevenlabs.api_key` |
| `SPEECHIFY_API_KEY` | `speechify.api_key` |
| `TELNYX_API_KEY` | `telnyx.api_key` |
| `MINIMAX_API_KEY` | `minimax.api_key` |
| `MIMO_API_KEY` | `mimo.api_key` |
| `SUPABASE_API_KEY` | `supabase.api_key` |
Expand Down
8 changes: 7 additions & 1 deletion docs/configuration.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ provider = "deepgram" # aliyun | assemblyai | deepgram | openai |
provider = "openai" # openai | ollama | agent

[tts]
provider = "cartesia" # cartesia | deepgram | elevenlabs | mimo | minimax | speechify | vibevoice
provider = "cartesia" # cartesia | deepgram | elevenlabs | mimo | minimax | speechify | telnyx | vibevoice

# [grok] # Used when realtime.provider = "grok"
# api_key = ""
Expand Down Expand Up @@ -113,6 +113,11 @@ api_key = ""
voice_id = ""
model = ""

[telnyx] # Telnyx 托管合成,当 tts.provider = "telnyx" 时使用
api_key = ""
voice = "Telnyx.Qwen3TTS.d9348e0d-988a-42cc-a64e-18093fe45c03" # GET /v2/text-to-speech/voices 目录中的任意音色;可用性因账号而异
voice_speed = 1.0 # 播放速率倍数,限制在 0.8-1.2

[minimax]
api_key = ""
voice_id = "" # Defaults to English_Graceful_Lady; 40+ languages available
Expand Down Expand Up @@ -181,6 +186,7 @@ voice = "en-Emma_woman"
| `CARTESIA_API_KEY` | `cartesia.api_key` |
| `ELEVENLABS_API_KEY` | `elevenlabs.api_key` |
| `SPEECHIFY_API_KEY` | `speechify.api_key` |
| `TELNYX_API_KEY` | `telnyx.api_key` |
| `MINIMAX_API_KEY` | `minimax.api_key` |
| `MIMO_API_KEY` | `mimo.api_key` |
| `SUPABASE_API_KEY` | `supabase.api_key` |
Expand Down
25 changes: 24 additions & 1 deletion docs/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
|------|-----------|----------------------|
| STT | `aliyun`, `assemblyai`, `deepgram`, `openai`, `vibevoice`, `volcengine` | Matching provider API key, or a local VibeVoice ASR server |
| LLM | `openai`, `ollama`, `agent` | OpenAI API key, an Ollama instance you control, or your own HTTP agent endpoint |
| TTS | `cartesia`, `deepgram`, `elevenlabs`, `mimo`, `minimax`, `speechify`, `vibevoice` | Matching provider API key, or a local VibeVoice TTS server |
| TTS | `cartesia`, `deepgram`, `elevenlabs`, `mimo`, `minimax`, `speechify`, `telnyx`, `vibevoice` | Matching provider API key, or a local VibeVoice TTS server |
| Speech-to-speech | `grok` | xAI API key — replaces STT, LLM, and TTS together |
| RAG (optional) | `pgvector`, `supabase` | Postgres connection string or Supabase URL + key, plus an OpenAI key for embeddings |

Expand All @@ -17,6 +17,7 @@ Notes:
- `llm.provider = "agent"` POSTs each turn to an HTTP endpoint you host; your agent owns memory, prompting, and tools, and replies stream back as SSE, chunked text, or JSON. See [Bring your own agent](./bring-your-own-agent.md).
- `stt.provider = "vibevoice"` and `tts.provider = "vibevoice"` use local models; start the Python sidecars first.
- `tts.provider = "minimax"` covers 40+ languages and is the strongest option for Mandarin. See [MiniMax TTS](#minimax-tts) for the region and model-plan caveats.
- `tts.provider = "telnyx"` is Telnyx hosted synthesis over a per-utterance WebSocket; voice availability varies by account. See [Telnyx TTS](#telnyx-tts) for the connection model and the voice catalog.
- `tts.provider = "mimo"` is Xiaomi's MiMo TTS, with Chinese and English voices and optional voice cloning on the paid models.
- `stt.provider = "aliyun"` is Alibaba Cloud Model Studio (DashScope) streaming ASR; `vocabulary_id` biases it toward domain terms.
- `stt.provider = "volcengine"` is Doubao streaming ASR — useful where Deepgram is slow to reach or its Mandarin is not good enough. The console gives a free hourly allowance.
Expand Down Expand Up @@ -125,6 +126,28 @@ Three things to get right:

Delivery tags map onto MiniMax's emotion enum: `[warm]` and `[excited]` become `happy`, `[calm]` and `[empathetic]` become `calm`. `[empathetic]` deliberately lands on `calm` rather than `sad`, which overshoots into sounding upset on apologies and bad news. Tags with no emotion mapping still take effect through speed, which is clamped to MiniMax's 0.5–2.0 range.

## Telnyx TTS

Telnyx hosted speech synthesis over WebSocket, streaming linear16 at the pipeline's native 16 kHz so the audio path never resamples.

```toml
[tts]
provider = "telnyx"

[telnyx]
api_key = ""
voice = "Telnyx.Qwen3TTS.d9348e0d-988a-42cc-a64e-18093fe45c03"
voice_speed = 1.0
```

Three things to know:

- **One WebSocket per utterance.** The protocol has no per-utterance completion marker; `isFinal` arrives only after the client sends an empty-text teardown, so the client dials a fresh connection for each utterance: init, text, teardown, collect audio until the final frame, server closes. Measured against a persistent connection this costs nothing on the live path: synthesis outpaces playback (~2.4x) and first audio arrives well under a second after dial.
- **Voices are per-account.** `voice` is any catalog name from `GET /v2/text-to-speech/voices`, and availability varies by account; a voice your key is not provisioned for fails the WebSocket handshake with HTTP 403 rather than erroring mid-call. The config default (`Telnyx.Qwen3TTS.d9348e0d-988a-42cc-a64e-18093fe45c03`) is a verified Qwen3TTS voice (Delta, female in the catalog); not a guarantee for every key.
- **Telnyx LLMs need no new provider.** `openai.base_url = "https://api.telnyx.com/v2/ai"` points the existing `openai` LLM provider at Telnyx inference (e.g. model `glm-5.3`) with zero code.

Delivery tags map onto `voice_speed` (clamped to 0.8–1.2, the same conversational band as Cartesia), and `voice_speed` in config sets the baseline pace for untagged sentences.

## Local VibeVoice setup

VibeVoice provides fully local STT and TTS with no API keys, using [VibeVoice-ASR](https://huggingface.co/mlx-community/VibeVoice-ASR-4bit) for recognition and [VibeVoice-Realtime-0.5B](https://huggingface.co/mlx-community/VibeVoice-Realtime-0.5B-6bit) for synthesis via two lightweight Python sidecars. On Apple Silicon they use [mlx-audio](https://github.com/Blaizzy/mlx-audio) (MLX); on Linux/Windows they fall back to PyTorch automatically.
Expand Down
25 changes: 24 additions & 1 deletion docs/providers.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
|------|-----------|----------------------|
| STT | `aliyun`、`assemblyai`、`deepgram`、`openai`、`vibevoice`、`volcengine` | 对应服务商的 API key,或一个本地 VibeVoice ASR 服务 |
| LLM | `openai`、`ollama`、`agent` | OpenAI API key、你自己掌控的 Ollama 实例,或你自己的 HTTP 智能体端点 |
| TTS | `cartesia`、`deepgram`、`elevenlabs`、`mimo`、`minimax`、`speechify`、`vibevoice` | 对应服务商的 API key,或一个本地 VibeVoice TTS 服务 |
| TTS | `cartesia`、`deepgram`、`elevenlabs`、`mimo`、`minimax`、`speechify`、`telnyx`、`vibevoice` | 对应服务商的 API key,或一个本地 VibeVoice TTS 服务 |
| 语音到语音 | `grok` | xAI API key —— 一并取代 STT、LLM 与 TTS |
| RAG(可选) | `pgvector`、`supabase` | Postgres 连接串或 Supabase URL + key,另需 OpenAI key 用于 embedding |

Expand All @@ -17,6 +17,7 @@
- `llm.provider = "agent"` 把每一轮对话 POST 到你托管的 HTTP 端点;记忆、提示词与工具都由你的智能体掌控,回复以 SSE、分块文本或 JSON 流式返回。见[接入你自己的智能体](./bring-your-own-agent.zh-CN.md)。
- `stt.provider = "vibevoice"` 与 `tts.provider = "vibevoice"` 使用本地模型;请先启动 Python 边车进程。
- `tts.provider = "minimax"` 覆盖 40+ 语言,是中文场景下最强的选项。区域与套餐相关的坑见 [MiniMax TTS](#minimax-tts)。
- `tts.provider = "telnyx"` 是 Telnyx 托管合成,每个话语一条 WebSocket 连接;音色可用性因账号而异。连接模型与音色目录见 [Telnyx TTS](#telnyx-tts)。
- `tts.provider = "mimo"` 是小米 MiMo TTS,中英文音色齐备,付费模型还支持声音克隆。
- `stt.provider = "aliyun"` 是阿里云百炼(DashScope)流式 ASR;`vocabulary_id` 可以把模型往你的领域词上带。
- `stt.provider = "volcengine"` 是豆包流式 ASR —— 适合 Deepgram 访问慢、或它的中文识别不够好的场景。控制台有免费时长可以先试。
Expand Down Expand Up @@ -125,6 +126,28 @@ model = "speech-2.6-turbo"

表达标签映射到 MiniMax 的情绪枚举:`[warm]` 与 `[excited]` 变成 `happy`,`[calm]` 与 `[empathetic]` 变成 `calm`。`[empathetic]` 刻意落在 `calm` 而不是 `sad` —— 后者在道歉与坏消息场景下会过头,听起来像在难过。没有情绪映射的标签仍会通过语速生效,语速被限制在 MiniMax 的 0.5–2.0 范围内。

## Telnyx TTS

Telnyx 托管语音合成,走 WebSocket,以流水线原生的 16 kHz linear16 流式输出,音频路径无需任何重采样。

```toml
[tts]
provider = "telnyx"

[telnyx]
api_key = ""
voice = "Telnyx.Qwen3TTS.d9348e0d-988a-42cc-a64e-18093fe45c03"
voice_speed = 1.0
```

有三件事必须弄对:

- **每个话语一条 WebSocket 连接。** 协议没有逐话语的完成标记:只有客户端发出空文本 teardown 后,服务端才会发 `isFinal`,因此客户端为每个话语新建一条连接:init、文本、teardown、收齐音频直到 final 帧、服务端关闭。与常驻连接相比,这在实时链路上没有代价:合成速度约为播放的 2.4 倍,拨号后不到一秒就有首个音频到达。
- **音色因账号而异。** `voice` 是 `GET /v2/text-to-speech/voices` 目录中的任意名称,可用性因账号而异:你的 key 未开通的音色会在 WebSocket 握手阶段就返回 HTTP 403,而不是在通话中途报错。配置默认值(`Telnyx.Qwen3TTS.d9348e0d-988a-42cc-a64e-18093fe45c03`)是一个已验证的 Qwen3TTS 音色(目录中名为 Delta,女声),并不对每个 key 都保证可用。
- **Telnyx 的 LLM 无需新增服务商。** `openai.base_url = "https://api.telnyx.com/v2/ai"` 即可让现有的 `openai` LLM 服务商直连 Telnyx 推理(例如模型 `glm-5.3`),零代码改动。

表达标签映射到 `voice_speed`(限制在 0.8–1.2,与 Cartesia 相同的对话档位),配置里的 `voice_speed` 则是未打标签句子的基准语速。

## 本地 VibeVoice 配置

VibeVoice 提供完全本地、无需 API key 的 STT 与 TTS:识别用 [VibeVoice-ASR](https://huggingface.co/mlx-community/VibeVoice-ASR-4bit),合成用 [VibeVoice-Realtime-0.5B](https://huggingface.co/mlx-community/VibeVoice-Realtime-0.5B-6bit),通过两个轻量 Python 边车进程运行。在 Apple Silicon 上使用 [mlx-audio](https://github.com/Blaizzy/mlx-audio)(MLX);在 Linux/Windows 上自动回退到 PyTorch。
Expand Down
19 changes: 19 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Config struct {
Cartesia CartesiaConfig `toml:"cartesia"`
ElevenLabs ElevenLabsConfig `toml:"elevenlabs"`
Speechify SpeechifyConfig `toml:"speechify"`
Telnyx TelnyxConfig `toml:"telnyx"`
MiniMax MiniMaxConfig `toml:"minimax"`
MiMo MiMoConfig `toml:"mimo"`
Pgvector PgvectorConfig `toml:"pgvector"`
Expand Down Expand Up @@ -292,6 +293,20 @@ type SpeechifyConfig struct {
Model string `toml:"model"`
}

// TelnyxConfig configures Telnyx hosted speech synthesis, used when
// tts.provider = "telnyx".
type TelnyxConfig struct {
APIKey string `toml:"api_key"`
// Voice is a catalog voice from GET /v2/text-to-speech/voices. Empty
// defaults to the Qwen3TTS voice Delta (Telnyx.Qwen3TTS.d9348e0d-988a-42cc-a64e-18093fe45c03). Availability varies by account, and
// a voice the account is not provisioned for fails the WebSocket
// handshake with HTTP 403.
Voice string `toml:"voice"`
// VoiceSpeed is a playback-rate multiplier (1.0 = normal), clamped to
// the 0.8-1.2 conversational band. Zero defaults to 1.0.
VoiceSpeed float64 `toml:"voice_speed"`
}

type MiMoConfig struct {
APIKey string `toml:"api_key"`
// Voice is a built-in voice name, passed through verbatim — the Chinese
Expand Down Expand Up @@ -434,6 +449,9 @@ func Load(path string) (*Config, error) {
if cfg.Cartesia.MaxConcurrency == 0 {
cfg.Cartesia.MaxConcurrency = 3
}
if cfg.Telnyx.VoiceSpeed == 0 {
cfg.Telnyx.VoiceSpeed = 1.0
}
setDefault(&cfg.LLM.Provider, "openai")
setDefault(&cfg.TTS.Provider, "cartesia")
setDefault(&cfg.OpenAI.Model, "gpt-4o-mini")
Expand Down Expand Up @@ -549,6 +567,7 @@ var envOverrides = []struct {
{"CARTESIA_API_KEY", func(c *Config) *string { return &c.Cartesia.APIKey }},
{"ELEVENLABS_API_KEY", func(c *Config) *string { return &c.ElevenLabs.APIKey }},
{"SPEECHIFY_API_KEY", func(c *Config) *string { return &c.Speechify.APIKey }},
{"TELNYX_API_KEY", func(c *Config) *string { return &c.Telnyx.APIKey }},
{"MINIMAX_API_KEY", func(c *Config) *string { return &c.MiniMax.APIKey }},
{"MIMO_API_KEY", func(c *Config) *string { return &c.MiMo.APIKey }},
{"SUPABASE_API_KEY", func(c *Config) *string { return &c.Supabase.APIKey }},
Expand Down
7 changes: 6 additions & 1 deletion internal/tts/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ func newProviderClient(cfg *config.Config) (Client, error) {
return nil, ErrMissingAPIKey{Provider: "speechify", Field: "[speechify] api_key"}
}
return NewSpeechifyClient(cfg.Speechify.APIKey, cfg.Speechify.VoiceID, cfg.Speechify.Model), nil
case "telnyx":
if cfg.Telnyx.APIKey == "" {
return nil, ErrMissingAPIKey{Provider: "telnyx", Field: "[telnyx] api_key"}
}
return NewTelnyxClient(cfg.Telnyx.APIKey, cfg.Telnyx.Voice, cfg.Telnyx.VoiceSpeed), nil
case "minimax":
if cfg.MiniMax.APIKey == "" {
return nil, ErrMissingAPIKey{Provider: "minimax", Field: "[minimax] api_key"}
Expand All @@ -96,7 +101,7 @@ func newProviderClient(cfg *config.Config) (Client, error) {
case "vibevoice":
return NewVibeVoiceClient(cfg.VibeVoice.TTSURL, cfg.VibeVoice.Voice), nil
default:
return nil, fmt.Errorf("unknown tts provider %q (supported: aliyun, cartesia, deepgram, elevenlabs, mimo, minimax, speechify, vibevoice, volcengine)", cfg.TTS.Provider)
return nil, fmt.Errorf("unknown tts provider %q (supported: aliyun, cartesia, deepgram, elevenlabs, mimo, minimax, speechify, telnyx, vibevoice, volcengine)", cfg.TTS.Provider)
}
}

Expand Down
Loading
Loading