mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-19 22:21:04 +08:00
Feat: v0.27.0 model provider (#16604)
This commit is contained in:
@@ -23,6 +23,7 @@ from common.exceptions import ArgumentException, NotFoundException
|
||||
from api.apps import login_required, current_user
|
||||
from api.utils.api_utils import validate_request, get_request_json, get_error_argument_result, get_json_result
|
||||
from api.apps.services import memory_api_service
|
||||
from api.db.joint_services.tenant_model_service import ensure_tenant_model_ids_for_params
|
||||
from api.utils.pagination_utils import validate_rest_api_page_size
|
||||
|
||||
|
||||
@@ -35,7 +36,15 @@ async def create_memory():
|
||||
req = await get_request_json()
|
||||
t_parsed = time.perf_counter() if timing_enabled else None
|
||||
try:
|
||||
memory_info = {"name": req["name"], "memory_type": req["memory_type"], "embd_id": req["embd_id"], "llm_id": req["llm_id"]}
|
||||
# Resolve tenant_model IDs from model names
|
||||
tenant_id = current_user.id
|
||||
memory_info = {
|
||||
"name": req["name"],
|
||||
"memory_type": req["memory_type"],
|
||||
"embd_id": req["embd_id"],
|
||||
"llm_id": req["llm_id"]
|
||||
}
|
||||
ensure_tenant_model_ids_for_params(tenant_id, memory_info)
|
||||
success, res = await memory_api_service.create_memory(memory_info)
|
||||
if timing_enabled:
|
||||
logging.info(
|
||||
@@ -79,26 +88,12 @@ async def create_memory():
|
||||
@login_required
|
||||
async def update_memory(memory_id):
|
||||
req = await get_request_json()
|
||||
new_settings = {
|
||||
k: req[k]
|
||||
for k in [
|
||||
"name",
|
||||
"permissions",
|
||||
"llm_id",
|
||||
"embd_id",
|
||||
"memory_type",
|
||||
"memory_size",
|
||||
"forgetting_policy",
|
||||
"temperature",
|
||||
"avatar",
|
||||
"description",
|
||||
"system_prompt",
|
||||
"user_prompt",
|
||||
"tenant_llm_id",
|
||||
"tenant_embd_id",
|
||||
]
|
||||
if k in req
|
||||
}
|
||||
# Resolve tenant_model IDs from model names when name is provided but id is not
|
||||
ensure_tenant_model_ids_for_params(current_user.id, req)
|
||||
new_settings = {k: req[k] for k in [
|
||||
"name", "permissions", "llm_id", "embd_id", "memory_type", "memory_size", "forgetting_policy", "temperature",
|
||||
"avatar", "description", "system_prompt", "user_prompt", "tenant_llm_id", "tenant_embd_id"
|
||||
] if k in req}
|
||||
try:
|
||||
success, res = await memory_api_service.update_memory(memory_id, new_settings)
|
||||
if success:
|
||||
|
||||
@@ -17,7 +17,7 @@ import logging
|
||||
|
||||
from quart import request
|
||||
|
||||
from api.apps import login_required
|
||||
from api.apps import login_required, current_user
|
||||
from api.utils.api_utils import (
|
||||
add_tenant_id_to_kwargs,
|
||||
get_error_argument_result,
|
||||
@@ -316,6 +316,9 @@ async def create_provider_instance(tenant_id: str = None, provider_id_or_name: s
|
||||
required:
|
||||
- instance_name
|
||||
- api_key
|
||||
- base_url
|
||||
- region
|
||||
- model_info
|
||||
properties:
|
||||
instance_name:
|
||||
type: string
|
||||
@@ -336,10 +339,24 @@ async def create_provider_instance(tenant_id: str = None, provider_id_or_name: s
|
||||
type: object
|
||||
"""
|
||||
data = await request.get_json()
|
||||
if not provider_id_or_name:
|
||||
return get_error_argument_result(message="provider_id_or_name is required")
|
||||
if not data or "instance_name" not in data:
|
||||
return get_error_argument_result(message="instance_name is required")
|
||||
|
||||
instance_name = data["instance_name"]
|
||||
# data only contains instance_name — no other fields needed
|
||||
if set(data.keys()) == {"instance_name"}:
|
||||
try:
|
||||
success, msg = await provider_api_service.create_name_only_provider_instance(tenant_id, provider_id_or_name, instance_name)
|
||||
if success:
|
||||
return get_result(message=msg)
|
||||
else:
|
||||
return get_error_data_result(message=msg)
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
return get_error_data_result(message="Internal server error")
|
||||
|
||||
api_key = data.get("api_key", "")
|
||||
base_url = data.get("base_url", "")
|
||||
region = data.get("region", "")
|
||||
@@ -397,7 +414,10 @@ async def verify_provider_api_key(provider_id_or_name: str = None):
|
||||
description: Region.
|
||||
model_info:
|
||||
type: object
|
||||
description: Model info.
|
||||
description: Model info. optional
|
||||
instance_id:
|
||||
type: string
|
||||
description: Instance ID. optional
|
||||
responses:
|
||||
200:
|
||||
description: Instance created successfully.
|
||||
@@ -405,6 +425,8 @@ async def verify_provider_api_key(provider_id_or_name: str = None):
|
||||
type: object
|
||||
"""
|
||||
data = await request.get_json()
|
||||
if not provider_id_or_name:
|
||||
return get_error_argument_result(message="provider_id_or_name is required")
|
||||
if not data or ("api_key" not in data and provider_id_or_name != "VLLM"):
|
||||
return get_error_argument_result(message="api_key is required")
|
||||
|
||||
@@ -414,8 +436,16 @@ async def verify_provider_api_key(provider_id_or_name: str = None):
|
||||
model_info = data.get("model_info", [])
|
||||
|
||||
try:
|
||||
success, msg = await provider_api_service.verify_api_key(provider_id_or_name, api_key, base_url, region, model_info)
|
||||
success, msg, model_verify_result = await provider_api_service.verify_api_key(provider_id_or_name, api_key, base_url, region, model_info)
|
||||
if success:
|
||||
if data.get("instance_id"):
|
||||
# if instance_id is provided, update the model verify result
|
||||
instance_id = data["instance_id"]
|
||||
try:
|
||||
for model, verify_result in model_verify_result.items():
|
||||
provider_api_service.update_model(current_user.id, provider_id_or_name, instance_id, model, {"verify": verify_result})
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
return get_result(message=msg)
|
||||
else:
|
||||
return get_error_data_result(message=msg)
|
||||
@@ -512,6 +542,117 @@ def show_provider_instance(tenant_id: str = None, provider_id_or_name: str = Non
|
||||
return get_error_data_result(message="Internal server error")
|
||||
|
||||
|
||||
@manager.route("/providers/<provider_id_or_name>/instances/<instance_id_or_name>", methods=["PUT"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
async def update_provider_instance(tenant_id: str = None, provider_id_or_name: str = None, instance_id_or_name: str = None):
|
||||
"""
|
||||
Update a provider instance.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: path
|
||||
name: provider_id_or_name
|
||||
type: string
|
||||
required: true
|
||||
description: Provider ID or name.
|
||||
- in: path
|
||||
name: instance_id_or_name
|
||||
type: string
|
||||
required: true
|
||||
description: Instance ID or name.
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
- in: body
|
||||
name: body
|
||||
description: Instance update parameters.
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- instance_name
|
||||
- api_key
|
||||
- base_url
|
||||
- region
|
||||
- model_info
|
||||
properties:
|
||||
instance_name:
|
||||
type: string
|
||||
description: Instance name.
|
||||
api_key:
|
||||
type: string
|
||||
description: API key.
|
||||
base_url:
|
||||
type: string
|
||||
description: Base URL.
|
||||
region:
|
||||
type: string
|
||||
description: Region.
|
||||
model_info:
|
||||
type: array
|
||||
description: List of models to configure for this instance.
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
model_type:
|
||||
type: array
|
||||
description: Model types.
|
||||
model_name:
|
||||
type: string
|
||||
description: Model name.
|
||||
max_tokens:
|
||||
type: integer
|
||||
description: Max tokens.
|
||||
extra:
|
||||
type: object
|
||||
description: Extra model info (e.g. is_tools).
|
||||
verify:
|
||||
type: boolean
|
||||
description: Verify api_key and base_url, default true
|
||||
responses:
|
||||
200:
|
||||
description: Instance updated successfully.
|
||||
schema:
|
||||
type: object
|
||||
"""
|
||||
data = await request.get_json()
|
||||
if not provider_id_or_name:
|
||||
return get_error_argument_result(message="provider_id_or_name is required")
|
||||
if not instance_id_or_name:
|
||||
return get_error_argument_result(message="instance_id_or_name is required")
|
||||
if not data:
|
||||
return get_error_argument_result(message="Request body is required")
|
||||
required_keys = ["instance_name", "api_key", "base_url", "model_info"]
|
||||
missing = [k for k in required_keys if k not in data]
|
||||
if missing:
|
||||
return get_error_argument_result(message=f"Missing required fields: {', '.join(missing)}")
|
||||
|
||||
instance_name = data["instance_name"]
|
||||
api_key = data["api_key"]
|
||||
base_url = data["base_url"]
|
||||
region = data.get("region", "default")
|
||||
model_info = data["model_info"]
|
||||
verify = data.get("verify", True)
|
||||
|
||||
try:
|
||||
success, msg = await provider_api_service.update_provider_instance(
|
||||
tenant_id, provider_id_or_name, instance_id_or_name, instance_name, api_key, base_url, region, model_info, verify
|
||||
)
|
||||
if success:
|
||||
return get_result(message=msg)
|
||||
else:
|
||||
return get_error_data_result(message=msg)
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
return get_error_data_result(message="Internal server error")
|
||||
|
||||
|
||||
@manager.route("/providers/<provider_id_or_name>/instances", methods=["DELETE"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
@@ -761,12 +902,72 @@ async def add_model_to_instance(tenant_id: str, provider_id_or_name: str, instan
|
||||
return get_error_data_result(message="Internal server error")
|
||||
|
||||
|
||||
@manager.route("/providers/<provider_id_or_name>/instances/<instance_id_or_name>/models", methods=["DELETE"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
async def delete_models_from_instance(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str):
|
||||
"""
|
||||
Delete models from an instance.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: path
|
||||
name: provider_id_or_name
|
||||
type: string
|
||||
required: true
|
||||
description: Provider ID or name.
|
||||
- in: path
|
||||
name: instance_id_or_name
|
||||
type: string
|
||||
required: true
|
||||
description: Instance ID or name.
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
- in: body
|
||||
name: body
|
||||
description: Model details.
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- model_name
|
||||
- model_type
|
||||
properties:
|
||||
model_name:
|
||||
type: list of string
|
||||
description: Model name.
|
||||
responses:
|
||||
200:
|
||||
description: Model deleted successfully.
|
||||
"""
|
||||
data = await request.get_json()
|
||||
if not data or "model_name" not in data:
|
||||
return get_error_argument_result(message="model_name is required")
|
||||
model_name = data["model_name"]
|
||||
try:
|
||||
success, result = await provider_api_service.delete_models_from_instance(tenant_id, provider_id_or_name, instance_id_or_name, model_name)
|
||||
if success:
|
||||
return get_result(message=result)
|
||||
else:
|
||||
return get_error_data_result(message=result)
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
return get_error_data_result(message="Internal server error")
|
||||
|
||||
|
||||
|
||||
@manager.route("/providers/<provider_id_or_name>/instances/<instance_id_or_name>/models/<path:model_name>", methods=["PATCH"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
async def enable_or_disable_model(tenant_id: str = None, provider_id_or_name: str = None, instance_id_or_name: str = None, model_name: str = None):
|
||||
async def alter_model(tenant_id: str = None, provider_id_or_name: str = None, instance_id_or_name: str = None, model_name: str = None):
|
||||
"""
|
||||
Enable or disable a model.
|
||||
Enable or disable a model, or update max_tokens
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
@@ -813,15 +1014,17 @@ async def enable_or_disable_model(tenant_id: str = None, provider_id_or_name: st
|
||||
type: object
|
||||
"""
|
||||
data = await request.get_json()
|
||||
if not data or "status" not in data:
|
||||
return get_error_argument_result(message="status is required")
|
||||
if not data or ("status" not in data and "max_tokens" not in data):
|
||||
return get_error_argument_result(message="status or max_tokens required.")
|
||||
|
||||
status = data["status"]
|
||||
if status not in ("active", "inactive"):
|
||||
update_dict = {k: data[k] for k in ["status", "max_tokens", "model_type", "extra"] if k in data}
|
||||
if update_dict.get("status") and update_dict["status"] not in ("active", "inactive"):
|
||||
return get_error_argument_result(message="status must be 'active' or 'inactive'")
|
||||
|
||||
try:
|
||||
success, msg = provider_api_service.update_model_status(tenant_id, provider_id_or_name, instance_id_or_name, model_name, status)
|
||||
success, msg = provider_api_service.update_model(
|
||||
tenant_id, provider_id_or_name, instance_id_or_name, model_name, update_dict
|
||||
)
|
||||
if success:
|
||||
return get_result(message=msg)
|
||||
else:
|
||||
|
||||
@@ -79,8 +79,8 @@ async def create_memory(memory_info: dict):
|
||||
"memory_type": list[str],
|
||||
"embd_id": str,
|
||||
"llm_id": str,
|
||||
"tenant_embd_id": str,
|
||||
"tenant_llm_id": str
|
||||
"tenant_embd_id": str | None,
|
||||
"tenant_llm_id": str | None
|
||||
}
|
||||
"""
|
||||
# check name length
|
||||
@@ -98,7 +98,15 @@ async def create_memory(memory_info: dict):
|
||||
if invalid_type:
|
||||
raise ArgumentException(f"Memory type '{invalid_type}' is not supported.")
|
||||
memory_type = list(memory_type)
|
||||
success, res = MemoryService.create_memory(tenant_id=current_user.id, name=memory_name, memory_type=memory_type, embd_id=memory_info["embd_id"], llm_id=memory_info["llm_id"])
|
||||
success, res = MemoryService.create_memory(
|
||||
tenant_id=current_user.id,
|
||||
name=memory_name,
|
||||
memory_type=memory_type,
|
||||
embd_id=memory_info["embd_id"],
|
||||
llm_id=memory_info["llm_id"],
|
||||
tenant_embd_id=memory_info.get("tenant_embd_id"),
|
||||
tenant_llm_id=memory_info.get("tenant_llm_id"),
|
||||
)
|
||||
if success:
|
||||
return True, format_ret_data_from_memory(res)
|
||||
else:
|
||||
|
||||
@@ -21,6 +21,7 @@ from api.db.services.tenant_model_instance_service import TenantModelInstanceSer
|
||||
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 api.utils.model_utils import get_model_type_human, calculate_model_type
|
||||
from common.constants import ActiveStatusEnum, LLMType
|
||||
from common.settings import FACTORY_LLM_INFOS
|
||||
|
||||
@@ -120,37 +121,13 @@ def _get_model_info(tenant_id: str, default_model: str, model_type: str):
|
||||
logging.warning(f"Instance '{instance_name}' not found for provider '{provider_name}'")
|
||||
return None
|
||||
|
||||
# Check if model is enabled (no TenantModel record or status != inactive means enabled)
|
||||
model_entity = TenantModelService.get_by_provider_id_and_instance_id_and_model_type_and_model_name(provider_obj.id, instance_obj.id, model_type, model_name)
|
||||
enable = model_entity is None or model_entity.status == ActiveStatusEnum.ACTIVE.value
|
||||
|
||||
if not enable:
|
||||
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}'")
|
||||
return None
|
||||
|
||||
if model_entity:
|
||||
return {
|
||||
"model_provider": provider_name,
|
||||
"model_instance": instance_name,
|
||||
"model_name": model_name,
|
||||
"model_type": model_type,
|
||||
"enable": enable,
|
||||
}
|
||||
|
||||
# Check if model is in the LLM factory info
|
||||
factory_info = [f for f in (FACTORY_LLM_INFOS or []) if f["name"] == provider_name]
|
||||
if not factory_info:
|
||||
logging.warning(f"Provider '{provider_name}' not found in factory info")
|
||||
return None
|
||||
|
||||
llms = factory_info[0].get("llm", [])
|
||||
target_llm = [llm for llm in llms if llm["llm_name"] == model_name]
|
||||
if not target_llm:
|
||||
logging.warning(f"Model '{model_name}' not found for provider '{provider_name}'")
|
||||
return None
|
||||
|
||||
# Check if the model_type matches
|
||||
if model_type not in _factory_model_types(target_llm[0]):
|
||||
logging.warning(f"Model '{model_name}' isn't a {model_type} model")
|
||||
if not model_record.status == ActiveStatusEnum.ACTIVE.value:
|
||||
logging.warning(f"Model '{model_name}' is disabled")
|
||||
return None
|
||||
|
||||
return {
|
||||
@@ -158,7 +135,7 @@ def _get_model_info(tenant_id: str, default_model: str, model_type: str):
|
||||
"model_instance": instance_name,
|
||||
"model_name": model_name,
|
||||
"model_type": model_type,
|
||||
"enable": enable,
|
||||
"enable": True
|
||||
}
|
||||
|
||||
|
||||
@@ -200,21 +177,11 @@ def _check_model_available(tenant_id: str, provider_name: str, instance_name: st
|
||||
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
|
||||
model_entity = TenantModelService.get_by_provider_id_and_instance_id_and_model_type_and_model_name(provider_obj.id, instance_obj.id, model_type, model_name)
|
||||
if model_entity:
|
||||
if model_entity.status != ActiveStatusEnum.ACTIVE.value:
|
||||
return False, f"Model '{model_name}' isn't available"
|
||||
return True, None
|
||||
|
||||
llms = factory_info[0].get("llm", [])
|
||||
target_llm = [llm for llm in llms if llm["llm_name"] == model_name]
|
||||
if not target_llm and not model_entity:
|
||||
return False, f"Model '{model_name}' not found for provider '{provider_name}'"
|
||||
|
||||
if target_llm:
|
||||
if model_type not in _factory_model_types(target_llm[0]):
|
||||
return False, f"Model '{model_name}' isn't a {model_type} model"
|
||||
|
||||
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"
|
||||
return True, None
|
||||
|
||||
|
||||
@@ -301,8 +268,7 @@ def list_tenant_added_models(tenant_id: str, model_type_filter: str = None):
|
||||
ensure_paddleocr_from_env(tenant_id)
|
||||
ensure_opendataloader_from_env(tenant_id)
|
||||
|
||||
if model_type_filter:
|
||||
model_type_filter = model_type_filter.lower()
|
||||
model_type_filter_bin = calculate_model_type(model_type_filter.lower()) if model_type_filter else None
|
||||
|
||||
providers = TenantModelProviderService.get_by_tenant_id(tenant_id)
|
||||
if not providers:
|
||||
@@ -314,6 +280,7 @@ def list_tenant_added_models(tenant_id: str, model_type_filter: str = None):
|
||||
return True, []
|
||||
provider_instance_map: dict = {}
|
||||
provider_info_map = {provider.id: provider for provider in providers}
|
||||
instance_info_map = {instance.id: instance for instance in instances}
|
||||
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):
|
||||
@@ -322,80 +289,17 @@ def list_tenant_added_models(tenant_id: str, model_type_filter: str = None):
|
||||
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}))
|
||||
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}"
|
||||
if model_record_map.get(instance_model_key):
|
||||
model_record_map[instance_model_key].append(model)
|
||||
else:
|
||||
model_record_map[instance_model_key] = [model]
|
||||
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
|
||||
|
||||
added_models = []
|
||||
model_key_in_factory = []
|
||||
provider_names = [provider.provider_name for provider in providers]
|
||||
factory_rank_mapping = {factory["name"]: -_to_int(factory.get("rank", "500")) for factory in FACTORY_LLM_INFOS}
|
||||
for factory in FACTORY_LLM_INFOS:
|
||||
if factory["name"] not in provider_names:
|
||||
continue
|
||||
factory_instances = provider_instance_map.get(factory["name"])
|
||||
if not factory_instances:
|
||||
continue
|
||||
for llm in factory["llm"]:
|
||||
factory_model_types = _factory_model_types(llm)
|
||||
if model_type_filter and model_type_filter not in factory_model_types:
|
||||
continue
|
||||
|
||||
for factory_instance in factory_instances:
|
||||
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]
|
||||
inactive_model_types = [manual_model.model_type for manual_model in manual_modified_models if manual_model.status == ActiveStatusEnum.INACTIVE.value]
|
||||
unsupport_model_types = [manual_model.model_type for manual_model in manual_modified_models if manual_model.status == ActiveStatusEnum.UNSUPPORTED.value]
|
||||
model_types = list(set(factory_model_types + active_model_types) - set(inactive_model_types) - set(unsupport_model_types))
|
||||
if not model_types:
|
||||
continue
|
||||
|
||||
added_models.append(
|
||||
{
|
||||
"model_type": model_types,
|
||||
"name": llm["llm_name"],
|
||||
"provider_id": factory_instance.provider_id,
|
||||
"provider_name": provider_info_map[factory_instance.provider_id].provider_name if provider_info_map.get(factory_instance.provider_id) else "",
|
||||
"instance_id": factory_instance.id,
|
||||
"instance_name": factory_instance.instance_name,
|
||||
}
|
||||
)
|
||||
|
||||
manual_added_model_record_keys = list(set(model_record_map.keys()) - set(model_key_in_factory))
|
||||
if manual_added_model_record_keys:
|
||||
instance_info_map = {instance.id: instance for instance in instances}
|
||||
for model_record_key in manual_added_model_record_keys:
|
||||
model_records = model_record_map.get(model_record_key, [])
|
||||
if not model_records:
|
||||
continue
|
||||
# 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
|
||||
|
||||
added_models.append(
|
||||
{
|
||||
"model_type": model_types,
|
||||
"name": model_name,
|
||||
"provider_id": provider_id,
|
||||
"provider_name": provider_info_map[provider_id].provider_name if provider_info_map.get(provider_id) else "",
|
||||
"instance_id": instance_id,
|
||||
"instance_name": instance_info_map[instance_id].instance_name if instance_info_map.get(instance_id) else "",
|
||||
}
|
||||
)
|
||||
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]
|
||||
|
||||
# Add TEI Builtin embedding model if configured
|
||||
compose_profiles = os.getenv("COMPOSE_PROFILES", "")
|
||||
@@ -415,6 +319,7 @@ def list_tenant_added_models(tenant_id: str, model_type_filter: str = None):
|
||||
}
|
||||
)
|
||||
|
||||
added_models.sort(key=lambda x: (factory_rank_mapping.get(x["provider_name"]), x["provider_name"], x["instance_name"]))
|
||||
added_models.sort(
|
||||
key=lambda x: (factory_rank_mapping.get(x["provider_name"]), x["provider_name"], x["instance_name"]))
|
||||
|
||||
return True, added_models
|
||||
|
||||
@@ -18,13 +18,13 @@ import json
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
from common.constants import LLMType, ActiveStatusEnum
|
||||
from common.misc_utils import get_uuid
|
||||
from common.constants import LLMType, ActiveStatusEnum, ModelVerifyStatusEnum
|
||||
from common.settings import FACTORY_LLM_INFOS
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance, delete_models_by_instance_ids, delete_instances_by_provider_ids
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance, delete_models_by_instance_ids, delete_instances_by_provider_ids, _decode_api_key_config
|
||||
from api.db.services.tenant_model_provider_service import TenantModelProviderService
|
||||
from api.db.services.tenant_model_instance_service import TenantModelInstanceService
|
||||
from api.db.services.tenant_model_service import TenantModelService
|
||||
from api.utils.model_utils import get_model_type_human, calculate_model_type
|
||||
from rag.llm import ChatModel, EmbeddingModel, ModelMeta, OcrModel, RerankModel, TTSModel
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ def list_providers(tenant_id: str, all_available: bool = False):
|
||||
List providers for a tenant.
|
||||
|
||||
If available_only is True, list all system-wide providers (pool providers).
|
||||
Otherwise, list providers that the tenant has configured.
|
||||
Otherwise, list providers that the tenant has configured, with a has_instance flag.
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param all_available: whether to list all available providers
|
||||
@@ -102,11 +102,13 @@ def list_providers(tenant_id: str, all_available: bool = False):
|
||||
for name in factory_names:
|
||||
if name not in ["Youdao", "FastEmbed", "BAAI", "Builtin", "siliconflow_intl"] and factory_info_mapping.get(name):
|
||||
factory_info = factory_info_mapping[name]
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, name)
|
||||
has_instance = bool(provider_obj and TenantModelInstanceService.get_all_by_provider_id(provider_obj.id))
|
||||
model_types = sorted(set(model_type for llm in factory_info.get("llm", []) for model_type in _factory_model_types(llm))) if factory_info.get("llm", []) else []
|
||||
if name in ["MinerU", "PaddleOCR", "OpenDataLoader"]:
|
||||
model_types.append("ocr")
|
||||
|
||||
provider = {"model_types": model_types, "name": factory_info["name"], "url": {"default": factory_info.get("url", "")}}
|
||||
provider = {"has_instance": has_instance, "model_types": model_types, "name": factory_info["name"], "url": {"default": factory_info.get("url", "")}}
|
||||
if factory_info["name"].lower() == "siliconflow":
|
||||
provider["url"]["intl"] = factory_info_map.get("siliconflow_intl", {}).get("url", "https://api.siliconflow.com/v1")
|
||||
elif factory_info["name"] == "Tongyi-Qianwen":
|
||||
@@ -199,7 +201,7 @@ async def list_provider_models(provider_id_or_name: str, api_key: str = None, ba
|
||||
static_llms = [
|
||||
{
|
||||
"name": _factory_llm_name(llm),
|
||||
"max_tokens": llm["max_tokens"],
|
||||
"max_tokens": llm.get("max_tokens", 8192),
|
||||
"model_types": _factory_model_types(llm),
|
||||
"features": (llm.get("features") if llm.get("features") is not None else ((["is_tools"] if llm.get("is_tools") else []) + (["thinking"] if llm.get("thinking") else []))),
|
||||
}
|
||||
@@ -255,6 +257,181 @@ def show_provider_model(provider_id_or_name: str, model_name: str):
|
||||
}
|
||||
|
||||
|
||||
async def update_provider_instance(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str, instance_name: str, api_key: str|dict, base_url: str, region: str, model_info: list[dict]=None, verify: bool=True):
|
||||
"""
|
||||
Update a provider instance.
|
||||
|
||||
Updates the instance's api_key, base_url, region, and re-creates all models
|
||||
based on the provided model_info list.
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param provider_id_or_name: provider/factory ID or name
|
||||
:param instance_id_or_name: instance ID or name
|
||||
:param instance_name: instance name (used as a logical identifier)
|
||||
:param api_key: API key
|
||||
:param base_url: base url
|
||||
:param region: region
|
||||
:param model_info: model info, [{
|
||||
"model_type": ["chat"], # support multiple
|
||||
"model_name": "name",
|
||||
"max_tokens": 4096,
|
||||
"extra": {
|
||||
"is_tools": True
|
||||
}
|
||||
}]
|
||||
:param verify: verify api_key
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
if not provider_id_or_name:
|
||||
return False, "Provider ID or name is required"
|
||||
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_id(tenant_id, provider_id_or_name)
|
||||
if not provider_obj:
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_id_or_name)
|
||||
if not provider_obj:
|
||||
return False, f"Provider '{provider_id_or_name}' does not exist"
|
||||
|
||||
provider_name = provider_obj.provider_name
|
||||
|
||||
# Find the instance
|
||||
instance_obj = None
|
||||
if instance_id_or_name:
|
||||
_, instance_obj = TenantModelInstanceService.get_by_id(instance_id_or_name)
|
||||
if instance_obj and instance_obj.provider_id != provider_obj.id:
|
||||
instance_obj = None
|
||||
if not instance_obj:
|
||||
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_id_or_name)
|
||||
if not instance_obj:
|
||||
return False, f"No instance found for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'"
|
||||
|
||||
base_url = _normalize_provider_base_url(provider_name, base_url)
|
||||
api_key = _normalize_provider_api_key(provider_name, api_key)
|
||||
|
||||
api_key_str = ""
|
||||
if api_key:
|
||||
api_key_str = api_key if isinstance(api_key, str) else json.dumps(api_key)
|
||||
|
||||
# Verify api_key
|
||||
model_verify_result = {}
|
||||
if verify:
|
||||
success, msg, model_verify_result = await verify_api_key(provider_name, api_key, base_url, region, model_info)
|
||||
if not success:
|
||||
return False, msg
|
||||
|
||||
# Update instance record
|
||||
update_dict = {
|
||||
"api_key": api_key_str,
|
||||
}
|
||||
if instance_name != instance_obj.instance_name:
|
||||
update_dict["instance_name"] = instance_name
|
||||
|
||||
extra_fields = {}
|
||||
if base_url:
|
||||
extra_fields["base_url"] = base_url
|
||||
if region:
|
||||
extra_fields["region"] = region
|
||||
# Preserve existing extra fields not overwritten
|
||||
existing_extra = json.loads(instance_obj.extra) if instance_obj.extra else {}
|
||||
existing_extra.update(extra_fields)
|
||||
update_dict["extra"] = json.dumps(existing_extra)
|
||||
TenantModelInstanceService.update_by_id(instance_obj.id, update_dict)
|
||||
|
||||
# Use the (possibly updated) instance_name for model recreation
|
||||
effective_instance_name = instance_name
|
||||
|
||||
# Upsert models: add new ones, update existing ones, remove ones no longer selected
|
||||
existing_model_objs = TenantModelService.get_models_by_instance_id(instance_obj.id)
|
||||
existing_model_names = {model_obj.model_name: model_obj for model_obj in existing_model_objs}
|
||||
|
||||
# Delete models that are no longer in the submitted model_info
|
||||
submitted_model_names = set()
|
||||
if model_info:
|
||||
submitted_model_names = {m.get("model_name") for m in model_info if m.get("model_name")}
|
||||
elif model_info is not None:
|
||||
# model_info is explicitly an empty list — remove all models
|
||||
submitted_model_names = set()
|
||||
models_to_remove = set(existing_model_names.keys()) - submitted_model_names
|
||||
if models_to_remove:
|
||||
TenantModelService.delete_by_ids([existing_model_names[n].id for n in models_to_remove])
|
||||
|
||||
msg = ""
|
||||
if model_info:
|
||||
for model in model_info:
|
||||
model_name = model.get("model_name")
|
||||
if not model_name:
|
||||
continue
|
||||
if verify:
|
||||
verify_status = model_verify_result.get(model_name, ModelVerifyStatusEnum.UNKNOWN.value)
|
||||
if model.get("extra"):
|
||||
model["extra"].update({"verify": verify_status})
|
||||
else:
|
||||
model["extra"] = {"verify": verify_status}
|
||||
|
||||
if model_name in existing_model_names:
|
||||
# Update existing model
|
||||
update_dict = {}
|
||||
if isinstance(model.get("model_type"), (str, list)):
|
||||
target_model_type = calculate_model_type(model["model_type"])
|
||||
if target_model_type != existing_model_names[model_name].model_type:
|
||||
update_dict["model_type"] = target_model_type
|
||||
merged_extra = json.loads(existing_model_names[model_name].extra) if existing_model_names[model_name].extra else {}
|
||||
merged_extra.update(model["extra"])
|
||||
if "max_tokens" in model:
|
||||
merged_extra.update({"max_tokens": model["max_tokens"]})
|
||||
update_dict["extra"] = json.dumps(merged_extra)
|
||||
if update_dict:
|
||||
TenantModelService.update_model(existing_model_names[model_name].id, update_dict)
|
||||
else:
|
||||
# Add new model
|
||||
success, _msg = add_model_to_instance(tenant_id, provider_name, effective_instance_name, **model)
|
||||
if not success:
|
||||
msg += _msg
|
||||
else:
|
||||
if model_info is None:
|
||||
# model_info not provided — add all factory default models (same as create)
|
||||
factory_info = [f for f in FACTORY_LLM_INFOS if f["name"] == provider_name]
|
||||
factory_llms = factory_info[0]["llm"]
|
||||
for llm in factory_llms:
|
||||
llm_name = _factory_llm_name(llm)
|
||||
if llm_name in existing_model_names:
|
||||
# Update existing
|
||||
update_dict = {}
|
||||
target_model_type = calculate_model_type(_factory_model_types(llm))
|
||||
if target_model_type != existing_model_names[llm_name].model_type:
|
||||
update_dict["model_type"] = target_model_type
|
||||
db_extra = json.loads(existing_model_names[llm_name].extra) if existing_model_names[llm_name].extra else {}
|
||||
db_extra_fields = {
|
||||
"max_tokens": llm["max_tokens"],
|
||||
"is_tools": llm.get("is_tools", False),
|
||||
"thinking": "thinking" in llm.get("features", []),
|
||||
}
|
||||
if verify:
|
||||
verify_status = model_verify_result.get(llm_name, ModelVerifyStatusEnum.UNKNOWN.value)
|
||||
db_extra_fields["verify"] = verify_status
|
||||
db_extra.update(db_extra_fields)
|
||||
update_dict["extra"] = json.dumps(db_extra)
|
||||
if update_dict:
|
||||
TenantModelService.update_model(existing_model_names[llm_name].id, update_dict)
|
||||
else:
|
||||
extra_fields = {
|
||||
"is_tools": llm.get("is_tools", False),
|
||||
"thinking": "thinking" in llm.get("features", []),
|
||||
}
|
||||
if verify:
|
||||
verify_status = model_verify_result.get(llm_name, ModelVerifyStatusEnum.UNKNOWN.value)
|
||||
extra_fields["verify"] = verify_status
|
||||
success, _msg = add_model_to_instance(tenant_id, provider_name, effective_instance_name, **{
|
||||
"model_type": _factory_model_types(llm),
|
||||
"model_name": llm_name,
|
||||
"max_tokens": llm["max_tokens"],
|
||||
"extra": extra_fields
|
||||
})
|
||||
if not success:
|
||||
msg += _msg
|
||||
|
||||
return True, "success"
|
||||
|
||||
|
||||
async def create_provider_instance(tenant_id: str, provider_id_or_name: str, instance_name: str, api_key: str | dict, base_url: str, region: str, model_info: list[dict] = None):
|
||||
"""
|
||||
Create a provider instance.
|
||||
@@ -305,17 +482,9 @@ async def create_provider_instance(tenant_id: str, provider_id_or_name: str, ins
|
||||
if api_key:
|
||||
api_key_str = api_key if isinstance(api_key, str) else json.dumps(api_key)
|
||||
|
||||
# Only verify when there are models to probe. Generic providers such as
|
||||
# "OpenAI-API-Compatible" may start empty and receive custom models later.
|
||||
factory_entry = next((f for f in FACTORY_LLM_INFOS if f["name"] == provider_name), None)
|
||||
if (factory_entry and factory_entry.get("llm")) or model_info:
|
||||
success, msg = await verify_api_key(provider_name, api_key, base_url, region, model_info)
|
||||
if not success:
|
||||
return False, msg
|
||||
|
||||
success, msg = await verify_api_key(provider_name, api_key, base_url, region, model_info)
|
||||
success, verify_msg, model_verify_result = await verify_api_key(provider_name, api_key, base_url, region, model_info)
|
||||
if not success:
|
||||
return False, msg
|
||||
return False, verify_msg
|
||||
|
||||
extra_fields = {}
|
||||
if base_url:
|
||||
@@ -326,15 +495,72 @@ async def create_provider_instance(tenant_id: str, provider_id_or_name: str, ins
|
||||
if model_info:
|
||||
msg = ""
|
||||
for model in model_info:
|
||||
if model.get("extra"):
|
||||
model["extra"].update({"verify": model_verify_result.get(model["model_name"], ModelVerifyStatusEnum.UNKNOWN.value)})
|
||||
else:
|
||||
model["extra"] = {"verify": model_verify_result.get(model["model_name"], ModelVerifyStatusEnum.UNKNOWN.value)}
|
||||
success, _msg = add_model_to_instance(tenant_id, provider_name, instance_name, **model)
|
||||
if not success:
|
||||
msg += _msg
|
||||
if msg:
|
||||
return False, msg
|
||||
else:
|
||||
msg = ""
|
||||
target_factory_name = "siliconflow_intl" if provider_name.lower() == "siliconflow" and region == "intl" else provider_name
|
||||
factory_info = [f for f in FACTORY_LLM_INFOS if f["name"] == target_factory_name]
|
||||
factory_llms = factory_info[0]["llm"]
|
||||
for llm in factory_llms:
|
||||
llm_name = _factory_llm_name(llm)
|
||||
success, _msg = add_model_to_instance(tenant_id, provider_name, instance_name, **{
|
||||
"model_type": _factory_model_types(llm),
|
||||
"model_name": llm_name,
|
||||
"max_tokens": llm["max_tokens"],
|
||||
"extra": {
|
||||
"is_tools": llm.get("is_tools", False),
|
||||
"thinking": "thinking" in llm.get("features", []),
|
||||
"verify": model_verify_result.get(llm_name, ModelVerifyStatusEnum.UNKNOWN.value)
|
||||
}
|
||||
})
|
||||
if not success:
|
||||
msg += _msg
|
||||
if msg:
|
||||
return False, msg
|
||||
|
||||
return True, "success"
|
||||
|
||||
|
||||
async def create_name_only_provider_instance(tenant_id: str, provider_name: str, instance_name: str):
|
||||
"""
|
||||
Create a provider instance with only a name (no api_key/base_url validation).
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param provider_name: provider/factory name
|
||||
:param instance_name: instance name (used as a logical identifier)
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
if not provider_name:
|
||||
return False, "Provider name is required"
|
||||
|
||||
if instance_name == "default":
|
||||
return False, "Instance name cannot be 'default'"
|
||||
|
||||
allowed_factories = [f["name"] for f in FACTORY_LLM_INFOS]
|
||||
if provider_name not in allowed_factories:
|
||||
return False, f"Provider '{provider_name}' is not allowed"
|
||||
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
if not provider_obj:
|
||||
return False, f"Provider '{provider_name}' does not exist"
|
||||
|
||||
TenantModelInstanceService.create_instance(
|
||||
provider_id=provider_obj.id,
|
||||
instance_name=instance_name,
|
||||
api_key="",
|
||||
extra=json.dumps({})
|
||||
)
|
||||
return True, "success"
|
||||
|
||||
|
||||
def list_provider_instances(tenant_id: str, provider_id_or_name: str):
|
||||
"""
|
||||
List provider instances for a tenant.
|
||||
@@ -388,7 +614,7 @@ async def verify_api_key(provider_id_or_name: str, api_key: str | dict, base_url
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
if not provider_id_or_name:
|
||||
return False, "Provider ID or name is required"
|
||||
return False, "Provider ID or name is required", {}
|
||||
|
||||
provider_obj = None
|
||||
if provider_id_or_name:
|
||||
@@ -405,24 +631,21 @@ async def verify_api_key(provider_id_or_name: str, api_key: str | dict, base_url
|
||||
|
||||
factory_info = [f for f in FACTORY_LLM_INFOS if f["name"] == target_factory_name]
|
||||
if not factory_info:
|
||||
return False, f"Provider '{provider_id_or_name}' not found"
|
||||
return False, f"Provider '{provider_id_or_name}' not found", {}
|
||||
|
||||
factory_llms = factory_info[0]["llm"]
|
||||
if not factory_llms:
|
||||
if not model_info:
|
||||
return False, f"No models found for provider '{provider_id_or_name}'"
|
||||
factory_llms = [
|
||||
{
|
||||
"model_type": _type,
|
||||
"llm_name": model.get("model_name", ""),
|
||||
}
|
||||
for model in model_info
|
||||
if model
|
||||
for _type in model.get("model_type", [])
|
||||
]
|
||||
if model_info:
|
||||
factory_llms = [{
|
||||
"model_type": _type,
|
||||
"llm_name": model.get("model_name", ""),
|
||||
} for model in model_info if model for _type in model.get("model_type", [])]
|
||||
if not factory_llms:
|
||||
return False, f"No valid models found for provider '{provider_id_or_name}'"
|
||||
return False, f"No valid models found for provider '{provider_id_or_name}'", {}
|
||||
else:
|
||||
factory_llms = factory_info[0]["llm"]
|
||||
if not factory_llms:
|
||||
return False, f"No models found for provider '{provider_id_or_name}'", {}
|
||||
|
||||
model_verify_result = {}
|
||||
# test if api key works
|
||||
chat_passed, embd_passed, rerank_passed, ocr_passed, tts_passed = False, False, False, False, False
|
||||
timeout_seconds = int(os.environ.get("LLM_TIMEOUT_SECONDS", 10))
|
||||
@@ -448,6 +671,7 @@ async def verify_api_key(provider_id_or_name: str, api_key: str | dict, base_url
|
||||
if len(arr[0]) == 0:
|
||||
raise Exception("Fail")
|
||||
embd_passed = True
|
||||
model_verify_result[llm["llm_name"]] = ModelVerifyStatusEnum.SUCCESS.value
|
||||
except Exception as e:
|
||||
logging.exception(
|
||||
"Fail to access embedding model for provider=%s model=%s",
|
||||
@@ -455,6 +679,7 @@ async def verify_api_key(provider_id_or_name: str, api_key: str | dict, base_url
|
||||
llm["llm_name"],
|
||||
)
|
||||
msg += f"\nFail to access embedding model({llm['llm_name']}) using this api key." + str(e)
|
||||
model_verify_result[llm["llm_name"]] = ModelVerifyStatusEnum.FAIL.value
|
||||
elif not chat_passed and LLMType.CHAT.value in model_types:
|
||||
assert provider_name in ChatModel, f"Chat model from {provider_name} is not supported yet."
|
||||
mdl = ChatModel[provider_name](api_key_str, llm["llm_name"], base_url=base_url, **extra)
|
||||
@@ -472,8 +697,10 @@ async def verify_api_key(provider_id_or_name: str, api_key: str | dict, base_url
|
||||
|
||||
result = await asyncio.wait_for(check_streamly(), timeout=timeout_seconds)
|
||||
if result:
|
||||
model_verify_result[llm["llm_name"]] = ModelVerifyStatusEnum.SUCCESS.value
|
||||
chat_passed = True
|
||||
else:
|
||||
model_verify_result[llm["llm_name"]] = ModelVerifyStatusEnum.FAIL.value
|
||||
raise Exception("No valid response received")
|
||||
except Exception as e:
|
||||
logging.exception(
|
||||
@@ -481,6 +708,7 @@ async def verify_api_key(provider_id_or_name: str, api_key: str | dict, base_url
|
||||
provider_name,
|
||||
llm["llm_name"],
|
||||
)
|
||||
model_verify_result[llm["llm_name"]] = ModelVerifyStatusEnum.FAIL.value
|
||||
msg += f"\nFail to access model({provider_name}/{llm['llm_name']}) using this api key." + str(e)
|
||||
elif not rerank_passed and LLMType.RERANK.value in model_types:
|
||||
if provider_name not in RerankModel:
|
||||
@@ -497,6 +725,7 @@ async def verify_api_key(provider_id_or_name: str, api_key: str | dict, base_url
|
||||
if len(arr) == 0 or tc == 0:
|
||||
raise Exception("Fail")
|
||||
rerank_passed = True
|
||||
model_verify_result[llm["llm_name"]] = ModelVerifyStatusEnum.SUCCESS.value
|
||||
logging.debug(f"passed model rerank {llm['llm_name']}")
|
||||
except Exception as e:
|
||||
logging.exception(
|
||||
@@ -504,6 +733,7 @@ async def verify_api_key(provider_id_or_name: str, api_key: str | dict, base_url
|
||||
provider_name,
|
||||
llm["llm_name"],
|
||||
)
|
||||
model_verify_result[llm["llm_name"]] = ModelVerifyStatusEnum.FAIL.value
|
||||
msg += f"\nFail to access model({provider_name}/{llm['llm_name']}) using this api key." + str(e)
|
||||
elif not ocr_passed and LLMType.OCR.value in model_types:
|
||||
assert provider_name in OcrModel, f"OCR model from {provider_name} is not supported yet."
|
||||
@@ -515,6 +745,7 @@ async def verify_api_key(provider_id_or_name: str, api_key: str | dict, base_url
|
||||
)
|
||||
if not ok:
|
||||
raise RuntimeError(reason or "Model not available")
|
||||
model_verify_result[llm["llm_name"]] = ModelVerifyStatusEnum.SUCCESS.value
|
||||
ocr_passed = True
|
||||
except Exception as e:
|
||||
logging.exception(
|
||||
@@ -522,6 +753,7 @@ async def verify_api_key(provider_id_or_name: str, api_key: str | dict, base_url
|
||||
provider_name,
|
||||
llm["llm_name"],
|
||||
)
|
||||
model_verify_result[llm["llm_name"]] = ModelVerifyStatusEnum.FAIL.value
|
||||
msg += f"\nFail to access model({provider_name}/{llm['llm_name']})." + str(e)
|
||||
elif not tts_passed and LLMType.TTS.value in model_types:
|
||||
assert provider_name in TTSModel, f"TTS model from {provider_name} is not supported yet."
|
||||
@@ -536,6 +768,7 @@ async def verify_api_key(provider_id_or_name: str, api_key: str | dict, base_url
|
||||
asyncio.to_thread(drain_tts),
|
||||
timeout=timeout_seconds,
|
||||
)
|
||||
model_verify_result[llm["llm_name"]] = ModelVerifyStatusEnum.SUCCESS.value
|
||||
tts_passed = True
|
||||
except Exception as e:
|
||||
logging.exception(
|
||||
@@ -543,13 +776,14 @@ async def verify_api_key(provider_id_or_name: str, api_key: str | dict, base_url
|
||||
provider_name,
|
||||
llm["llm_name"],
|
||||
)
|
||||
model_verify_result[llm["llm_name"]] = ModelVerifyStatusEnum.FAIL.value
|
||||
msg += f"\nFail to access model({provider_name}/{llm['llm_name']})." + str(e)
|
||||
if any([embd_passed, chat_passed, rerank_passed, ocr_passed, tts_passed]):
|
||||
msg = ""
|
||||
break
|
||||
|
||||
success = any([embd_passed, chat_passed, rerank_passed, ocr_passed, tts_passed])
|
||||
return success, "success" if success else msg
|
||||
return success, "success" if success else msg, model_verify_result
|
||||
|
||||
|
||||
def show_provider_instance(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str):
|
||||
@@ -578,7 +812,16 @@ def show_provider_instance(tenant_id: str, provider_id_or_name: str, instance_id
|
||||
return False, f"No instance found for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'"
|
||||
|
||||
extra_fields = json.loads(instance_obj.extra) if instance_obj.extra else {}
|
||||
return True, {"id": instance_obj.id, "instance_name": instance_obj.instance_name, "provider_id": provider_id, "region": extra_fields.get("region", ""), "status": instance_obj.status}
|
||||
|
||||
return True, {
|
||||
"id": instance_obj.id,
|
||||
"instance_name": instance_obj.instance_name,
|
||||
"provider_id": provider_id,
|
||||
"region": extra_fields.get("region", ""),
|
||||
"base_url": extra_fields.get("base_url", ""),
|
||||
"api_key": instance_obj.api_key,
|
||||
"status": instance_obj.status
|
||||
}
|
||||
|
||||
|
||||
def drop_provider_instances(tenant_id: str, provider_id_or_name: str, instance_id_or_names: list):
|
||||
@@ -618,55 +861,6 @@ def drop_provider_instances(tenant_id: str, provider_id_or_name: str, instance_i
|
||||
return True, None
|
||||
|
||||
|
||||
def _hybrid_get_instance_models(provider_name: str, instance_id: str):
|
||||
# List all models from the LLM dictionary for this provider
|
||||
factory_info = [f for f in FACTORY_LLM_INFOS if f["name"] == provider_name]
|
||||
if not factory_info:
|
||||
return False, f"Provider '{provider_name}' not found"
|
||||
|
||||
# Get model records for this instance from tenant_model table
|
||||
model_records = TenantModelService.get_models_by_instance_id(instance_id)
|
||||
# Build a map of model_name -> status, type
|
||||
model_info_map: dict = {}
|
||||
model_unsupported_type_map = {}
|
||||
for model_record in model_records:
|
||||
if model_record.status == ActiveStatusEnum.UNSUPPORTED.value:
|
||||
if model_unsupported_type_map.get(model_record.model_name):
|
||||
model_unsupported_type_map[model_record.model_name].append(model_record.model_type)
|
||||
else:
|
||||
model_unsupported_type_map[model_record.model_name] = [model_record.model_type]
|
||||
continue
|
||||
if model_info_map.get(model_record.model_name):
|
||||
model_info_map[model_record.model_name]["model_type"].append(model_record.model_type)
|
||||
else:
|
||||
model_info_map[model_record.model_name] = {"status": model_record.status, "model_type": [model_record.model_type], "extra": model_record.extra}
|
||||
|
||||
llms = factory_info[0].get("llm", [])
|
||||
models = []
|
||||
for llm in llms:
|
||||
models.append(
|
||||
{
|
||||
"name": llm["llm_name"],
|
||||
"model_type": list(set(_factory_model_types(llm) + model_info_map.get(llm["llm_name"], {}).get("model_type", [])) - set(model_unsupported_type_map.get(llm["llm_name"], []))),
|
||||
"max_tokens": llm.get("max_tokens"),
|
||||
"status": model_info_map.get(llm["llm_name"], {}).get("status", "active"),
|
||||
}
|
||||
)
|
||||
factory_models = [m["name"] for m in models]
|
||||
for model_name, model_info_dict in model_info_map.items():
|
||||
if model_name not in factory_models:
|
||||
extra_fields = json.loads(model_info_dict["extra"]) if model_info_dict["extra"] else {}
|
||||
models.append(
|
||||
{
|
||||
"name": model_name,
|
||||
"model_type": set(model_info_dict["model_type"]) - set(model_unsupported_type_map.get(model_name, [])),
|
||||
"max_tokens": extra_fields.get("max_tokens", 8192),
|
||||
"status": model_info_dict["status"],
|
||||
}
|
||||
)
|
||||
return True, models
|
||||
|
||||
|
||||
def list_instance_models(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str, supported_only: bool = False):
|
||||
"""
|
||||
List models for a provider instance.
|
||||
@@ -708,8 +902,21 @@ def list_instance_models(tenant_id: str, provider_id_or_name: str, instance_id_o
|
||||
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_id_or_name)
|
||||
if not instance_obj:
|
||||
return False, f"No instance found for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'"
|
||||
# Get models
|
||||
model_objs = TenantModelService.get_models_by_instance_id(instance_obj.id)
|
||||
model_list = []
|
||||
for model in model_objs:
|
||||
model_extra = json.loads(model.extra)
|
||||
model_list.append({
|
||||
"name": model.model_name,
|
||||
"model_type": get_model_type_human(model.model_type),
|
||||
"max_tokens": model_extra.get("max_tokens", 8192) if model.extra else 8192,
|
||||
"status": model.status,
|
||||
"verify": model_extra.get("verify", ModelVerifyStatusEnum.UNKNOWN.value),
|
||||
"features": (["is_tools"] if model_extra.get("is_tools") else []) + (["thinking"] if model_extra.get("thinking") else [])
|
||||
})
|
||||
|
||||
return _hybrid_get_instance_models(provider_obj.provider_name, instance_obj.id)
|
||||
return True, model_list
|
||||
|
||||
|
||||
def update_instance_models(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str, model_names: list, model_types: list):
|
||||
@@ -731,19 +938,16 @@ def update_instance_models(tenant_id: str, provider_id_or_name: str, instance_id
|
||||
if not instance_obj:
|
||||
return False, f"No instance found for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'"
|
||||
|
||||
found, models = _hybrid_get_instance_models(provider_obj.provider_name, instance_obj.id)
|
||||
if not found:
|
||||
return False, models
|
||||
|
||||
model_info_map = {model["name"]: model for model in models}
|
||||
not_exist_models = set(model_names) - set(model_info_map.keys())
|
||||
model_objs = TenantModelService.get_models_by_instance_id(instance_obj.id)
|
||||
not_exist_models = set(model_names) - {model_obj.model_name for model_obj in model_objs}
|
||||
if not_exist_models:
|
||||
return False, f"Models {not_exist_models} not found for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'"
|
||||
for model_name in model_names:
|
||||
model_info = model_info_map.get(model_name, {})
|
||||
TenantModelService.upsert_model_type(
|
||||
provider_obj.id, instance_obj.id, model_name, {"add": list(set(model_types) - set(model_info["model_type"])), "delete": list(set(model_info["model_type"]) - set(model_types))}
|
||||
)
|
||||
|
||||
target_model_type_bin = calculate_model_type(model_types)
|
||||
to_update = [model_obj.id for model_obj in model_objs if model_obj.model_type != target_model_type_bin and model_obj.model_name in model_names]
|
||||
if to_update:
|
||||
TenantModelService.batch_update_model_type(to_update, target_model_type_bin)
|
||||
|
||||
return True, "success"
|
||||
|
||||
|
||||
@@ -772,22 +976,26 @@ def add_model_to_instance(tenant_id: str, provider_id_or_name: str, instance_id_
|
||||
if isinstance(model_type, str):
|
||||
model_type = [model_type]
|
||||
|
||||
for _type in model_type:
|
||||
extra_fields = {"max_tokens": max_tokens}
|
||||
target_model = [llm for llm in llms if _type in _factory_model_types(llm) and llm["llm_name"] == model_name]
|
||||
if target_model:
|
||||
extra_fields.update({"is_tools": target_model[0].get("is_tools", False)})
|
||||
if extra:
|
||||
if provider_id_or_name == "SoMark" and LLMType.OCR.value in model_type:
|
||||
extra_fields["ocr_config"] = extra
|
||||
else:
|
||||
extra_fields.update(extra)
|
||||
TenantModelService.insert(model_name=model_name, provider_id=provider_obj.id, instance_id=instance_obj.id, model_type=_type, extra=json.dumps(extra_fields))
|
||||
model_type_bin = calculate_model_type(model_type)
|
||||
extra_fields = {"max_tokens": max_tokens}
|
||||
target_model = [llm for llm in llms if llm["llm_name"] == model_name]
|
||||
if target_model:
|
||||
extra_fields.update({"is_tools": target_model[0].get("is_tools", False)})
|
||||
extra_fields.update({"thinking": "thinking" in target_model[0].get("features", [])})
|
||||
if extra:
|
||||
extra_fields.update(extra)
|
||||
TenantModelService.insert(
|
||||
model_name=model_name,
|
||||
provider_id=provider_obj.id,
|
||||
instance_id=instance_obj.id,
|
||||
model_type=model_type_bin,
|
||||
extra=json.dumps(extra_fields)
|
||||
)
|
||||
|
||||
return True, "success"
|
||||
|
||||
|
||||
def update_model_status(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str, model_name: str, status: str):
|
||||
def update_model(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str, model_name: str, update_dict: dict):
|
||||
"""
|
||||
Enable or disable a model for a provider instance.
|
||||
|
||||
@@ -800,10 +1008,12 @@ def update_model_status(tenant_id: str, provider_id_or_name: str, instance_id_or
|
||||
:param provider_id_or_name: provider/factory ID or name
|
||||
:param instance_id_or_name: instance ID or name
|
||||
:param model_name: model name
|
||||
:param status: "active" or "inactive" (ActiveStatusEnum values)
|
||||
:param update_dict:
|
||||
status: "active" or "inactive" (ActiveStatusEnum values)
|
||||
max_tokens: > 0
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
if status not in (ActiveStatusEnum.ACTIVE.value, ActiveStatusEnum.INACTIVE.value):
|
||||
if update_dict.get("status") and update_dict["status"] not in (ActiveStatusEnum.ACTIVE.value, ActiveStatusEnum.INACTIVE.value):
|
||||
return False, f"status must be '{ActiveStatusEnum.ACTIVE.value}' or '{ActiveStatusEnum.INACTIVE.value}'"
|
||||
|
||||
# Check if provider exists for this tenant
|
||||
@@ -824,39 +1034,65 @@ def update_model_status(tenant_id: str, provider_id_or_name: str, instance_id_or
|
||||
if not instance_obj:
|
||||
return False, f"No instance found for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'"
|
||||
|
||||
# Check if model record already exists in tenant_model table
|
||||
model_obj_list = TenantModelService.get_by_provider_id_and_instance_id_and_model_name(provider_obj.id, instance_obj.id, model_name)
|
||||
model_obj = TenantModelService.get_by_provider_id_and_instance_id_and_model_name(provider_obj.id, instance_obj.id, model_name)
|
||||
to_update = {}
|
||||
if "status" in update_dict and update_dict.get("status") != model_obj.status:
|
||||
to_update.update({"status": update_dict["status"]})
|
||||
new_extra = update_dict.get("extra", {})
|
||||
if "max_tokens" in update_dict:
|
||||
new_extra.update({"max_tokens": update_dict["max_tokens"]})
|
||||
if "verify" in update_dict:
|
||||
new_extra.update({"verify": update_dict["verify"]})
|
||||
if new_extra:
|
||||
db_extra = json.loads(model_obj.extra)
|
||||
db_extra.update(**new_extra)
|
||||
to_update.update({"extra": json.dumps(db_extra)})
|
||||
if "model_type" in update_dict:
|
||||
target_model_type = calculate_model_type(update_dict["model_type"])
|
||||
if target_model_type != model_obj.model_type:
|
||||
to_update.update({"model_type": target_model_type})
|
||||
|
||||
if model_obj_list:
|
||||
# Model record exists — update its status
|
||||
TenantModelService.batch_update_model_status([m.id for m in model_obj_list if m.status != ActiveStatusEnum.UNSUPPORTED.value], status)
|
||||
else:
|
||||
# Model record does not exist
|
||||
if status == ActiveStatusEnum.ACTIVE.value:
|
||||
# Default is active, no need to add a record
|
||||
return True, None
|
||||
# status is "inactive" — create a record with inactive status
|
||||
# Look up model schema from FACTORY_LLM_INFOS
|
||||
factory_info = [f for f in FACTORY_LLM_INFOS if f["name"] == provider_obj.provider_name]
|
||||
if not factory_info:
|
||||
return False, f"Provider '{provider_id_or_name}' not found"
|
||||
llms = factory_info[0].get("llm", [])
|
||||
target_llm = [llm for llm in llms if llm["llm_name"] == model_name]
|
||||
if not target_llm:
|
||||
return False, f"provider {provider_obj.provider_name} model {model_name} not found"
|
||||
if to_update:
|
||||
TenantModelService.update_model(model_obj.id, to_update)
|
||||
|
||||
for model_type in _factory_model_types(target_llm[0]):
|
||||
TenantModelService.insert(
|
||||
id=get_uuid(),
|
||||
model_name=model_name,
|
||||
model_type=model_type,
|
||||
provider_id=provider_obj.id,
|
||||
instance_id=instance_obj.id,
|
||||
status=status,
|
||||
extra=json.dumps({"max_tokens": target_llm[0].get("max_tokens", 8192), "is_tools": target_llm[0].get("is_tools", False)}),
|
||||
)
|
||||
return True, "success"
|
||||
|
||||
return True, None
|
||||
|
||||
async def delete_models_from_instance(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str, model_name: list[str]):
|
||||
"""
|
||||
Delete models from instance.
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param provider_id_or_name: provider/factory ID or name
|
||||
:param instance_id_or_name: instance ID or name
|
||||
:param model_name: list of model name
|
||||
"""
|
||||
# Check if provider exists for this tenant (by ID first, then by name)
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_id(tenant_id, provider_id_or_name)
|
||||
if not provider_obj:
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_id_or_name)
|
||||
if not provider_obj:
|
||||
return False, f"No provider found for provider '{provider_id_or_name}'"
|
||||
|
||||
# Check if instance exists (by ID first, then by name)
|
||||
instance_obj = None
|
||||
if instance_id_or_name:
|
||||
_, instance_obj = TenantModelInstanceService.get_by_id(instance_id_or_name)
|
||||
if instance_obj and instance_obj.provider_id != provider_obj.id:
|
||||
instance_obj = None
|
||||
if not instance_obj:
|
||||
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_id_or_name)
|
||||
if not instance_obj:
|
||||
return False, f"No instance found for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'"
|
||||
|
||||
model_objs = TenantModelService.get_models_by_instance_id(instance_obj.id)
|
||||
not_exist_models = set(model_name) - {model_obj.model_name for model_obj in model_objs}
|
||||
if not_exist_models:
|
||||
return False, f"Models {not_exist_models} not found for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'"
|
||||
|
||||
TenantModelService.delete_by_ids([model_obj.id for model_obj in model_objs if model_obj.model_name in model_name])
|
||||
|
||||
return True, "success"
|
||||
|
||||
|
||||
async def chat_to_model(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str, model_name: str, message: str, stream: bool = False, thinking: bool = False):
|
||||
@@ -896,7 +1132,7 @@ async def chat_to_model(tenant_id: str, provider_id_or_name: str, instance_id_or
|
||||
# Get model config
|
||||
composite_name = f"{model_name}@{instance_name}@{provider_name}"
|
||||
try:
|
||||
model_config = get_model_config_from_provider_instance(tenant_id, LLMType.CHAT.value, composite_name)
|
||||
model_config = get_model_config_from_provider_instance(tenant_id, LLMType.CHAT, composite_name)
|
||||
except LookupError:
|
||||
return False, f"Model '{composite_name}' not authorized"
|
||||
|
||||
|
||||
@@ -725,18 +725,19 @@ class Tenant(DataBaseModel):
|
||||
name = CharField(max_length=100, null=True, help_text="Tenant name", index=True)
|
||||
public_key = CharField(max_length=255, null=True, index=True)
|
||||
llm_id = CharField(max_length=128, null=False, help_text="default llm ID", index=True)
|
||||
tenant_llm_id = IntegerField(null=True, help_text="id in tenant_llm", index=True)
|
||||
tenant_llm_id = CharField(max_length=32, null=True, help_text="id in tenant_model", index=True)
|
||||
embd_id = CharField(max_length=128, null=False, help_text="default embedding model ID", index=True)
|
||||
tenant_embd_id = IntegerField(null=True, help_text="id in tenant_llm", index=True)
|
||||
tenant_embd_id = CharField(max_length=32, null=True, help_text="id in tenant_model", index=True)
|
||||
asr_id = CharField(max_length=128, null=False, help_text="default ASR model ID", index=True)
|
||||
tenant_asr_id = IntegerField(null=True, help_text="id in tenant_llm", index=True)
|
||||
tenant_asr_id = CharField(max_length=32, null=True, help_text="id in tenant_model", index=True)
|
||||
img2txt_id = CharField(max_length=128, null=False, help_text="default image to text model ID", index=True)
|
||||
tenant_img2txt_id = IntegerField(null=True, help_text="id in tenant_llm", index=True)
|
||||
tenant_img2txt_id = CharField(max_length=32, null=True, help_text="id in tenant_model", index=True)
|
||||
rerank_id = CharField(max_length=128, null=False, help_text="default rerank model ID", index=True)
|
||||
tenant_rerank_id = IntegerField(null=True, help_text="id in tenant_llm", index=True)
|
||||
tenant_rerank_id = CharField(max_length=32, null=True, help_text="id in tenant_model", index=True)
|
||||
tts_id = CharField(max_length=256, null=True, help_text="default tts model ID", index=True)
|
||||
tenant_tts_id = IntegerField(null=True, help_text="id in tenant_llm", index=True)
|
||||
tenant_tts_id = CharField(max_length=32, null=True, help_text="id in tenant_model", index=True)
|
||||
ocr_id = CharField(max_length=256, null=True, help_text="default OCR model ID", index=True)
|
||||
tenant_ocr_id = CharField(max_length=32, null=True, help_text="id in tenant_model", index=True)
|
||||
parser_ids = CharField(max_length=256, null=False, help_text="document processors", index=True)
|
||||
credit = IntegerField(default=512, index=True)
|
||||
status = CharField(max_length=1, null=True, help_text="is it validate(0: wasted, 1: validate)", default="1", index=True)
|
||||
@@ -803,7 +804,6 @@ class LLM(DataBaseModel):
|
||||
|
||||
|
||||
class TenantLLM(DataBaseModel):
|
||||
id = PrimaryKeyField()
|
||||
tenant_id = CharField(max_length=32, null=False, index=True)
|
||||
llm_factory = CharField(max_length=128, null=False, help_text="LLM factory name", index=True)
|
||||
model_type = CharField(max_length=128, null=True, help_text="LLM, Text Embedding, Image2Text, ASR", index=True)
|
||||
@@ -843,7 +843,7 @@ class Knowledgebase(DataBaseModel):
|
||||
language = CharField(max_length=32, null=True, default="Chinese" if "zh_CN" in os.getenv("LANG", "") else "English", help_text="English|Chinese", index=True)
|
||||
description = TextField(null=True, help_text="KB description")
|
||||
embd_id = CharField(max_length=128, null=False, help_text="default embedding model ID", index=True)
|
||||
tenant_embd_id = IntegerField(null=True, help_text="id in tenant_llm", index=True)
|
||||
tenant_embd_id = CharField(max_length=32, null=True, help_text="id in tenant_model", index=True)
|
||||
permission = CharField(max_length=16, null=False, help_text="me|team", default="me", index=True)
|
||||
created_by = CharField(max_length=32, null=False, index=True)
|
||||
doc_num = IntegerField(default=0, index=True)
|
||||
@@ -1011,7 +1011,7 @@ class Dialog(DataBaseModel):
|
||||
icon = TextField(null=True, help_text="icon base64 string")
|
||||
language = CharField(max_length=32, null=True, default="Chinese" if "zh_CN" in os.getenv("LANG", "") else "English", help_text="English|Chinese", index=True)
|
||||
llm_id = CharField(max_length=128, null=False, help_text="default llm ID")
|
||||
tenant_llm_id = IntegerField(null=True, help_text="id in tenant_llm", index=True)
|
||||
tenant_llm_id = CharField(max_length=32, null=True, help_text="id in tenant_model", index=True)
|
||||
|
||||
llm_setting = JSONField(null=False, default={"temperature": 0.1, "top_p": 0.3, "frequency_penalty": 0.7, "presence_penalty": 0.4, "max_tokens": 512})
|
||||
prompt_type = CharField(max_length=16, null=False, default="simple", help_text="simple|advanced", index=True)
|
||||
@@ -1031,7 +1031,7 @@ class Dialog(DataBaseModel):
|
||||
do_refer = CharField(max_length=1, null=False, default="1", help_text="it needs to insert reference index into answer or not")
|
||||
|
||||
rerank_id = CharField(max_length=128, null=False, help_text="default rerank model ID")
|
||||
tenant_rerank_id = IntegerField(null=True, help_text="id in tenant_llm", index=True)
|
||||
tenant_rerank_id = CharField(max_length=32, null=True, help_text="id in tenant_model", index=True)
|
||||
kb_ids = JSONField(null=False, default=[])
|
||||
status = CharField(max_length=1, null=True, help_text="is it validate(0: wasted, 1: validate)", default="1", index=True)
|
||||
|
||||
@@ -1408,9 +1408,9 @@ class Memory(DataBaseModel):
|
||||
memory_type = IntegerField(null=False, default=1, index=True, help_text="Bit flags (LSB->MSB): 1=raw, 2=semantic, 4=episodic, 8=procedural. E.g., 5 enables raw + episodic.")
|
||||
storage_type = CharField(max_length=32, default="table", null=False, index=True, help_text="table|graph")
|
||||
embd_id = CharField(max_length=128, null=False, index=False, help_text="embedding model ID")
|
||||
tenant_embd_id = IntegerField(null=True, help_text="id in tenant_llm", index=True)
|
||||
tenant_embd_id = CharField(max_length=32, null=True, help_text="id in tenant_model", index=True)
|
||||
llm_id = CharField(max_length=128, null=False, index=False, help_text="chat model ID")
|
||||
tenant_llm_id = IntegerField(null=True, help_text="id in tenant_llm", index=True)
|
||||
tenant_llm_id = CharField(max_length=32, null=True, help_text="id in tenant_model", index=True)
|
||||
permissions = CharField(max_length=16, null=False, index=True, help_text="me|team", default="me")
|
||||
description = TextField(null=True, help_text="description")
|
||||
memory_size = IntegerField(default=5242880, null=False, index=False)
|
||||
@@ -1460,7 +1460,7 @@ class TenantModel(DataBaseModel):
|
||||
model_name = CharField(max_length=128, null=True, index=False, help_text="Model name")
|
||||
provider_id = CharField(max_length=32, null=False, index=False)
|
||||
instance_id = CharField(max_length=32, null=False, index=True)
|
||||
model_type = CharField(max_length=32, null=False, index=False, help_text="Model type")
|
||||
model_type = IntegerField(null=False, default=1, index=True, help_text="Bit flags (LSB->MSB): 1=chat, 2=embedding, 4=speech2text, 8=image2text, 16=rerank, 32=tts, 64=ocr")
|
||||
status = CharField(max_length=32, default="active", index=False)
|
||||
extra = CharField(max_length=1024, default="{}", index=False)
|
||||
|
||||
@@ -1790,18 +1790,6 @@ def migrate_db():
|
||||
# Migrate system_settings.value from CharField to TextField for longer sandbox configs
|
||||
alter_db_column_type(migrator, "system_settings", "value", TextField(null=False, help_text="Configuration value (JSON, string, etc.)"))
|
||||
alter_db_add_column(migrator, "document", "content_hash", CharField(max_length=32, null=True, help_text="xxhash128 of document content for change detection", default="", index=True))
|
||||
update_tenant_llm_to_id_primary_key()
|
||||
alter_db_add_column(migrator, "tenant", "tenant_llm_id", IntegerField(null=True, help_text="id in tenant_llm", index=True))
|
||||
alter_db_add_column(migrator, "tenant", "tenant_embd_id", IntegerField(null=True, help_text="id in tenant_llm", index=True))
|
||||
alter_db_add_column(migrator, "tenant", "tenant_asr_id", IntegerField(null=True, help_text="id in tenant_llm", index=True))
|
||||
alter_db_add_column(migrator, "tenant", "tenant_img2txt_id", IntegerField(null=True, help_text="id in tenant_llm", index=True))
|
||||
alter_db_add_column(migrator, "tenant", "tenant_rerank_id", IntegerField(null=True, help_text="id in tenant_llm", index=True))
|
||||
alter_db_add_column(migrator, "tenant", "tenant_tts_id", IntegerField(null=True, help_text="id in tenant_llm", index=True))
|
||||
alter_db_add_column(migrator, "knowledgebase", "tenant_embd_id", IntegerField(null=True, help_text="id in tenant_llm", index=True))
|
||||
alter_db_add_column(migrator, "dialog", "tenant_llm_id", IntegerField(null=True, help_text="id in tenant_llm", index=True))
|
||||
alter_db_add_column(migrator, "dialog", "tenant_rerank_id", IntegerField(null=True, help_text="id in tenant_llm", index=True))
|
||||
alter_db_add_column(migrator, "memory", "tenant_embd_id", IntegerField(null=True, help_text="id in tenant_llm", index=True))
|
||||
alter_db_add_column(migrator, "memory", "tenant_llm_id", IntegerField(null=True, help_text="id in tenant_llm", index=True))
|
||||
alter_db_add_column(migrator, "user_canvas_version", "release", BooleanField(null=False, help_text="is released", default=False, index=True))
|
||||
alter_db_add_column(migrator, "user_canvas", "tags", CharField(max_length=512, null=False, default="", help_text="Comma-separated tags for organizing agents", index=True))
|
||||
alter_db_add_column(migrator, "api_4_conversation", "version_title", CharField(max_length=255, null=True, help_text="canvas version title when session created", index=False))
|
||||
|
||||
@@ -27,7 +27,7 @@ from api.db.db_models import Task
|
||||
from api.db.services.task_service import TaskService
|
||||
from api.db.services.memory_service import MemoryService
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance, get_model_config_by_id
|
||||
from api.utils.memory_utils import get_memory_type_human
|
||||
from memory.services.messages import MessageService
|
||||
from memory.services.query import MsgTextQuery, get_vector
|
||||
@@ -155,18 +155,8 @@ async def save_extracted_to_memory_only(memory_id: str, message_dict, source_mes
|
||||
return await embed_and_save(memory, message_list, task_id)
|
||||
|
||||
|
||||
async def extract_by_llm(
|
||||
tenant_id: str,
|
||||
tenant_llm_id: int,
|
||||
extract_conf: dict,
|
||||
memory_type: List[str],
|
||||
user_input: str,
|
||||
agent_response: str,
|
||||
system_prompt: str = "",
|
||||
user_prompt: str = "",
|
||||
task_id: str = None,
|
||||
llm_id: str = "",
|
||||
) -> List[dict]:
|
||||
async def extract_by_llm(tenant_id: str, tenant_llm_id: str | None, extract_conf: dict, memory_type: List[str], user_input: str,
|
||||
agent_response: str, system_prompt: str = "", user_prompt: str="", task_id: str=None, llm_id: str = "") -> List[dict]:
|
||||
if not system_prompt:
|
||||
system_prompt = PromptAssembler.assemble_system_prompt({"memory_type": memory_type})
|
||||
conversation_content = f"User Input: {user_input}\nAgent Response: {agent_response}"
|
||||
@@ -177,7 +167,13 @@ async def extract_by_llm(
|
||||
user_prompts.append({"role": "user", "content": f"Conversation: {conversation_content}\nConversation Time: {conversation_time}\nCurrent Time: {conversation_time}"})
|
||||
else:
|
||||
user_prompts.append({"role": "user", "content": PromptAssembler.assemble_user_prompt(conversation_content, conversation_time, conversation_time)})
|
||||
llm_config = get_model_config_from_provider_instance(tenant_id, LLMType.CHAT, llm_id)
|
||||
if tenant_llm_id:
|
||||
try:
|
||||
llm_config = get_model_config_by_id(tenant_id, tenant_llm_id)
|
||||
except LookupError:
|
||||
llm_config = get_model_config_from_provider_instance(tenant_id, LLMType.CHAT, llm_id)
|
||||
else:
|
||||
llm_config = get_model_config_from_provider_instance(tenant_id, LLMType.CHAT, llm_id)
|
||||
with LLMBundle(tenant_id, llm_config) as llm:
|
||||
if task_id:
|
||||
TaskService.update_progress(task_id, {"progress": 0.15, "progress_msg": timestamp_to_date(current_timestamp()) + " " + "Prepared prompts and LLM."})
|
||||
@@ -197,8 +193,14 @@ async def extract_by_llm(
|
||||
]
|
||||
|
||||
|
||||
async def embed_and_save(memory, message_list: list[dict], task_id: str = None):
|
||||
embd_model_config = get_model_config_from_provider_instance(memory.tenant_id, LLMType.EMBEDDING, memory.embd_id)
|
||||
async def embed_and_save(memory, message_list: list[dict], task_id: str=None):
|
||||
if memory.tenant_embd_id:
|
||||
try:
|
||||
embd_model_config = get_model_config_by_id(memory.tenant_id, memory.tenant_embd_id)
|
||||
except LookupError:
|
||||
embd_model_config = get_model_config_from_provider_instance(memory.tenant_id, LLMType.EMBEDDING, memory.embd_id)
|
||||
else:
|
||||
embd_model_config = get_model_config_from_provider_instance(memory.tenant_id, LLMType.EMBEDDING, memory.embd_id)
|
||||
with LLMBundle(memory.tenant_id, embd_model_config) as embedding_model:
|
||||
if task_id:
|
||||
TaskService.update_progress(task_id, {"progress": 0.65, "progress_msg": timestamp_to_date(current_timestamp()) + " " + "Prepared embedding model."})
|
||||
|
||||
@@ -21,6 +21,7 @@ from common import settings
|
||||
from common.constants import (
|
||||
ActiveStatusEnum,
|
||||
LLMType,
|
||||
ModelTypeBinary,
|
||||
MINERU_DEFAULT_CONFIG,
|
||||
MINERU_ENV_KEYS,
|
||||
OPENDATALOADER_DEFAULT_CONFIG,
|
||||
@@ -34,6 +35,7 @@ from api.db.services.tenant_llm_service import TenantService
|
||||
from api.db.services.tenant_model_provider_service import TenantModelProviderService
|
||||
from api.db.services.tenant_model_instance_service import TenantModelInstanceService
|
||||
from api.db.services.tenant_model_service import TenantModelService
|
||||
from api.utils.model_utils import calculate_model_type, get_model_type_human
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -78,7 +80,7 @@ def _decode_api_key_config(raw_api_key: str) -> tuple[str, bool | None, str | No
|
||||
|
||||
|
||||
def get_first_provider_model_name(tenant_id: str, provider_name: str, model_type: str | enum.Enum) -> str | None:
|
||||
model_type_val = model_type if isinstance(model_type, str) else model_type.value
|
||||
model_type_bin = calculate_model_type(model_type)
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
if not provider_obj:
|
||||
return None
|
||||
@@ -87,7 +89,7 @@ def get_first_provider_model_name(tenant_id: str, provider_name: str, model_type
|
||||
if instance_obj.status != ActiveStatusEnum.ACTIVE.value:
|
||||
continue
|
||||
for model_obj in TenantModelService.get_models_by_instance_id(instance_obj.id):
|
||||
if model_obj.model_type == model_type_val and model_obj.status == ActiveStatusEnum.ACTIVE.value:
|
||||
if model_obj.model_type & model_type_bin and model_obj.status == ActiveStatusEnum.ACTIVE.value:
|
||||
return f"{model_obj.model_name}@{instance_obj.instance_name}@{provider_name}"
|
||||
return None
|
||||
|
||||
@@ -133,7 +135,7 @@ def _ensure_ocr_provider_from_env(tenant_id: str, provider_name: str, model_name
|
||||
model_name=model_name,
|
||||
provider_id=provider_obj.id,
|
||||
instance_id=instance_obj.id,
|
||||
model_type=LLMType.OCR.value,
|
||||
model_type=ModelTypeBinary.OCR.value,
|
||||
extra=json.dumps({"max_tokens": 0}),
|
||||
)
|
||||
|
||||
@@ -163,19 +165,26 @@ def get_tenant_default_model_by_type(tenant_id: str, model_type: str | enum.Enum
|
||||
if not exist:
|
||||
raise LookupError("Tenant not found")
|
||||
model_type_val = model_type if isinstance(model_type, str) else model_type.value
|
||||
model_id: str | None = None
|
||||
model_name: str = ""
|
||||
match model_type_val:
|
||||
case LLMType.EMBEDDING.value:
|
||||
model_id = tenant.tenant_embd_id
|
||||
model_name = tenant.embd_id
|
||||
case LLMType.SPEECH2TEXT.value:
|
||||
model_id = tenant.tenant_asr_id
|
||||
model_name = tenant.asr_id
|
||||
case LLMType.IMAGE2TEXT.value:
|
||||
model_id = tenant.tenant_img2txt_id
|
||||
model_name = tenant.img2txt_id
|
||||
case LLMType.CHAT.value:
|
||||
model_id = tenant.tenant_llm_id
|
||||
model_name = tenant.llm_id
|
||||
case LLMType.RERANK.value:
|
||||
model_id = tenant.tenant_rerank_id
|
||||
model_name = tenant.rerank_id
|
||||
case LLMType.TTS.value:
|
||||
model_id = tenant.tenant_tts_id
|
||||
model_name = tenant.tts_id
|
||||
case LLMType.OCR.value:
|
||||
raise Exception("OCR model name is required")
|
||||
@@ -183,6 +192,12 @@ def get_tenant_default_model_by_type(tenant_id: str, model_type: str | enum.Enum
|
||||
raise Exception(f"Unknown model type {model_type}")
|
||||
if not model_name:
|
||||
raise Exception(f"No default {model_type} model is set.")
|
||||
# Prefer resolving by tenant_model.id when available
|
||||
if model_id:
|
||||
try:
|
||||
return get_model_config_by_id(tenant_id, model_id)
|
||||
except LookupError:
|
||||
logger.warning("tenant_model id=%s not found, falling back to model_name lookup for %s", model_id, model_name)
|
||||
return get_model_config_from_provider_instance(tenant_id, model_type, model_name)
|
||||
|
||||
|
||||
@@ -280,7 +295,7 @@ def get_model_config_from_provider_instance(tenant_id, model_type: str | enum.En
|
||||
"api_key": api_key,
|
||||
"llm_name": model_obj.model_name,
|
||||
"api_base": extra_fields.get("base_url", ""),
|
||||
"model_type": model_obj.model_type,
|
||||
"model_type": model_type_val,
|
||||
"is_tools": model_extra.get("is_tools", is_tool),
|
||||
"max_tokens": max_tokens,
|
||||
}
|
||||
@@ -294,32 +309,116 @@ def get_model_config_from_provider_instance(tenant_id, model_type: str | enum.En
|
||||
|
||||
return model_config
|
||||
else:
|
||||
region = extra_fields.get("region", "default")
|
||||
if region == "intl" and provider_name.lower() == "siliconflow":
|
||||
target_factory_name = "siliconflow_intl"
|
||||
else:
|
||||
target_factory_name = provider_name
|
||||
fac_list = [f for f in settings.FACTORY_LLM_INFOS if f["name"] == target_factory_name]
|
||||
if not fac_list:
|
||||
raise LookupError(f"Model provider config not found: {provider_name}")
|
||||
llm_list = [llm for llm in fac_list[0]["llm"] if llm["llm_name"] == pure_model_name]
|
||||
if not llm_list:
|
||||
raise LookupError(f"Instance {instance_name} not found for model {model_name}.")
|
||||
llm_info = llm_list[0]
|
||||
if model_type_val not in _factory_model_types(llm_info):
|
||||
raise LookupError(f"Model {model_name} is not a {model_type_val} model.")
|
||||
model_config = {
|
||||
"llm_factory": provider_obj.provider_name,
|
||||
"api_key": api_key,
|
||||
"llm_name": llm_info["llm_name"],
|
||||
"api_base": extra_fields.get("base_url", ""),
|
||||
"model_type": model_type_val,
|
||||
"is_tools": llm_info.get("is_tools", is_tool),
|
||||
"max_tokens": llm_info.get("max_tokens") or 8192,
|
||||
}
|
||||
if api_key_payload is not None:
|
||||
model_config["api_key_payload"] = api_key_payload
|
||||
return model_config
|
||||
raise LookupError(f"Model {model_name} not found for model {model_type_val}")
|
||||
|
||||
|
||||
def get_model_config_by_id(tenant_id: str, model_id: str):
|
||||
"""Get model config from tenant_model by its id (CharField PK)."""
|
||||
exist, model_obj = TenantModelService.get_by_id(model_id)
|
||||
if not exist:
|
||||
raise LookupError(f"TenantModel id={model_id} not found.")
|
||||
if model_obj.status != ActiveStatusEnum.ACTIVE.value:
|
||||
raise LookupError(f"TenantModel id={model_id} is disabled.")
|
||||
|
||||
provider_obj = TenantModelProviderService.get_by_id(model_obj.provider_id)
|
||||
if not provider_obj:
|
||||
raise LookupError(f"Provider id={model_obj.provider_id} not found for model id={model_id}.")
|
||||
|
||||
# Validate that tenant_id owns the provider or is a joined tenant of the provider's owner.
|
||||
if tenant_id != provider_obj.tenant_id:
|
||||
joined_tenants = TenantService.get_joined_tenants_by_user_id(tenant_id)
|
||||
joined_tenant_ids = [t["tenant_id"] for t in joined_tenants]
|
||||
if provider_obj.tenant_id not in joined_tenant_ids:
|
||||
raise LookupError(f"Tenant {tenant_id} has no access to provider owned by tenant {provider_obj.tenant_id}.")
|
||||
|
||||
instance_obj = TenantModelInstanceService.get_by_id(model_obj.instance_id)
|
||||
if not instance_obj:
|
||||
raise LookupError(f"Instance id={model_obj.instance_id} not found for model id={model_id}.")
|
||||
|
||||
api_key, is_tool, api_key_payload = _decode_api_key_config(instance_obj.api_key)
|
||||
extra_fields = json.loads(instance_obj.extra) if instance_obj.extra else {}
|
||||
model_extra = json.loads(model_obj.extra) if model_obj.extra else {}
|
||||
|
||||
model_config = {
|
||||
"llm_factory": provider_obj.provider_name,
|
||||
"api_key": api_key,
|
||||
"llm_name": model_obj.model_name,
|
||||
"api_base": extra_fields.get("base_url", ""),
|
||||
"model_type": model_obj.model_type,
|
||||
"is_tools": model_extra.get("is_tools", is_tool),
|
||||
"max_tokens": model_extra.get("max_tokens") or 8192,
|
||||
}
|
||||
if provider_obj.provider_name.lower() == "somark":
|
||||
model_config["extra"] = model_extra
|
||||
|
||||
if api_key_payload is not None:
|
||||
model_config["api_key_payload"] = api_key_payload
|
||||
|
||||
return model_config
|
||||
|
||||
|
||||
def resolve_model_id(tenant_id: str, model_type: str | enum.Enum, model_name: str) -> str | None:
|
||||
"""Given a tenant_id, model_type and model_name (e.g. 'model@instance@provider'),
|
||||
look up the corresponding tenant_model.id. Returns None if not found."""
|
||||
pure_model_name, instance_name, provider_name = split_model_name(model_name)
|
||||
model_type_val = model_type if isinstance(model_type, str) else model_type.value
|
||||
|
||||
# Builtin TEI embedding — no tenant_model row exists
|
||||
compose_profiles = os.getenv("COMPOSE_PROFILES", "")
|
||||
is_tei_builtin_embedding = (
|
||||
model_type_val == LLMType.EMBEDDING.value and "tei-" in compose_profiles and pure_model_name == os.getenv("TEI_MODEL", "") and (provider_name == "Builtin" or not provider_name)
|
||||
)
|
||||
if is_tei_builtin_embedding:
|
||||
return None
|
||||
|
||||
if not provider_name:
|
||||
raise LookupError(f"Provider name is required to resolve model id for {model_name}.")
|
||||
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
if not provider_obj:
|
||||
raise LookupError(f"Provider {provider_name} not found for model {model_name}.")
|
||||
|
||||
instance_obj = _resolve_instance_for_model(provider_obj, instance_name, model_name)
|
||||
model_obj = TenantModelService.get_by_provider_id_and_instance_id_and_model_type_and_model_name(
|
||||
provider_obj.id, instance_obj.id, model_type_val, pure_model_name
|
||||
)
|
||||
if not model_obj:
|
||||
raise LookupError(f"Model {model_name} not found for type {model_type_val}.")
|
||||
return model_obj.id
|
||||
|
||||
|
||||
# Mapping from model-name field → (LLMType, tenant_model id field)
|
||||
_MODEL_NAME_TO_ID_FIELD_MAP: dict[str, tuple[str, str]] = {
|
||||
"llm_id": (LLMType.CHAT, "tenant_llm_id"),
|
||||
"embd_id": (LLMType.EMBEDDING, "tenant_embd_id"),
|
||||
"rerank_id": (LLMType.RERANK, "tenant_rerank_id"),
|
||||
"asr_id": (LLMType.SPEECH2TEXT, "tenant_asr_id"),
|
||||
"img2txt_id": (LLMType.IMAGE2TEXT, "tenant_img2txt_id"),
|
||||
"tts_id": (LLMType.TTS, "tenant_tts_id"),
|
||||
}
|
||||
|
||||
|
||||
def ensure_tenant_model_ids_for_params(tenant_id: str, params: dict) -> dict:
|
||||
"""For each model-name field present in *params*, resolve the corresponding
|
||||
tenant_model id if the id field is not already present.
|
||||
|
||||
Modifies *params* in-place (adds ``tenant_*_id`` keys) and returns it.
|
||||
Silently skips resolution when the model is not found in tenant_model
|
||||
(e.g. builtin TEI embedding).
|
||||
|
||||
Typical usage at API entry points:
|
||||
|
||||
req = await get_request_json()
|
||||
ensure_tenant_model_ids_for_params(current_user.id, req)
|
||||
# req now has tenant_llm_id / tenant_embd_id etc. filled in
|
||||
"""
|
||||
for name_field, (model_type, id_field) in _MODEL_NAME_TO_ID_FIELD_MAP.items():
|
||||
if name_field in params and id_field not in params:
|
||||
try:
|
||||
params[id_field] = resolve_model_id(tenant_id, model_type, params[name_field])
|
||||
except LookupError:
|
||||
logger.debug("Could not resolve %s → %s for tenant %s, skipping", name_field, id_field, tenant_id)
|
||||
return params
|
||||
|
||||
|
||||
def get_api_key(tenant_id: str, model_name: str):
|
||||
@@ -339,27 +438,13 @@ def get_model_type_by_name(tenant_id: str, model_name: str):
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
if not provider_obj:
|
||||
raise LookupError(f"Provider {provider_name} not found for model {model_name}.")
|
||||
instance_obj = _resolve_instance_for_model(provider_obj, instance_name, model_name)
|
||||
model_objs = TenantModelService.get_by_provider_id_and_instance_id_and_model_name(provider_obj.id, instance_obj.id, pure_model_name)
|
||||
types_in_json = []
|
||||
if not model_objs:
|
||||
extra_fields = json.loads(instance_obj.extra) if instance_obj.extra else {}
|
||||
region = extra_fields.get("region", "default")
|
||||
if region == "intl" and provider_name.lower() == "siliconflow":
|
||||
target_factory_name = "siliconflow_intl"
|
||||
else:
|
||||
target_factory_name = provider_name
|
||||
fac_list = [f for f in settings.FACTORY_LLM_INFOS if f["name"] == target_factory_name]
|
||||
if not fac_list:
|
||||
raise LookupError(f"Model provider config not found: {provider_name}")
|
||||
llm_list = [llm for llm in fac_list[0]["llm"] if llm["llm_name"] == pure_model_name]
|
||||
if not llm_list:
|
||||
raise LookupError(f"Model {pure_model_name} not found for model {model_name}.")
|
||||
types_in_json = _factory_model_types(llm_list[0])
|
||||
return list(
|
||||
set(types_in_json + [model_obj.model_type for model_obj in model_objs if model_obj.status != ActiveStatusEnum.UNSUPPORTED.value])
|
||||
- {model_obj.model_type for model_obj in model_objs if model_obj.status == ActiveStatusEnum.UNSUPPORTED.value}
|
||||
)
|
||||
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name)
|
||||
if not instance_obj:
|
||||
raise LookupError(f"Instance {instance_name} not found for model {model_name}.")
|
||||
model_obj = TenantModelService.get_by_provider_id_and_instance_id_and_model_name(provider_obj.id, instance_obj.id, pure_model_name)
|
||||
if not model_obj:
|
||||
raise LookupError(f"Model {model_name} not found.")
|
||||
return get_model_type_human(model_obj.model_type)
|
||||
|
||||
|
||||
def delete_models_by_instance_ids(instance_ids: list[str]):
|
||||
@@ -386,23 +471,3 @@ def ensure_somark_from_env(tenant_id: str) -> str | None:
|
||||
"somark-from-env",
|
||||
_collect_env_config(SOMARK_ENV_KEYS, SOMARK_DEFAULT_CONFIG),
|
||||
)
|
||||
|
||||
|
||||
def get_models_by_tenant_and_provider_and_model_type(tenant_id: str, provider_name: str, model_type: str):
|
||||
"""
|
||||
Query TenantModel records by tenant_id, provider_name and model_name.
|
||||
Returns all matching model records under all instances of the specified provider.
|
||||
"""
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
if not provider_obj:
|
||||
return []
|
||||
instances = TenantModelInstanceService.get_all_by_provider_id(provider_obj.id)
|
||||
if not instances:
|
||||
return []
|
||||
results = []
|
||||
for inst in instances:
|
||||
models = TenantModelService.get_by_provider_id_and_instance_id_and_model_type(provider_obj.id, inst.id, model_type)
|
||||
supported = [model for model in models if model.status != ActiveStatusEnum.UNSUPPORTED.value]
|
||||
if supported:
|
||||
results.extend(supported)
|
||||
return results
|
||||
|
||||
@@ -39,7 +39,7 @@ from api.utils.reference_metadata_utils import (
|
||||
enrich_chunks_with_document_metadata,
|
||||
resolve_reference_metadata_preferences,
|
||||
)
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_from_provider_instance, get_model_type_by_name
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_from_provider_instance, get_model_type_by_name, get_model_config_by_id
|
||||
from common.time_utils import current_timestamp, datetime_format
|
||||
from common.text_utils import normalize_arabic_digits
|
||||
from rag.advanced_rag.knowlege_compile.mind_map_extractor import MindMapExtractor
|
||||
@@ -293,11 +293,25 @@ async def async_chat_solo(dialog, messages, stream=True, session_id=None):
|
||||
image_files = []
|
||||
|
||||
if dialog.llm_id:
|
||||
llm_types = get_model_type_by_name(dialog.tenant_id, dialog.llm_id)
|
||||
if "chat" in llm_types:
|
||||
model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.CHAT, dialog.llm_id)
|
||||
if dialog.tenant_llm_id:
|
||||
try:
|
||||
llm_types = get_model_type_by_name(dialog.tenant_id, dialog.llm_id)
|
||||
if "chat" in llm_types:
|
||||
model_config = get_model_config_by_id(dialog.tenant_id, dialog.tenant_llm_id)
|
||||
else:
|
||||
model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.IMAGE2TEXT, dialog.llm_id)
|
||||
except LookupError:
|
||||
llm_types = get_model_type_by_name(dialog.tenant_id, dialog.llm_id)
|
||||
if "chat" in llm_types:
|
||||
model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.CHAT, dialog.llm_id)
|
||||
else:
|
||||
model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.IMAGE2TEXT, dialog.llm_id)
|
||||
else:
|
||||
model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.IMAGE2TEXT, dialog.llm_id)
|
||||
llm_types = get_model_type_by_name(dialog.tenant_id, dialog.llm_id)
|
||||
if "chat" in llm_types:
|
||||
model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.CHAT, dialog.llm_id)
|
||||
else:
|
||||
model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.IMAGE2TEXT, dialog.llm_id)
|
||||
else:
|
||||
model_config = get_tenant_default_model_by_type(dialog.tenant_id, LLMType.CHAT)
|
||||
|
||||
@@ -356,14 +370,26 @@ def get_models(dialog, trace_context=None, langfuse_session_id=None):
|
||||
raise LookupError("Embedding model(%s) not found" % embedding_list[0])
|
||||
|
||||
if dialog.llm_id:
|
||||
chat_model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.CHAT, dialog.llm_id)
|
||||
if dialog.tenant_llm_id:
|
||||
try:
|
||||
chat_model_config = get_model_config_by_id(dialog.tenant_id, dialog.tenant_llm_id)
|
||||
except LookupError:
|
||||
chat_model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.CHAT, dialog.llm_id)
|
||||
else:
|
||||
chat_model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.CHAT, dialog.llm_id)
|
||||
else:
|
||||
chat_model_config = get_tenant_default_model_by_type(dialog.tenant_id, LLMType.CHAT)
|
||||
|
||||
chat_mdl = LLMBundle(dialog.tenant_id, chat_model_config, trace_context=trace_context, langfuse_session_id=langfuse_session_id)
|
||||
|
||||
if dialog.rerank_id:
|
||||
rerank_model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.RERANK, dialog.rerank_id)
|
||||
if dialog.tenant_rerank_id:
|
||||
try:
|
||||
rerank_model_config = get_model_config_by_id(dialog.tenant_id, dialog.tenant_rerank_id)
|
||||
except LookupError:
|
||||
rerank_model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.RERANK, dialog.rerank_id)
|
||||
else:
|
||||
rerank_model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.RERANK, dialog.rerank_id)
|
||||
rerank_mdl = LLMBundle(dialog.tenant_id, rerank_model_config, trace_context=trace_context, langfuse_session_id=langfuse_session_id)
|
||||
|
||||
if dialog.prompt_config.get("tts"):
|
||||
@@ -555,11 +581,25 @@ async def async_chat(dialog, messages, stream=True, **kwargs):
|
||||
|
||||
chat_start_ts = timer()
|
||||
if dialog.llm_id:
|
||||
llm_types = get_model_type_by_name(dialog.tenant_id, dialog.llm_id)
|
||||
if "chat" in llm_types:
|
||||
llm_model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.CHAT, dialog.llm_id)
|
||||
if dialog.tenant_llm_id:
|
||||
try:
|
||||
llm_types = get_model_type_by_name(dialog.tenant_id, dialog.llm_id)
|
||||
if "chat" in llm_types:
|
||||
llm_model_config = get_model_config_by_id(dialog.tenant_id, dialog.tenant_llm_id)
|
||||
else:
|
||||
llm_model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.IMAGE2TEXT, dialog.llm_id)
|
||||
except LookupError:
|
||||
llm_types = get_model_type_by_name(dialog.tenant_id, dialog.llm_id)
|
||||
if "chat" in llm_types:
|
||||
llm_model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.CHAT, dialog.llm_id)
|
||||
else:
|
||||
llm_model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.IMAGE2TEXT, dialog.llm_id)
|
||||
else:
|
||||
llm_model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.IMAGE2TEXT, dialog.llm_id)
|
||||
llm_types = get_model_type_by_name(dialog.tenant_id, dialog.llm_id)
|
||||
if "chat" in llm_types:
|
||||
llm_model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.CHAT, dialog.llm_id)
|
||||
else:
|
||||
llm_model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.IMAGE2TEXT, dialog.llm_id)
|
||||
else:
|
||||
llm_model_config = get_tenant_default_model_by_type(dialog.tenant_id, LLMType.CHAT)
|
||||
|
||||
|
||||
@@ -109,7 +109,8 @@ class MemoryService(CommonService):
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def create_memory(cls, tenant_id: str, name: str, memory_type: List[str], embd_id: str, llm_id: str):
|
||||
def create_memory(cls, tenant_id: str, name: str, memory_type: List[str], embd_id: str, llm_id: str,
|
||||
tenant_embd_id: str | None = None, tenant_llm_id: str | None = None):
|
||||
# Deduplicate name within tenant
|
||||
memory_name = duplicate_name(cls.query, name=name, tenant_id=tenant_id)
|
||||
if len(memory_name) > MEMORY_NAME_LIMIT:
|
||||
@@ -124,7 +125,9 @@ class MemoryService(CommonService):
|
||||
"memory_type": calculate_memory_type(memory_type),
|
||||
"tenant_id": tenant_id,
|
||||
"embd_id": embd_id,
|
||||
"tenant_embd_id": tenant_embd_id,
|
||||
"llm_id": llm_id,
|
||||
"tenant_llm_id": tenant_llm_id,
|
||||
"system_prompt": PromptAssembler.assemble_system_prompt({"memory_type": memory_type}),
|
||||
"create_time": timestamp,
|
||||
"create_date": format_time,
|
||||
|
||||
@@ -197,11 +197,13 @@ class TaskService(CommonService):
|
||||
Knowledgebase.tenant_id,
|
||||
Knowledgebase.language,
|
||||
Knowledgebase.embd_id,
|
||||
Knowledgebase.tenant_embd_id,
|
||||
Knowledgebase.pagerank,
|
||||
Knowledgebase.parser_config.alias("kb_parser_config"),
|
||||
Tenant.img2txt_id,
|
||||
Tenant.asr_id,
|
||||
Tenant.llm_id,
|
||||
Tenant.tenant_llm_id,
|
||||
cls.model.update_time,
|
||||
]
|
||||
docs = (
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from common.constants import ActiveStatusEnum
|
||||
from api.db.db_models import DB, TenantModel
|
||||
from api.db.services.common_service import CommonService
|
||||
from api.utils.model_utils import calculate_model_type
|
||||
|
||||
|
||||
class TenantModelService(CommonService):
|
||||
@@ -24,17 +24,30 @@ class TenantModelService(CommonService):
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_by_provider_id_and_instance_id_and_model_name(cls, provider_id, instance_id, model_name):
|
||||
return list(cls.model.select().where(cls.model.provider_id == provider_id, cls.model.instance_id == instance_id, cls.model.model_name == model_name))
|
||||
return cls.model.get_or_none(cls.model.provider_id == provider_id, cls.model.instance_id == instance_id, cls.model.model_name == model_name)
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_by_provider_id_and_instance_id_and_model_type_and_model_name(cls, provider_id, instance_id, model_type, model_name):
|
||||
return cls.model.get_or_none(cls.model.provider_id == provider_id, cls.model.instance_id == instance_id, cls.model.model_type == model_type, cls.model.model_name == model_name)
|
||||
if isinstance(model_type, str):
|
||||
model_type = calculate_model_type([model_type])
|
||||
return cls.model.get_or_none(
|
||||
cls.model.provider_id == provider_id,
|
||||
cls.model.instance_id == instance_id,
|
||||
cls.model.model_type.bin_and(model_type) > 0,
|
||||
cls.model.model_name == model_name
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_by_provider_id_and_instance_id_and_model_type(cls, provider_id, instance_id, model_type):
|
||||
return cls.model.get_or_none(cls.model.provider_id == provider_id, cls.model.instance_id == instance_id, cls.model.model_type == model_type)
|
||||
if isinstance(model_type, str):
|
||||
model_type = calculate_model_type([model_type])
|
||||
return cls.model.get_or_none(
|
||||
cls.model.provider_id == provider_id,
|
||||
cls.model.instance_id == instance_id,
|
||||
cls.model.model_type.bin_and(model_type) > 0
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
@@ -53,33 +66,17 @@ class TenantModelService(CommonService):
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def upsert_model_type(cls, provider_id: str, instance_id: str, model_name: str, operation: dict):
|
||||
model_type_records = cls.model.select().where(cls.model.provider_id == provider_id, cls.model.instance_id == instance_id, cls.model.model_name == model_name)
|
||||
if not model_type_records:
|
||||
for _type in operation.get("add", []):
|
||||
cls.insert(model_name=model_name, provider_id=provider_id, instance_id=instance_id, model_type=_type, extra="{}")
|
||||
for _type in operation.get("delete", []):
|
||||
cls.insert(model_name=model_name, provider_id=provider_id, instance_id=instance_id, model_type=_type, status=ActiveStatusEnum.UNSUPPORTED, extra="{}")
|
||||
return len(operation.get("add", [])) + len(operation.get("delete", []))
|
||||
model_record_example = [model_record for model_record in model_type_records if model_record.status != ActiveStatusEnum.UNSUPPORTED.value]
|
||||
extra_fields = model_record_example[0].extra if model_record_example else "{}"
|
||||
model_status = model_record_example[0].status if model_record_example else ActiveStatusEnum.ACTIVE.value
|
||||
type_record_map = {record.model_type: record for record in model_type_records}
|
||||
operated_cnt = 0
|
||||
for _type in operation.get("add", []):
|
||||
if type_record_map.get(_type):
|
||||
cls.update_by_id(type_record_map[_type].id, {"status": model_status})
|
||||
def update_model(cls, model_id, update_dict):
|
||||
return cls.model.update(**update_dict).where(cls.model.id == model_id).execute()
|
||||
|
||||
else:
|
||||
cls.insert(model_name=model_name, provider_id=provider_id, instance_id=instance_id, model_type=_type, status=model_status, extra=extra_fields)
|
||||
operated_cnt += 1
|
||||
for _type in operation.get("delete", []):
|
||||
if type_record_map.get(_type):
|
||||
cls.update_by_id(type_record_map[_type].id, {"status": ActiveStatusEnum.UNSUPPORTED.value})
|
||||
else:
|
||||
cls.insert(model_name=model_name, provider_id=provider_id, instance_id=instance_id, model_type=_type, status=ActiveStatusEnum.UNSUPPORTED.value, extra=extra_fields)
|
||||
operated_cnt += 1
|
||||
return operated_cnt
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def batch_update_model_type(cls, model_ids, model_type):
|
||||
if isinstance(model_type, str):
|
||||
model_type = calculate_model_type([model_type])
|
||||
elif isinstance(model_type, list):
|
||||
model_type = calculate_model_type(model_type)
|
||||
return cls.model.update(model_type=model_type).where(cls.model.id.in_(model_ids)).execute()
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
|
||||
33
api/utils/model_utils.py
Normal file
33
api/utils/model_utils.py
Normal file
@@ -0,0 +1,33 @@
|
||||
#
|
||||
# Copyright 2025 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.
|
||||
#
|
||||
from typing import List
|
||||
|
||||
from common.constants import ModelTypeBinary
|
||||
|
||||
|
||||
def get_model_type_human(model_type: int) -> List[str]:
|
||||
return [mt.name.lower() for mt in ModelTypeBinary if model_type & mt.value]
|
||||
|
||||
|
||||
def calculate_model_type(model_type_name_list: List[str]|str) -> int:
|
||||
model_type = 0
|
||||
if isinstance(model_type_name_list, str):
|
||||
model_type_name_list = [model_type_name_list]
|
||||
type_value_map = {mt.name.lower(): mt.value for mt in ModelTypeBinary}
|
||||
for mt in model_type_name_list:
|
||||
if mt in type_value_map:
|
||||
model_type |= type_value_map[mt]
|
||||
return model_type
|
||||
@@ -942,7 +942,7 @@ class SearchDatasetReq(BaseModel):
|
||||
keyword: Annotated[bool, Field(default=False)]
|
||||
search_id: Annotated[str | None, Field(default=None)]
|
||||
rerank_id: Annotated[str | None, Field(default=None)]
|
||||
tenant_rerank_id: Annotated[int | None, Field(default=None)]
|
||||
tenant_rerank_id: Annotated[str | None, Field(default=None)]
|
||||
meta_data_filter: Annotated[dict | None, Field(default=None)]
|
||||
|
||||
|
||||
@@ -964,7 +964,7 @@ class SearchDatasetsReq(BaseModel):
|
||||
keyword: Annotated[bool, Field(default=False)]
|
||||
search_id: Annotated[str | None, Field(default=None)]
|
||||
rerank_id: Annotated[str | None, Field(default=None)]
|
||||
tenant_rerank_id: Annotated[int | None, Field(default=None)]
|
||||
tenant_rerank_id: Annotated[str | None, Field(default=None)]
|
||||
meta_data_filter: Annotated[dict | None, Field(default=None)]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user