2026-05-29 17:39:41 +08:00
|
|
|
#
|
|
|
|
|
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
|
|
|
|
#
|
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
|
#
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
#
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
|
# limitations under the License.
|
|
|
|
|
#
|
|
|
|
|
import logging
|
2026-07-07 07:10:27 +05:30
|
|
|
import os
|
2026-05-29 17:39:41 +08:00
|
|
|
|
2026-07-07 07:10:27 +05:30
|
|
|
from api.db.joint_services.tenant_model_service import ensure_mineru_from_env, ensure_opendataloader_from_env, ensure_paddleocr_from_env
|
2026-05-29 17:39:41 +08:00
|
|
|
from api.db.services.tenant_model_instance_service import TenantModelInstanceService
|
2026-07-07 07:10:27 +05:30
|
|
|
from api.db.services.tenant_model_provider_service import TenantModelProviderService
|
2026-05-29 17:39:41 +08:00
|
|
|
from api.db.services.tenant_model_service import TenantModelService
|
|
|
|
|
from api.db.services.user_service import TenantService
|
2026-07-08 09:47:29 +08:00
|
|
|
from api.utils.model_utils import get_model_type_human, calculate_model_type
|
2026-07-07 07:10:27 +05:30
|
|
|
from common.constants import ActiveStatusEnum, LLMType
|
|
|
|
|
from common.settings import FACTORY_LLM_INFOS
|
2026-05-29 17:39:41 +08:00
|
|
|
|
|
|
|
|
# Mapping from model_type string to Tenant model field name
|
|
|
|
|
MODEL_TYPE_TO_FIELD = {
|
|
|
|
|
"chat": "llm_id",
|
|
|
|
|
"embedding": "embd_id",
|
|
|
|
|
"rerank": "rerank_id",
|
|
|
|
|
"asr": "asr_id",
|
|
|
|
|
"vision": "img2txt_id",
|
|
|
|
|
"tts": "tts_id",
|
|
|
|
|
"ocr": "ocr_id",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MODEL_TAG_TO_TYPE = {
|
|
|
|
|
"chat": "chat",
|
|
|
|
|
"embedding": "embedding",
|
|
|
|
|
"rerank": "rerank",
|
|
|
|
|
"asr": "speech2text",
|
|
|
|
|
"vision": "image2text",
|
|
|
|
|
"tts": "tts",
|
|
|
|
|
"ocr": "ocr",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-06-03 10:17:12 +08:00
|
|
|
def _to_int(v, default=500):
|
|
|
|
|
try:
|
|
|
|
|
return int(v)
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
|
return default
|
|
|
|
|
|
|
|
|
|
|
2026-06-10 15:35:21 +08:00
|
|
|
def _factory_model_types(llm: dict) -> list[str]:
|
|
|
|
|
model_type = llm.get("model_type")
|
|
|
|
|
if isinstance(model_type, list):
|
|
|
|
|
return model_type
|
|
|
|
|
return [model_type] if model_type else []
|
|
|
|
|
|
|
|
|
|
|
2026-05-29 17:39:41 +08:00
|
|
|
def _get_model_info(tenant_id: str, default_model: str, model_type: str):
|
|
|
|
|
"""
|
|
|
|
|
Parse a composite model string (modelName@instanceName@providerName or modelName@providerName)
|
|
|
|
|
and validate that the provider, instance, and model exist.
|
|
|
|
|
|
|
|
|
|
Returns a dict with model info or None on error.
|
|
|
|
|
"""
|
|
|
|
|
if not default_model:
|
|
|
|
|
return None
|
|
|
|
|
|
2026-07-07 07:10:27 +05:30
|
|
|
# The composite key is right-anchored: provider_name is always the *last*
|
|
|
|
|
# '@'-separated field. Use rsplit so a model_name that itself contains '@'
|
|
|
|
|
# (e.g. LM Studio IDs like `text-embedding-nomic-embed-text-v1.5@q8_0`)
|
|
|
|
|
# remains intact in the leftmost field instead of being truncated.
|
|
|
|
|
parts = default_model.rsplit("@", 2)
|
2026-05-29 17:39:41 +08:00
|
|
|
if len(parts) == 3:
|
|
|
|
|
model_name, instance_name, provider_name = parts
|
|
|
|
|
elif len(parts) == 2:
|
|
|
|
|
model_name, provider_name = parts
|
|
|
|
|
instance_name = "default"
|
2026-07-07 07:10:27 +05:30
|
|
|
else:
|
fix: show default embedding model when provider is not yet registered (#15511)
### What problem does this PR solve?
### Problem
On the Model Providers page, the Embedding Model dropdown in System
Model Settings shows empty (no default selected), even though a default
embedding model is configured in `service_conf.yaml`.
### Root Cause
Two issues were identified:
1. **Backend: `_get_model_info` fails for unregistered providers**
The tenant's `embd_id` is set to `bge-m3@xxxx` during initialization
(from the placeholder config `factory: 'xxxx'`). The `_get_model_info`
function requires the provider to exist in `tenant_model_provider`
table, but `xxxx` is never a real provider. Even after the user adds a
real provider (e.g., ZHIPU-AI), the stale `embd_id` still references the
non-existent one, causing the function to return `None`.
2. **Frontend: default models cache not invalidated after adding
provider**
`useAddProviderInstance` only invalidates `addedProviders` and
`allModels` caches after adding a provider instance, but does **not**
invalidate the `defaultModels` cache. This means the default model list
is not re-fetched until the user manually refreshes the page.
### Fix
**`api/apps/services/models_api_service.py`**
- Added `_resolve_model_from_tenant_providers()` helper: when the
default model's provider doesn't exist (e.g., placeholder `xxxx`), it
searches through the tenant's actually registered providers for a model
of the same type and returns the first match.
- When an instance name doesn't match (e.g., `"default"` vs actual name
`"1"`), the function now auto-resolves to the first real instance under
that provider.
- Falls back to `FACTORY_LLM_INFOS` validation when neither provider nor
instance exists.
**`web/src/hooks/use-llm-request.tsx`**
- Added `queryClient.invalidateQueries({ queryKey:
LlmKeys.defaultModels() })` to `useAddProviderInstance` so that the
default model list is re-fetched immediately after a provider instance
is added, eliminating the need for a manual page refresh.
### Testing
- Verified with a tenant whose `embd_id=bge-m3@xxxx` and only provider
is ZHIPU-AI (instance `1`): `_resolve_model_from_tenant_providers`
correctly resolves to `embedding-2@1@ZHIPU-AI`.
- After adding a provider via the UI, the embedding model dropdown now
immediately shows the resolved default without requiring a page refresh.
### Type of change
- [x] Bug Fix (non-breaking change which fixes an issue)
---------
Signed-off-by: noob <yixiao121314@outlook.com>
2026-06-03 18:55:49 -07:00
|
|
|
model_name = parts[0]
|
|
|
|
|
provider_name = ""
|
|
|
|
|
instance_name = "default"
|
2026-05-29 17:39:41 +08:00
|
|
|
|
|
|
|
|
model_type = MODEL_TAG_TO_TYPE.get(model_type, model_type)
|
|
|
|
|
# Special case: OCR with infiniflow@default@deepdoc is always enabled
|
|
|
|
|
if model_type == "ocr" and provider_name == "infiniflow" and instance_name == "default" and model_name == "deepdoc":
|
|
|
|
|
return {
|
|
|
|
|
"model_provider": provider_name,
|
|
|
|
|
"model_instance": instance_name,
|
|
|
|
|
"model_name": model_name,
|
|
|
|
|
"model_type": model_type,
|
|
|
|
|
"enable": True,
|
|
|
|
|
}
|
|
|
|
|
|
fix: show default embedding model when provider is not yet registered (#15511)
### What problem does this PR solve?
### Problem
On the Model Providers page, the Embedding Model dropdown in System
Model Settings shows empty (no default selected), even though a default
embedding model is configured in `service_conf.yaml`.
### Root Cause
Two issues were identified:
1. **Backend: `_get_model_info` fails for unregistered providers**
The tenant's `embd_id` is set to `bge-m3@xxxx` during initialization
(from the placeholder config `factory: 'xxxx'`). The `_get_model_info`
function requires the provider to exist in `tenant_model_provider`
table, but `xxxx` is never a real provider. Even after the user adds a
real provider (e.g., ZHIPU-AI), the stale `embd_id` still references the
non-existent one, causing the function to return `None`.
2. **Frontend: default models cache not invalidated after adding
provider**
`useAddProviderInstance` only invalidates `addedProviders` and
`allModels` caches after adding a provider instance, but does **not**
invalidate the `defaultModels` cache. This means the default model list
is not re-fetched until the user manually refreshes the page.
### Fix
**`api/apps/services/models_api_service.py`**
- Added `_resolve_model_from_tenant_providers()` helper: when the
default model's provider doesn't exist (e.g., placeholder `xxxx`), it
searches through the tenant's actually registered providers for a model
of the same type and returns the first match.
- When an instance name doesn't match (e.g., `"default"` vs actual name
`"1"`), the function now auto-resolves to the first real instance under
that provider.
- Falls back to `FACTORY_LLM_INFOS` validation when neither provider nor
instance exists.
**`web/src/hooks/use-llm-request.tsx`**
- Added `queryClient.invalidateQueries({ queryKey:
LlmKeys.defaultModels() })` to `useAddProviderInstance` so that the
default model list is re-fetched immediately after a provider instance
is added, eliminating the need for a manual page refresh.
### Testing
- Verified with a tenant whose `embd_id=bge-m3@xxxx` and only provider
is ZHIPU-AI (instance `1`): `_resolve_model_from_tenant_providers`
correctly resolves to `embedding-2@1@ZHIPU-AI`.
- After adding a provider via the UI, the embedding model dropdown now
immediately shows the resolved default without requiring a page refresh.
### Type of change
- [x] Bug Fix (non-breaking change which fixes an issue)
---------
Signed-off-by: noob <yixiao121314@outlook.com>
2026-06-03 18:55:49 -07:00
|
|
|
# Special case: TEI Builtin embedding model
|
|
|
|
|
compose_profiles = os.getenv("COMPOSE_PROFILES", "")
|
|
|
|
|
tei_model = os.getenv("TEI_MODEL", "")
|
2026-07-03 12:53:39 +08:00
|
|
|
if model_type == "embedding" and "tei-" in compose_profiles and tei_model and model_name == tei_model and (not provider_name or provider_name == "Builtin"):
|
fix: show default embedding model when provider is not yet registered (#15511)
### What problem does this PR solve?
### Problem
On the Model Providers page, the Embedding Model dropdown in System
Model Settings shows empty (no default selected), even though a default
embedding model is configured in `service_conf.yaml`.
### Root Cause
Two issues were identified:
1. **Backend: `_get_model_info` fails for unregistered providers**
The tenant's `embd_id` is set to `bge-m3@xxxx` during initialization
(from the placeholder config `factory: 'xxxx'`). The `_get_model_info`
function requires the provider to exist in `tenant_model_provider`
table, but `xxxx` is never a real provider. Even after the user adds a
real provider (e.g., ZHIPU-AI), the stale `embd_id` still references the
non-existent one, causing the function to return `None`.
2. **Frontend: default models cache not invalidated after adding
provider**
`useAddProviderInstance` only invalidates `addedProviders` and
`allModels` caches after adding a provider instance, but does **not**
invalidate the `defaultModels` cache. This means the default model list
is not re-fetched until the user manually refreshes the page.
### Fix
**`api/apps/services/models_api_service.py`**
- Added `_resolve_model_from_tenant_providers()` helper: when the
default model's provider doesn't exist (e.g., placeholder `xxxx`), it
searches through the tenant's actually registered providers for a model
of the same type and returns the first match.
- When an instance name doesn't match (e.g., `"default"` vs actual name
`"1"`), the function now auto-resolves to the first real instance under
that provider.
- Falls back to `FACTORY_LLM_INFOS` validation when neither provider nor
instance exists.
**`web/src/hooks/use-llm-request.tsx`**
- Added `queryClient.invalidateQueries({ queryKey:
LlmKeys.defaultModels() })` to `useAddProviderInstance` so that the
default model list is re-fetched immediately after a provider instance
is added, eliminating the need for a manual page refresh.
### Testing
- Verified with a tenant whose `embd_id=bge-m3@xxxx` and only provider
is ZHIPU-AI (instance `1`): `_resolve_model_from_tenant_providers`
correctly resolves to `embedding-2@1@ZHIPU-AI`.
- After adding a provider via the UI, the embedding model dropdown now
immediately shows the resolved default without requiring a page refresh.
### Type of change
- [x] Bug Fix (non-breaking change which fixes an issue)
---------
Signed-off-by: noob <yixiao121314@outlook.com>
2026-06-03 18:55:49 -07:00
|
|
|
return {
|
|
|
|
|
"model_provider": "Builtin",
|
|
|
|
|
"model_instance": "default",
|
|
|
|
|
"model_name": model_name,
|
|
|
|
|
"model_type": model_type,
|
|
|
|
|
"enable": True,
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-29 17:39:41 +08:00
|
|
|
# Check if the provider exists for the tenant
|
|
|
|
|
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
|
|
|
|
if not provider_obj:
|
|
|
|
|
logging.warning(f"Provider '{provider_name}' not found for tenant '{tenant_id}'")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
# Check if the instance exists
|
|
|
|
|
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name)
|
|
|
|
|
if not instance_obj:
|
|
|
|
|
logging.warning(f"Instance '{instance_name}' not found for provider '{provider_name}'")
|
|
|
|
|
return None
|
|
|
|
|
|
2026-07-08 09:47:29 +08:00
|
|
|
model_record = TenantModelService.get_by_provider_id_and_instance_id_and_model_name(provider_obj.id, instance_obj.id, model_name)
|
|
|
|
|
if not model_record:
|
|
|
|
|
logging.warning(f"Model '{model_name}' not found for provider '{provider_name}' and instance '{instance_name}'")
|
2026-05-29 17:39:41 +08:00
|
|
|
return None
|
|
|
|
|
|
2026-07-08 09:47:29 +08:00
|
|
|
if not model_record.status == ActiveStatusEnum.ACTIVE.value:
|
|
|
|
|
logging.warning(f"Model '{model_name}' is disabled")
|
2026-05-29 17:39:41 +08:00
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"model_provider": provider_name,
|
|
|
|
|
"model_instance": instance_name,
|
|
|
|
|
"model_name": model_name,
|
|
|
|
|
"model_type": model_type,
|
2026-07-08 09:47:29 +08:00
|
|
|
"enable": True
|
2026-05-29 17:39:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _check_model_available(tenant_id: str, provider_name: str, instance_name: str, model_name: str, model_type: str):
|
|
|
|
|
"""
|
|
|
|
|
Validate that a model is available for the tenant:
|
|
|
|
|
- Provider exists for the tenant
|
|
|
|
|
- Instance exists under the provider
|
|
|
|
|
- Model is in the LLM factory info for the provider
|
|
|
|
|
- Model type matches
|
|
|
|
|
- Model is not disabled in TenantModel table
|
|
|
|
|
|
|
|
|
|
Returns (success, error_message).
|
|
|
|
|
"""
|
|
|
|
|
if provider_name == "infiniflow" and instance_name == "default" and model_name == "deepdoc":
|
|
|
|
|
return True, None
|
|
|
|
|
|
|
|
|
|
if model_type == "ocr" and provider_name == "infiniflow" and instance_name == "default" and model_name == "deepdoc":
|
|
|
|
|
return True, None
|
|
|
|
|
|
|
|
|
|
compose_profiles = os.getenv("COMPOSE_PROFILES", "")
|
2026-07-03 12:53:39 +08:00
|
|
|
is_tei_builtin_embedding = model_type == LLMType.EMBEDDING.value and "tei-" in compose_profiles and model_name == os.getenv("TEI_MODEL", "") and (provider_name == "Builtin" or not provider_name)
|
2026-05-29 17:39:41 +08:00
|
|
|
if is_tei_builtin_embedding:
|
|
|
|
|
return True, None
|
|
|
|
|
|
|
|
|
|
# Check provider
|
|
|
|
|
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
|
|
|
|
if not provider_obj:
|
|
|
|
|
return False, f"Provider '{provider_name}' not found"
|
|
|
|
|
|
|
|
|
|
# Check instance
|
|
|
|
|
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name)
|
|
|
|
|
if not instance_obj:
|
|
|
|
|
return False, f"Instance '{instance_name}' not found for provider '{provider_name}'"
|
|
|
|
|
|
|
|
|
|
# Check model schema
|
|
|
|
|
factory_info = [f for f in (FACTORY_LLM_INFOS or []) if f["name"] == provider_name]
|
|
|
|
|
if not factory_info:
|
|
|
|
|
return False, f"Provider '{provider_name}' not found in factory info"
|
|
|
|
|
model_type = MODEL_TAG_TO_TYPE.get(model_type, model_type)
|
|
|
|
|
# Check if model is disabled
|
2026-07-08 09:47:29 +08:00
|
|
|
model_record = TenantModelService.get_by_provider_id_and_instance_id_and_model_name(provider_obj.id, instance_obj.id, model_name)
|
|
|
|
|
if model_record.status != ActiveStatusEnum.ACTIVE.value:
|
|
|
|
|
return False, f"Model '{model_name}' isn't available"
|
|
|
|
|
if model_type not in get_model_type_human(model_record.model_type):
|
|
|
|
|
return False, f"Model '{model_name}' isn't a {model_type} model"
|
2026-05-29 17:39:41 +08:00
|
|
|
return True, None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def list_tenant_default_models(tenant_id: str):
|
|
|
|
|
"""
|
|
|
|
|
List all default models for a tenant.
|
|
|
|
|
|
|
|
|
|
For each model type (chat, embedding, rerank, asr, vision, tts, ocr),
|
|
|
|
|
reads the composite model ID string from the Tenant record and resolves
|
|
|
|
|
it into provider/instance/name components.
|
|
|
|
|
|
|
|
|
|
:param tenant_id: tenant ID
|
|
|
|
|
:return: (success, result_or_error_message)
|
|
|
|
|
"""
|
|
|
|
|
e, tenant = TenantService.get_by_id(tenant_id)
|
|
|
|
|
if not e:
|
|
|
|
|
return False, "Tenant not found"
|
|
|
|
|
|
|
|
|
|
models = []
|
|
|
|
|
|
|
|
|
|
for model_type, field_name in MODEL_TYPE_TO_FIELD.items():
|
|
|
|
|
default_model = getattr(tenant, field_name, None)
|
|
|
|
|
if not default_model:
|
|
|
|
|
continue
|
|
|
|
|
model_info = _get_model_info(tenant_id, default_model, model_type)
|
|
|
|
|
if model_info:
|
|
|
|
|
models.append(model_info)
|
|
|
|
|
|
|
|
|
|
return True, {"models": models}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_tenant_default_models(tenant_id: str, model_provider: str, model_instance: str, model_name: str, model_type: str):
|
|
|
|
|
"""
|
|
|
|
|
Set or clear a tenant default model.
|
|
|
|
|
|
|
|
|
|
If model_provider, model_instance, and model_name are all provided,
|
|
|
|
|
validates the model and sets it as the default.
|
|
|
|
|
If all three are empty, clears the default for the given model type.
|
|
|
|
|
|
|
|
|
|
:param tenant_id: tenant ID
|
|
|
|
|
:param model_provider: provider name
|
|
|
|
|
:param model_instance: instance name
|
|
|
|
|
:param model_name: model name
|
|
|
|
|
:param model_type: model type (chat, embedding, rerank, asr, vision, tts, ocr)
|
|
|
|
|
:return: (success, result_or_error_message)
|
|
|
|
|
"""
|
|
|
|
|
field_name = MODEL_TYPE_TO_FIELD.get(model_type)
|
|
|
|
|
if not field_name:
|
|
|
|
|
return False, f"model type '{model_type}' is invalid"
|
|
|
|
|
|
|
|
|
|
e, tenant = TenantService.get_by_id(tenant_id)
|
|
|
|
|
if not e:
|
|
|
|
|
return False, "Tenant not found"
|
|
|
|
|
|
|
|
|
|
if not model_provider and not model_instance and not model_name:
|
|
|
|
|
# Clear the default model
|
|
|
|
|
default_model = ""
|
|
|
|
|
elif model_provider and model_instance and model_name:
|
|
|
|
|
# Validate and set the default model
|
|
|
|
|
success, msg = _check_model_available(tenant_id, model_provider, model_instance, model_name, model_type)
|
|
|
|
|
if not success:
|
|
|
|
|
return False, msg
|
|
|
|
|
default_model = f"{model_name}@{model_instance}@{model_provider}"
|
|
|
|
|
else:
|
|
|
|
|
return False, "model_provider, model_instance and model_name must be specified together"
|
|
|
|
|
|
|
|
|
|
TenantService.update_by_id(tenant_id, {field_name: default_model})
|
|
|
|
|
return True, "success"
|
|
|
|
|
|
|
|
|
|
|
2026-07-03 12:53:39 +08:00
|
|
|
def list_tenant_added_models(tenant_id: str, model_type_filter: str = None):
|
2026-05-29 17:39:41 +08:00
|
|
|
"""
|
|
|
|
|
List all added models for a tenant.
|
|
|
|
|
|
|
|
|
|
:param tenant_id: tenant ID
|
|
|
|
|
:param model_type_filter: model type filter (chat, embedding, rerank, asr, vision, tts, ocr)
|
|
|
|
|
:return: (success, result_or_error_message)
|
|
|
|
|
"""
|
|
|
|
|
e, tenant = TenantService.get_by_id(tenant_id)
|
|
|
|
|
if not e:
|
|
|
|
|
return False, "Tenant not found"
|
|
|
|
|
|
2026-06-10 13:04:13 +08:00
|
|
|
ensure_mineru_from_env(tenant_id)
|
|
|
|
|
ensure_paddleocr_from_env(tenant_id)
|
2026-06-10 14:59:57 +08:00
|
|
|
ensure_opendataloader_from_env(tenant_id)
|
2026-06-10 13:04:13 +08:00
|
|
|
|
2026-07-08 09:47:29 +08:00
|
|
|
model_type_filter_bin = calculate_model_type(model_type_filter.lower()) if model_type_filter else None
|
2026-05-29 17:39:41 +08:00
|
|
|
|
|
|
|
|
providers = TenantModelProviderService.get_by_tenant_id(tenant_id)
|
|
|
|
|
if not providers:
|
|
|
|
|
return True, []
|
|
|
|
|
|
|
|
|
|
provider_ids = [provider.id for provider in providers]
|
|
|
|
|
instances = TenantModelInstanceService.get_by_provider_ids(provider_ids)
|
|
|
|
|
if not instances:
|
|
|
|
|
return True, []
|
|
|
|
|
provider_instance_map: dict = {}
|
|
|
|
|
provider_info_map = {provider.id: provider for provider in providers}
|
2026-07-08 09:47:29 +08:00
|
|
|
instance_info_map = {instance.id: instance for instance in instances}
|
2026-05-29 17:39:41 +08:00
|
|
|
for provider_instance_record in instances:
|
|
|
|
|
provider_name = provider_info_map[provider_instance_record.provider_id].provider_name if provider_info_map.get(provider_instance_record.provider_id) else ""
|
|
|
|
|
if provider_instance_map.get(provider_name):
|
|
|
|
|
provider_instance_map[provider_name].append(provider_instance_record)
|
|
|
|
|
else:
|
|
|
|
|
provider_instance_map[provider_name] = [provider_instance_record]
|
|
|
|
|
|
|
|
|
|
model_records = TenantModelService.get_models_by_provider_ids_and_instance_ids(provider_ids, list({instance.id for instance in instances}))
|
2026-07-08 09:47:29 +08:00
|
|
|
target_type_records = [record for record in model_records if record.model_type & model_type_filter_bin] if model_type_filter_bin else model_records
|
2026-05-29 17:39:41 +08:00
|
|
|
|
2026-06-03 10:17:12 +08:00
|
|
|
factory_rank_mapping = {factory["name"]: -_to_int(factory.get("rank", "500")) for factory in FACTORY_LLM_INFOS}
|
2026-07-08 09:47:29 +08:00
|
|
|
added_models = [{
|
|
|
|
|
"model_type": get_model_type_human(model_record.model_type),
|
|
|
|
|
"name": model_record.model_name,
|
|
|
|
|
"provider_id": model_record.provider_id,
|
|
|
|
|
"provider_name": provider_info_map[model_record.provider_id].provider_name,
|
|
|
|
|
"instance_id": model_record.instance_id,
|
|
|
|
|
"instance_name": instance_info_map[model_record.instance_id].instance_name
|
|
|
|
|
} for model_record in target_type_records]
|
2026-05-29 17:39:41 +08:00
|
|
|
|
fix: show default embedding model when provider is not yet registered (#15511)
### What problem does this PR solve?
### Problem
On the Model Providers page, the Embedding Model dropdown in System
Model Settings shows empty (no default selected), even though a default
embedding model is configured in `service_conf.yaml`.
### Root Cause
Two issues were identified:
1. **Backend: `_get_model_info` fails for unregistered providers**
The tenant's `embd_id` is set to `bge-m3@xxxx` during initialization
(from the placeholder config `factory: 'xxxx'`). The `_get_model_info`
function requires the provider to exist in `tenant_model_provider`
table, but `xxxx` is never a real provider. Even after the user adds a
real provider (e.g., ZHIPU-AI), the stale `embd_id` still references the
non-existent one, causing the function to return `None`.
2. **Frontend: default models cache not invalidated after adding
provider**
`useAddProviderInstance` only invalidates `addedProviders` and
`allModels` caches after adding a provider instance, but does **not**
invalidate the `defaultModels` cache. This means the default model list
is not re-fetched until the user manually refreshes the page.
### Fix
**`api/apps/services/models_api_service.py`**
- Added `_resolve_model_from_tenant_providers()` helper: when the
default model's provider doesn't exist (e.g., placeholder `xxxx`), it
searches through the tenant's actually registered providers for a model
of the same type and returns the first match.
- When an instance name doesn't match (e.g., `"default"` vs actual name
`"1"`), the function now auto-resolves to the first real instance under
that provider.
- Falls back to `FACTORY_LLM_INFOS` validation when neither provider nor
instance exists.
**`web/src/hooks/use-llm-request.tsx`**
- Added `queryClient.invalidateQueries({ queryKey:
LlmKeys.defaultModels() })` to `useAddProviderInstance` so that the
default model list is re-fetched immediately after a provider instance
is added, eliminating the need for a manual page refresh.
### Testing
- Verified with a tenant whose `embd_id=bge-m3@xxxx` and only provider
is ZHIPU-AI (instance `1`): `_resolve_model_from_tenant_providers`
correctly resolves to `embedding-2@1@ZHIPU-AI`.
- After adding a provider via the UI, the embedding model dropdown now
immediately shows the resolved default without requiring a page refresh.
### Type of change
- [x] Bug Fix (non-breaking change which fixes an issue)
---------
Signed-off-by: noob <yixiao121314@outlook.com>
2026-06-03 18:55:49 -07:00
|
|
|
# Add TEI Builtin embedding model if configured
|
|
|
|
|
compose_profiles = os.getenv("COMPOSE_PROFILES", "")
|
|
|
|
|
tei_model = os.getenv("TEI_MODEL", "")
|
|
|
|
|
if "tei-" in compose_profiles and tei_model:
|
|
|
|
|
if not model_type_filter or model_type_filter == "embedding":
|
2026-07-03 12:53:39 +08:00
|
|
|
tei_already_added = any(m["provider_name"] == "Builtin" and m["name"] == tei_model for m in added_models)
|
fix: show default embedding model when provider is not yet registered (#15511)
### What problem does this PR solve?
### Problem
On the Model Providers page, the Embedding Model dropdown in System
Model Settings shows empty (no default selected), even though a default
embedding model is configured in `service_conf.yaml`.
### Root Cause
Two issues were identified:
1. **Backend: `_get_model_info` fails for unregistered providers**
The tenant's `embd_id` is set to `bge-m3@xxxx` during initialization
(from the placeholder config `factory: 'xxxx'`). The `_get_model_info`
function requires the provider to exist in `tenant_model_provider`
table, but `xxxx` is never a real provider. Even after the user adds a
real provider (e.g., ZHIPU-AI), the stale `embd_id` still references the
non-existent one, causing the function to return `None`.
2. **Frontend: default models cache not invalidated after adding
provider**
`useAddProviderInstance` only invalidates `addedProviders` and
`allModels` caches after adding a provider instance, but does **not**
invalidate the `defaultModels` cache. This means the default model list
is not re-fetched until the user manually refreshes the page.
### Fix
**`api/apps/services/models_api_service.py`**
- Added `_resolve_model_from_tenant_providers()` helper: when the
default model's provider doesn't exist (e.g., placeholder `xxxx`), it
searches through the tenant's actually registered providers for a model
of the same type and returns the first match.
- When an instance name doesn't match (e.g., `"default"` vs actual name
`"1"`), the function now auto-resolves to the first real instance under
that provider.
- Falls back to `FACTORY_LLM_INFOS` validation when neither provider nor
instance exists.
**`web/src/hooks/use-llm-request.tsx`**
- Added `queryClient.invalidateQueries({ queryKey:
LlmKeys.defaultModels() })` to `useAddProviderInstance` so that the
default model list is re-fetched immediately after a provider instance
is added, eliminating the need for a manual page refresh.
### Testing
- Verified with a tenant whose `embd_id=bge-m3@xxxx` and only provider
is ZHIPU-AI (instance `1`): `_resolve_model_from_tenant_providers`
correctly resolves to `embedding-2@1@ZHIPU-AI`.
- After adding a provider via the UI, the embedding model dropdown now
immediately shows the resolved default without requiring a page refresh.
### Type of change
- [x] Bug Fix (non-breaking change which fixes an issue)
---------
Signed-off-by: noob <yixiao121314@outlook.com>
2026-06-03 18:55:49 -07:00
|
|
|
if not tei_already_added:
|
2026-07-03 12:53:39 +08:00
|
|
|
added_models.append(
|
|
|
|
|
{
|
|
|
|
|
"model_type": ["embedding"],
|
|
|
|
|
"name": tei_model,
|
|
|
|
|
"provider_id": "",
|
|
|
|
|
"provider_name": "Builtin",
|
|
|
|
|
"instance_id": "",
|
|
|
|
|
"instance_name": "default",
|
|
|
|
|
}
|
|
|
|
|
)
|
fix: show default embedding model when provider is not yet registered (#15511)
### What problem does this PR solve?
### Problem
On the Model Providers page, the Embedding Model dropdown in System
Model Settings shows empty (no default selected), even though a default
embedding model is configured in `service_conf.yaml`.
### Root Cause
Two issues were identified:
1. **Backend: `_get_model_info` fails for unregistered providers**
The tenant's `embd_id` is set to `bge-m3@xxxx` during initialization
(from the placeholder config `factory: 'xxxx'`). The `_get_model_info`
function requires the provider to exist in `tenant_model_provider`
table, but `xxxx` is never a real provider. Even after the user adds a
real provider (e.g., ZHIPU-AI), the stale `embd_id` still references the
non-existent one, causing the function to return `None`.
2. **Frontend: default models cache not invalidated after adding
provider**
`useAddProviderInstance` only invalidates `addedProviders` and
`allModels` caches after adding a provider instance, but does **not**
invalidate the `defaultModels` cache. This means the default model list
is not re-fetched until the user manually refreshes the page.
### Fix
**`api/apps/services/models_api_service.py`**
- Added `_resolve_model_from_tenant_providers()` helper: when the
default model's provider doesn't exist (e.g., placeholder `xxxx`), it
searches through the tenant's actually registered providers for a model
of the same type and returns the first match.
- When an instance name doesn't match (e.g., `"default"` vs actual name
`"1"`), the function now auto-resolves to the first real instance under
that provider.
- Falls back to `FACTORY_LLM_INFOS` validation when neither provider nor
instance exists.
**`web/src/hooks/use-llm-request.tsx`**
- Added `queryClient.invalidateQueries({ queryKey:
LlmKeys.defaultModels() })` to `useAddProviderInstance` so that the
default model list is re-fetched immediately after a provider instance
is added, eliminating the need for a manual page refresh.
### Testing
- Verified with a tenant whose `embd_id=bge-m3@xxxx` and only provider
is ZHIPU-AI (instance `1`): `_resolve_model_from_tenant_providers`
correctly resolves to `embedding-2@1@ZHIPU-AI`.
- After adding a provider via the UI, the embedding model dropdown now
immediately shows the resolved default without requiring a page refresh.
### Type of change
- [x] Bug Fix (non-breaking change which fixes an issue)
---------
Signed-off-by: noob <yixiao121314@outlook.com>
2026-06-03 18:55:49 -07:00
|
|
|
|
2026-07-08 09:47:29 +08:00
|
|
|
added_models.sort(
|
|
|
|
|
key=lambda x: (factory_rank_mapping.get(x["provider_name"]), x["provider_name"], x["instance_name"]))
|
2026-06-02 13:24:53 +08:00
|
|
|
|
2026-05-29 17:39:41 +08:00
|
|
|
return True, added_models
|