From 1302fe23ff81ccf0ed916fe1e87ec7334be42023 Mon Sep 17 00:00:00 2001 From: zhizhi <928570418@qq.com> Date: Fri, 26 Dec 2025 15:53:24 +0800 Subject: [PATCH 01/62] =?UTF-8?q?=E2=9C=A8config=20ModelEngine=20Service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/consts/model.py | 1 + backend/services/model_health_service.py | 7 ++-- backend/services/model_management_service.py | 14 +++++--- backend/services/model_provider_service.py | 21 +++++++----- .../components/model/ModelAddDialog.tsx | 33 ++++++++++++++++--- frontend/hooks/model/useSiliconModelList.ts | 27 +++++++++++---- frontend/public/locales/en/common.json | 1 + frontend/public/locales/zh/common.json | 1 + frontend/services/modelService.ts | 4 +++ 9 files changed, 83 insertions(+), 26 deletions(-) diff --git a/backend/consts/model.py b/backend/consts/model.py index cf22afbf2..46652ec99 100644 --- a/backend/consts/model.py +++ b/backend/consts/model.py @@ -66,6 +66,7 @@ class ProviderModelRequest(BaseModel): provider: str model_type: str api_key: Optional[str] = '' + base_url: Optional[str] = '' class BatchCreateModelsRequest(BaseModel): diff --git a/backend/services/model_health_service.py b/backend/services/model_health_service.py index 30d1a925d..78f6413ee 100644 --- a/backend/services/model_health_service.py +++ b/backend/services/model_health_service.py @@ -195,14 +195,17 @@ async def verify_model_config_connectivity(model_config: dict): connectivity = await _perform_connectivity_check( model_name, model_type, model_base_url, model_api_key, ssl_verify ) - + if not connectivity and ssl_verify: + connectivity = await _perform_connectivity_check( + model_name, model_type, model_base_url, model_api_key, False + ) if not connectivity: return { "connectivity": False, "model_name": model_name, "error": f"Failed to connect to model '{model_name}' at {model_base_url}. Please verify the URL, API key, and network connection." } - + return { "connectivity": True, "model_name": model_name, diff --git a/backend/services/model_management_service.py b/backend/services/model_management_service.py index 7e7c59a5a..2a64ece88 100644 --- a/backend/services/model_management_service.py +++ b/backend/services/model_management_service.py @@ -45,6 +45,10 @@ async def create_model_for_tenant(user_id: str, tenant_id: str, model_data: Dict model_base_url.replace(LOCALHOST_NAME, DOCKER_INTERNAL_HOST) .replace(LOCALHOST_IP, DOCKER_INTERNAL_HOST) ) + model_data['ssl_verify'] = True + if "open/router" in model_base_url: + model_data['ssl_verify'] = False + # Split model_name into repo and name model_repo, model_name = split_repo_name( @@ -286,7 +290,7 @@ async def delete_model_for_tenant(user_id: str, tenant_id: str, display_name: st raise LookupError(f"Model not found: {display_name}") deleted_types: List[str] = [] - + # Check if any of the models is multi_embedding (which means we have both types) has_multi_embedding = any( m.get("model_type") == "multi_embedding" for m in models @@ -343,12 +347,12 @@ async def list_models_for_tenant(tenant_id: str): try: records = get_model_records(None, tenant_id) result: List[Dict[str, Any]] = [] - + # Type mapping for backwards compatibility (chat -> llm for frontend) type_map = { "chat": "llm", } - + for record in records: record["model_name"] = add_repo_to_name( model_repo=record["model_repo"], @@ -356,11 +360,11 @@ async def list_models_for_tenant(tenant_id: str): ) record["connect_status"] = ModelConnectStatusEnum.get_value( record.get("connect_status")) - + # Map model_type if necessary (for ModelEngine compatibility) if record.get("model_type") in type_map: record["model_type"] = type_map[record["model_type"]] - + result.append(record) logging.debug("Successfully retrieved model list") diff --git a/backend/services/model_provider_service.py b/backend/services/model_provider_service.py index e154aba72..3dfa70c52 100644 --- a/backend/services/model_provider_service.py +++ b/backend/services/model_provider_service.py @@ -85,19 +85,24 @@ async def get_models(self, provider_config: Dict) -> List[Dict]: List of models with canonical fields """ try: - if not MODEL_ENGINE_HOST or not MODEL_ENGINE_APIKEY: - logger.warning("ModelEngine environment variables not configured") + # Allow overriding host and api key via provider_config (from frontend). + # Fall back to environment-configured values. + model_type: str = provider_config.get("model_type", "") + host = provider_config.get("base_url") or MODEL_ENGINE_HOST + api_key = provider_config.get("api_key") or MODEL_ENGINE_APIKEY + + if not host or not api_key: + logger.warning("ModelEngine host or api key not configured") return [] - model_type: str = provider_config.get("model_type", "") - headers = {"Authorization": f"Bearer {MODEL_ENGINE_APIKEY}"} + headers = {"Authorization": f"Bearer {api_key}"} async with aiohttp.ClientSession( timeout=aiohttp.ClientTimeout(total=30), connector=aiohttp.TCPConnector(ssl=False) ) as session: async with session.get( - f"{MODEL_ENGINE_HOST}/open/router/v1/models", + f"{host.rstrip('/')}/open/router/v1/models", headers=headers ) as response: response.raise_for_status() @@ -130,9 +135,9 @@ async def get_models(self, provider_config: Dict) -> List[Dict]: "model_type": internal_type, "model_tag": me_type, "max_tokens": DEFAULT_LLM_MAX_TOKENS if internal_type in ("llm", "vlm") else 0, - # ModelEngine models will get base_url and api_key from environment - "base_url": MODEL_ENGINE_HOST, - "api_key": MODEL_ENGINE_APIKEY, + # ModelEngine models will get base_url and api_key from provider_config (or env) + "base_url": host, + "api_key": api_key, }) return filtered_models diff --git a/frontend/app/[locale]/models/components/model/ModelAddDialog.tsx b/frontend/app/[locale]/models/components/model/ModelAddDialog.tsx index f2257e03d..951d6f201 100644 --- a/frontend/app/[locale]/models/components/model/ModelAddDialog.tsx +++ b/frontend/app/[locale]/models/components/model/ModelAddDialog.tsx @@ -4,10 +4,10 @@ import { useTranslation } from "react-i18next"; import { Modal, Select, Input, Button, Switch, Tooltip, App } from "antd"; import { InfoCircleFilled } from "@ant-design/icons"; import { - LoaderCircle, - ChevronRight, - ChevronDown, - Settings + LoaderCircle, + ChevronRight, + ChevronDown, + Settings } from "lucide-react"; import { useConfig } from "@/hooks/useConfig"; @@ -169,6 +169,7 @@ export const ModelAddDialog = ({ // Whether to import multiple models at once isBatchImport: false, provider: "modelengine", + modelEngineUrl: "", vectorDimension: "1024", // Default chunk size range for embedding models chunkSizeRange: [ @@ -306,6 +307,14 @@ export const ModelAddDialog = ({ // Check if the form is valid const isFormValid = () => { if (form.isBatchImport) { + // If provider is ModelEngine, require the ModelEngine URL as well. + if (form.provider === "modelengine") { + return ( + form.provider.trim() !== "" && + form.apiKey.trim() !== "" && + ((form as any).modelEngineUrl || "").toString().trim() !== "" + ); + } return form.provider.trim() !== "" && form.apiKey.trim() !== ""; } if (form.type === MODEL_TYPES.EMBEDDING) { @@ -602,6 +611,7 @@ export const ModelAddDialog = ({ isMultimodal: false, isBatchImport: false, provider: "silicon", + modelEngineUrl: "", vectorDimension: "1024", chunkSizeRange: [ DEFAULT_EXPECTED_CHUNK_SIZE, @@ -675,6 +685,21 @@ export const ModelAddDialog = ({ + {/* ModelEngine URL input (only when provider is ModelEngine) */} + {form.provider === "modelengine" && ( +
{agentDisplayNameError}
- )} - {!agentDisplayNameError && - (isCreatingNewAgent || currentDisplayName !== originalDisplayName) && - agentDisplayNameStatus === NAME_CHECK_STATUS.EXISTS_IN_TENANT && ( -- {t("agent.error.displayNameExists", { - displayName: agentDisplayName, - })} -
- )} - {!agentDisplayNameError && - agentDisplayNameStatus !== NAME_CHECK_STATUS.EXISTS_IN_TENANT && - shouldShowDuplicateDisplayNameReason && ( -- {t("agent.error.displayNameExists", { - displayName: agentDisplayName || editingAgent?.display_name || "", - })} -
- )} -{agentNameError}
- )} - {!agentNameError && - (isCreatingNewAgent || currentAgentName !== originalAgentName) && - agentNameStatus === NAME_CHECK_STATUS.EXISTS_IN_TENANT && ( -- {t("agent.error.nameExists", { name: agentName })} -
- )} - {!agentNameError && - agentNameStatus !== NAME_CHECK_STATUS.EXISTS_IN_TENANT && - shouldShowDuplicateNameReason && ( -- {t("agent.error.nameExists", { - name: agentName || editingAgent?.name || "", - })} -
- )} -- {t("agent.author.hint", { defaultValue: "Default: {{email}}", email: user.email })} -
- )} -- {t("agent.error.modelUnavailable", { - modelName: effectiveModelName, - })} -
- )} - {llmModels.length === 0 && ( -- {t("businessLogic.config.error.noAvailableModels")} -
- )} -
+ {currentContainerLogs || t("mcpConfig.containerLogs.empty")}
+
+ )}
+ {tool?.description}
+