openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
515 lines (480 loc) • 12 kB
Markdown
summary: "The tts config block, per-provider settings, local speech engines, and override precedence"
title: "Text-to-speech configuration"
read_when:
- You are writing the tts block in openclaw.json
- You need the config snippet for one speech provider
- You want a local Speech Swift or speech-core engine
- You need per-agent, per-channel, or per-account voice overrides
## Configuration
TTS config lives under `tts` in `~/.openclaw/openclaw.json`. Pick a
preset and adapt the provider block. The `speakerVoice`/`speakerVoiceId`
fields shown below are canonical; each provider's own `voice`/`voiceId`/
`voiceName` field names still work as legacy aliases.
OpenRouter and DeepInfra use the first nonblank value from `speakerVoice`,
`speakerVoiceId`, `voice`, and `voiceId`, in that order, before the provider default.
Talk applies the same order to its provider block; when all four fields are absent
or blank, it keeps the base TTS voice.
<Tabs>
<Tab title="Azure Speech">
```json5
{
tts: {
auto: "always",
provider: "azure-speech",
providers: {
"azure-speech": {
apiKey: "${AZURE_SPEECH_KEY}",
region: "eastus",
speakerVoice: "en-US-JennyNeural",
lang: "en-US",
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
voiceNoteOutputFormat: "ogg-24khz-16bit-mono-opus",
},
},
},
}
```
</Tab>
<Tab title="ElevenLabs">
```json5
{
tts: {
auto: "always",
provider: "elevenlabs",
providers: {
elevenlabs: {
apiKey: "${ELEVENLABS_API_KEY}",
modelId: "eleven_multilingual_v2",
speakerVoiceId: "EXAVITQu4vr4xnSDxMaL",
},
},
},
}
```
</Tab>
<Tab title="Fish Audio">
```json5
{
tts: {
auto: "tagged",
provider: "fish-audio",
providers: {
"fish-audio": {
apiKey: "${FISH_API_KEY}",
model: "s2.1-pro",
speakerVoiceId: "802e3bc2b27e49c2995d23ef70e6ac89",
latency: "balanced",
},
},
},
}
```
</Tab>
<Tab title="Google Gemini">
```json5
{
tts: {
auto: "always",
provider: "google",
providers: {
google: {
apiKey: "${GEMINI_API_KEY}",
model: "gemini-3.8-flash-tts",
speakerVoice: "Kore",
// Optional natural-language style prompts:
// audioProfile: "Speak in a calm, podcast-host tone.",
// speakerName: "Alex",
},
},
},
}
```
</Tab>
<Tab title="Gradium">
```json5
{
tts: {
auto: "always",
provider: "gradium",
providers: {
gradium: {
apiKey: "${GRADIUM_API_KEY}",
speakerVoiceId: "YTpq7expH9539ERJ",
},
},
},
}
```
</Tab>
<Tab title="Inworld">
```json5
{
tts: {
auto: "always",
provider: "inworld",
providers: {
inworld: {
apiKey: "${INWORLD_API_KEY}",
modelId: "inworld-tts-1.5-max",
speakerVoiceId: "Sarah",
temperature: 0.7,
},
},
},
}
```
</Tab>
<Tab title="Local CLI">
```json5
{
tts: {
auto: "always",
provider: "tts-local-cli",
providers: {
"tts-local-cli": {
command: "say",
args: ["-o", "{{OutputPath}}", "{{Text}}"],
outputFormat: "wav",
timeoutMs: 120000,
},
},
},
}
```
</Tab>
<Tab title="Microsoft (no key)">
```json5
{
tts: {
auto: "always",
provider: "microsoft",
providers: {
microsoft: {
enabled: true,
speakerVoice: "en-US-MichelleNeural",
lang: "en-US",
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
rate: "+0%",
pitch: "+0%",
},
},
},
}
```
</Tab>
<Tab title="MiniMax">
```json5
{
tts: {
auto: "always",
provider: "minimax",
providers: {
minimax: {
apiKey: "${MINIMAX_API_KEY}",
model: "speech-2.8-hd",
speakerVoiceId: "English_expressive_narrator",
speed: 1.0,
vol: 1.0,
pitch: 0,
},
},
},
}
```
</Tab>
<Tab title="OpenAI + ElevenLabs">
```json5
{
tts: {
auto: "always",
provider: "openai",
summaryModel: "openai/gpt-4.1-mini",
modelOverrides: { enabled: true },
providers: {
openai: {
apiKey: "${OPENAI_API_KEY}",
model: "gpt-4o-mini-tts",
speakerVoice: "alloy",
},
elevenlabs: {
apiKey: "${ELEVENLABS_API_KEY}",
modelId: "eleven_multilingual_v2",
speakerVoiceId: "EXAVITQu4vr4xnSDxMaL",
voiceSettings: { stability: 0.5, similarityBoost: 0.75, style: 0.0, useSpeakerBoost: true, speed: 1.0 },
applyTextNormalization: "auto",
languageCode: "en",
},
},
},
}
```
</Tab>
<Tab title="OpenRouter">
```json5
{
tts: {
auto: "always",
provider: "openrouter",
providers: {
openrouter: {
apiKey: "${OPENROUTER_API_KEY}",
model: "hexgrad/kokoro-82m",
speakerVoice: "af_alloy",
responseFormat: "mp3",
},
},
},
}
```
</Tab>
<Tab title="Volcengine">
```json5
{
tts: {
auto: "always",
provider: "volcengine",
providers: {
volcengine: {
apiKey: "${VOLCENGINE_TTS_API_KEY}",
resourceId: "seed-tts-1.0",
speakerVoice: "en_female_anna_mars_bigtts",
},
},
},
}
```
</Tab>
<Tab title="xAI">
```json5
{
tts: {
auto: "always",
provider: "xai",
providers: {
xai: {
apiKey: "${XAI_API_KEY}",
speakerVoiceId: "eve",
language: "en",
responseFormat: "mp3",
},
},
},
}
```
</Tab>
<Tab title="Xiaomi MiMo">
```json5
{
tts: {
auto: "always",
provider: "xiaomi",
providers: {
xiaomi: {
apiKey: "${XIAOMI_API_KEY}",
model: "mimo-v2.5-tts",
speakerVoice: "mimo_default",
format: "mp3",
},
},
},
}
```
</Tab>
</Tabs>
For Xiaomi `mimo-v2.5-tts-voicedesign`, omit `speakerVoice` and set `style` to
the voice-design prompt. OpenClaw sends that prompt as the TTS `user` message
and does not send `audio.voice` for the voicedesign model.
### Local Speech Swift and speech-core
[Speech Swift](https://github.com/soniqo/speech-swift) and
[speech-core](https://github.com/soniqo/speech-core) provide local speech
inference across macOS, Linux, and Windows. Use the OpenAI-compatible HTTP
provider when Speech Swift and OpenClaw run on the same Mac. Use Local CLI for
direct executable integration on any supported host.
Install `ffmpeg` when a channel needs OpenClaw to convert WAV output to Opus or
raw PCM.
<Tabs>
<Tab title="macOS HTTP">
<Warning>
This HTTP setup requires Speech Swift v0.0.23 or later. If Homebrew already
installed an older version, run `brew update && brew upgrade speech` first.
</Warning>
Start Speech Swift's local server:
```bash
brew install speech
speech-server --port 8080
```
Point the OpenAI speech provider at its loopback endpoint. `responseFormat`
must be `wav` because the local endpoint does not emit compressed audio:
```json5
{
tts: {
auto: "always",
provider: "openai",
providers: {
openai: {
apiKey: "local",
baseUrl: "http://127.0.0.1:8080/v1",
model: "tts-1",
speakerVoice: "alloy",
responseFormat: "wav",
},
},
},
}
```
`tts-1` selects Kokoro. Speech Swift registry aliases such as `qwen3-tts`,
`cosyvoice`, and `voxcpm2` select other local engines. The placeholder API key
is required by OpenClaw's provider configuration but is not validated by the
loopback server.
</Tab>
<Tab title="macOS CLI">
The Homebrew `speech` executable can write directly to OpenClaw's
per-invocation output path:
```json5
{
tts: {
auto: "always",
provider: "tts-local-cli",
providers: {
"tts-local-cli": {
command: "speech",
args: ["speak", "{{Text}}", "--output", "{{OutputPath}}"],
outputFormat: "wav",
timeoutMs: 120000,
},
},
},
}
```
</Tab>
<Tab title="Linux CLI">
Install a speech-core Linux release package, download the ONNX model set once,
and verify synthesis before starting OpenClaw:
```bash
speech download-models
speech speak "Hello from OpenClaw" hello.wav
```
Then configure the packaged Kokoro command:
```json5
{
tts: {
auto: "always",
provider: "tts-local-cli",
providers: {
"tts-local-cli": {
command: "speech",
args: ["speak", "{{Text}}", "{{OutputPath}}"],
outputFormat: "wav",
timeoutMs: 120000,
},
},
},
}
```
See the [speech-core Linux CLI reference](https://github.com/soniqo/speech-core/blob/main/docs/cli.md)
for release packages and model-directory settings.
</Tab>
<Tab title="Windows CLI">
Download the speech-core Windows release, extract it, and install the ONNX
models once:
```powershell
$Version = "0.0.11"
$Url = "https://github.com/soniqo/speech-core/releases/download/v$Version/speech-$Version-windows-x64.zip"
Invoke-WebRequest $Url -OutFile speech.zip
Expand-Archive speech.zip
Set-Location "speech\speech-$Version-windows-x64\bin"
Set-ExecutionPolicy -Scope Process Bypass
.\speech_download_models.ps1
```
`Set-ExecutionPolicy -Scope Process Bypass` lets the unsigned
`speech_download_models.ps1` run in this shell only. `-Scope Process` does not
change the machine or user execution policy, and the relaxation ends when the
shell exits.
Then point Local CLI at the packaged Kokoro executable. Replace `C:\path\to`
with your extraction directory, and `0.0.11` with the `$Version` you downloaded:
```json5
{
tts: {
auto: "always",
provider: "tts-local-cli",
providers: {
"tts-local-cli": {
command: "C:\\path\\to\\speech-0.0.11-windows-x64\\bin\\speech_synthesize.exe",
args: ["{{OutputPath}}", "{{Text}}", "en"],
outputFormat: "wav",
timeoutMs: 120000,
},
},
},
}
```
See the [speech-core Windows CLI reference](https://github.com/soniqo/speech-core/blob/main/docs/cli.md)
for the packaged server, model cache, and standalone command syntax.
</Tab>
</Tabs>
### Per-agent voice overrides
Use `agents.entries.*.tts` when one agent should speak with a different provider,
voice, model, persona, or auto-TTS mode. The agent block deep-merges over
`tts`, so provider credentials can stay in the global provider config:
```json5
{
tts: {
auto: "always",
provider: "elevenlabs",
providers: {
elevenlabs: { apiKey: "${ELEVENLABS_API_KEY}", modelId: "eleven_multilingual_v2" },
},
},
agents: {
entries: {
reader: {
tts: {
providers: {
elevenlabs: { speakerVoiceId: "EXAVITQu4vr4xnSDxMaL" },
},
},
},
},
},
}
```
To pin a per-agent persona, set `agents.entries.*.tts.persona` alongside provider
config — it overrides the global `tts.persona` for that agent only.
Precedence order for automatic replies, `/tts audio`, `/tts status`, and the
`tts` agent tool. Later layers win: each layer deep-merges over the ones above
it, so the last layer that sets a field decides its value.
1. `tts`
2. active `agents.entries.*.tts`
3. channel override, when the channel supports `channels.<channel>.tts`
4. account override, when the channel passes `channels.<channel>.accounts.<id>.tts`
5. local `/tts` preferences for this host
6. inline `[[tts:...]]` directives when [model overrides](/tools/tts/commands#model-driven-directives) are enabled
Channel and account overrides use the same shape as `tts` and
deep-merge over the earlier layers, so shared provider credentials can stay in
`tts` while a channel or bot account changes only speaker voice, model, persona,
or auto mode:
```json5
{
tts: {
provider: "openai",
providers: {
openai: { apiKey: "${OPENAI_API_KEY}", model: "gpt-4o-mini-tts" },
},
},
channels: {
feishu: {
accounts: {
english: {
tts: {
providers: {
openai: { speakerVoice: "shimmer" },
},
},
},
},
},
},
}
```