diff --git a/tests/test_funasr_asr.py b/tests/test_funasr_asr.py new file mode 100644 index 00000000..6e768240 --- /dev/null +++ b/tests/test_funasr_asr.py @@ -0,0 +1,71 @@ +import importlib.util +import sys +import types +from pathlib import Path + +import pytest + + +ROOT = Path(__file__).resolve().parents[1] +MODULE_PATH = ROOT / "tools" / "asr" / "funasr_asr.py" + + +def load_funasr_asr(monkeypatch, auto_model): + funasr = types.ModuleType("funasr") + funasr.AutoModel = auto_model + monkeypatch.setitem(sys.modules, "funasr", funasr) + + modelscope = types.ModuleType("modelscope") + modelscope.snapshot_download = lambda *args, **kwargs: None + monkeypatch.setitem(sys.modules, "modelscope", modelscope) + + tqdm = types.ModuleType("tqdm") + tqdm.tqdm = lambda iterable: iterable + monkeypatch.setitem(sys.modules, "tqdm", tqdm) + + torch = types.ModuleType("torch") + torch.cuda = types.SimpleNamespace(is_available=lambda: False) + monkeypatch.setitem(sys.modules, "torch", torch) + + module_name = "funasr_asr_under_test" + sys.modules.pop(module_name, None) + spec = importlib.util.spec_from_file_location(module_name, MODULE_PATH) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def test_fun_asr_nano_falls_back_to_modelscope_when_hf_config_is_not_resolved(monkeypatch): + calls = [] + + def fake_auto_model(**kwargs): + calls.append(kwargs.copy()) + if kwargs["hub"] == "hf": + raise RuntimeError("model 'FunAudioLLM/Fun-ASR-Nano-2512' is not registered.") + if kwargs["hub"] == "ms": + return types.SimpleNamespace(hub=kwargs["hub"], model_name=kwargs["model"]) + raise AssertionError(f"unexpected hub {kwargs['hub']}") + + module = load_funasr_asr(monkeypatch, fake_auto_model) + + model = module.create_model("zh", backend="fun-asr-nano") + + assert model.hub == "ms" + assert model.model_name == "FunAudioLLM/Fun-ASR-Nano-2512" + assert [(call["model"], call["hub"], call["trust_remote_code"]) for call in calls] == [ + ("FunAudioLLM/Fun-ASR-Nano-2512", "hf", True), + ("FunAudioLLM/Fun-ASR-Nano-2512", "ms", False), + ] + assert all(call["vad_model"] == "fsmn-vad" for call in calls) + assert all(call["device"] == "cpu" for call in calls) + assert all(call["disable_update"] is True for call in calls) + + +def test_fun_asr_nano_keeps_non_registration_errors_visible(monkeypatch): + def fake_auto_model(**kwargs): + raise RuntimeError("network unavailable") + + module = load_funasr_asr(monkeypatch, fake_auto_model) + + with pytest.raises(RuntimeError, match="network unavailable"): + module.create_model("zh", backend="fun-asr-nano") diff --git a/tools/asr/funasr_asr.py b/tools/asr/funasr_asr.py index 45b83187..56d5336b 100644 --- a/tools/asr/funasr_asr.py +++ b/tools/asr/funasr_asr.py @@ -9,6 +9,11 @@ from modelscope import snapshot_download from tqdm import tqdm funasr_models = {} # 存储模型避免重复加载 +FUN_ASR_NANO_MODEL_ID = "FunAudioLLM/Fun-ASR-Nano-2512" +FUN_ASR_NANO_MODEL_SOURCES = ( + {"model": FUN_ASR_NANO_MODEL_ID, "hub": "hf", "trust_remote_code": True}, + {"model": FUN_ASR_NANO_MODEL_ID, "hub": "ms", "trust_remote_code": False}, +) def only_asr(input_file, language, backend="fun-asr-nano"): @@ -21,6 +26,29 @@ def only_asr(input_file, language, backend="fun-asr-nano"): return text +def is_unregistered_model_error(exc): + message = str(exc).lower() + return "model" in message and "not registered" in message + + +def create_fun_asr_nano_model(device): + common_kwargs = dict( + vad_model="fsmn-vad", + device=device, + disable_update=True, + ) + last_error = None + for model_source in FUN_ASR_NANO_MODEL_SOURCES: + try: + return AutoModel(**model_source, **common_kwargs) + except (KeyError, RuntimeError, ValueError) as exc: + if not is_unregistered_model_error(exc): + raise + last_error = exc + + raise last_error + + def create_model(language="zh", **kwargs): backend = kwargs.get("backend", "fun-asr-nano") @@ -33,14 +61,7 @@ def create_model(language="zh", **kwargs): return funasr_models[cache_key] if backend == "fun-asr-nano": - model = AutoModel( - model="FunAudioLLM/Fun-ASR-Nano-2512", - trust_remote_code=True, - hub="hf", - vad_model="fsmn-vad", - device=device, - disable_update=True, - ) + model = create_fun_asr_nano_model(device) print(f"FunASR Fun-ASR-Nano 模型加载完成: {language.upper()}") else: model = AutoModel(