From 2a117098ecfa24c5b49b3a7af244692cf0cedbac Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Mon, 11 Aug 2025 10:31:24 +0800 Subject: [PATCH] feat: vLLM large language model configuration --- backend/apps/ai_model/model_factory.py | 15 +++++++++--- backend/apps/system/api/aimodel.py | 5 ++-- .../apps/system/schemas/ai_model_schema.py | 1 + .../src/assets/model/icon_vllm_colorful.png | Bin 0 -> 4710 bytes frontend/src/entity/supplier.ts | 23 +++++++++++++++++- frontend/src/views/system/model/ModelForm.vue | 10 +++++--- 6 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 frontend/src/assets/model/icon_vllm_colorful.png diff --git a/backend/apps/ai_model/model_factory.py b/backend/apps/ai_model/model_factory.py index 6391501c..f7e5817d 100644 --- a/backend/apps/ai_model/model_factory.py +++ b/backend/apps/ai_model/model_factory.py @@ -11,7 +11,7 @@ from apps.ai_model.openai.llm import BaseChatOpenAI from apps.system.models.system_model import AiModelDetail from common.core.db import engine from common.utils.utils import prepare_model_arg - +from langchain_community.llms import VLLMOpenAI # from langchain_community.llms import Tongyi, VLLM @@ -60,7 +60,14 @@ class BaseLLM(ABC): """Return the langchain LLM instance""" return self._llm - +class OpenAIvLLM(BaseLLM): + def _init_llm(self) -> VLLMOpenAI: + return VLLMOpenAI( + openai_api_key=self.config.api_key or 'Empty', + openai_api_base=self.config.api_base_url, + model_name=self.config.model_name, + **self.config.additional_params, + ) class OpenAILLM(BaseLLM): def _init_llm(self) -> BaseChatModel: return BaseChatOpenAI( @@ -81,7 +88,7 @@ class LLMFactory: _llm_types: Dict[str, Type[BaseLLM]] = { "openai": OpenAILLM, "tongyi": OpenAILLM, - "vllm": OpenAILLM + "vllm": OpenAIvLLM } @classmethod @@ -129,7 +136,7 @@ def get_default_config() -> LLMConfig: # 构造 LLMConfig return LLMConfig( model_id=db_model.id, - model_type="openai", + model_type="openai" if db_model.protocol == 1 else "vllm", model_name=db_model.base_model, api_key=db_model.api_key, api_base_url=db_model.api_domain, diff --git a/backend/apps/system/api/aimodel.py b/backend/apps/system/api/aimodel.py index 9e949af6..6eea3549 100644 --- a/backend/apps/system/api/aimodel.py +++ b/backend/apps/system/api/aimodel.py @@ -20,7 +20,7 @@ async def check_llm(info: AiModelCreator, trans: Trans): try: additional_params = {item.key: prepare_model_arg(item.val) for item in info.config_list if item.key and item.val} config = LLMConfig( - model_type="openai", + model_type="openai" if info.protocol == 1 else "vllm", model_name=info.base_model, api_key=info.api_key, api_base_url=info.api_domain, @@ -74,7 +74,8 @@ async def query( AiModelDetail.name, AiModelDetail.model_type, AiModelDetail.base_model, - AiModelDetail.supplier, + AiModelDetail.supplier, + AiModelDetail.protocol, AiModelDetail.default_model) if keyword is not None: statement = statement.where(AiModelDetail.name.like(f"%{keyword}%")) diff --git a/backend/apps/system/schemas/ai_model_schema.py b/backend/apps/system/schemas/ai_model_schema.py index 5bfd206b..8b5946bf 100644 --- a/backend/apps/system/schemas/ai_model_schema.py +++ b/backend/apps/system/schemas/ai_model_schema.py @@ -9,6 +9,7 @@ class AiModelItem(BaseModel): model_type: int base_model: str supplier: int + protocol: int default_model: bool = False class AiModelGridItem(AiModelItem, BaseCreatorDTO): diff --git a/frontend/src/assets/model/icon_vllm_colorful.png b/frontend/src/assets/model/icon_vllm_colorful.png new file mode 100644 index 0000000000000000000000000000000000000000..aefd05250f4d35125460dc125326703dc16c837d GIT binary patch literal 4710 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 M2PgZiW!_Q$2mi}|k^lez literal 0 HcmV?d00001 diff --git a/frontend/src/entity/supplier.ts b/frontend/src/entity/supplier.ts index 2c8a271f..11932f85 100644 --- a/frontend/src/entity/supplier.ts +++ b/frontend/src/entity/supplier.ts @@ -8,18 +8,25 @@ import icon_openai_colorful from '@/assets/model/icon_openai_colorful.png' import icon_kimi_colorful from '@/assets/model/icon_kimi_colorful.png' import icon_txhy_colorful from '@/assets/model/icon_txhy_colorful.png' import icon_hsyq_colorful from '@/assets/model/icon_hsyq_colorful.png' +import icon_vllm_colorful from '@/assets/model/icon_vllm_colorful.png' type ModelArg = { key: string; val?: string | number; type: string; range?: string } type ModelOption = { name: string; api_domain?: string; args?: ModelArg[] } type ModelConfig = Record< number, - { api_domain: string; common_args?: ModelArg[]; model_options: ModelOption[] } + { + api_domain: string + common_args?: ModelArg[] + model_options: ModelOption[] + } > export const supplierList: Array<{ id: number name: string icon: any + type?: string + is_private?: boolean model_config: ModelConfig }> = [ { @@ -259,6 +266,20 @@ export const supplierList: Array<{ }, }, }, + { + id: 11, + name: 'vLLM', + icon: icon_vllm_colorful, + type: 'vllm', + is_private: true, + model_config: { + 0: { + api_domain: 'http://127.0.0.1:8000/v1', + common_args: [{ key: 'temperature', val: 0.6, type: 'number', range: '[0, 1]' }], + model_options: [], + }, + }, + }, ] export const base_model_options = (supplier_id: number, model_type?: number) => { diff --git a/frontend/src/views/system/model/ModelForm.vue b/frontend/src/views/system/model/ModelForm.vue index 501865d0..3a211d6c 100644 --- a/frontend/src/views/system/model/ModelForm.vue +++ b/frontend/src/views/system/model/ModelForm.vue @@ -38,6 +38,7 @@ const modelForm = reactive({ api_key: '', api_domain: '', config_list: [], + protocol: 1, }) const isCreate = ref(false) const modelRef = ref() @@ -90,7 +91,7 @@ const handleCurrentChange = (val: any) => { currentPage.value = val } -const rules = { +const rules = computed(() => ({ model_type: [ { required: true, @@ -109,12 +110,12 @@ const rules = { name: [{ required: true, message: t('model.the_basic_model'), trigger: 'blur' }], api_key: [ { - required: true, + required: !currentSupplier.value?.is_private, message: t('datasource.please_enter') + t('common.empty') + 'API Key', trigger: 'blur', }, ], -} +})) onMounted(() => { setTimeout(() => { @@ -169,6 +170,7 @@ const supplierChang = (supplier: any) => { const config = supplier.model_config[modelForm.model_type || 0] modelForm.api_domain = config.api_domain modelForm.base_model = '' + modelForm.protocol = supplier.type === 'vllm' ? 2 : 1 advancedSetting.value = [] } let curId = +new Date() @@ -201,7 +203,7 @@ const formatAdvancedSetting = (list: Array) => { advancedSetting.value = setting_list } const baseModelChange = (val: string) => { - if (!val || !modelForm.supplier || !modelList.value?.length) { + if (!val || !modelForm.supplier) { return } const current_model = modelList.value?.find((model: any) => model.name == val)