From fccfb28813751b232c70546d396a3be5b07b6e8c Mon Sep 17 00:00:00 2001 From: baixianfeng_zj Date: Wed, 24 Dec 2025 09:22:03 +0800 Subject: [PATCH] feat:Enables Alibaba Cloud's Hundred Refinements Model Management to support custom API URLs-patch1 --- .../credential/embedding.py | 4 ++-- .../credential/image.py | 4 ++-- .../credential/itv.py | 2 ++ .../credential/llm.py | 2 +- .../credential/reranker.py | 2 ++ .../credential/tti.py | 2 ++ .../credential/tts.py | 2 ++ .../credential/ttv.py | 2 ++ .../model/embedding.py | 6 ++++-- .../model/image.py | 19 ++++++++++++++++--- .../model/reranker.py | 6 +++++- .../model/tti.py | 13 ++++++++++--- .../model/tts.py | 9 ++++++++- .../model/ttv.py | 6 ++++++ 14 files changed, 64 insertions(+), 15 deletions(-) diff --git a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/embedding.py b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/embedding.py index 28127c055..30b4fb423 100644 --- a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/embedding.py +++ b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/embedding.py @@ -36,6 +36,8 @@ class BaiLianEmbeddingModelParams(BaseForm): class AliyunBaiLianEmbeddingCredential(BaseForm, BaseModelCredential): + api_base = forms.TextInputField(_('API URL'), required=False, default_value='https://dashscope.aliyuncs.com/compatible-mode/v1') + dashscope_api_key = forms.PasswordInputField('API Key', required=True) def is_valid( self, @@ -91,5 +93,3 @@ class AliyunBaiLianEmbeddingCredential(BaseForm, BaseModelCredential): def get_model_params_setting_form(self, model_name): return BaiLianEmbeddingModelParams() - - dashscope_api_key = forms.PasswordInputField('API Key', required=True) diff --git a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py index 24657f6f4..13f88cb2a 100644 --- a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py +++ b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py @@ -37,6 +37,8 @@ class QwenModelParams(BaseForm): class QwenVLModelCredential(BaseForm, BaseModelCredential): + api_base = forms.TextInputField(_('API URL'), required=False, default_value='https://dashscope.aliyuncs.com/compatible-mode/v1') + api_key = forms.PasswordInputField('API Key', required=True) def is_valid( self, @@ -84,7 +86,5 @@ class QwenVLModelCredential(BaseForm, BaseModelCredential): def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]: return {**model, 'api_key': super().encryption(model.get('api_key', ''))} - api_key = forms.PasswordInputField('API Key', required=True) - def get_model_params_setting_form(self, model_name): return QwenModelParams() diff --git a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/itv.py b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/itv.py index 7f38f49c4..413d8eb1d 100644 --- a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/itv.py +++ b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/itv.py @@ -5,6 +5,7 @@ from typing import Dict, Any from django.utils.translation import gettext_lazy as _, gettext from common.exception.app_exception import AppApiException +from common import forms from common.forms import BaseForm, PasswordInputField, SingleSelect, SliderField, TooltipLabel from common.forms.switch_field import SwitchField from models_provider.base_model_provider import BaseModelCredential, ValidCode @@ -41,6 +42,7 @@ class ImageToVideoModelCredential(BaseForm, BaseModelCredential): Provides validation and encryption for the model credentials. """ + api_base = forms.TextInputField(_('API URL'), required=False, default_value='https://dashscope.aliyuncs.com/compatible-mode/v1') api_key = PasswordInputField('API Key', required=True) def is_valid( diff --git a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py index 9511e2ef0..104f98f90 100644 --- a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py +++ b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py @@ -39,7 +39,7 @@ class BaiLianLLMModelParams(BaseForm): class BaiLianLLMModelCredential(BaseForm, BaseModelCredential): - api_base = forms.TextInputField(_('API URL'), required=True) + api_base = forms.TextInputField(_('API URL'), required=True, default_value='https://dashscope.aliyuncs.com/compatible-mode/v1') api_key = forms.PasswordInputField(_('API Key'), required=True) def is_valid( diff --git a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py index a43b4007b..b3264e882 100644 --- a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py +++ b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py @@ -6,6 +6,7 @@ from django.utils.translation import gettext as _ from langchain_core.documents import Document from common.exception.app_exception import AppApiException +from common import forms from common.forms import BaseForm, PasswordInputField from models_provider.base_model_provider import BaseModelCredential, ValidCode from models_provider.impl.aliyun_bai_lian_model_provider.model.reranker import AliyunBaiLianReranker @@ -17,6 +18,7 @@ class AliyunBaiLianRerankerCredential(BaseForm, BaseModelCredential): Provides validation and encryption for the model credentials. """ + api_base = forms.TextInputField(_('API URL'), required=False, default_value='https://dashscope.aliyuncs.com/compatible-mode/v1') dashscope_api_key = PasswordInputField('API Key', required=True) def is_valid( diff --git a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py index 7825501e4..42d7869cf 100644 --- a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py +++ b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py @@ -5,6 +5,7 @@ from typing import Dict, Any from django.utils.translation import gettext_lazy as _, gettext from common.exception.app_exception import AppApiException +from common import forms from common.forms import BaseForm, PasswordInputField, SingleSelect, SliderField, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger @@ -68,6 +69,7 @@ class QwenTextToImageModelCredential(BaseForm, BaseModelCredential): Provides validation and encryption for the model credentials. """ + api_base = forms.TextInputField(_('API URL'), required=False, default_value='https://dashscope.aliyuncs.com/compatible-mode/v1') api_key = PasswordInputField('API Key', required=True) def is_valid( diff --git a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py index 089d25900..671698ea0 100644 --- a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py +++ b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py @@ -5,6 +5,7 @@ from typing import Dict from django.utils.translation import gettext_lazy as _, gettext from common.exception.app_exception import AppApiException +from common import forms from common.forms import BaseForm, PasswordInputField, SingleSelect, SliderField, TooltipLabel from models_provider.base_model_provider import BaseModelCredential, ValidCode from common.utils.logger import maxkb_logger @@ -59,6 +60,7 @@ class AliyunBaiLianTTSModelCredential(BaseForm, BaseModelCredential): Provides validation and encryption for the model credentials. """ + api_base = forms.TextInputField(_('API URL'), required=False, default_value='https://dashscope.aliyuncs.com/compatible-mode/v1') api_key = PasswordInputField("API Key", required=True) def is_valid( diff --git a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/ttv.py b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/ttv.py index b78f86ab9..1dd2c2011 100644 --- a/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/ttv.py +++ b/apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/ttv.py @@ -5,6 +5,7 @@ from typing import Dict, Any from django.utils.translation import gettext_lazy as _, gettext from common.exception.app_exception import AppApiException +from common import forms from common.forms import BaseForm, PasswordInputField, SingleSelect, SliderField, TooltipLabel from common.forms.switch_field import SwitchField from models_provider.base_model_provider import BaseModelCredential, ValidCode @@ -43,6 +44,7 @@ class TextToVideoModelCredential(BaseForm, BaseModelCredential): Provides validation and encryption for the model credentials. """ + api_base = forms.TextInputField(_('API URL'), required=False, default_value='https://dashscope.aliyuncs.com/compatible-mode/v1') api_key = PasswordInputField('API Key', required=True) def is_valid( diff --git a/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/embedding.py b/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/embedding.py index c02f5dc52..bac5789e3 100644 --- a/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/embedding.py +++ b/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/embedding.py @@ -17,8 +17,9 @@ class AliyunBaiLianEmbedding(MaxKBBaseModel): model_name: str optional_params: dict - def __init__(self, api_key, model_name: str, optional_params: dict): - self.client = OpenAI(api_key=api_key, base_url='https://dashscope.aliyuncs.com/compatible-mode/v1').embeddings + def __init__(self, api_key, api_base, model_name: str, optional_params: dict): + api_base = api_base or 'https://dashscope.aliyuncs.com/compatible-mode/v1' + self.client = OpenAI(api_key=api_key, base_url=api_base).embeddings self.model_name = model_name self.optional_params = optional_params @@ -30,6 +31,7 @@ class AliyunBaiLianEmbedding(MaxKBBaseModel): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) return AliyunBaiLianEmbedding( api_key=model_credential.get('dashscope_api_key'), + api_base=model_credential.get('api_base'), model_name=model_name, optional_params=optional_params ) diff --git a/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/image.py b/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/image.py index ac4f20855..43f346845 100644 --- a/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/image.py +++ b/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/image.py @@ -24,10 +24,11 @@ class QwenVLChatModel(MaxKBBaseModel, BaseChatOpenAI): @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) + api_base = model_credential.get('api_base') or 'https://dashscope.aliyuncs.com/compatible-mode/v1' chat_tong_yi = QwenVLChatModel( model_name=model_name, openai_api_key=model_credential.get('api_key'), - openai_api_base='https://dashscope.aliyuncs.com/compatible-mode/v1', + openai_api_base=api_base, # stream_options={"include_usage": True}, streaming=True, stream_usage=True, @@ -41,7 +42,15 @@ class QwenVLChatModel(MaxKBBaseModel, BaseChatOpenAI): def get_upload_policy(self, api_key, model_name): """获取文件上传凭证""" - url = "https://dashscope.aliyuncs.com/api/v1/uploads" + # 如果有自定义api_base,提取host部分,否则使用默认URL + if hasattr(self, 'openai_api_base') and self.openai_api_base: + # 从api_base中提取host,替换默认URL + from urllib.parse import urlparse + parsed_url = urlparse(self.openai_api_base) + base_url = f"{parsed_url.scheme}://{parsed_url.netloc}" + url = f"{base_url}/api/v1/uploads" + else: + url = "https://dashscope.aliyuncs.com/api/v1/uploads" headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" @@ -109,7 +118,11 @@ class QwenVLChatModel(MaxKBBaseModel, BaseChatOpenAI): stop: Optional[list[str]] = None, **kwargs: Any, ) -> Iterator[BaseMessageChunk]: - url = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions" + # 如果有自定义api_base,使用它,否则使用默认URL + if hasattr(self, 'openai_api_base') and self.openai_api_base: + url = f"{self.openai_api_base}/chat/completions" + else: + url = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions" headers = { "Authorization": f"Bearer {self.openai_api_key.get_secret_value()}", diff --git a/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/reranker.py b/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/reranker.py index 9b5b121a0..029dc3854 100644 --- a/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/reranker.py +++ b/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/reranker.py @@ -12,7 +12,6 @@ from typing import Sequence, Optional, Any, Dict import dashscope from langchain_core.callbacks import Callbacks from langchain_core.documents import BaseDocumentCompressor, Document -from langchain_core.documents import BaseDocumentCompressor from models_provider.base_model_provider import MaxKBBaseModel @@ -20,6 +19,7 @@ from models_provider.base_model_provider import MaxKBBaseModel class AliyunBaiLianReranker(MaxKBBaseModel, BaseDocumentCompressor): model: Optional[str] api_key: Optional[str] + api_base: Optional[str] top_n: Optional[int] = 3 # 取前 N 个最相关的结果 @@ -31,6 +31,7 @@ class AliyunBaiLianReranker(MaxKBBaseModel, BaseDocumentCompressor): def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): return AliyunBaiLianReranker(model=model_name, api_key=model_credential.get('dashscope_api_key'), + api_base=model_credential.get('api_base'), top_n=model_kwargs.get('top_n', 3)) def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \ @@ -39,6 +40,9 @@ class AliyunBaiLianReranker(MaxKBBaseModel, BaseDocumentCompressor): return [] texts = [doc.page_content for doc in documents] + # 如果提供了api_base,则配置dashscope使用自定义endpoint + if self.api_base: + dashscope.base_http_url = self.api_base resp = dashscope.TextReRank.call( model=self.model, query=query, diff --git a/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tti.py b/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tti.py index 2ca3696af..b6e58ebd3 100644 --- a/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tti.py +++ b/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tti.py @@ -1,6 +1,6 @@ # coding=utf-8 from http import HTTPStatus -from typing import Dict +from typing import Dict, Optional from dashscope import ImageSynthesis, MultiModalConversation from django.utils.translation import gettext @@ -15,12 +15,14 @@ from models_provider.impl.base_tti import BaseTextToImage class QwenTextToImageModel(MaxKBBaseModel, BaseTextToImage): api_key: str + api_base: Optional[str] model_name: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') + self.api_base = kwargs.get('api_base') self.model_name = kwargs.get('model_name') self.params = kwargs.get('params') @@ -37,6 +39,7 @@ class QwenTextToImageModel(MaxKBBaseModel, BaseTextToImage): chat_tong_yi = QwenTextToImageModel( model_name=model_name, api_key=model_credential.get('api_key'), + api_base=model_credential.get('api_base'), **optional_params, ) return chat_tong_yi @@ -47,9 +50,11 @@ class QwenTextToImageModel(MaxKBBaseModel, BaseTextToImage): def generate_image(self, prompt: str, negative_prompt: str = None): if self.model_name.startswith("wan"): + # 如果提供了api_base,则使用自定义base_url,否则使用默认URL + base_url = self.api_base or 'https://dashscope.aliyuncs.com/compatible-mode/v1' rsp = ImageSynthesis.call(api_key=self.api_key, model=self.model_name, - base_url='https://dashscope.aliyuncs.com/compatible-mode/v1', + base_url=base_url, prompt=prompt, negative_prompt=negative_prompt, **self.params) @@ -73,12 +78,14 @@ class QwenTextToImageModel(MaxKBBaseModel, BaseTextToImage): ] } ] + # 如果提供了api_base,则使用自定义base_url,否则使用默认URL + base_url = self.api_base or 'https://dashscope.aliyuncs.com/v1' rsp = MultiModalConversation.call( api_key=self.api_key, model=self.model_name, messages=messages, result_format='message', - base_url='https://dashscope.aliyuncs.com/v1', + base_url=base_url, stream=False, negative_prompt=negative_prompt, **self.params diff --git a/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tts.py b/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tts.py index bea3d584c..d589df324 100644 --- a/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tts.py +++ b/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tts.py @@ -1,6 +1,7 @@ -from typing import Dict +from typing import Dict, Optional import dashscope +from dashscope.api_entities.dashscope_response import DashScopeAPIResponse from django.utils.translation import gettext as _ @@ -11,12 +12,14 @@ from models_provider.impl.base_tts import BaseTextToSpeech class AliyunBaiLianTextToSpeech(MaxKBBaseModel, BaseTextToSpeech): api_key: str + api_base: Optional[str] model: str params: dict def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') + self.api_base = kwargs.get('api_base') self.model = kwargs.get('model') self.params = kwargs.get('params') @@ -34,6 +37,7 @@ class AliyunBaiLianTextToSpeech(MaxKBBaseModel, BaseTextToSpeech): return AliyunBaiLianTextToSpeech( model=model_name, api_key=model_credential.get('api_key'), + api_base=model_credential.get('api_base'), **optional_params, ) @@ -42,6 +46,9 @@ class AliyunBaiLianTextToSpeech(MaxKBBaseModel, BaseTextToSpeech): def text_to_speech(self, text): dashscope.api_key = self.api_key + # 如果提供了api_base,则配置dashscope使用自定义endpoint + if self.api_base: + dashscope.base_http_url = self.api_base text = _remove_empty_lines(text) if 'sambert' in self.model: from dashscope.audio.tts import SpeechSynthesizer diff --git a/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/ttv.py b/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/ttv.py index 234bdc217..a9d00859d 100644 --- a/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/ttv.py +++ b/apps/models_provider/impl/aliyun_bai_lian_model_provider/model/ttv.py @@ -14,6 +14,7 @@ from common.utils.logger import maxkb_logger class GenerationVideoModel(MaxKBBaseModel, BaseGenerationVideo): api_key: str + api_base: Optional[str] model_name: str params: dict max_retries: int = 3 @@ -22,6 +23,7 @@ class GenerationVideoModel(MaxKBBaseModel, BaseGenerationVideo): def __init__(self, **kwargs): super().__init__(**kwargs) self.api_key = kwargs.get('api_key') + self.api_base = kwargs.get('api_base') self.model_name = kwargs.get('model_name') self.params = kwargs.get('params', {}) self.max_retries = kwargs.get('max_retries', 3) @@ -40,6 +42,7 @@ class GenerationVideoModel(MaxKBBaseModel, BaseGenerationVideo): return GenerationVideoModel( model_name=model_name, api_key=model_credential.get('api_key'), + api_base=model_credential.get('api_base'), **optional_params, ) @@ -83,6 +86,9 @@ class GenerationVideoModel(MaxKBBaseModel, BaseGenerationVideo): params.update(self.params) # --- 异步提交任务 --- + # 如果提供了api_base,则配置dashscope使用自定义endpoint + if self.api_base: + params['base_url'] = self.api_base rsp = self._safe_call(VideoSynthesis.async_call, **params) if rsp.status_code != HTTPStatus.OK: maxkb_logger.info(f'提交任务失败,status_code: {rsp.status_code}, code: {rsp.code}, message: {rsp.message}')