能力声明与只读查询契约
宿主设置页需要在不启动引擎的前提下知道:这个插件有没有音色表?支不支持克隆音色 / 删除音色 / 原生语速?
声明方式(两种等价,宿主取并集)
方式一:mod.json(推荐)
json
{
"capabilities": {
"voices": true,
"voice_clone": true,
"voice_remove": true,
"speed": false
}
}| 键 | 含义 | 等价类属性 / 方法 |
|---|---|---|
voices | 提供音色表(设置页显示「朗读音色」下拉) | 重写 list_voices() |
voice_clone | 支持注册克隆音色(设置页显示「注册克隆音色」面板,含软件内录制参考音频) | supports_voice_clone = True |
voice_remove | 支持删除已注册音色(仅 kind="registered" 的音色旁显示「删除音色」) | 重写 remove_voice() |
speed | 原生支持语速(模型内部时长预测,质量更好) | supports_speed = True |
方式二:类属性 / 方法覆盖
python
class MyTTS(BaseTTSProvider):
supports_voice_clone = True # 支持克隆音色
supports_speed = True # 原生语速
def list_voices(self): ... # 有音色表
def remove_voice(self, name): ...只读查询契约(强制)
宿主查能力时绝不创建插件实例,更不会启动你的本地服务。
宿主只读两个来源:
mod.json的capabilities;- 你的类属性(只
import模块,不调用__init__/initialize)。
由此得出两条硬要求:
能力必须是类级可读的。 只在
__init__/initialize里赋值 → 宿主在没实例时读不到。 (宿主已有实例时会优先读实例属性,但不能依赖这一点。) 👉 结论:supports_voice_clone/supports_speed写在类体里,或在mod.json声明。voices/voice_remove看的是方法有没有被覆盖,实例属性覆盖不了方法。任何只读方法都不得启动服务:
list_voices()、check_ready()、以及任何get_*/ 状态查询。 数据没准备好就返回空表 /(False, code, msg),宿主按「未就绪」处理。
宿主在哪些路径上只读探测
- 打开设置页 / 切换 Tab
- 查看模型卡状态
- 读取音色列表、查「是否支持音色克隆 / 删除」
- 恢复上次生效的引擎(
set_active_provider())—— 生效引擎只是配置变更,不预热、不加载
⚠️ 反例(真实 BUG):把
_start_server()写在initialize()里。 用户「只是打开设置页或切到该引擎页签」就会白等数秒并吃掉数 GB 显存。 见 本地服务型引擎强制契约。
参考实现
extensions/community_qwen3_tts(capabilities + 类属性双写,且只读路径全部不启服务)。
相关
- tts 语音合成 — 音色表 / 克隆 / 语速的完整用法
- 本地服务型引擎强制契约 — 惰性启动
- Provider 签名与数据类型
- 模型工件 —
check_ready()语义
