OpenClaw 全平台 TTS 接入手册
这个系列是什么:完整的技术 runbook——直接交给 Claude Code 执行就行。复制手册、替换占位符(
YOUR_FISH_API_KEY、YOUR_VOLC_APP_ID等),发给 Agent,让它按步骤来。背后的故事在实战录同名篇。
OpenClaw 语音合成:全平台 TTS 配置指南
覆盖 OpenClaw 支持的全部五个 TTS 服务商。每个章节独立,直接跳到你需要的服务商。
目录
1. 前置条件
配置任何 TTS 服务商之前,确保:
- 框架已安装并运行
- 你有
openclaw.json(网关配置文件)的访问权限 - 你知道怎么重启网关或用
kill -USR1热加载
TTS 配置在 openclaw.json 的 messages.tts 路径下。所有服务商配置都嵌套在这里。
2. Fish Audio 配置
Fish Audio 输出 OGG/Opus 格式——Telegram 语音气泡直接用,不用转码。
第 1 步:拿 API 密钥
在 fish.audio 注册账号,从控制台拿 API 密钥。
第 2 步:挑一个声音
浏览音色库或克隆自己的声音。复制 reference ID。
第 3 步:写入 openclaw.json
{
messages: {
tts: {
auto: "always", // 或 "tagged"(LLM 自己决定什么时候发声)/ "inbound"(只在回复语音时发声)
provider: "fishaudio",
fishaudio: {
apiKey: "YOUR_FISH_API_KEY", // 或设环境变量 FISH_API_KEY
referenceId: "YOUR_VOICE_REFERENCE_ID", // 来自 fish.audio 音色库
},
},
},
}
第 4 步:重启网关
kill -USR1 $(pgrep -f openclaw) # 热加载
第 5 步:验证
/tts status
预期输出:Provider: fishaudio (configured)。
测试:
/tts audio 你好,我是 Fish Audio
配置字段
| 字段 | 环境变量回退 | 说明 |
|---|---|---|
apiKey | FISH_API_KEY | Fish Audio API 密钥 |
referenceId | — | fish.audio 音色模型 ID(留空用默认音色) |
API 详情
POST https://api.fish.audio/v1/tts
Headers: Authorization: Bearer <apiKey>, model: s1
Body: { "text": "...", "reference_id": "...", "format": "opus", "opus_bitrate": 64 }
返回原始 Opus 音频缓冲区。输出文件为 .ogg——Telegram 自动识别为语音气泡。
注意
- Fish Audio 不支持情感标记。
[方括号]会被直接念出来。 - 输出格式为 OGG/Opus——Telegram 自动显示为语音气泡。
referenceId为空时用默认音色。
3. 火山引擎 v2 配置(情感控制)
火山引擎 v2 用 seed-tts-2.0 声音克隆模型,通过 context_texts API 参数实现 LLM 驱动的情感控制。说白了就是:LLM 在每个句子前加 [情感] 标记,框架解析后逐句调 API 并附加情感指令,最后拼接成一条完整的语音消息。
第 1 步:注册火山引擎
第 2 步:开通语音合成服务
开通"语音合成",从控制台拿 App ID 和 Access Token。
第 3 步:克隆声音(推荐)
在火山控制台克隆声音。记下 speaker ID——以 S_ 开头(如 S_EVeoGUVU1)。没有克隆声音的话可以用内置音色如 zh_female_linzhiling_mars_bigtts。
第 4 步:写入 openclaw.json
{
messages: {
tts: {
auto: "tagged", // "tagged" = LLM 决定什么时候发声;"always" = 每条都发
provider: "volcano",
volcano: {
appId: "YOUR_VOLC_APP_ID", // 或环境变量 VOLC_TTS_APP_ID
accessKey: "YOUR_VOLC_ACCESS_KEY", // 或环境变量 VOLC_TTS_ACCESS_TOKEN
version: "v2", // 情感控制必须设为 v2
speaker: "YOUR_CLONED_VOICE_ID", // 如 "S_EVeoGUVU1"
// resourceId 自动默认为 "volc.seedicl.default"(v2)
},
},
},
}
第 5 步:重启网关
kill -USR1 $(pgrep -f openclaw) # 热加载
第 6 步:验证
/tts status
预期输出:Provider: volcano v2 (configured)。
测试:
/tts audio [开心]你好呀!
配置字段
| 字段 | 环境变量回退 | 默认值 | 说明 |
|---|---|---|---|
appId | VOLC_TTS_APP_ID | — | 火山应用 ID |
accessKey | VOLC_TTS_ACCESS_TOKEN | — | 火山访问令牌 |
version | — | "v1" | 必须设为 "v2" 才有情感控制 |
resourceId | — | "volc.seedicl.default" | 模型资源,会根据 ID 自动检测 |
speaker | — | "zh_female_linzhiling_mars_bigtts" | 发音人或克隆声音 ID。克隆声音用 S_ 前缀 |
情感标记语法
LLM 用三种风格(都传给 context_texts):
情感标签(简短关键词):
[开心]你好呀!今天天气真好!
[伤心]可是我的猫生病了。
[愤怒]这太过分了!
语音指令(描述性指示):
[用温柔甜蜜的声音]晚安,好梦。
[用冷淡不耐烦的语气]随便你吧。
[用激动兴奋的声音]我们赢了!
情境描述(场景叙述):
[她正在生气地质问对方]你到底去哪儿了?
[他刚收到好消息非常开心]太好了,我通过了!
端到端数据流
LLM 生成: "[开心]你好呀![伤心]我好难过。"
|
v
第 1 步:系统提示词注入 — 告诉 LLM 用 [方括号] 标记
|
v
第 2 步:消息处理 — 从用户可见文本中去掉 [标记],
| 保留给 TTS 输入
v
第 3 步:TTS 分发 — 检测到 v2,进入 v2 路径
|
v
第 4 步:情感解析 — 拆分成片段
| 片段 1: { emotion: "开心", text: "你好呀!" }
| 片段 2: { emotion: "伤心", text: "我好难过。" }
v
第 5 步:逐片段调 API,附带情感上下文
| POST .../api/v3/tts/unidirectional
| req_params.additions = {"context_texts":["开心"]}
v
第 6 步:拼接缓冲区 — 合并 MP3 片段
|
v
Telegram 上显示为语音气泡 (audioAsVoice: true)
用户看到: "你好呀!我好难过。"(没有方括号)
API 详情
端点(v1 和 v2 相同):
POST https://openspeech.bytedance.com/api/v3/tts/unidirectional
每个片段的请求体:
{
"user": { "uid": "tts-client" },
"req_params": {
"text": "你好呀!今天天气真好!",
"speaker": "S_EVeoGUVU1",
"audio_params": { "format": "mp3", "sample_rate": 24000 },
"additions": "{\"context_texts\":[\"开心\"]}"
}
}
响应:流式二进制 MP3 块(从 JSON 帧响应中解析,base64 data 字段)。
v1 vs v2
| v1 | v2 | |
|---|---|---|
| 资源 ID | seed-tts-1.0 | volc.seedicl.default |
| 情感控制 | 没有 | 逐句 context_texts |
| 输出 | 单段音频,无情感 | 拼接片段,有情感变化 |
| Telegram 语音泡 | 不行(MP3,非语音兼容) | 可以(voiceCompatible: true) |
| 标记处理 | [方括号] 被去除 | 显示时去除,TTS 输入时保留 |
注意
version: "v2"必填。 不设就走 v1(没情感,没语音气泡)。context_texts只影响每次 API 调用的第一句。 所以框架要逐句拆分调用。[方括号]标记不能提前去掉。 只从用户可见文本中移除。- MP3 输出在 Telegram 上能显示语音气泡。 v2 设了
voiceCompatible: true。
4. ElevenLabs 配置
第 1 步:拿 API 密钥
在 elevenlabs.io 注册,从控制台拿 API 密钥。
第 2 步:选声音
浏览音色库或克隆自己的声音。复制 voice ID。
第 3 步:写入 openclaw.json
{
messages: {
tts: {
auto: "always",
provider: "elevenlabs",
elevenlabs: {
apiKey: "YOUR_ELEVENLABS_API_KEY", // 或环境变量 ELEVENLABS_API_KEY
voiceId: "YOUR_ELEVENLABS_VOICE_ID",
modelId: "eleven_multilingual_v2",
voiceSettings: {
stability: 0.5,
similarityBoost: 0.75,
style: 0.0,
useSpeakerBoost: true,
speed: 1.0,
},
},
},
},
}
第 4 步:重启网关
kill -USR1 $(pgrep -f openclaw) # 热加载
第 5 步:验证
/tts status
预期输出:Provider: elevenlabs (configured)。
测试:
/tts audio 你好,我是 ElevenLabs
配置字段
| 字段 | 环境变量回退 | 说明 |
|---|---|---|
apiKey | ELEVENLABS_API_KEY / XI_API_KEY | ElevenLabs API 密钥 |
baseUrl | — | 覆盖 API 基础 URL(默认 https://api.elevenlabs.io) |
voiceId | — | 音色 ID |
modelId | — | 模型(如 eleven_multilingual_v2) |
seed | — | 整数 0..4294967295(尽力确定性) |
applyTextNormalization | — | auto、on 或 off |
languageCode | — | 两字母 ISO 639-1 代码(如 en、zh) |
voiceSettings.stability | — | 0..1 — 越低越有表现力 |
voiceSettings.similarityBoost | — | 0..1 — 越高越接近原声 |
voiceSettings.style | — | 0..1 |
voiceSettings.useSpeakerBoost | — | true 或 false |
voiceSettings.speed | — | 0.5..2.0(1.0 正常语速) |
输出格式
- Telegram:Opus 语音消息(
opus_48000_64— 48kHz / 64kbps,语音气泡必需) - 其他平台:MP3(
mp3_44100_128— 44.1kHz / 128kbps)
5. OpenAI TTS 配置
第 1 步:拿 API 密钥
在 platform.openai.com 创建 API 密钥。
第 2 步:写入 openclaw.json
{
messages: {
tts: {
auto: "always",
provider: "openai",
openai: {
apiKey: "YOUR_OPENAI_API_KEY", // 或环境变量 OPENAI_API_KEY
model: "gpt-4o-mini-tts",
voice: "alloy", // alloy, echo, fable, onyx, nova, shimmer
},
},
},
}
第 3 步:重启网关
kill -USR1 $(pgrep -f openclaw) # 热加载
第 4 步:验证
/tts status
预期输出:Provider: openai (configured)。
测试:
/tts audio 你好,我是 OpenAI
配置字段
| 字段 | 环境变量回退 | 说明 |
|---|---|---|
apiKey | OPENAI_API_KEY | OpenAI API 密钥 |
model | — | TTS 模型(如 gpt-4o-mini-tts) |
voice | — | 音色:alloy、echo、fable、onyx、nova、shimmer |
输出格式
- Telegram:Opus 语音消息
- 其他平台:MP3
6. Edge TTS 配置(免密钥)
Edge TTS 用微软 Edge 的在线神经网络语音合成服务,通过 node-edge-tts 调用。不用 API 密钥。配置最简单,也是没其他 key 时的兜底选项。
第 1 步:写入 openclaw.json
{
messages: {
tts: {
auto: "always",
provider: "edge",
edge: {
enabled: true,
voice: "zh-CN-XiaoxiaoNeural", // 中文用 zh-CN-XiaoxiaoNeural
lang: "zh-CN",
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
rate: "+10%",
pitch: "-5%",
},
},
},
}
第 2 步:重启网关
kill -USR1 $(pgrep -f openclaw) # 热加载
第 3 步:验证
/tts status
预期输出:Provider: edge (configured)。
测试:
/tts audio 你好,我是 Edge TTS
配置字段
| 字段 | 默认值 | 说明 |
|---|---|---|
enabled | true | 是否启用 Edge TTS |
voice | en-US-MichelleNeural | Edge 神经网络音色名称 |
lang | en-US | 语言代码 |
outputFormat | audio-24khz-48kbitrate-mono-mp3 | Edge 输出格式(参见微软文档) |
rate | — | 语速调节(如 +10%、-20%) |
pitch | — | 音调调节(如 -5%、+10%) |
volume | — | 音量调节(如 +50%) |
saveSubtitles | — | 在音频旁保存 JSON 字幕文件 |
proxy | — | Edge TTS 请求的代理 URL |
timeoutMs | — | 请求超时(毫秒) |
注意
- Edge TTS 是公共网络服务,没有 SLA 和配额。尽力而为。
- 不是所有
outputFormat值都被支持。配的格式失败时框架会自动重试 MP3。 - Telegram
sendVoice接受 OGG/MP3/M4A。想保证 Opus 语音消息就用 OpenAI 或 ElevenLabs。
完全禁用 Edge TTS
{
messages: {
tts: {
edge: {
enabled: false,
},
},
},
}
7. 降级配置
配了多个服务商时,框架优先用选定的服务商,失败时自动降级到其他可用的。
示例:OpenAI 主力 + ElevenLabs 降级
{
messages: {
tts: {
auto: "always",
provider: "openai",
summaryModel: "openai/gpt-4.1-mini",
openai: {
apiKey: "YOUR_OPENAI_API_KEY",
model: "gpt-4o-mini-tts",
voice: "alloy",
},
elevenlabs: {
apiKey: "YOUR_ELEVENLABS_API_KEY",
voiceId: "YOUR_ELEVENLABS_VOICE_ID",
modelId: "eleven_multilingual_v2",
},
},
},
}
没指定 provider 时的优先级
openai(如果OPENAI_API_KEY已设)elevenlabs(如果ELEVENLABS_API_KEY已设)edge(始终可用)
长回复自动摘要
TTS 启用且回复超过 maxLength(默认 1500 字符)时,框架先用 summaryModel 摘要,再转音频。
{
messages: {
tts: {
auto: "always",
maxTextLength: 4000, // TTS 输入硬上限(字符数)
timeoutMs: 30000, // 请求超时(毫秒)
summaryModel: "openai/gpt-4.1-mini",
},
},
}
禁用自动摘要:
/tts summary off
自动 TTS 行为
启用后框架会:
- 回复已含媒体或
MEDIA:指令时跳过 TTS - 跳过极短回复(< 10 字符)
- 启用摘要时自动摘要长回复
- 把生成的音频附加到回复中
流程
回复 -> TTS 已启用?
否 -> 发送文本
是 -> 包含媒体 / MEDIA: / 太短?
是 -> 发送文本
否 -> 长度 > 上限?
否 -> TTS -> 附加音频
是 -> 摘要已启用?
否 -> 发送文本
是 -> 摘要 -> TTS -> 附加音频
模型驱动的覆盖
默认情况下模型可以发 [[tts:...]] 指令覆盖单条回复的音色:
给你。
[[tts:voiceId=pMsXgVXv3BLzUgSXRplE model=eleven_v3 speed=1.1]]
[[tts:text]](笑声) 再读一遍那首歌。[[/tts:text]]
启用模型切换服务商:
{
messages: {
tts: {
modelOverrides: {
enabled: true,
allowProvider: true,
},
},
},
}
禁用所有模型覆盖:
{
messages: {
tts: {
modelOverrides: {
enabled: false,
},
},
},
}
8. 斜杠命令参考
统一命令:/tts(Discord 上用 /voice,因为 /tts 是 Discord 内置命令)。
| 命令 | 效果 |
|---|---|
/tts off | 关闭当前会话的自动 TTS |
/tts always | 每条回复都启用 TTS(别名:/tts on) |
/tts inbound | 仅在收到语音消息后用语音回复 |
/tts tagged | 仅在 LLM 发出 [[tts]] 标签时发语音 |
/tts status | 显示当前 TTS 服务商和配置 |
/tts provider openai | 切换服务商(openai / elevenlabs / edge / volcano / fishaudio) |
/tts limit 2000 | 设置摘要阈值(字符数) |
/tts summary off | 禁用长回复自动摘要 |
/tts audio 你好 | 生成一次性音频回复(不开启自动 TTS) |
注意:
- 命令需要授权发送者(白名单/所有者规则仍然适用)
commands.text或原生命令注册必须启用off|always|inbound|tagged是会话级开关limit和summary存储在本地偏好,不在主配置里
用户偏好
斜杠命令把覆盖写入本地偏好文件。
存的字段:enabled、provider、maxLength、summarize。
这些设置覆盖该主机上的 messages.tts.* 配置。
9. 出问题了?对着查
完全没音频:
- TTS 是否启用?
auto不能是"off"。跑/tts status看看。 - 服务商配置了没?必填字段都有了没?
- API 密钥设了没?查
openclaw.json或环境变量。 - 回复是不是太短?TTS 跳过少于 10 字符的回复。
- 回复是不是已经有媒体了?TTS 跳过含媒体或
MEDIA:指令的回复。
有音频但没情感(火山 v2):
- 设了
version: "v2"没?/tts status应该显示volcano v2。 - LLM 在生成
[方括号]标记没?检查系统提示词有没有情感指令。 - 标记是不是在 TTS 前被错误去掉了?只应该从可见文本中移除。
语音以文件而不是气泡形式发送(Telegram):
- Fish Audio:输出应该是
.ogg——检查voiceCompatible: true。 - 火山 v2:MP3 输出应该有
voiceCompatible: true。 - OpenAI/ElevenLabs:Telegram 格式应该是 Opus。
[方括号] 显示在用户可见文本里:
- 火山 v2:情感标记剥离步骤应该把它们从显示文本中去掉。
- 非火山服务商:
[方括号]不被支持,会被直接念出来。
语音消息重复发送:
- TTS 工具应该返回 "Audio delivered. Do not re-send." 防止 LLM 重复发送。
Edge TTS 静默失败:
- Edge TTS 是公共服务,可能被限流或宕机。
- 检查
outputFormat是否被支持。失败时框架自动重试 MP3。
背后的故事在实战录同名篇。