Skip to main content

audio_cpp/
types.rs

1//! 与 `capi.cpp` JSON 输出一一对应的结构化类型。
2//!
3//! 这些类型由 serde 从 shim 返回的 JSON 反序列化而来,字段名与
4//! `capi.cpp` 的 `dump_*` 系列函数保持一致。
5
6use std::collections::BTreeMap;
7
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10/// 任务种类(映射 `audiocpp_model_create_task_session` 的 `task` 参数)。
11///
12/// serde 表示与 [`TaskKind::as_str`] 一致(`"sep"` 而非 `"sourceseparation"`),
13/// 与 C 边界字符串一一对应。
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum TaskKind {
16    /// 语音活动检测(VAD)
17    Vad,
18    /// 语音识别(ASR)
19    Asr,
20    /// 说话人分离(Diarization)
21    Diar,
22    /// 音频源分离(Source Separation,如 Demucs 分离人声/鼓/贝斯/其他)
23    SourceSeparation,
24    /// 音频生成(Audio Generation,如 MiniMax Music3 音乐生成)
25    AudioGeneration,
26    /// 语音合成(TTS)
27    Tts,
28    /// 语音克隆(Voice Cloning)
29    VoiceCloning,
30    /// 语音转换(Voice Conversion)
31    VoiceConversion,
32    /// 语音到语音(Speech-to-Speech)
33    SpeechToSpeech,
34    /// 强制对齐(Alignment)
35    Alignment,
36    /// 声音设计(Voice Design)
37    VoiceDesign,
38    /// 说话人识别(Speaker Recognition)
39    SpeakerRecognition,
40    /// 歌唱语音转换(SVC)
41    Svc,
42    /// MIDI 生成
43    Midi,
44    /// 轮次检测(Turn Detection,如 Smart Turn 会话是否说完;仅离线)
45    TurnDetection,
46    /// 唤醒词检测(Wake Word,如 microWakeWord / sherpa KWS;离线+流式)
47    WakeWord,
48    /// 音频分类(Audio Classification,如 CED / AST AudioSet 的 527 类;
49    /// 仅离线,结果在 `custom_schema_output`)
50    AudioClassification,
51}
52
53impl TaskKind {
54    /// 转换为传给 C 边界的字符串。
55    pub fn as_str(self) -> &'static str {
56        match self {
57            TaskKind::Vad => "vad",
58            TaskKind::Asr => "asr",
59            TaskKind::Diar => "diar",
60            TaskKind::SourceSeparation => "sep",
61            TaskKind::AudioGeneration => "gen",
62            TaskKind::Tts => "tts",
63            TaskKind::VoiceCloning => "clon",
64            TaskKind::VoiceConversion => "vc",
65            TaskKind::SpeechToSpeech => "s2s",
66            TaskKind::Alignment => "align",
67            TaskKind::VoiceDesign => "vdes",
68            TaskKind::SpeakerRecognition => "spk",
69            TaskKind::Svc => "svc",
70            TaskKind::Midi => "midi",
71            TaskKind::TurnDetection => "turn",
72            TaskKind::WakeWord => "wake",
73            TaskKind::AudioClassification => "cls",
74        }
75    }
76}
77
78impl Serialize for TaskKind {
79    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
80        serializer.serialize_str(self.as_str())
81    }
82}
83
84impl<'de> Deserialize<'de> for TaskKind {
85    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
86        let s = String::deserialize(deserializer)?;
87        match s.as_str() {
88            "vad" => Ok(TaskKind::Vad),
89            "asr" => Ok(TaskKind::Asr),
90            "diar" => Ok(TaskKind::Diar),
91            "sep" => Ok(TaskKind::SourceSeparation),
92            "gen" => Ok(TaskKind::AudioGeneration),
93            "tts" => Ok(TaskKind::Tts),
94            "clon" => Ok(TaskKind::VoiceCloning),
95            "vc" => Ok(TaskKind::VoiceConversion),
96            "s2s" => Ok(TaskKind::SpeechToSpeech),
97            "align" => Ok(TaskKind::Alignment),
98            "vdes" => Ok(TaskKind::VoiceDesign),
99            "spk" => Ok(TaskKind::SpeakerRecognition),
100            "svc" => Ok(TaskKind::Svc),
101            "midi" => Ok(TaskKind::Midi),
102            "turn" => Ok(TaskKind::TurnDetection),
103            // 上游别名:`wake_word`(`wake` 的别名)、`classify`(`cls` 的别名),
104            // capabilities 等 JSON 里出现的是规范短名,解析时两者都接受。
105            "wake" | "wake_word" => Ok(TaskKind::WakeWord),
106            "cls" | "classify" => Ok(TaskKind::AudioClassification),
107            other => Err(serde::de::Error::unknown_variant(
108                other,
109                &[
110                    "vad", "asr", "diar", "sep", "gen", "tts", "clon", "vc", "s2s", "align",
111                    "vdes", "spk", "svc", "midi", "turn", "wake", "cls",
112                ],
113            )),
114        }
115    }
116}
117
118/// 会话运行模式(离线 / 流式)。
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
120#[serde(rename_all = "lowercase")]
121pub enum RunMode {
122    /// 离线:一次请求 → 一次结果。
123    Offline,
124    /// 流式:持续送入音频、持续产出事件。
125    Streaming,
126}
127
128impl RunMode {
129    /// 转换为传给 C 边界的字符串。
130    pub fn as_str(self) -> &'static str {
131        match self {
132            RunMode::Offline => "offline",
133            RunMode::Streaming => "streaming",
134        }
135    }
136}
137
138/// 模型族(映射 `Registry::load` 的 `family_hint` 参数)。
139///
140/// 枚举覆盖上游 `engine_runtime` 的全部 loader 族,`as_str()` 返回传给
141/// C 边界的字符串。GGUF / NeMo safetensors 无法被引擎自动探测族别,加载
142/// 时必须显式指定;用枚举代替裸字符串可避免拼写错误。
143///
144/// 上游社区持续新增模型,本枚举可能滞后:unknown 族可直接用
145/// [`ModelFamily::Custom`] 传任意字符串,`From<&str>` 也会把未收录的名字
146/// 自动映射到 `Custom`。
147#[derive(Debug, Clone, PartialEq, Eq, Hash)]
148pub enum ModelFamily {
149    // ---- VAD ----
150    /// Silero VAD(内置权重,通常可省略 family_hint)
151    SileroVad,
152    /// MarbleNet VAD(NeMo 格式,须显式指定)
153    MarblenetVad,
154    /// PulseVAD(16kHz 单声道语音活动检测,2.1K student / 81K teacher;
155    /// 仅离线,阈值选项键为 `threshold`)
156    Pulsevad,
157    /// FireRed VAD(DFSMN 语音活动检测,16kHz mono;离线+流式,
158    /// 流式需 Stream-VAD checkpoint;选项 `threshold` 等)
159    FireredVad,
160
161    // ---- ASR ----
162    /// Qwen3 ASR
163    Qwen3Asr,
164    /// Citrinet ASR
165    CitrinetAsr,
166    /// SenseVoice ASR(阿里,社区模型)
167    SenseAsr,
168    /// Fun ASR Nano
169    FunAsrNano,
170    /// Higgs Audio STT
171    HiggsAudioStt,
172    /// Hviske ASR
173    HviskeAsr,
174    /// Hviske ASR v6(丹麦语 Whisper-rotary 编码器 + Qwen3 解码器;
175    /// task 为 `asr` 仅离线,16kHz 输入)
176    HviskeAsrV6,
177    /// Kroko ASR
178    KrokoAsr,
179    /// Nemotron ASR
180    NemotronAsr,
181    /// Nemotron 3 Diarization(NVIDIA 八说话人流式分离,社区预览;
182    /// CMake target `nemotron_3_diar`,用 `model-nemotron-3-diar` feature 编译;
183    /// task 为 `diar`,离线+原生批+流式,16kHz 输入)
184    Nemotron3Diar,
185    /// Parakeet TDT
186    ParakeetTdt,
187    /// Vibevoice ASR
188    VibevoiceAsr,
189    /// VibeVoice ASR 流式 7B(离线+流式;与 VibevoiceAsr 同属
190    /// `vibevoice_asr` CMake target,用 `model-vibevoice-asr` feature 编译)
191    VibevoiceAsrStreaming,
192    /// FireRed Audio(语音理解 / 生成,含 Qwen3.5 运行时)
193    FireredAudio,
194    /// Granite 5 ASR(IBM Granite Speech 5.0 470M TurboCTC ASR,社区模型)
195    Granite5Asr,
196    /// Audio8 ASR(社区 ASR 模型)
197    Audio8Asr,
198    /// Audio8 TTS(社区 TTS 模型)
199    Audio8Tts,
200    /// VibeASR(社区 INT8/ternary ASR)
201    Vibeasr,
202    /// Moonshine ASR(社区流式 ASR,离线+流式)
203    MoonshineAsr,
204    /// Niagara ASR(ABR Niagara 英文批处理 ASR,仅离线)
205    NiagaraAsr,
206    /// Canary ASR(NVIDIA Canary 180M Flash 多语 ASR/翻译,仅离线;
207    /// en/de/es/fr,选项 `target_language`/`pnc`)
208    CanaryAsr,
209    /// Cohere ASR(Cohere Transcribe 多语转写,仅离线;14 语种,
210    /// 选项 `pnc`/`max_tokens`)
211    CohereAsr,
212    /// MOSS-Transcribe-Diarize(Whisper 编码器 + Qwen3 解码器,
213    /// 转写+说话人分离+时间戳联合输出;离线+流式)
214    MossTranscribeDiarize,
215    /// Confucius4-R2T2(网易实时流式 ASR,社区模型;离线+流式)
216    Confucius4R2t2,
217    /// Audio Flamingo(转写/描述/问答,speech+music+环境音;task 为 `asr` 仅离线)
218    AudioFlamingo,
219    /// CrisperWhisper(Whisper 逐字/意图转写;task 为 `asr`/`align`,离线+流式)
220    Crisperwhisper,
221    /// GigaAM ASR(v3 与多语 Conformer,CTC/RNN-T 解码;task 为 `asr` 仅离线)
222    GigaamAsr,
223    /// Index-Echo-S2TT(语音到文本翻译,双语时间戳字幕;task 为 `asr` 仅离线)
224    IndexEcho,
225    /// LFM2-Audio(Liquid LFM2.5-Audio,FastConformer 编码器;task 为 `asr` 仅离线)
226    Lfm2Audio,
227    /// OWSM(ESPnet Open Whisper 式多语 ASR/翻译;task 为 `asr`,离线+流式)
228    Owsm,
229    /// OWSM-CTC(ESPnet 非自回归多语 ASR/翻译;task 为 `asr`,离线+流式)
230    OwsmCtc,
231    /// SAMSONE(紧凑音频语言模型:描述/问答/声音音乐语音理解;
232    /// task 为 `asr` 仅离线)
233    Samsone,
234    /// Whistle ASR(社区离线语音识别,本地 FP32 GGUF;task 为 `asr` 仅离线)
235    WhistleAsr,
236
237    // ---- TTS ----
238    /// Qwen3 TTS
239    Qwen3Tts,
240    /// Confucius4 TTS
241    Confucius4Tts,
242    /// Dots TTS
243    DotsTts,
244    /// Fish Audio
245    FishAudio,
246    /// GLM TTS
247    GlmTts,
248    /// Higgs Audio TTS
249    HiggsAudioTts,
250    /// Index TTS2
251    IndexTts2,
252    /// Irodori TTS
253    IrodoriTts,
254    /// MOSS TTS Local
255    MossTtsLocal,
256    /// MOSS TTS Nano
257    MossTtsNano,
258    /// MOSS-TTS-v1.5(8B delay-pattern 零样本声音克隆 TTS,en/zh;
259    /// task 为 `tts`/`clon` 仅离线;克隆需参考音频,请求选项
260    /// `tokens`/`instruct`/`language`/`moss_tts_v15.weight_type`)
261    MossTtsV15,
262    /// MOSS VoiceGen(语音生成)
263    MossVoicegen,
264    /// Neutts
265    Neutts,
266    /// Outetts
267    Outetts,
268    /// Pocket TTS
269    PocketTts,
270    /// VieNeu TTS v3 Turbo(越南语/英语 48kHz TTS + 即时声音克隆,社区模型;
271    /// 上游族名由 `vietneu_tts` 改名而来,旧名仍可经 `From<&str>` 解析;
272    /// task 为 `tts`/`clon` 仅离线;文本输入为 SEA-G2P 音素)
273    VieneuV3Turbo,
274    /// FireRed TTS3(零样本语音合成)
275    Fireredtts3,
276    /// F5 TTS
277    F5Tts,
278    /// Magpie TTS
279    MagpieTts,
280    /// PersonaPlex(多角色对话语音)
281    Personaplex,
282    /// MiniMax H3
283    MinimaxH3,
284    /// SanoTTS(社区 TTS,en/vi/id 共 7 个声音)
285    Sanotts,
286    /// Sopro V2 Turbo TTS
287    SoproTts,
288    /// MiraTTS(社区 TTS)
289    MiraTts,
290    /// CosyVoice3 TTS
291    Cosyvoice3,
292    /// BreezeTTS
293    BreezeTts,
294    /// Soprano TTS
295    SopranoTts,
296    /// Echo TTS
297    EchoTts,
298    /// VoxCPM1 TTS
299    Voxcpm1,
300    /// Kokoro TTS(Kokoro 82M 多语 TTS,仅离线)
301    KokoroTts,
302    /// ZipVoice(k2-fsa 社区流匹配 TTS,含 Distill 变体;en/zh;
303    /// task 为 `tts`/`clon` 仅离线;零样本克隆需 `reference_text` 选项)
304    Zipvoice,
305    /// AuK(腾讯 AuK / AuK-Flash 语音生成与编辑;
306    /// task 为 `tts`/`edit` 仅离线)
307    Auk,
308    /// Piper TTS(社区 VITS TTS,首包英文 `en_US-lessac-medium`,22.05kHz mono;
309    /// task 为 `tts` 仅离线;请求选项 `speed`/`variation`/`duration_variation`/`seed`)
310    PiperTts,
311    /// KittenTTS Mini 0.8(社区英文 TTS,80M 参数,8 个内置声音,24kHz mono;
312    /// task 为 `tts` 仅离线;请求选项 `speed`/`seed`,`--voice-id` 选声音)
313    KittenTts,
314    /// Kitten TTS 2(原生多语 Qwen3 语音生成 + S3 meanflow 解码 + 参考音频克隆;
315    /// task 为 `tts`/`clon` 仅离线)
316    KittenTts2,
317    /// KugelAudio-0-Open(预置音色 TTS,增量音频输出;task 为 `tts`,离线+流式)
318    Kugelaudio,
319    /// Maya1(3B 表现力英文 TTS,自然语言音色设计 + 行内情感控制;
320    /// task 为 `tts` 仅离线)
321    Maya1,
322    /// MOSS-TTSD(8B delay-pattern 对话 TTS:单遍多说话人 + 逐人零样本克隆,
323    /// 24kHz 16 codebook;task 为 `tts`/`clon` 仅离线)
324    MossTtsd,
325
326    // ---- 说话人分离 / 语音转换 / 音乐生成 ----
327    /// SortFormer 说话人分离(Diar)
328    SortformerDiar,
329    /// SortFormer v2.1 说话人分离(离线+流式,社区模型;
330    /// CMake target `sortformer_diar_v2`,用 `model-sortformer-diar-v2` feature 编译)
331    SortformerDiarV2,
332    /// Seed VC(语音转换)
333    SeedVc,
334    /// RVC(语音转换)
335    Rvc,
336    /// MeanVC2(语音转换)
337    Meanvc2,
338    /// Tone Color VC(独立语音转换,需参考说话人录音;task 为 `vc` 仅离线)
339    ToneColorVc,
340    /// Chatterbox(说话人还原 / TTS)
341    Chatterbox,
342    /// Chatterbox Turbo TTS(快速推理变体)
343    ChatterboxTurbo,
344    /// Vevo2(零样本 TTS / VC / 语音编辑)
345    Vevo2,
346    /// VoxCPM2
347    Voxcpm2,
348    /// Complex Inversion(多任务音频)
349    AceStep,
350
351    // ---- 音乐 / 音频生成与分离 ----
352    /// HTDemucs 音乐源分离
353    Htdemucs,
354    /// HTDemucs 6-stem(鼓/贝斯/人声/其他/吉他/钢琴六轨;task 为 `sep` 仅离线;
355    /// 与 `htdemucs` 同属 `demucs` CMake target,用 `model-demucs` feature 编译)
356    Htdemucs6Stems,
357    /// MossFormer2(重叠语音分离,16kHz;task 为 `sep` 仅离线)
358    Mossformer2,
359    /// TF-GridNet(时频 GridNet 重叠语音分离;task 为 `sep` 仅离线)
360    TfGridnet,
361    /// Mel Band Roformer(乐音建模)
362    MelBandRoformer,
363    /// BSRoformer
364    BsRoformer,
365    /// Muscriptor(乐理描述)
366    Muscriptor,
367    /// Omnivoice(条件自回归生成)
368    Omnivoice,
369    /// Stable Audio(音频生成)
370    StableAudio,
371    /// MiniMax Music3(音乐生成)
372    MinimaxMusic3,
373    /// Supertonic(音乐生成)
374    Supertonic,
375    /// YuE2(音乐生成:符号 ABC 规划 + 语义 codec + NAR 声学流合成;
376    /// 仅离线,task 为 `gen`;请求为纯文本(歌词)+ `style` 选项,不消费音频)
377    Yue2,
378    /// SheetSage2(音频转符号谱:输入录音,输出 ABC 乐谱产物;
379    /// 仅离线,task 为 `midi`;结果在 `artifact_output` / `output_artifacts`)
380    Sheetsage2,
381    /// Voxtral Realtime
382    VoxtralRealtime,
383    /// AudioSR(语音/音频超分辨率)
384    Audiosr,
385    /// Apollo(44.1kHz 压缩音频音乐修复;task 为 `s2s` 仅离线)
386    Apollo,
387    /// UniverSR(复数 STFT 流匹配音频超分至 48kHz;task 为 `s2s` 仅离线;
388    /// 选项 `input_sample_rate`/`sampler_mode`/`num_inference_steps`/
389    /// `guidance_scale`/`seed`)
390    Universr,
391    /// 内置音频工具(降噪/增强/超分:deepfilternet2、rnnoise、zipenhancer、
392    /// gtcrn 系列、flashsr;task 为 `s2s` 仅离线;加载时 `model_path`
393    /// 传权重文件或其所在目录(权重需另行下载,非内嵌),另须经 `load_options`
394    /// 传 `{"utility":"<工具 ID>"}`(如 `"rnnoise"`)指定实现)
395    BuiltinAudioUtils,
396    /// ControlFoley(可控电影音效生成)
397    ControlFoley,
398    /// MidoAudio / MIdashengLM 生成式音频
399    MidashEnglmGen,
400    /// LiveAvatar(Wan S2V 音频驱动数字人;task 为 `sfx`(即 `gen`),
401    /// 离线+流式)
402    Liveavatar,
403    /// RE-USE(采样率无关语音修复,双向 Mamba;task 为 `s2s` 仅离线)
404    Reuse,
405    /// SAM Audio(prompt 条件音频分离;task 为 `s2s` 仅离线)
406    SamAudio,
407    /// Sidon v0.1(单说话人语音修复,48kHz 输出;task 为 `s2s` 仅离线)
408    Sidon,
409    /// Smart Turn(音频原生轮次完成检测,Whisper-Tiny 编码器;
410    /// task 为 `turn` 仅离线,16kHz mono,选项 `threshold`,结果在
411    /// `custom_schema_output`(schema `smart_turn.v1`))
412    SmartTurn,
413
414    // ---- 唤醒词 / 音频分类 ----
415    /// microWakeWord(MixedNet TFLite 转 GGUF 的流式唤醒词检测;
416    /// task 为 `wake`(别名 `wake_word`),离线+流式,16kHz mono,
417    /// 检测结果在 `speech_segments`)
418    MicroWakeWord,
419    /// sherpa KWS(Zipformer transducer 关键词检测,sherpa-onnx 导出;
420    /// task 为 `wake`(别名 `wake_word`),离线+流式,16kHz mono,
421    /// 检测结果在 `speech_segments`)
422    SherpaKws,
423    /// CED(Consistent Ensemble Distillation ViT 音频分类,527 个 AudioSet
424    /// 类别;task 为 `cls`(别名 `classify`)仅离线,结果在
425    /// `custom_schema_output`(schema `ced.classification.v1`))
426    Ced,
427    /// AST AudioSet(Audio Spectrogram Transformer 多标签分类,527 个
428    /// AudioSet 类别;task 为 `cls`(别名 `classify`)仅离线,结果在
429    /// `custom_schema_output`(schema `ast_audioset.classification.v1`))
430    AstAudioset,
431
432    // ---- 其他 ----
433    /// Miocodec / Miotts(小米语音)
434    Miocodec,
435    /// Miotts(小米 TTS)
436    Miotts,
437    /// Vibevoice(语音转换)
438    Vibevoice,
439    /// Dramabox(剧本播客)
440    Dramabox,
441    /// Heartmula
442    Heartmula,
443    /// Inflect V2
444    InflectV2,
445    /// Qwen3 Forced Aligner(强制对齐)
446    Qwen3ForcedAligner,
447    /// MMS Forced Aligner(强制对齐,社区模型)
448    MmsForcedAligner,
449
450    /// 未收录的模型族名(原样传给 C 边界)。
451    Custom(String),
452}
453
454impl ModelFamily {
455    /// 按文件名关键词推断模型族。
456    ///
457    /// GGUF / NeMo safetensors 无法被引擎自动探测族别,加载时必须显式指定。
458    /// 上游社区习惯以模型族名命名权重文件(如 `qwen3-asr-0.6b-q8_0.gguf`、
459    /// `sortformer-diar-4spk-v1-q8_0.gguf`),本函数从文件名中的关键词
460    /// 匹配到对应 [`ModelFamily`];无法识别时返回 `None`,调用方应回退到
461    /// 显式 hint 或 [`ModelFamily::Custom`]。
462    ///
463    /// 匹配规则按关键词**首次命中**,文件名需包含明确的族名(如 `qwen3`、
464    /// `citrinet`、`moss-tts-nano`)。`#family=` 片段(`path#family=qwen3_asr`)
465    /// 会被剥离后参与匹配,同时优先于关键词命中。
466    ///
467    /// # 示例
468    ///
469    /// ```
470    /// use audio_cpp::ModelFamily;
471    ///
472    /// assert_eq!(
473    ///     ModelFamily::from_path("models/qwen3-asr-0.6b-q8_0.gguf"),
474    ///     Some(ModelFamily::Qwen3Asr),
475    /// );
476    /// assert_eq!(
477    ///     ModelFamily::from_path("model.gguf#family=citrinet_asr"),
478    ///     Some(ModelFamily::CitrinetAsr),
479    /// );
480    /// assert_eq!(ModelFamily::from_path("my-weights.gguf"), None);
481    /// ```
482    pub fn from_path(path: &str) -> Option<ModelFamily> {
483        let lower = path.to_ascii_lowercase();
484        // `#family=xxx` 片段:显式指定优先,直接映射后返回。
485        if let Some((_, f)) = lower.rsplit_once("#family=") {
486            return Some(ModelFamily::from(f.trim()));
487        }
488        // 关键词 → 族:按文件名常用命名约定匹配。
489        const KEYWORDS: &[(&str, ModelFamily)] = &[
490            ("silero_vad", ModelFamily::SileroVad),
491            ("silero-vad", ModelFamily::SileroVad),
492            ("marblenet_vad", ModelFamily::MarblenetVad),
493            ("marblenet-vad", ModelFamily::MarblenetVad),
494            ("marblenet", ModelFamily::MarblenetVad),
495            ("pulsevad", ModelFamily::Pulsevad),
496            ("pulse-vad", ModelFamily::Pulsevad),
497            ("pulse", ModelFamily::Pulsevad),
498            ("firered_vad", ModelFamily::FireredVad),
499            ("firered-vad", ModelFamily::FireredVad),
500            ("fireredvad", ModelFamily::FireredVad),
501            ("qwen3_asr", ModelFamily::Qwen3Asr),
502            ("qwen3-asr", ModelFamily::Qwen3Asr),
503            ("qwen3_tts", ModelFamily::Qwen3Tts),
504            ("qwen3-tts", ModelFamily::Qwen3Tts),
505            ("qwen3_forced_aligner", ModelFamily::Qwen3ForcedAligner),
506            ("qwen3-forced-aligner", ModelFamily::Qwen3ForcedAligner),
507            ("qwen3", ModelFamily::Qwen3Asr),
508            ("citrinet", ModelFamily::CitrinetAsr),
509            ("sense_asr", ModelFamily::SenseAsr),
510            ("sense-asr", ModelFamily::SenseAsr),
511            ("sensevoice", ModelFamily::SenseAsr),
512            ("fun-asr-nano", ModelFamily::FunAsrNano),
513            ("fun_asr_nano", ModelFamily::FunAsrNano),
514            ("funasr", ModelFamily::FunAsrNano),
515            ("higgs_audio_stt", ModelFamily::HiggsAudioStt),
516            ("higgs_audio_tts", ModelFamily::HiggsAudioTts),
517            ("higgs", ModelFamily::HiggsAudioStt),
518            // hviske_asr_v6 必须排在裸 "hviske" 之前,否则会被吞掉。
519            ("hviske_asr_v6", ModelFamily::HviskeAsrV6),
520            ("hviske-asr-v6", ModelFamily::HviskeAsrV6),
521            ("hviske-v6", ModelFamily::HviskeAsrV6),
522            ("hviskev6", ModelFamily::HviskeAsrV6),
523            ("hviske", ModelFamily::HviskeAsr),
524            ("whistle_asr", ModelFamily::WhistleAsr),
525            ("whistle-asr", ModelFamily::WhistleAsr),
526            ("whistle", ModelFamily::WhistleAsr),
527            ("kroko", ModelFamily::KrokoAsr),
528            ("nemotron_3_diar", ModelFamily::Nemotron3Diar),
529            ("nemotron-3-diar", ModelFamily::Nemotron3Diar),
530            ("nemotron_3", ModelFamily::Nemotron3Diar),
531            ("nemotron-3", ModelFamily::Nemotron3Diar),
532            ("nemotron", ModelFamily::NemotronAsr),
533            ("parakeet", ModelFamily::ParakeetTdt),
534            (
535                "vibevoice_asr_streaming",
536                ModelFamily::VibevoiceAsrStreaming,
537            ),
538            (
539                "vibevoice-asr-streaming",
540                ModelFamily::VibevoiceAsrStreaming,
541            ),
542            ("vibevoice_asr", ModelFamily::VibevoiceAsr),
543            ("firered_audio", ModelFamily::FireredAudio),
544            ("firered-audio", ModelFamily::FireredAudio),
545            ("granite5asr", ModelFamily::Granite5Asr),
546            ("granite-speech", ModelFamily::Granite5Asr),
547            ("granite_speech", ModelFamily::Granite5Asr),
548            ("granite", ModelFamily::Granite5Asr),
549            ("audio8_asr", ModelFamily::Audio8Asr),
550            ("audio8-asr", ModelFamily::Audio8Asr),
551            ("audio8asr", ModelFamily::Audio8Asr),
552            ("arkasr", ModelFamily::Audio8Asr),
553            ("audio8_tts", ModelFamily::Audio8Tts),
554            ("audio8-tts", ModelFamily::Audio8Tts),
555            ("audio8tts", ModelFamily::Audio8Tts),
556            ("fireredtts3", ModelFamily::Fireredtts3),
557            ("firered_tts3", ModelFamily::Fireredtts3),
558            ("firered-tts3", ModelFamily::Fireredtts3),
559            ("vibevoice", ModelFamily::Vibevoice),
560            // confucius4_r2t2 必须排在裸 "confucius" 之前,否则会被吞掉。
561            ("confucius4_r2t2", ModelFamily::Confucius4R2t2),
562            ("confucius4-r2t2", ModelFamily::Confucius4R2t2),
563            ("r2t2", ModelFamily::Confucius4R2t2),
564            ("confucius", ModelFamily::Confucius4Tts),
565            ("dots_tts", ModelFamily::DotsTts),
566            ("dots-tts", ModelFamily::DotsTts),
567            ("fish_audio", ModelFamily::FishAudio),
568            ("fish-audio", ModelFamily::FishAudio),
569            ("glm_tts", ModelFamily::GlmTts),
570            ("glm-tts", ModelFamily::GlmTts),
571            ("index_tts2", ModelFamily::IndexTts2),
572            ("index-tts2", ModelFamily::IndexTts2),
573            ("irodori", ModelFamily::IrodoriTts),
574            // moss_transcribe_diarize 必须排在裸 "moss" 之前,否则会被吞掉。
575            (
576                "moss_transcribe_diarize",
577                ModelFamily::MossTranscribeDiarize,
578            ),
579            (
580                "moss-transcribe-diarize",
581                ModelFamily::MossTranscribeDiarize,
582            ),
583            // mossformer2 必须排在裸 "moss" 之前,否则会被吞掉。
584            ("mossformer2", ModelFamily::Mossformer2),
585            ("mossformer-2", ModelFamily::Mossformer2),
586            ("mossformer", ModelFamily::Mossformer2),
587            ("moss-tts-nano", ModelFamily::MossTtsNano),
588            ("moss_tts_nano", ModelFamily::MossTtsNano),
589            ("moss-tts-local", ModelFamily::MossTtsLocal),
590            ("moss_tts_local", ModelFamily::MossTtsLocal),
591            // moss_tts_v15 / moss_ttsd 必须排在裸 "moss" 之前,否则会被吞掉。
592            ("moss-tts-v15", ModelFamily::MossTtsV15),
593            ("moss_tts_v15", ModelFamily::MossTtsV15),
594            ("moss_ttsd", ModelFamily::MossTtsd),
595            ("moss-ttsd", ModelFamily::MossTtsd),
596            ("mossttsd", ModelFamily::MossTtsd),
597            ("moss_voicegen", ModelFamily::MossVoicegen),
598            ("moss-voicegen", ModelFamily::MossVoicegen),
599            ("moss", ModelFamily::MossTtsNano),
600            ("neutts", ModelFamily::Neutts),
601            ("outetts", ModelFamily::Outetts),
602            ("pocket_tts", ModelFamily::PocketTts),
603            ("pocket-tts", ModelFamily::PocketTts),
604            ("vietneu", ModelFamily::VieneuV3Turbo),
605            ("vieneu", ModelFamily::VieneuV3Turbo),
606            ("f5_tts", ModelFamily::F5Tts),
607            ("f5-tts", ModelFamily::F5Tts),
608            ("magpie", ModelFamily::MagpieTts),
609            ("personaplex", ModelFamily::Personaplex),
610            ("soprano_tts", ModelFamily::SopranoTts),
611            ("soprano-tts", ModelFamily::SopranoTts),
612            ("soprano", ModelFamily::SopranoTts),
613            // index_echo 必须排在裸 "echo" 之前,否则会被吞掉。
614            ("index_echo", ModelFamily::IndexEcho),
615            ("index-echo", ModelFamily::IndexEcho),
616            ("indexecho", ModelFamily::IndexEcho),
617            ("echo_tts", ModelFamily::EchoTts),
618            ("echo-tts", ModelFamily::EchoTts),
619            ("echo", ModelFamily::EchoTts),
620            ("kokoro_tts", ModelFamily::KokoroTts),
621            ("kokoro-tts", ModelFamily::KokoroTts),
622            ("kokoro", ModelFamily::KokoroTts),
623            ("piper_tts", ModelFamily::PiperTts),
624            ("piper-tts", ModelFamily::PiperTts),
625            ("piper", ModelFamily::PiperTts),
626            // kitten_tts2 必须排在 kitten_tts / 裸 "kitten" 之前,否则会被吞掉。
627            ("kitten_tts2", ModelFamily::KittenTts2),
628            ("kitten-tts2", ModelFamily::KittenTts2),
629            ("kitten_tts_2", ModelFamily::KittenTts2),
630            ("kittentts2", ModelFamily::KittenTts2),
631            ("kitten_tts", ModelFamily::KittenTts),
632            ("kitten-tts", ModelFamily::KittenTts),
633            ("kitten", ModelFamily::KittenTts),
634            ("kugelaudio", ModelFamily::Kugelaudio),
635            ("kugel-audio", ModelFamily::Kugelaudio),
636            ("kugel_audio", ModelFamily::Kugelaudio),
637            ("kugel", ModelFamily::Kugelaudio),
638            ("maya1", ModelFamily::Maya1),
639            ("maya-1", ModelFamily::Maya1),
640            ("maya_1", ModelFamily::Maya1),
641            ("maya", ModelFamily::Maya1),
642            ("voxcpm1", ModelFamily::Voxcpm1),
643            ("voxcpm-1", ModelFamily::Voxcpm1),
644            ("sanotts", ModelFamily::Sanotts),
645            ("sano-tts", ModelFamily::Sanotts),
646            ("sano_tts", ModelFamily::Sanotts),
647            ("sopro_tts", ModelFamily::SoproTts),
648            ("sopro-tts", ModelFamily::SoproTts),
649            ("sopro", ModelFamily::SoproTts),
650            ("mira_tts", ModelFamily::MiraTts),
651            ("mira-tts", ModelFamily::MiraTts),
652            ("miratts", ModelFamily::MiraTts),
653            ("mira", ModelFamily::MiraTts),
654            ("cosyvoice3", ModelFamily::Cosyvoice3),
655            ("cosyvoice-3", ModelFamily::Cosyvoice3),
656            ("cosyvoice", ModelFamily::Cosyvoice3),
657            ("breeze_tts", ModelFamily::BreezeTts),
658            ("breeze-tts", ModelFamily::BreezeTts),
659            ("breeze", ModelFamily::BreezeTts),
660            ("vibeasr", ModelFamily::Vibeasr),
661            ("vibe-asr", ModelFamily::Vibeasr),
662            ("moonshine_asr", ModelFamily::MoonshineAsr),
663            ("moonshine-asr", ModelFamily::MoonshineAsr),
664            ("moonshine", ModelFamily::MoonshineAsr),
665            ("niagara_asr", ModelFamily::NiagaraAsr),
666            ("niagara-asr", ModelFamily::NiagaraAsr),
667            ("niagara", ModelFamily::NiagaraAsr),
668            ("canary_asr", ModelFamily::CanaryAsr),
669            ("canary-asr", ModelFamily::CanaryAsr),
670            ("canary", ModelFamily::CanaryAsr),
671            ("cohere_asr", ModelFamily::CohereAsr),
672            ("cohere-asr", ModelFamily::CohereAsr),
673            ("cohere", ModelFamily::CohereAsr),
674            ("audio_flamingo", ModelFamily::AudioFlamingo),
675            ("audio-flamingo", ModelFamily::AudioFlamingo),
676            ("audioflamingo", ModelFamily::AudioFlamingo),
677            ("crisperwhisper", ModelFamily::Crisperwhisper),
678            ("crisper-whisper", ModelFamily::Crisperwhisper),
679            ("crisper_whisper", ModelFamily::Crisperwhisper),
680            ("gigaam_asr", ModelFamily::GigaamAsr),
681            ("gigaam-asr", ModelFamily::GigaamAsr),
682            ("gigaam", ModelFamily::GigaamAsr),
683            ("lfm2_audio", ModelFamily::Lfm2Audio),
684            ("lfm2-audio", ModelFamily::Lfm2Audio),
685            ("lfm2", ModelFamily::Lfm2Audio),
686            // owsm_ctc 必须排在裸 "owsm" 之前,否则会被吞掉。
687            ("owsm_ctc", ModelFamily::OwsmCtc),
688            ("owsm-ctc", ModelFamily::OwsmCtc),
689            ("owsmctc", ModelFamily::OwsmCtc),
690            ("owsm", ModelFamily::Owsm),
691            ("samsone", ModelFamily::Samsone),
692            ("sam-sone", ModelFamily::Samsone),
693            ("zipvoice", ModelFamily::Zipvoice),
694            ("zip-voice", ModelFamily::Zipvoice),
695            ("liveavatar", ModelFamily::Liveavatar),
696            ("live-avatar", ModelFamily::Liveavatar),
697            ("reuse", ModelFamily::Reuse),
698            ("re-use", ModelFamily::Reuse),
699            ("sam_audio", ModelFamily::SamAudio),
700            ("sam-audio", ModelFamily::SamAudio),
701            ("samaudio", ModelFamily::SamAudio),
702            ("sidon", ModelFamily::Sidon),
703            ("smart_turn", ModelFamily::SmartTurn),
704            ("smart-turn", ModelFamily::SmartTurn),
705            ("smartturn", ModelFamily::SmartTurn),
706            ("auk", ModelFamily::Auk),
707            ("minimax_h3", ModelFamily::MinimaxH3),
708            ("minimax-h3", ModelFamily::MinimaxH3),
709            ("sortformer_diar_v2", ModelFamily::SortformerDiarV2),
710            ("sortformer-diar-v2", ModelFamily::SortformerDiarV2),
711            ("sortformer_diar", ModelFamily::SortformerDiar),
712            ("sortformer-diar", ModelFamily::SortformerDiar),
713            ("sortformer", ModelFamily::SortformerDiar),
714            ("seed_vc", ModelFamily::SeedVc),
715            ("seed-vc", ModelFamily::SeedVc),
716            ("seedvc", ModelFamily::SeedVc),
717            ("rvc", ModelFamily::Rvc),
718            ("meanvc2", ModelFamily::Meanvc2),
719            ("mean-vc2", ModelFamily::Meanvc2),
720            ("tone_color_vc", ModelFamily::ToneColorVc),
721            ("tone-color-vc", ModelFamily::ToneColorVc),
722            ("tone_color", ModelFamily::ToneColorVc),
723            ("tonecolor", ModelFamily::ToneColorVc),
724            ("chatterbox_turbo", ModelFamily::ChatterboxTurbo),
725            ("chatterbox-turbo", ModelFamily::ChatterboxTurbo),
726            ("chatterbox", ModelFamily::Chatterbox),
727            ("vevo2", ModelFamily::Vevo2),
728            ("voxcpm2", ModelFamily::Voxcpm2),
729            ("ace_step", ModelFamily::AceStep),
730            ("ace-step", ModelFamily::AceStep),
731            // htdemucs_6stems(含别名 6s)必须排在裸 "htdemucs" 之前,否则会被吞掉。
732            ("htdemucs_6stems", ModelFamily::Htdemucs6Stems),
733            ("htdemucs-6stems", ModelFamily::Htdemucs6Stems),
734            ("htdemucs_6s", ModelFamily::Htdemucs6Stems),
735            ("htdemucs-6s", ModelFamily::Htdemucs6Stems),
736            ("htdemucs", ModelFamily::Htdemucs),
737            ("demucs", ModelFamily::Htdemucs),
738            ("tf_gridnet", ModelFamily::TfGridnet),
739            ("tf-gridnet", ModelFamily::TfGridnet),
740            ("tfgridnet", ModelFamily::TfGridnet),
741            ("gridnet", ModelFamily::TfGridnet),
742            ("mel-band-roformer", ModelFamily::MelBandRoformer),
743            ("mel_band_roformer", ModelFamily::MelBandRoformer),
744            ("bs-roformer", ModelFamily::BsRoformer),
745            ("bs_roformer", ModelFamily::BsRoformer),
746            ("muscriptor", ModelFamily::Muscriptor),
747            ("omnivoice", ModelFamily::Omnivoice),
748            ("stable_audio", ModelFamily::StableAudio),
749            ("stable-audio", ModelFamily::StableAudio),
750            ("minimax_music3", ModelFamily::MinimaxMusic3),
751            ("minimax-music3", ModelFamily::MinimaxMusic3),
752            ("supertonic", ModelFamily::Supertonic),
753            ("yue2", ModelFamily::Yue2),
754            ("yue-2", ModelFamily::Yue2),
755            ("sheetsage2", ModelFamily::Sheetsage2),
756            ("sheetsage-2", ModelFamily::Sheetsage2),
757            ("sheetsage", ModelFamily::Sheetsage2),
758            ("voxtral-realtime", ModelFamily::VoxtralRealtime),
759            ("voxtral", ModelFamily::VoxtralRealtime),
760            ("audiosr", ModelFamily::Audiosr),
761            ("audio-sr", ModelFamily::Audiosr),
762            ("apollo", ModelFamily::Apollo),
763            ("universr", ModelFamily::Universr),
764            ("univer-sr", ModelFamily::Universr),
765            ("controlfoley", ModelFamily::ControlFoley),
766            ("control-foley", ModelFamily::ControlFoley),
767            ("midashenglm_gen", ModelFamily::MidashEnglmGen),
768            ("midashenglm-gen", ModelFamily::MidashEnglmGen),
769            ("midashen", ModelFamily::MidashEnglmGen),
770            ("builtin_audio_utils", ModelFamily::BuiltinAudioUtils),
771            ("builtin-audio-utils", ModelFamily::BuiltinAudioUtils),
772            ("deepfilternet", ModelFamily::BuiltinAudioUtils),
773            ("rnnoise", ModelFamily::BuiltinAudioUtils),
774            ("zipenhancer", ModelFamily::BuiltinAudioUtils),
775            ("gtcrn", ModelFamily::BuiltinAudioUtils),
776            ("flashsr", ModelFamily::BuiltinAudioUtils),
777            ("miocodec", ModelFamily::Miocodec),
778            ("miotts", ModelFamily::Miotts),
779            ("dramabox", ModelFamily::Dramabox),
780            ("heartmula", ModelFamily::Heartmula),
781            ("inflect", ModelFamily::InflectV2),
782            ("mms_forced_aligner", ModelFamily::MmsForcedAligner),
783            ("mms", ModelFamily::MmsForcedAligner),
784            ("forced-aligner", ModelFamily::Qwen3ForcedAligner),
785            // 唤醒词 / 分类族:`ced` 是 `forced-aligner`(for**ced**)的子串,
786            // 必须排在它之后,否则对齐模型会被误判为 CED。
787            ("micro_wake_word", ModelFamily::MicroWakeWord),
788            ("micro-wake-word", ModelFamily::MicroWakeWord),
789            ("micro_wake", ModelFamily::MicroWakeWord),
790            ("micro-wake", ModelFamily::MicroWakeWord),
791            ("microwake", ModelFamily::MicroWakeWord),
792            ("sherpa_kws", ModelFamily::SherpaKws),
793            ("sherpa-kws", ModelFamily::SherpaKws),
794            ("sherpa", ModelFamily::SherpaKws),
795            ("ced", ModelFamily::Ced),
796            ("ast_audioset", ModelFamily::AstAudioset),
797            ("ast-audioset", ModelFamily::AstAudioset),
798            ("audioset", ModelFamily::AstAudioset),
799        ];
800        KEYWORDS
801            .iter()
802            .find(|(k, _)| lower.contains(k))
803            .map(|(_, family)| family.clone())
804    }
805
806    /// 转换为传给 C 边界的字符串。
807    pub fn as_str(&self) -> &str {
808        match self {
809            ModelFamily::SileroVad => "silero_vad",
810            ModelFamily::MarblenetVad => "marblenet_vad",
811            ModelFamily::Pulsevad => "pulsevad",
812            ModelFamily::FireredVad => "firered_vad",
813            ModelFamily::Qwen3Asr => "qwen3_asr",
814            ModelFamily::CitrinetAsr => "citrinet_asr",
815            ModelFamily::SenseAsr => "sense_asr",
816            ModelFamily::FunAsrNano => "fun_asr_nano",
817            ModelFamily::HiggsAudioStt => "higgs_audio_stt",
818            ModelFamily::HviskeAsr => "hviske_asr",
819            ModelFamily::HviskeAsrV6 => "hviske_asr_v6",
820            ModelFamily::KrokoAsr => "kroko_asr",
821            ModelFamily::NemotronAsr => "nemotron_asr",
822            ModelFamily::Nemotron3Diar => "nemotron_3_diar",
823            ModelFamily::ParakeetTdt => "parakeet_tdt",
824            ModelFamily::VibevoiceAsr => "vibevoice_asr",
825            ModelFamily::VibevoiceAsrStreaming => "vibevoice_asr_streaming",
826            ModelFamily::FireredAudio => "firered_audio",
827            ModelFamily::Granite5Asr => "granite5asr",
828            ModelFamily::Audio8Asr => "audio8_asr",
829            ModelFamily::Audio8Tts => "audio8_tts",
830            ModelFamily::Vibeasr => "vibeasr",
831            ModelFamily::MoonshineAsr => "moonshine_asr",
832            ModelFamily::NiagaraAsr => "niagara_asr",
833            ModelFamily::CanaryAsr => "canary_asr",
834            ModelFamily::CohereAsr => "cohere_asr",
835            ModelFamily::MossTranscribeDiarize => "moss_transcribe_diarize",
836            ModelFamily::Confucius4R2t2 => "confucius4_r2t2",
837            ModelFamily::AudioFlamingo => "audio_flamingo",
838            ModelFamily::Crisperwhisper => "crisperwhisper",
839            ModelFamily::GigaamAsr => "gigaam_asr",
840            ModelFamily::IndexEcho => "index_echo",
841            ModelFamily::Lfm2Audio => "lfm2_audio",
842            ModelFamily::Owsm => "owsm",
843            ModelFamily::OwsmCtc => "owsm_ctc",
844            ModelFamily::Samsone => "samsone",
845            ModelFamily::WhistleAsr => "whistle_asr",
846            ModelFamily::Qwen3Tts => "qwen3_tts",
847            ModelFamily::Confucius4Tts => "confucius4_tts",
848            ModelFamily::DotsTts => "dots_tts",
849            ModelFamily::FishAudio => "fish_audio",
850            ModelFamily::GlmTts => "glm_tts",
851            ModelFamily::HiggsAudioTts => "higgs_audio_tts",
852            ModelFamily::IndexTts2 => "index_tts2",
853            ModelFamily::IrodoriTts => "irodori_tts",
854            ModelFamily::MossTtsLocal => "moss_tts_local",
855            ModelFamily::MossTtsNano => "moss_tts_nano",
856            ModelFamily::MossTtsV15 => "moss_tts_v15",
857            ModelFamily::MossVoicegen => "moss_voicegen",
858            ModelFamily::Neutts => "neutts",
859            ModelFamily::Outetts => "outetts",
860            ModelFamily::PocketTts => "pocket_tts",
861            ModelFamily::VieneuV3Turbo => "vieneu_v3_turbo",
862            ModelFamily::Fireredtts3 => "fireredtts3",
863            ModelFamily::F5Tts => "f5_tts",
864            ModelFamily::MagpieTts => "magpie_tts",
865            ModelFamily::Personaplex => "personaplex",
866            ModelFamily::SopranoTts => "soprano_tts",
867            ModelFamily::EchoTts => "echo_tts",
868            ModelFamily::Voxcpm1 => "voxcpm1",
869            ModelFamily::KokoroTts => "kokoro_tts",
870            ModelFamily::Zipvoice => "zipvoice",
871            ModelFamily::Auk => "auk",
872            ModelFamily::PiperTts => "piper_tts",
873            ModelFamily::KittenTts => "kitten_tts",
874            ModelFamily::KittenTts2 => "kitten_tts2",
875            ModelFamily::Kugelaudio => "kugelaudio",
876            ModelFamily::Maya1 => "maya1",
877            ModelFamily::MossTtsd => "moss_ttsd",
878            ModelFamily::MinimaxH3 => "minimax_h3",
879            ModelFamily::Sanotts => "sanotts",
880            ModelFamily::SoproTts => "sopro_tts",
881            ModelFamily::MiraTts => "mira_tts",
882            ModelFamily::Cosyvoice3 => "cosyvoice3",
883            ModelFamily::BreezeTts => "breeze_tts",
884            ModelFamily::SortformerDiar => "sortformer_diar",
885            ModelFamily::SortformerDiarV2 => "sortformer_diar_v2",
886            ModelFamily::SeedVc => "seed_vc",
887            ModelFamily::Rvc => "rvc",
888            ModelFamily::Meanvc2 => "meanvc2",
889            ModelFamily::ToneColorVc => "tone_color_vc",
890            ModelFamily::Chatterbox => "chatterbox",
891            ModelFamily::ChatterboxTurbo => "chatterbox_turbo",
892            ModelFamily::Vevo2 => "vevo2",
893            ModelFamily::Voxcpm2 => "voxcpm2",
894            ModelFamily::AceStep => "ace_step",
895            ModelFamily::Htdemucs => "htdemucs",
896            ModelFamily::Htdemucs6Stems => "htdemucs_6stems",
897            ModelFamily::Mossformer2 => "mossformer2",
898            ModelFamily::TfGridnet => "tf_gridnet",
899            ModelFamily::MelBandRoformer => "mel_band_roformer",
900            ModelFamily::BsRoformer => "bs_roformer",
901            ModelFamily::Muscriptor => "muscriptor",
902            ModelFamily::Omnivoice => "omnivoice",
903            ModelFamily::StableAudio => "stable_audio",
904            ModelFamily::MinimaxMusic3 => "minimax_music3",
905            ModelFamily::Supertonic => "supertonic",
906            ModelFamily::Yue2 => "yue2",
907            ModelFamily::Sheetsage2 => "sheetsage2",
908            ModelFamily::VoxtralRealtime => "voxtral_realtime",
909            ModelFamily::Audiosr => "audiosr",
910            ModelFamily::Apollo => "apollo",
911            ModelFamily::Universr => "universr",
912            ModelFamily::BuiltinAudioUtils => "builtin_audio_utils",
913            ModelFamily::ControlFoley => "controlfoley",
914            ModelFamily::MidashEnglmGen => "midashenglm_gen",
915            ModelFamily::Liveavatar => "liveavatar",
916            ModelFamily::Reuse => "reuse",
917            ModelFamily::SamAudio => "sam_audio",
918            ModelFamily::Sidon => "sidon",
919            ModelFamily::SmartTurn => "smart_turn",
920            ModelFamily::MicroWakeWord => "micro_wake_word",
921            ModelFamily::SherpaKws => "sherpa_kws",
922            ModelFamily::Ced => "ced",
923            ModelFamily::AstAudioset => "ast_audioset",
924            ModelFamily::Miocodec => "miocodec",
925            ModelFamily::Miotts => "miotts",
926            ModelFamily::Vibevoice => "vibevoice",
927            ModelFamily::Dramabox => "dramabox",
928            ModelFamily::Heartmula => "heartmula",
929            ModelFamily::InflectV2 => "inflect_v2",
930            ModelFamily::Qwen3ForcedAligner => "qwen3_forced_aligner",
931            ModelFamily::MmsForcedAligner => "mms_forced_aligner",
932            ModelFamily::Custom(s) => s,
933        }
934    }
935}
936
937impl From<&str> for ModelFamily {
938    /// 从字符串构造:收录的名字映射到对应变体,未收录的落入 `Custom`。
939    fn from(s: &str) -> Self {
940        match s {
941            "silero_vad" => ModelFamily::SileroVad,
942            "marblenet_vad" => ModelFamily::MarblenetVad,
943            "pulsevad" => ModelFamily::Pulsevad,
944            "firered_vad" => ModelFamily::FireredVad,
945            "firered-vad" => ModelFamily::FireredVad,
946            "qwen3_asr" => ModelFamily::Qwen3Asr,
947            "citrinet_asr" => ModelFamily::CitrinetAsr,
948            "sense_asr" => ModelFamily::SenseAsr,
949            "fun_asr_nano" => ModelFamily::FunAsrNano,
950            "higgs_audio_stt" => ModelFamily::HiggsAudioStt,
951            "hviske_asr" => ModelFamily::HviskeAsr,
952            "hviske_asr_v6" => ModelFamily::HviskeAsrV6,
953            "hviske-asr-v6" => ModelFamily::HviskeAsrV6,
954            "kroko_asr" => ModelFamily::KrokoAsr,
955            "nemotron_asr" => ModelFamily::NemotronAsr,
956            "nemotron_3_diar" => ModelFamily::Nemotron3Diar,
957            "parakeet_tdt" => ModelFamily::ParakeetTdt,
958            "vibevoice_asr" => ModelFamily::VibevoiceAsr,
959            "vibevoice_asr_streaming" => ModelFamily::VibevoiceAsrStreaming,
960            "firered_audio" => ModelFamily::FireredAudio,
961            "granite5asr" => ModelFamily::Granite5Asr,
962            "granite_speech5_asr" => ModelFamily::Granite5Asr,
963            "granite_speech" => ModelFamily::Granite5Asr,
964            "granite_speech5_ctc" => ModelFamily::Granite5Asr,
965            "audio8_asr" => ModelFamily::Audio8Asr,
966            "arkasr" => ModelFamily::Audio8Asr,
967            "audio8_tts" => ModelFamily::Audio8Tts,
968            "vibeasr" => ModelFamily::Vibeasr,
969            "moonshine_asr" => ModelFamily::MoonshineAsr,
970            "niagara_asr" => ModelFamily::NiagaraAsr,
971            "canary_asr" => ModelFamily::CanaryAsr,
972            "cohere_asr" => ModelFamily::CohereAsr,
973            "moss_transcribe_diarize" => ModelFamily::MossTranscribeDiarize,
974            "confucius4_r2t2" => ModelFamily::Confucius4R2t2,
975            "audio_flamingo" => ModelFamily::AudioFlamingo,
976            "crisperwhisper" => ModelFamily::Crisperwhisper,
977            "gigaam_asr" => ModelFamily::GigaamAsr,
978            "index_echo" => ModelFamily::IndexEcho,
979            "lfm2_audio" => ModelFamily::Lfm2Audio,
980            "owsm" => ModelFamily::Owsm,
981            "owsm_ctc" => ModelFamily::OwsmCtc,
982            "samsone" => ModelFamily::Samsone,
983            "whistle_asr" => ModelFamily::WhistleAsr,
984            "whistle-asr" => ModelFamily::WhistleAsr,
985            "qwen3_tts" => ModelFamily::Qwen3Tts,
986            "confucius4_tts" => ModelFamily::Confucius4Tts,
987            "dots_tts" => ModelFamily::DotsTts,
988            "fish_audio" => ModelFamily::FishAudio,
989            "glm_tts" => ModelFamily::GlmTts,
990            "higgs_audio_tts" => ModelFamily::HiggsAudioTts,
991            "index_tts2" => ModelFamily::IndexTts2,
992            "irodori_tts" => ModelFamily::IrodoriTts,
993            "moss_tts_local" => ModelFamily::MossTtsLocal,
994            "moss_tts_nano" => ModelFamily::MossTtsNano,
995            "moss_tts_v15" => ModelFamily::MossTtsV15,
996            "moss_voicegen" => ModelFamily::MossVoicegen,
997            "neutts" => ModelFamily::Neutts,
998            "outetts" => ModelFamily::Outetts,
999            "pocket_tts" => ModelFamily::PocketTts,
1000            "vietneu_tts" => ModelFamily::VieneuV3Turbo,
1001            "vieneu_v3_turbo" => ModelFamily::VieneuV3Turbo,
1002            "fireredtts3" => ModelFamily::Fireredtts3,
1003            "f5_tts" => ModelFamily::F5Tts,
1004            "magpie_tts" => ModelFamily::MagpieTts,
1005            "personaplex" => ModelFamily::Personaplex,
1006            "soprano_tts" => ModelFamily::SopranoTts,
1007            "echo_tts" => ModelFamily::EchoTts,
1008            "voxcpm1" => ModelFamily::Voxcpm1,
1009            "kokoro_tts" => ModelFamily::KokoroTts,
1010            "zipvoice" => ModelFamily::Zipvoice,
1011            "auk" => ModelFamily::Auk,
1012            "piper_tts" => ModelFamily::PiperTts,
1013            "kitten_tts" => ModelFamily::KittenTts,
1014            "kitten_tts2" => ModelFamily::KittenTts2,
1015            "kugelaudio" => ModelFamily::Kugelaudio,
1016            "maya1" => ModelFamily::Maya1,
1017            "moss_ttsd" => ModelFamily::MossTtsd,
1018            "minimax_h3" => ModelFamily::MinimaxH3,
1019            "sanotts" => ModelFamily::Sanotts,
1020            "sopro_tts" => ModelFamily::SoproTts,
1021            "sopro" => ModelFamily::SoproTts,
1022            "sopro_v2" => ModelFamily::SoproTts,
1023            "sopro_v2_turbo" => ModelFamily::SoproTts,
1024            "mira_tts" => ModelFamily::MiraTts,
1025            "miratts" => ModelFamily::MiraTts,
1026            "mira" => ModelFamily::MiraTts,
1027            "MiraTTS" => ModelFamily::MiraTts,
1028            "cosyvoice3" => ModelFamily::Cosyvoice3,
1029            "breeze_tts" => ModelFamily::BreezeTts,
1030            "sortformer_diar" => ModelFamily::SortformerDiar,
1031            "sortformer_diar_v2" => ModelFamily::SortformerDiarV2,
1032            "seed_vc" => ModelFamily::SeedVc,
1033            "rvc" => ModelFamily::Rvc,
1034            "meanvc2" => ModelFamily::Meanvc2,
1035            "tone_color_vc" => ModelFamily::ToneColorVc,
1036            "chatterbox" => ModelFamily::Chatterbox,
1037            "chatterbox_turbo" => ModelFamily::ChatterboxTurbo,
1038            "vevo2" => ModelFamily::Vevo2,
1039            "voxcpm2" => ModelFamily::Voxcpm2,
1040            "ace_step" => ModelFamily::AceStep,
1041            "htdemucs" => ModelFamily::Htdemucs,
1042            "htdemucs_6stems" => ModelFamily::Htdemucs6Stems,
1043            "htdemucs_6s" => ModelFamily::Htdemucs6Stems,
1044            "mossformer2" => ModelFamily::Mossformer2,
1045            "tf_gridnet" => ModelFamily::TfGridnet,
1046            "tf-gridnet" => ModelFamily::TfGridnet,
1047            "mel_band_roformer" => ModelFamily::MelBandRoformer,
1048            "bs_roformer" => ModelFamily::BsRoformer,
1049            "muscriptor" => ModelFamily::Muscriptor,
1050            "omnivoice" => ModelFamily::Omnivoice,
1051            "stable_audio" => ModelFamily::StableAudio,
1052            "minimax_music3" => ModelFamily::MinimaxMusic3,
1053            "supertonic" => ModelFamily::Supertonic,
1054            "yue2" => ModelFamily::Yue2,
1055            "sheetsage2" => ModelFamily::Sheetsage2,
1056            "voxtral_realtime" => ModelFamily::VoxtralRealtime,
1057            "audiosr" => ModelFamily::Audiosr,
1058            "apollo" => ModelFamily::Apollo,
1059            "universr" => ModelFamily::Universr,
1060            "builtin_audio_utils" => ModelFamily::BuiltinAudioUtils,
1061            "controlfoley" => ModelFamily::ControlFoley,
1062            "midashenglm_gen" => ModelFamily::MidashEnglmGen,
1063            "liveavatar" => ModelFamily::Liveavatar,
1064            "reuse" => ModelFamily::Reuse,
1065            "sam_audio" => ModelFamily::SamAudio,
1066            "sidon" => ModelFamily::Sidon,
1067            "smart_turn" => ModelFamily::SmartTurn,
1068            "micro_wake_word" => ModelFamily::MicroWakeWord,
1069            "micro-wake-word" => ModelFamily::MicroWakeWord,
1070            "sherpa_kws" => ModelFamily::SherpaKws,
1071            "sherpa-kws" => ModelFamily::SherpaKws,
1072            "ced" => ModelFamily::Ced,
1073            "ast_audioset" => ModelFamily::AstAudioset,
1074            "ast-audioset" => ModelFamily::AstAudioset,
1075            "miocodec" => ModelFamily::Miocodec,
1076            "miotts" => ModelFamily::Miotts,
1077            "vibevoice" => ModelFamily::Vibevoice,
1078            "dramabox" => ModelFamily::Dramabox,
1079            "heartmula" => ModelFamily::Heartmula,
1080            "inflect_v2" => ModelFamily::InflectV2,
1081            "qwen3_forced_aligner" => ModelFamily::Qwen3ForcedAligner,
1082            "mms_forced_aligner" => ModelFamily::MmsForcedAligner,
1083            other => ModelFamily::Custom(other.to_owned()),
1084        }
1085    }
1086}
1087
1088impl From<String> for ModelFamily {
1089    fn from(s: String) -> Self {
1090        ModelFamily::from(s.as_str())
1091    }
1092}
1093
1094impl AsRef<str> for ModelFamily {
1095    fn as_ref(&self) -> &str {
1096        self.as_str()
1097    }
1098}
1099
1100impl std::fmt::Display for ModelFamily {
1101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1102        f.write_str(self.as_str())
1103    }
1104}
1105
1106/// 计算后端(映射 `audiocpp_model_create_task_session` 的 `backend` 参数)。
1107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1108#[serde(rename_all = "lowercase")]
1109pub enum Backend {
1110    /// CPU
1111    Cpu,
1112    /// CUDA
1113    Cuda,
1114    /// HIP / ROCm
1115    Hip,
1116    /// Vulkan
1117    Vulkan,
1118    /// Metal
1119    Metal,
1120    /// 自动选择最佳可用后端
1121    Best,
1122}
1123
1124impl Backend {
1125    /// 转换为传给 C 边界的字符串。
1126    pub fn as_str(self) -> &'static str {
1127        match self {
1128            Backend::Cpu => "cpu",
1129            Backend::Cuda => "cuda",
1130            Backend::Hip => "hip",
1131            Backend::Vulkan => "vulkan",
1132            Backend::Metal => "metal",
1133            Backend::Best => "best",
1134        }
1135    }
1136}
1137
1138/// 计算设备(`audiocpp_registry_devices_json` 的一项)。
1139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1140pub struct Device {
1141    /// 后端名,如 `"CPU"`。
1142    pub backend: String,
1143    /// 设备序号。
1144    pub index: i32,
1145    /// 设备名。
1146    pub name: String,
1147    /// 设备类型,如 `"CPU"`。
1148    #[serde(rename = "type")]
1149    pub kind: String,
1150}
1151
1152/// 模型族 loader 的声明信息(`audiocpp_registry_loaders_json` 的一项)。
1153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1154pub struct LoaderInfo {
1155    /// 模型族名。
1156    pub family: String,
1157    /// 能力集合。
1158    pub capabilities: Capabilities,
1159    /// 指令策略描述。
1160    pub instructions_policy: String,
1161    /// 可用的 API 端点。
1162    pub api_endpoints: Vec<String>,
1163}
1164
1165/// 能力集合(`audiocpp_model_capabilities_json` / loader 声明)。
1166#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1167pub struct Capabilities {
1168    /// 支持的任务及其模式。
1169    pub supported_tasks: Vec<SupportedTask>,
1170    /// 支持的语言。
1171    pub languages: Vec<String>,
1172    /// 是否支持说话人参考音频。
1173    pub supports_speaker_reference: bool,
1174    /// 是否支持风格条件。
1175    pub supports_style_condition: bool,
1176    /// 是否支持时间戳。
1177    pub supports_timestamps: bool,
1178}
1179
1180/// 一个受支持的任务及其可用模式。
1181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1182pub struct SupportedTask {
1183    /// 任务名,如 `"vad"`。
1184    pub task: String,
1185    /// 可用模式,如 `["offline","streaming"]`。
1186    pub modes: Vec<String>,
1187}
1188
1189/// 已加载模型的元数据(`audiocpp_model_metadata_json`)。
1190#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1191pub struct ModelMetadata {
1192    /// 模型族。
1193    pub family: String,
1194    /// 变体。
1195    pub variant: String,
1196    /// 描述。
1197    pub description: String,
1198    /// 候选配置。
1199    pub config_candidates: Vec<String>,
1200    /// 候选权重。
1201    pub weight_candidates: Vec<String>,
1202}
1203
1204/// 一段音频在采样轴上的时间范围。
1205#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1206pub struct TimeSpan {
1207    /// 起始采样点(含)。
1208    pub start_sample: i64,
1209    /// 结束采样点(不含)。
1210    pub end_sample: i64,
1211}
1212
1213/// 语音活动片段。
1214#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1215pub struct SpeechSegment {
1216    /// 时间范围。
1217    pub span: TimeSpan,
1218    /// 置信度(0..1)。
1219    pub confidence: f32,
1220    /// 附带文本(如 ASR 片段)。
1221    pub text: String,
1222}
1223
1224/// 说话人分离中的一段发言(谁在什么时间说话)。
1225#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1226pub struct SpeakerTurn {
1227    /// 说话人 id,如 `"speaker_0"`。
1228    pub speaker_id: String,
1229    /// 发言时间范围。
1230    pub span: TimeSpan,
1231    /// 置信度(0..1)。
1232    pub confidence: f32,
1233    /// 该段文本(部分模型产出,如带转写的说话人分离)。
1234    #[serde(default)]
1235    pub text: String,
1236}
1237
1238/// 词级时间戳(ASR 逐词对齐)。
1239#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1240pub struct WordTimestamp {
1241    /// 时间范围(采样点)。
1242    pub span: TimeSpan,
1243    /// 词文本。
1244    pub word: String,
1245    /// 置信度(0..1)。
1246    pub confidence: f32,
1247}
1248
1249/// 模型产出的产物(embedding / 状态 / 对齐结果等)。
1250#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1251pub struct VoiceArtifact {
1252    /// 产物种类(如 `speaker_embedding` / `transcript_alignment`)。
1253    pub kind: String,
1254    /// 产物 id。
1255    pub id: String,
1256    /// 二进制载荷的 base64 编码(无载荷时为空字符串)。
1257    #[serde(default)]
1258    pub payload_base64: String,
1259    /// 附加元数据。
1260    #[serde(default)]
1261    pub meta: BTreeMap<String, String>,
1262}
1263
1264/// CLI 选项信息(模型支持的请求 / 会话 / 加载选项)。
1265#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1266pub struct CliOption {
1267    /// 选项名。
1268    pub name: String,
1269    /// 值名称(帮助文本)。
1270    pub value_name: String,
1271    /// 描述。
1272    pub description: String,
1273    /// 是否必填。
1274    pub required: bool,
1275    /// 默认值(可选)。
1276    #[serde(default, skip_serializing_if = "Option::is_none")]
1277    pub default_value: Option<String>,
1278    /// 最小值(可选,字符串形式)。
1279    #[serde(default, skip_serializing_if = "Option::is_none")]
1280    pub min_value: Option<String>,
1281    /// 最大值(可选,字符串形式)。
1282    #[serde(default, skip_serializing_if = "Option::is_none")]
1283    pub max_value: Option<String>,
1284}
1285
1286/// 模型预检时发现的配置 / 权重资产。
1287#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1288pub struct NamedAsset {
1289    /// 资产 id。
1290    pub id: String,
1291    /// 资产路径。
1292    pub path: String,
1293}
1294
1295/// 模型的三类 CLI 选项(请求 / 会话 / 加载)。
1296#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1297pub struct CliInterface {
1298    /// 请求级选项(如 `audio_chunk_seconds`)。
1299    #[serde(default)]
1300    pub request_options: Vec<CliOption>,
1301    /// 会话级选项。
1302    #[serde(default)]
1303    pub session_options: Vec<CliOption>,
1304    /// 加载级选项(如 `weight_id`)。
1305    #[serde(default)]
1306    pub load_options: Vec<CliOption>,
1307}
1308
1309/// 模型预检结果(`Registry::inspect`)。
1310#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1311pub struct ModelInspection {
1312    /// 模型元数据。
1313    pub metadata: ModelMetadata,
1314    /// 能力集合。
1315    pub capabilities: Capabilities,
1316    /// CLI 选项(请求 / 会话 / 加载)。
1317    pub cli: CliInterface,
1318    /// 发现的配置文件。
1319    #[serde(default)]
1320    pub discovered_configs: Vec<NamedAsset>,
1321    /// 发现的权重文件。
1322    #[serde(default)]
1323    pub discovered_weights: Vec<NamedAsset>,
1324    /// 模型根目录。
1325    #[serde(default)]
1326    pub model_root: String,
1327}
1328
1329/// 文本输出(如 ASR / TTS 的文本)。
1330#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1331pub struct TextOutput {
1332    /// 文本内容。
1333    pub text: String,
1334    /// 语言。
1335    pub language: String,
1336}
1337
1338/// 音频输出的描述信息(采样率 / 声道 / 长度)。
1339#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1340pub struct AudioBufferInfo {
1341    /// 采样率。
1342    pub sample_rate: i32,
1343    /// 声道数。
1344    pub channels: i32,
1345    /// 采样数。
1346    pub sample_count: usize,
1347    /// 实际采样数据(f32,-1..1)。仅当生成音频需要回传时存在
1348    /// (如 TTS 的 `audio_output`);VAD / ASR 通常不携带。
1349    #[serde(default)]
1350    pub samples: Option<Vec<f32>>,
1351}
1352
1353/// 带名字的音频输出(如 TTS 的多段输出)。
1354#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1355pub struct NamedAudioOutput {
1356    /// 输出 id。
1357    pub id: String,
1358    /// 音频描述。
1359    pub audio: AudioBufferInfo,
1360    /// 附加元数据。
1361    pub meta: BTreeMap<String, String>,
1362}
1363
1364/// 自定义结构化输出(上游 `TaskResult::custom_schema_output`)。
1365///
1366/// 目前有三类产出:Smart Turn(task `turn`)的 `"smart_turn.v1"`
1367///(`data` 为 `{"complete": bool, "probability": number}`,`threshold`
1368/// 请求选项判定,默认 0.5);CED(task `cls`)的 `"ced.classification.v1"`;
1369/// AST AudioSet(task `cls`)的 `"ast_audioset.classification.v1"`。
1370/// 后两者的 `data` 为 `{"scores": [...]}`(527 个 AudioSet 类别得分)。
1371/// `data` 为任意 JSON,用 `serde_json::Value` 承载。
1372#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1373pub struct CustomSchemaOutput {
1374    /// 输出 schema 标识,如 `"smart_turn.v1"`。
1375    pub schema: String,
1376    /// 结构化数据载荷。
1377    pub data: serde_json::Value,
1378}
1379
1380/// 一次任务执行的完整结果(`audiocpp_session_run_offline` / `finish`)。
1381#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1382pub struct TaskResult {
1383    /// 检测到的语音片段。
1384    pub speech_segments: Vec<SpeechSegment>,
1385    /// 说话人分离的发言分段(存在时)。
1386    pub speaker_turns: Vec<SpeakerTurn>,
1387    /// 文本输出(存在时)。
1388    pub text_output: Option<TextOutput>,
1389    /// 音频输出(存在时)。
1390    pub audio_output: Option<AudioBufferInfo>,
1391    /// 命名音频输出列表。
1392    pub named_audio_outputs: Vec<NamedAudioOutput>,
1393    /// 词级时间戳(存在时)。
1394    ///
1395    /// 是否输出按模型族而定,空数组只表示"本次无词戳",先查选项再报缺口:
1396    /// - Qwen3 ASR:默认**不**输出;需请求选项 `return_timestamps=true`(见
1397    ///   [`crate::options::request::RETURN_TIMESTAMPS`]),且建会话时经会话选项
1398    ///   配好对齐模型(见
1399    ///   [`crate::options::session::QWEN3_ASR_FORCED_ALIGNER_MODEL_PATH`]),
1400    ///   上游用对齐器内部回填。流式模式不支持词戳(直接抛错)。
1401    /// - 强制对齐族(`qwen3_forced_aligner` / `mms_forced_aligner`,task
1402    ///   `align`)直接输出;ASR 主文本 + 对齐回填是无词戳族的常规降级链。
1403    #[serde(default)]
1404    pub word_timestamps: Vec<WordTimestamp>,
1405    /// 自定义结构化输出(存在时,如 Smart Turn 的 `smart_turn.v1` 轮次判定)。
1406    #[serde(default)]
1407    pub custom_schema_output: Option<CustomSchemaOutput>,
1408    /// 单个产物输出(存在时,如 voice-clone 的 cached_voice_id)。
1409    #[serde(default)]
1410    pub artifact_output: Option<VoiceArtifact>,
1411    /// 多个产物输出(存在时)。
1412    #[serde(default)]
1413    pub output_artifacts: Vec<VoiceArtifact>,
1414}
1415
1416/// 流式会话产出的单个事件。
1417#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1418pub struct StreamEvent {
1419    /// 语音活动事件列表。
1420    pub voice_activity: Vec<VoiceActivityEvent>,
1421    /// 部分文本(流式 ASR 等)。
1422    pub partial_text: Option<TextOutput>,
1423    /// 可修正的整段转写假设(上游 `StreamEvent::partial_text_snapshot`):
1424    /// 客户端用它替换暂存显示;`partial_text` 仍是仅追加的已确认文本。
1425    /// 仅部分模型按需输出(如 parakeet 系热词流式),缺省为 `None`。
1426    #[serde(default)]
1427    pub partial_text_snapshot: Option<TextOutput>,
1428    /// 音频输出(存在时)。
1429    pub audio_output: Option<AudioBufferInfo>,
1430    /// 命名音频输出列表(如流式 TTS 的逐块 `chunk_N`)。
1431    #[serde(default)]
1432    pub named_audio_outputs: Vec<NamedAudioOutput>,
1433    /// 说话人分段(流式说话人分离等,存在时)。
1434    #[serde(default)]
1435    pub speaker_turns: Vec<SpeakerTurn>,
1436    /// 词级时间戳(仅产出词戳的族才有;Qwen3 ASR 流式不支持词戳,
1437    /// 见 [`TaskResult::word_timestamps`])。
1438    #[serde(default)]
1439    pub word_timestamps: Vec<WordTimestamp>,
1440    /// 产物输出(流式产出 embedding / 状态等,存在时)。
1441    #[serde(default)]
1442    pub output_artifacts: Vec<VoiceArtifact>,
1443    /// 是否为最终事件。
1444    pub is_final: bool,
1445}
1446
1447/// 语音活动事件(`speech_start` / `speech_end` / `speech_segment`)。
1448#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1449pub struct VoiceActivityEvent {
1450    /// 事件类型。
1451    pub kind: String,
1452    /// 触发位置(采样点)。
1453    pub sample: i64,
1454    /// 语音概率。
1455    pub probability: f32,
1456    /// 片段(`speech_segment` 事件携带)。
1457    pub segment: Option<SpeechSegment>,
1458}
1459
1460/// 流式策略描述(`audiocpp_session_streaming_policy_json`)。
1461#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1462pub struct StreamingPolicy {
1463    /// 输入类型,如 `"audio_chunks"`。
1464    pub input: String,
1465    /// 输出类型,如 `"pull_events"` / `"final_result"`。
1466    pub output: String,
1467    /// 推荐的音频分块采样数。
1468    pub preferred_audio_chunk_samples: usize,
1469    /// 推荐的音频分块秒数。
1470    pub preferred_audio_chunk_seconds: f64,
1471}
1472
1473#[cfg(test)]
1474mod tests {
1475    // 测试断言中的 unwrap/expect 是惯用法:失败即测试失败,展开错误链无意义。
1476    #![allow(clippy::unwrap_used, clippy::expect_used)]
1477    use super::*;
1478
1479    /// 所有非 Custom 变体的 `as_str()` 必须能被 `From<&str>` 精确回收到
1480    /// 原变体(而不是落入 `Custom`),且 `as_str` 与 `From` 两张 match
1481    /// 表完全一致——防止新增模型族时漏改其一。
1482    #[test]
1483    fn model_family_roundtrip() {
1484        let mut variants: Vec<ModelFamily> = Vec::new();
1485        macro_rules! push {
1486            ($($v:ident),* $(,)?) => {
1487                $(variants.push(ModelFamily::$v);)*
1488            };
1489        }
1490        push![
1491            // VAD
1492            SileroVad,
1493            MarblenetVad,
1494            Pulsevad,
1495            FireredVad,
1496            // ASR
1497            Qwen3Asr,
1498            CitrinetAsr,
1499            SenseAsr,
1500            FunAsrNano,
1501            HiggsAudioStt,
1502            HviskeAsr,
1503            HviskeAsrV6,
1504            KrokoAsr,
1505            NemotronAsr,
1506            Nemotron3Diar,
1507            ParakeetTdt,
1508            VibevoiceAsr,
1509            VibevoiceAsrStreaming,
1510            FireredAudio,
1511            Granite5Asr,
1512            Audio8Asr,
1513            Audio8Tts,
1514            Vibeasr,
1515            MoonshineAsr,
1516            NiagaraAsr,
1517            CanaryAsr,
1518            CohereAsr,
1519            MossTranscribeDiarize,
1520            Confucius4R2t2,
1521            AudioFlamingo,
1522            Crisperwhisper,
1523            GigaamAsr,
1524            IndexEcho,
1525            Lfm2Audio,
1526            Owsm,
1527            OwsmCtc,
1528            Samsone,
1529            WhistleAsr,
1530            // TTS
1531            Qwen3Tts,
1532            Confucius4Tts,
1533            DotsTts,
1534            FishAudio,
1535            GlmTts,
1536            HiggsAudioTts,
1537            IndexTts2,
1538            IrodoriTts,
1539            MossTtsLocal,
1540            MossTtsNano,
1541            MossTtsV15,
1542            MossVoicegen,
1543            Neutts,
1544            Outetts,
1545            PocketTts,
1546            VieneuV3Turbo,
1547            Fireredtts3,
1548            F5Tts,
1549            MagpieTts,
1550            Personaplex,
1551            SopranoTts,
1552            EchoTts,
1553            Voxcpm1,
1554            KokoroTts,
1555            Zipvoice,
1556            Auk,
1557            PiperTts,
1558            KittenTts,
1559            KittenTts2,
1560            Kugelaudio,
1561            Maya1,
1562            MossTtsd,
1563            MinimaxH3,
1564            Sanotts,
1565            SoproTts,
1566            MiraTts,
1567            Cosyvoice3,
1568            BreezeTts,
1569            // 分离 / 转换 / 音乐
1570            SortformerDiar,
1571            SortformerDiarV2,
1572            SeedVc,
1573            Rvc,
1574            Meanvc2,
1575            ToneColorVc,
1576            Chatterbox,
1577            ChatterboxTurbo,
1578            Vevo2,
1579            Voxcpm2,
1580            AceStep,
1581            Htdemucs,
1582            Htdemucs6Stems,
1583            Mossformer2,
1584            TfGridnet,
1585            MelBandRoformer,
1586            BsRoformer,
1587            Muscriptor,
1588            Omnivoice,
1589            StableAudio,
1590            MinimaxMusic3,
1591            Supertonic,
1592            Yue2,
1593            Sheetsage2,
1594            VoxtralRealtime,
1595            Audiosr,
1596            Apollo,
1597            Universr,
1598            BuiltinAudioUtils,
1599            ControlFoley,
1600            MidashEnglmGen,
1601            Liveavatar,
1602            Reuse,
1603            SamAudio,
1604            Sidon,
1605            SmartTurn,
1606            // 唤醒词 / 音频分类
1607            MicroWakeWord,
1608            SherpaKws,
1609            Ced,
1610            AstAudioset,
1611            // 其他
1612            Miocodec,
1613            Miotts,
1614            Vibevoice,
1615            Dramabox,
1616            Heartmula,
1617            InflectV2,
1618            Qwen3ForcedAligner,
1619            MmsForcedAligner,
1620        ];
1621        assert!(
1622            variants.len() >= 40,
1623            "ModelFamily 应至少覆盖上游全部 40+ loader 族,当前 {}",
1624            variants.len()
1625        );
1626        for v in variants {
1627            let name = v.as_str();
1628            assert_eq!(
1629                ModelFamily::from(name),
1630                v,
1631                "as_str() 与 From<&str> 不一致:{}",
1632                name
1633            );
1634            // Display / AsRef 应等价于 as_str。
1635            assert_eq!(format!("{v}"), name);
1636            assert_eq!(v.as_ref(), name);
1637        }
1638    }
1639
1640    #[test]
1641    fn model_family_custom_fallback() {
1642        // 未收录的名字落入 Custom 并原样保留。
1643        let m = ModelFamily::from("brand_new_family");
1644        match &m {
1645            ModelFamily::Custom(s) => assert_eq!(s, "brand_new_family"),
1646            other => panic!("未收录名字应落入 Custom,得到 {other:?}"),
1647        }
1648        assert_eq!(m.as_str(), "brand_new_family");
1649        // Custom 值本身不再被 From 回收。
1650        assert_eq!(
1651            ModelFamily::from("brand_new_family"),
1652            ModelFamily::Custom("brand_new_family".to_owned())
1653        );
1654        // From<String> 与 AsRef 一致。
1655        assert_eq!(
1656            ModelFamily::from("qwen3_asr".to_owned()),
1657            ModelFamily::Qwen3Asr
1658        );
1659    }
1660
1661    #[test]
1662    fn model_family_from_path() {
1663        // 关键词命中(常见 GGUF 命名)
1664        assert_eq!(
1665            ModelFamily::from_path("models/qwen3-asr-0.6b-q8_0.gguf"),
1666            Some(ModelFamily::Qwen3Asr)
1667        );
1668        assert_eq!(
1669            ModelFamily::from_path("sortformer-diar-4spk-v1-q8_0.gguf"),
1670            Some(ModelFamily::SortformerDiar)
1671        );
1672        assert_eq!(
1673            ModelFamily::from_path("citrinet-asr-q8_0.gguf"),
1674            Some(ModelFamily::CitrinetAsr)
1675        );
1676        assert_eq!(
1677            ModelFamily::from_path("moss-tts-nano-q8_0.gguf"),
1678            Some(ModelFamily::MossTtsNano)
1679        );
1680        // moss_tts_v15 不被裸 "moss" 关键词吞掉。
1681        assert_eq!(
1682            ModelFamily::from_path("moss-tts-v15-q8_0.gguf"),
1683            Some(ModelFamily::MossTtsV15)
1684        );
1685        // nemotron_3_diar 不被裸 "nemotron" 关键词吞掉。
1686        assert_eq!(
1687            ModelFamily::from_path("nemotron-3-diarization-bf16.gguf"),
1688            Some(ModelFamily::Nemotron3Diar)
1689        );
1690        // vieneu_v3_turbo / 旧名 vietneu 均命中同一族。
1691        assert_eq!(
1692            ModelFamily::from_path("vieneu-v3-turbo-q8_0.gguf"),
1693            Some(ModelFamily::VieneuV3Turbo)
1694        );
1695        assert_eq!(ModelFamily::from("vietneu_tts"), ModelFamily::VieneuV3Turbo);
1696        // 精确规则优先于裸关键词:qwen3-tts / qwen3-forced-aligner 不能判成 Qwen3Asr
1697        assert_eq!(
1698            ModelFamily::from_path("qwen3-tts-12hz-0.6b-base-q8_0.gguf"),
1699            Some(ModelFamily::Qwen3Tts)
1700        );
1701        assert_eq!(
1702            ModelFamily::from_path("qwen3-forced-aligner-q8_0.gguf"),
1703            Some(ModelFamily::Qwen3ForcedAligner)
1704        );
1705        assert_eq!(
1706            ModelFamily::from_path("mira-tts-q8_0.gguf"),
1707            Some(ModelFamily::MiraTts)
1708        );
1709        assert_eq!(
1710            ModelFamily::from_path("htdemucs-6s-q8_0.gguf"),
1711            Some(ModelFamily::Htdemucs6Stems)
1712        );
1713        // htdemucs_6stems 不被裸 "htdemucs" 关键词吞掉;裸名仍判回 Htdemucs。
1714        assert_eq!(
1715            ModelFamily::from_path("htdemucs-q8_0.gguf"),
1716            Some(ModelFamily::Htdemucs)
1717        );
1718        // kitten_tts2 不被 kitten_tts / 裸 "kitten" 吞掉。
1719        assert_eq!(
1720            ModelFamily::from_path("kitten-tts2-q8_0.gguf"),
1721            Some(ModelFamily::KittenTts2)
1722        );
1723        assert_eq!(
1724            ModelFamily::from_path("kitten-tts-q8_0.gguf"),
1725            Some(ModelFamily::KittenTts)
1726        );
1727        // index_echo 不被裸 "echo" 吞掉。
1728        assert_eq!(
1729            ModelFamily::from_path("index-echo-q8_0.gguf"),
1730            Some(ModelFamily::IndexEcho)
1731        );
1732        assert_eq!(
1733            ModelFamily::from_path("echo-tts-q8_0.gguf"),
1734            Some(ModelFamily::EchoTts)
1735        );
1736        // owsm_ctc 不被裸 "owsm" 吞掉。
1737        assert_eq!(
1738            ModelFamily::from_path("owsm-ctc-q8_0.gguf"),
1739            Some(ModelFamily::OwsmCtc)
1740        );
1741        assert_eq!(
1742            ModelFamily::from_path("owsm-q8_0.gguf"),
1743            Some(ModelFamily::Owsm)
1744        );
1745        // moss_ttsd 不被裸 "moss" 吞掉。
1746        assert_eq!(
1747            ModelFamily::from_path("moss-ttsd-q8_0.gguf"),
1748            Some(ModelFamily::MossTtsd)
1749        );
1750        // 新族常规命中抽查。
1751        assert_eq!(
1752            ModelFamily::from_path("smart-turn-q8_0.gguf"),
1753            Some(ModelFamily::SmartTurn)
1754        );
1755        // firered_vad 独立成族(非 firered_audio)。
1756        assert_eq!(
1757            ModelFamily::from_path("firered-vad-q8_0.gguf"),
1758            Some(ModelFamily::FireredVad)
1759        );
1760        // hviske_asr_v6 不被裸 "hviske" 吞掉;裸名仍判回 HviskeAsr。
1761        assert_eq!(
1762            ModelFamily::from_path("hviske-asr-v6-q8_0.gguf"),
1763            Some(ModelFamily::HviskeAsrV6)
1764        );
1765        assert_eq!(
1766            ModelFamily::from_path("hviske-asr-q8_0.gguf"),
1767            Some(ModelFamily::HviskeAsr)
1768        );
1769        assert_eq!(
1770            ModelFamily::from_path("whistle-asr-q8_0.gguf"),
1771            Some(ModelFamily::WhistleAsr)
1772        );
1773        // mossformer2 不被裸 "moss" 吞掉。
1774        assert_eq!(
1775            ModelFamily::from_path("mossformer2-ss-16k-f32.gguf"),
1776            Some(ModelFamily::Mossformer2)
1777        );
1778        assert_eq!(
1779            ModelFamily::from_path("tf-gridnet-q8_0.gguf"),
1780            Some(ModelFamily::TfGridnet)
1781        );
1782        // 唤醒词族。
1783        assert_eq!(
1784            ModelFamily::from_path("micro-wake-word-hey-jarvis-q8_0.gguf"),
1785            Some(ModelFamily::MicroWakeWord)
1786        );
1787        assert_eq!(
1788            ModelFamily::from_path("sherpa-kws-keywords-q8_0.gguf"),
1789            Some(ModelFamily::SherpaKws)
1790        );
1791        // ced 不吞掉对齐模型(for**ced**-aligner),且 audioset 后缀判给 CED。
1792        assert_eq!(
1793            ModelFamily::from_path("ced-base-16k-q8_0.gguf"),
1794            Some(ModelFamily::Ced)
1795        );
1796        assert_eq!(
1797            ModelFamily::from_path("qwen3-forced-aligner-q8_0.gguf"),
1798            Some(ModelFamily::Qwen3ForcedAligner)
1799        );
1800        assert_eq!(
1801            ModelFamily::from_path("ast-audioset-q8_0.gguf"),
1802            Some(ModelFamily::AstAudioset)
1803        );
1804        assert_eq!(
1805            ModelFamily::from_path("tone-color-vc-q8_0.gguf"),
1806            Some(ModelFamily::ToneColorVc)
1807        );
1808        assert_eq!(
1809            ModelFamily::from("htdemucs_6s"),
1810            ModelFamily::Htdemucs6Stems
1811        );
1812        // 流式/新变体不被其前缀家族吞掉(子串匹配顺序)
1813        assert_eq!(
1814            ModelFamily::from_path("vibevoice-asr-streaming-7b-q8_0.gguf"),
1815            Some(ModelFamily::VibevoiceAsrStreaming)
1816        );
1817        assert_eq!(
1818            ModelFamily::from_path("sortformer-diar-v2-q8_0.gguf"),
1819            Some(ModelFamily::SortformerDiarV2)
1820        );
1821        assert_eq!(
1822            ModelFamily::from_path("kokoro-tts-82m-q8_0.gguf"),
1823            Some(ModelFamily::KokoroTts)
1824        );
1825        assert_eq!(
1826            ModelFamily::from_path("moonshine-tiny-q8_0.gguf"),
1827            Some(ModelFamily::MoonshineAsr)
1828        );
1829        assert_eq!(
1830            ModelFamily::from_path("niagara-asr-q8_0.gguf"),
1831            Some(ModelFamily::NiagaraAsr)
1832        );
1833        assert_eq!(
1834            ModelFamily::from_path("yue2-music-q8_0.gguf"),
1835            Some(ModelFamily::Yue2)
1836        );
1837        assert_eq!(
1838            ModelFamily::from_path("sheetsage2-q8_0.gguf"),
1839            Some(ModelFamily::Sheetsage2)
1840        );
1841        assert_eq!(
1842            ModelFamily::from_path("pulsevad-2.1k-f32.gguf"),
1843            Some(ModelFamily::Pulsevad)
1844        );
1845        assert_eq!(
1846            ModelFamily::from_path("canary-180m-flash-q8_0.gguf"),
1847            Some(ModelFamily::CanaryAsr)
1848        );
1849        assert_eq!(
1850            ModelFamily::from_path("cohere-transcribe-03-2026-q8_0.gguf"),
1851            Some(ModelFamily::CohereAsr)
1852        );
1853        // moss_transcribe_diarize 不能被裸 "moss" 吞掉
1854        assert_eq!(
1855            ModelFamily::from_path("moss-transcribe-diarize-q8_0.gguf"),
1856            Some(ModelFamily::MossTranscribeDiarize)
1857        );
1858        assert_eq!(
1859            ModelFamily::from_path("apollo-orig.gguf"),
1860            Some(ModelFamily::Apollo)
1861        );
1862        assert_eq!(
1863            ModelFamily::from_path("universr-audio-orig.gguf"),
1864            Some(ModelFamily::Universr)
1865        );
1866        assert_eq!(
1867            ModelFamily::from_path("zipvoice-distill-q8_0.gguf"),
1868            Some(ModelFamily::Zipvoice)
1869        );
1870        assert_eq!(
1871            ModelFamily::from_path("auk-flash-q8_0.gguf"),
1872            Some(ModelFamily::Auk)
1873        );
1874        assert_eq!(
1875            ModelFamily::from_path("liveavatar-nvfp4.gguf"),
1876            Some(ModelFamily::Liveavatar)
1877        );
1878        assert_eq!(
1879            ModelFamily::from_path("piper-en-us-lessac-medium-orig.gguf"),
1880            Some(ModelFamily::PiperTts)
1881        );
1882        assert_eq!(
1883            ModelFamily::from_path("kitten-tts-mini-0.8-orig.gguf"),
1884            Some(ModelFamily::KittenTts)
1885        );
1886        // confucius4_r2t2 不能被裸 "confucius" 吞掉(判成 Confucius4Tts)
1887        assert_eq!(
1888            ModelFamily::from_path("confucius4-r2t2-q8_0.gguf"),
1889            Some(ModelFamily::Confucius4R2t2)
1890        );
1891        assert_eq!(
1892            ModelFamily::from_path("confucius4-tts-q8_0.gguf"),
1893            Some(ModelFamily::Confucius4Tts)
1894        );
1895        // 内置音频工具:model_path 直接传工具 ID 也能映射到族。
1896        assert_eq!(
1897            ModelFamily::from_path("deepfilternet2"),
1898            Some(ModelFamily::BuiltinAudioUtils)
1899        );
1900        // 大小写不敏感
1901        assert_eq!(
1902            ModelFamily::from_path("Qwen3-ASR.Q8_0.GGUF"),
1903            Some(ModelFamily::Qwen3Asr)
1904        );
1905        // #family= 显式覆盖优先于关键词(大小写不敏感)
1906        assert_eq!(
1907            ModelFamily::from_path("model.gguf#family=citrinet_asr"),
1908            Some(ModelFamily::CitrinetAsr)
1909        );
1910        assert_eq!(
1911            ModelFamily::from_path("model.gguf#family=MiraTTS"),
1912            Some(ModelFamily::MiraTts)
1913        );
1914        assert_eq!(
1915            ModelFamily::from_path("model.gguf#family=qwen3_tts"),
1916            Some(ModelFamily::Qwen3Tts)
1917        );
1918        // 无法识别
1919        assert_eq!(ModelFamily::from_path("my-weights.gguf"), None);
1920        assert_eq!(ModelFamily::from_path(""), None);
1921    }
1922
1923    #[test]
1924    fn task_kind_as_str() {
1925        let cases = [
1926            (TaskKind::Vad, "vad"),
1927            (TaskKind::Asr, "asr"),
1928            (TaskKind::Diar, "diar"),
1929            (TaskKind::SourceSeparation, "sep"),
1930            (TaskKind::AudioGeneration, "gen"),
1931            (TaskKind::Tts, "tts"),
1932            (TaskKind::VoiceCloning, "clon"),
1933            (TaskKind::VoiceConversion, "vc"),
1934            (TaskKind::SpeechToSpeech, "s2s"),
1935            (TaskKind::Alignment, "align"),
1936            (TaskKind::VoiceDesign, "vdes"),
1937            (TaskKind::SpeakerRecognition, "spk"),
1938            (TaskKind::Svc, "svc"),
1939            (TaskKind::Midi, "midi"),
1940            (TaskKind::TurnDetection, "turn"),
1941            (TaskKind::WakeWord, "wake"),
1942            (TaskKind::AudioClassification, "cls"),
1943        ];
1944        for (k, want) in cases {
1945            assert_eq!(k.as_str(), want);
1946        }
1947    }
1948
1949    #[test]
1950    fn task_kind_aliases() {
1951        // 上游别名与规范短名互通:`wake_word`→WakeWord,`classify`→AudioClassification。
1952        assert_eq!(
1953            serde_json::from_str::<TaskKind>("\"wake_word\"").unwrap(),
1954            TaskKind::WakeWord
1955        );
1956        assert_eq!(
1957            serde_json::from_str::<TaskKind>("\"classify\"").unwrap(),
1958            TaskKind::AudioClassification
1959        );
1960    }
1961
1962    #[test]
1963    fn task_kind_serde_roundtrip() {
1964        // serde 表示必须与 as_str(C 边界字符串)一致,而非变体名的
1965        // 小写形式(如 "sep" 而非 "sourceseparation")。
1966        let all = [
1967            TaskKind::Vad,
1968            TaskKind::Asr,
1969            TaskKind::Diar,
1970            TaskKind::SourceSeparation,
1971            TaskKind::AudioGeneration,
1972            TaskKind::Tts,
1973            TaskKind::VoiceCloning,
1974            TaskKind::VoiceConversion,
1975            TaskKind::SpeechToSpeech,
1976            TaskKind::Alignment,
1977            TaskKind::VoiceDesign,
1978            TaskKind::SpeakerRecognition,
1979            TaskKind::Svc,
1980            TaskKind::Midi,
1981            TaskKind::TurnDetection,
1982            TaskKind::WakeWord,
1983            TaskKind::AudioClassification,
1984        ];
1985        for k in all {
1986            let s = serde_json::to_string(&k).unwrap();
1987            assert_eq!(s, format!("\"{}\"", k.as_str()));
1988            assert_eq!(serde_json::from_str::<TaskKind>(&s).unwrap(), k);
1989        }
1990        assert!(serde_json::from_str::<TaskKind>("\"sourceseparation\"").is_err());
1991    }
1992
1993    #[test]
1994    fn run_mode_as_str() {
1995        assert_eq!(RunMode::Offline.as_str(), "offline");
1996        assert_eq!(RunMode::Streaming.as_str(), "streaming");
1997    }
1998
1999    #[test]
2000    fn backend_as_str() {
2001        let cases = [
2002            (Backend::Cpu, "cpu"),
2003            (Backend::Cuda, "cuda"),
2004            (Backend::Hip, "hip"),
2005            (Backend::Vulkan, "vulkan"),
2006            (Backend::Metal, "metal"),
2007            (Backend::Best, "best"),
2008        ];
2009        for (b, want) in cases {
2010            assert_eq!(b.as_str(), want);
2011        }
2012    }
2013
2014    /// 结构化类型应能从 C 侧 `dump_*` 产出的 JSON 反序列化(serde 契约)。
2015    #[test]
2016    fn structured_types_deserialize() {
2017        let result: TaskResult = serde_json::from_str(
2018            r#"{
2019                "speech_segments": [
2020                    {"span": {"start_sample": 0, "end_sample": 1600}, "confidence": 0.95, "text": ""}
2021                ],
2022                "text_output": {"text": "hi", "language": "en"},
2023                "audio_output": {"sample_rate": 24000, "channels": 1, "sample_count": 0, "samples": []},
2024                "named_audio_outputs": [],
2025                "speaker_turns": [
2026                    {"speaker_id": "speaker_0", "span": {"start_sample": 0, "end_sample": 1600}, "confidence": 0.9, "text": "hello"}
2027                ],
2028                "word_timestamps": [
2029                    {"span": {"start_sample": 0, "end_sample": 320}, "word": "hello", "confidence": 0.99}
2030                ],
2031                "artifact_output": {"kind": "speaker_embedding", "id": "spk1", "payload_base64": "", "meta": {}},
2032                "output_artifacts": [
2033                    {"kind": "custom", "id": "a", "payload_base64": "AQID", "meta": {"k": "v"}}
2034                ]
2035            }"#,
2036        )
2037        .unwrap();
2038        assert_eq!(result.speech_segments.len(), 1);
2039        assert_eq!(result.speech_segments[0].span.start_sample, 0);
2040        assert_eq!(result.speech_segments[0].confidence, 0.95);
2041        assert_eq!(result.text_output.as_ref().unwrap().text, "hi");
2042        assert_eq!(result.audio_output.unwrap().sample_rate, 24000);
2043        assert_eq!(result.speaker_turns[0].text, "hello");
2044        assert_eq!(result.word_timestamps[0].word, "hello");
2045        assert_eq!(
2046            result.artifact_output.as_ref().unwrap().kind,
2047            "speaker_embedding"
2048        );
2049        assert_eq!(result.output_artifacts[0].payload_base64, "AQID");
2050
2051        let ev: StreamEvent = serde_json::from_str(
2052            r#"{
2053                "voice_activity": [
2054                    {"kind": "speech_start", "sample": 100, "probability": 0.8, "segment": null}
2055                ],
2056                "partial_text": null,
2057                "partial_text_snapshot": {"text": "hel", "language": "en"},
2058                "audio_output": null,
2059                "named_audio_outputs": [{"id": "chunk_0", "audio": {"sample_rate": 48000, "channels": 2, "sample_count": 320, "samples": []}, "meta": {}}],
2060                "speaker_turns": [],
2061                "word_timestamps": [],
2062                "output_artifacts": [],
2063                "is_final": true
2064            }"#,
2065        )
2066        .unwrap();
2067        assert_eq!(ev.voice_activity[0].kind, "speech_start");
2068        assert_eq!(ev.named_audio_outputs[0].id, "chunk_0");
2069        assert_eq!(ev.partial_text_snapshot.as_ref().unwrap().text, "hel");
2070        assert!(ev.is_final);
2071    }
2072
2073    #[test]
2074    fn model_inspection_deserialize() {
2075        // 验证 inspect 返回的 ModelInspection 结构与上游 dump_inspection 对齐,
2076        // 且缺失字段(无 meta / 无发现资产)能向后兼容。
2077        let info: ModelInspection = serde_json::from_str(
2078            r#"{
2079                "metadata": {
2080                    "family": "qwen3_asr",
2081                    "variant": "q8_0",
2082                    "description": "Qwen3 ASR",
2083                    "config_candidates": ["config.json"],
2084                    "weight_candidates": ["qwen3-asr-q8_0.gguf"]
2085                },
2086                "capabilities": {
2087                    "supported_tasks": [{"task": "asr", "modes": ["offline", "streaming"]}],
2088                    "languages": ["zh", "en"],
2089                    "supports_speaker_reference": false,
2090                    "supports_style_condition": false,
2091                    "supports_timestamps": true
2092                },
2093                "discovered_weights": [
2094                    {"id": "model", "path": "/models/qwen3-asr-q8_0.gguf"}
2095                ],
2096                "cli": {
2097                    "request_options": [
2098                        {"name": "language", "value_name": "CODE", "description": "语言", "required": false, "default_value": "auto"}
2099                    ],
2100                    "session_options": [],
2101                    "load_options": []
2102                }
2103            }"#,
2104        )
2105        .unwrap();
2106        assert_eq!(info.metadata.family, "qwen3_asr");
2107        assert!(
2108            info.capabilities
2109                .supported_tasks
2110                .iter()
2111                .any(|t| t.task == "asr" && t.modes.contains(&"streaming".to_string()))
2112        );
2113        assert_eq!(info.discovered_weights[0].id, "model");
2114        assert_eq!(info.cli.request_options[0].name, "language");
2115        assert_eq!(
2116            info.cli.request_options[0].default_value.as_deref(),
2117            Some("auto")
2118        );
2119    }
2120}