From 69c885a78a6e27839bb47995f6899aeaa4029471 Mon Sep 17 00:00:00 2001 From: CaptainB Date: Wed, 9 Oct 2024 18:58:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E9=98=BF=E9=87=8C?= =?UTF-8?q?=E7=99=BE=E7=82=BC=E8=AF=AD=E9=9F=B3=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aliyun_bai_lian_model_provider.py | 15 ++++- .../credential/stt.py | 42 ++++++++++++ .../credential/tts.py | 43 ++++++++++++ .../model/iat_mp3_16k.mp3 | Bin 0 -> 17876 bytes .../model/stt.py | 63 ++++++++++++++++++ .../model/tts.py | 43 ++++++++++++ 6 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py create mode 100644 apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py create mode 100644 apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/model/iat_mp3_16k.mp3 create mode 100644 apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/model/stt.py create mode 100644 apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/model/tts.py diff --git a/apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py b/apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py index 9dd8f97ab08..83d42a57cd5 100644 --- a/apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py +++ b/apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py @@ -13,13 +13,26 @@ ModelInfoManage from setting.models_provider.impl.aliyun_bai_lian_model_provider.credential.reranker import \ AliyunBaiLianRerankerCredential +from setting.models_provider.impl.aliyun_bai_lian_model_provider.credential.stt import AliyunBaiLianSTTModelCredential +from setting.models_provider.impl.aliyun_bai_lian_model_provider.credential.tts import AliyunBaiLianTTSModelCredential from setting.models_provider.impl.aliyun_bai_lian_model_provider.model.reranker import AliyunBaiLianReranker +from setting.models_provider.impl.aliyun_bai_lian_model_provider.model.stt import AliyunBaiLianSpeechToText +from setting.models_provider.impl.aliyun_bai_lian_model_provider.model.tts import AliyunBaiLianTextToSpeech from smartdoc.conf import PROJECT_DIR aliyun_bai_lian_model_credential = AliyunBaiLianRerankerCredential() +aliyun_bai_lian_tts_model_credential = AliyunBaiLianTTSModelCredential() +aliyun_bai_lian_stt_model_credential = AliyunBaiLianSTTModelCredential() + model_info_list = [ModelInfo('gte-rerank', '阿里巴巴通义实验室开发的GTE-Rerank文本排序系列模型,开发者可以通过LlamaIndex框架进行集成高质量文本检索、排序。', - ModelTypeConst.RERANKER, aliyun_bai_lian_model_credential, AliyunBaiLianReranker) + ModelTypeConst.RERANKER, aliyun_bai_lian_model_credential, AliyunBaiLianReranker), + ModelInfo('paraformer-realtime-v2', + '中文(含粤语等各种方言)、英文、日语、韩语支持多个语种自由切换', + ModelTypeConst.STT, aliyun_bai_lian_stt_model_credential, AliyunBaiLianSpeechToText), + ModelInfo('cosyvoice-v1', + 'CosyVoice基于新一代生成式语音大模型,能根据上下文预测情绪、语调、韵律等,具有更好的拟人效果', + ModelTypeConst.TTS, aliyun_bai_lian_tts_model_credential, AliyunBaiLianTextToSpeech), ] model_info_manage = ModelInfoManage.builder().append_model_info_list(model_info_list).build() diff --git a/apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py b/apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py new file mode 100644 index 00000000000..5c9290b1519 --- /dev/null +++ b/apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py @@ -0,0 +1,42 @@ +# coding=utf-8 + +from typing import Dict + +from common import forms +from common.exception.app_exception import AppApiException +from common.forms import BaseForm +from setting.models_provider.base_model_provider import BaseModelCredential, ValidCode + + +class AliyunBaiLianSTTModelCredential(BaseForm, BaseModelCredential): + api_key = forms.PasswordInputField("API Key", required=True) + + def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], provider, + raise_exception=False): + model_type_list = provider.get_model_type_list() + if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): + raise AppApiException(ValidCode.valid_error.value, f'{model_type} 模型类型不支持') + + for key in ['api_key']: + if key not in model_credential: + if raise_exception: + raise AppApiException(ValidCode.valid_error.value, f'{key} 字段为必填字段') + else: + return False + try: + model = provider.get_model(model_type, model_name, model_credential) + model.check_auth() + except Exception as e: + if isinstance(e, AppApiException): + raise e + if raise_exception: + raise AppApiException(ValidCode.valid_error.value, f'校验失败,请检查参数是否正确: {str(e)}') + else: + return False + return True + + def encryption_dict(self, model: Dict[str, object]): + return {**model, 'api_key': super().encryption(model.get('api_key', ''))} + + def get_model_params_setting_form(self, model_name): + pass diff --git a/apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py b/apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py new file mode 100644 index 00000000000..fe54ddaba22 --- /dev/null +++ b/apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py @@ -0,0 +1,43 @@ +# coding=utf-8 + +from typing import Dict + +from common import forms +from common.exception.app_exception import AppApiException +from common.forms import BaseForm +from setting.models_provider.base_model_provider import BaseModelCredential, ValidCode + + +class AliyunBaiLianTTSModelCredential(BaseForm, BaseModelCredential): + api_key = forms.PasswordInputField("API Key", required=True) + + def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], provider, + raise_exception=False): + model_type_list = provider.get_model_type_list() + if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))): + raise AppApiException(ValidCode.valid_error.value, f'{model_type} 模型类型不支持') + + for key in ['api_key']: + if key not in model_credential: + if raise_exception: + raise AppApiException(ValidCode.valid_error.value, f'{key} 字段为必填字段') + else: + return False + try: + model = provider.get_model(model_type, model_name, model_credential) + model.check_auth() + except Exception as e: + if isinstance(e, AppApiException): + raise e + if raise_exception: + raise AppApiException(ValidCode.valid_error.value, f'校验失败,请检查参数是否正确: {str(e)}') + else: + return False + return True + + def encryption_dict(self, model: Dict[str, object]): + return {**model, 'api_key': super().encryption(model.get('api_key', ''))} + + + def get_model_params_setting_form(self, model_name): + pass diff --git a/apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/model/iat_mp3_16k.mp3 b/apps/setting/models_provider/impl/aliyun_bai_lian_model_provider/model/iat_mp3_16k.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..75e744c8ff5188208a3797561efb2e096d4fa015 GIT binary patch literal 17876 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