ENZH
和 AI 讨论这篇文章
ChatGPTClaude

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. 前置条件

开始配之前先确认几件事。下面三条都满足了,再往下走。缺哪条就先把哪条补上:

  • 框架已安装并运行
  • 你有 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、把情感指令附上去,最后拼接成一条完整的语音消息。五家里面只有它能做带情感的声音,就是会哭会笑那种。

火山引擎 v2 的情感语音流水线:LLM 加情感标记 → 从可见文本去标记 → 逐句拆分 → 逐句调 API 附情感上下文 → 拼接 MP3 成一条语音气泡火山引擎 v2 的情感语音流水线:LLM 加情感标记 → 从可见文本去标记 → 逐句拆分 → 逐句调 API 附情感上下文 → 拼接 MP3 成一条语音气泡

第 1 步:注册火山引擎

console.volcengine.com 把账号注册好。注册完直接进控制台。

第 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),这一行必须带 v2 字样。没有 v2 的话,就是 version 没设对。然后测一句带情感标记的:

/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

每个片段的请求体长这样。additions 里带的就是这个片段的情感上下文。

{
  "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 配置

ElevenLabs 可调的参数是五家里最细的,stability、similarityBoost、style、语速全都能单独拧。对音色质感有要求的人用它比较合适,可以一点点调到自己满意为止。

第 1 步:拿 API 密钥

elevenlabs.io 注册账号,然后从控制台把 API 密钥拿到。后面还要一个 voice ID,下一步挑。

第 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 配置

OpenAI TTS 不用注册什么新账号,反正大多数人手里本来就有 OPENAI_API_KEY。如果只是想先把语音跑通,不纠结音色细节,从它开始最快。

第 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. 降级配置

同时配了多个服务商的时候,框架优先用 provider 指定的那家。哪次请求失败了,它会自动降级到其他配置可用的服务商,不用你手动切。

示例: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 行为

启用之后,框架的行为是下面这几条。全都是自动做的,不用你自己管。

自动 TTS 的判断回路:每条回复先看 TTS 开没开、是不是太短或带媒体、超不超长,超长就先摘要再转音频,否则直接发文本自动 TTS 的判断回路:每条回复先看 TTS 开没开、是不是太短或带媒体、超不超长,超长就先摘要再转音频,否则直接发文本

  • 回复已含媒体或 MEDIA: 指令时跳过 TTS
  • 跳过极短回复(< 10 字符)
  • 启用摘要时自动摘要长回复
  • 把生成的音频附加到回复中

流程

回复 -> TTS 已启用?
  否  -> 发送文本
  是  -> 包含媒体 / MEDIA: / 太短?
          是 -> 发送文本
          否 -> 长度 > 上限?
                   否 -> TTS -> 附加音频
                   是 -> 摘要已启用?
                            否 -> 发送文本
                            是 -> 摘要 -> TTS -> 附加音频

模型驱动的覆盖

模型覆盖默认是开着的。模型可以在回复里夹一条 [[tts:...]] 指令,覆盖掉单条回复的音色。比如:

给你。

[[tts:voiceId=pMsXgVXv3BLzUgSXRplE model=eleven_v3 speed=1.1]]
[[tts:text]](笑声) 再读一遍那首歌。[[/tts:text]]

想让模型连服务商都能切,就把 allowProvider 打开。配置是这样的。

{
  messages: {
    tts: {
      modelOverrides: {
        enabled: true,
        allowProvider: true,
      },
    },
  },
}

也可以把模型覆盖整个关掉。一条 enabled: false 的事。

{
  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。

手册到这里就完了。背后这套配置是怎么折腾出来的,看实战录同名篇

和 AI 讨论这篇文章
ChatGPTClaude
AI 随想第 13 篇 · 共 28 篇

订阅更新

新文章发布时发到你的邮箱,不发别的。


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