Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
204 lines
8.5 KiB
Python
204 lines
8.5 KiB
Python
"""实验 7-6:全自动 TTS 质量评估流水线 —— 配置与测试语料。
|
||
|
||
本模块集中管理:
|
||
- 用到的 OpenAI 模型名与计费单价(仅供参考成本估算);
|
||
- 多个 TTS「配置」(model / voice / speed 的组合,作为待对比的对象);
|
||
- 一组带挑战性的参考文本(数字 / 多音字 / 长句 / 专有名词 + 情感)。
|
||
"""
|
||
|
||
import os
|
||
from dataclasses import dataclass, field
|
||
from dotenv import load_dotenv
|
||
|
||
load_dotenv()
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 模型名(均为 OpenAI,读 OPENAI_API_KEY)。
|
||
# ---------------------------------------------------------------------------
|
||
WHISPER_MODEL = "whisper-1" # 语音转写(回译),用于计算 WER/字准确率(须走 OpenAI 直连)
|
||
JUDGE_MODEL = "gpt-5.6-luna" # LLM Rubric 评审模型(当前廉价旗舰;chat 调用可回退 OpenRouter)
|
||
|
||
# 可选的 Gemini 音频评审(书中方案)。默认用当前廉价旗舰 gemini-3.5-flash(已验证支持
|
||
# 音频输入,能直接「听」合成语音)。模型名可能随时间过期,运行时会通过 REST /models
|
||
# 探测校正。仅当 --gemini 开启时才会用到。
|
||
GEMINI_MODEL_DEFAULT = "gemini-3.5-flash"
|
||
|
||
# 计费单价(美元),仅用于打印粗略成本,不影响评分。数值随官方调整可能变化。
|
||
PRICE = {
|
||
"tts-1": 15.0 / 1_000_000, # $ / 字符
|
||
"tts-1-hd": 30.0 / 1_000_000, # $ / 字符
|
||
"gpt-4o-mini-tts": 12.0 / 1_000_000,
|
||
"whisper-1": 0.006 / 60, # $ / 秒
|
||
}
|
||
|
||
|
||
@dataclass
|
||
class TTSConfig:
|
||
"""一个待评估的 TTS 配置。name 需在整表内唯一。
|
||
|
||
provider 指明合成走哪个服务商(openai / elevenlabs / fishaudio / minimax /
|
||
doubao)。model / voice / speed 的语义由各 provider 自行解释:例如 elevenlabs
|
||
的 voice 是 voice_id,fishaudio 的 voice 是 reference_id(可留空用默认音色)。
|
||
"""
|
||
|
||
name: str
|
||
model: str
|
||
voice: str
|
||
speed: float = 1.0
|
||
provider: str = "openai"
|
||
|
||
def supports_speed(self) -> bool:
|
||
# 只有部分 provider/模型支持 speed 参数;不支持时忽略该字段。
|
||
if self.provider == "openai":
|
||
return self.model in ("tts-1", "tts-1-hd")
|
||
return self.provider in ("minimax", "doubao")
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 多 provider 注册表(对应书中「接入主流服务:OpenAI、ElevenLabs、Fish Audio、
|
||
# Minimax、豆包」)。每个 provider 声明所需环境变量与一个代表性配置,便于跨服务商
|
||
# 横向对比。除 OpenAI 外均按各家公开 REST 接口实现,缺 key 时该 provider 的行会被
|
||
# 记为失败而不影响整表(见 demo.py)。
|
||
# ---------------------------------------------------------------------------
|
||
# 环境变量别名:同一凭据可能有多个历史/惯用名,任意一个被设置即视为已配置。
|
||
ENV_ALIASES = {
|
||
"FISH_API_KEY": ("FISH_API_KEY", "FISHAUDIO_API_KEY"),
|
||
}
|
||
|
||
|
||
def env_get(name: str) -> str:
|
||
"""读取环境变量,支持 ENV_ALIASES 中登记的别名,返回第一个非空值(已 strip)。"""
|
||
import os
|
||
for n in ENV_ALIASES.get(name, (name,)):
|
||
val = os.environ.get(n, "").strip()
|
||
if val:
|
||
return val
|
||
return ""
|
||
|
||
|
||
@dataclass
|
||
class ProviderInfo:
|
||
key: str # 内部标识(--providers 用)
|
||
label: str # 展示名
|
||
env: tuple # 该 provider 合成所需的环境变量名
|
||
note: str # 一句话说明 voice 字段语义等
|
||
|
||
def configured(self) -> bool:
|
||
return all(env_get(e) for e in self.env)
|
||
|
||
|
||
PROVIDERS = {
|
||
"openai": ProviderInfo(
|
||
"openai", "OpenAI", ("OPENAI_API_KEY",),
|
||
"voice=alloy/nova/…,model=tts-1/tts-1-hd/gpt-4o-mini-tts;本仓库唯一端到端验证过的 provider。",
|
||
),
|
||
"elevenlabs": ProviderInfo(
|
||
"elevenlabs", "ElevenLabs", ("ELEVENLABS_API_KEY",),
|
||
"voice=voice_id,model 默认 eleven_multilingual_v2(多语言/中文)。",
|
||
),
|
||
"fishaudio": ProviderInfo(
|
||
"fishaudio", "Fish Audio", ("FISH_API_KEY",),
|
||
"voice=reference_id(留空用默认音色),走 /v1/tts;key 亦可用别名 FISHAUDIO_API_KEY。",
|
||
),
|
||
"minimax": ProviderInfo(
|
||
"minimax", "Minimax", ("MINIMAX_API_KEY",),
|
||
"voice=voice_id,model 默认 speech-2.8-hd(另有 speech-2.8-turbo);Bearer 鉴权,"
|
||
"MINIMAX_REGION 选 global(api.minimax.io)/cn(api.minimaxi.com)。",
|
||
),
|
||
"doubao": ProviderInfo(
|
||
"doubao", "豆包(火山引擎)", ("DOUBAO_APP_ID", "DOUBAO_ACCESS_TOKEN"),
|
||
"voice=voice_type,走 openspeech.bytedance.com;鉴权头为 'Bearer;{token}'。",
|
||
),
|
||
}
|
||
|
||
# 各 provider 的代表性配置(--providers 选中时,每个 provider 取这一条参与对比)。
|
||
# 非 OpenAI 的 voice/model 取各家常见默认值,可在此按账号可用音色调整。
|
||
PROVIDER_CONFIGS = {
|
||
# Reuse the identical default-grid identity so a cross-provider campaign
|
||
# can audit an existing OpenAI artifact instead of synthesizing it twice.
|
||
"openai": TTSConfig("tts1-alloy-1.0", provider="openai", model="tts-1", voice="alloy"),
|
||
"elevenlabs": TTSConfig("elevenlabs-multi", provider="elevenlabs",
|
||
model="eleven_multilingual_v2", voice="21m00Tcm4TlvDq8ikWAM"),
|
||
# This immutable reference ID is the same real source voice used to build
|
||
# Chapter 9's checked-in 24-clip Fish S1 library. Accounts that cannot
|
||
# access it can supply FISH_REFERENCE_ID explicitly; an empty/default
|
||
# voice would make the voice-consistency arm scientifically meaningless.
|
||
"fishaudio": TTSConfig(
|
||
"fishaudio-s1-clone",
|
||
provider="fishaudio",
|
||
model="s1",
|
||
voice=os.getenv("FISH_REFERENCE_ID", "6df3c1e14c9440e9ac978556536bf116"),
|
||
),
|
||
"minimax": TTSConfig("minimax-hd", provider="minimax",
|
||
model="speech-2.8-hd", voice="male-qn-qingse"),
|
||
"doubao": TTSConfig("doubao-tts", provider="doubao",
|
||
model="volcano_tts", voice="zh_female_qingxin"),
|
||
}
|
||
|
||
|
||
# 默认对比的配置集合:覆盖 model(tts-1 vs tts-1-hd)、voice、speed 三个维度,
|
||
# 便于观察不同配置在准确性/自然度上的差异。默认全部走 OpenAI 以保证零额外配置跑通。
|
||
TTS_CONFIGS = [
|
||
TTSConfig("tts1-alloy-1.0", model="tts-1", voice="alloy", speed=1.0),
|
||
TTSConfig("tts1hd-alloy-1.0", model="tts-1-hd", voice="alloy", speed=1.0),
|
||
TTSConfig("tts1-nova-1.0", model="tts-1", voice="nova", speed=1.0),
|
||
TTSConfig("tts1-alloy-1.5", model="tts-1", voice="alloy", speed=1.5),
|
||
]
|
||
|
||
# 可选加入(--extra 开启):gpt-4o-mini-tts。默认不加入以保证一定跑通。
|
||
EXTRA_CONFIGS = [
|
||
TTSConfig("4omini-nova-1.0", model="gpt-4o-mini-tts", voice="nova", speed=1.0),
|
||
]
|
||
|
||
|
||
@dataclass
|
||
class Sample:
|
||
"""一条参考文本 + 期望情感标签(供 Rubric 情感维度参考)。"""
|
||
|
||
id: str
|
||
text: str
|
||
challenge: str # 该样本主要考察的挑战点
|
||
emotion: str = "中性"
|
||
|
||
|
||
# 多样化测试语料:数字/日期、多音字、长句、专有名词+情感。
|
||
CORPUS = [
|
||
Sample(
|
||
id="num",
|
||
text="2026年第三季度营收增长了37.5%,同比提升12个百分点。",
|
||
challenge="数字/百分比/日期",
|
||
emotion="中性",
|
||
),
|
||
Sample(
|
||
id="polyphone",
|
||
text="银行行长正在重新调整这件事的重点,长此以往,还得还清所有欠款。",
|
||
challenge="多音字(行/长/重/还)",
|
||
emotion="中性",
|
||
),
|
||
Sample(
|
||
id="long",
|
||
text="据报道,随着人工智能技术的快速发展,越来越多的企业开始将大语言模型"
|
||
"应用于客户服务、内容创作和数据分析等场景,从而显著提升了运营效率。",
|
||
challenge="长句/新闻文体",
|
||
emotion="中性",
|
||
),
|
||
Sample(
|
||
id="emotion",
|
||
text="太棒了!OpenAI 刚刚发布的新模型在 GAIA 基准测试上表现惊人!",
|
||
challenge="专有名词 + 感叹情感",
|
||
emotion="兴奋",
|
||
),
|
||
Sample(
|
||
id="sad",
|
||
text="很遗憾地通知您,救援队今天仍然没有找到失踪的登山者。",
|
||
challenge="悲伤情感/低语速低语调",
|
||
emotion="悲伤",
|
||
),
|
||
Sample(
|
||
id="question",
|
||
text="请问您希望把明天下午三点的预约改到星期五上午吗?",
|
||
challenge="对话文体/疑问句升调",
|
||
emotion="礼貌询问",
|
||
),
|
||
]
|