diff --git a/main/manager-api/src/main/resources/db/changelog/202606301405.sql b/main/manager-api/src/main/resources/db/changelog/202606301405.sql new file mode 100644 index 00000000..4939a68c --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202606301405.sql @@ -0,0 +1,9 @@ +-- Add language configuration for local FunASR ASR. +UPDATE `ai_model_provider` +SET `fields` = '[{"key":"model_dir","label":"模型目录","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"},{"key":"language","label":"识别语言","type":"string","default":"auto"}]' +WHERE `id` = 'SYSTEM_ASR_FunASR'; + +UPDATE `ai_model_config` +SET `config_json` = JSON_SET(`config_json`, '$.language', 'auto') +WHERE `id` = 'ASR_FunASR' +AND JSON_EXTRACT(`config_json`, '$.language') IS NULL; diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml old mode 100755 new mode 100644 index 9be7149e..2dcc1921 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -690,3 +690,10 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202606261131.sql + - changeSet: + id: 202606301405 + author: LauraGPT + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202606301405.sql diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index 802dcb50..323b562e 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -360,6 +360,9 @@ ASR: type: fun_local model_dir: models/SenseVoiceSmall output_dir: tmp/ + # 识别语种:auto 自动检测;如需限制只识别中文可设为 zh,避免中文短句误判为韩文等。 + # SenseVoice 支持 zh、en、ja、ko、yue 等语种标记。 + language: auto FunASRServer: # 独立部署FunASR,使用FunASR的API服务,只需要五句话 # 第一句:mkdir -p ./funasr-runtime-resources/models diff --git a/main/xiaozhi-server/core/providers/asr/fun_local.py b/main/xiaozhi-server/core/providers/asr/fun_local.py index 62ae99e7..fa428710 100644 --- a/main/xiaozhi-server/core/providers/asr/fun_local.py +++ b/main/xiaozhi-server/core/providers/asr/fun_local.py @@ -50,6 +50,7 @@ class ASRProvider(ASRProviderBase): self.interface_type = InterfaceType.LOCAL self.model_dir = config.get("model_dir") self.output_dir = config.get("output_dir") # 修正配置键名 + self.language = config.get("language", "auto") self.delete_audio_file = delete_audio_file # 确保输出目录存在 @@ -80,7 +81,7 @@ class ASRProvider(ASRProviderBase): self.model.generate, input=artifacts.pcm_bytes, cache={}, - language="auto", + language=self.language, use_itn=True, batch_size_s=60, ) diff --git a/main/xiaozhi-server/tests/test_fun_local_language.py b/main/xiaozhi-server/tests/test_fun_local_language.py new file mode 100644 index 00000000..9c1dd35b --- /dev/null +++ b/main/xiaozhi-server/tests/test_fun_local_language.py @@ -0,0 +1,87 @@ +import asyncio +import importlib +import sys +import types +from pathlib import Path + + +def install_fakes(monkeypatch): + class Logger: + def bind(self, **_kwargs): + return self + + def debug(self, *_args, **_kwargs): + pass + + def error(self, *_args, **_kwargs): + pass + + def info(self, *_args, **_kwargs): + pass + + def warning(self, *_args, **_kwargs): + pass + + logger_module = types.ModuleType("config.logger") + logger_module.setup_logging = lambda: Logger() + config_module = types.ModuleType("config") + config_module.__path__ = [] + monkeypatch.setitem(sys.modules, "config", config_module) + monkeypatch.setitem(sys.modules, "config.logger", logger_module) + + psutil_module = types.ModuleType("psutil") + psutil_module.virtual_memory = lambda: types.SimpleNamespace(total=4 * 1024 * 1024 * 1024) + monkeypatch.setitem(sys.modules, "psutil", psutil_module) + + class Base: + pass + + base_module = types.ModuleType("core.providers.asr.base") + base_module.ASRProviderBase = Base + monkeypatch.setitem(sys.modules, "core.providers.asr.base", base_module) + + dto_module = types.ModuleType("core.providers.asr.dto.dto") + dto_module.InterfaceType = types.SimpleNamespace(LOCAL="local") + monkeypatch.setitem(sys.modules, "core.providers.asr.dto.dto", dto_module) + + utils_module = types.ModuleType("core.providers.asr.utils") + utils_module.lang_tag_filter = lambda text: {"content": text} + monkeypatch.setitem(sys.modules, "core.providers.asr.utils", utils_module) + + class FakeAutoModel: + last_instance = None + + def __init__(self, **_kwargs): + self.generate_kwargs = None + FakeAutoModel.last_instance = self + + def generate(self, **kwargs): + self.generate_kwargs = kwargs + return [{"text": "你好"}] + + funasr_module = types.ModuleType("funasr") + funasr_module.AutoModel = FakeAutoModel + monkeypatch.setitem(sys.modules, "funasr", funasr_module) + return FakeAutoModel + + +def test_fun_local_passes_configured_language_to_funasr_generate(tmp_path, monkeypatch): + server_root = Path(__file__).resolve().parents[1] + monkeypatch.syspath_prepend(str(server_root)) + fake_model = install_fakes(monkeypatch) + monkeypatch.delitem(sys.modules, "core.providers.asr.fun_local", raising=False) + module = importlib.import_module("core.providers.asr.fun_local") + + provider = module.ASRProvider( + {"model_dir": "models/SenseVoiceSmall", "output_dir": str(tmp_path), "language": "zh"}, + delete_audio_file=False, + ) + + artifacts = types.SimpleNamespace(pcm_bytes=b"\0\0", file_path=str(tmp_path / "audio.wav")) + text, audio_path = asyncio.run( + provider.speech_to_text([], "session-1", audio_format="pcm", artifacts=artifacts) + ) + + assert text == {"content": "你好"} + assert audio_path == artifacts.file_path + assert fake_model.last_instance.generate_kwargs["language"] == "zh"