Fix: ValueError: too many values to unpack in list_tenant_added_models for model IDs containing '@' (#16467) (#16468)

This commit is contained in:
S
2026-07-07 07:10:27 +05:30
committed by GitHub
parent d8cefcf052
commit f477d3329d
6 changed files with 760 additions and 54 deletions

View File

@@ -13,16 +13,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import os
import logging
import os
from api.db.joint_services.tenant_model_service import ensure_mineru_from_env, ensure_paddleocr_from_env, ensure_opendataloader_from_env
from common.constants import ActiveStatusEnum, LLMType
from common.settings import FACTORY_LLM_INFOS
from api.db.services.tenant_model_provider_service import TenantModelProviderService
from api.db.joint_services.tenant_model_service import ensure_mineru_from_env, ensure_opendataloader_from_env, ensure_paddleocr_from_env
from api.db.services.tenant_model_instance_service import TenantModelInstanceService
from api.db.services.tenant_model_provider_service import TenantModelProviderService
from api.db.services.tenant_model_service import TenantModelService
from api.db.services.user_service import TenantService
from common.constants import ActiveStatusEnum, LLMType
from common.settings import FACTORY_LLM_INFOS
# Mapping from model_type string to Tenant model field name
MODEL_TYPE_TO_FIELD = {
@@ -70,19 +70,20 @@ def _get_model_info(tenant_id: str, default_model: str, model_type: str):
if not default_model:
return None
parts = default_model.split("@")
# 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)
if len(parts) == 3:
model_name, instance_name, provider_name = parts
elif len(parts) == 2:
model_name, provider_name = parts
instance_name = "default"
elif len(parts) == 1:
else:
model_name = parts[0]
provider_name = ""
instance_name = "default"
else:
logging.warning(f"Invalid model string: {default_model}")
return None
model_type = MODEL_TAG_TO_TYPE.get(model_type, model_type)
# Special case: OCR with infiniflow@default@deepdoc is always enabled
@@ -324,7 +325,7 @@ def list_tenant_added_models(tenant_id: str, model_type_filter: str = None):
target_type_records = [record for record in model_records if record.model_type == model_type_filter] if model_type_filter else model_records
model_record_map = {}
for model in target_type_records:
instance_model_key = f"{model.provider_id}@{model.instance_id}@{model.model_name}"
instance_model_key = f"{model.provider_id}|{model.instance_id}|{model.model_name}"
if model_record_map.get(instance_model_key):
model_record_map[instance_model_key].append(model)
else:
@@ -346,7 +347,7 @@ def list_tenant_added_models(tenant_id: str, model_type_filter: str = None):
continue
for factory_instance in factory_instances:
model_record_key = f"{factory_instance.provider_id}@{factory_instance.id}@{llm['llm_name']}"
model_record_key = f"{factory_instance.provider_id}|{factory_instance.id}|{llm['llm_name']}"
model_key_in_factory.append(model_record_key)
manual_modified_models = model_record_map.get(model_record_key, [])
active_model_types = [manual_model.model_type for manual_model in manual_modified_models if manual_model.status == ActiveStatusEnum.ACTIVE.value]
@@ -374,7 +375,13 @@ def list_tenant_added_models(tenant_id: str, model_type_filter: str = None):
model_records = model_record_map.get(model_record_key, [])
if not model_records:
continue
provider_id, instance_id, model_name = model_record_key.split("@")
# The internal key uses '|' as separator (UUID|UUID|model_name)
# since model_name may contain '@' characters.
try:
provider_id, instance_id, model_name = model_record_key.split("|", 2)
except ValueError:
logging.warning(f"Skipping malformed manual model record key: {model_record_key!r}")
continue
model_types = [model.model_type for model in model_records if model.status == ActiveStatusEnum.ACTIVE.value]
if not model_types:
continue

View File

@@ -188,19 +188,27 @@ def get_tenant_default_model_by_type(tenant_id: str, model_type: str | enum.Enum
def split_model_name(model_name: str):
# Parse model_name: {model_name} or {model_name}@{factory_name} or {model_name}@{instance_name}@{factory_name}
parts = model_name.split("@")
if len(parts) == 1:
pure_model_name = parts[0]
provider_name = ""
instance_name = ""
elif len(parts) == 2:
pure_model_name = parts[0]
provider_name = parts[1]
#
# The composite key is right-anchored on the provider: the *last* '@'-separated
# field is the factory/provider, the second-to-last is the instance (when
# present), and everything to the left is the bare model name. Some model
# names legitimately contain '@' characters themselves (e.g. LM Studio
# embedding model IDs such as `text-embedding-nomic-embed-text-v1.5@q8_0`),
# which produces composite keys like
# `text-embedding-nomic-embed-text-v1.5@q8_0@lmstudio@LM-Studio`.
# Use rsplit with maxsplit=2 from the right so any '@' characters embedded
# in the leftmost model-name component are preserved as part of that name.
parts = model_name.rsplit("@", 2)
n = len(parts)
if n == 3:
pure_model_name, instance_name, provider_name = parts
elif n == 2:
pure_model_name, provider_name = parts
instance_name = "default"
else:
pure_model_name = parts[0]
instance_name = parts[1]
provider_name = parts[2]
provider_name = ""
instance_name = ""
return pure_model_name, instance_name, provider_name