ENZH

OpenClaw 全平台 TTS 接入手册

这个系列是什么:完整的技术 runbook——直接交给 Claude Code 执行就行。复制手册、替换占位符(YOUR_FISH_API_KEYYOUR_VOLC_APP_ID 等),发给 Agent,让它按步骤来。背后的故事在实战录同名篇

OpenClaw 语音合成:全平台 TTS 配置指南

覆盖 OpenClaw 支持的全部五个 TTS 服务商。每个章节独立,直接跳到你需要的服务商。


目录

  1. 前置条件
  2. Fish Audio 配置
  3. 火山引擎 v2 配置(情感控制)
  4. ElevenLabs 配置
  5. OpenAI TTS 配置
  6. Edge TTS 配置(免密钥)
  7. 降级配置
  8. 斜杠命令参考
  9. 排障清单

1. 前置条件

配置任何 TTS 服务商之前,确保:

  • 框架已安装并运行
  • 你有 openclaw.json(网关配置文件)的访问权限
  • 你知道怎么重启网关或用 kill -USR1 热加载

TTS 配置在 openclaw.jsonmessages.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

配置字段

字段环境变量回退说明
apiKeyFISH_API_KEYFish Audio API 密钥
referenceIdfish.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 步:注册火山引擎

前往 console.volcengine.com

第 2 步:开通语音合成服务

开通"语音合成",从控制台拿 App IDAccess 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 [开心]你好呀!

配置字段

字段环境变量回退默认值说明
appIdVOLC_TTS_APP_ID火山应用 ID
accessKeyVOLC_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

v1v2
资源 IDseed-tts-1.0volc.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

配置字段

字段环境变量回退说明
apiKeyELEVENLABS_API_KEY / XI_API_KEYElevenLabs API 密钥
baseUrl覆盖 API 基础 URL(默认 https://api.elevenlabs.io
voiceId音色 ID
modelId模型(如 eleven_multilingual_v2
seed整数 0..4294967295(尽力确定性)
applyTextNormalizationautoonoff
languageCode两字母 ISO 639-1 代码(如 enzh
voiceSettings.stability0..1 — 越低越有表现力
voiceSettings.similarityBoost0..1 — 越高越接近原声
voiceSettings.style0..1
voiceSettings.useSpeakerBoosttruefalse
voiceSettings.speed0.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

配置字段

字段环境变量回退说明
apiKeyOPENAI_API_KEYOpenAI API 密钥
modelTTS 模型(如 gpt-4o-mini-tts
voice音色:alloyechofableonyxnovashimmer

输出格式

  • 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

配置字段

字段默认值说明
enabledtrue是否启用 Edge TTS
voiceen-US-MichelleNeuralEdge 神经网络音色名称
langen-US语言代码
outputFormataudio-24khz-48kbitrate-mono-mp3Edge 输出格式(参见微软文档)
rate语速调节(如 +10%-20%
pitch音调调节(如 -5%+10%
volume音量调节(如 +50%
saveSubtitles在音频旁保存 JSON 字幕文件
proxyEdge 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 时的优先级

  1. openai(如果 OPENAI_API_KEY 已设)
  2. elevenlabs(如果 ELEVENLABS_API_KEY 已设)
  3. 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 是会话级开关
  • limitsummary 存储在本地偏好,不在主配置里

用户偏好

斜杠命令把覆盖写入本地偏好文件。

存的字段:enabledprovidermaxLengthsummarize

这些设置覆盖该主机上的 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。

背后的故事在实战录同名篇


© Xingfan Xia 2024 - 2026 · CC BY-NC 4.0