编辑此页 / 查看本页的源代码
实验 9-5:控制标记驱动的可控 TTS¶
《深入理解 AI Agent》实验 9-5 的可运行配套项目。
核心思路:让主 LLM 的输出不只是文本,还带上控制标记(情感 / 语速 / 风格 / 停顿 / 笑声等);执行层解析这些标记,映射到一个参考语音库里对应的音色/风格档案, 再合成语音。这样「在哪里该停顿、该用什么语气」的决策交给了 LLM,同一段文本在不同 控制标记下能合成出不同风格、情感、节奏的语音。
Provider 适配(重要)¶
书中实验 9-5 使用 Fish Audio S1 的声音克隆:用 3-10 秒参考语音零样本克隆同一 音色,构建覆盖情绪 × 语速 × 风格的参考语音库,靠控制标记选择参考语音,Fish Audio 保证不同参考语音之间音色一致、只有韵律和情感变化。
本环境无 Fish Audio 可用 key,因此改用 OpenAI TTS 演示完全相同的思路:
| 书中(Fish Audio) | 本项目(OpenAI TTS) |
|---|---|
| 声音克隆保证音色一致 | 全库固定同一个 voice(alloy),音色不变 |
| 每条参考语音的韵律/情感 | 每个档案对应一段 instructions 风格提示词 |
| 控制标记选参考语音 | 控制标记解析后 -> 选 (情绪,语速,风格) 档案 |
- 首选模型
gpt-4o-mini-tts:支持instructions参数,可用一段中文提示词精确 控制情感/语速/口吻,最贴近「控制标记 → 风格化语音」的语义。 - 若首选模型不可用,代码自动兜底到
tts-1:不支持 instructions,改用多 voice +speed参数 + 文本级停顿近似。
必须用 OpenAI 直连 Key:本实验只用 TTS 语音合成端点(gpt-4o-mini-tts / tts-1),
这类音频端点只有 OpenAI 直连才有——OpenRouter 只做聊天补全、无音频合成端点,故无法回退到
OPENROUTER_API_KEY。离线查看语音库/标记映射(--list-voices / --dump-mapping)则无需任何 Key。
局限:OpenAI TTS 无法像 Fish Audio 那样原生生成笑声/叹气等非语言音。本项目对
<laugh>/[SIGH]用「匹配情绪的拟声词」(如“哈哈,”“唉——”)近似,[PAUSE]/[THINKING]等停顿则用 ffmpeg 生成真实静音插入,可被 ffprobe 验证时长。
控制标记 → TTS 参数 映射¶
状态标记(持续生效,直到被同类标记改变)¶
| 标记 | 中文写法 | 作用 |
|---|---|---|
[EMO:neutral\|happy\|frustrated\|thinking] |
[情感=中性\|高兴\|沮丧\|思考] |
切换情绪 |
[SPEED:normal\|fast\|slow] / [SPEED:0.8x] |
[语速=正常\|快\|慢] |
切换语速 |
[STYLE:formal\|casual] |
[风格=正式\|轻松] |
切换口吻 |
三个维度组合成参考语音库的一个档案(如 happy_fast_formal),
再拼成一段 instructions 提示词交给 gpt-4o-mini-tts。
内联标记(一次性事件)¶
| 标记 | 作用 |
|---|---|
[THINKING] |
切到「思考/慢速/正式」参考语音 + 插入 0.5s 停顿 |
[SEARCHING] |
同上,停顿 0.4s(搜索性犹豫) |
[PAUSE] / <pause> / [停顿] |
插入 0.5s 静音 |
[BREATH] / <breath> |
插入 0.4s 换气停顿 |
[SIGH] / <sigh> |
叹气拟声词「唉——」+ 0.3s 停顿 |
[LAUGH:small] / <laugh> |
轻笑拟声词「哈哈,」(欢快音色) |
<emphasis>…</emphasis> / [强调]…[/强调] |
对包裹文本追加「加重强调」提示词 |
参考语音库¶
voice_library.py 由 情绪(4) × 语速(3) × 风格(2) 笛卡尔积生成 24 条档案,全部固定
voice=alloy(音色一致),仅 instructions 不同。可单独运行查看:
安装与运行¶
pip install -r requirements.txt # 需系统已装 ffmpeg/ffprobe
cp env.example .env # 填入有效的 OPENAI_API_KEY
python demo.py # 生成 output/*.mp3
demo.py 做两件事:
- 三种配置对比(书中要求),同一段带标记文本:
A_no_markers.mp3无控制标记(流畅但机械)B_single_voice.mp3单一参考语音(自然但情感单调)C_voice_library.mp3多参考语音库(按标记切换情感/语速/停顿)- 同文本 / 不同控制标记 → 多个不同风格音频:
variant_*.mp3。
运行时会打印每个音频的「控制标记 → 参数」解析过程,以及 ffprobe 时长信息。
常用参数(python demo.py --help):
| 参数 | 作用 |
|---|---|
--quick |
只跑三种配置对比(A/B/C),跳过 5 个风格变体,减少 TTS 调用与耗时 |
--text 文本 |
只合成这一段自定义文本(可内嵌控制标记,如 [情感=高兴][THINKING]…) |
--emotion / --speed / --style |
为 --text 指定情绪/语速/口吻(等价于在文本前加对应状态标记) |
-o / --output 路径 |
--text 模式的输出 mp3 路径(默认 output/custom.mp3) |
--list-voices |
离线(无需 API key):打印完整参考语音库(24 条档案及其 instructions) |
--dump-mapping |
离线(无需 API key):打印控制标记 → 动作映射表,并演示对示例文本的解析过程 |
预期输出示例(真实节选)¶
首选模型: gpt-4o-mini-tts(不可用时自动兜底 tts-1)
对比实验:同一段带控制标记的文本,三种配置
原始文本: [EMO:happy][SPEED:fast]太好了!您的订单已确认。[THINKING]嗯,让我查一下发货时间...[EMO:neutral][SPEED:normal]预计明天下午送达。
[C] 多参考语音库(解析控制标记 -> 逐段切换参考语音 + 停顿)
-- 控制标记解析过程 --
[EMO:happy] -> 情绪 = happy
[SPEED:fast] -> 语速 = fast
[THINKING] -> 切换到 思考/慢速/正式 参考语音
[THINKING] 停顿 -> 插入静音 500ms
-- 合成片段 --
· [happy_fast_formal ] gpt-4o-mini-tts voice=alloy text='太好了!您的订单已确认。'
· [静音 500ms]
· [thinking_slow_formal ] gpt-4o-mini-tts voice=alloy text='嗯,让我查一下发货时间...'
· [neutral_normal_formal ] gpt-4o-mini-tts voice=alloy text='预计明天下午送达。'
=> output/C_voice_library.mp3 | format_name=mp3 duration=11.324000 ...
对照三种配置的 ffprobe 时长即可看出差异:C(多参考语音库)因插入真实静音停顿,
比 A/B(8.5s 左右)更长(约 11.3s),且各片段用了不同的 (情绪,语速,风格) 档案。
(每次运行会真实调用 OpenAI TTS,时长/字节数会有小幅波动。)
文件说明¶
| 文件 | 作用 |
|---|---|
voice_library.py |
参考语音库 + 控制维度 → instructions 映射 |
markup.py |
控制标记解析器:带标记文本 → 片段列表(语音/静音) |
tts.py |
OpenAI TTS 合成 + ffmpeg 生成静音/拼接 |
demo.py |
演示入口,三种配置对比 + 风格变体 |