Feat: v0.27.0 model provider (#16604)

This commit is contained in:
Lynn
2026-07-08 09:47:29 +08:00
committed by GitHub
parent cb93883f3f
commit 0ae5961e1c
94 changed files with 7539 additions and 3044 deletions

View File

@@ -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))

View File

@@ -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."})

View File

@@ -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

View File

@@ -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)

View File

@@ -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,

View File

@@ -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 = (

View File

@@ -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()