mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-11-13 14:51:12 +08:00
Feat: tenant llm provider (#14595)
### What problem does this PR solve? Python implementation of the Go-based model_provider API suite. ### Type of change - [x] New Feature (non-breaking change which adds functionality) --------- Co-authored-by: bill <yibie_jingnian@163.com>
This commit is contained in:
1 parent
b79f79d9b9
commit
dc4b82523b
148 files changed
+6059
-3075
No files matched your search
@@ -189,6 +189,7 @@ COPY mcp mcp
|
||||
COPY common common
|
||||
COPY memory memory
|
||||
COPY bin bin
|
||||
COPY tools/scripts tools/scripts
|
||||
|
||||
COPY docker/service_conf.yaml.template ./conf/service_conf.yaml.template
|
||||
COPY docker/entrypoint.sh ./
|
||||
|
||||
@@ -27,10 +27,9 @@ import json_repair
|
||||
|
||||
from agent.component.llm import LLM, LLMParam
|
||||
from agent.tools.base import LLMToolPluginCallSession, ToolBase, ToolMeta, ToolParamBase
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_type_and_name
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance, get_model_type_by_name
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.services.mcp_server_service import MCPServerService
|
||||
from api.db.services.tenant_llm_service import TenantLLMService
|
||||
from common.connection_utils import timeout
|
||||
from common.mcp_tool_call_conn import MCPToolBinding, MCPToolCallSession, mcp_tool_metadata_to_openai_tool
|
||||
from rag.prompts.generator import citation_plus, citation_prompt, full_question, kb_prompt, message_fit_in, structured_output_prompt
|
||||
@@ -81,7 +80,9 @@ class Agent(LLM, ToolBase):
|
||||
original_name = cpn.get_meta()["function"]["name"]
|
||||
indexed_name = f"{original_name}_{idx}"
|
||||
self.tools[indexed_name] = cpn
|
||||
chat_model_config = get_model_config_by_type_and_name(self._canvas.get_tenant_id(), TenantLLMService.llm_id2llm_type(self._param.llm_id), self._param.llm_id)
|
||||
model_types = get_model_type_by_name(self._canvas.get_tenant_id(), self._param.llm_id)
|
||||
model_type = "chat" if "chat" in model_types else model_types[0]
|
||||
chat_model_config = get_model_config_from_provider_instance(self._canvas.get_tenant_id(), model_type, self._param.llm_id)
|
||||
self.chat_mdl = LLMBundle(
|
||||
self._canvas.get_tenant_id(),
|
||||
chat_model_config,
|
||||
|
||||
@@ -33,10 +33,9 @@ from urllib.request import Request, urlopen
|
||||
from agent.component.base import ComponentBase
|
||||
from agent.component.llm import LLMParam
|
||||
from api.db import FileType
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_type_and_name
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance, get_model_type_by_name
|
||||
from api.db.services import duplicate_name
|
||||
from api.db.services.file_service import FileService
|
||||
from api.db.services.tenant_llm_service import TenantLLMService
|
||||
from api.utils.file_utils import filename_type
|
||||
from common import settings
|
||||
from common.connection_utils import timeout
|
||||
@@ -394,9 +393,9 @@ class Browser(ComponentBase, ABC):
|
||||
def _build_browser_llm(self):
|
||||
from browser_use.llm import ChatBrowserUse, ChatOpenAI
|
||||
|
||||
chat_model_config = get_model_config_by_type_and_name(
|
||||
chat_model_config = get_model_config_from_provider_instance(
|
||||
self._canvas.get_tenant_id(),
|
||||
TenantLLMService.llm_id2llm_type(self._param.llm_id),
|
||||
get_model_type_by_name(self._param.llm_id),
|
||||
self._param.llm_id,
|
||||
)
|
||||
cfg = self._as_model_config_dict(chat_model_config)
|
||||
|
||||
@@ -21,7 +21,7 @@ from abc import ABC
|
||||
|
||||
from common.constants import LLMType
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_type_and_name
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance
|
||||
from agent.component.llm import LLMParam, LLM
|
||||
from common.connection_utils import timeout
|
||||
from rag.llm.chat_model import ERROR_PREFIX
|
||||
@@ -123,7 +123,7 @@ class Categorize(LLM, ABC):
|
||||
msg[-1]["content"] = query_value
|
||||
self.set_input_value(query_key, msg[-1]["content"])
|
||||
self._param.update_prompt()
|
||||
chat_model_config = get_model_config_by_type_and_name(self._canvas.get_tenant_id(), LLMType.CHAT, self._param.llm_id)
|
||||
chat_model_config = get_model_config_from_provider_instance(self._canvas.get_tenant_id(), LLMType.CHAT, self._param.llm_id)
|
||||
chat_mdl = LLMBundle(self._canvas.get_tenant_id(), chat_model_config)
|
||||
|
||||
user_prompt = """
|
||||
|
||||
@@ -24,8 +24,7 @@ import json_repair
|
||||
from functools import partial
|
||||
from common.constants import LLMType
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.services.tenant_llm_service import TenantLLMService
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_type_and_name
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance, get_model_type_by_name
|
||||
from agent.component.base import ComponentBase, ComponentParamBase
|
||||
from common.connection_utils import timeout
|
||||
from rag.prompts.generator import tool_call_summary, message_fit_in, citation_prompt, structured_output_prompt
|
||||
@@ -85,7 +84,9 @@ class LLM(ComponentBase):
|
||||
|
||||
def __init__(self, canvas, component_id, param: ComponentParamBase):
|
||||
super().__init__(canvas, component_id, param)
|
||||
chat_model_config = get_model_config_by_type_and_name(self._canvas.get_tenant_id(), TenantLLMService.llm_id2llm_type(self._param.llm_id), self._param.llm_id)
|
||||
model_types = get_model_type_by_name(self._canvas.get_tenant_id(), self._param.llm_id)
|
||||
model_type = "chat" if "chat" in model_types else model_types[0]
|
||||
chat_model_config = get_model_config_from_provider_instance(self._canvas.get_tenant_id(), model_type, self._param.llm_id)
|
||||
self.chat_mdl = LLMBundle(self._canvas.get_tenant_id(), chat_model_config,
|
||||
max_retries=self._param.max_retries,
|
||||
retry_interval=self._param.delay_after_error)
|
||||
@@ -247,9 +248,11 @@ class LLM(ComponentBase):
|
||||
self.set_input_value(k, args[k])
|
||||
|
||||
self.imgs = self._uniq_images(self.imgs + extracted_imgs)
|
||||
if self.imgs and TenantLLMService.llm_id2llm_type(self._param.llm_id) == LLMType.CHAT.value:
|
||||
self.chat_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.IMAGE2TEXT.value,
|
||||
self._param.llm_id, max_retries=self._param.max_retries,
|
||||
model_types = get_model_type_by_name(self._canvas.get_tenant_id(), self._param.llm_id)
|
||||
model_type = LLMType.CHAT.value if LLMType.CHAT.value in model_types else model_types[0]
|
||||
model_config = get_model_config_from_provider_instance(self._canvas.get_tenant_id(), model_type, self._param.llm_id)
|
||||
if self.imgs:
|
||||
self.chat_mdl = LLMBundle(self._canvas.get_tenant_id(), model_config, max_retries=self._param.max_retries,
|
||||
retry_interval=self._param.delay_after_error
|
||||
)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.services.memory_service import MemoryService
|
||||
from api.db.joint_services import memory_message_service
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_type_and_name, get_tenant_default_model_by_type
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_from_provider_instance
|
||||
from common import settings
|
||||
from common.connection_utils import timeout
|
||||
from rag.app.tag import label_question
|
||||
@@ -121,12 +121,12 @@ class Retrieval(ToolBase, ABC):
|
||||
embd_mdl = None
|
||||
if embd_nms:
|
||||
tenant_id = self._canvas.get_tenant_id()
|
||||
embd_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.EMBEDDING, embd_nms[0])
|
||||
embd_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.EMBEDDING, embd_nms[0])
|
||||
embd_mdl = LLMBundle(tenant_id, embd_model_config)
|
||||
|
||||
rerank_mdl = None
|
||||
if self._param.rerank_id:
|
||||
rerank_model_config = get_model_config_by_type_and_name(kbs[0].tenant_id, LLMType.RERANK, self._param.rerank_id)
|
||||
rerank_model_config = get_model_config_from_provider_instance(kbs[0].tenant_id, LLMType.RERANK, self._param.rerank_id)
|
||||
rerank_mdl = LLMBundle(kbs[0].tenant_id, rerank_model_config)
|
||||
|
||||
vars = self.get_input_elements_from_text(query_text)
|
||||
|
||||
@@ -35,8 +35,7 @@ from api.db.services.llm_service import LLMBundle
|
||||
from common.metadata_utils import apply_meta_data_filter
|
||||
from api.db.services.search_service import SearchService
|
||||
from api.db.services.user_service import UserTenantService
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_by_id, \
|
||||
get_model_config_by_type_and_name
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_from_provider_instance
|
||||
from common.misc_utils import get_uuid, thread_pool_exec
|
||||
from api.utils.api_utils import check_duplicate_ids, get_error_data_result, get_json_result, \
|
||||
get_result, get_request_json, server_error_response, token_required, validate_request
|
||||
@@ -435,7 +434,6 @@ async def retrieval_test_embedded():
|
||||
return get_error_data_result("`top_k` must be greater than 0")
|
||||
langs = req.get("cross_languages", [])
|
||||
rerank_id = req.get("rerank_id", "")
|
||||
tenant_rerank_id = req.get("tenant_rerank_id", "")
|
||||
tenant_id = objs[0].tenant_id
|
||||
if not tenant_id:
|
||||
return get_error_data_result(message="permission denined.")
|
||||
@@ -458,7 +456,7 @@ async def retrieval_test_embedded():
|
||||
if meta_data_filter.get("method") in ["auto", "semi_auto"]:
|
||||
chat_id = search_config.get("chat_id", "")
|
||||
if chat_id:
|
||||
chat_model_config = await thread_pool_exec(get_model_config_by_type_and_name, tenant_id, LLMType.CHAT, chat_id)
|
||||
chat_model_config = await thread_pool_exec(get_model_config_from_provider_instance, tenant_id, LLMType.CHAT, chat_id)
|
||||
else:
|
||||
chat_model_config = await thread_pool_exec(get_tenant_default_model_by_type, tenant_id, LLMType.CHAT)
|
||||
chat_mdl = LLMBundle(tenant_id, chat_model_config)
|
||||
@@ -504,24 +502,12 @@ async def retrieval_test_embedded():
|
||||
|
||||
if langs:
|
||||
_question = await cross_languages(kb.tenant_id, None, _question, langs)
|
||||
if kb.tenant_embd_id:
|
||||
embd_model_config = await thread_pool_exec(get_model_config_by_id, kb.tenant_embd_id)
|
||||
else:
|
||||
embd_model_config = await thread_pool_exec(get_model_config_by_type_and_name, kb.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
embd_model_config = await thread_pool_exec(get_model_config_from_provider_instance, kb.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
embd_mdl = LLMBundle(kb.tenant_id, embd_model_config)
|
||||
|
||||
rerank_mdl = None
|
||||
if tenant_rerank_id:
|
||||
allowed_rerank_tenant_ids = {tenant_id, *tenant_ids}
|
||||
rerank_model_config = await thread_pool_exec(
|
||||
get_model_config_by_id,
|
||||
tenant_rerank_id,
|
||||
allowed_rerank_tenant_ids,
|
||||
tenant_id,
|
||||
)
|
||||
rerank_mdl = LLMBundle(kb.tenant_id, rerank_model_config)
|
||||
elif rerank_id:
|
||||
rerank_model_config = await thread_pool_exec(get_model_config_by_type_and_name, tenant_id, LLMType.RERANK, rerank_id)
|
||||
if rerank_id:
|
||||
rerank_model_config = await thread_pool_exec(get_model_config_from_provider_instance, tenant_id, LLMType.RERANK, rerank_id)
|
||||
rerank_mdl = LLMBundle(kb.tenant_id, rerank_model_config)
|
||||
|
||||
if req.get("keyword", False):
|
||||
@@ -586,7 +572,7 @@ async def related_questions_embedded():
|
||||
|
||||
chat_id = search_config.get("chat_id", "")
|
||||
if chat_id:
|
||||
chat_model_config = await thread_pool_exec(get_model_config_by_type_and_name, tenant_id, LLMType.CHAT, chat_id)
|
||||
chat_model_config = await thread_pool_exec(get_model_config_from_provider_instance, tenant_id, LLMType.CHAT, chat_id)
|
||||
else:
|
||||
chat_model_config = await thread_pool_exec(get_tenant_default_model_by_type, tenant_id, LLMType.CHAT)
|
||||
chat_mdl = LLMBundle(tenant_id, chat_model_config)
|
||||
|
||||
@@ -26,8 +26,7 @@ from quart import Response, request
|
||||
|
||||
from api.apps import current_user, login_required
|
||||
from api.db.joint_services.tenant_model_service import (
|
||||
get_model_config_by_type_and_name,
|
||||
get_tenant_default_model_by_type,
|
||||
get_tenant_default_model_by_type, get_model_config_from_provider_instance, get_api_key, split_model_name
|
||||
)
|
||||
from api.db.services.chunk_feedback_service import ChunkFeedbackService
|
||||
from api.db.services.conversation_service import ConversationService, structure_answer
|
||||
@@ -35,7 +34,6 @@ from api.db.services.dialog_service import DialogService, async_chat, gen_mindma
|
||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.services.search_service import SearchService
|
||||
from api.db.services.tenant_llm_service import TenantLLMService
|
||||
from api.db.services.user_service import TenantService, UserTenantService
|
||||
from api.utils.api_utils import (
|
||||
check_duplicate_ids,
|
||||
@@ -46,7 +44,6 @@ from api.utils.api_utils import (
|
||||
validate_request,
|
||||
)
|
||||
from api.utils.pagination_utils import validate_rest_api_page_size
|
||||
from api.utils.tenant_utils import ensure_tenant_model_id_for_params
|
||||
from common.constants import LLMType, RetCode, StatusEnum
|
||||
from common import settings
|
||||
from common.misc_utils import get_uuid, thread_pool_exec
|
||||
@@ -241,37 +238,44 @@ async def _validate_llm_id(llm_id, tenant_id, llm_setting=None):
|
||||
if not llm_id:
|
||||
return None
|
||||
|
||||
llm_name, llm_factory = TenantLLMService.split_model_name_and_factory(llm_id)
|
||||
model_type = (llm_setting or {}).get("model_type")
|
||||
if model_type not in {"chat", "image2text"}:
|
||||
conf_model_type = (llm_setting or {}).get("model_type")
|
||||
if isinstance(conf_model_type, str):
|
||||
model_type = conf_model_type if conf_model_type in {"chat", "image2text"} else "chat"
|
||||
elif isinstance(conf_model_type, list):
|
||||
model_type = "image2text" if "image2text" in conf_model_type else "chat"
|
||||
else:
|
||||
model_type = "chat"
|
||||
|
||||
if not await thread_pool_exec(
|
||||
TenantLLMService.query,
|
||||
tenant_id=tenant_id,
|
||||
llm_name=llm_name,
|
||||
llm_factory=llm_factory,
|
||||
model_type=model_type,
|
||||
):
|
||||
try:
|
||||
await thread_pool_exec(
|
||||
get_model_config_from_provider_instance,
|
||||
tenant_id=tenant_id,
|
||||
model_name=llm_id,
|
||||
model_type=model_type,
|
||||
)
|
||||
except Exception as e:
|
||||
logging.error(f"Fail to get model config for {llm_id}: {e}")
|
||||
return f"`llm_id` {llm_id} doesn't exist"
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
async def _validate_rerank_id(rerank_id, tenant_id):
|
||||
if not rerank_id:
|
||||
return None
|
||||
llm_name, llm_factory = TenantLLMService.split_model_name_and_factory(rerank_id)
|
||||
parts = rerank_id.split('@')
|
||||
llm_name = parts[0]
|
||||
if llm_name in _DEFAULT_RERANK_MODELS:
|
||||
return None
|
||||
if await thread_pool_exec(
|
||||
TenantLLMService.query,
|
||||
tenant_id=tenant_id,
|
||||
llm_name=llm_name,
|
||||
llm_factory=llm_factory,
|
||||
model_type="rerank",
|
||||
):
|
||||
return None
|
||||
return f"`rerank_id` {rerank_id} doesn't exist"
|
||||
try:
|
||||
await thread_pool_exec(
|
||||
get_model_config_from_provider_instance,
|
||||
tenant_id=tenant_id,
|
||||
model_name=rerank_id,
|
||||
model_type="rerank",
|
||||
)
|
||||
except Exception as e:
|
||||
logging.error(f"Fail to get model config for {rerank_id}: {e}")
|
||||
return f"`rerank_id` {rerank_id} doesn't exist"
|
||||
return None
|
||||
|
||||
|
||||
# def _validate_prompt_config(prompt_config):
|
||||
@@ -302,7 +306,7 @@ async def _validate_dataset_ids(dataset_ids, tenant_id):
|
||||
return f"The dataset {dataset_id} doesn't own parsed file"
|
||||
kbs.append(kb)
|
||||
|
||||
embd_ids = [TenantLLMService.split_model_name_and_factory(kb.embd_id)[0] for kb in kbs]
|
||||
embd_ids = [split_model_name(kb.embd_id)[0] for kb in kbs]
|
||||
if len(set(embd_ids)) > 1:
|
||||
return f'Datasets use different embedding models: {[kb.embd_id for kb in kbs]}'
|
||||
|
||||
@@ -380,7 +384,6 @@ async def create():
|
||||
# if err:
|
||||
# return get_data_error_result(message=err)
|
||||
|
||||
req = ensure_tenant_model_id_for_params(current_user.id, req)
|
||||
req = {field: value for field, value in req.items() if field in _PERSISTED_FIELDS}
|
||||
for field in _READONLY_FIELDS:
|
||||
req.pop(field, None)
|
||||
@@ -540,8 +543,6 @@ async def update_chat(chat_id):
|
||||
# kb_ids = req.get("kb_ids", current_chat.get("kb_ids", []))
|
||||
# if not kb_ids and not prompt_config.get("tavily_api_key") and _has_knowledge_placeholder(prompt_config):
|
||||
# return get_data_error_result(message="Please remove `{knowledge}` in system prompt since no dataset / Tavily used here.")
|
||||
|
||||
req = ensure_tenant_model_id_for_params(current_user.id, req)
|
||||
req = {field: value for field, value in req.items() if field in _PERSISTED_FIELDS}
|
||||
for field in _READONLY_FIELDS:
|
||||
req.pop(field, None)
|
||||
@@ -632,7 +633,6 @@ async def patch_chat(chat_id):
|
||||
# if not kb_ids and not prompt_config.get("tavily_api_key") and _has_knowledge_placeholder(prompt_config):
|
||||
# return get_data_error_result(message="Please remove `{knowledge}` in system prompt since no dataset / Tavily used here.")
|
||||
|
||||
req = ensure_tenant_model_id_for_params(current_user.id, req)
|
||||
req = {field: value for field, value in req.items() if field in _PERSISTED_FIELDS}
|
||||
for field in _READONLY_FIELDS:
|
||||
req.pop(field, None)
|
||||
@@ -1101,7 +1101,7 @@ async def recommendation():
|
||||
|
||||
chat_id = search_config.get("chat_id", "")
|
||||
if chat_id:
|
||||
chat_model_config = get_model_config_by_type_and_name(current_user.id, LLMType.CHAT, chat_id)
|
||||
chat_model_config = get_model_config_from_provider_instance(current_user.id, LLMType.CHAT, chat_id)
|
||||
else:
|
||||
chat_model_config = get_tenant_default_model_by_type(current_user.id, LLMType.CHAT)
|
||||
chat_mdl = LLMBundle(current_user.id, chat_model_config)
|
||||
@@ -1199,10 +1199,16 @@ async def session_completion(chat_id_in_arg=""):
|
||||
conv.reference.append({"chunks": [], "doc_aggs": []})
|
||||
|
||||
if chat_model_id:
|
||||
if not await thread_pool_exec(TenantLLMService.get_api_key, tenant_id=dia.tenant_id, model_name=chat_model_id):
|
||||
if not await thread_pool_exec(get_api_key, tenant_id=dia.tenant_id, model_name=chat_model_id):
|
||||
return get_data_error_result(message=f"Cannot use specified model {chat_model_id}.")
|
||||
dia.llm_id = chat_model_id
|
||||
dia.llm_setting = chat_model_config
|
||||
else:
|
||||
logging.info("empty chat_model_id in req, use default chat model.")
|
||||
_, tenant_info = TenantService.get_by_id(dia.tenant_id)
|
||||
if not tenant_info or not tenant_info.llm_id:
|
||||
raise LookupError("No default chat model for tenant.")
|
||||
dia.llm_id = tenant_info.llm_id
|
||||
|
||||
stream_mode = req.pop("stream", True)
|
||||
|
||||
|
||||
@@ -24,8 +24,7 @@ from quart import request
|
||||
|
||||
from api.apps import login_required
|
||||
from api.db.joint_services.tenant_model_service import (
|
||||
get_model_config_by_id,
|
||||
get_model_config_by_type_and_name,
|
||||
get_model_config_from_provider_instance,
|
||||
get_tenant_default_model_by_type,
|
||||
)
|
||||
from api.db.db_models import Document, Task
|
||||
@@ -291,16 +290,12 @@ async def retrieval_test(tenant_id):
|
||||
e, kb = KnowledgebaseService.get_by_id(kb_ids[0])
|
||||
if not e:
|
||||
return get_error_data_result(message="Dataset not found!")
|
||||
embd_model_config = get_model_config_by_id(kb.tenant_embd_id) if kb.tenant_embd_id else get_model_config_by_type_and_name(kb.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
embd_model_config = get_model_config_from_provider_instance(kb.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
embd_mdl = LLMBundle(kb.tenant_id, embd_model_config)
|
||||
|
||||
rerank_mdl = None
|
||||
if req.get("tenant_rerank_id"):
|
||||
allowed_rerank_tenant_ids = {tenant_id, *[dataset.tenant_id for dataset in kbs]}
|
||||
rerank_model_config = get_model_config_by_id(req["tenant_rerank_id"], allowed_tenant_ids=allowed_rerank_tenant_ids, requester_tenant_id=tenant_id)
|
||||
rerank_mdl = LLMBundle(kb.tenant_id, rerank_model_config)
|
||||
elif req.get("rerank_id"):
|
||||
rerank_model_config = get_model_config_by_type_and_name(kb.tenant_id, LLMType.RERANK, req["rerank_id"])
|
||||
if req.get("rerank_id"):
|
||||
rerank_model_config = get_model_config_from_provider_instance(kb.tenant_id, LLMType.RERANK, req["rerank_id"])
|
||||
rerank_mdl = LLMBundle(kb.tenant_id, rerank_model_config)
|
||||
|
||||
if langs:
|
||||
@@ -517,12 +512,8 @@ async def add_chunk(tenant_id, dataset_id, document_id):
|
||||
d["img_id"] = f"{dataset_id}-{chunk_id}"
|
||||
d["doc_type_kwd"] = "image"
|
||||
|
||||
tenant_embd_id = DocumentService.get_tenant_embd_id(document_id)
|
||||
if tenant_embd_id:
|
||||
model_config = get_model_config_by_id(tenant_embd_id)
|
||||
else:
|
||||
embd_id = DocumentService.get_embd_id(document_id)
|
||||
model_config = get_model_config_by_type_and_name(dataset_tenant_id, LLMType.EMBEDDING.value, embd_id)
|
||||
embd_id = DocumentService.get_embd_id(document_id)
|
||||
model_config = get_model_config_from_provider_instance(dataset_tenant_id, LLMType.EMBEDDING.value, embd_id)
|
||||
embd_mdl = TenantLLMService.model_instance(model_config)
|
||||
v, c = embd_mdl.encode([doc.name, req["content"] if not d["question_kwd"] else "\n".join(d["question_kwd"])])
|
||||
v = 0.1 * v[0] + 0.9 * v[1]
|
||||
@@ -661,12 +652,8 @@ async def update_chunk(tenant_id, dataset_id, document_id, chunk_id):
|
||||
d["img_id"] = f"{dataset_id}-{chunk_id}"
|
||||
d["doc_type_kwd"] = "image"
|
||||
|
||||
tenant_embd_id = DocumentService.get_tenant_embd_id(document_id)
|
||||
if tenant_embd_id:
|
||||
model_config = get_model_config_by_id(tenant_embd_id)
|
||||
else:
|
||||
embd_id = DocumentService.get_embd_id(document_id)
|
||||
model_config = get_model_config_by_type_and_name(dataset_tenant_id, LLMType.EMBEDDING.value, embd_id)
|
||||
embd_id = DocumentService.get_embd_id(document_id)
|
||||
model_config = get_model_config_from_provider_instance(dataset_tenant_id, LLMType.EMBEDDING.value, embd_id)
|
||||
embd_mdl = TenantLLMService.model_instance(model_config)
|
||||
if doc.parser_id == ParserType.QA:
|
||||
arr = [t for t in re.split(r"[\n\t]", d["content_with_weight"]) if len(t) > 1]
|
||||
|
||||
@@ -149,6 +149,8 @@ async def create(tenant_id: str = None):
|
||||
return get_result(data=result)
|
||||
else:
|
||||
return get_error_data_result(message=result)
|
||||
except LookupError as e:
|
||||
return get_error_argument_result(str(e))
|
||||
except ValueError as e:
|
||||
return get_error_argument_result(str(e))
|
||||
except Exception as e:
|
||||
|
||||
@@ -27,7 +27,7 @@ from api.db.services.document_service import DocumentService
|
||||
from api.db.services.doc_metadata_service import DocMetadataService
|
||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_id, get_model_config_by_type_and_name, get_tenant_default_model_by_type
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_from_provider_instance
|
||||
from common.metadata_utils import meta_filter, convert_conditions
|
||||
from api.utils.api_utils import apikey_required, build_error_result, get_request_json, get_json_result
|
||||
from rag.app.tag import label_question
|
||||
@@ -260,10 +260,7 @@ async def retrieval(tenant_id):
|
||||
kb_id,
|
||||
)
|
||||
return build_error_result(message="No authorization.", code=RetCode.AUTHENTICATION_ERROR)
|
||||
if kb.tenant_embd_id:
|
||||
model_config = get_model_config_by_id(kb.tenant_embd_id)
|
||||
else:
|
||||
model_config = get_model_config_by_type_and_name(kb.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
model_config = get_model_config_from_provider_instance(kb.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
embd_mdl = LLMBundle(kb.tenant_id, model_config)
|
||||
if metadata_condition:
|
||||
doc_ids.extend(meta_filter(metas, convert_conditions(metadata_condition), metadata_condition.get("logic", "and")))
|
||||
|
||||
@@ -18,12 +18,11 @@ import os
|
||||
import time
|
||||
|
||||
from quart import request, g
|
||||
from common.constants import LLMType, RetCode
|
||||
from common.constants import RetCode
|
||||
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.utils.tenant_utils import ensure_tenant_model_id_for_params
|
||||
from api.utils.pagination_utils import validate_rest_api_page_size
|
||||
|
||||
|
||||
@@ -36,18 +35,11 @@ async def create_memory():
|
||||
req = await get_request_json()
|
||||
t_parsed = time.perf_counter() if timing_enabled else None
|
||||
try:
|
||||
req = ensure_tenant_model_id_for_params(current_user.id, req)
|
||||
if not req.get("tenant_llm_id"):
|
||||
raise ArgumentException(
|
||||
f"Tenant Model with name {req['llm_id']} and type {LLMType.CHAT.value} not found"
|
||||
)
|
||||
memory_info = {
|
||||
"name": req["name"],
|
||||
"memory_type": req["memory_type"],
|
||||
"embd_id": req["embd_id"],
|
||||
"llm_id": req["llm_id"],
|
||||
"tenant_embd_id": req["tenant_embd_id"],
|
||||
"tenant_llm_id": req["tenant_llm_id"],
|
||||
"llm_id": req["llm_id"]
|
||||
}
|
||||
success, res = await memory_api_service.create_memory(memory_info)
|
||||
if timing_enabled:
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
#
|
||||
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
import logging
|
||||
|
||||
from quart import request
|
||||
|
||||
from api.apps import login_required
|
||||
from api.apps.services import models_api_service
|
||||
from api.utils.api_utils import (
|
||||
add_tenant_id_to_kwargs,
|
||||
get_error_argument_result,
|
||||
get_error_data_result,
|
||||
get_result,
|
||||
)
|
||||
|
||||
|
||||
@manager.route("/models", methods=["GET"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
def get_added_models(tenant_id: str):
|
||||
"""
|
||||
List tenant all added models.
|
||||
---
|
||||
tags:
|
||||
- Models
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
responses:
|
||||
200:
|
||||
description: List of added models.
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
models:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
model_provider:
|
||||
type: string
|
||||
model_instance:
|
||||
type: string
|
||||
model_name:
|
||||
type: string
|
||||
model_type:
|
||||
type: string
|
||||
enable:
|
||||
type: boolean
|
||||
"""
|
||||
model_type_filter = request.args.get("type")
|
||||
try:
|
||||
success, result = models_api_service.list_tenant_added_models(tenant_id, model_type_filter)
|
||||
if success:
|
||||
return get_result(data=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("/models/default", methods=["GET"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
def get_default_models(tenant_id: str):
|
||||
"""
|
||||
List tenant default models.
|
||||
---
|
||||
tags:
|
||||
- Models
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
responses:
|
||||
200:
|
||||
description: List of default models.
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
models:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
model_provider:
|
||||
type: string
|
||||
model_instance:
|
||||
type: string
|
||||
model_name:
|
||||
type: string
|
||||
model_type:
|
||||
type: string
|
||||
enable:
|
||||
type: boolean
|
||||
"""
|
||||
try:
|
||||
success, result = models_api_service.list_tenant_default_models(tenant_id)
|
||||
if success:
|
||||
return get_result(data=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("/models/default", methods=["PATCH"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
async def set_default_models(tenant_id: str):
|
||||
"""
|
||||
Set or clear a tenant default model.
|
||||
---
|
||||
tags:
|
||||
- Models
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
- in: body
|
||||
name: body
|
||||
description: Model configuration.
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- model_type
|
||||
properties:
|
||||
model_provider:
|
||||
type: string
|
||||
description: Provider name. Required when setting a model; omit to clear.
|
||||
model_instance:
|
||||
type: string
|
||||
description: Instance name. Required when setting a model; omit to clear.
|
||||
model_name:
|
||||
type: string
|
||||
description: Model name. Required when setting a model; omit to clear.
|
||||
model_type:
|
||||
type: string
|
||||
description: "Model type: chat, embedding, rerank, asr, vision, tts, ocr"
|
||||
responses:
|
||||
200:
|
||||
description: Default model updated.
|
||||
schema:
|
||||
type: object
|
||||
"""
|
||||
data = await request.get_json()
|
||||
if not data or "model_type" not in data:
|
||||
return get_error_argument_result(message="model_type is required")
|
||||
|
||||
model_provider = data.get("model_provider", "")
|
||||
model_instance = data.get("model_instance", "")
|
||||
model_name = data.get("model_name", "")
|
||||
model_type = data["model_type"]
|
||||
|
||||
try:
|
||||
success, msg = models_api_service.set_tenant_default_models(
|
||||
tenant_id, model_provider, model_instance, model_name, model_type
|
||||
)
|
||||
if success:
|
||||
logging.info(f"success: {success}, msg: {msg}")
|
||||
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")
|
||||
@@ -22,7 +22,7 @@ from quart import Response, jsonify
|
||||
from api.apps import current_user, login_required
|
||||
from api.db.services.dialog_service import DialogService, async_chat
|
||||
from api.db.services.doc_metadata_service import DocMetadataService
|
||||
from api.db.services.tenant_llm_service import TenantLLMService
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance, get_api_key
|
||||
from api.utils.api_utils import get_error_data_result, get_request_json, validate_request
|
||||
from common.constants import RetCode, StatusEnum
|
||||
from common.metadata_utils import convert_conditions, meta_filter
|
||||
@@ -33,17 +33,18 @@ def _validate_llm_id(llm_id, tenant_id, llm_setting=None):
|
||||
if not llm_id:
|
||||
return None
|
||||
|
||||
llm_name, llm_factory = TenantLLMService.split_model_name_and_factory(llm_id)
|
||||
model_type = (llm_setting or {}).get("model_type")
|
||||
if model_type not in {"chat", "image2text"}:
|
||||
model_type = "chat"
|
||||
|
||||
if not TenantLLMService.query(
|
||||
tenant_id=tenant_id,
|
||||
llm_name=llm_name,
|
||||
llm_factory=llm_factory,
|
||||
model_type=model_type,
|
||||
):
|
||||
try:
|
||||
get_model_config_from_provider_instance(
|
||||
tenant_id=tenant_id,
|
||||
model_name=llm_id,
|
||||
model_type=model_type,
|
||||
)
|
||||
except Exception as e:
|
||||
logging.error(f"Fail to get model config for {llm_id}: {e}")
|
||||
return f"`llm_id` {llm_id} doesn't exist"
|
||||
return None
|
||||
|
||||
@@ -133,7 +134,7 @@ async def openai_chat_completions(chat_id):
|
||||
if llm_id_error:
|
||||
return get_error_data_result(message=llm_id_error, code=RetCode.ARGUMENT_ERROR)
|
||||
dia.llm_id = requested_model
|
||||
if not TenantLLMService.get_api_key(tenant_id=dia.tenant_id, model_name=requested_model):
|
||||
if not get_api_key(tenant_id=dia.tenant_id, model_name=requested_model):
|
||||
return get_error_data_result(message=f"Cannot use specified model {requested_model}.")
|
||||
|
||||
metadata_condition = extra_body.get("metadata_condition") or {}
|
||||
|
||||
@@ -0,0 +1,796 @@
|
||||
#
|
||||
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
import logging
|
||||
|
||||
from quart import request
|
||||
|
||||
from api.apps import login_required
|
||||
from api.utils.api_utils import (
|
||||
add_tenant_id_to_kwargs,
|
||||
get_error_argument_result,
|
||||
get_error_data_result,
|
||||
get_result,
|
||||
)
|
||||
from api.apps.services import provider_api_service
|
||||
|
||||
|
||||
@manager.route("/providers", methods=["GET"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
def list_providers(tenant_id: str = None):
|
||||
"""
|
||||
List providers.
|
||||
---
|
||||
parameters:
|
||||
- in: query
|
||||
name: available
|
||||
type: string
|
||||
required: false
|
||||
description: "If 'true', list all available system providers; otherwise list tenant-configured providers."
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
responses:
|
||||
200:
|
||||
description: List of providers.
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
"""
|
||||
available_only = request.args.get("available", "").lower() == "true"
|
||||
try:
|
||||
success, result = provider_api_service.list_providers(tenant_id, available_only)
|
||||
if success:
|
||||
return get_result(data=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", methods=["PUT"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
async def add_provider(tenant_id: str = None):
|
||||
"""
|
||||
Add a provider for the tenant.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
- in: body
|
||||
name: body
|
||||
description: Provider creation parameters.
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- provider_name
|
||||
properties:
|
||||
provider_name:
|
||||
type: string
|
||||
description: Provider/factory name.
|
||||
responses:
|
||||
200:
|
||||
description: Provider added successfully.
|
||||
schema:
|
||||
type: object
|
||||
"""
|
||||
data = await request.get_json()
|
||||
if not data or "provider_name" not in data:
|
||||
return get_error_argument_result(message="provider_name is required")
|
||||
|
||||
provider_name = data["provider_name"]
|
||||
|
||||
try:
|
||||
success, msg = provider_api_service.add_provider(tenant_id, provider_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")
|
||||
|
||||
|
||||
@manager.route("/providers/<provider_name>", methods=["GET"]) # noqa: F821
|
||||
@login_required
|
||||
def show_provider(provider_name: str):
|
||||
"""
|
||||
Show provider details.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: path
|
||||
name: provider_name
|
||||
type: string
|
||||
required: true
|
||||
description: Provider name.
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
responses:
|
||||
200:
|
||||
description: Provider details.
|
||||
schema:
|
||||
type: object
|
||||
"""
|
||||
try:
|
||||
success, result = provider_api_service.show_provider(provider_name)
|
||||
if success:
|
||||
return get_result(data=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_name>", methods=["DELETE"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
def delete_provider(tenant_id: str = None, provider_name: str = None):
|
||||
"""
|
||||
Delete a provider and all its models for the tenant.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: path
|
||||
name: provider_name
|
||||
type: string
|
||||
required: true
|
||||
description: Provider name.
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
responses:
|
||||
200:
|
||||
description: Provider deleted successfully.
|
||||
schema:
|
||||
type: object
|
||||
"""
|
||||
try:
|
||||
success, msg = provider_api_service.delete_provider(tenant_id, provider_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")
|
||||
|
||||
|
||||
@manager.route("/providers/<provider_name>/models", methods=["GET"]) # noqa: F821
|
||||
@login_required
|
||||
def list_provider_models(provider_name: str):
|
||||
"""
|
||||
List models for a provider.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: path
|
||||
name: provider_name
|
||||
type: string
|
||||
required: true
|
||||
description: Provider name.
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
responses:
|
||||
200:
|
||||
description: List of models for the provider.
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
"""
|
||||
try:
|
||||
success, result = provider_api_service.list_provider_models(provider_name)
|
||||
if success:
|
||||
return get_result(data=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_name>/models/<path:model_name>", methods=["GET"]) # noqa: F821
|
||||
@login_required
|
||||
def show_provider_model(provider_name: str, model_name: str):
|
||||
"""
|
||||
Show a specific model for a provider.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: path
|
||||
name: provider_name
|
||||
type: string
|
||||
required: true
|
||||
description: Provider name.
|
||||
- in: path
|
||||
name: model_name
|
||||
type: string
|
||||
required: true
|
||||
description: Model name.
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
responses:
|
||||
200:
|
||||
description: Model details.
|
||||
schema:
|
||||
type: object
|
||||
"""
|
||||
try:
|
||||
success, result = provider_api_service.show_provider_model(provider_name, model_name)
|
||||
if success:
|
||||
return get_result(data=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_name>/instances", methods=["POST"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
async def create_provider_instance(tenant_id: str = None, provider_name: str = None):
|
||||
"""
|
||||
Create a provider instance.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: path
|
||||
name: provider_name
|
||||
type: string
|
||||
required: true
|
||||
description: Provider name.
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
- in: body
|
||||
name: body
|
||||
description: Instance creation parameters.
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- instance_name
|
||||
- api_key
|
||||
properties:
|
||||
instance_name:
|
||||
type: string
|
||||
description: Instance name.
|
||||
api_key:
|
||||
type: string
|
||||
description: API key.
|
||||
responses:
|
||||
200:
|
||||
description: Instance created successfully.
|
||||
schema:
|
||||
type: object
|
||||
"""
|
||||
data = await request.get_json()
|
||||
if not data or "instance_name" not in data or "api_key" not in data:
|
||||
return get_error_argument_result(message="instance_name and api_key are required")
|
||||
|
||||
instance_name = data["instance_name"]
|
||||
api_key = data["api_key"]
|
||||
base_url = data.get("base_url", "")
|
||||
region = data.get("region", "")
|
||||
|
||||
try:
|
||||
success, msg = provider_api_service.create_provider_instance(tenant_id, provider_name, instance_name, api_key, base_url, region)
|
||||
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_name>/instances", methods=["GET"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
def list_provider_instances(tenant_id: str = None, provider_name: str = None):
|
||||
"""
|
||||
List provider instances.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: path
|
||||
name: provider_name
|
||||
type: string
|
||||
required: true
|
||||
description: Provider name.
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
responses:
|
||||
200:
|
||||
description: List of provider instances.
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
"""
|
||||
try:
|
||||
success, result = provider_api_service.list_provider_instances(tenant_id, provider_name)
|
||||
if success:
|
||||
return get_result(data=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_name>/instances/<instance_name>", methods=["GET"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
def show_provider_instance(tenant_id: str = None, provider_name: str = None, instance_name: str = None):
|
||||
"""
|
||||
Show a provider instance.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: path
|
||||
name: provider_name
|
||||
type: string
|
||||
required: true
|
||||
description: Provider name.
|
||||
- in: path
|
||||
name: instance_name
|
||||
type: string
|
||||
required: true
|
||||
description: Instance name.
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
responses:
|
||||
200:
|
||||
description: Instance details.
|
||||
schema:
|
||||
type: object
|
||||
"""
|
||||
try:
|
||||
success, result = provider_api_service.show_provider_instance(tenant_id, provider_name, instance_name)
|
||||
if success:
|
||||
return get_result(data=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_name>/instances", methods=["DELETE"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
async def drop_provider_instances(tenant_id: str = None, provider_name: str = None):
|
||||
"""
|
||||
Drop provider instances.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: path
|
||||
name: provider_name
|
||||
type: string
|
||||
required: true
|
||||
description: Provider name.
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
- in: body
|
||||
name: body
|
||||
description: Instance deletion parameters.
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- instances
|
||||
properties:
|
||||
instances:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: List of instance names to drop.
|
||||
responses:
|
||||
200:
|
||||
description: Instances dropped successfully.
|
||||
schema:
|
||||
type: object
|
||||
"""
|
||||
data = await request.get_json()
|
||||
if not data or "instances" not in data:
|
||||
return get_error_argument_result(message="instances is required")
|
||||
|
||||
instances = data["instances"]
|
||||
if not instances:
|
||||
return get_error_argument_result(message="instances is required")
|
||||
|
||||
try:
|
||||
success, msg = provider_api_service.drop_provider_instances(tenant_id, provider_name, instances)
|
||||
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_name>/instances/<instance_name>/models", methods=["GET"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
def list_instance_models(tenant_id: str = None, provider_name: str = None, instance_name: str = None):
|
||||
"""
|
||||
List models for a provider instance.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: path
|
||||
name: provider_name
|
||||
type: string
|
||||
required: true
|
||||
description: Provider name.
|
||||
- in: path
|
||||
name: instance_name
|
||||
type: string
|
||||
required: true
|
||||
description: Instance name.
|
||||
- in: query
|
||||
name: supported
|
||||
type: string
|
||||
required: false
|
||||
description: "If 'true', list only supported models."
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
responses:
|
||||
200:
|
||||
description: List of models.
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
"""
|
||||
supported_only = request.args.get("supported", "").lower() == "true"
|
||||
try:
|
||||
success, result = provider_api_service.list_instance_models(
|
||||
tenant_id, provider_name, instance_name, supported_only
|
||||
)
|
||||
if success:
|
||||
return get_result(data=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_name>/instances/<instance_name>/models", methods=["POST"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
async def add_model_to_instance(tenant_id: str, provider_name: str, instance_name: str):
|
||||
"""
|
||||
Add a model to an instance.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: path
|
||||
name: provider_name
|
||||
type: string
|
||||
required: true
|
||||
description: Provider name.
|
||||
- in: path
|
||||
name: instance_name
|
||||
type: string
|
||||
required: true
|
||||
description: Instance 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: string
|
||||
description: Model name.
|
||||
model_type:
|
||||
type: string
|
||||
description: Model type.
|
||||
max_tokens:
|
||||
type: integer
|
||||
description: Maximum number of tokens.
|
||||
extra:
|
||||
type: object
|
||||
description: Extra model details.
|
||||
responses:
|
||||
200:
|
||||
description: Model added successfully.
|
||||
"""
|
||||
data = await request.get_json()
|
||||
if not data or "model_name" not in data or "model_type" not in data:
|
||||
return get_error_argument_result(message="model_name and model_type are required")
|
||||
|
||||
model_name = data["model_name"]
|
||||
model_type = data["model_type"]
|
||||
max_tokens = data.get("max_tokens", 8192)
|
||||
extra = data.get("extra", {})
|
||||
|
||||
try:
|
||||
success, result = provider_api_service.add_model_to_instance(
|
||||
tenant_id, provider_name, instance_name, model_name, model_type, max_tokens, extra
|
||||
)
|
||||
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_name>/instances/<instance_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_name: str = None, instance_name: str = None, model_name: str = None):
|
||||
"""
|
||||
Enable or disable a model.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: path
|
||||
name: provider_name
|
||||
type: string
|
||||
required: true
|
||||
description: Provider name.
|
||||
- in: path
|
||||
name: instance_name
|
||||
type: string
|
||||
required: true
|
||||
description: Instance name.
|
||||
- in: path
|
||||
name: model_name
|
||||
type: string
|
||||
required: true
|
||||
description: Model name.
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
- in: body
|
||||
name: body
|
||||
description: Model status update.
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum: ["active", "inactive"]
|
||||
description: Model status.
|
||||
responses:
|
||||
200:
|
||||
description: Model status updated.
|
||||
schema:
|
||||
type: object
|
||||
"""
|
||||
data = await request.get_json()
|
||||
if not data or "status" not in data:
|
||||
return get_error_argument_result(message="status is required")
|
||||
|
||||
status = data["status"]
|
||||
if 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_name, instance_name, model_name, status)
|
||||
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_name>/instances/<instance_name>/models/<path:model_name>", methods=["POST"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
async def chat_to_model(tenant_id: str = None, provider_name: str = None, instance_name: str = None, model_name: str = None):
|
||||
"""
|
||||
Chat to a model.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
parameters:
|
||||
- in: path
|
||||
name: provider_name
|
||||
type: string
|
||||
required: true
|
||||
description: Provider name.
|
||||
- in: path
|
||||
name: instance_name
|
||||
type: string
|
||||
required: true
|
||||
description: Instance name.
|
||||
- in: path
|
||||
name: model_name
|
||||
type: string
|
||||
required: true
|
||||
description: Model name.
|
||||
- in: header
|
||||
name: Authorization
|
||||
type: string
|
||||
required: true
|
||||
description: Bearer token for authentication.
|
||||
- in: body
|
||||
name: body
|
||||
description: Chat request.
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- message
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
description: Chat message.
|
||||
stream:
|
||||
type: boolean
|
||||
description: Whether to stream the response.
|
||||
thinking:
|
||||
type: boolean
|
||||
description: Whether to enable thinking/reasoning.
|
||||
responses:
|
||||
200:
|
||||
description: Chat response.
|
||||
schema:
|
||||
type: object
|
||||
"""
|
||||
data = await request.get_json()
|
||||
if not data or "message" not in data:
|
||||
return get_error_argument_result(message="message is required")
|
||||
|
||||
message = data["message"]
|
||||
stream = data.get("stream", False)
|
||||
thinking = data.get("thinking", False)
|
||||
|
||||
try:
|
||||
success, result = await provider_api_service.chat_to_model(
|
||||
tenant_id, provider_name, instance_name, model_name, message, stream, thinking
|
||||
)
|
||||
if not success:
|
||||
return get_error_data_result(message=result)
|
||||
|
||||
if stream and isinstance(result, dict) and result.get("type") == "stream":
|
||||
# Streaming response using SSE
|
||||
from quart import Response
|
||||
llm = result["llm"]
|
||||
|
||||
async def generate():
|
||||
async for chunk in llm.async_chat_streamly(
|
||||
None,
|
||||
[{"role": "user", "content": message}],
|
||||
{"temperature": 0.9},
|
||||
):
|
||||
if chunk and isinstance(chunk, str) and chunk.find("**ERROR**") < 0:
|
||||
yield f"data: [MESSAGE]{chunk}\n\n"
|
||||
yield "data: [DONE]\n\n"
|
||||
|
||||
return Response(generate(), mimetype="text/event-stream", headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
})
|
||||
|
||||
# Non-streaming response
|
||||
return get_result(data=result)
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
return get_error_data_result(message="Internal server error")
|
||||
@@ -44,7 +44,6 @@ from api.utils.api_utils import (
|
||||
validate_request,
|
||||
)
|
||||
from api.utils.crypt import decrypt
|
||||
from api.utils.tenant_utils import ensure_tenant_model_id_for_params
|
||||
from rag.utils.redis_conn import REDIS_CONN
|
||||
from api.apps import login_required, current_user, login_user, logout_user
|
||||
from api.utils.web_utils import (
|
||||
@@ -631,8 +630,7 @@ async def set_tenant_info():
|
||||
req = await get_request_json()
|
||||
try:
|
||||
tid = req.pop("tenant_id")
|
||||
update_dict = ensure_tenant_model_id_for_params(tid, req)
|
||||
TenantService.update_by_id(tid, update_dict)
|
||||
TenantService.update_by_id(tid, req)
|
||||
return get_json_result(data=True)
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
|
||||
@@ -17,6 +17,8 @@ import logging
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance
|
||||
from common.constants import PAGERANK_FLD
|
||||
from common import settings
|
||||
from api.db.db_models import File
|
||||
@@ -912,11 +914,7 @@ async def search(dataset_id: str, tenant_id: str, req: dict):
|
||||
:param req: search request
|
||||
:return: (success, result) or (success, error_message)
|
||||
"""
|
||||
from api.db.joint_services.tenant_model_service import (
|
||||
get_model_config_by_id,
|
||||
get_model_config_by_type_and_name,
|
||||
get_tenant_default_model_by_type,
|
||||
)
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type
|
||||
from api.db.services.doc_metadata_service import DocMetadataService
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.services.search_service import SearchService
|
||||
@@ -985,7 +983,7 @@ async def search(dataset_id: str, tenant_id: str, req: dict):
|
||||
if meta_data_filter.get("method") in ["auto", "semi_auto"]:
|
||||
chat_id = search_config.get("chat_id", "")
|
||||
if chat_id:
|
||||
chat_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.CHAT, search_config["chat_id"])
|
||||
chat_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.CHAT, search_config["chat_id"])
|
||||
else:
|
||||
chat_model_config = get_tenant_default_model_by_type(tenant_id, LLMType.CHAT)
|
||||
chat_mdl = LLMBundle(tenant_id, chat_model_config)
|
||||
@@ -1018,28 +1016,17 @@ async def search(dataset_id: str, tenant_id: str, req: dict):
|
||||
_question = question
|
||||
if langs:
|
||||
_question = await cross_languages(kb.tenant_id, None, _question, langs)
|
||||
if kb.tenant_embd_id:
|
||||
embd_model_config = get_model_config_by_id(kb.tenant_embd_id)
|
||||
elif kb.embd_id:
|
||||
embd_model_config = get_model_config_by_type_and_name(kb.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
if kb.embd_id:
|
||||
embd_model_config = get_model_config_from_provider_instance(kb.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
else:
|
||||
embd_model_config = get_tenant_default_model_by_type(kb.tenant_id, LLMType.EMBEDDING)
|
||||
embd_mdl = LLMBundle(kb.tenant_id, embd_model_config)
|
||||
|
||||
rerank_mdl = None
|
||||
if req.get("tenant_rerank_id"):
|
||||
allowed_rerank_tenant_ids = {tenant_id, kb.tenant_id}
|
||||
rerank_model_config = get_model_config_by_id(
|
||||
req["tenant_rerank_id"],
|
||||
allowed_tenant_ids=allowed_rerank_tenant_ids,
|
||||
requester_tenant_id=tenant_id,
|
||||
)
|
||||
rerank_id = search_config.get("rerank_id") or req.get("rerank_id")
|
||||
if rerank_id:
|
||||
rerank_model_config = get_model_config_from_provider_instance(kb.tenant_id, LLMType.RERANK.value, rerank_id)
|
||||
rerank_mdl = LLMBundle(kb.tenant_id, rerank_model_config)
|
||||
else:
|
||||
rerank_id = search_config.get("rerank_id") or req.get("rerank_id")
|
||||
if rerank_id:
|
||||
rerank_model_config = get_model_config_by_type_and_name(kb.tenant_id, LLMType.RERANK.value, rerank_id)
|
||||
rerank_mdl = LLMBundle(kb.tenant_id, rerank_model_config)
|
||||
|
||||
if search_config.get("keyword", req.get("keyword", False)):
|
||||
default_chat_model_config = get_tenant_default_model_by_type(kb.tenant_id, LLMType.CHAT)
|
||||
@@ -1099,9 +1086,6 @@ def check_embedding(dataset_id: str, tenant_id: str, req: dict):
|
||||
from common.doc_store.doc_store_base import OrderByExpr
|
||||
from rag.nlp import search
|
||||
|
||||
from api.db.joint_services.tenant_model_service import (
|
||||
get_model_config_by_type_and_name,
|
||||
)
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from common.constants import LLMType
|
||||
|
||||
@@ -1214,7 +1198,7 @@ def check_embedding(dataset_id: str, tenant_id: str, req: dict):
|
||||
if not ok:
|
||||
return False, err
|
||||
|
||||
embd_model_config = get_model_config_by_type_and_name(kb.tenant_id, LLMType.EMBEDDING, embd_id)
|
||||
embd_model_config = get_model_config_from_provider_instance(kb.tenant_id, LLMType.EMBEDDING, embd_id)
|
||||
emb_mdl = LLMBundle(kb.tenant_id, embd_model_config)
|
||||
|
||||
n = int(req.get("check_num", 5))
|
||||
@@ -1293,11 +1277,7 @@ async def search_datasets(tenant_id: str, req: dict):
|
||||
:param req: search request containing dataset_ids and other params
|
||||
:return: (success, result) or (success, error_message)
|
||||
"""
|
||||
from api.db.joint_services.tenant_model_service import (
|
||||
get_model_config_by_id,
|
||||
get_model_config_by_type_and_name,
|
||||
get_tenant_default_model_by_type,
|
||||
)
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type
|
||||
from api.db.services.doc_metadata_service import DocMetadataService
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.services.search_service import SearchService
|
||||
@@ -1373,7 +1353,7 @@ async def search_datasets(tenant_id: str, req: dict):
|
||||
if meta_data_filter.get("method") in ["auto", "semi_auto"]:
|
||||
chat_id = search_config.get("chat_id", "")
|
||||
if chat_id:
|
||||
chat_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.CHAT, search_config["chat_id"])
|
||||
chat_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.CHAT, search_config["chat_id"])
|
||||
else:
|
||||
chat_model_config = get_tenant_default_model_by_type(tenant_id, LLMType.CHAT)
|
||||
chat_mdl = LLMBundle(tenant_id, chat_model_config)
|
||||
@@ -1408,28 +1388,17 @@ async def search_datasets(tenant_id: str, req: dict):
|
||||
_question = question
|
||||
if langs:
|
||||
_question = await cross_languages(kb.tenant_id, None, _question, langs)
|
||||
if kb.tenant_embd_id:
|
||||
embd_model_config = get_model_config_by_id(kb.tenant_embd_id)
|
||||
elif kb.embd_id:
|
||||
embd_model_config = get_model_config_by_type_and_name(kb.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
if kb.embd_id:
|
||||
embd_model_config = get_model_config_from_provider_instance(kb.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
else:
|
||||
embd_model_config = get_tenant_default_model_by_type(kb.tenant_id, LLMType.EMBEDDING)
|
||||
embd_mdl = LLMBundle(kb.tenant_id, embd_model_config)
|
||||
|
||||
rerank_mdl = None
|
||||
if req.get("tenant_rerank_id"):
|
||||
allowed_rerank_tenant_ids = {tenant_id, *[dataset.tenant_id for dataset in kbs]}
|
||||
rerank_model_config = get_model_config_by_id(
|
||||
req["tenant_rerank_id"],
|
||||
allowed_tenant_ids=allowed_rerank_tenant_ids,
|
||||
requester_tenant_id=tenant_id,
|
||||
)
|
||||
rerank_id = search_config.get("rerank_id") or req.get("rerank_id")
|
||||
if rerank_id:
|
||||
rerank_model_config = get_model_config_from_provider_instance(kb.tenant_id, LLMType.RERANK.value, rerank_id)
|
||||
rerank_mdl = LLMBundle(kb.tenant_id, rerank_model_config)
|
||||
else:
|
||||
rerank_id = search_config.get("rerank_id") or req.get("rerank_id")
|
||||
if rerank_id:
|
||||
rerank_model_config = get_model_config_by_type_and_name(kb.tenant_id, LLMType.RERANK.value, rerank_id)
|
||||
rerank_mdl = LLMBundle(kb.tenant_id, rerank_model_config)
|
||||
|
||||
if search_config.get("keyword", req.get("keyword", False)):
|
||||
default_chat_model_config = get_tenant_default_model_by_type(kb.tenant_id, LLMType.CHAT)
|
||||
|
||||
@@ -21,11 +21,10 @@ from api.db.services.canvas_service import UserCanvasService
|
||||
from api.db.services.task_service import TaskService
|
||||
from api.db.joint_services.memory_message_service import get_memory_size_cache, judge_system_prompt_is_default, queue_save_to_memory_task, query_message
|
||||
from api.utils.memory_utils import format_ret_data_from_memory, get_memory_type_human
|
||||
from api.utils.tenant_utils import ensure_tenant_model_id_for_params
|
||||
from api.constants import MEMORY_NAME_LIMIT, MEMORY_SIZE_LIMIT
|
||||
from memory.services.messages import MessageService
|
||||
from memory.utils.prompt_util import PromptAssembler
|
||||
from common.constants import MemoryType, ForgettingPolicy, LLMType
|
||||
from common.constants import MemoryType, ForgettingPolicy
|
||||
from common.exceptions import ArgumentException, NotFoundException
|
||||
from common.time_utils import current_timestamp, timestamp_to_date
|
||||
|
||||
@@ -104,9 +103,7 @@ async def create_memory(memory_info: dict):
|
||||
name=memory_name,
|
||||
memory_type=memory_type,
|
||||
embd_id=memory_info["embd_id"],
|
||||
llm_id=memory_info["llm_id"],
|
||||
tenant_llm_id=memory_info["tenant_llm_id"],
|
||||
tenant_embd_id=memory_info["tenant_embd_id"]
|
||||
llm_id=memory_info["llm_id"]
|
||||
)
|
||||
if success:
|
||||
return True, format_ret_data_from_memory(res)
|
||||
@@ -133,7 +130,6 @@ async def update_memory(memory_id: str, new_memory_setting: dict):
|
||||
}
|
||||
"""
|
||||
current_memory = _require_memory_access(memory_id)
|
||||
owner_tenant_id = current_memory.tenant_id
|
||||
|
||||
update_dict = {}
|
||||
# check name length
|
||||
@@ -150,32 +146,15 @@ async def update_memory(memory_id: str, new_memory_setting: dict):
|
||||
if new_memory_setting["permissions"] not in [e.value for e in TenantPermission]:
|
||||
raise ArgumentException(f"Unknown permission '{new_memory_setting['permissions']}'.")
|
||||
update_dict["permissions"] = new_memory_setting["permissions"]
|
||||
if ("tenant_llm_id" in new_memory_setting or "tenant_embd_id" in new_memory_setting) and not (
|
||||
new_memory_setting.get("llm_id") or new_memory_setting.get("embd_id")
|
||||
):
|
||||
raise ArgumentException(
|
||||
"Do not set tenant_llm_id or tenant_embd_id directly; update llm_id and/or embd_id instead."
|
||||
)
|
||||
if new_memory_setting.get("llm_id") or new_memory_setting.get("embd_id"):
|
||||
merged = {
|
||||
"llm_id": new_memory_setting.get("llm_id") or current_memory.llm_id,
|
||||
"embd_id": new_memory_setting.get("embd_id") or current_memory.embd_id,
|
||||
}
|
||||
merged = ensure_tenant_model_id_for_params(owner_tenant_id, merged)
|
||||
if not merged.get("tenant_llm_id"):
|
||||
raise ArgumentException(
|
||||
f"Tenant Model with name {merged['llm_id']} and type {LLMType.CHAT.value} not found"
|
||||
)
|
||||
if new_memory_setting.get("embd_id") and not merged.get("tenant_embd_id"):
|
||||
raise ArgumentException(
|
||||
f"Tenant Model with name {merged['embd_id']} and type {LLMType.EMBEDDING.value} not found"
|
||||
)
|
||||
if new_memory_setting.get("llm_id"):
|
||||
update_dict["llm_id"] = merged["llm_id"]
|
||||
if new_memory_setting.get("embd_id"):
|
||||
update_dict["embd_id"] = merged["embd_id"]
|
||||
update_dict["tenant_llm_id"] = merged["tenant_llm_id"]
|
||||
update_dict["tenant_embd_id"] = merged.get("tenant_embd_id")
|
||||
if new_memory_setting.get("memory_type"):
|
||||
memory_type = set(new_memory_setting["memory_type"])
|
||||
invalid_type = memory_type - {e.name.lower() for e in MemoryType}
|
||||
@@ -216,7 +195,7 @@ async def update_memory(memory_id: str, new_memory_setting: dict):
|
||||
return True, memory_dict
|
||||
# check memory empty when update embd_id, memory_type
|
||||
memory_size = get_memory_size_cache(memory_id, current_memory.tenant_id)
|
||||
not_allowed_update = [f for f in ["tenant_embd_id", "embd_id", "memory_type"] if f in to_update and memory_size > 0]
|
||||
not_allowed_update = [f for f in ["embd_id", "memory_type"] if f in to_update and memory_size > 0]
|
||||
if not_allowed_update:
|
||||
raise ArgumentException(f"Can't update {not_allowed_update} when memory isn't empty.")
|
||||
if "memory_type" in to_update:
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
#
|
||||
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
import os
|
||||
import logging
|
||||
|
||||
from common.constants import ActiveStatusEnum, LLMType
|
||||
from common.settings import FACTORY_LLM_INFOS
|
||||
from api.db.services.tenant_model_provider_service import TenantModelProviderService
|
||||
from api.db.services.tenant_model_instance_service import TenantModelInstanceService
|
||||
from api.db.services.tenant_model_service import TenantModelService
|
||||
from api.db.services.user_service import TenantService
|
||||
|
||||
# Mapping from model_type string to Tenant model field name
|
||||
MODEL_TYPE_TO_FIELD = {
|
||||
"chat": "llm_id",
|
||||
"embedding": "embd_id",
|
||||
"rerank": "rerank_id",
|
||||
"asr": "asr_id",
|
||||
"vision": "img2txt_id",
|
||||
"tts": "tts_id",
|
||||
"ocr": "ocr_id",
|
||||
}
|
||||
|
||||
MODEL_TAG_TO_TYPE = {
|
||||
"chat": "chat",
|
||||
"embedding": "embedding",
|
||||
"rerank": "rerank",
|
||||
"asr": "speech2text",
|
||||
"vision": "image2text",
|
||||
"tts": "tts",
|
||||
"ocr": "ocr",
|
||||
}
|
||||
|
||||
|
||||
def _get_model_info(tenant_id: str, default_model: str, model_type: str):
|
||||
"""
|
||||
Parse a composite model string (modelName@instanceName@providerName or modelName@providerName)
|
||||
and validate that the provider, instance, and model exist.
|
||||
|
||||
Returns a dict with model info or None on error.
|
||||
"""
|
||||
if not default_model:
|
||||
return None
|
||||
|
||||
parts = default_model.split("@")
|
||||
if len(parts) == 3:
|
||||
model_name, instance_name, provider_name = parts
|
||||
elif len(parts) == 2:
|
||||
model_name, provider_name = parts
|
||||
instance_name = "default"
|
||||
else:
|
||||
logging.warning(f"Invalid model string: {default_model}")
|
||||
return None
|
||||
|
||||
model_type = MODEL_TAG_TO_TYPE.get(model_type, model_type)
|
||||
# Special case: OCR with infiniflow@default@deepdoc is always enabled
|
||||
if model_type == "ocr" and provider_name == "infiniflow" and instance_name == "default" and model_name == "deepdoc":
|
||||
return {
|
||||
"model_provider": provider_name,
|
||||
"model_instance": instance_name,
|
||||
"model_name": model_name,
|
||||
"model_type": model_type,
|
||||
"enable": True,
|
||||
}
|
||||
|
||||
# Check if the provider exists for the tenant
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
if not provider_obj:
|
||||
logging.warning(f"Provider '{provider_name}' not found for tenant '{tenant_id}'")
|
||||
return None
|
||||
|
||||
# Check if the instance exists
|
||||
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name)
|
||||
if not instance_obj:
|
||||
logging.warning(f"Instance '{instance_name}' not found for provider '{provider_name}'")
|
||||
return None
|
||||
|
||||
# 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.INACTIVE.value
|
||||
|
||||
if not enable:
|
||||
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 target_llm[0].get("model_type") != model_type:
|
||||
logging.warning(f"Model '{model_name}' isn't a {model_type} model")
|
||||
return None
|
||||
|
||||
return {
|
||||
"model_provider": provider_name,
|
||||
"model_instance": instance_name,
|
||||
"model_name": model_name,
|
||||
"model_type": model_type,
|
||||
"enable": enable,
|
||||
}
|
||||
|
||||
|
||||
def _check_model_available(tenant_id: str, provider_name: str, instance_name: str, model_name: str, model_type: str):
|
||||
"""
|
||||
Validate that a model is available for the tenant:
|
||||
- Provider exists for the tenant
|
||||
- Instance exists under the provider
|
||||
- Model is in the LLM factory info for the provider
|
||||
- Model type matches
|
||||
- Model is not disabled in TenantModel table
|
||||
|
||||
Returns (success, error_message).
|
||||
"""
|
||||
if provider_name == "infiniflow" and instance_name == "default" and model_name == "deepdoc":
|
||||
return True, None
|
||||
|
||||
if model_type == "ocr" and provider_name == "infiniflow" and instance_name == "default" and model_name == "deepdoc":
|
||||
return True, None
|
||||
|
||||
compose_profiles = os.getenv("COMPOSE_PROFILES", "")
|
||||
is_tei_builtin_embedding = (
|
||||
model_type == LLMType.EMBEDDING.value
|
||||
and "tei-" in compose_profiles
|
||||
and model_name == os.getenv("TEI_MODEL", "")
|
||||
and (provider_name == "Builtin" or provider_name is None)
|
||||
)
|
||||
if is_tei_builtin_embedding:
|
||||
return True, None
|
||||
|
||||
# Check provider
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
if not provider_obj:
|
||||
return False, f"Provider '{provider_name}' not found"
|
||||
|
||||
# Check instance
|
||||
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name)
|
||||
if not instance_obj:
|
||||
return False, f"Instance '{instance_name}' not found for provider '{provider_name}'"
|
||||
|
||||
# Check model schema
|
||||
factory_info = [f for f in (FACTORY_LLM_INFOS or []) if f["name"] == provider_name]
|
||||
if not factory_info:
|
||||
return False, f"Provider '{provider_name}' not found in factory info"
|
||||
model_type = MODEL_TAG_TO_TYPE.get(model_type, model_type)
|
||||
# Check if model is disabled
|
||||
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 == "inactive":
|
||||
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 target_llm[0].get("model_type") != model_type:
|
||||
return False, f"Model '{model_name}' isn't a {model_type} model"
|
||||
|
||||
return True, None
|
||||
|
||||
|
||||
def list_tenant_default_models(tenant_id: str):
|
||||
"""
|
||||
List all default models for a tenant.
|
||||
|
||||
For each model type (chat, embedding, rerank, asr, vision, tts, ocr),
|
||||
reads the composite model ID string from the Tenant record and resolves
|
||||
it into provider/instance/name components.
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
e, tenant = TenantService.get_by_id(tenant_id)
|
||||
if not e:
|
||||
return False, "Tenant not found"
|
||||
|
||||
models = []
|
||||
|
||||
for model_type, field_name in MODEL_TYPE_TO_FIELD.items():
|
||||
default_model = getattr(tenant, field_name, None)
|
||||
if not default_model:
|
||||
continue
|
||||
model_info = _get_model_info(tenant_id, default_model, model_type)
|
||||
if model_info:
|
||||
models.append(model_info)
|
||||
|
||||
return True, {"models": models}
|
||||
|
||||
|
||||
def set_tenant_default_models(tenant_id: str, model_provider: str, model_instance: str, model_name: str, model_type: str):
|
||||
"""
|
||||
Set or clear a tenant default model.
|
||||
|
||||
If model_provider, model_instance, and model_name are all provided,
|
||||
validates the model and sets it as the default.
|
||||
If all three are empty, clears the default for the given model type.
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param model_provider: provider name
|
||||
:param model_instance: instance name
|
||||
:param model_name: model name
|
||||
:param model_type: model type (chat, embedding, rerank, asr, vision, tts, ocr)
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
field_name = MODEL_TYPE_TO_FIELD.get(model_type)
|
||||
if not field_name:
|
||||
return False, f"model type '{model_type}' is invalid"
|
||||
|
||||
e, tenant = TenantService.get_by_id(tenant_id)
|
||||
if not e:
|
||||
return False, "Tenant not found"
|
||||
|
||||
if not model_provider and not model_instance and not model_name:
|
||||
# Clear the default model
|
||||
default_model = ""
|
||||
elif model_provider and model_instance and model_name:
|
||||
# Validate and set the default model
|
||||
success, msg = _check_model_available(tenant_id, model_provider, model_instance, model_name, model_type)
|
||||
if not success:
|
||||
return False, msg
|
||||
default_model = f"{model_name}@{model_instance}@{model_provider}"
|
||||
else:
|
||||
return False, "model_provider, model_instance and model_name must be specified together"
|
||||
|
||||
TenantService.update_by_id(tenant_id, {field_name: default_model})
|
||||
return True, "success"
|
||||
|
||||
|
||||
def list_tenant_added_models(tenant_id: str, model_type_filter: str=None):
|
||||
"""
|
||||
List all added models for a tenant.
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param model_type_filter: model type filter (chat, embedding, rerank, asr, vision, tts, ocr)
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
e, tenant = TenantService.get_by_id(tenant_id)
|
||||
if not e:
|
||||
return False, "Tenant not found"
|
||||
|
||||
if model_type_filter:
|
||||
model_type_filter = model_type_filter.lower()
|
||||
|
||||
providers = TenantModelProviderService.get_by_tenant_id(tenant_id)
|
||||
if not providers:
|
||||
return True, []
|
||||
|
||||
provider_ids = [provider.id for provider in providers]
|
||||
instances = TenantModelInstanceService.get_by_provider_ids(provider_ids)
|
||||
if not instances:
|
||||
return True, []
|
||||
provider_instance_map: dict = {}
|
||||
provider_info_map = {provider.id: provider for provider in providers}
|
||||
for provider_instance_record in instances:
|
||||
provider_name = provider_info_map[provider_instance_record.provider_id].provider_name if provider_info_map.get(provider_instance_record.provider_id) else ""
|
||||
if provider_instance_map.get(provider_name):
|
||||
provider_instance_map[provider_name].append(provider_instance_record)
|
||||
else:
|
||||
provider_instance_map[provider_name] = [provider_instance_record]
|
||||
|
||||
model_records = TenantModelService.get_models_by_provider_ids_and_instance_ids(provider_ids, list({instance.id for instance in instances}))
|
||||
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]
|
||||
|
||||
added_models = []
|
||||
model_key_in_factory = []
|
||||
provider_names = [provider.provider_name for provider in providers]
|
||||
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"]:
|
||||
if model_type_filter and llm["model_type"] != model_type_filter:
|
||||
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]
|
||||
model_types = list(set([llm["model_type"]] + active_model_types) - set(inactive_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
|
||||
provider_id, instance_id, model_name = model_record_key.split("_")
|
||||
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 ""
|
||||
})
|
||||
|
||||
return True, added_models
|
||||
@@ -0,0 +1,558 @@
|
||||
#
|
||||
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
import json
|
||||
import logging
|
||||
|
||||
from common.constants import LLMType, ActiveStatusEnum
|
||||
from common.misc_utils import get_uuid
|
||||
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.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
|
||||
|
||||
|
||||
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.
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param all_available: whether to list all available providers
|
||||
:return: (success, result)
|
||||
"""
|
||||
if not FACTORY_LLM_INFOS:
|
||||
return False, []
|
||||
|
||||
if all_available:
|
||||
providers = []
|
||||
for factory_info in FACTORY_LLM_INFOS:
|
||||
model_types = sorted(set(
|
||||
llm["model_type"]
|
||||
for llm in factory_info.get("llm", [])
|
||||
if llm.get("model_type")
|
||||
))
|
||||
providers.append({
|
||||
"model_types": model_types,
|
||||
"name": factory_info["name"],
|
||||
"url": {
|
||||
"default": factory_info.get("url", "")
|
||||
}
|
||||
})
|
||||
return True, providers
|
||||
|
||||
# List tenant-configured providers
|
||||
factory_names = TenantModelProviderService.list_provider_names_by_tenant_id(tenant_id)
|
||||
|
||||
providers = []
|
||||
factory_info_mapping = {f["name"]: f for f in FACTORY_LLM_INFOS}
|
||||
for name in factory_names:
|
||||
if factory_info_mapping.get(name):
|
||||
factory_info = factory_info_mapping[name]
|
||||
model_types = sorted(set(
|
||||
llm["model_type"]
|
||||
for llm in factory_info.get("llm", [])
|
||||
if llm.get("model_type")
|
||||
))
|
||||
providers.append({
|
||||
"model_types": model_types,
|
||||
"name": factory_info["name"],
|
||||
"url": {
|
||||
"default": factory_info.get("url", "")
|
||||
}
|
||||
})
|
||||
|
||||
return True, providers
|
||||
|
||||
|
||||
def add_provider(tenant_id: str, provider_name: str):
|
||||
"""
|
||||
Add a provider (factory) for a tenant.
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param provider_name: provider/factory name
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
if not FACTORY_LLM_INFOS:
|
||||
return False, "No providers found"
|
||||
# Check if factory is allowed
|
||||
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"
|
||||
|
||||
existing = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
if existing:
|
||||
return False, f"Provider {provider_name} already exists"
|
||||
|
||||
TenantModelProviderService.insert(
|
||||
tenant_id=tenant_id,
|
||||
provider_name=provider_name
|
||||
)
|
||||
return True, "success"
|
||||
|
||||
|
||||
def delete_provider(tenant_id: str, provider_name: str):
|
||||
"""
|
||||
Delete all instances and models for a provider.
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param provider_name: provider/factory name
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
if not provider_obj:
|
||||
return False, f"Provider {provider_name} not found"
|
||||
instance_objs = TenantModelInstanceService.get_by_provider_id(provider_obj.id)
|
||||
if not instance_objs:
|
||||
return False, f"No instances found for provider {provider_name}"
|
||||
instance_ids = [instance_obj.id for instance_obj in instance_objs]
|
||||
delete_models_by_instance_ids(instance_ids)
|
||||
delete_instances_by_provider_ids([provider_obj.id])
|
||||
TenantModelProviderService.delete_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
return True, "success"
|
||||
|
||||
|
||||
def show_provider(provider_name: str):
|
||||
"""
|
||||
Show provider details from LLMFactories.
|
||||
|
||||
:param provider_name: provider/factory name
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
fac_list = [f for f in FACTORY_LLM_INFOS if f["name"]==provider_name]
|
||||
if not fac_list:
|
||||
return False, f"Provider '{provider_name}' not found"
|
||||
factory_info = fac_list[0]
|
||||
return True, {
|
||||
"base_url": {
|
||||
"default": factory_info.get("url", "")
|
||||
},
|
||||
"name": factory_info["name"],
|
||||
"total_models": len(factory_info.get("llm", []))
|
||||
}
|
||||
|
||||
|
||||
def list_provider_models(provider_name: str):
|
||||
"""
|
||||
List all models for a provider from the LLM dictionary.
|
||||
|
||||
:param provider_name: provider/factory name
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
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"
|
||||
llms = factory_info[0]["llm"]
|
||||
if not llms:
|
||||
return False, f"No models found for provider '{provider_name}'"
|
||||
|
||||
models = []
|
||||
for llm in llms:
|
||||
models.append({
|
||||
"name": llm["name"],
|
||||
"max_tokens": llm["max_tokens"],
|
||||
"model_types": [llm["model_type"]],
|
||||
"features": None
|
||||
})
|
||||
return True, models
|
||||
|
||||
|
||||
def show_provider_model(provider_name: str, model_name: str):
|
||||
"""
|
||||
Show a specific model for a provider.
|
||||
|
||||
:param provider_name: provider/factory name
|
||||
:param model_name: model name
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
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"
|
||||
llms = factory_info[0]["llm"]
|
||||
if not llms:
|
||||
return False, f"No models found for provider '{provider_name}'"
|
||||
target_llm = [llm for llm in llms if llm["name"] == model_name]
|
||||
if not target_llm:
|
||||
return False, f"Model '{model_name}' not found"
|
||||
llm_info = target_llm[0]
|
||||
|
||||
return True, {
|
||||
"name": llm_info["name"],
|
||||
"max_tokens": llm_info["max_tokens"],
|
||||
"model_types": [llm_info["model_type"]],
|
||||
"thinking": None,
|
||||
"model_type_map": {
|
||||
llm_info["model_type"]: True
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def create_provider_instance(tenant_id: str, provider_name: str, instance_name: str, api_key: str, base_url: str, region: str):
|
||||
"""
|
||||
Create a provider instance.
|
||||
|
||||
The instance_name parameter is accepted for API compatibility but in the old
|
||||
model all records under a factory share the same API key configuration.
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param provider_name: provider/factory name
|
||||
:param instance_name: instance name (used as a logical identifier)
|
||||
:param api_key: API key
|
||||
:param base_url: base url
|
||||
:param region: region
|
||||
: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'"
|
||||
|
||||
# Check if provider exists in the system
|
||||
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"
|
||||
|
||||
if api_key:
|
||||
same_key_instance = TenantModelInstanceService.get_by_provider_id_and_api_key(provider_obj.id, api_key)
|
||||
if same_key_instance:
|
||||
return False, f"Already exist instance: {same_key_instance.instance_name} with api_key {api_key}"
|
||||
|
||||
import json
|
||||
extra_fields = {}
|
||||
if base_url:
|
||||
extra_fields["base_url"] = base_url
|
||||
if region:
|
||||
extra_fields["region"] = region
|
||||
TenantModelInstanceService.create_instance(provider_id=provider_obj.id,instance_name=instance_name,api_key=api_key, extra=json.dumps(extra_fields))
|
||||
|
||||
return True, "success"
|
||||
|
||||
|
||||
def list_provider_instances(tenant_id: str, provider_name: str):
|
||||
"""
|
||||
List provider instances for a tenant.
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param provider_name: provider/factory name
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
import json
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
if not provider_obj:
|
||||
return False, f"No provider found for provider '{provider_name}'"
|
||||
provider_id = provider_obj.id
|
||||
instance_objs = TenantModelInstanceService.get_all_by_provider_id(provider_id)
|
||||
if not instance_objs:
|
||||
return True, []
|
||||
instances = []
|
||||
for instance_obj in instance_objs:
|
||||
extra_fields = json.loads(instance_obj.extra) if instance_obj.extra else {}
|
||||
instances.append({
|
||||
"api_key": instance_obj.api_key,
|
||||
"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, instances
|
||||
|
||||
|
||||
def show_provider_instance(tenant_id: str, provider_name: str, instance_name: str):
|
||||
"""
|
||||
Show a specific provider instance.
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param provider_name: provider/factory name
|
||||
:param instance_name: instance name
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
if not provider_obj:
|
||||
return False, f"No provider found for provider '{provider_name}'"
|
||||
provider_id = provider_obj.id
|
||||
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_id, instance_name)
|
||||
if not instance_obj:
|
||||
return False, f"No instance found for provider '{provider_name}' and instance '{instance_name}'"
|
||||
|
||||
import json
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
def drop_provider_instances(tenant_id: str, provider_name: str, instance_names: list):
|
||||
"""
|
||||
Drop provider instances.
|
||||
for the specified models/instances.
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param provider_name: provider/factory name
|
||||
:param instance_names: list of instance names to drop
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
if not provider_obj:
|
||||
return False, f"No provider found for provider '{provider_name}'"
|
||||
provider_id = provider_obj.id
|
||||
not_exist_instances = []
|
||||
instance_ids = []
|
||||
for instance_name in instance_names:
|
||||
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_id, instance_name)
|
||||
if not instance_obj:
|
||||
not_exist_instances.append(instance_name)
|
||||
continue
|
||||
instance_ids.append(instance_obj.id)
|
||||
if not_exist_instances:
|
||||
return False, f"No instance found for provider '{provider_name}' and instance '{not_exist_instances}'"
|
||||
delete_models_by_instance_ids(instance_ids)
|
||||
TenantModelInstanceService.delete_by_ids(instance_ids)
|
||||
return True, None
|
||||
|
||||
|
||||
def list_instance_models(tenant_id: str, provider_name: str, instance_name: str, supported_only: bool = False):
|
||||
"""
|
||||
List models for a provider instance.
|
||||
|
||||
Follows the Go version's logic:
|
||||
- Reads tenant_model table to determine disabled models (records exist = disabled).
|
||||
- Lists all models from the LLM dictionary for the provider.
|
||||
- Models present in tenant_model table are marked "inactive", others "active".
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param provider_name: provider/factory name
|
||||
:param instance_name: instance name
|
||||
:param supported_only: if True, only list supported models (from LLM dictionary)
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
if not provider_obj:
|
||||
return False, f"No provider found for provider '{provider_name}'"
|
||||
|
||||
if supported_only:
|
||||
# List all models supported by this provider from the LLM dictionary
|
||||
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"
|
||||
llms = factory_info[0].get("llm", [])
|
||||
models = [{"name": llm["llm_name"]} for llm in llms]
|
||||
return True, models
|
||||
|
||||
# Get instance
|
||||
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name)
|
||||
if not instance_obj:
|
||||
return False, f"No instance found for provider '{provider_name}' and instance '{instance_name}'"
|
||||
|
||||
# Get model records for this instance from tenant_model table
|
||||
model_records = TenantModelService.get_models_by_instance_id(instance_obj.id)
|
||||
# Build a map of model_name -> status, type
|
||||
model_info_map: dict = {}
|
||||
for model_record in model_records:
|
||||
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
|
||||
}
|
||||
|
||||
# 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"
|
||||
|
||||
llms = factory_info[0].get("llm", [])
|
||||
models = []
|
||||
for llm in llms:
|
||||
models.append({
|
||||
"name": llm["llm_name"],
|
||||
"model_type": [llm["model_type"]] + model_info_map.get(llm["llm_name"], {}).get("model_type", []),
|
||||
"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": model_info_dict["model_type"],
|
||||
"max_tokens": extra_fields.get("max_tokens", 8192),
|
||||
"status": model_info_dict["status"],
|
||||
})
|
||||
|
||||
return True, models
|
||||
|
||||
|
||||
def add_model_to_instance(tenant_id: str, provider_name: str, instance_name: str, model_name: str, model_type: str|list[str], max_tokens: int, extra: dict):
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
if not provider_obj:
|
||||
return False, f"No provider found for provider '{provider_name}'"
|
||||
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name)
|
||||
if not instance_obj:
|
||||
return False, f"No instance found for provider '{provider_name}' and instance '{instance_name}'"
|
||||
model_obj = TenantModelService.get_by_provider_id_and_instance_id_and_model_name(provider_obj.id, instance_obj.id, model_name)
|
||||
if model_obj:
|
||||
return False, f"Model '{model_name}' already exists for provider '{provider_name}' and instance '{instance_name}'"
|
||||
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"
|
||||
llms = factory_info[0].get("llm", [])
|
||||
if isinstance(model_type, str):
|
||||
model_type = [model_type]
|
||||
|
||||
import json
|
||||
|
||||
for _type in model_type:
|
||||
extra_fields = {"max_tokens": max_tokens}
|
||||
target_model = [llm for llm in llms if llm["model_type"] == _type and llm["llm_name"] == model_name]
|
||||
if target_model:
|
||||
extra_fields.update({"is_tool": target_model[0].get("is_tool", False)})
|
||||
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)
|
||||
)
|
||||
|
||||
return True, "success"
|
||||
|
||||
|
||||
def update_model_status(tenant_id: str, provider_name: str, instance_name: str, model_name: str, status: str):
|
||||
"""
|
||||
Enable or disable a model for a provider instance.
|
||||
|
||||
- If the model record exists in tenant_model, update its status.
|
||||
- If the model record does not exist:
|
||||
- status="active": no need to add a record (default is active/enabled).
|
||||
- status="inactive": create a record with status="inactive".
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param provider_name: provider/factory name
|
||||
:param instance_name: instance name
|
||||
:param model_name: model name
|
||||
:param status: "active" or "inactive" (ActiveStatusEnum values)
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
if 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
|
||||
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
|
||||
if not provider_obj:
|
||||
return False, f"No provider found for provider '{provider_name}'"
|
||||
|
||||
# Check if instance exists
|
||||
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name)
|
||||
if not instance_obj:
|
||||
return False, f"No instance found for provider '{provider_name}' and instance '{instance_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
|
||||
)
|
||||
|
||||
if model_obj_list:
|
||||
# Model record exists — update its status
|
||||
TenantModelService.batch_update_model_status([m.id for m in model_obj_list], 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_name]
|
||||
if not factory_info:
|
||||
return False, f"Provider '{provider_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_name} model {model_name} not found"
|
||||
|
||||
TenantModelService.insert(
|
||||
id=get_uuid(),
|
||||
model_name=model_name,
|
||||
model_type=target_llm[0]["model_type"],
|
||||
provider_id=provider_obj.id,
|
||||
instance_id=instance_obj.id,
|
||||
status=status,
|
||||
)
|
||||
|
||||
return True, None
|
||||
|
||||
|
||||
async def chat_to_model(tenant_id: str, provider_name: str, instance_name: str, model_name: str, message: str, stream: bool = False, thinking: bool = False):
|
||||
"""
|
||||
Chat to a model.
|
||||
|
||||
:param tenant_id: tenant ID
|
||||
:param provider_name: provider/factory name
|
||||
:param instance_name: instance name
|
||||
:param model_name: model name
|
||||
:param message: chat message
|
||||
:param stream: whether to stream the response
|
||||
:param thinking: whether to enable thinking/reasoning
|
||||
:return: (success, result_or_error_message)
|
||||
"""
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
|
||||
# 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)
|
||||
except LookupError:
|
||||
return False, f"Model '{composite_name}' not authorized"
|
||||
|
||||
if not model_config:
|
||||
return False, f"Model '{composite_name}' not found"
|
||||
|
||||
llm = LLMBundle(tenant_id, model_config)
|
||||
|
||||
if stream:
|
||||
return True, {"type": "stream", "llm": llm, "model_config": model_config}
|
||||
|
||||
# Non-streaming chat
|
||||
try:
|
||||
response = await llm.async_chat(
|
||||
None,
|
||||
[{"role": "user", "content": message}],
|
||||
{"temperature": 0.9},
|
||||
)
|
||||
result = {
|
||||
"answer": response,
|
||||
"reasoning_content": "",
|
||||
}
|
||||
return True, result
|
||||
except Exception as e:
|
||||
logging.exception(f"Chat to model failed: {e}")
|
||||
return False, str(e)
|
||||
@@ -763,6 +763,7 @@ class Tenant(DataBaseModel):
|
||||
tenant_rerank_id = IntegerField(null=True, help_text="id in tenant_llm", 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)
|
||||
ocr_id = CharField(max_length=256, null=True, help_text="default OCR model ID", 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)
|
||||
@@ -1350,6 +1351,67 @@ class SystemSettings(DataBaseModel):
|
||||
class Meta:
|
||||
db_table = "system_settings"
|
||||
|
||||
class TenantModelProvider(DataBaseModel):
|
||||
id = CharField(max_length=32, primary_key=True)
|
||||
provider_name = CharField(max_length=128, null=False, index=False, help_text="LLM provider name")
|
||||
tenant_id = CharField(max_length=32, null=False, index=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "tenant_model_provider"
|
||||
indexes = (
|
||||
(("tenant_id", "provider_name"), True),
|
||||
)
|
||||
|
||||
class TenantModelInstance(DataBaseModel):
|
||||
id = CharField(max_length=32, primary_key=True)
|
||||
instance_name = CharField(max_length=128, null=False, index=False, help_text="Model instance name")
|
||||
provider_id = CharField(max_length=32, null=False, index=False)
|
||||
api_key = CharField(max_length=512, null=False, index=False, help_text="API key")
|
||||
status = CharField(max_length=32, default="active", index=False)
|
||||
extra = CharField(max_length=512, default="{}", index=False)
|
||||
|
||||
class Meta:
|
||||
db_table = "tenant_model_instance"
|
||||
indexes = (
|
||||
(("api_key", "provider_id"), True),
|
||||
)
|
||||
|
||||
|
||||
class TenantModel(DataBaseModel):
|
||||
id = CharField(max_length=32, primary_key=True)
|
||||
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")
|
||||
status = CharField(max_length=32, default="active", index=False)
|
||||
extra = CharField(max_length=1024, default="{}", index=False)
|
||||
|
||||
class Meta:
|
||||
db_table = "tenant_model"
|
||||
|
||||
|
||||
class TenantModelGroup(DataBaseModel):
|
||||
id = CharField(max_length=32, primary_key=True)
|
||||
group_type = CharField(max_length=32, null=False, index=False, help_text="Group type")
|
||||
model_name = CharField(max_length=128, null=True, index=False, help_text="Model name")
|
||||
strategy = CharField(max_length=32, default="weighted", index=False, help_text="Routing strategy")
|
||||
|
||||
class Meta:
|
||||
db_table = "tenant_model_group"
|
||||
|
||||
class TenantModelGroupMapping(DataBaseModel):
|
||||
group_id = CharField(max_length=32, null=False, index=True, help_text="Group ID")
|
||||
provider_id = CharField(max_length=32, null=False, index=False)
|
||||
instance_id = CharField(max_length=32, null=False, index=False)
|
||||
model_id = CharField(max_length=32, null=False, index=True)
|
||||
weight = IntegerField(default=100, index=False, help_text="Routing weight")
|
||||
status = CharField(max_length=32, default="active", index=False)
|
||||
|
||||
class Meta:
|
||||
db_table = "tenant_model_group_mapping"
|
||||
primary_key = CompositeKey("group_id", "provider_id", "instance_id", "model_id")
|
||||
|
||||
|
||||
def alter_db_add_column(migrator, table_name, column_name, column_type):
|
||||
try:
|
||||
migrate(migrator.add_column(table_name, column_name, column_type))
|
||||
@@ -1668,6 +1730,7 @@ def migrate_db():
|
||||
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))
|
||||
alter_db_column_type(migrator, "document", "size", BigIntegerField(default=0, index=True))
|
||||
alter_db_column_type(migrator, "file", "size", BigIntegerField(default=0, index=True))
|
||||
alter_db_add_column(migrator, "tenant", "ocr_id", CharField(max_length=128, null=True, help_text="default ocr model ID", index=True))
|
||||
logging.disable(logging.NOTSET)
|
||||
# this is after re-enabling logging to allow logging changed user emails
|
||||
migrate_add_unique_email(migrator)
|
||||
+5
-104
@@ -24,17 +24,15 @@ from copy import deepcopy
|
||||
from peewee import IntegrityError
|
||||
|
||||
from api.db import UserTenantRole
|
||||
from api.db.db_models import init_database_tables as init_web_db, LLMFactories, LLM, TenantLLM, Knowledgebase, Dialog, Memory
|
||||
from api.db.db_models import init_database_tables as init_web_db, LLMFactories, LLM, TenantLLM
|
||||
from api.db.services import UserService
|
||||
from api.db.services.canvas_service import CanvasTemplateService
|
||||
from api.db.services.document_service import DocumentService
|
||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from api.db.services.memory_service import MemoryService
|
||||
from api.db.services.tenant_llm_service import LLMFactoriesService, TenantLLMService
|
||||
from api.db.services.llm_service import LLMService, LLMBundle, get_init_tenant_llm
|
||||
from api.db.services.user_service import TenantService, UserTenantService
|
||||
from api.db.services.system_settings_service import SystemSettingsService
|
||||
from api.db.services.dialog_service import DialogService
|
||||
from api.db.template_utils import normalize_canvas_template_categories
|
||||
from api.db.joint_services.memory_message_service import init_message_id_sequence, init_memory_size_cache, fix_missing_tokenized_memory
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type
|
||||
@@ -154,6 +152,8 @@ def init_llm_factory():
|
||||
except Exception:
|
||||
pass
|
||||
break
|
||||
|
||||
def update_document_number_in_init():
|
||||
doc_count = DocumentService.get_all_kb_doc_count()
|
||||
for kb_id in KnowledgebaseService.get_all_ids():
|
||||
KnowledgebaseService.update_document_number_in_init(kb_id=kb_id, doc_num=doc_count.get(kb_id, 0))
|
||||
@@ -189,7 +189,8 @@ def init_web_data():
|
||||
|
||||
init_table()
|
||||
|
||||
init_llm_factory()
|
||||
# init_llm_factory()
|
||||
update_document_number_in_init()
|
||||
# if not UserService.get_all().count():
|
||||
# init_superuser()
|
||||
|
||||
@@ -197,7 +198,6 @@ def init_web_data():
|
||||
init_message_id_sequence()
|
||||
init_memory_size_cache()
|
||||
fix_missing_tokenized_memory()
|
||||
fix_empty_tenant_model_id()
|
||||
logging.info("init web data success:{}".format(time.time() - start_time))
|
||||
|
||||
def init_table():
|
||||
@@ -226,105 +226,6 @@ def init_table():
|
||||
raise e
|
||||
|
||||
|
||||
def fix_empty_tenant_model_id():
|
||||
# knowledgebase
|
||||
empty_tenant_embd_id_kbs = KnowledgebaseService.get_null_tenant_embd_id_row()
|
||||
if empty_tenant_embd_id_kbs:
|
||||
logging.info(f"Found {len(empty_tenant_embd_id_kbs)} empty tenant_embd_id knowledgebase.")
|
||||
kb_groups: dict = {}
|
||||
for obj in empty_tenant_embd_id_kbs:
|
||||
if kb_groups.get((obj.tenant_id, obj.embd_id)):
|
||||
kb_groups[(obj.tenant_id, obj.embd_id)].append(obj.id)
|
||||
else:
|
||||
kb_groups[(obj.tenant_id, obj.embd_id)] = [obj.id]
|
||||
update_cnt = 0
|
||||
for k, v in kb_groups.items():
|
||||
tenant_llm = TenantLLMService.get_api_key(k[0], k[1])
|
||||
if tenant_llm:
|
||||
update_cnt += KnowledgebaseService.filter_update([Knowledgebase.id.in_(v)], {"tenant_embd_id": tenant_llm.id})
|
||||
logging.info(f"Update {update_cnt} tenant_embd_id in table knowledgebase.")
|
||||
# dialog
|
||||
empty_tenant_llm_id_dialog = DialogService.get_null_tenant_llm_id_row()
|
||||
if empty_tenant_llm_id_dialog:
|
||||
logging.info(f"Found {len(empty_tenant_llm_id_dialog)} empty tenant_llm_id dialogs.")
|
||||
dialog_groups: dict = {}
|
||||
for obj in empty_tenant_llm_id_dialog:
|
||||
if dialog_groups.get((obj.tenant_id, obj.llm_id)):
|
||||
dialog_groups[(obj.tenant_id, obj.llm_id)].append(obj.id)
|
||||
else:
|
||||
dialog_groups[(obj.tenant_id, obj.llm_id)] = [obj.id]
|
||||
update_cnt = 0
|
||||
for k, v in dialog_groups.items():
|
||||
tenant_llm = TenantLLMService.get_api_key(k[0], k[1])
|
||||
if tenant_llm:
|
||||
update_cnt += DialogService.filter_update([Dialog.id.in_(v)], {"tenant_llm_id": tenant_llm.id})
|
||||
logging.info(f"Update {update_cnt} tenant_llm_id in table dialog.")
|
||||
|
||||
empty_tenant_rerank_id_dialog = DialogService.get_null_tenant_rerank_id_row()
|
||||
if empty_tenant_rerank_id_dialog:
|
||||
logging.info(f"Found {len(empty_tenant_rerank_id_dialog)} empty tenant_rerank_id dialogs.")
|
||||
dialog_groups: dict = {}
|
||||
for obj in empty_tenant_rerank_id_dialog:
|
||||
if dialog_groups.get((obj.tenant_id, obj.rerank_id)):
|
||||
dialog_groups[(obj.tenant_id, obj.rerank_id)].append(obj.id)
|
||||
else:
|
||||
dialog_groups[(obj.tenant_id, obj.rerank_id)] = [obj.id]
|
||||
update_cnt = 0
|
||||
for k, v in dialog_groups.items():
|
||||
tenant_llm = TenantLLMService.get_api_key(k[0], k[1])
|
||||
if tenant_llm:
|
||||
update_cnt += DialogService.filter_update([Dialog.id.in_(v)], {"tenant_rerank_id": tenant_llm.id})
|
||||
logging.info(f"Update {update_cnt} tenant_rerank_id in table dialog.")
|
||||
# memory
|
||||
empty_tenant_embd_id_memories = MemoryService.get_null_tenant_embd_id_row()
|
||||
if empty_tenant_embd_id_memories:
|
||||
logging.info(f"Found {len(empty_tenant_embd_id_memories)} empty tenant_embd_id memories.")
|
||||
memory_groups: dict = {}
|
||||
for obj in empty_tenant_embd_id_memories:
|
||||
if memory_groups.get((obj.tenant_id, obj.embd_id)):
|
||||
memory_groups[(obj.tenant_id, obj.embd_id)].append(obj.id)
|
||||
else:
|
||||
memory_groups[(obj.tenant_id, obj.embd_id)] = [obj.id]
|
||||
update_cnt = 0
|
||||
for k, v in memory_groups.items():
|
||||
tenant_llm = TenantLLMService.get_api_key(k[0], k[1])
|
||||
if tenant_llm:
|
||||
update_cnt += MemoryService.filter_update([Memory.id.in_(v)], {"tenant_embd_id": tenant_llm.id})
|
||||
logging.info(f"Update {update_cnt} tenant_embd_id in table memory.")
|
||||
|
||||
empty_tenant_llm_id_memories = MemoryService.get_null_tenant_llm_id_row()
|
||||
if empty_tenant_llm_id_memories:
|
||||
logging.info(f"Found {len(empty_tenant_llm_id_memories)} empty tenant_llm_id memories.")
|
||||
memory_groups: dict = {}
|
||||
for obj in empty_tenant_llm_id_memories:
|
||||
if memory_groups.get((obj.tenant_id, obj.llm_id)):
|
||||
memory_groups[(obj.tenant_id, obj.llm_id)].append(obj.id)
|
||||
else:
|
||||
memory_groups[(obj.tenant_id, obj.llm_id)] = [obj.id]
|
||||
update_cnt = 0
|
||||
for k, v in memory_groups.items():
|
||||
tenant_llm = TenantLLMService.get_api_key(k[0], k[1])
|
||||
if tenant_llm:
|
||||
update_cnt += MemoryService.filter_update([Memory.id.in_(v)], {"tenant_llm_id": tenant_llm.id})
|
||||
logging.info(f"Update {update_cnt} tenant_llm_id in table memory.")
|
||||
# tenant
|
||||
empty_tenant_model_id_tenants = TenantService.get_null_tenant_model_id_rows()
|
||||
if empty_tenant_model_id_tenants:
|
||||
logging.info(f"Found {len(empty_tenant_model_id_tenants)} empty tenant_model_id tenants.")
|
||||
update_cnt = 0
|
||||
for obj in empty_tenant_model_id_tenants:
|
||||
tenant_dict = obj.to_dict()
|
||||
update_dict = {}
|
||||
for key in ["llm_id", "embd_id", "asr_id", "img2txt_id", "rerank_id", "tts_id"]:
|
||||
if tenant_dict.get(key) and not tenant_dict.get(f"tenant_{key}"):
|
||||
tenant_model = TenantLLMService.get_api_key(tenant_dict["id"], tenant_dict[key])
|
||||
if tenant_model:
|
||||
update_dict.update({f"tenant_{key}": tenant_model.id})
|
||||
if update_dict:
|
||||
update_cnt += TenantService.update_by_id(tenant_dict["id"], update_dict)
|
||||
logging.info(f"Update {update_cnt} tenant_model_id in table tenant.")
|
||||
logging.info("Fix empty tenant_model_id done.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
init_web_db()
|
||||
init_web_data()
|
||||
@@ -26,7 +26,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_by_id, get_model_config_by_type_and_name
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance
|
||||
from api.utils.memory_utils import get_memory_type_human
|
||||
from memory.services.messages import MessageService
|
||||
from memory.services.query import MsgTextQuery, get_vector
|
||||
@@ -153,14 +153,7 @@ async def extract_by_llm(tenant_id: str, tenant_llm_id: int, extract_conf: dict,
|
||||
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)})
|
||||
if tenant_llm_id:
|
||||
llm_config = get_model_config_by_id(
|
||||
tenant_llm_id,
|
||||
allowed_tenant_ids=tenant_id,
|
||||
requester_tenant_id=tenant_id,
|
||||
)
|
||||
else:
|
||||
llm_config = get_model_config_by_type_and_name(tenant_id, LLMType.CHAT, llm_id)
|
||||
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."})
|
||||
@@ -177,14 +170,7 @@ async def extract_by_llm(tenant_id: str, tenant_llm_id: int, extract_conf: dict,
|
||||
|
||||
|
||||
async def embed_and_save(memory, message_list: list[dict], task_id: str=None):
|
||||
if memory.tenant_embd_id:
|
||||
embd_model_config = get_model_config_by_id(
|
||||
memory.tenant_embd_id,
|
||||
allowed_tenant_ids=memory.tenant_id,
|
||||
requester_tenant_id=memory.tenant_id,
|
||||
)
|
||||
else:
|
||||
embd_model_config = get_model_config_by_type_and_name(memory.tenant_id, LLMType.EMBEDDING, memory.embd_id)
|
||||
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."})
|
||||
@@ -255,14 +241,7 @@ def query_message(filter_dict: dict, params: dict):
|
||||
question = params["query"]
|
||||
question = question.strip()
|
||||
memory = memory_list[0]
|
||||
if memory.tenant_embd_id:
|
||||
embd_model_config = get_model_config_by_id(
|
||||
memory.tenant_embd_id,
|
||||
allowed_tenant_ids=memory.tenant_id,
|
||||
requester_tenant_id=memory.tenant_id,
|
||||
)
|
||||
else:
|
||||
embd_model_config = get_model_config_by_type_and_name(memory.tenant_id, LLMType.EMBEDDING, memory.embd_id)
|
||||
embd_model_config = get_model_config_from_provider_instance(memory.tenant_id, LLMType.EMBEDDING, memory.embd_id)
|
||||
embd_model = LLMBundle(memory.tenant_id, embd_model_config)
|
||||
match_dense = get_vector(question, embd_model, similarity=params["similarity_threshold"])
|
||||
match_text, _ = MsgTextQuery().question(question, min_match=params["similarity_threshold"])
|
||||
|
||||
@@ -17,131 +17,15 @@ import logging
|
||||
import os
|
||||
import enum
|
||||
from common import settings
|
||||
from common.constants import LLMType
|
||||
from api.db.services.llm_service import LLMService
|
||||
from common.constants import LLMType, ActiveStatusEnum
|
||||
from api.db.services.tenant_llm_service import TenantLLMService, 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
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_model_config_by_id(
|
||||
tenant_model_id: int,
|
||||
allowed_tenant_ids: str | list[str] | set[str] | tuple[str, ...] | None = None,
|
||||
requester_tenant_id: str | None = None,
|
||||
) -> dict:
|
||||
found, model_config = TenantLLMService.get_by_id(tenant_model_id)
|
||||
if not found:
|
||||
raise LookupError(f"Tenant Model with id {tenant_model_id} not found")
|
||||
if allowed_tenant_ids is not None:
|
||||
if isinstance(allowed_tenant_ids, str):
|
||||
allowed_tenant_ids = {allowed_tenant_ids}
|
||||
else:
|
||||
allowed_tenant_ids = {str(tenant_id) for tenant_id in allowed_tenant_ids if tenant_id}
|
||||
if str(model_config.tenant_id) not in allowed_tenant_ids:
|
||||
logger.warning(
|
||||
"Denied tenant model access: tenant_model_id=%s model_tenant_id=%s "
|
||||
"allowed_tenant_ids=%s requester_tenant_id=%s",
|
||||
tenant_model_id,
|
||||
model_config.tenant_id,
|
||||
sorted(allowed_tenant_ids),
|
||||
requester_tenant_id,
|
||||
)
|
||||
raise LookupError(f"Tenant Model with id {tenant_model_id} not authorized")
|
||||
config_dict = model_config.to_dict()
|
||||
api_key, is_tools, api_key_payload = TenantLLMService._decode_api_key_config(config_dict.get("api_key", ""))
|
||||
config_dict["api_key"] = api_key
|
||||
if api_key_payload is not None:
|
||||
config_dict["api_key_payload"] = api_key_payload
|
||||
if is_tools is not None:
|
||||
config_dict["is_tools"] = is_tools
|
||||
llm = LLMService.query(llm_name=config_dict["llm_name"])
|
||||
if "is_tools" not in config_dict and llm:
|
||||
config_dict["is_tools"] = llm[0].is_tools
|
||||
return config_dict
|
||||
|
||||
|
||||
def get_model_config_by_type_and_name(tenant_id: str, model_type: str, model_name: str):
|
||||
if not model_name:
|
||||
raise Exception("Model Name is required")
|
||||
model_type_val = model_type.value if hasattr(model_type, "value") else model_type
|
||||
model_config = TenantLLMService.get_api_key(tenant_id, model_name, model_type_val)
|
||||
if not model_config:
|
||||
# model_name in format 'name@factory', split model_name and try again
|
||||
pure_model_name, fid = TenantLLMService.split_model_name_and_factory(model_name)
|
||||
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 (fid == "Builtin" or fid is None)
|
||||
)
|
||||
if is_tei_builtin_embedding:
|
||||
# configured local embedding model
|
||||
embedding_cfg = settings.EMBEDDING_CFG
|
||||
config_dict = {
|
||||
"llm_factory": "Builtin",
|
||||
"api_key": embedding_cfg["api_key"],
|
||||
"llm_name": pure_model_name,
|
||||
"api_base": embedding_cfg["base_url"],
|
||||
"model_type": LLMType.EMBEDDING.value,
|
||||
}
|
||||
elif model_type_val == LLMType.CHAT.value:
|
||||
# Retry as CHAT with pure_model_name first; then fall back to a multimodal model registered under IMAGE2TEXT.
|
||||
model_config = TenantLLMService.get_api_key(tenant_id, pure_model_name, LLMType.CHAT.value)
|
||||
if not model_config:
|
||||
model_config = TenantLLMService.get_api_key(tenant_id, pure_model_name, LLMType.IMAGE2TEXT.value)
|
||||
if not model_config:
|
||||
raise LookupError(f"Tenant Model with name {model_name} and type {model_type_val} not found")
|
||||
config_dict = model_config.to_dict()
|
||||
elif model_type_val == LLMType.IMAGE2TEXT.value:
|
||||
model_config = TenantLLMService.get_api_key(tenant_id, pure_model_name, LLMType.IMAGE2TEXT.value)
|
||||
if not model_config:
|
||||
# Fall back to a chat model only if it has declared IMAGE2TEXT capability (tag check via llm table)
|
||||
chat_config = TenantLLMService.get_api_key(tenant_id, pure_model_name, LLMType.CHAT.value)
|
||||
logger.debug("IMAGE2TEXT config not found for %s; chat_config found: %s", pure_model_name, chat_config is not None)
|
||||
if chat_config:
|
||||
llm_entry = LLMService.query(fid=chat_config.llm_factory, llm_name=chat_config.llm_name)
|
||||
tags = [t.strip() for t in (llm_entry[0].tags or "").split(",")] if llm_entry else []
|
||||
logger.debug("LLM tags for %s/%s: %s", chat_config.llm_factory, chat_config.llm_name, tags)
|
||||
if "IMAGE2TEXT" in tags:
|
||||
logger.debug("Promoting chat config to IMAGE2TEXT for %s", pure_model_name)
|
||||
model_config = chat_config
|
||||
if not model_config:
|
||||
raise LookupError(f"Tenant Model with name {model_name} and type {model_type_val} not found")
|
||||
config_dict = model_config.to_dict()
|
||||
config_dict["model_type"] = LLMType.IMAGE2TEXT.value
|
||||
else:
|
||||
model_config = TenantLLMService.get_api_key(tenant_id, pure_model_name, model_type_val)
|
||||
if not model_config:
|
||||
raise LookupError(f"Tenant Model with name {model_name} and type {model_type_val} not found")
|
||||
config_dict = model_config.to_dict()
|
||||
else:
|
||||
# model_name without @factory
|
||||
config_dict = model_config.to_dict()
|
||||
api_key, is_tools, api_key_payload = TenantLLMService._decode_api_key_config(config_dict.get("api_key", ""))
|
||||
config_dict["api_key"] = api_key
|
||||
if api_key_payload is not None:
|
||||
config_dict["api_key_payload"] = api_key_payload
|
||||
if is_tools is not None:
|
||||
config_dict["is_tools"] = is_tools
|
||||
config_model_type = config_dict.get("model_type")
|
||||
config_model_type = config_model_type.value if hasattr(config_model_type, "value") else config_model_type
|
||||
if config_model_type != model_type_val and not (
|
||||
model_type_val == LLMType.CHAT.value
|
||||
and config_model_type == LLMType.IMAGE2TEXT.value
|
||||
) and not (
|
||||
model_type_val == LLMType.IMAGE2TEXT.value
|
||||
and config_model_type == LLMType.CHAT.value
|
||||
):
|
||||
raise LookupError(
|
||||
f"Tenant Model with name {model_name} has type {config_model_type}, expected {model_type_val}"
|
||||
)
|
||||
llm = LLMService.query(llm_name=config_dict["llm_name"])
|
||||
if "is_tools" not in config_dict and llm:
|
||||
config_dict["is_tools"] = llm[0].is_tools
|
||||
return config_dict
|
||||
|
||||
|
||||
def get_tenant_default_model_by_type(tenant_id: str, model_type: str|enum.Enum):
|
||||
exist, tenant = TenantService.get_by_id(tenant_id)
|
||||
if not exist:
|
||||
@@ -167,4 +51,135 @@ 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.")
|
||||
return get_model_config_by_type_and_name(tenant_id, model_type, model_name)
|
||||
return get_model_config_from_provider_instance(tenant_id, model_type, model_name)
|
||||
|
||||
|
||||
def split_model_name(model_name: str):
|
||||
# Parse model_name: {model_name} or {model_name}@{factory_name} or {model_name}@{instance_name}@{factory_name}
|
||||
parts = model_name.split("@")
|
||||
if len(parts) == 1:
|
||||
pure_model_name = parts[0]
|
||||
provider_name = ""
|
||||
instance_name = ""
|
||||
elif len(parts) == 2:
|
||||
pure_model_name = parts[0]
|
||||
provider_name = parts[1]
|
||||
instance_name = "default"
|
||||
else:
|
||||
pure_model_name = parts[0]
|
||||
instance_name = parts[1]
|
||||
provider_name = parts[2]
|
||||
return pure_model_name, instance_name, provider_name
|
||||
|
||||
|
||||
def get_model_config_from_provider_instance(tenant_id, model_type: str|enum.Enum, model_name: str):
|
||||
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 embedding model
|
||||
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 provider_name is None)
|
||||
)
|
||||
if is_tei_builtin_embedding:
|
||||
# configured local embedding model
|
||||
embedding_cfg = settings.EMBEDDING_CFG
|
||||
return {
|
||||
"llm_factory": "Builtin",
|
||||
"api_key": embedding_cfg["api_key"],
|
||||
"llm_name": pure_model_name,
|
||||
"api_base": embedding_cfg["base_url"],
|
||||
"model_type": LLMType.EMBEDDING.value,
|
||||
}
|
||||
|
||||
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 = 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_type_and_model_name(provider_obj.id, instance_obj.id, model_type_val, pure_model_name)
|
||||
|
||||
import json
|
||||
api_key, is_tool, api_key_payload = TenantLLMService._decode_api_key_config(instance_obj.api_key)
|
||||
extra_fields = json.loads(instance_obj.extra) if instance_obj.extra else {}
|
||||
|
||||
if model_obj:
|
||||
if model_obj.status == ActiveStatusEnum.INACTIVE.value:
|
||||
raise LookupError(f"Model {model_name} is disabled.")
|
||||
|
||||
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_tool": extra_fields.get("is_tool", is_tool)
|
||||
}
|
||||
if api_key_payload is not None:
|
||||
model_config["api_key_payload"] = api_key_payload
|
||||
|
||||
return model_config
|
||||
else:
|
||||
fac_list = [f for f in settings.FACTORY_LLM_INFOS if f["name"] == provider_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 config not found: {model_name}")
|
||||
llm_info = llm_list[0]
|
||||
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": llm_info["model_type"],
|
||||
"is_tool": llm_info.get("is_tool", is_tool)
|
||||
}
|
||||
if api_key_payload is not None:
|
||||
model_config["api_key_payload"] = api_key_payload
|
||||
return model_config
|
||||
|
||||
|
||||
def get_api_key(tenant_id: str, model_name: str):
|
||||
_, instance_name, provider_name = split_model_name(model_name)
|
||||
|
||||
if not provider_name:
|
||||
raise LookupError("Provider name is required.")
|
||||
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.")
|
||||
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.")
|
||||
return instance_obj.api_key
|
||||
|
||||
|
||||
def get_model_type_by_name(tenant_id: str, model_name: str):
|
||||
pure_model_name, instance_name, provider_name = split_model_name(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 = 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_objs = TenantModelService.get_by_provider_id_and_instance_id_and_model_name(provider_obj.id, instance_obj.id, pure_model_name)
|
||||
if not model_objs:
|
||||
fac_list = [f for f in settings.FACTORY_LLM_INFOS if f["name"] == provider_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}.")
|
||||
return [llm_list[0]["model_type"]]
|
||||
return [model_obj.model_type for model_obj in model_objs]
|
||||
|
||||
|
||||
def delete_models_by_instance_ids(instance_ids: list[str]):
|
||||
return TenantModelService.delete_by_instance_ids(instance_ids)
|
||||
|
||||
|
||||
def delete_instances_by_provider_ids(provider_ids: list[str]):
|
||||
return TenantModelInstanceService.delete_by_provider_ids(provider_ids)
|
||||
@@ -42,7 +42,7 @@ def _split_name_counter(filename: str) -> tuple[str, int | None]:
|
||||
return filename, None
|
||||
|
||||
|
||||
def duplicate_name(query_func, **kwargs) -> str:
|
||||
def duplicate_name(query_func, name_field: str="name", **kwargs) -> str:
|
||||
"""
|
||||
Generates a unique filename by appending/incrementing a counter when duplicates exist.
|
||||
|
||||
@@ -54,6 +54,7 @@ def duplicate_name(query_func, **kwargs) -> str:
|
||||
query_func: Callable that accepts keyword arguments and returns:
|
||||
- True if name exists (should be modified)
|
||||
- False if name is available
|
||||
name_field: the field name of name in db. default to 'name'
|
||||
**kwargs: Must contain 'name' key with original filename to check
|
||||
|
||||
Returns:
|
||||
@@ -72,10 +73,10 @@ def duplicate_name(query_func, **kwargs) -> str:
|
||||
"""
|
||||
MAX_RETRIES = 1000
|
||||
|
||||
if "name" not in kwargs:
|
||||
raise KeyError("Arguments must contain 'name' key")
|
||||
if name_field not in kwargs:
|
||||
raise KeyError(f"Arguments must contain '{name_field}' key")
|
||||
|
||||
original_name = kwargs["name"]
|
||||
original_name = kwargs[name_field]
|
||||
current_name = original_name
|
||||
retries = 0
|
||||
|
||||
@@ -92,7 +93,7 @@ def duplicate_name(query_func, **kwargs) -> str:
|
||||
|
||||
new_name = f"{main_part}({counter}){suffix}"
|
||||
|
||||
kwargs["name"] = new_name
|
||||
kwargs[name_field] = new_name
|
||||
current_name = new_name
|
||||
retries += 1
|
||||
|
||||
|
||||
@@ -39,8 +39,7 @@ from api.utils.reference_metadata_utils import (
|
||||
enrich_chunks_with_document_metadata,
|
||||
resolve_reference_metadata_preferences,
|
||||
)
|
||||
from api.db.services.tenant_llm_service import TenantLLMService
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_id, get_model_config_by_type_and_name, get_tenant_default_model_by_type
|
||||
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 common.time_utils import current_timestamp, datetime_format
|
||||
from common.text_utils import normalize_arabic_digits
|
||||
from rag.graphrag.general.mind_map_extractor import MindMapExtractor
|
||||
@@ -288,21 +287,19 @@ class DialogService(CommonService):
|
||||
|
||||
|
||||
async def async_chat_solo(dialog, messages, stream=True):
|
||||
llm_type = TenantLLMService.llm_id2llm_type(dialog.llm_id)
|
||||
llm_types = get_model_type_by_name(dialog.tenant_id, dialog.llm_id)
|
||||
attachments = ""
|
||||
image_attachments = []
|
||||
image_files = []
|
||||
if "files" in messages[-1]:
|
||||
if llm_type == "chat":
|
||||
if "chat" in llm_types:
|
||||
text_attachments, image_attachments = split_file_attachments(messages[-1]["files"])
|
||||
else:
|
||||
text_attachments, image_files = split_file_attachments(messages[-1]["files"], raw=True)
|
||||
attachments = "\n\n".join(text_attachments)
|
||||
|
||||
if dialog.llm_id:
|
||||
model_config = get_model_config_by_type_and_name(dialog.tenant_id, LLMType.CHAT, dialog.llm_id)
|
||||
elif dialog.tenant_llm_id:
|
||||
model_config = get_model_config_by_id(dialog.tenant_llm_id)
|
||||
model_config = get_model_config_from_provider_instance(dialog.tenant_id, LLMType.CHAT, dialog.llm_id)
|
||||
else:
|
||||
model_config = get_tenant_default_model_by_type(dialog.tenant_id, LLMType.CHAT)
|
||||
|
||||
@@ -317,10 +314,10 @@ async def async_chat_solo(dialog, messages, stream=True):
|
||||
msg = [{"role": m["role"], "content": re.sub(r"##\d+\$\$", "", m["content"])} for m in messages if m["role"] != "system"]
|
||||
if attachments and msg:
|
||||
msg[-1]["content"] += attachments
|
||||
if llm_type == "chat" and image_attachments:
|
||||
if "chat" in llm_types and image_attachments:
|
||||
convert_last_user_msg_to_multimodal(msg, image_attachments, factory)
|
||||
if stream:
|
||||
if llm_type == "chat":
|
||||
if "chat" in llm_types:
|
||||
stream_iter = chat_mdl.async_chat_streamly_delta(prompt_config.get("system", ""), msg, dialog.llm_setting)
|
||||
else:
|
||||
stream_iter = chat_mdl.async_chat_streamly_delta(prompt_config.get("system", ""), msg, dialog.llm_setting, images=image_files)
|
||||
@@ -331,7 +328,7 @@ async def async_chat_solo(dialog, messages, stream=True):
|
||||
continue
|
||||
yield {"answer": value, "reference": {}, "audio_binary": tts(tts_mdl, value), "prompt": "", "created_at": time.time(), "final": False}
|
||||
else:
|
||||
if llm_type == "chat":
|
||||
if "chat" in llm_types:
|
||||
answer = await chat_mdl.async_chat(prompt_config.get("system", ""), msg, dialog.llm_setting)
|
||||
else:
|
||||
answer = await chat_mdl.async_chat(prompt_config.get("system", ""), msg, dialog.llm_setting, images=image_files)
|
||||
@@ -349,22 +346,20 @@ def get_models(dialog):
|
||||
|
||||
if embedding_list:
|
||||
embd_owner_tenant_id = kbs[0].tenant_id
|
||||
embd_model_config = get_model_config_by_type_and_name(embd_owner_tenant_id, LLMType.EMBEDDING, embedding_list[0])
|
||||
embd_model_config = get_model_config_from_provider_instance(embd_owner_tenant_id, LLMType.EMBEDDING, embedding_list[0])
|
||||
embd_mdl = LLMBundle(embd_owner_tenant_id, embd_model_config)
|
||||
if not embd_mdl:
|
||||
raise LookupError("Embedding model(%s) not found" % embedding_list[0])
|
||||
|
||||
if dialog.llm_id:
|
||||
chat_model_config = get_model_config_by_type_and_name(dialog.tenant_id, LLMType.CHAT, dialog.llm_id)
|
||||
elif dialog.tenant_llm_id:
|
||||
chat_model_config = get_model_config_by_id(dialog.tenant_llm_id)
|
||||
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)
|
||||
|
||||
if dialog.rerank_id:
|
||||
rerank_model_config = get_model_config_by_type_and_name(dialog.tenant_id, LLMType.RERANK, dialog.rerank_id)
|
||||
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)
|
||||
|
||||
if dialog.prompt_config.get("tts"):
|
||||
@@ -554,11 +549,14 @@ async def async_chat(dialog, messages, stream=True, **kwargs):
|
||||
return
|
||||
|
||||
chat_start_ts = timer()
|
||||
llm_type = TenantLLMService.llm_id2llm_type(dialog.llm_id)
|
||||
if llm_type == "image2text":
|
||||
llm_model_config = TenantLLMService.get_model_config(dialog.tenant_id, LLMType.IMAGE2TEXT, dialog.llm_id)
|
||||
if dialog.llm_id:
|
||||
llm_types = get_model_type_by_name(dialog.tenant_id, dialog.llm_id)
|
||||
if "image2text" in llm_types:
|
||||
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.CHAT, dialog.llm_id)
|
||||
else:
|
||||
llm_model_config = TenantLLMService.get_model_config(dialog.tenant_id, LLMType.CHAT, dialog.llm_id)
|
||||
llm_model_config = get_tenant_default_model_by_type(dialog.tenant_id, LLMType.CHAT)
|
||||
|
||||
factory = llm_model_config.get("llm_factory", "") if llm_model_config else ""
|
||||
max_tokens = llm_model_config.get("max_tokens", 8192)
|
||||
@@ -598,7 +596,7 @@ async def async_chat(dialog, messages, stream=True, **kwargs):
|
||||
if "doc_ids" in messages[-1]:
|
||||
attachments = [doc_id for doc_id in messages[-1]["doc_ids"] if doc_id]
|
||||
if "files" in messages[-1]:
|
||||
if llm_type == "chat":
|
||||
if llm_model_config["model_type"] == "chat":
|
||||
text_attachments, image_attachments = split_file_attachments(messages[-1]["files"])
|
||||
else:
|
||||
text_attachments, image_files = split_file_attachments(messages[-1]["files"], raw=True)
|
||||
@@ -769,7 +767,7 @@ async def async_chat(dialog, messages, stream=True, **kwargs):
|
||||
prompt4citation = citation_prompt()
|
||||
msg.extend([{"role": m["role"], "content": re.sub(r"##\d+\$\$", "", m["content"])} for m in messages if m["role"] != "system"])
|
||||
used_token_count, msg = message_fit_in(msg, int(max_tokens * 0.95))
|
||||
if llm_type == "chat" and image_attachments:
|
||||
if llm_model_config["model_type"] == "chat" and image_attachments:
|
||||
convert_last_user_msg_to_multimodal(msg, image_attachments, factory)
|
||||
assert len(msg) >= 2, f"message_fit_in has bug: {msg}"
|
||||
prompt = msg[0]["content"]
|
||||
@@ -881,7 +879,7 @@ async def async_chat(dialog, messages, stream=True, **kwargs):
|
||||
langfuse_generation = None
|
||||
|
||||
if stream:
|
||||
if llm_type == "chat":
|
||||
if llm_model_config["model_type"] == "chat":
|
||||
stream_iter = chat_mdl.async_chat_streamly_delta(prompt + prompt4citation, msg[1:], gen_conf)
|
||||
else:
|
||||
stream_iter = chat_mdl.async_chat_streamly_delta(prompt + prompt4citation, msg[1:], gen_conf, images=image_files)
|
||||
@@ -900,7 +898,7 @@ async def async_chat(dialog, messages, stream=True, **kwargs):
|
||||
final["audio_binary"] = None
|
||||
yield final
|
||||
else:
|
||||
if llm_type == "chat":
|
||||
if llm_model_config["model_type"] == "chat":
|
||||
answer = await chat_mdl.async_chat(prompt + prompt4citation, msg[1:], gen_conf)
|
||||
else:
|
||||
answer = await chat_mdl.async_chat(prompt + prompt4citation, msg[1:], gen_conf, images=image_files)
|
||||
@@ -1540,12 +1538,12 @@ async def async_ask(question, kb_ids, tenant_id, chat_llm_name=None, search_conf
|
||||
is_knowledge_graph = all([kb.parser_id == ParserType.KG for kb in kbs])
|
||||
retriever = settings.retriever if not is_knowledge_graph else settings.kg_retriever
|
||||
embd_owner_tenant_id = kbs[0].tenant_id
|
||||
embd_model_config = get_model_config_by_type_and_name(embd_owner_tenant_id, LLMType.EMBEDDING, embedding_list[0])
|
||||
embd_model_config = get_model_config_from_provider_instance(embd_owner_tenant_id, LLMType.EMBEDDING, embedding_list[0])
|
||||
embd_mdl = LLMBundle(embd_owner_tenant_id, embd_model_config)
|
||||
chat_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.CHAT, chat_llm_name)
|
||||
chat_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.CHAT, chat_llm_name)
|
||||
chat_mdl = LLMBundle(tenant_id, chat_model_config)
|
||||
if rerank_id:
|
||||
rerank_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.RERANK, rerank_id)
|
||||
rerank_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.RERANK, rerank_id)
|
||||
rerank_mdl = LLMBundle(tenant_id, rerank_model_config)
|
||||
max_tokens = chat_mdl.max_length
|
||||
tenant_ids = list(set([kb.tenant_id for kb in kbs]))
|
||||
@@ -1649,23 +1647,18 @@ async def gen_mindmap(question, kb_ids, tenant_id, search_config={}):
|
||||
kbs = KnowledgebaseService.get_by_ids(kb_ids)
|
||||
if not kbs:
|
||||
return {"error": "No KB selected"}
|
||||
tenant_embedding_list = list(set([kb.tenant_embd_id for kb in kbs]))
|
||||
tenant_ids = list(set([kb.tenant_id for kb in kbs]))
|
||||
if tenant_embedding_list[0]:
|
||||
embd_model_config = get_model_config_by_id(tenant_embedding_list[0])
|
||||
embd_owner_tenant_id = kbs[0].tenant_id
|
||||
else:
|
||||
embd_owner_tenant_id = kbs[0].tenant_id
|
||||
embd_model_config = get_model_config_by_type_and_name(embd_owner_tenant_id, LLMType.EMBEDDING, kbs[0].embd_id)
|
||||
embd_owner_tenant_id = kbs[0].tenant_id
|
||||
embd_model_config = get_model_config_from_provider_instance(embd_owner_tenant_id, LLMType.EMBEDDING, kbs[0].embd_id)
|
||||
embd_mdl = LLMBundle(embd_owner_tenant_id, embd_model_config)
|
||||
chat_id = search_config.get("chat_id", "")
|
||||
if chat_id:
|
||||
chat_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.CHAT, chat_id)
|
||||
chat_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.CHAT, chat_id)
|
||||
else:
|
||||
chat_model_config = get_tenant_default_model_by_type(tenant_id, LLMType.CHAT)
|
||||
chat_mdl = LLMBundle(tenant_id, chat_model_config)
|
||||
if rerank_id:
|
||||
rerank_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.RERANK, rerank_id)
|
||||
rerank_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.RERANK, rerank_id)
|
||||
rerank_mdl = LLMBundle(tenant_id, rerank_model_config)
|
||||
|
||||
if meta_data_filter:
|
||||
|
||||
@@ -24,7 +24,7 @@ from typing import Generator
|
||||
|
||||
from api.db.db_models import LLM
|
||||
from api.db.services.common_service import CommonService
|
||||
from api.db.services.tenant_llm_service import LLM4Tenant, TenantLLMService
|
||||
from api.db.services.tenant_llm_service import LLM4Tenant
|
||||
from common.constants import LLMType
|
||||
from common.token_utils import num_tokens_from_string
|
||||
|
||||
@@ -137,9 +137,9 @@ class LLMBundle(LLM4Tenant):
|
||||
|
||||
embeddings, used_tokens = self.mdl.encode(safe_texts)
|
||||
if self.model_config["llm_factory"] == "Builtin":
|
||||
logging.debug("LLMBundle.encode_queries query: {}, emd len: {}, used_tokens: {}. Builtin model don't need to update token usage".format(texts, len(embeddings), used_tokens))
|
||||
elif not TenantLLMService.increase_usage_by_id(self.model_config["id"], used_tokens):
|
||||
logging.error("LLMBundle.encode can't update token usage for <tenant redacted>/EMBEDDING used_tokens: {}".format(used_tokens))
|
||||
logging.debug("LLMBundle.encode query: {}, emd len: {}, used_tokens: {}. Builtin model don't need to update token usage".format(texts, len(embeddings), used_tokens))
|
||||
else:
|
||||
logging.info("LLMBundle.encode used_tokens: {}, llm_name: {}".format(used_tokens, self.model_config["llm_name"]))
|
||||
|
||||
if self.langfuse:
|
||||
generation.update(usage_details={"total_tokens": used_tokens})
|
||||
@@ -162,8 +162,8 @@ class LLMBundle(LLM4Tenant):
|
||||
emd, used_tokens = self.mdl.encode_queries(query)
|
||||
if self.model_config["llm_factory"] == "Builtin":
|
||||
logging.info("LLMBundle.encode_queries query: {}, emd len: {}, used_tokens: {}. Builtin model don't need to update token usage".format(query, len(emd), used_tokens))
|
||||
elif not TenantLLMService.increase_usage_by_id(self.model_config["id"], used_tokens):
|
||||
logging.error("LLMBundle.encode_queries can't update token usage for <tenant redacted>/EMBEDDING used_tokens: {}".format(used_tokens))
|
||||
else:
|
||||
logging.info("LLMBundle.encode_queries used_tokens: {}, llm_name: {}".format(used_tokens, self.model_config["llm_name"]))
|
||||
|
||||
if self.langfuse:
|
||||
generation.update(usage_details={"total_tokens": used_tokens})
|
||||
@@ -176,8 +176,7 @@ class LLMBundle(LLM4Tenant):
|
||||
generation = self.langfuse.start_observation(trace_context=self.trace_context, as_type="generation", name="similarity", model=self.model_config["llm_name"], input={"query": query, "texts": texts})
|
||||
|
||||
sim, used_tokens = self.mdl.similarity(query, texts)
|
||||
if not TenantLLMService.increase_usage_by_id(self.model_config["id"], used_tokens):
|
||||
logging.error("LLMBundle.similarity can't update token usage for {}/RERANK used_tokens: {}".format(self.tenant_id, used_tokens))
|
||||
logging.info("LLMBundle.similarity used_tokens: {}, llm_name: {}".format(used_tokens, self.model_config["llm_name"]))
|
||||
|
||||
if self.langfuse:
|
||||
generation.update(usage_details={"total_tokens": used_tokens})
|
||||
@@ -190,8 +189,7 @@ class LLMBundle(LLM4Tenant):
|
||||
generation = self.langfuse.start_observation(trace_context=self.trace_context, as_type="generation", name="describe", metadata={"model": self.model_config["llm_name"]})
|
||||
|
||||
txt, used_tokens = self.mdl.describe(image)
|
||||
if not TenantLLMService.increase_usage_by_id(self.model_config["id"], used_tokens):
|
||||
logging.error("LLMBundle.describe can't update token usage for {}/IMAGE2TEXT used_tokens: {}".format(self.tenant_id, used_tokens))
|
||||
logging.info("LLMBundle.describe used_tokens: {}, llm_name: {}".format(used_tokens, self.model_config["llm_name"]))
|
||||
|
||||
if self.langfuse:
|
||||
generation.update(output={"output": txt}, usage_details={"total_tokens": used_tokens})
|
||||
@@ -204,8 +202,7 @@ class LLMBundle(LLM4Tenant):
|
||||
generation = self.langfuse.start_observation(trace_context=self.trace_context, as_type="generation", name="describe_with_prompt", metadata={"model": self.model_config["llm_name"], "prompt": prompt})
|
||||
|
||||
txt, used_tokens = self.mdl.describe_with_prompt(image, prompt)
|
||||
if not TenantLLMService.increase_usage_by_id(self.model_config["id"], used_tokens):
|
||||
logging.error("LLMBundle.describe can't update token usage for {}/IMAGE2TEXT used_tokens: {}".format(self.tenant_id, used_tokens))
|
||||
logging.info("LLMBundle.describe_with_prompt used_tokens: {}, llm_name: {}".format(used_tokens, self.model_config["llm_name"]))
|
||||
|
||||
if self.langfuse:
|
||||
generation.update(output={"output": txt}, usage_details={"total_tokens": used_tokens})
|
||||
@@ -218,8 +215,7 @@ class LLMBundle(LLM4Tenant):
|
||||
generation = self.langfuse.start_observation(trace_context=self.trace_context, as_type="generation", name="transcription", metadata={"model": self.model_config["llm_name"]})
|
||||
|
||||
txt, used_tokens = self.mdl.transcription(audio)
|
||||
if not TenantLLMService.increase_usage_by_id(self.model_config["id"], used_tokens):
|
||||
logging.error("LLMBundle.transcription can't update token usage for {}/SEQUENCE2TXT used_tokens: {}".format(self.tenant_id, used_tokens))
|
||||
logging.info("LLMBundle.transcription used_tokens: {}, llm_name: {}".format(used_tokens, self.model_config["llm_name"]))
|
||||
|
||||
if self.langfuse:
|
||||
generation.update(output={"output": txt}, usage_details={"total_tokens": used_tokens})
|
||||
@@ -254,7 +250,7 @@ class LLMBundle(LLM4Tenant):
|
||||
finally:
|
||||
if final_text:
|
||||
used_tokens = num_tokens_from_string(final_text)
|
||||
TenantLLMService.increase_usage_by_id(self.model_config["id"], used_tokens)
|
||||
logging.info("LLMBundle.stream_transcription used_tokens: {}, llm_name: {}".format(used_tokens, self.model_config["llm_name"]))
|
||||
|
||||
if self.langfuse:
|
||||
generation.update(
|
||||
@@ -273,8 +269,7 @@ class LLMBundle(LLM4Tenant):
|
||||
)
|
||||
|
||||
full_text, used_tokens = mdl.transcription(audio)
|
||||
if not TenantLLMService.increase_usage_by_id(self.model_config["id"], used_tokens):
|
||||
logging.error(f"LLMBundle.stream_transcription can't update token usage for {self.tenant_id}/SEQUENCE2TXT used_tokens: {used_tokens}")
|
||||
logging.info("LLMBundle.stream_transcription used_tokens: {}, llm_name: {}".format(used_tokens, self.model_config["llm_name"]))
|
||||
|
||||
if self.langfuse:
|
||||
generation.update(
|
||||
@@ -295,8 +290,7 @@ class LLMBundle(LLM4Tenant):
|
||||
|
||||
for chunk in self.mdl.tts(text):
|
||||
if isinstance(chunk, int):
|
||||
if not TenantLLMService.increase_usage_by_id(self.model_config["id"], chunk):
|
||||
logging.error("LLMBundle.tts can't update token usage for {}/TTS".format(self.tenant_id))
|
||||
logging.info("LLMBundle.tts used_tokens: {}, model_name: {}".format(chunk, self.model_config["llm_name"]))
|
||||
return
|
||||
yield chunk
|
||||
|
||||
@@ -431,8 +425,8 @@ class LLMBundle(LLM4Tenant):
|
||||
if not self.verbose_tool_use:
|
||||
txt = re.sub(r"<tool_call>.*?</tool_call>", "", txt, flags=re.DOTALL)
|
||||
|
||||
if used_tokens and not TenantLLMService.increase_usage_by_id(self.model_config["id"], used_tokens):
|
||||
logging.error("LLMBundle.async_chat can't update token usage for {}/CHAT llm_name: {}, used_tokens: {}".format(self.tenant_id, self.model_config["llm_name"], used_tokens))
|
||||
if used_tokens:
|
||||
logging.info("LLMBundle.async_chat used_tokens: {}, llm_name: {}".format(used_tokens, self.model_config["llm_name"]))
|
||||
|
||||
if generation:
|
||||
generation.update(output={"output": txt}, usage_details={"total_tokens": used_tokens})
|
||||
@@ -479,8 +473,8 @@ class LLMBundle(LLM4Tenant):
|
||||
generation.update(output={"error": str(e)})
|
||||
generation.end()
|
||||
raise
|
||||
if total_tokens and not TenantLLMService.increase_usage_by_id(self.model_config["id"], total_tokens):
|
||||
logging.error("LLMBundle.async_chat_streamly can't update token usage for {}/CHAT llm_name: {}, used_tokens: {}".format(self.tenant_id, self.model_config["llm_name"], total_tokens))
|
||||
if total_tokens:
|
||||
logging.info("LLMBundle.async_chat_streamly used_tokens: {}, llm_name: {}".format(total_tokens, self.model_config["llm_name"]))
|
||||
if generation:
|
||||
generation.update(output={"output": ans}, usage_details={"total_tokens": total_tokens})
|
||||
generation.end()
|
||||
@@ -522,8 +516,8 @@ class LLMBundle(LLM4Tenant):
|
||||
generation.update(output={"error": str(e)})
|
||||
generation.end()
|
||||
raise
|
||||
if total_tokens and not TenantLLMService.increase_usage_by_id(self.model_config["id"], total_tokens):
|
||||
logging.error("LLMBundle.async_chat_streamly can't update token usage for {}/CHAT llm_name: {}, used_tokens: {}".format(self.tenant_id, self.model_config["llm_name"], total_tokens))
|
||||
if total_tokens:
|
||||
logging.info("LLMBundle.async_chat_streamly_delta used_tokens: {}, llm_name: {}".format(total_tokens, self.model_config["llm_name"]))
|
||||
if generation:
|
||||
generation.update(output={"output": ans}, usage_details={"total_tokens": total_tokens})
|
||||
generation.end()
|
||||
|
||||
@@ -112,7 +112,7 @@ class MemoryService(CommonService):
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def create_memory(cls, tenant_id: str, name: str, memory_type: List[str], embd_id: str, tenant_embd_id: int, llm_id: str, tenant_llm_id: int):
|
||||
def create_memory(cls, tenant_id: str, name: str, memory_type: List[str], embd_id: str, llm_id: str):
|
||||
# Deduplicate name within tenant
|
||||
memory_name = duplicate_name(
|
||||
cls.query,
|
||||
@@ -131,9 +131,7 @@ 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,
|
||||
|
||||
@@ -188,30 +188,36 @@ class TenantLLMService(CommonService):
|
||||
api_key = model_config.get("api_key_payload", model_config["api_key"])
|
||||
if model_config["model_type"] == LLMType.EMBEDDING.value:
|
||||
if model_config["llm_factory"] not in EmbeddingModel:
|
||||
logging.error(f"Factory {model_config['llm_factory']} not in embedding model. Supported factories: {EmbeddingModel.keys()}")
|
||||
return None
|
||||
return EmbeddingModel[model_config["llm_factory"]](api_key, model_config["llm_name"], base_url=model_config["api_base"])
|
||||
|
||||
elif model_config["model_type"] == LLMType.RERANK:
|
||||
elif model_config["model_type"] == LLMType.RERANK.value:
|
||||
if model_config["llm_factory"] not in RerankModel:
|
||||
logging.error(f"Factory {model_config['llm_factory']} not in rerank model. Supported factories: {RerankModel.keys()}")
|
||||
return None
|
||||
return RerankModel[model_config["llm_factory"]](api_key, model_config["llm_name"], base_url=model_config["api_base"])
|
||||
|
||||
elif model_config["model_type"] == LLMType.IMAGE2TEXT.value:
|
||||
if model_config["llm_factory"] not in CvModel:
|
||||
logging.error(f"Factory {model_config['llm_factory']} not in cv model. Supported factories: {CvModel.keys()}")
|
||||
return None
|
||||
return CvModel[model_config["llm_factory"]](api_key, model_config["llm_name"], lang, base_url=model_config["api_base"], **kwargs)
|
||||
|
||||
elif model_config["model_type"] == LLMType.CHAT.value:
|
||||
if model_config["llm_factory"] not in ChatModel:
|
||||
logging.error(f"Factory {model_config['llm_factory']} not in chat model. Supported factories: {ChatModel.keys()}")
|
||||
return None
|
||||
return ChatModel[model_config["llm_factory"]](api_key, model_config["llm_name"], base_url=model_config["api_base"], **kwargs)
|
||||
|
||||
elif model_config["model_type"] == LLMType.SPEECH2TEXT:
|
||||
elif model_config["model_type"] == LLMType.SPEECH2TEXT.value:
|
||||
if model_config["llm_factory"] not in Seq2txtModel:
|
||||
logging.error(f"Factory {model_config['llm_factory']} not in speech2text model. Supported factories: {Seq2txtModel.keys()}")
|
||||
return None
|
||||
return Seq2txtModel[model_config["llm_factory"]](key=api_key, model_name=model_config["llm_name"], lang=lang, base_url=model_config["api_base"])
|
||||
elif model_config["model_type"] == LLMType.TTS:
|
||||
elif model_config["model_type"] == LLMType.TTS.value:
|
||||
if model_config["llm_factory"] not in TTSModel:
|
||||
logging.error(f"Factory {model_config['llm_factory']} not in tts model. Supported factories: {TTSModel.keys()}")
|
||||
return None
|
||||
return TTSModel[model_config["llm_factory"]](
|
||||
api_key,
|
||||
@@ -219,8 +225,9 @@ class TenantLLMService(CommonService):
|
||||
base_url=model_config["api_base"],
|
||||
)
|
||||
|
||||
elif model_config["model_type"] == LLMType.OCR:
|
||||
elif model_config["model_type"] == LLMType.OCR.value:
|
||||
if model_config["llm_factory"] not in OcrModel:
|
||||
logging.error(f"Factory {model_config['llm_factory']} not in ocr model. Supported factories: {OcrModel.keys()}")
|
||||
return None
|
||||
return OcrModel[model_config["llm_factory"]](
|
||||
key=api_key,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#
|
||||
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from api.db.db_models import DB, TenantModelGroupMapping
|
||||
from api.db.services.common_service import CommonService
|
||||
|
||||
|
||||
class TenantModelGroupMappingService(CommonService):
|
||||
model = TenantModelGroupMapping
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_by_composite_id(cls, group_id, provider_id, instance_id, model_id):
|
||||
return cls.model.get_or_none(
|
||||
cls.model.group_id == group_id,
|
||||
cls.model.provider_id == provider_id,
|
||||
cls.model.instance_id == instance_id,
|
||||
cls.model.model_id == model_id,
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from api.db.db_models import TenantModelGroup
|
||||
from api.db.services.common_service import CommonService
|
||||
|
||||
|
||||
class TenantModelGroupService(CommonService):
|
||||
model = TenantModelGroup
|
||||
@@ -0,0 +1,69 @@
|
||||
#
|
||||
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from common.misc_utils import get_uuid
|
||||
from api.db.db_models import DB, TenantModelInstance
|
||||
from api.db.services.common_service import CommonService
|
||||
from api.db.services import duplicate_name
|
||||
|
||||
class TenantModelInstanceService(CommonService):
|
||||
model = TenantModelInstance
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def create_instance(cls, provider_id: str, instance_name: str, api_key: str, extra: str):
|
||||
unique_instance_name = duplicate_name(cls.query, name_field="instance_name", provider_id=provider_id, instance_name=instance_name)
|
||||
return cls.insert(id=get_uuid(), provider_id=provider_id, instance_name=unique_instance_name, api_key=api_key, extra=extra)
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_all_by_provider_id(cls, provider_id):
|
||||
return list(cls.model.select().where(cls.model.provider_id == provider_id))
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_by_provider_ids(cls, provider_ids):
|
||||
return list(cls.model.select().where(cls.model.provider_id.in_(provider_ids)))
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_by_provider_id_and_instance_name(cls, provider_id, instance_name):
|
||||
return cls.model.get_or_none(
|
||||
cls.model.provider_id == provider_id,
|
||||
cls.model.instance_name == instance_name,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_by_provider_id_and_api_key(cls, provider_id, api_key):
|
||||
return cls.model.get_or_none(
|
||||
cls.model.provider_id == provider_id,
|
||||
cls.model.api_key == api_key
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def delete_by_provider_id_and_instance_name(cls, provider_id, instance_name):
|
||||
return cls.model.delete().where(
|
||||
cls.model.provider_id == provider_id,
|
||||
cls.model.instance_name == instance_name,
|
||||
).execute()
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def delete_by_provider_ids(cls, provider_ids):
|
||||
return cls.model.delete().where(
|
||||
cls.model.provider_id.in_(provider_ids)
|
||||
).execute()
|
||||
@@ -0,0 +1,52 @@
|
||||
#
|
||||
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from api.db.db_models import DB, TenantModelProvider
|
||||
from api.db.services.common_service import CommonService
|
||||
|
||||
|
||||
class TenantModelProviderService(CommonService):
|
||||
model = TenantModelProvider
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_by_tenant_id_and_provider_name(cls, tenant_id, provider_name):
|
||||
return cls.model.get_or_none(
|
||||
cls.model.tenant_id == tenant_id,
|
||||
cls.model.provider_name == provider_name,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_by_tenant_id(cls, tenant_id):
|
||||
return list(cls.model.select().where(cls.model.tenant_id == tenant_id))
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def delete_by_tenant_id(cls, tenant_id):
|
||||
return cls.model.delete().where(cls.model.tenant_id == tenant_id).execute()
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def delete_by_tenant_id_and_provider_name(cls, tenant_id, provider_name):
|
||||
return cls.model.delete().where(
|
||||
cls.model.tenant_id == tenant_id,
|
||||
cls.model.provider_name == provider_name,
|
||||
).execute()
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def list_provider_names_by_tenant_id(cls, tenant_id):
|
||||
return [row.provider_name for row in cls.model.select(cls.model.provider_name).where(cls.model.tenant_id == tenant_id)]
|
||||
@@ -0,0 +1,61 @@
|
||||
#
|
||||
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from api.db.db_models import DB, TenantModel
|
||||
from api.db.services.common_service import CommonService
|
||||
|
||||
|
||||
class TenantModelService(CommonService):
|
||||
model = TenantModel
|
||||
|
||||
@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))
|
||||
|
||||
@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
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_models_by_instance_id(cls, instance_id):
|
||||
return list(cls.model.select().where(cls.model.instance_id == instance_id))
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_models_by_provider_ids_and_instance_ids(cls, provider_ids, instance_ids):
|
||||
return list(cls.model.select().where(cls.model.provider_id.in_(provider_ids), cls.model.instance_id.in_(instance_ids)))
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def batch_update_model_status(cls, model_ids, status):
|
||||
return cls.model.update(status=status).where(cls.model.id.in_(model_ids)).execute()
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def delete_by_id(cls, model_id):
|
||||
return cls.model.delete().where(cls.model.id == model_id).execute()
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def delete_by_instance_ids(cls, instance_ids):
|
||||
return cls.model.delete().where(cls.model.instance_id.in_(instance_ids)).execute()
|
||||
@@ -191,6 +191,7 @@ class TenantService(CommonService):
|
||||
cls.model.asr_id,
|
||||
cls.model.img2txt_id,
|
||||
cls.model.tts_id,
|
||||
cls.model.ocr_id,
|
||||
cls.model.parser_ids,
|
||||
UserTenant.role]
|
||||
return list(cls.model.select(*fields)
|
||||
|
||||
+5
-15
@@ -41,7 +41,7 @@ except ImportError: # pragma: no cover - optional dependency
|
||||
|
||||
from peewee import OperationalError
|
||||
|
||||
from common.constants import ActiveEnum
|
||||
from common.constants import ActiveEnum, LLMType
|
||||
from api.db.db_models import APIToken
|
||||
from api.utils.json_encode import CustomJSONEncoder
|
||||
from common.mcp_tool_call_conn import MCPToolCallSession, close_multiple_mcp_toolcall_sessions
|
||||
@@ -576,8 +576,7 @@ def check_duplicate_ids(ids, id_type="item"):
|
||||
|
||||
|
||||
def verify_embedding_availability(embd_id: str, tenant_id: str) -> tuple[bool, str | None]:
|
||||
from api.db.services.llm_service import LLMService
|
||||
from api.db.services.tenant_llm_service import TenantLLMService
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance
|
||||
|
||||
"""
|
||||
Verifies availability of an embedding model for a specific tenant.
|
||||
@@ -613,18 +612,9 @@ def verify_embedding_availability(embd_id: str, tenant_id: str) -> tuple[bool, s
|
||||
(False, {'code': 101, 'message': "Unsupported model: <invalid_model>"})
|
||||
"""
|
||||
try:
|
||||
llm_name, llm_factory = TenantLLMService.split_model_name_and_factory(embd_id)
|
||||
in_llm_service = bool(LLMService.query(llm_name=llm_name, fid=llm_factory, model_type="embedding"))
|
||||
|
||||
tenant_llms = TenantLLMService.get_my_llms(tenant_id=tenant_id)
|
||||
is_tenant_model = any(llm["llm_name"] == llm_name and llm["llm_factory"] == llm_factory and llm["model_type"] == "embedding" for llm in tenant_llms)
|
||||
|
||||
is_builtin_model = llm_factory == "Builtin"
|
||||
if not (is_builtin_model or is_tenant_model or in_llm_service):
|
||||
return False, f"Unsupported model: <{embd_id}>"
|
||||
|
||||
if not (is_builtin_model or is_tenant_model):
|
||||
return False, f"Unauthorized model: <{embd_id}>"
|
||||
get_model_config_from_provider_instance(tenant_id, LLMType.EMBEDDING, embd_id)
|
||||
except LookupError as e:
|
||||
return False, str(e)
|
||||
except OperationalError as e:
|
||||
logging.exception(e)
|
||||
return False, "Database operation failed"
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
#
|
||||
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from common.constants import LLMType
|
||||
from common.exceptions import ArgumentException
|
||||
from api.db.services.tenant_llm_service import TenantLLMService
|
||||
|
||||
_KEY_TO_MODEL_TYPE = {
|
||||
"llm_id": LLMType.CHAT,
|
||||
"embd_id": LLMType.EMBEDDING,
|
||||
"asr_id": LLMType.SPEECH2TEXT,
|
||||
"img2txt_id": LLMType.IMAGE2TEXT,
|
||||
"rerank_id": LLMType.RERANK,
|
||||
"tts_id": LLMType.TTS,
|
||||
}
|
||||
|
||||
def ensure_tenant_model_id_for_params(tenant_id: str, param_dict: dict, *, strict: bool = False) -> dict:
|
||||
for key in ["llm_id", "embd_id", "asr_id", "img2txt_id", "rerank_id", "tts_id"]:
|
||||
if param_dict.get(key) and not param_dict.get(f"tenant_{key}"):
|
||||
model_type = _KEY_TO_MODEL_TYPE.get(key)
|
||||
tenant_model = TenantLLMService.get_api_key(tenant_id, param_dict[key], model_type)
|
||||
if not tenant_model and model_type == LLMType.CHAT:
|
||||
tenant_model = TenantLLMService.get_api_key(tenant_id, param_dict[key])
|
||||
if tenant_model:
|
||||
param_dict.update({f"tenant_{key}": tenant_model.id})
|
||||
else:
|
||||
if strict:
|
||||
model_type_val = model_type.value if hasattr(model_type, "value") else model_type
|
||||
raise ArgumentException(
|
||||
f"Tenant Model with name {param_dict[key]} and type {model_type_val} not found"
|
||||
)
|
||||
param_dict.update({f"tenant_{key}": 0})
|
||||
return param_dict
|
||||
@@ -66,6 +66,11 @@ class StatusEnum(Enum):
|
||||
INVALID = "0"
|
||||
|
||||
|
||||
class ActiveStatusEnum(Enum):
|
||||
ACTIVE = "active"
|
||||
INACTIVE = "inactive"
|
||||
|
||||
|
||||
class ActiveEnum(Enum):
|
||||
ACTIVE = "1"
|
||||
INACTIVE = "0"
|
||||
|
||||
@@ -4376,21 +4376,21 @@
|
||||
"llm_name": "Qwen3-Reranker-8B",
|
||||
"tags": "TEXT RE-RANK,32K",
|
||||
"max_tokens": 32768,
|
||||
"model_type": "reranker",
|
||||
"model_type": "rerank",
|
||||
"is_tools": false
|
||||
},
|
||||
{
|
||||
"llm_name": "Qwen3-Reranker-4B",
|
||||
"tags": "TEXT RE-RANK,32K",
|
||||
"max_tokens": 32768,
|
||||
"model_type": "reranker",
|
||||
"model_type": "rerank",
|
||||
"is_tools": false
|
||||
},
|
||||
{
|
||||
"llm_name": "Qwen3-Reranker-0.6B",
|
||||
"tags": "TEXT RE-RANK,32K",
|
||||
"max_tokens": 32768,
|
||||
"model_type": "reranker",
|
||||
"model_type": "rerank",
|
||||
"is_tools": false
|
||||
},
|
||||
{
|
||||
@@ -4432,7 +4432,7 @@
|
||||
"llm_name": "jina-reranker-m0",
|
||||
"tags": "TEXT RE-RANK,10K",
|
||||
"max_tokens": 10240,
|
||||
"model_type": "reranker",
|
||||
"model_type": "rerank",
|
||||
"is_tools": false
|
||||
},
|
||||
{
|
||||
@@ -4446,7 +4446,7 @@
|
||||
"llm_name": "bce-reranker-base_v1",
|
||||
"tags": "TEXT RE-RANK",
|
||||
"max_tokens": 512,
|
||||
"model_type": "reranker",
|
||||
"model_type": "rerank",
|
||||
"is_tools": false
|
||||
},
|
||||
{
|
||||
@@ -4460,7 +4460,7 @@
|
||||
"llm_name": "bge-reranker-v2-m3",
|
||||
"tags": "TEXT RE-RANK",
|
||||
"max_tokens": 8192,
|
||||
"model_type": "reranker",
|
||||
"model_type": "rerank",
|
||||
"is_tools": false
|
||||
},
|
||||
{
|
||||
@@ -6158,13 +6158,13 @@
|
||||
"llm_name": "baai/bge-reranker-v2-m3",
|
||||
"tags": "RE-RANK,8K",
|
||||
"max_tokens": 8000,
|
||||
"model_type": "reranker"
|
||||
"model_type": "rerank"
|
||||
},
|
||||
{
|
||||
"llm_name": "qwen/qwen3-reranker-8b",
|
||||
"tags": "RE-RANK,32K",
|
||||
"max_tokens": 32768,
|
||||
"model_type": "reranker"
|
||||
"model_type": "rerank"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -28,6 +28,7 @@ services:
|
||||
# Example configuration to start Admin server:
|
||||
command:
|
||||
- --enable-adminserver
|
||||
- --init-model-provider-tables
|
||||
ports:
|
||||
- ${SVR_WEB_HTTP_PORT}:80
|
||||
- ${SVR_WEB_HTTPS_PORT}:443
|
||||
|
||||
+15
-1
@@ -16,6 +16,7 @@ function usage() {
|
||||
echo " --disable-datasync Disables synchronization of datasource workers."
|
||||
echo " --enable-mcpserver Enables the MCP server."
|
||||
echo " --enable-adminserver Enables the Admin server."
|
||||
echo " --init-model-provider-tables Run model provider table migrations and exit."
|
||||
echo " --init-superuser Initializes the superuser."
|
||||
echo " --consumer-no-beg=<num> Start range for consumers (if using range-based)."
|
||||
echo " --consumer-no-end=<num> End range for consumers (if using range-based)."
|
||||
@@ -38,6 +39,7 @@ ENABLE_DATASYNC=1
|
||||
ENABLE_MCP_SERVER=0
|
||||
ENABLE_ADMIN_SERVER=0 # Default close admin server
|
||||
INIT_SUPERUSER_ARGS="" # Default to not initialize superuser
|
||||
INIT_MODEL_PROVIDER_TABLES=0
|
||||
CONSUMER_NO_BEG=0
|
||||
CONSUMER_NO_END=0
|
||||
WORKERS=1
|
||||
@@ -89,6 +91,10 @@ for arg in "$@"; do
|
||||
ENABLE_ADMIN_SERVER=1
|
||||
shift
|
||||
;;
|
||||
--init-model-provider-tables)
|
||||
INIT_MODEL_PROVIDER_TABLES=1
|
||||
shift
|
||||
;;
|
||||
--init-superuser)
|
||||
INIT_SUPERUSER_ARGS="--init-superuser"
|
||||
shift
|
||||
@@ -266,6 +272,14 @@ function wait_for_server() {
|
||||
ensure_docling
|
||||
ensure_db_init
|
||||
|
||||
if [[ "${INIT_MODEL_PROVIDER_TABLES}" -eq 1 ]]; then
|
||||
echo "Running model provider table migrations..."
|
||||
"$PY" tools/scripts/mysql_migration.py --stages tenant_model_provider --config conf/service_conf.yaml --execute
|
||||
"$PY" tools/scripts/mysql_migration.py --stages tenant_model_instance --config conf/service_conf.yaml --execute
|
||||
"$PY" tools/scripts/mysql_migration.py --stages tenant_model --config conf/service_conf.yaml --execute
|
||||
echo "Model provider table migrations completed."
|
||||
fi
|
||||
|
||||
if [[ "${ENABLE_WEBSERVER}" -eq 1 ]]; then
|
||||
echo "Starting nginx..."
|
||||
/usr/sbin/nginx
|
||||
@@ -280,7 +294,7 @@ if [[ "${ENABLE_WEBSERVER}" -eq 1 ]]; then
|
||||
if [[ "${API_PROXY_SCHEME}" == "hybrid" ]]; then
|
||||
while true; do
|
||||
echo "Attempt to start RAGFlow go server..."
|
||||
wait_for_server "http://127.0.0.1:9380/healthz" "ragflow_server"
|
||||
wait_for_server "http://127.0.0.1:9380/api/v1/system/healthz" "ragflow_server"
|
||||
echo "Starting RAGFlow go server..."
|
||||
bin/server_main
|
||||
sleep 1;
|
||||
|
||||
@@ -263,7 +263,7 @@ func (m *ModelProviderService) CreateProviderInstance(providerName, instanceName
|
||||
InstanceName: instanceName,
|
||||
ProviderID: provider.ID,
|
||||
APIKey: apiKey,
|
||||
Status: "enable",
|
||||
Status: "active",
|
||||
Extra: extraStr,
|
||||
}
|
||||
err = m.modelInstanceDAO.Create(tenantModelProvider)
|
||||
|
||||
+5
-5
@@ -31,7 +31,7 @@ from common.token_utils import num_tokens_from_string
|
||||
|
||||
from common.constants import LLMType, MAXIMUM_PAGE_NUMBER
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_type_and_name, get_tenant_default_model_by_type
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_from_provider_instance
|
||||
from rag.utils.file_utils import extract_embed_file, extract_links_from_pdf, extract_links_from_docx, extract_html
|
||||
from deepdoc.parser import DocxParser, EpubParser, ExcelParser, HtmlParser, JsonParser, MarkdownElementExtractor, MarkdownParser, PdfParser, TxtParser
|
||||
from deepdoc.parser.figure_parser import VisionFigureParser, vision_figure_parser_docx_wrapper_naive, vision_figure_parser_pdf_wrapper
|
||||
@@ -128,7 +128,7 @@ def by_mineru(
|
||||
|
||||
if mineru_llm_name:
|
||||
try:
|
||||
ocr_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.OCR, mineru_llm_name)
|
||||
ocr_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.OCR, mineru_llm_name)
|
||||
ocr_model = LLMBundle(tenant_id=tenant_id, model_config=ocr_model_config, lang=lang)
|
||||
pdf_parser = ocr_model.mdl
|
||||
|
||||
@@ -211,7 +211,7 @@ def by_opendataloader(
|
||||
|
||||
if opendataloader_llm_name:
|
||||
try:
|
||||
ocr_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.OCR, opendataloader_llm_name)
|
||||
ocr_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.OCR, opendataloader_llm_name)
|
||||
ocr_model = LLMBundle(tenant_id=tenant_id, model_config=ocr_model_config, lang=lang)
|
||||
pdf_parser = ocr_model.mdl
|
||||
parse_options = {k: kwargs[k] for k in ("hybrid", "image_output", "sanitize") if k in kwargs}
|
||||
@@ -272,7 +272,7 @@ def by_paddleocr(
|
||||
|
||||
if paddleocr_llm_name:
|
||||
try:
|
||||
ocr_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.OCR, paddleocr_llm_name)
|
||||
ocr_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.OCR, paddleocr_llm_name)
|
||||
ocr_model = LLMBundle(tenant_id=tenant_id, model_config=ocr_model_config, lang=lang)
|
||||
pdf_parser = ocr_model.mdl
|
||||
sections, tables = pdf_parser.parse_pdf(
|
||||
@@ -301,7 +301,7 @@ def by_plaintext(filename, binary=None, from_page=0, to_page=MAXIMUM_PAGE_NUMBER
|
||||
tenant_id = kwargs.get("tenant_id")
|
||||
if not tenant_id:
|
||||
raise ValueError("tenant_id is required when using vision layout recognizer")
|
||||
vision_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.IMAGE2TEXT, layout_recognizer)
|
||||
vision_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.IMAGE2TEXT, layout_recognizer)
|
||||
vision_model = LLMBundle(
|
||||
tenant_id,
|
||||
model_config=vision_model_config,
|
||||
|
||||
+2
-5
@@ -25,7 +25,7 @@ from common import settings
|
||||
from common.constants import LLMType
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_id, get_model_config_by_type_and_name
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance
|
||||
from common.misc_utils import get_uuid
|
||||
from rag.nlp import tokenize, search
|
||||
from ranx import evaluate
|
||||
@@ -43,10 +43,7 @@ class Benchmark:
|
||||
e, self.kb = KnowledgebaseService.get_by_id(kb_id)
|
||||
self.similarity_threshold = self.kb.similarity_threshold
|
||||
self.vector_similarity_weight = self.kb.vector_similarity_weight
|
||||
if self.kb.tenant_embd_id:
|
||||
embd_model_config = get_model_config_by_id(self.kb.tenant_embd_id)
|
||||
else:
|
||||
embd_model_config = get_model_config_by_type_and_name(self.kb.tenant_id, LLMType.EMBEDDING, self.kb.embd_id)
|
||||
embd_model_config = get_model_config_from_provider_instance(self.kb.tenant_id, LLMType.EMBEDDING, self.kb.embd_id)
|
||||
self.embd_mdl = LLMBundle(self.kb.tenant_id, embd_model_config, lang=self.kb.language)
|
||||
self.tenant_id = ''
|
||||
self.index_name = ''
|
||||
|
||||
@@ -27,7 +27,7 @@ from PIL import Image
|
||||
from api.db.services.file2document_service import File2DocumentService
|
||||
from api.db.services.file_service import FileService
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_type_and_name, get_tenant_default_model_by_type
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_from_provider_instance
|
||||
from common import settings
|
||||
from common.constants import LLMType
|
||||
from common.misc_utils import get_uuid, thread_pool_exec
|
||||
@@ -381,7 +381,7 @@ class Parser(ProcessBase):
|
||||
raise RuntimeError("MinerU model not configured. Please add MinerU in Model Providers or set MINERU_* env.")
|
||||
|
||||
tenant_id = self._canvas._tenant_id
|
||||
ocr_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.OCR, parser_model_name)
|
||||
ocr_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.OCR, parser_model_name)
|
||||
ocr_model = LLMBundle(tenant_id, ocr_model_config, lang=conf.get("lang", "Chinese"))
|
||||
pdf_parser = ocr_model.mdl
|
||||
|
||||
@@ -458,7 +458,7 @@ class Parser(ProcessBase):
|
||||
raise RuntimeError("OpenDataLoader model not configured. Please add OpenDataLoader in Model Providers.")
|
||||
|
||||
tenant_id = self._canvas._tenant_id
|
||||
ocr_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.OCR, parser_model_name)
|
||||
ocr_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.OCR, parser_model_name)
|
||||
ocr_model = LLMBundle(tenant_id, ocr_model_config)
|
||||
pdf_parser = ocr_model.mdl
|
||||
|
||||
@@ -563,7 +563,7 @@ class Parser(ProcessBase):
|
||||
raise RuntimeError("PaddleOCR model not configured. Please add PaddleOCR in Model Providers or set PADDLEOCR_* env.")
|
||||
|
||||
tenant_id = self._canvas._tenant_id
|
||||
ocr_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.OCR, parser_model_name)
|
||||
ocr_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.OCR, parser_model_name)
|
||||
ocr_model = LLMBundle(tenant_id, ocr_model_config)
|
||||
pdf_parser = ocr_model.mdl
|
||||
|
||||
@@ -593,7 +593,7 @@ class Parser(ProcessBase):
|
||||
# Vision parser treats each page as a large image block.
|
||||
else:
|
||||
if conf.get("parse_method"):
|
||||
vision_model_config = get_model_config_by_type_and_name(self._canvas._tenant_id, LLMType.IMAGE2TEXT, conf["parse_method"])
|
||||
vision_model_config = get_model_config_from_provider_instance(self._canvas._tenant_id, LLMType.IMAGE2TEXT, conf["parse_method"])
|
||||
else:
|
||||
vision_model_config = get_tenant_default_model_by_type(self._canvas._tenant_id, LLMType.IMAGE2TEXT)
|
||||
vision_model = LLMBundle(self._canvas._tenant_id, vision_model_config, lang=self._param.setups["pdf"].get("lang"))
|
||||
@@ -1067,7 +1067,7 @@ class Parser(ProcessBase):
|
||||
else:
|
||||
lang = conf["lang"]
|
||||
# use VLM to describe the picture
|
||||
cv_model_config = get_model_config_by_type_and_name(self._canvas.get_tenant_id(), LLMType.IMAGE2TEXT, conf["parse_method"])
|
||||
cv_model_config = get_model_config_from_provider_instance(self._canvas.get_tenant_id(), LLMType.IMAGE2TEXT, conf["parse_method"])
|
||||
cv_model = LLMBundle(self._canvas.get_tenant_id(), cv_model_config, lang=lang)
|
||||
img_binary = io.BytesIO()
|
||||
img.save(img_binary, format="JPEG")
|
||||
@@ -1103,7 +1103,7 @@ class Parser(ProcessBase):
|
||||
tmpf.write(blob)
|
||||
tmpf.flush()
|
||||
tmp_path = os.path.abspath(tmpf.name)
|
||||
seq2txt_model_config = get_model_config_by_type_and_name(self._canvas.get_tenant_id(), LLMType.SPEECH2TEXT, vlm["llm_id"])
|
||||
seq2txt_model_config = get_model_config_from_provider_instance(self._canvas.get_tenant_id(), LLMType.SPEECH2TEXT, vlm["llm_id"])
|
||||
seq2txt_mdl = LLMBundle(self._canvas.get_tenant_id(), seq2txt_model_config)
|
||||
txt = seq2txt_mdl.transcription(tmp_path)
|
||||
|
||||
@@ -1116,7 +1116,7 @@ class Parser(ProcessBase):
|
||||
conf = self._param.setups["video"]
|
||||
vlm = conf.get("vlm")
|
||||
self.set_output("output_format", conf["output_format"])
|
||||
cv_model_config = get_model_config_by_type_and_name(self._canvas.get_tenant_id(), LLMType.IMAGE2TEXT, vlm["llm_id"])
|
||||
cv_model_config = get_model_config_from_provider_instance(self._canvas.get_tenant_id(), LLMType.IMAGE2TEXT, vlm["llm_id"])
|
||||
cv_mdl = LLMBundle(self._canvas.get_tenant_id(), cv_model_config)
|
||||
video_prompt = str(conf.get("prompt", "") or "")
|
||||
txt = asyncio.run(cv_mdl.async_chat(system="", history=[], gen_conf={}, video_bytes=blob, filename=name, video_prompt=video_prompt))
|
||||
|
||||
@@ -20,8 +20,7 @@ from bs4 import BeautifulSoup
|
||||
from docx import Document
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.joint_services.tenant_model_service import (
|
||||
get_model_config_by_type_and_name,
|
||||
get_tenant_default_model_by_type,
|
||||
get_tenant_default_model_by_type, get_model_config_from_provider_instance,
|
||||
)
|
||||
from common.constants import LLMType
|
||||
from deepdoc.parser.figure_parser import VisionFigureParser
|
||||
@@ -173,7 +172,7 @@ def enhance_media_sections_with_vision(
|
||||
|
||||
try:
|
||||
try:
|
||||
vision_model_config = get_model_config_by_type_and_name(
|
||||
vision_model_config = get_model_config_from_provider_instance(
|
||||
tenant_id, LLMType.IMAGE2TEXT, vlm_conf["llm_id"]
|
||||
)
|
||||
except Exception:
|
||||
|
||||
@@ -21,7 +21,7 @@ import numpy as np
|
||||
from common.constants import LLMType
|
||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_by_id, get_model_config_by_type_and_name
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_from_provider_instance
|
||||
from common.connection_utils import timeout
|
||||
from rag.flow.base import ProcessBase, ProcessParamBase
|
||||
from rag.flow.parser.pdf_chunk_metadata import finalize_pdf_chunk
|
||||
@@ -60,10 +60,7 @@ class Tokenizer(ProcessBase):
|
||||
token_count = 0
|
||||
if self._canvas._kb_id:
|
||||
e, kb = KnowledgebaseService.get_by_id(self._canvas._kb_id)
|
||||
if kb.tenant_embd_id:
|
||||
embd_model_config = get_model_config_by_id(kb.tenant_embd_id)
|
||||
else:
|
||||
embd_model_config = get_model_config_by_type_and_name(self._canvas._tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
embd_model_config = get_model_config_from_provider_instance(self._canvas._tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
else:
|
||||
embd_model_config = get_tenant_default_model_by_type(self._canvas._tenant_id, LLMType.EMBEDDING)
|
||||
embedding_model = LLMBundle(self._canvas._tenant_id, embd_model_config)
|
||||
|
||||
@@ -24,7 +24,7 @@ from common.constants import LLMType
|
||||
from api.db.services.document_service import DocumentService
|
||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_by_id, get_model_config_by_type_and_name
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_from_provider_instance
|
||||
from rag.graphrag.general.graph_extractor import GraphExtractor
|
||||
from rag.graphrag.general.index import update_graph, with_resolution, with_community
|
||||
from common import settings
|
||||
@@ -74,10 +74,7 @@ async def main():
|
||||
llm_config = get_tenant_default_model_by_type(args.tenant_id, LLMType.CHAT)
|
||||
llm_bdl = LLMBundle(args.tenant_id, llm_config)
|
||||
_, kb = KnowledgebaseService.get_by_id(kb_id)
|
||||
if kb.tenant_embd_id:
|
||||
embd_model_config = get_model_config_by_id(kb.tenant_embd_id)
|
||||
else:
|
||||
embd_model_config = get_model_config_by_type_and_name(args.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
embd_model_config = get_model_config_from_provider_instance(args.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
embed_bdl = LLMBundle(args.tenant_id, embd_model_config)
|
||||
|
||||
graph, doc_ids = await update_graph(
|
||||
|
||||
@@ -24,7 +24,7 @@ from common.constants import LLMType
|
||||
from api.db.services.document_service import DocumentService
|
||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_id, get_model_config_by_type_and_name, get_tenant_default_model_by_type
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_from_provider_instance
|
||||
from rag.graphrag.general.index import update_graph
|
||||
from rag.graphrag.light.graph_extractor import GraphExtractor
|
||||
from common import settings
|
||||
@@ -75,10 +75,7 @@ async def main():
|
||||
llm_config = get_tenant_default_model_by_type(args.tenant_id, LLMType.CHAT)
|
||||
llm_bdl = LLMBundle(args.tenant_id, llm_config)
|
||||
_, kb = KnowledgebaseService.get_by_id(kb_id)
|
||||
if kb.tenant_embd_id:
|
||||
embd_model_config = get_model_config_by_id(kb.tenant_embd_id)
|
||||
else:
|
||||
embd_model_config = get_model_config_by_type_and_name(args.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
embd_model_config = get_model_config_from_provider_instance(args.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
embed_bdl = LLMBundle(args.tenant_id, embd_model_config)
|
||||
|
||||
graph, doc_ids = await update_graph(
|
||||
|
||||
@@ -318,7 +318,7 @@ if __name__ == "__main__":
|
||||
from common.constants import LLMType
|
||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_by_id, get_model_config_by_type_and_name
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_from_provider_instance
|
||||
from rag.nlp import search
|
||||
|
||||
settings.init_settings()
|
||||
@@ -332,10 +332,7 @@ if __name__ == "__main__":
|
||||
llm_config = get_tenant_default_model_by_type(args.tenant_id, LLMType.CHAT)
|
||||
llm_bdl = LLMBundle(args.tenant_id, llm_config)
|
||||
_, kb = KnowledgebaseService.get_by_id(kb_id)
|
||||
if kb.tenant_embd_id:
|
||||
embd_model_config = get_model_config_by_id(kb.tenant_embd_id)
|
||||
else:
|
||||
embd_model_config = get_model_config_by_type_and_name(args.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
embd_model_config = get_model_config_from_provider_instance(args.tenant_id, LLMType.EMBEDDING, kb.embd_id)
|
||||
embed_bdl = LLMBundle(args.tenant_id, embd_model_config)
|
||||
|
||||
kg = KGSearch(settings.docStoreConn)
|
||||
|
||||
+10
-10
@@ -22,6 +22,7 @@ from copy import deepcopy
|
||||
from typing import Tuple
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
import json_repair
|
||||
|
||||
from common.misc_utils import hash_str2int
|
||||
from rag.nlp import rag_tokenizer
|
||||
from rag.prompts.template import load_prompt
|
||||
@@ -244,14 +245,14 @@ async def question_proposal(chat_mdl, content, topn=3):
|
||||
async def full_question(tenant_id=None, llm_id=None, messages=[], language=None, chat_mdl=None):
|
||||
from common.constants import LLMType
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.services.tenant_llm_service import TenantLLMService
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_type_and_name
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance, get_model_type_by_name
|
||||
|
||||
if not chat_mdl:
|
||||
if TenantLLMService.llm_id2llm_type(llm_id) == "image2text":
|
||||
chat_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.IMAGE2TEXT, llm_id)
|
||||
model_types = get_model_type_by_name(tenant_id, llm_id)
|
||||
if "image2text" in model_types:
|
||||
chat_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.IMAGE2TEXT, llm_id)
|
||||
else:
|
||||
chat_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.CHAT, llm_id)
|
||||
chat_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.CHAT, llm_id)
|
||||
chat_mdl = LLMBundle(tenant_id, chat_model_config)
|
||||
conv = []
|
||||
for m in messages:
|
||||
@@ -280,16 +281,15 @@ async def full_question(tenant_id=None, llm_id=None, messages=[], language=None,
|
||||
async def cross_languages(tenant_id, llm_id, query, languages=[]):
|
||||
from common.constants import LLMType
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.services.tenant_llm_service import TenantLLMService
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_type_and_name, get_tenant_default_model_by_type
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance, get_tenant_default_model_by_type, get_model_type_by_name
|
||||
|
||||
if llm_id and TenantLLMService.llm_id2llm_type(llm_id) == "image2text":
|
||||
chat_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.IMAGE2TEXT, llm_id)
|
||||
if llm_id and "image2text" in get_model_type_by_name(tenant_id, llm_id) :
|
||||
chat_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.IMAGE2TEXT, llm_id)
|
||||
else:
|
||||
if not llm_id:
|
||||
chat_model_config = get_tenant_default_model_by_type(tenant_id, LLMType.CHAT)
|
||||
else:
|
||||
chat_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.CHAT, llm_id)
|
||||
chat_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.CHAT, llm_id)
|
||||
chat_mdl = LLMBundle(tenant_id, chat_model_config)
|
||||
rendered_sys_prompt = PROMPT_JINJA_ENV.from_string(CROSS_LANGUAGES_SYS_PROMPT_TEMPLATE).render()
|
||||
rendered_user_prompt = PROMPT_JINJA_ENV.from_string(CROSS_LANGUAGES_USER_PROMPT_TEMPLATE).render(query=query,
|
||||
|
||||
+10
-10
@@ -79,7 +79,7 @@ from api.db.services.doc_metadata_service import DocMetadataService
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.services.task_service import TaskService, has_canceled, CANVAS_DEBUG_DOC_ID, GRAPH_RAPTOR_FAKE_DOC_ID
|
||||
from api.db.services.file2document_service import File2DocumentService
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_type_and_name, get_tenant_default_model_by_type
|
||||
from api.db.joint_services.tenant_model_service import get_tenant_default_model_by_type, get_model_config_from_provider_instance
|
||||
from common.versions import get_ragflow_version
|
||||
from api.db.db_models import close_connection
|
||||
from rag.app import laws, paper, presentation, manual, qa, table, book, resume, picture, naive, one, audio, \
|
||||
@@ -423,7 +423,7 @@ async def build_chunks(task, progress_callback):
|
||||
if task["parser_config"].get("auto_keywords", 0):
|
||||
st = timer()
|
||||
progress_callback(msg="Start to generate keywords for every chunk ...")
|
||||
chat_model_config = get_model_config_by_type_and_name(task["tenant_id"], LLMType.CHAT, task["llm_id"])
|
||||
chat_model_config = get_model_config_from_provider_instance(task["tenant_id"], LLMType.CHAT, task["llm_id"])
|
||||
chat_mdl = LLMBundle(task["tenant_id"], chat_model_config, lang=task["language"])
|
||||
|
||||
async def doc_keyword_extraction(chat_mdl, d, topn):
|
||||
@@ -461,7 +461,7 @@ async def build_chunks(task, progress_callback):
|
||||
if task["parser_config"].get("auto_questions", 0):
|
||||
st = timer()
|
||||
progress_callback(msg="Start to generate questions for every chunk ...")
|
||||
chat_model_config = get_model_config_by_type_and_name(task["tenant_id"], LLMType.CHAT, task["llm_id"])
|
||||
chat_model_config = get_model_config_from_provider_instance(task["tenant_id"], LLMType.CHAT, task["llm_id"])
|
||||
chat_mdl = LLMBundle(task["tenant_id"], chat_model_config, lang=task["language"])
|
||||
|
||||
async def doc_question_proposal(chat_mdl, d, topn):
|
||||
@@ -498,7 +498,7 @@ async def build_chunks(task, progress_callback):
|
||||
if task["parser_config"].get("enable_metadata", False) and (task["parser_config"].get("metadata") or task["parser_config"].get("built_in_metadata")):
|
||||
st = timer()
|
||||
progress_callback(msg="Start to generate meta-data for every chunk ...")
|
||||
chat_model_config = get_model_config_by_type_and_name(task["tenant_id"], LLMType.CHAT, task["llm_id"])
|
||||
chat_model_config = get_model_config_from_provider_instance(task["tenant_id"], LLMType.CHAT, task["llm_id"])
|
||||
chat_mdl = LLMBundle(task["tenant_id"], chat_model_config, lang=task["language"])
|
||||
|
||||
async def gen_metadata_task(chat_mdl, d):
|
||||
@@ -575,7 +575,7 @@ async def build_chunks(task, progress_callback):
|
||||
set_tags_to_cache(kb_ids, all_tags)
|
||||
else:
|
||||
all_tags = json.loads(all_tags)
|
||||
chat_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.CHAT, task["llm_id"])
|
||||
chat_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.CHAT, task["llm_id"])
|
||||
chat_mdl = LLMBundle(task["tenant_id"], chat_model_config, lang=task["language"])
|
||||
|
||||
docs_to_tag = []
|
||||
@@ -641,7 +641,7 @@ async def build_chunks(task, progress_callback):
|
||||
@timed_with_recording
|
||||
def build_TOC(task, docs, progress_callback):
|
||||
progress_callback(msg="Start to generate table of content ...")
|
||||
chat_model_config = get_model_config_by_type_and_name(task["tenant_id"], LLMType.CHAT, task["llm_id"])
|
||||
chat_model_config = get_model_config_from_provider_instance(task["tenant_id"], LLMType.CHAT, task["llm_id"])
|
||||
chat_mdl = LLMBundle(task["tenant_id"], chat_model_config, lang=task["language"])
|
||||
docs = sorted(docs, key=lambda d: (
|
||||
d.get("page_num_int", 0)[0] if isinstance(d.get("page_num_int", 0), list) else d.get("page_num_int", 0),
|
||||
@@ -817,7 +817,7 @@ async def run_dataflow(task: dict):
|
||||
set_progress(task_id, prog=0.82, msg="\n-------------------------------------\nStart to embedding...")
|
||||
e, kb = KnowledgebaseService.get_by_id(task["kb_id"])
|
||||
embedding_id = kb.embd_id
|
||||
embd_model_config = get_model_config_by_type_and_name(task["tenant_id"], LLMType.EMBEDDING, embedding_id)
|
||||
embd_model_config = get_model_config_from_provider_instance(task["tenant_id"], LLMType.EMBEDDING, embedding_id)
|
||||
embedding_model = LLMBundle(task["tenant_id"], embd_model_config)
|
||||
|
||||
@timeout(60)
|
||||
@@ -1404,7 +1404,7 @@ async def do_handle_task(task):
|
||||
try:
|
||||
# bind embedding model
|
||||
if task_embedding_id:
|
||||
embd_model_config = get_model_config_by_type_and_name(task_tenant_id, LLMType.EMBEDDING, task_embedding_id)
|
||||
embd_model_config = get_model_config_from_provider_instance(task_tenant_id, LLMType.EMBEDDING, task_embedding_id)
|
||||
else:
|
||||
embd_model_config = get_tenant_default_model_by_type(task_tenant_id, LLMType.EMBEDDING)
|
||||
embedding_model = LLMBundle(task_tenant_id, embd_model_config, lang=task_language)
|
||||
@@ -1452,7 +1452,7 @@ async def do_handle_task(task):
|
||||
return
|
||||
|
||||
# bind LLM for raptor
|
||||
chat_model_config = get_model_config_by_type_and_name(task_tenant_id, LLMType.CHAT, kb_task_llm_id)
|
||||
chat_model_config = get_model_config_from_provider_instance(task_tenant_id, LLMType.CHAT, kb_task_llm_id)
|
||||
chat_model = LLMBundle(task_tenant_id, chat_model_config, lang=task_language)
|
||||
# run RAPTOR
|
||||
async with kg_limiter:
|
||||
@@ -1511,7 +1511,7 @@ async def do_handle_task(task):
|
||||
|
||||
graphrag_conf = kb_parser_config.get("graphrag", {})
|
||||
start_ts = timer()
|
||||
chat_model_config = get_model_config_by_type_and_name(task_tenant_id, LLMType.CHAT, kb_task_llm_id)
|
||||
chat_model_config = get_model_config_from_provider_instance(task_tenant_id, LLMType.CHAT, kb_task_llm_id)
|
||||
chat_model = LLMBundle(task_tenant_id, chat_model_config, lang=task_language)
|
||||
with_resolution = graphrag_conf.get("resolution", False)
|
||||
with_community = graphrag_conf.get("community", False)
|
||||
|
||||
@@ -39,7 +39,7 @@ from rag.svr.task_executor_refactor.task_context import TaskContext
|
||||
|
||||
from api.db.services.doc_metadata_service import DocMetadataService
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_type_and_name
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance
|
||||
from rag.prompts.generator import gen_metadata, keyword_extraction, question_proposal, content_tagging
|
||||
from rag.graphrag.utils import get_llm_cache, set_llm_cache
|
||||
|
||||
@@ -55,7 +55,7 @@ async def extract_keywords(docs: List[Dict], ctx: TaskContext) -> None:
|
||||
|
||||
st = timer()
|
||||
ctx.progress_cb(msg="Start to generate keywords for every chunk ...")
|
||||
chat_model_config = get_model_config_by_type_and_name(ctx.tenant_id, LLMType.CHAT, ctx.llm_id)
|
||||
chat_model_config = get_model_config_from_provider_instance(ctx.tenant_id, LLMType.CHAT, ctx.llm_id)
|
||||
with LLMBundle(ctx.tenant_id, chat_model_config, lang=ctx.language) as chat_model:
|
||||
|
||||
async def doc_keyword_extraction(chat_mdl, d, topn):
|
||||
@@ -98,7 +98,7 @@ async def generate_questions(docs: List[Dict], ctx: TaskContext) -> None:
|
||||
|
||||
st = timer()
|
||||
ctx.progress_cb(msg="Start to generate questions for every chunk ...")
|
||||
chat_model_config = get_model_config_by_type_and_name(ctx.tenant_id, LLMType.CHAT, ctx.llm_id)
|
||||
chat_model_config = get_model_config_from_provider_instance(ctx.tenant_id, LLMType.CHAT, ctx.llm_id)
|
||||
with LLMBundle(ctx.tenant_id, chat_model_config, lang=ctx.language) as chat_model:
|
||||
|
||||
async def doc_question_proposal(chat_mdl, d, topn):
|
||||
@@ -179,7 +179,7 @@ async def generate_metadata(docs: List[Dict], ctx: TaskContext) -> None:
|
||||
|
||||
st = timer()
|
||||
ctx.progress_cb(msg="Start to generate meta-data for every chunk ...")
|
||||
chat_model_config = get_model_config_by_type_and_name(ctx.tenant_id, LLMType.CHAT, ctx.llm_id)
|
||||
chat_model_config = get_model_config_from_provider_instance(ctx.tenant_id, LLMType.CHAT, ctx.llm_id)
|
||||
with LLMBundle(ctx.tenant_id, chat_model_config, lang=ctx.language) as chat_model:
|
||||
metadata_conf = build_metadata_config(ctx.parser_config)
|
||||
|
||||
@@ -244,7 +244,7 @@ async def apply_tags(docs: List[Dict], ctx: TaskContext) -> None:
|
||||
st = timer()
|
||||
examples = []
|
||||
all_tags = settings.retriever.all_tags_in_portion(tenant_id, kb_ids, S)
|
||||
chat_model_config = get_model_config_by_type_and_name(tenant_id, LLMType.CHAT, ctx.llm_id)
|
||||
chat_model_config = get_model_config_from_provider_instance(tenant_id, LLMType.CHAT, ctx.llm_id)
|
||||
with LLMBundle(ctx.tenant_id, chat_model_config, lang=ctx.language) as chat_model:
|
||||
|
||||
docs_to_tag = []
|
||||
|
||||
@@ -38,7 +38,7 @@ from api.db.services.canvas_service import UserCanvasService
|
||||
from api.db.services.document_service import DocumentService
|
||||
from api.db.services.doc_metadata_service import DocMetadataService
|
||||
from api.db.services.pipeline_operation_log_service import PipelineOperationLogService
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_by_type_and_name
|
||||
from api.db.joint_services.tenant_model_service import get_model_config_from_provider_instance
|
||||
from common.constants import LLMType, PipelineTaskType
|
||||
from common.metadata_utils import update_metadata_to
|
||||
from common.misc_utils import thread_pool_exec
|
||||
@@ -244,7 +244,7 @@ class DataflowService:
|
||||
self._progress(prog=0.82, msg="\n-------------------------------------\nStart to embedding...")
|
||||
e, kb = self._get_kb_by_id(ctx.kb_id)
|
||||
embedding_id = kb.embd_id
|
||||
embd_model_config = get_model_config_by_type_and_name(
|
||||
embd_model_config = get_model_config_from_provider_instance(
|
||||
ctx.tenant_id, LLMType.EMBEDDING, embedding_id
|
||||
)
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
|
||||
@@ -32,8 +32,8 @@ from api.db.services.document_service import DocumentService
|
||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from api.db.joint_services.memory_message_service import handle_save_to_memory_task
|
||||
from api.db.joint_services.tenant_model_service import (
|
||||
get_model_config_by_type_and_name,
|
||||
get_tenant_default_model_by_type,
|
||||
get_model_config_from_provider_instance
|
||||
)
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.services.task_service import GRAPH_RAPTOR_FAKE_DOC_ID
|
||||
@@ -212,7 +212,7 @@ class TaskHandler:
|
||||
|
||||
try:
|
||||
if task_embedding_id:
|
||||
embd_model_config = get_model_config_by_type_and_name(
|
||||
embd_model_config = get_model_config_from_provider_instance(
|
||||
task_tenant_id, LLMType.EMBEDDING, task_embedding_id
|
||||
)
|
||||
else:
|
||||
@@ -269,7 +269,7 @@ class TaskHandler:
|
||||
return
|
||||
|
||||
# Bind LLM for raptor
|
||||
chat_model_config = get_model_config_by_type_and_name(
|
||||
chat_model_config = get_model_config_from_provider_instance(
|
||||
task_tenant_id, LLMType.CHAT, kb_task_llm_id
|
||||
)
|
||||
with LLMBundle(task_tenant_id, chat_model_config, lang=ctx.language) as chat_model:
|
||||
@@ -354,7 +354,7 @@ class TaskHandler:
|
||||
|
||||
graphrag_conf = kb_parser_config.get("graphrag", {})
|
||||
start_ts = timer()
|
||||
chat_model_config = get_model_config_by_type_and_name(
|
||||
chat_model_config = get_model_config_from_provider_instance(
|
||||
task_tenant_id, LLMType.CHAT, kb_task_llm_id
|
||||
)
|
||||
with LLMBundle(task_tenant_id, chat_model_config, lang=task_language) as chat_model:
|
||||
@@ -512,7 +512,7 @@ class TaskHandler:
|
||||
def _build_toc(cls, ctx: TaskContext, docs: List[Dict], progress_cb: Callable) -> Optional[Dict]:
|
||||
"""Build table of contents."""
|
||||
progress_cb(msg="Start to generate table of content ...")
|
||||
chat_model_config = get_model_config_by_type_and_name(
|
||||
chat_model_config = get_model_config_from_provider_instance(
|
||||
ctx.tenant_id, LLMType.CHAT, ctx.llm_id
|
||||
)
|
||||
with LLMBundle(ctx.tenant_id, chat_model_config, lang=ctx.language) as chat_mdl:
|
||||
|
||||
+84
-18
@@ -92,6 +92,7 @@ def get_email():
|
||||
|
||||
|
||||
def get_my_llms(auth, name):
|
||||
# todo deprecated
|
||||
url = HOST_ADDRESS + "/v1/llm/my_llms"
|
||||
authorization = {"Authorization": auth}
|
||||
response = requests.get(url=url, headers=authorization)
|
||||
@@ -103,7 +104,20 @@ def get_my_llms(auth, name):
|
||||
return False
|
||||
|
||||
|
||||
def get_added_models(auth, factory_name):
|
||||
url = HOST_ADDRESS + "/api/v1/models"
|
||||
authorization = {"Authorization": auth}
|
||||
response = requests.get(url=url, headers=authorization)
|
||||
res = response.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res.get("message"))
|
||||
added_factory = {model["provider_name"] for model in res.get("data", [])}
|
||||
if factory_name in added_factory:
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_models(auth):
|
||||
# todo deprecated
|
||||
url = HOST_ADDRESS + "/v1/llm/set_api_key"
|
||||
authorization = {"Authorization": auth}
|
||||
models_info = {
|
||||
@@ -118,7 +132,32 @@ def add_models(auth):
|
||||
pytest.exit(f"Critical error in add_models: {res.get('message')}")
|
||||
|
||||
|
||||
def add_model_instance(auth):
|
||||
add_provider_api = HOST_ADDRESS + "/api/v1/providers"
|
||||
authorization = {"Authorization": auth}
|
||||
add_provider_response = requests.put(url=add_provider_api, headers=authorization, json={"provider_name": "ZHIPU-AI"})
|
||||
add_provider_res = add_provider_response.json()
|
||||
if add_provider_res.get("code") != 0:
|
||||
pytest.exit(f"Critical error in add model provider: {add_provider_res.get('message')}")
|
||||
|
||||
add_instance_api = HOST_ADDRESS + "/api/v1/providers/ZHIPU-AI/instances"
|
||||
add_instance_response = requests.post(url=add_instance_api, headers=authorization, json={
|
||||
"instance_name": "CI",
|
||||
"api_key": ZHIPU_AI_API_KEY,
|
||||
"region": "default",
|
||||
"base_url": ""
|
||||
})
|
||||
add_instance_res = add_instance_response.json()
|
||||
if add_instance_res.get("code") != 0:
|
||||
pytest.exit(f"Critical error in add model instance: {add_instance_res.get('message')}")
|
||||
|
||||
add_success = get_added_models(auth, "ZHIPU-AI")
|
||||
if not add_success:
|
||||
pytest.exit("Critical error in check added model: add model failed")
|
||||
|
||||
|
||||
def get_tenant_info(auth):
|
||||
# todo deprecated
|
||||
url = HOST_ADDRESS + "/api/v1/users/me/models"
|
||||
authorization = {"Authorization": auth}
|
||||
response = requests.get(url=url, headers=authorization)
|
||||
@@ -131,22 +170,49 @@ def get_tenant_info(auth):
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def set_tenant_info(get_auth):
|
||||
auth = get_auth
|
||||
try:
|
||||
add_models(auth)
|
||||
tenant_id = get_tenant_info(auth)
|
||||
except Exception as e:
|
||||
pytest.exit(f"Error in set_tenant_info: {str(e)}")
|
||||
url = HOST_ADDRESS + "/api/v1/users/me/models"
|
||||
if not get_added_models(auth, "ZHIPU-AI"):
|
||||
try:
|
||||
add_model_instance(auth)
|
||||
except Exception as e:
|
||||
pytest.exit(f"Error in set_tenant_info: {str(e)}")
|
||||
url = HOST_ADDRESS + "/api/v1/models/default"
|
||||
authorization = {"Authorization": get_auth}
|
||||
tenant_info = {
|
||||
"tenant_id": tenant_id,
|
||||
"llm_id": "glm-4-flash@ZHIPU-AI",
|
||||
"embd_id": "BAAI/bge-small-en-v1.5@Builtin",
|
||||
"img2txt_id": "glm-4v@ZHIPU-AI",
|
||||
"asr_id": "",
|
||||
"tts_id": None,
|
||||
}
|
||||
response = requests.patch(url=url, headers=authorization, json=tenant_info)
|
||||
res = response.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res.get("message"))
|
||||
# set chat model
|
||||
set_default_llm_response = requests.patch(
|
||||
url=url,
|
||||
headers=authorization,
|
||||
json={
|
||||
"model_provider": "ZHIPU-AI",
|
||||
"model_instance": "CI",
|
||||
"model_type": "chat",
|
||||
"model_name": "glm-4-flash"
|
||||
})
|
||||
llm_res = set_default_llm_response.json()
|
||||
if llm_res.get("code") != 0:
|
||||
raise Exception(llm_res.get("message"))
|
||||
# set embedding model
|
||||
set_default_embedding_response = requests.patch(
|
||||
url=url,
|
||||
headers=authorization,
|
||||
json={
|
||||
"model_provider": "Builtin",
|
||||
"model_instance": "Local",
|
||||
"model_type": "embedding",
|
||||
"model_name": "BAAI/bge-small-en-v1.5"
|
||||
})
|
||||
embd_res = set_default_embedding_response.json()
|
||||
if embd_res.get("code") != 0:
|
||||
raise Exception(embd_res.get("message"))
|
||||
# set image to text model
|
||||
set_default_img2txt_response = requests.patch(
|
||||
url=url,
|
||||
headers=authorization,
|
||||
json={
|
||||
"model_provider": "ZHIPU-AI",
|
||||
"model_instance": "CI",
|
||||
"model_type": "vision",
|
||||
"model_name": "glm-4v"
|
||||
})
|
||||
img2txt_res = set_default_img2txt_response.json()
|
||||
if img2txt_res.get("code") != 0:
|
||||
raise Exception(img2txt_res.get("message"))
|
||||
@@ -46,7 +46,7 @@ DEFAULT_PARSER_CONFIG = {
|
||||
"image_context_size": 0,
|
||||
"table_context_size": 0,
|
||||
"topn_tags": 3,
|
||||
"llm_id": "glm-4-flash@ZHIPU-AI",
|
||||
"llm_id": "glm-4-flash@CI@ZHIPU-AI",
|
||||
"raptor": {
|
||||
"use_raptor": True,
|
||||
"prompt": "Please summarize the following paragraphs. Be careful with the numbers, do not make things up. Paragraphs as following:\n {cluster_content}\nThe above is the content you need to summarize.",
|
||||
|
||||
+72
-19
@@ -171,6 +171,7 @@ def token(auth):
|
||||
|
||||
|
||||
def get_my_llms(auth, name):
|
||||
# todo deprecated
|
||||
url = HOST_ADDRESS + f"/{VERSION}/llm/my_llms"
|
||||
authorization = {"Authorization": auth}
|
||||
response = requests.get(url=url, headers=authorization)
|
||||
@@ -182,7 +183,21 @@ def get_my_llms(auth, name):
|
||||
return False
|
||||
|
||||
|
||||
def get_added_models(auth, factory_name):
|
||||
url = HOST_ADDRESS + "/api/v1/models"
|
||||
authorization = {"Authorization": auth}
|
||||
response = requests.get(url=url, headers=authorization)
|
||||
res = response.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res.get("message"))
|
||||
added_factory = {model["provider_name"] for model in res.get("data", [])}
|
||||
if factory_name in added_factory:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def add_models(auth):
|
||||
# todo deprecated
|
||||
url = HOST_ADDRESS + f"/{VERSION}/llm/set_api_key"
|
||||
authorization = {"Authorization": auth}
|
||||
models_info = {
|
||||
@@ -197,7 +212,32 @@ def add_models(auth):
|
||||
pytest.exit(f"Critical error in add_models: {res.get('message')}")
|
||||
|
||||
|
||||
def add_model_instance(auth):
|
||||
add_provider_api = HOST_ADDRESS + "/api/v1/providers"
|
||||
authorization = {"Authorization": auth}
|
||||
add_provider_response = requests.put(url=add_provider_api, headers=authorization, json={"provider_name": "ZHIPU-AI"})
|
||||
add_provider_res = add_provider_response.json()
|
||||
if add_provider_res.get("code") != 0:
|
||||
pytest.exit(f"Critical error in add model provider: {add_provider_res.get('message')}")
|
||||
|
||||
add_instance_api = HOST_ADDRESS + "/api/v1/providers/ZHIPU-AI/instances"
|
||||
add_instance_response = requests.post(url=add_instance_api, headers=authorization, json={
|
||||
"instance_name": "CI",
|
||||
"api_key": ZHIPU_AI_API_KEY,
|
||||
"region": "default",
|
||||
"base_url": ""
|
||||
})
|
||||
add_instance_res = add_instance_response.json()
|
||||
if add_instance_res.get("code") != 0:
|
||||
pytest.exit(f"Critical error in add model instance: {add_instance_res.get('message')}")
|
||||
|
||||
add_success = get_added_models(auth, "ZHIPU-AI")
|
||||
if not add_success:
|
||||
pytest.exit("Critical error in check added model: add model failed")
|
||||
|
||||
|
||||
def get_tenant_info(auth):
|
||||
# todo deprecated
|
||||
url = HOST_ADDRESS + f"/api/{VERSION}/users/me/models"
|
||||
authorization = {"Authorization": auth}
|
||||
response = requests.get(url=url, headers=authorization)
|
||||
@@ -209,23 +249,36 @@ def get_tenant_info(auth):
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def set_tenant_info(auth):
|
||||
tenant_id = None
|
||||
try:
|
||||
add_models(auth)
|
||||
tenant_id = get_tenant_info(auth)
|
||||
except Exception as e:
|
||||
pytest.exit(f"Error in set_tenant_info: {str(e)}")
|
||||
url = HOST_ADDRESS + f"/api/{VERSION}/users/me/models"
|
||||
if not get_added_models(auth, "ZHIPU-AI"):
|
||||
try:
|
||||
add_model_instance(auth)
|
||||
except Exception as e:
|
||||
pytest.exit(f"Error in set_tenant_info: {str(e)}")
|
||||
url = HOST_ADDRESS + "/api/v1/models/default"
|
||||
authorization = {"Authorization": auth}
|
||||
tenant_info = {
|
||||
"tenant_id": tenant_id,
|
||||
"llm_id": "glm-4-flash@ZHIPU-AI",
|
||||
"embd_id": "BAAI/bge-small-en-v1.5@Builtin",
|
||||
"img2txt_id": "",
|
||||
"asr_id": "",
|
||||
"tts_id": None,
|
||||
}
|
||||
response = requests.patch(url=url, headers=authorization, json=tenant_info)
|
||||
res = response.json()
|
||||
if res.get("code") != 0:
|
||||
raise Exception(res.get("message"))
|
||||
# set chat model
|
||||
set_default_llm_response = requests.patch(
|
||||
url=url,
|
||||
headers=authorization,
|
||||
json={
|
||||
"model_provider": "ZHIPU-AI",
|
||||
"model_instance": "CI",
|
||||
"model_type": "chat",
|
||||
"model_name": "glm-4-flash"
|
||||
})
|
||||
llm_res = set_default_llm_response.json()
|
||||
if llm_res.get("code") != 0:
|
||||
raise Exception(llm_res.get("message"))
|
||||
# set embedding model
|
||||
set_default_embedding_response = requests.patch(
|
||||
url=url,
|
||||
headers=authorization,
|
||||
json={
|
||||
"model_provider": "Builtin",
|
||||
"model_instance": "Local",
|
||||
"model_type": "embedding",
|
||||
"model_name": "BAAI/bge-small-en-v1.5"
|
||||
})
|
||||
embd_res = set_default_embedding_response.json()
|
||||
if embd_res.get("code") != 0:
|
||||
raise Exception(embd_res.get("message"))
|
||||
@@ -753,26 +753,6 @@ def _load_chat_routes_unit_module(monkeypatch):
|
||||
kb_service_mod.KnowledgebaseService = _StubKnowledgebaseService
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.knowledgebase_service", kb_service_mod)
|
||||
|
||||
tenant_llm_service_mod = ModuleType("api.db.services.tenant_llm_service")
|
||||
|
||||
class _StubTenantLLMService:
|
||||
@staticmethod
|
||||
def split_model_name_and_factory(model_name):
|
||||
if model_name and "@" in model_name:
|
||||
return tuple(model_name.split("@", 1))
|
||||
return model_name, None
|
||||
|
||||
@staticmethod
|
||||
def query(**_kwargs):
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def get_api_key(*_args, **_kwargs):
|
||||
return SimpleNamespace(id=1)
|
||||
|
||||
tenant_llm_service_mod.TenantLLMService = _StubTenantLLMService
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.tenant_llm_service", tenant_llm_service_mod)
|
||||
|
||||
llm_service_mod = ModuleType("api.db.services.llm_service")
|
||||
llm_service_mod.LLMBundle = lambda *_args, **_kwargs: None
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.llm_service", llm_service_mod)
|
||||
@@ -782,8 +762,10 @@ def _load_chat_routes_unit_module(monkeypatch):
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.search_service", search_service_mod)
|
||||
|
||||
tenant_model_service_mod = ModuleType("api.db.joint_services.tenant_model_service")
|
||||
tenant_model_service_mod.get_model_config_by_type_and_name = lambda *_args, **_kwargs: {}
|
||||
tenant_model_service_mod.get_model_config_from_provider_instance = lambda *_args, **_kwargs: {}
|
||||
tenant_model_service_mod.get_tenant_default_model_by_type = lambda *_args, **_kwargs: {}
|
||||
tenant_model_service_mod.get_api_key = lambda *_args, **_kwargs: SimpleNamespace(id=1)
|
||||
tenant_model_service_mod.split_model_name = lambda model: (model.split("@")[0],"default", "factory")
|
||||
monkeypatch.setitem(sys.modules, "api.db.joint_services.tenant_model_service", tenant_model_service_mod)
|
||||
|
||||
user_service_mod = ModuleType("api.db.services.user_service")
|
||||
@@ -832,10 +814,6 @@ def _load_chat_routes_unit_module(monkeypatch):
|
||||
api_utils_mod.validate_request = lambda *_args, **_kwargs: (lambda func: func)
|
||||
monkeypatch.setitem(sys.modules, "api.utils.api_utils", api_utils_mod)
|
||||
|
||||
tenant_utils_mod = ModuleType("api.utils.tenant_utils")
|
||||
tenant_utils_mod.ensure_tenant_model_id_for_params = lambda _tenant_id, req: req
|
||||
monkeypatch.setitem(sys.modules, "api.utils.tenant_utils", tenant_utils_mod)
|
||||
|
||||
rag_pkg = ModuleType("rag")
|
||||
rag_pkg.__path__ = [str(repo_root / "rag")]
|
||||
monkeypatch.setitem(sys.modules, "rag", rag_pkg)
|
||||
@@ -1151,7 +1129,7 @@ def test_chat_create_accepts_provider_scoped_rerank_id_unit(monkeypatch):
|
||||
"name": "chat-a",
|
||||
"icon": "icon.png",
|
||||
"dataset_ids": ["kb-1"],
|
||||
"llm_id": "glm-4@ZHIPU-AI",
|
||||
"llm_id": "glm-4@@CI@ZHIPU-AI",
|
||||
"llm_setting": {"temperature": 0.8},
|
||||
"prompt_config": {
|
||||
"system": "Answer with {knowledge}",
|
||||
@@ -1162,7 +1140,7 @@ def test_chat_create_accepts_provider_scoped_rerank_id_unit(monkeypatch):
|
||||
"vector_similarity_weight": 0.25,
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(module.TenantService, "get_by_id", lambda _tid: (True, SimpleNamespace(llm_id="glm-4@ZHIPU-AI")))
|
||||
monkeypatch.setattr(module.TenantService, "get_by_id", lambda _tid: (True, SimpleNamespace(llm_id="glm-4@CI@ZHIPU-AI")))
|
||||
monkeypatch.setattr(module.DialogService, "query", lambda **_kwargs: [])
|
||||
monkeypatch.setattr(module.KnowledgebaseService, "accessible", lambda **_kwargs: [SimpleNamespace(id="kb-1")])
|
||||
monkeypatch.setattr(module.KnowledgebaseService, "query", lambda **_kwargs: [_DummyKB()])
|
||||
@@ -1170,30 +1148,18 @@ def test_chat_create_accepts_provider_scoped_rerank_id_unit(monkeypatch):
|
||||
|
||||
def _split_model_name_and_factory(model_name):
|
||||
return {
|
||||
"glm-4@ZHIPU-AI": ("glm-4", "ZHIPU-AI"),
|
||||
"custom-reranker@OpenAI": ("custom-reranker", "OpenAI"),
|
||||
"glm-4@ZHIPU-AI": ("glm-4", "default", "ZHIPU-AI"),
|
||||
"glm-4@CI@ZHIPU-AI": ("glm-4", "CI", "ZHIPU-AI"),
|
||||
"custom-reranker@OpenAI": ("custom-reranker", "default", "OpenAI")
|
||||
}.get(model_name, (model_name, None))
|
||||
|
||||
def _query(**kwargs):
|
||||
query_calls.append(kwargs)
|
||||
if kwargs == {
|
||||
"tenant_id": "tenant-1",
|
||||
"llm_name": "glm-4",
|
||||
"llm_factory": "ZHIPU-AI",
|
||||
"model_type": "chat",
|
||||
}:
|
||||
return [SimpleNamespace(id="llm-1")]
|
||||
if kwargs == {
|
||||
"tenant_id": "tenant-1",
|
||||
"llm_name": "custom-reranker",
|
||||
"llm_factory": "OpenAI",
|
||||
"model_type": "rerank",
|
||||
}:
|
||||
return [SimpleNamespace(id="rerank-1")]
|
||||
return []
|
||||
monkeypatch.setattr(module, "split_model_name", _split_model_name_and_factory)
|
||||
|
||||
monkeypatch.setattr(module.TenantLLMService, "split_model_name_and_factory", _split_model_name_and_factory)
|
||||
monkeypatch.setattr(module.TenantLLMService, "query", _query)
|
||||
def _get_model_config_from_provider_instance(**kwargs):
|
||||
query_calls.append(kwargs)
|
||||
return {}
|
||||
|
||||
monkeypatch.setattr(module, "get_model_config_from_provider_instance", _get_model_config_from_provider_instance)
|
||||
|
||||
def _save(**kwargs):
|
||||
saved.update(kwargs)
|
||||
@@ -1207,8 +1173,7 @@ def test_chat_create_accepts_provider_scoped_rerank_id_unit(monkeypatch):
|
||||
assert saved["rerank_id"] == "custom-reranker@OpenAI"
|
||||
assert {
|
||||
"tenant_id": "tenant-1",
|
||||
"llm_name": "custom-reranker",
|
||||
"llm_factory": "OpenAI",
|
||||
"model_name": "custom-reranker@OpenAI",
|
||||
"model_type": "rerank",
|
||||
} in query_calls
|
||||
|
||||
@@ -1220,7 +1185,7 @@ def test_chat_create_allows_default_knowledge_placeholder_without_sources_unit(m
|
||||
_set_route_unit_request_json(monkeypatch, module, {"name": "chat-a"})
|
||||
monkeypatch.setattr(module.TenantService, "get_by_id", lambda _tid: (True, SimpleNamespace(llm_id="glm-4")))
|
||||
monkeypatch.setattr(module.DialogService, "query", lambda **_kwargs: [])
|
||||
monkeypatch.setattr(module.TenantLLMService, "get_api_key", lambda *_args, **_kwargs: SimpleNamespace(id=1))
|
||||
monkeypatch.setattr(module, "get_api_key", lambda *_args, **_kwargs: SimpleNamespace(id=1))
|
||||
|
||||
def _save(**kwargs):
|
||||
saved.update(kwargs)
|
||||
@@ -1262,8 +1227,7 @@ def test_chat_create_uses_direct_chat_fields_unit(monkeypatch):
|
||||
monkeypatch.setattr(module.KnowledgebaseService, "accessible", lambda **_kwargs: [SimpleNamespace(id="kb-1")])
|
||||
monkeypatch.setattr(module.KnowledgebaseService, "query", lambda **_kwargs: [_DummyKB()])
|
||||
monkeypatch.setattr(module.KnowledgebaseService, "get_by_id", lambda _id: (True, _DummyKB()))
|
||||
monkeypatch.setattr(module.TenantLLMService, "split_model_name_and_factory", lambda model: (model.split("@")[0], "factory"))
|
||||
monkeypatch.setattr(module.TenantLLMService, "query", lambda **_kwargs: [SimpleNamespace(id="llm-1")])
|
||||
monkeypatch.setattr(module, "split_model_name", lambda model: (model.split("@")[0],"default", "factory"))
|
||||
|
||||
def _save(**kwargs):
|
||||
saved.update(kwargs)
|
||||
@@ -1407,8 +1371,8 @@ def test_patch_chat_drops_response_only_fields_before_update_unit(monkeypatch):
|
||||
monkeypatch.setattr(module.TenantService, "get_by_id", lambda _tid: (True, SimpleNamespace(llm_id="glm-4")))
|
||||
monkeypatch.setattr(module.KnowledgebaseService, "accessible", lambda **_kwargs: [SimpleNamespace(id="kb-1")])
|
||||
monkeypatch.setattr(module.KnowledgebaseService, "query", lambda **_kwargs: [_DummyKB()])
|
||||
monkeypatch.setattr(module.TenantLLMService, "split_model_name_and_factory", lambda model: (model.split("@")[0], "factory"))
|
||||
monkeypatch.setattr(module.TenantLLMService, "query", lambda **_kwargs: [SimpleNamespace(id="llm-1")])
|
||||
monkeypatch.setattr(module, "split_model_name", lambda model: (model.split("@")[0],"default", "factory"))
|
||||
monkeypatch.setattr(module, "get_api_key", lambda *args, **kwargs: SimpleNamespace(id=1))
|
||||
|
||||
def _update(_chat_id, req):
|
||||
updated.update(req)
|
||||
@@ -1477,8 +1441,7 @@ def test_update_chat_allows_knowledge_placeholder_without_sources_unit(monkeypat
|
||||
monkeypatch.setattr(module.DialogService, "query", lambda **_kwargs: [SimpleNamespace(id="chat-1")])
|
||||
monkeypatch.setattr(module.DialogService, "get_by_id", lambda _id: (True, _DummyDialogRecord(existing)))
|
||||
monkeypatch.setattr(module.TenantService, "get_by_id", lambda _tid: (True, SimpleNamespace(llm_id="glm-4")))
|
||||
monkeypatch.setattr(module.TenantLLMService, "split_model_name_and_factory", lambda model: (model.split("@")[0], "factory"))
|
||||
monkeypatch.setattr(module.TenantLLMService, "query", lambda **_kwargs: [SimpleNamespace(id="llm-1")])
|
||||
monkeypatch.setattr(module, "split_model_name", lambda model: (model.split("@")[0], "default", "factory"))
|
||||
updated = {}
|
||||
|
||||
def _update(_chat_id, payload):
|
||||
@@ -1534,35 +1497,35 @@ def test_chat_create_avatar_contract(rest_client, clear_chats, tmp_path):
|
||||
def test_chat_create_llm_contract(rest_client, clear_chats, ensure_parsed_document):
|
||||
dataset_id, _ = ensure_parsed_document()
|
||||
cases = [
|
||||
("default llm", {}, 0, "", "glm-4-flash@ZHIPU-AI", {}),
|
||||
("explicit llm_id", {"llm_id": "glm-4"}, 0, "", "glm-4", {}),
|
||||
("default llm", {}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {}),
|
||||
("explicit llm_id", {"llm_id": "glm-4"}, 102, "`llm_id` glm-4 doesn't exist", None, None),
|
||||
("unknown llm_id", {"llm_id": "unknown"}, 102, "`llm_id` unknown doesn't exist", None, None),
|
||||
("temperature zero", {"llm_setting": {"temperature": 0}}, 0, "", "glm-4-flash@ZHIPU-AI", {"temperature": 0}),
|
||||
("temperature one", {"llm_setting": {"temperature": 1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"temperature": 1}),
|
||||
("temperature negative one", {"llm_setting": {"temperature": -1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"temperature": -1}),
|
||||
("temperature ten", {"llm_setting": {"temperature": 10}}, 0, "", "glm-4-flash@ZHIPU-AI", {"temperature": 10}),
|
||||
("temperature string", {"llm_setting": {"temperature": "a"}}, 0, "", "glm-4-flash@ZHIPU-AI", {"temperature": "a"}),
|
||||
("top_p zero", {"llm_setting": {"top_p": 0}}, 0, "", "glm-4-flash@ZHIPU-AI", {"top_p": 0}),
|
||||
("top_p one", {"llm_setting": {"top_p": 1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"top_p": 1}),
|
||||
("top_p negative one", {"llm_setting": {"top_p": -1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"top_p": -1}),
|
||||
("top_p ten", {"llm_setting": {"top_p": 10}}, 0, "", "glm-4-flash@ZHIPU-AI", {"top_p": 10}),
|
||||
("top_p string", {"llm_setting": {"top_p": "a"}}, 0, "", "glm-4-flash@ZHIPU-AI", {"top_p": "a"}),
|
||||
("presence_penalty zero", {"llm_setting": {"presence_penalty": 0}}, 0, "", "glm-4-flash@ZHIPU-AI", {"presence_penalty": 0}),
|
||||
("presence_penalty one", {"llm_setting": {"presence_penalty": 1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"presence_penalty": 1}),
|
||||
("presence_penalty negative one", {"llm_setting": {"presence_penalty": -1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"presence_penalty": -1}),
|
||||
("presence_penalty ten", {"llm_setting": {"presence_penalty": 10}}, 0, "", "glm-4-flash@ZHIPU-AI", {"presence_penalty": 10}),
|
||||
("presence_penalty string", {"llm_setting": {"presence_penalty": "a"}}, 0, "", "glm-4-flash@ZHIPU-AI", {"presence_penalty": "a"}),
|
||||
("frequency_penalty zero", {"llm_setting": {"frequency_penalty": 0}}, 0, "", "glm-4-flash@ZHIPU-AI", {"frequency_penalty": 0}),
|
||||
("frequency_penalty one", {"llm_setting": {"frequency_penalty": 1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"frequency_penalty": 1}),
|
||||
("frequency_penalty negative one", {"llm_setting": {"frequency_penalty": -1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"frequency_penalty": -1}),
|
||||
("frequency_penalty ten", {"llm_setting": {"frequency_penalty": 10}}, 0, "", "glm-4-flash@ZHIPU-AI", {"frequency_penalty": 10}),
|
||||
("frequency_penalty string", {"llm_setting": {"frequency_penalty": "a"}}, 0, "", "glm-4-flash@ZHIPU-AI", {"frequency_penalty": "a"}),
|
||||
("max_token zero", {"llm_setting": {"max_token": 0}}, 0, "", "glm-4-flash@ZHIPU-AI", {"max_token": 0}),
|
||||
("max_token 1024", {"llm_setting": {"max_token": 1024}}, 0, "", "glm-4-flash@ZHIPU-AI", {"max_token": 1024}),
|
||||
("max_token negative one", {"llm_setting": {"max_token": -1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"max_token": -1}),
|
||||
("max_token ten", {"llm_setting": {"max_token": 10}}, 0, "", "glm-4-flash@ZHIPU-AI", {"max_token": 10}),
|
||||
("max_token string", {"llm_setting": {"max_token": "a"}}, 0, "", "glm-4-flash@ZHIPU-AI", {"max_token": "a"}),
|
||||
("unknown llm setting key", {"llm_setting": {"unknown": "unknown"}}, 0, "", "glm-4-flash@ZHIPU-AI", {"unknown": "unknown"}),
|
||||
("temperature zero", {"llm_setting": {"temperature": 0}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"temperature": 0}),
|
||||
("temperature one", {"llm_setting": {"temperature": 1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"temperature": 1}),
|
||||
("temperature negative one", {"llm_setting": {"temperature": -1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"temperature": -1}),
|
||||
("temperature ten", {"llm_setting": {"temperature": 10}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"temperature": 10}),
|
||||
("temperature string", {"llm_setting": {"temperature": "a"}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"temperature": "a"}),
|
||||
("top_p zero", {"llm_setting": {"top_p": 0}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"top_p": 0}),
|
||||
("top_p one", {"llm_setting": {"top_p": 1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"top_p": 1}),
|
||||
("top_p negative one", {"llm_setting": {"top_p": -1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"top_p": -1}),
|
||||
("top_p ten", {"llm_setting": {"top_p": 10}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"top_p": 10}),
|
||||
("top_p string", {"llm_setting": {"top_p": "a"}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"top_p": "a"}),
|
||||
("presence_penalty zero", {"llm_setting": {"presence_penalty": 0}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"presence_penalty": 0}),
|
||||
("presence_penalty one", {"llm_setting": {"presence_penalty": 1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"presence_penalty": 1}),
|
||||
("presence_penalty negative one", {"llm_setting": {"presence_penalty": -1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"presence_penalty": -1}),
|
||||
("presence_penalty ten", {"llm_setting": {"presence_penalty": 10}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"presence_penalty": 10}),
|
||||
("presence_penalty string", {"llm_setting": {"presence_penalty": "a"}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"presence_penalty": "a"}),
|
||||
("frequency_penalty zero", {"llm_setting": {"frequency_penalty": 0}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"frequency_penalty": 0}),
|
||||
("frequency_penalty one", {"llm_setting": {"frequency_penalty": 1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"frequency_penalty": 1}),
|
||||
("frequency_penalty negative one", {"llm_setting": {"frequency_penalty": -1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"frequency_penalty": -1}),
|
||||
("frequency_penalty ten", {"llm_setting": {"frequency_penalty": 10}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"frequency_penalty": 10}),
|
||||
("frequency_penalty string", {"llm_setting": {"frequency_penalty": "a"}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"frequency_penalty": "a"}),
|
||||
("max_token zero", {"llm_setting": {"max_token": 0}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"max_token": 0}),
|
||||
("max_token 1024", {"llm_setting": {"max_token": 1024}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"max_token": 1024}),
|
||||
("max_token negative one", {"llm_setting": {"max_token": -1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"max_token": -1}),
|
||||
("max_token ten", {"llm_setting": {"max_token": 10}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"max_token": 10}),
|
||||
("max_token string", {"llm_setting": {"max_token": "a"}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"max_token": "a"}),
|
||||
("unknown llm setting key", {"llm_setting": {"unknown": "unknown"}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"unknown": "unknown"}),
|
||||
]
|
||||
|
||||
for index, (scenario_name, extra_payload, expected_code, expected_message, expected_llm_id, expected_llm_setting) in enumerate(cases, start=1):
|
||||
@@ -1801,35 +1764,35 @@ def test_chat_update_avatar_contract(rest_client, clear_chats, ensure_parsed_doc
|
||||
def test_chat_update_llm_contract(rest_client, clear_chats, ensure_parsed_document):
|
||||
dataset_id, _ = ensure_parsed_document()
|
||||
cases = [
|
||||
("default llm", {}, 0, "", "glm-4-flash@ZHIPU-AI", {}),
|
||||
("explicit llm_id", {"llm_id": "glm-4"}, 0, "", "glm-4", {}),
|
||||
("default llm", {}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {}),
|
||||
("explicit llm_id", {"llm_id": "glm-4"}, 102, "`llm_id` glm-4 doesn't exist", None, None),
|
||||
("unknown llm_id", {"llm_id": "unknown"}, 102, "`llm_id` unknown doesn't exist", None, None),
|
||||
("temperature zero", {"llm_setting": {"temperature": 0}}, 0, "", "glm-4-flash@ZHIPU-AI", {"temperature": 0}),
|
||||
("temperature one", {"llm_setting": {"temperature": 1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"temperature": 1}),
|
||||
("temperature negative one", {"llm_setting": {"temperature": -1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"temperature": -1}),
|
||||
("temperature ten", {"llm_setting": {"temperature": 10}}, 0, "", "glm-4-flash@ZHIPU-AI", {"temperature": 10}),
|
||||
("temperature string", {"llm_setting": {"temperature": "a"}}, 0, "", "glm-4-flash@ZHIPU-AI", {"temperature": "a"}),
|
||||
("top_p zero", {"llm_setting": {"top_p": 0}}, 0, "", "glm-4-flash@ZHIPU-AI", {"top_p": 0}),
|
||||
("top_p one", {"llm_setting": {"top_p": 1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"top_p": 1}),
|
||||
("top_p negative one", {"llm_setting": {"top_p": -1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"top_p": -1}),
|
||||
("top_p ten", {"llm_setting": {"top_p": 10}}, 0, "", "glm-4-flash@ZHIPU-AI", {"top_p": 10}),
|
||||
("top_p string", {"llm_setting": {"top_p": "a"}}, 0, "", "glm-4-flash@ZHIPU-AI", {"top_p": "a"}),
|
||||
("presence_penalty zero", {"llm_setting": {"presence_penalty": 0}}, 0, "", "glm-4-flash@ZHIPU-AI", {"presence_penalty": 0}),
|
||||
("presence_penalty one", {"llm_setting": {"presence_penalty": 1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"presence_penalty": 1}),
|
||||
("presence_penalty negative one", {"llm_setting": {"presence_penalty": -1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"presence_penalty": -1}),
|
||||
("presence_penalty ten", {"llm_setting": {"presence_penalty": 10}}, 0, "", "glm-4-flash@ZHIPU-AI", {"presence_penalty": 10}),
|
||||
("presence_penalty string", {"llm_setting": {"presence_penalty": "a"}}, 0, "", "glm-4-flash@ZHIPU-AI", {"presence_penalty": "a"}),
|
||||
("frequency_penalty zero", {"llm_setting": {"frequency_penalty": 0}}, 0, "", "glm-4-flash@ZHIPU-AI", {"frequency_penalty": 0}),
|
||||
("frequency_penalty one", {"llm_setting": {"frequency_penalty": 1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"frequency_penalty": 1}),
|
||||
("frequency_penalty negative one", {"llm_setting": {"frequency_penalty": -1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"frequency_penalty": -1}),
|
||||
("frequency_penalty ten", {"llm_setting": {"frequency_penalty": 10}}, 0, "", "glm-4-flash@ZHIPU-AI", {"frequency_penalty": 10}),
|
||||
("frequency_penalty string", {"llm_setting": {"frequency_penalty": "a"}}, 0, "", "glm-4-flash@ZHIPU-AI", {"frequency_penalty": "a"}),
|
||||
("max_token zero", {"llm_setting": {"max_token": 0}}, 0, "", "glm-4-flash@ZHIPU-AI", {"max_token": 0}),
|
||||
("max_token 1024", {"llm_setting": {"max_token": 1024}}, 0, "", "glm-4-flash@ZHIPU-AI", {"max_token": 1024}),
|
||||
("max_token negative one", {"llm_setting": {"max_token": -1}}, 0, "", "glm-4-flash@ZHIPU-AI", {"max_token": -1}),
|
||||
("max_token ten", {"llm_setting": {"max_token": 10}}, 0, "", "glm-4-flash@ZHIPU-AI", {"max_token": 10}),
|
||||
("max_token string", {"llm_setting": {"max_token": "a"}}, 0, "", "glm-4-flash@ZHIPU-AI", {"max_token": "a"}),
|
||||
("unknown llm setting key", {"llm_setting": {"unknown": "unknown"}}, 0, "", "glm-4-flash@ZHIPU-AI", {"unknown": "unknown"}),
|
||||
("temperature zero", {"llm_setting": {"temperature": 0}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"temperature": 0}),
|
||||
("temperature one", {"llm_setting": {"temperature": 1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"temperature": 1}),
|
||||
("temperature negative one", {"llm_setting": {"temperature": -1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"temperature": -1}),
|
||||
("temperature ten", {"llm_setting": {"temperature": 10}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"temperature": 10}),
|
||||
("temperature string", {"llm_setting": {"temperature": "a"}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"temperature": "a"}),
|
||||
("top_p zero", {"llm_setting": {"top_p": 0}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"top_p": 0}),
|
||||
("top_p one", {"llm_setting": {"top_p": 1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"top_p": 1}),
|
||||
("top_p negative one", {"llm_setting": {"top_p": -1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"top_p": -1}),
|
||||
("top_p ten", {"llm_setting": {"top_p": 10}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"top_p": 10}),
|
||||
("top_p string", {"llm_setting": {"top_p": "a"}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"top_p": "a"}),
|
||||
("presence_penalty zero", {"llm_setting": {"presence_penalty": 0}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"presence_penalty": 0}),
|
||||
("presence_penalty one", {"llm_setting": {"presence_penalty": 1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"presence_penalty": 1}),
|
||||
("presence_penalty negative one", {"llm_setting": {"presence_penalty": -1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"presence_penalty": -1}),
|
||||
("presence_penalty ten", {"llm_setting": {"presence_penalty": 10}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"presence_penalty": 10}),
|
||||
("presence_penalty string", {"llm_setting": {"presence_penalty": "a"}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"presence_penalty": "a"}),
|
||||
("frequency_penalty zero", {"llm_setting": {"frequency_penalty": 0}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"frequency_penalty": 0}),
|
||||
("frequency_penalty one", {"llm_setting": {"frequency_penalty": 1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"frequency_penalty": 1}),
|
||||
("frequency_penalty negative one", {"llm_setting": {"frequency_penalty": -1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"frequency_penalty": -1}),
|
||||
("frequency_penalty ten", {"llm_setting": {"frequency_penalty": 10}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"frequency_penalty": 10}),
|
||||
("frequency_penalty string", {"llm_setting": {"frequency_penalty": "a"}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"frequency_penalty": "a"}),
|
||||
("max_token zero", {"llm_setting": {"max_token": 0}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"max_token": 0}),
|
||||
("max_token 1024", {"llm_setting": {"max_token": 1024}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"max_token": 1024}),
|
||||
("max_token negative one", {"llm_setting": {"max_token": -1}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"max_token": -1}),
|
||||
("max_token ten", {"llm_setting": {"max_token": 10}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"max_token": 10}),
|
||||
("max_token string", {"llm_setting": {"max_token": "a"}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"max_token": "a"}),
|
||||
("unknown llm setting key", {"llm_setting": {"unknown": "unknown"}}, 0, "", "glm-4-flash@CI@ZHIPU-AI", {"unknown": "unknown"}),
|
||||
]
|
||||
|
||||
for index, (scenario_name, extra_payload, expected_code, expected_message, expected_llm_id, expected_llm_setting) in enumerate(cases, start=1):
|
||||
@@ -2033,13 +1996,13 @@ def test_chat_update_mapping_and_validation_branches_p2(rest_client, clear_chats
|
||||
def test_chat_update_rejects_unparsed_document(rest_client, clear_chats, create_document):
|
||||
dataset_id, _ = create_document()
|
||||
create_res = rest_client.post("/chats", json={"name": "restful_chat_update_unparsed_target", "dataset_ids": []})
|
||||
assert create_res.status_code == 200
|
||||
assert create_res.status_code == 200, create_res.text
|
||||
create_payload = create_res.json()
|
||||
assert create_payload["code"] == 0, create_payload
|
||||
chat_id = create_payload["data"]["id"]
|
||||
|
||||
res = rest_client.patch(f"/chats/{chat_id}", json={"dataset_ids": [dataset_id]})
|
||||
assert res.status_code == 200
|
||||
assert res.status_code == 200, res.text
|
||||
payload = res.json()
|
||||
assert payload["code"] == 102, payload
|
||||
assert "doesn't own parsed file" in payload["message"], payload
|
||||
@@ -459,7 +459,7 @@ def test_dataset_update_embedding_model_with_existing_chunks_contract(rest_clien
|
||||
assert dataset_payload["code"] == 0, dataset_payload
|
||||
current_embedding = dataset_payload["data"]["embedding_model"]
|
||||
|
||||
candidates = ["embedding-3@ZHIPU-AI", "BAAI/bge-small-en-v1.5@Builtin"]
|
||||
candidates = ["embedding-3@CI@ZHIPU-AI", "BAAI/bge-small-en-v1.5@Local@Builtin"]
|
||||
last_payload = None
|
||||
for candidate in candidates:
|
||||
if candidate == current_embedding:
|
||||
@@ -810,10 +810,10 @@ def test_dataset_update_embedding_model_invalid_and_none_contract(rest_client, c
|
||||
dataset_id = create_payload["data"]["id"]
|
||||
|
||||
invalid_cases = [
|
||||
("unknown@ZHIPU-AI", "Unsupported model: <unknown@ZHIPU-AI>"),
|
||||
("embedding-3@unknown", "Unsupported model: <embedding-3@unknown>"),
|
||||
("text-embedding-v3@Tongyi-Qianwen", "Unauthorized model: <text-embedding-v3@Tongyi-Qianwen>"),
|
||||
("text-embedding-3-small@OpenAI", "Unauthorized model: <text-embedding-3-small@OpenAI>"),
|
||||
("unknown@ZHIPU-AI", "Instance default not found for model unknown@ZHIPU-AI."),
|
||||
("embedding-3@unknown", "Provider unknown not found for model embedding-3@unknown."),
|
||||
("text-embedding-v3@Tongyi-Qianwen", "Provider Tongyi-Qianwen not found for model text-embedding-v3@Tongyi-Qianwen."),
|
||||
("text-embedding-3-small@OpenAI", "Provider OpenAI not found for model text-embedding-3-small@OpenAI."),
|
||||
]
|
||||
for embedding_model, expected_message in invalid_cases:
|
||||
res = rest_client.put(
|
||||
@@ -834,7 +834,7 @@ def test_dataset_update_embedding_model_invalid_and_none_contract(rest_client, c
|
||||
assert list_res.status_code == 200
|
||||
list_payload = list_res.json()
|
||||
assert list_payload["code"] == 0, list_payload
|
||||
assert list_payload["data"][0]["embedding_model"] == "BAAI/bge-small-en-v1.5@Builtin", list_payload
|
||||
assert list_payload["data"][0]["embedding_model"] == "BAAI/bge-small-en-v1.5@Local@Builtin", list_payload
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
@@ -1164,21 +1164,21 @@ def test_dataset_create_permission_contract(rest_client, clear_datasets, name, p
|
||||
@pytest.mark.parametrize(
|
||||
"name, embedding_model, expected_code, expected_embedding_model, expected_message, unauthorized_is_xfail",
|
||||
[
|
||||
("builtin_baai", "BAAI/bge-small-en-v1.5@Builtin", 0, "BAAI/bge-small-en-v1.5@Builtin", None, False),
|
||||
("tenant_zhipu", "embedding-3@ZHIPU-AI", 0, "embedding-3@ZHIPU-AI", None, True),
|
||||
("embedding_model_unset", "__UNSET__", 0, "BAAI/bge-small-en-v1.5@Builtin", None, False),
|
||||
("embedding_model_none", None, 0, "BAAI/bge-small-en-v1.5@Builtin", None, False),
|
||||
("unknown_llm_name", "unknown@ZHIPU-AI", 102, None, "Unsupported model: <unknown@ZHIPU-AI>", False),
|
||||
("unknown_llm_factory", "embedding-3@unknown", 102, None, "Unsupported model: <embedding-3@unknown>", False),
|
||||
("builtin_baai", "BAAI/bge-small-en-v1.5@Local@Builtin", 0, "BAAI/bge-small-en-v1.5@Local@Builtin", None, False),
|
||||
("tenant_zhipu", "embedding-3@CI@ZHIPU-AI", 0, "embedding-3@CI@ZHIPU-AI", None, True),
|
||||
("embedding_model_unset", "__UNSET__", 0, "BAAI/bge-small-en-v1.5@Local@Builtin", None, False),
|
||||
("embedding_model_none", None, 0, "BAAI/bge-small-en-v1.5@Local@Builtin", None, False),
|
||||
("unknown_llm_name", "unknown@ZHIPU-AI", 102, None, "Instance default not found for model unknown@ZHIPU-AI.", False),
|
||||
("unknown_llm_factory", "embedding-3@unknown", 102, None, "Provider unknown not found for model embedding-3@unknown.", False),
|
||||
(
|
||||
"tenant_no_auth_default_tenant_llm",
|
||||
"text-embedding-v3@Tongyi-Qianwen",
|
||||
102,
|
||||
None,
|
||||
"Unauthorized model: <text-embedding-v3@Tongyi-Qianwen>",
|
||||
"Provider Tongyi-Qianwen not found for model text-embedding-v3@Tongyi-Qianwen.",
|
||||
False,
|
||||
),
|
||||
("tenant_no_auth", "text-embedding-3-small@OpenAI", 102, None, "Unauthorized model: <text-embedding-3-small@OpenAI>", False),
|
||||
("tenant_no_auth", "text-embedding-3-small@OpenAI", 102, None, "Provider OpenAI not found for model text-embedding-3-small@OpenAI.", False),
|
||||
],
|
||||
ids=[
|
||||
"builtin_baai",
|
||||
|
||||
@@ -103,8 +103,6 @@ def _load_dify_retrieval_module(monkeypatch):
|
||||
monkeypatch.setitem(sys.modules, "deepdoc.parser.utils", deepdoc_parser_utils)
|
||||
monkeypatch.setitem(sys.modules, "xgboost", ModuleType("xgboost"))
|
||||
|
||||
tenant_llm_service_mod = ModuleType("api.db.services.tenant_llm_service")
|
||||
|
||||
class _MockModelConfig:
|
||||
def __init__(self, tenant_id, model_name):
|
||||
self.tenant_id = tenant_id
|
||||
@@ -157,15 +155,6 @@ def _load_dify_retrieval_module(monkeypatch):
|
||||
return parts[0], parts[1]
|
||||
return model_name, None
|
||||
|
||||
tenant_llm_service_mod.TenantService = _StubTenantService
|
||||
tenant_llm_service_mod.TenantLLMService = _StubTenantLLMService
|
||||
|
||||
class _StubLLMFactoriesService:
|
||||
pass
|
||||
|
||||
tenant_llm_service_mod.LLMFactoriesService = _StubLLMFactoriesService
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.tenant_llm_service", tenant_llm_service_mod)
|
||||
|
||||
llm_service_mod = ModuleType("api.db.services.llm_service")
|
||||
|
||||
class _StubLLM:
|
||||
@@ -233,12 +222,17 @@ def _load_dify_retrieval_module(monkeypatch):
|
||||
raise Exception("Model Name is required")
|
||||
return _MockModelConfig2(tenant_id, model_name).to_dict()
|
||||
|
||||
def _get_model_config_from_provider_instance(tenant_id: str, model_type: str, model_name: str):
|
||||
if not model_name:
|
||||
raise Exception("Model Name is required")
|
||||
return _MockModelConfig2(tenant_id, model_name).to_dict()
|
||||
|
||||
def _get_tenant_default_model_by_type(tenant_id: str, model_type):
|
||||
return _MockModelConfig2(tenant_id, "chat-model").to_dict()
|
||||
|
||||
tenant_model_service_mod.get_model_config_by_id = _get_model_config_by_id
|
||||
tenant_model_service_mod.get_model_config_by_type_and_name = _get_model_config_by_type_and_name
|
||||
tenant_model_service_mod.get_tenant_default_model_by_type = _get_tenant_default_model_by_type
|
||||
tenant_model_service_mod.get_model_config_from_provider_instance = _get_model_config_from_provider_instance
|
||||
monkeypatch.setitem(sys.modules, "api.db.joint_services.tenant_model_service", tenant_model_service_mod)
|
||||
|
||||
module_name = "test_dify_retrieval_routes_unit_module"
|
||||
|
||||
@@ -124,7 +124,7 @@ def test_openai_compatible_nonstream_shape(rest_client, create_chat):
|
||||
assert res.status_code == 200
|
||||
payload = res.json()
|
||||
|
||||
assert payload["object"] == "chat.completion", payload
|
||||
assert payload.get("object") == "chat.completion", payload
|
||||
assert isinstance(payload["choices"], list) and payload["choices"], payload
|
||||
first_choice = payload["choices"][0]
|
||||
assert first_choice.get("finish_reason") == "stop", payload
|
||||
|
||||
@@ -620,10 +620,6 @@ def _load_user_app(monkeypatch):
|
||||
api_utils_mod.validate_request = _validate_request
|
||||
monkeypatch.setitem(sys.modules, "api.utils.api_utils", api_utils_mod)
|
||||
|
||||
tenant_utils_mod = ModuleType("api.utils.tenant_utils")
|
||||
tenant_utils_mod.ensure_tenant_model_id_for_params = lambda _tenant_id, params: params
|
||||
monkeypatch.setitem(sys.modules, "api.utils.tenant_utils", tenant_utils_mod)
|
||||
|
||||
crypt_mod = ModuleType("api.utils.crypt")
|
||||
crypt_mod.decrypt = lambda value: value
|
||||
monkeypatch.setitem(sys.modules, "api.utils.crypt", crypt_mod)
|
||||
@@ -1505,13 +1501,20 @@ def _load_chat_routes_unit_module(monkeypatch):
|
||||
})
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.knowledgebase_service", kb_service_mod)
|
||||
|
||||
tenant_llm_service_mod = ModuleType("api.db.services.tenant_llm_service")
|
||||
tenant_llm_service_mod.TenantLLMService = type('TenantLLMService', (), {
|
||||
'split_model_name_and_factory': staticmethod(lambda model: (model.split('@', 1)[0], model.split('@', 1)[1] if '@' in model else None)),
|
||||
'query': staticmethod(lambda **_kwargs: [SimpleNamespace(id='llm-1')]),
|
||||
'get_api_key': staticmethod(lambda *_args, **_kwargs: SimpleNamespace(id=1)),
|
||||
})
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.tenant_llm_service", tenant_llm_service_mod)
|
||||
tenant_model_provider_mod = ModuleType("api.db.joint_services.tenant_model_service")
|
||||
tenant_model_provider_mod.get_model_config_from_provider_instance = lambda *_args, **_kwargs: {}
|
||||
tenant_model_provider_mod.get_tenant_default_model_by_type = lambda *_args, **_kwargs: {}
|
||||
def _split_model_name(model_name):
|
||||
parts = model_name.split("@")
|
||||
if len(parts) == 1:
|
||||
return parts[0], "", ""
|
||||
elif len(parts) == 2:
|
||||
return parts[0], "default", parts[1]
|
||||
else:
|
||||
return parts[0], parts[1], parts[2]
|
||||
tenant_model_provider_mod.split_model_name = staticmethod(_split_model_name)
|
||||
tenant_model_provider_mod.get_api_key = lambda *_args, **_kwargs: SimpleNamespace(id=1)
|
||||
monkeypatch.setitem(sys.modules, "api.db.joint_services.tenant_model_service", tenant_model_provider_mod)
|
||||
|
||||
llm_service_mod = ModuleType("api.db.services.llm_service")
|
||||
llm_service_mod.LLMBundle = lambda *_args, **_kwargs: None
|
||||
@@ -1521,11 +1524,6 @@ def _load_chat_routes_unit_module(monkeypatch):
|
||||
search_service_mod.SearchService = SimpleNamespace()
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.search_service", search_service_mod)
|
||||
|
||||
tenant_model_service_mod = ModuleType("api.db.joint_services.tenant_model_service")
|
||||
tenant_model_service_mod.get_model_config_by_type_and_name = lambda *_args, **_kwargs: {}
|
||||
tenant_model_service_mod.get_tenant_default_model_by_type = lambda *_args, **_kwargs: {}
|
||||
monkeypatch.setitem(sys.modules, "api.db.joint_services.tenant_model_service", tenant_model_service_mod)
|
||||
|
||||
user_service_mod = ModuleType("api.db.services.user_service")
|
||||
user_service_mod.UserService = type('UserService', (), {})
|
||||
user_service_mod.TenantService = type('TenantService', (), {
|
||||
@@ -1548,10 +1546,6 @@ def _load_chat_routes_unit_module(monkeypatch):
|
||||
api_utils_mod.get_request_json = lambda: _AwaitableValue({})
|
||||
monkeypatch.setitem(sys.modules, "api.utils.api_utils", api_utils_mod)
|
||||
|
||||
tenant_utils_mod = ModuleType("api.utils.tenant_utils")
|
||||
tenant_utils_mod.ensure_tenant_model_id_for_params = lambda _tenant_id, req: req
|
||||
monkeypatch.setitem(sys.modules, "api.utils.tenant_utils", tenant_utils_mod)
|
||||
|
||||
rag_pkg = ModuleType("rag")
|
||||
rag_pkg.__path__ = [str(repo_root / 'rag')]
|
||||
monkeypatch.setitem(sys.modules, 'rag', rag_pkg)
|
||||
|
||||
+1
-5
@@ -366,7 +366,7 @@ def _load_chat_module(monkeypatch):
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.search_service", search_service_mod)
|
||||
|
||||
tenant_model_service_mod = ModuleType("api.db.joint_services.tenant_model_service")
|
||||
tenant_model_service_mod.get_model_config_by_type_and_name = lambda *_args, **_kwargs: {}
|
||||
tenant_model_service_mod.get_model_config_from_provider_instance = lambda *_args, **_kwargs: {}
|
||||
tenant_model_service_mod.get_tenant_default_model_by_type = lambda *_args, **_kwargs: {}
|
||||
monkeypatch.setitem(sys.modules, "api.db.joint_services.tenant_model_service", tenant_model_service_mod)
|
||||
|
||||
@@ -418,10 +418,6 @@ def _load_chat_module(monkeypatch):
|
||||
api_utils_mod.validate_request = lambda *_args, **_kwargs: (lambda func: func)
|
||||
monkeypatch.setitem(sys.modules, "api.utils.api_utils", api_utils_mod)
|
||||
|
||||
tenant_utils_mod = ModuleType("api.utils.tenant_utils")
|
||||
tenant_utils_mod.ensure_tenant_model_id_for_params = lambda _tenant_id, req: req
|
||||
monkeypatch.setitem(sys.modules, "api.utils.tenant_utils", tenant_utils_mod)
|
||||
|
||||
rag_pkg = ModuleType("rag")
|
||||
rag_pkg.__path__ = [str(repo_root / "rag")]
|
||||
monkeypatch.setitem(sys.modules, "rag", rag_pkg)
|
||||
|
||||
+2
-2
@@ -238,7 +238,7 @@ def _load_dify_retrieval_module(monkeypatch):
|
||||
raise LookupError(f"Tenant Model with id {tenant_model_id} not authorized")
|
||||
return _MockModelConfig2(mock_tenant_id, "model-1").to_dict()
|
||||
|
||||
def _get_model_config_by_type_and_name(tenant_id: str, model_type: str, model_name: str):
|
||||
def _get_model_config_from_provider_instance(tenant_id: str, model_type: str, model_name: str):
|
||||
if not model_name:
|
||||
raise Exception("Model Name is required")
|
||||
return _MockModelConfig2(tenant_id, model_name).to_dict()
|
||||
@@ -248,7 +248,7 @@ def _load_dify_retrieval_module(monkeypatch):
|
||||
return _MockModelConfig2(tenant_id, "chat-model").to_dict()
|
||||
|
||||
tenant_model_service_mod.get_model_config_by_id = _get_model_config_by_id
|
||||
tenant_model_service_mod.get_model_config_by_type_and_name = _get_model_config_by_type_and_name
|
||||
tenant_model_service_mod.get_model_config_from_provider_instance = _get_model_config_from_provider_instance
|
||||
tenant_model_service_mod.get_tenant_default_model_by_type = _get_tenant_default_model_by_type
|
||||
monkeypatch.setitem(sys.modules, "api.db.joint_services.tenant_model_service", tenant_model_service_mod)
|
||||
|
||||
|
||||
+2
-2
@@ -478,7 +478,7 @@ def _load_doc_module(monkeypatch):
|
||||
raise LookupError(f"Tenant Model with id {tenant_model_id} not authorized")
|
||||
return _MockModelConfig2(mock_tenant_id, "model-1").to_dict()
|
||||
|
||||
def _get_model_config_by_type_and_name(tenant_id: str, model_type: str, model_name: str):
|
||||
def _get_model_config_from_provider_instance(tenant_id: str, model_type: str, model_name: str):
|
||||
if not model_name:
|
||||
raise Exception("Model Name is required")
|
||||
return _MockModelConfig2(tenant_id, model_name).to_dict()
|
||||
@@ -488,7 +488,7 @@ def _load_doc_module(monkeypatch):
|
||||
return _MockModelConfig2(tenant_id, "chat-model").to_dict()
|
||||
|
||||
tenant_model_service_mod.get_model_config_by_id = _get_model_config_by_id
|
||||
tenant_model_service_mod.get_model_config_by_type_and_name = _get_model_config_by_type_and_name
|
||||
tenant_model_service_mod.get_model_config_from_provider_instance = _get_model_config_from_provider_instance
|
||||
tenant_model_service_mod.get_tenant_default_model_by_type = _get_tenant_default_model_by_type
|
||||
monkeypatch.setitem(sys.modules, "api.db.joint_services.tenant_model_service", tenant_model_service_mod)
|
||||
|
||||
|
||||
+2
-7
@@ -481,7 +481,7 @@ def _load_session_module(monkeypatch):
|
||||
raise LookupError(f"Tenant Model with id {tenant_model_id} not authorized")
|
||||
return _MockModelConfig2(mock_tenant_id, "model-1").to_dict()
|
||||
|
||||
def _get_model_config_by_type_and_name(tenant_id: str, model_type: str, model_name: str):
|
||||
def _get_model_config_from_provider_instance(tenant_id: str, model_type: str, model_name: str):
|
||||
if not model_name:
|
||||
raise Exception("Model Name is required")
|
||||
return _MockModelConfig2(tenant_id, model_name, model_type).to_dict()
|
||||
@@ -525,7 +525,7 @@ def _load_session_module(monkeypatch):
|
||||
return _MockModelConfig2(tenant_id, model_name, model_type_val).to_dict()
|
||||
|
||||
tenant_model_service_mod.get_model_config_by_id = _get_model_config_by_id
|
||||
tenant_model_service_mod.get_model_config_by_type_and_name = _get_model_config_by_type_and_name
|
||||
tenant_model_service_mod.get_model_config_from_provider_instance = _get_model_config_from_provider_instance
|
||||
tenant_model_service_mod.get_tenant_default_model_by_type = _get_tenant_default_model_by_type
|
||||
monkeypatch.setitem(sys.modules, "api.db.joint_services.tenant_model_service", tenant_model_service_mod)
|
||||
|
||||
@@ -2095,7 +2095,6 @@ def _load_chat_api_module(monkeypatch):
|
||||
monkeypatch.setitem(sys.modules, "api.db.joint_services", joint_pkg)
|
||||
|
||||
tenant_model_svc = ModuleType("api.db.joint_services.tenant_model_service")
|
||||
tenant_model_svc.get_model_config_by_type_and_name = lambda *_a, **_k: {}
|
||||
tenant_model_svc.get_tenant_default_model_by_type = lambda *_a, **_k: {}
|
||||
monkeypatch.setitem(sys.modules, "api.db.joint_services.tenant_model_service", tenant_model_svc)
|
||||
|
||||
@@ -2185,10 +2184,6 @@ def _load_chat_api_module(monkeypatch):
|
||||
api_utils_mod.validate_request = lambda *_a, **_k: (lambda func: func)
|
||||
monkeypatch.setitem(sys.modules, "api.utils.api_utils", api_utils_mod)
|
||||
|
||||
tenant_utils_mod = ModuleType("api.utils.tenant_utils")
|
||||
tenant_utils_mod.ensure_tenant_model_id_for_params = lambda _tenant_id, req: req
|
||||
monkeypatch.setitem(sys.modules, "api.utils.tenant_utils", tenant_utils_mod)
|
||||
|
||||
rag_gen_mod = ModuleType("rag.prompts.generator")
|
||||
rag_gen_mod.chunks_format = lambda chunks: chunks
|
||||
monkeypatch.setitem(sys.modules, "rag.prompts.generator", rag_gen_mod)
|
||||
|
||||
@@ -184,7 +184,7 @@ class TestDatasetCreate:
|
||||
"name, embedding_model",
|
||||
[
|
||||
("BAAI/bge-small-en-v1.5@Builtin", "BAAI/bge-small-en-v1.5@Builtin"),
|
||||
("embedding-3@ZHIPU-AI", "embedding-3@ZHIPU-AI"),
|
||||
("embedding-3@ZHIPU-AI", "embedding-3@CI@ZHIPU-AI"),
|
||||
],
|
||||
ids=["builtin_baai", "tenant_zhipu"],
|
||||
)
|
||||
@@ -197,7 +197,7 @@ class TestDatasetCreate:
|
||||
@pytest.mark.parametrize(
|
||||
"name, embedding_model",
|
||||
[
|
||||
("unknown_llm_name", "unknown@ZHIPU-AI"),
|
||||
("unknown_llm_name", "unknown@CI@ZHIPU-AI"),
|
||||
("unknown_llm_factory", "embedding-3@unknown"),
|
||||
("tenant_no_auth_default_tenant_llm", "text-embedding-v3@Tongyi-Qianwen"),
|
||||
("tenant_no_auth", "text-embedding-3-small@OpenAI"),
|
||||
@@ -208,10 +208,7 @@ class TestDatasetCreate:
|
||||
payload = {"name": name, "embedding_model": embedding_model}
|
||||
with pytest.raises(Exception) as exception_info:
|
||||
client.create_dataset(**payload)
|
||||
if "tenant_no_auth" in name:
|
||||
assert str(exception_info.value) == f"Unauthorized model: <{embedding_model}>", str(exception_info.value)
|
||||
else:
|
||||
assert str(exception_info.value) == f"Unsupported model: <{embedding_model}>", str(exception_info.value)
|
||||
assert "not found" in str(exception_info.value), str(exception_info.value)
|
||||
|
||||
@pytest.mark.p2
|
||||
@pytest.mark.parametrize(
|
||||
@@ -240,13 +237,13 @@ class TestDatasetCreate:
|
||||
def test_embedding_model_unset(self, client):
|
||||
payload = {"name": "embedding_model_unset"}
|
||||
dataset = client.create_dataset(**payload)
|
||||
assert dataset.embedding_model == "BAAI/bge-small-en-v1.5@Builtin", str(dataset)
|
||||
assert dataset.embedding_model == "BAAI/bge-small-en-v1.5@Local@Builtin", str(dataset)
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_embedding_model_none(self, client):
|
||||
payload = {"name": "embedding_model_none", "embedding_model": None}
|
||||
dataset = client.create_dataset(**payload)
|
||||
assert dataset.embedding_model == "BAAI/bge-small-en-v1.5@Builtin", str(dataset)
|
||||
assert dataset.embedding_model == "BAAI/bge-small-en-v1.5@Local@Builtin", str(dataset)
|
||||
|
||||
@pytest.mark.p2
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -170,7 +170,7 @@ class TestDatasetUpdate:
|
||||
"embedding_model",
|
||||
[
|
||||
"BAAI/bge-small-en-v1.5@Builtin",
|
||||
"embedding-3@ZHIPU-AI",
|
||||
"embedding-3@CI@ZHIPU-AI",
|
||||
],
|
||||
ids=["builtin_baai", "tenant_zhipu"],
|
||||
)
|
||||
@@ -198,10 +198,7 @@ class TestDatasetUpdate:
|
||||
with pytest.raises(Exception) as exception_info:
|
||||
dataset.update({"name": name, "embedding_model": embedding_model})
|
||||
error_msg = str(exception_info.value)
|
||||
if "tenant_no_auth" in name:
|
||||
assert error_msg == f"Unauthorized model: <{embedding_model}>", error_msg
|
||||
else:
|
||||
assert error_msg == f"Unsupported model: <{embedding_model}>", error_msg
|
||||
assert "not found" in error_msg, error_msg
|
||||
|
||||
@pytest.mark.p2
|
||||
@pytest.mark.parametrize(
|
||||
@@ -231,10 +228,10 @@ class TestDatasetUpdate:
|
||||
def test_embedding_model_none(self, client, add_dataset_func):
|
||||
dataset = add_dataset_func
|
||||
dataset.update({"embedding_model": None})
|
||||
assert dataset.embedding_model == "BAAI/bge-small-en-v1.5@Builtin", str(dataset)
|
||||
assert dataset.embedding_model == "BAAI/bge-small-en-v1.5@Local@Builtin", str(dataset)
|
||||
|
||||
retrieved_dataset = client.get_dataset(name=dataset.name)
|
||||
assert retrieved_dataset.embedding_model == "BAAI/bge-small-en-v1.5@Builtin", str(retrieved_dataset)
|
||||
assert retrieved_dataset.embedding_model == "BAAI/bge-small-en-v1.5@Local@Builtin", str(retrieved_dataset)
|
||||
|
||||
@pytest.mark.p2
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -309,7 +309,7 @@ def _load_chunk_module(monkeypatch):
|
||||
|
||||
tenant_model_service_mod = ModuleType("api.db.joint_services.tenant_model_service")
|
||||
tenant_model_service_mod.get_model_config_by_id = lambda *_args, **_kwargs: {"llm_name": "embed", "model_type": "embedding"}
|
||||
tenant_model_service_mod.get_model_config_by_type_and_name = lambda *_args, **_kwargs: {"llm_name": "embed", "model_type": "embedding"}
|
||||
tenant_model_service_mod.get_model_config_from_provider_instance = lambda *_args, **_kwargs: {"llm_name": "embed", "model_type": "embedding"}
|
||||
tenant_model_service_mod.get_tenant_default_model_by_type = lambda *_args, **_kwargs: {"llm_name": "chat", "model_type": "chat"}
|
||||
monkeypatch.setitem(sys.modules, "api.db.joint_services.tenant_model_service", tenant_model_service_mod)
|
||||
|
||||
|
||||
@@ -344,10 +344,6 @@ def _load_user_app(monkeypatch):
|
||||
api_utils_mod.validate_request = _validate_request
|
||||
monkeypatch.setitem(sys.modules, "api.utils.api_utils", api_utils_mod)
|
||||
|
||||
tenant_utils_mod = ModuleType("api.utils.tenant_utils")
|
||||
tenant_utils_mod.ensure_tenant_model_id_for_params = lambda _tenant_id, params: params
|
||||
monkeypatch.setitem(sys.modules, "api.utils.tenant_utils", tenant_utils_mod)
|
||||
|
||||
crypt_mod = ModuleType("api.utils.crypt")
|
||||
crypt_mod.decrypt = lambda value: value
|
||||
monkeypatch.setitem(sys.modules, "api.utils.crypt", crypt_mod)
|
||||
|
||||
@@ -120,9 +120,8 @@ def _load_dify_retrieval(monkeypatch, *, kb, accessible, request_body, chunks=No
|
||||
_stub(
|
||||
monkeypatch,
|
||||
"api.db.joint_services.tenant_model_service",
|
||||
get_model_config_by_id=lambda *_a, **_k: {},
|
||||
get_model_config_by_type_and_name=lambda *_a, **_k: {},
|
||||
get_tenant_default_model_by_type=lambda *_a, **_k: {},
|
||||
get_model_config_from_provider_instance=lambda *_a, **_k: {},
|
||||
)
|
||||
|
||||
_stub(
|
||||
|
||||
@@ -203,7 +203,7 @@ def test_async_ask_final_event_carries_decorated_answer(monkeypatch):
|
||||
dialog_service.KnowledgebaseService, "get_by_ids", lambda _ids: [_KB]
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
dialog_service, "get_model_config_by_type_and_name",
|
||||
dialog_service, "get_model_config_from_provider_instance",
|
||||
lambda _tid, _type, _name: _LLM_CONFIG,
|
||||
)
|
||||
monkeypatch.setattr(dialog_service, "LLMBundle", lambda _tid, _cfg: chat_mdl)
|
||||
@@ -257,7 +257,7 @@ def test_async_ask_delta_events_carry_incremental_text_only(monkeypatch):
|
||||
dialog_service.KnowledgebaseService, "get_by_ids", lambda _ids: [_KB]
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
dialog_service, "get_model_config_by_type_and_name",
|
||||
dialog_service, "get_model_config_from_provider_instance",
|
||||
lambda _tid, _type, _name: _LLM_CONFIG,
|
||||
)
|
||||
monkeypatch.setattr(dialog_service, "LLMBundle", lambda _tid, _cfg: chat_mdl)
|
||||
@@ -345,10 +345,11 @@ def test_async_chat_final_event_carries_decorated_answer(monkeypatch):
|
||||
|
||||
# Stub out the heavy service/model calls
|
||||
monkeypatch.setattr(
|
||||
dialog_service.TenantLLMService, "llm_id2llm_type", lambda _llm_id: "chat"
|
||||
dialog_service, "get_model_type_by_name",
|
||||
lambda _tid, _llm_id: ["chat"]
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
dialog_service.TenantLLMService, "get_model_config",
|
||||
dialog_service, "get_model_config_from_provider_instance",
|
||||
lambda _tid, _type, _llm_id: _LLM_CONFIG,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
@@ -406,10 +407,11 @@ def test_async_chat_langfuse_uses_start_observation(monkeypatch):
|
||||
retriever = _StubRetriever()
|
||||
|
||||
monkeypatch.setattr(
|
||||
dialog_service.TenantLLMService, "llm_id2llm_type", lambda _llm_id: "chat"
|
||||
dialog_service, "get_model_type_by_name",
|
||||
lambda _tid, _llm_id: ["chat"]
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
dialog_service.TenantLLMService, "get_model_config",
|
||||
dialog_service, "get_model_config_from_provider_instance",
|
||||
lambda _tid, _type, _llm_id: _LLM_CONFIG,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
@@ -475,10 +477,11 @@ def test_async_chat_continues_when_langfuse_observation_start_fails(monkeypatch)
|
||||
retriever = _StubRetriever()
|
||||
|
||||
monkeypatch.setattr(
|
||||
dialog_service.TenantLLMService, "llm_id2llm_type", lambda _llm_id: "chat"
|
||||
dialog_service, "get_model_type_by_name",
|
||||
lambda _tid, _llm_id: ["chat"]
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
dialog_service.TenantLLMService, "get_model_config",
|
||||
dialog_service, "get_model_config_from_provider_instance",
|
||||
lambda _tid, _type, _llm_id: _LLM_CONFIG,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
|
||||
@@ -282,11 +282,14 @@ def test_async_chat_uses_all_docs_when_no_doc_ids_selected(monkeypatch):
|
||||
)
|
||||
|
||||
monkeypatch.setattr(dialog_service.settings, "retriever", retriever, raising=False)
|
||||
monkeypatch.setattr(dialog_service.TenantLLMService, "llm_id2llm_type", lambda _llm_id: "chat")
|
||||
monkeypatch.setattr(
|
||||
dialog_service.TenantLLMService,
|
||||
"get_model_config",
|
||||
lambda *_args, **_kwargs: {"llm_factory": "unit", "max_tokens": 4096},
|
||||
dialog_service, "get_model_type_by_name",
|
||||
lambda _tid, _llm_id: ["chat"]
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
dialog_service,
|
||||
"get_model_config_from_provider_instance",
|
||||
lambda *_args, **_kwargs: {"llm_factory": "unit", "max_tokens": 4096, "model_type": "chat"},
|
||||
)
|
||||
monkeypatch.setattr(dialog_service.TenantLangfuseService, "filter_by_tenant", lambda **_kwargs: None)
|
||||
monkeypatch.setattr(
|
||||
|
||||
@@ -312,7 +312,7 @@ def create_patch_embedding_model(vectors=None, vector_size=128):
|
||||
mock_model.__exit__ = MagicMock(return_value=False)
|
||||
|
||||
return patch(
|
||||
"rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name",
|
||||
"rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance",
|
||||
return_value=MagicMock(),
|
||||
), patch(
|
||||
"rag.svr.task_executor_refactor.task_handler.LLMBundle",
|
||||
|
||||
@@ -56,7 +56,7 @@ class TestExtractKeywords:
|
||||
{"content_with_weight": "This is test content two"},
|
||||
]
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_by_type_and_name") as mock_config:
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_from_provider_instance") as mock_config:
|
||||
mock_config.return_value = MagicMock()
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.LLMBundle") as mock_llm:
|
||||
@@ -84,7 +84,7 @@ class TestExtractKeywords:
|
||||
ctx.has_canceled_func = MagicMock(return_value=True)
|
||||
docs = [{"content_with_weight": "This is test content"}]
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_by_type_and_name") as mock_config:
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_from_provider_instance") as mock_config:
|
||||
mock_config.return_value = MagicMock()
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.LLMBundle") as mock_llm:
|
||||
@@ -106,7 +106,7 @@ class TestExtractKeywords:
|
||||
ctx = self._create_mock_context()
|
||||
docs = []
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_by_type_and_name") as mock_config:
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_from_provider_instance") as mock_config:
|
||||
mock_config.return_value = MagicMock()
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.LLMBundle") as mock_llm:
|
||||
@@ -146,7 +146,7 @@ class TestGenerateQuestions:
|
||||
{"content_with_weight": "This is test content one"},
|
||||
]
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_by_type_and_name") as mock_config:
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_from_provider_instance") as mock_config:
|
||||
mock_config.return_value = MagicMock()
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.LLMBundle") as mock_llm:
|
||||
@@ -174,7 +174,7 @@ class TestGenerateQuestions:
|
||||
ctx.has_canceled_func = MagicMock(return_value=True)
|
||||
docs = [{"content_with_weight": "This is test content"}]
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_by_type_and_name") as mock_config:
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_from_provider_instance") as mock_config:
|
||||
mock_config.return_value = MagicMock()
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.LLMBundle") as mock_llm:
|
||||
@@ -223,7 +223,7 @@ class TestGenerateMetadata:
|
||||
{"content_with_weight": "This is test content", "metadata_obj": {"category": "test"}},
|
||||
]
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_by_type_and_name") as mock_config:
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_from_provider_instance") as mock_config:
|
||||
mock_config.return_value = MagicMock()
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.LLMBundle") as mock_llm:
|
||||
@@ -256,7 +256,7 @@ class TestGenerateMetadata:
|
||||
{"content_with_weight": "This is test content", "metadata_obj": {"category": "test"}},
|
||||
]
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_by_type_and_name") as mock_config:
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_from_provider_instance") as mock_config:
|
||||
mock_config.return_value = MagicMock()
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.LLMBundle") as mock_llm:
|
||||
@@ -307,7 +307,7 @@ class TestApplyTags:
|
||||
{"content_with_weight": "This is test content"},
|
||||
]
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_by_type_and_name") as mock_config:
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_from_provider_instance") as mock_config:
|
||||
mock_config.return_value = MagicMock()
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.LLMBundle") as mock_llm:
|
||||
@@ -337,7 +337,7 @@ class TestApplyTags:
|
||||
{"content_with_weight": "This is test content"},
|
||||
]
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_by_type_and_name") as mock_config:
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.get_model_config_from_provider_instance") as mock_config:
|
||||
mock_config.return_value = MagicMock()
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.chunk_post_processor.LLMBundle") as mock_llm:
|
||||
|
||||
@@ -247,7 +247,7 @@ class TestTaskHandlerBuildToc:
|
||||
coro.close()
|
||||
return []
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_cfg:
|
||||
with patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_cfg:
|
||||
mock_cfg.return_value = MagicMock()
|
||||
with patch("rag.svr.task_executor_refactor.task_handler.LLMBundle") as mock_bundle:
|
||||
mock_msg = MagicMock()
|
||||
@@ -271,7 +271,7 @@ class TestTaskHandlerBuildToc:
|
||||
coro.close()
|
||||
return toc_result
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_cfg:
|
||||
with patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_cfg:
|
||||
mock_cfg.return_value = MagicMock()
|
||||
with patch("rag.svr.task_executor_refactor.task_handler.LLMBundle") as mock_bundle:
|
||||
mock_msg = MagicMock()
|
||||
|
||||
@@ -132,7 +132,7 @@ class TestStandardChunkingPipelineIntegration:
|
||||
with patch_get_storage_binary(), \
|
||||
patch_task_handler_settings(mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.chunk_service.settings", mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.LLMBundle") as mock_bundle, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_tenant_default_model_by_type") as mock_get_default, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.File2DocumentService") as mock_file_service, \
|
||||
@@ -177,7 +177,7 @@ class TestStandardChunkingPipelineIntegration:
|
||||
with patch_get_storage_binary(), \
|
||||
patch_task_handler_settings(mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.chunk_service.settings", mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.LLMBundle") as mock_bundle, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_tenant_default_model_by_type") as mock_get_default, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.File2DocumentService") as mock_file_service, \
|
||||
@@ -223,7 +223,7 @@ class TestStandardChunkingPipelineIntegration:
|
||||
with patch_get_storage_binary(), \
|
||||
patch_task_handler_settings(mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.chunk_service.settings", mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.LLMBundle") as mock_bundle, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_tenant_default_model_by_type") as mock_get_default, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.File2DocumentService") as mock_file_service, \
|
||||
@@ -271,7 +271,7 @@ class TestStandardChunkingPipelineIntegration:
|
||||
with patch_get_storage_binary(), \
|
||||
patch_task_handler_settings(mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.chunk_service.settings", mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.LLMBundle") as mock_bundle, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_tenant_default_model_by_type") as mock_get_default, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.File2DocumentService") as mock_file_service, \
|
||||
@@ -320,7 +320,7 @@ class TestStandardChunkingPipelineIntegration:
|
||||
with patch_get_storage_binary(), \
|
||||
patch_task_handler_settings(mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.chunk_service.settings", mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.LLMBundle") as mock_bundle, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_tenant_default_model_by_type") as mock_get_default, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.File2DocumentService") as mock_file_service, \
|
||||
@@ -457,7 +457,7 @@ class TestTaskCancellationCleanupIntegration:
|
||||
with patch_task_handler_settings(mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.search.index_name") as mock_index_name, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.thread_pool_exec") as mock_thread_exec, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.LLMBundle") as mock_bundle, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_tenant_default_model_by_type") as mock_get_default:
|
||||
|
||||
@@ -514,7 +514,7 @@ class TestRaptorPipelineIntegration:
|
||||
|
||||
with patch_task_handler_settings(mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.chunk_service.settings", mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.LLMBundle") as mock_bundle, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_tenant_default_model_by_type") as mock_get_default, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.search.index_name") as mock_index_name, \
|
||||
@@ -559,7 +559,7 @@ class TestRaptorPipelineIntegration:
|
||||
|
||||
with patch_task_handler_settings(mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.chunk_service.settings", mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.LLMBundle") as mock_bundle, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_tenant_default_model_by_type") as mock_get_default, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.search.index_name") as mock_index_name, \
|
||||
@@ -625,7 +625,7 @@ class TestEmbeddingModelBindingFailureIntegration:
|
||||
task_dict = self._create_standard_task_dict()
|
||||
ctx = create_task_context(task_dict)
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_get_config, \
|
||||
with patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_tenant_default_model_by_type") as mock_get_default:
|
||||
|
||||
mock_get_config.side_effect = Exception("Model not found")
|
||||
@@ -642,7 +642,7 @@ class TestEmbeddingModelBindingFailureIntegration:
|
||||
task_dict = self._create_standard_task_dict()
|
||||
ctx = create_task_context(task_dict)
|
||||
|
||||
with patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_get_config, \
|
||||
with patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_tenant_default_model_by_type") as mock_get_default:
|
||||
|
||||
mock_get_config.side_effect = Exception("Model not found")
|
||||
@@ -755,7 +755,7 @@ class TestTocAsyncFlowIntegration:
|
||||
with patch_get_storage_binary(), \
|
||||
patch_task_handler_settings(mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.chunk_service.settings", mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.LLMBundle") as mock_bundle, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_tenant_default_model_by_type") as mock_get_default, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.File2DocumentService") as mock_file_service, \
|
||||
@@ -821,7 +821,7 @@ class TestTocAsyncFlowIntegration:
|
||||
with patch_get_storage_binary(), \
|
||||
patch_task_handler_settings(mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.chunk_service.settings", mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.LLMBundle") as mock_bundle, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_tenant_default_model_by_type") as mock_get_default, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.File2DocumentService") as mock_file_service, \
|
||||
@@ -910,7 +910,7 @@ class TestRecordingContextDataFlowAssertions:
|
||||
with patch_get_storage_binary(), \
|
||||
patch_task_handler_settings(mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.chunk_service.settings", mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.LLMBundle") as mock_bundle, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_tenant_default_model_by_type") as mock_get_default, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.File2DocumentService") as mock_file_service, \
|
||||
@@ -956,7 +956,7 @@ class TestRecordingContextDataFlowAssertions:
|
||||
with patch_get_storage_binary(), \
|
||||
patch_task_handler_settings(mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.chunk_service.settings", mock_settings), \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_by_type_and_name") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_model_config_from_provider_instance") as mock_get_config, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.LLMBundle") as mock_bundle, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.get_tenant_default_model_by_type") as mock_get_default, \
|
||||
patch("rag.svr.task_executor_refactor.task_handler.File2DocumentService") as mock_file_service, \
|
||||
|
||||
@@ -331,8 +331,8 @@ class TenantModelProviderStage(MigrationStage):
|
||||
for tenant_id, llm_factory in batch:
|
||||
record_id = self.generate_uuid()
|
||||
values.append(f"('{record_id}', '{llm_factory}', '{tenant_id}', "
|
||||
f"{current_ts}, FROM_UNIXTIME({current_ts}), "
|
||||
f"{current_ts}, FROM_UNIXTIME({current_ts}))")
|
||||
f"{current_ts * 1000}, FROM_UNIXTIME({current_ts}), "
|
||||
f"{current_ts * 1000}, FROM_UNIXTIME({current_ts}))")
|
||||
|
||||
insert_sql = f"""
|
||||
INSERT INTO tenant_model_provider
|
||||
@@ -478,7 +478,7 @@ class TenantModelInstanceStage(MigrationStage):
|
||||
if self.dry_run:
|
||||
logger.info(f"[DRY RUN] Would insert {len(records)} records")
|
||||
for tenant_id, llm_factory, api_key, status, provider_id in records[:5]:
|
||||
logger.info(f" instance_name={llm_factory}, provider_id={provider_id}, api_key=***")
|
||||
logger.info(f" instance_name=default, provider_id={provider_id}, api_key=***")
|
||||
if len(records) > 5:
|
||||
logger.info(f" ... and {len(records) - 5} more records")
|
||||
return len(records), self.target_tables
|
||||
@@ -490,13 +490,13 @@ class TenantModelInstanceStage(MigrationStage):
|
||||
values = []
|
||||
for tenant_id, llm_factory, api_key, status, provider_id in batch:
|
||||
record_id = self.generate_uuid()
|
||||
instance_name = llm_factory.replace("'", "''") if llm_factory else ""
|
||||
instance_name = "default"
|
||||
api_key_escaped = api_key.replace("'", "''") if api_key else ""
|
||||
status_val = status if status else "active"
|
||||
status_val = "active" if status in ["1", "active", "enable"] else "inactive"
|
||||
values.append(f"('{record_id}', '{instance_name}', '{provider_id}', "
|
||||
f"'{api_key_escaped}', '{status_val}', "
|
||||
f"{current_ts}, FROM_UNIXTIME({current_ts}), "
|
||||
f"{current_ts}, FROM_UNIXTIME({current_ts}))")
|
||||
f"{current_ts * 1000}, FROM_UNIXTIME({current_ts}), "
|
||||
f"{current_ts * 1000}, FROM_UNIXTIME({current_ts}))")
|
||||
|
||||
insert_sql = f"""
|
||||
INSERT INTO tenant_model_instance
|
||||
@@ -518,6 +518,7 @@ class TenantModelInstanceStage(MigrationStage):
|
||||
provider_id VARCHAR(32) NOT NULL,
|
||||
api_key VARCHAR(512) NOT NULL,
|
||||
status VARCHAR(32) DEFAULT 'active',
|
||||
extra VARCHAR(512) DEFAULT '{}',
|
||||
create_time BIGINT,
|
||||
create_date DATETIME,
|
||||
update_time BIGINT,
|
||||
@@ -676,11 +677,11 @@ class TenantModelStage(MigrationStage):
|
||||
record_id = self.generate_uuid()
|
||||
model_name_escaped = llm_name.replace("'", "''") if llm_name else ""
|
||||
model_type_escaped = model_type.replace("'", "''") if model_type else ""
|
||||
status_val = status if status else "active"
|
||||
status_val = "active" if status in ["1", "active", "enable"] else "inactive"
|
||||
values.append(f"('{record_id}', '{model_name_escaped}', '{provider_id}', "
|
||||
f"'{instance_id}', '{model_type_escaped}', '{status_val}', "
|
||||
f"{current_ts}, FROM_UNIXTIME({current_ts}), "
|
||||
f"{current_ts}, FROM_UNIXTIME({current_ts}))")
|
||||
f"{current_ts * 1000}, FROM_UNIXTIME({current_ts}), "
|
||||
f"{current_ts * 1000}, FROM_UNIXTIME({current_ts}))")
|
||||
|
||||
insert_sql = f"""
|
||||
INSERT INTO tenant_model
|
||||
@@ -704,6 +705,7 @@ class TenantModelStage(MigrationStage):
|
||||
instance_id VARCHAR(32) NOT NULL,
|
||||
model_type VARCHAR(32) NOT NULL,
|
||||
status VARCHAR(32) DEFAULT 'active',
|
||||
extra VARCHAR(1024) DEFAULT '{}',
|
||||
create_time BIGINT,
|
||||
create_date DATETIME,
|
||||
update_time BIGINT,
|
||||
|
||||
@@ -38,9 +38,42 @@ For translation tasks, add keys **only to the explicitly requested language file
|
||||
When refactoring or extracting components, **verify layout behavior after each structural change** (especially `flex-1`, conditional rendering, or flex direction changes). Check that existing buttons, alignment, and responsive behavior remain intact. After extraction, verify: (1) all original props and behavior are preserved, (2) layout in parent contexts is identical, and (3) no syntax or type errors were introduced.
|
||||
|
||||
### State Management and Data Fetching
|
||||
|
||||
#### Query Key Factory (Mandatory)
|
||||
**Never write raw `queryKey` arrays inline.** Always use a query key factory object that returns `as const` tuples. Raw arrays duplicated across `useQuery` and `invalidateQueries` are brittle, unreadable, and cause stale-cache bugs when key structures drift.
|
||||
|
||||
```ts
|
||||
// ❌ Bad — raw array, hard to match with useQuery
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.AddedProviders, params.provider_name, params.instance_name, 'models'],
|
||||
});
|
||||
|
||||
// ✅ Good — factory reference, self-documenting
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: LlmKeys.instanceModels(params.provider_name, params.instance_name),
|
||||
});
|
||||
```
|
||||
|
||||
- Place the factory in the same file as the hooks, named `{Domain}Keys` (e.g., `LlmKeys`, `DatasetKeys`).
|
||||
- Every `useQuery` and every `invalidateQueries` must reference the same factory function.
|
||||
- Use `as const` on each factory return value for type-safe readonly tuples.
|
||||
|
||||
#### Cache Debugging
|
||||
For React Query / cache invalidation bugs, **carefully compare query keys across all consuming components and mutation hooks**. Mismatched keys (e.g., with/without `refreshCount`) are a common root cause of stale data or duplicate requests.
|
||||
- Systematically: (1) list every component/hook that calls `useQuery` for this data, (2) compare their query keys character-for-character, (3) check every mutation's `onSuccess` for cache invalidation, and (4) verify no parent re-renders are remounting the observer.
|
||||
|
||||
### Network Request Layering
|
||||
HTTP requests are organized in three layers. **Never import `@/utils/request`, `@/utils/next-request`, or `@/utils/api` directly inside a hook**:
|
||||
1. `src/hooks/use-xx-request.ts(x)` — React Query hooks; only call the service layer.
|
||||
2. `src/services/xx-service.ts` — Register endpoints via `registerNextServer`, all going through `@/utils/next-request`.
|
||||
3. `src/utils/next-request.ts` — The single axios instance; handles token, 401 redirects, and error notifications.
|
||||
|
||||
Interface types are split between two folders:
|
||||
- Response/data shape → `src/interfaces/database/xx.ts`
|
||||
- Request params/body → `src/interfaces/request/xx.ts`
|
||||
|
||||
Model-related endpoints (LLM provider / factory / my LLM, etc.) are consolidated in `src/services/llm-service.ts` rather than scattered across hooks. For GET endpoints, register with `method: 'get'` in the service, and on the call site pass `true` as the second argument to use the native axios config (e.g., `service.listProviders({ params: { available: true } }, true)`).
|
||||
|
||||
### Shared UI Component Lock
|
||||
The folder `src/components/ui/` is the project's **shared UI library** — it contains both official shadcn/ui primitives and project-authored common components built on top of shadcn. Both kinds are intended to be reused across the app and **must not be modified casually**.
|
||||
|
||||
@@ -55,3 +88,11 @@ The folder `src/components/ui/` is the project's **shared UI library** — it co
|
||||
- Extract complex logic into hooks or utils; keep components lean.
|
||||
- Use `PascalCase` for constants and component names.
|
||||
- Avoid duplicating component structures in JSX; favor render props or reusable components.
|
||||
|
||||
### Utility Libraries and Reuse
|
||||
- **Time/date handling**: Use `dayjs` for all date/time formatting, parsing, and manipulation.
|
||||
- **Utility hooks**: Prefer `ahooks` for common reusable hooks (e.g., `useDebounce`, `useSetState`).
|
||||
- **General utilities**: Lodash is available for utility functions when needed.
|
||||
- **Project utilities first**: Before reaching for a third-party library, check if the project already has an existing utility or hook that covers the need.
|
||||
- **Extract and share**: If repeated logic cannot be satisfied by an existing project utility or a third-party library, extract it into an appropriate shared hook (`src/hooks/`) or utility file (`src/utils/`).
|
||||
- **Check for duplicate patterns before adding**: When asked to add logic (validation, existence checks, API calls, etc.), first search for existing hooks/functions that do the same or similar thing — especially in the same file or sibling hooks. If a hook already does X, call it instead of re-implementing X inline. This applies to mutations calling mutations, utility wrappers, and boilerplate around API calls.
|
||||
@@ -1,13 +1,13 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useSelectLlmOptionsByModelType } from '@/hooks/use-llm-request';
|
||||
import { useFetchAllAddedModels } from '@/hooks/use-llm-request';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { camelCase } from 'lodash';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { MinerUOptionsFormField } from './mineru-options-form-field';
|
||||
import { SelectWithSearch } from './originui/select-with-search';
|
||||
import { buildModelTree } from './model-tree-select';
|
||||
import { PaddleOCROptionsFormField } from './paddleocr-options-form-field';
|
||||
import { TreeSelect, TreeSelectNode } from './tree-select';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
@@ -44,9 +44,9 @@ export function LayoutRecognizeFormField({
|
||||
const form = useFormContext();
|
||||
|
||||
const { t } = useTranslate('knowledgeDetails');
|
||||
const allOptions = useSelectLlmOptionsByModelType();
|
||||
const { data: allAddedModels } = useFetchAllAddedModels();
|
||||
|
||||
const options = useMemo(() => {
|
||||
const treeData = useMemo(() => {
|
||||
const list = optionsWithoutLLM
|
||||
? optionsWithoutLLM
|
||||
: [
|
||||
@@ -60,28 +60,28 @@ export function LayoutRecognizeFormField({
|
||||
value: x,
|
||||
}));
|
||||
|
||||
const image2TextList = [
|
||||
...allOptions[LlmModelType.Image2text],
|
||||
...allOptions[LlmModelType.Ocr],
|
||||
].map((x) => {
|
||||
return {
|
||||
...x,
|
||||
options: x.options.map((y) => {
|
||||
return {
|
||||
...y,
|
||||
label: (
|
||||
<div className="flex justify-between items-center gap-2">
|
||||
{y.label}
|
||||
<span className="text-red-500 text-sm">Experimental</span>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
const prependNodes: TreeSelectNode[] = list.map((x) => ({
|
||||
id: x.value,
|
||||
title: x.label,
|
||||
}));
|
||||
|
||||
return [...list, ...image2TextList];
|
||||
}, [allOptions, optionsWithoutLLM, t]);
|
||||
const modelTree = buildModelTree(
|
||||
allAddedModels,
|
||||
['image2text', 'ocr'],
|
||||
(node) => (
|
||||
<div className="flex justify-between items-center gap-2 w-full">
|
||||
<span className="flex items-center gap-1.5 truncate">
|
||||
{node.label}
|
||||
</span>
|
||||
<span className="text-state-error text-sm flex-shrink-0">
|
||||
Experimental
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
|
||||
return [...prependNodes, ...modelTree];
|
||||
}, [allAddedModels, optionsWithoutLLM, t]);
|
||||
|
||||
return (
|
||||
<FormField
|
||||
@@ -107,11 +107,17 @@ export function LayoutRecognizeFormField({
|
||||
</FormLabel>
|
||||
<div className={horizontal ? 'w-3/4' : 'w-full'}>
|
||||
<FormControl>
|
||||
<SelectWithSearch
|
||||
<TreeSelect
|
||||
{...field}
|
||||
options={options}
|
||||
data={treeData}
|
||||
testId={testId}
|
||||
></SelectWithSearch>
|
||||
showSearch
|
||||
defaultExpandAll
|
||||
renderSelected={(node) => {
|
||||
if (!node) return null;
|
||||
return node.label ?? node.title;
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
import { getLLMIconName, getLlmNameAndFIdByLlmId } from '@/utils/llm-util';
|
||||
import { parseModelValue } from '@/utils/llm-util';
|
||||
import { memo } from 'react';
|
||||
import { LlmIcon } from '../svg-icon';
|
||||
|
||||
interface IProps {
|
||||
id?: string;
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const LLMLabel = ({ value }: IProps) => {
|
||||
const { llmName, fId } = getLlmNameAndFIdByLlmId(value);
|
||||
export const LLMLabel = ({ value }: IProps) => {
|
||||
const parsed = value ? parseModelValue(value) : null;
|
||||
const modelName = parsed?.model_name;
|
||||
const instanceName = parsed?.model_instance;
|
||||
const iconName = parsed ? parsed.model_provider : '';
|
||||
|
||||
if (!modelName) return null;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1 text-xs text-text-secondary">
|
||||
<LlmIcon name={getLLMIconName(fId, llmName)} width={20} height={20} />
|
||||
<span className="flex-1 truncate"> {llmName}</span>
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
<LlmIcon
|
||||
name={iconName}
|
||||
width={22}
|
||||
height={22}
|
||||
imgClass="size-[22px] flex-shrink-0"
|
||||
/>
|
||||
<span className="font-medium truncate">{modelName}</span>
|
||||
{instanceName && (
|
||||
<span className="text-slate-400 truncate flex-shrink-0">
|
||||
{instanceName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
|
||||
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||
import { forwardRef, memo, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LlmSettingFieldItems } from '../llm-setting-items/next';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
||||
import { Select, SelectTrigger, SelectValue } from '../ui/select';
|
||||
import LLMLabel from './llm-label';
|
||||
|
||||
export interface NextInnerLLMSelectProps {
|
||||
id?: string;
|
||||
@@ -51,8 +51,6 @@ const NextInnerLLMSelect = forwardRef<
|
||||
}
|
||||
}, [filter, ttsModel]);
|
||||
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes(modelTypes);
|
||||
|
||||
return (
|
||||
<Select disabled={disabled} value={value}>
|
||||
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
|
||||
@@ -66,17 +64,13 @@ const NextInnerLLMSelect = forwardRef<
|
||||
data-testid={triggerTestId}
|
||||
>
|
||||
<SelectValue placeholder={t('common.pleaseSelect')}>
|
||||
{
|
||||
modelOptions
|
||||
.flatMap((x) => x.options)
|
||||
.find((x) => x.value === value)?.label
|
||||
}
|
||||
<LLMLabel value={value} />
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent side={'left'}>
|
||||
<LlmSettingFieldItems
|
||||
options={modelOptions}
|
||||
modelTypes={modelTypes}
|
||||
llmOptionTestIdPrefix={optionTestIdPrefix}
|
||||
></LlmSettingFieldItems>
|
||||
</PopoverContent>
|
||||
|
||||
@@ -1,45 +1,24 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
|
||||
import { ModelTreeSelect } from '@/components/model-tree-select';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SelectWithSearch } from '../originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '../ragflow-form';
|
||||
|
||||
export type LLMFormFieldProps = {
|
||||
options?: any[];
|
||||
modelTypes?: string[];
|
||||
name?: string;
|
||||
testId?: string;
|
||||
optionTestIdPrefix?: string;
|
||||
config?: any;
|
||||
};
|
||||
|
||||
export const useModelOptions = () => {
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||
LlmModelType.Chat,
|
||||
LlmModelType.Image2text,
|
||||
]);
|
||||
return {
|
||||
modelOptions,
|
||||
};
|
||||
};
|
||||
|
||||
export function LLMFormField({
|
||||
options,
|
||||
name,
|
||||
testId,
|
||||
optionTestIdPrefix,
|
||||
config,
|
||||
}: LLMFormFieldProps) {
|
||||
export function LLMFormField({ name, config, modelTypes }: LLMFormFieldProps) {
|
||||
const { t } = useTranslation();
|
||||
const { modelOptions } = useModelOptions();
|
||||
|
||||
return (
|
||||
<RAGFlowFormItem name={name || 'llm_id'} label={t('chat.model')}>
|
||||
<SelectWithSearch
|
||||
options={options || modelOptions}
|
||||
testId={testId}
|
||||
optionTestIdPrefix={optionTestIdPrefix}
|
||||
{...config}
|
||||
></SelectWithSearch>
|
||||
<ModelTreeSelect
|
||||
allowClear={config?.allowClear ?? false}
|
||||
modelTypes={modelTypes}
|
||||
/>
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import { useHandleFreedomChange } from './use-watch-change';
|
||||
|
||||
interface LlmSettingFieldItemsProps {
|
||||
prefix?: string;
|
||||
options?: any[];
|
||||
modelTypes?: string[];
|
||||
llmId?: string;
|
||||
llmSelectTestId?: string;
|
||||
llmOptionTestIdPrefix?: string;
|
||||
@@ -69,7 +69,7 @@ export const LlmSettingSchema = {
|
||||
|
||||
export function LlmSettingFieldItems({
|
||||
prefix,
|
||||
options,
|
||||
modelTypes,
|
||||
llmSelectTestId,
|
||||
llmOptionTestIdPrefix,
|
||||
showFields = [
|
||||
@@ -137,7 +137,7 @@ export function LlmSettingFieldItems({
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<LLMFormField
|
||||
options={options}
|
||||
modelTypes={modelTypes}
|
||||
name={llmId ?? getFieldWithPrefix('llm_id')}
|
||||
testId={llmSelectTestId}
|
||||
optionTestIdPrefix={llmOptionTestIdPrefix}
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
import LLMLabel from '@/components/llm-select/llm-label';
|
||||
import { LlmIcon } from '@/components/svg-icon';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { useFetchAllAddedModels } from '@/hooks/use-llm-request';
|
||||
import { IAddedModel } from '@/interfaces/database/llm';
|
||||
import { getRealModelName } from '@/utils/llm-util';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TreeSelect, TreeSelectNode } from './tree-select';
|
||||
|
||||
/** Maps form field names to their supported model types */
|
||||
export const ModelTypeMap: Record<string, string[]> = {
|
||||
llm_id: ['chat', 'image2text'],
|
||||
embd_id: ['embedding'],
|
||||
img2txt_id: ['image2text'],
|
||||
asr_id: ['speech2text'],
|
||||
rerank_id: ['rerank'],
|
||||
tts_id: ['tts'],
|
||||
};
|
||||
|
||||
export function buildModelTree(
|
||||
allModels: IAddedModel[],
|
||||
modelTypes: string[],
|
||||
renderLeafLabel?: (
|
||||
node: TreeSelectNode,
|
||||
model: IAddedModel,
|
||||
) => React.ReactNode,
|
||||
): TreeSelectNode[] {
|
||||
const filtered = allModels.filter((m) =>
|
||||
m.model_type?.some((t) => modelTypes.includes(t)),
|
||||
);
|
||||
|
||||
const seenLeafIds = new Set<string>();
|
||||
const providerMap = new Map<string, Map<string, IAddedModel[]>>();
|
||||
|
||||
for (const model of filtered) {
|
||||
let instances = providerMap.get(model.provider_name);
|
||||
if (!instances) {
|
||||
instances = new Map();
|
||||
providerMap.set(model.provider_name, instances);
|
||||
}
|
||||
let modelList = instances.get(model.instance_name);
|
||||
if (!modelList) {
|
||||
modelList = [];
|
||||
instances.set(model.instance_name, modelList);
|
||||
}
|
||||
modelList.push(model);
|
||||
}
|
||||
|
||||
return Array.from(providerMap.entries()).map(([provider, instances]) => ({
|
||||
id: provider,
|
||||
title: provider,
|
||||
children: Array.from(instances.entries()).map(([instance, models]) => ({
|
||||
id: `${provider}||${instance}`,
|
||||
title: instance,
|
||||
children: models.reduce<TreeSelectNode[]>((acc, m) => {
|
||||
const modelName = getRealModelName(m.name);
|
||||
const id = `${modelName}@${m.instance_name}@${m.provider_name}`;
|
||||
if (seenLeafIds.has(id)) return acc;
|
||||
seenLeafIds.add(id);
|
||||
const leafNode: TreeSelectNode = {
|
||||
id,
|
||||
title: modelName,
|
||||
label: (
|
||||
<span className="flex items-center gap-1.5 truncate">
|
||||
<LlmIcon
|
||||
name={m.provider_name}
|
||||
width={22}
|
||||
height={22}
|
||||
imgClass="size-[22px] flex-shrink-0"
|
||||
/>
|
||||
<span className="truncate">{modelName}</span>
|
||||
</span>
|
||||
),
|
||||
data: {
|
||||
provider_name: m.provider_name,
|
||||
instance_name: m.instance_name,
|
||||
model_name: modelName,
|
||||
},
|
||||
};
|
||||
if (renderLeafLabel) {
|
||||
leafNode.label = renderLeafLabel(leafNode, m);
|
||||
}
|
||||
acc.push(leafNode);
|
||||
return acc;
|
||||
}, []),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
export interface ModelTreeSelectProps {
|
||||
modelTypes?: string[];
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
showSearch?: boolean;
|
||||
allowClear?: boolean;
|
||||
className?: string;
|
||||
renderSelected?: (node: TreeSelectNode | undefined) => React.ReactNode;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
export function ModelTreeSelect({
|
||||
modelTypes = ModelTypeMap.llm_id,
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
placeholder,
|
||||
showSearch = true,
|
||||
allowClear = false,
|
||||
className,
|
||||
renderSelected,
|
||||
testId,
|
||||
}: ModelTreeSelectProps) {
|
||||
const { data: allAddedModels } = useFetchAllAddedModels();
|
||||
|
||||
const treeData = useMemo(
|
||||
() => buildModelTree(allAddedModels, modelTypes),
|
||||
[allAddedModels, modelTypes],
|
||||
);
|
||||
|
||||
const defaultRenderSelected = useCallback(
|
||||
(node: TreeSelectNode | undefined) => {
|
||||
if (!node?.id) return null;
|
||||
return <LLMLabel value={node.id} />;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<TreeSelect
|
||||
data={treeData}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
showSearch={showSearch}
|
||||
allowClear={allowClear}
|
||||
defaultExpandAll
|
||||
className={className}
|
||||
renderSelected={renderSelected ?? defaultRenderSelected}
|
||||
testId={testId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export interface ModelTreeSelectFormFieldProps extends ModelTreeSelectProps {
|
||||
name?: string;
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export function ModelTreeSelectFormField({
|
||||
name = 'llm_id',
|
||||
label,
|
||||
tooltip,
|
||||
...rest
|
||||
}: ModelTreeSelectFormFieldProps) {
|
||||
const form = useFormContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
{label && <FormLabel tooltip={tooltip}>{label}</FormLabel>}
|
||||
<FormControl>
|
||||
<ModelTreeSelect
|
||||
{...rest}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder={rest.placeholder ?? t('common.pleaseSelect')}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { ModelTreeSelect } from '@/components/model-tree-select';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useSelectLlmOptionsByModelType } from '@/hooks/use-llm-request';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { SelectWithSearch } from './originui/select-with-search';
|
||||
import { SliderInputFormField } from './slider-input-form-field';
|
||||
import {
|
||||
FormControl,
|
||||
@@ -26,8 +24,6 @@ const RerankId = 'rerank_id';
|
||||
function RerankFormField() {
|
||||
const form = useFormContext();
|
||||
const { t } = useTranslate('knowledgeDetails');
|
||||
const allOptions = useSelectLlmOptionsByModelType();
|
||||
const options = allOptions[LlmModelType.Rerank];
|
||||
|
||||
return (
|
||||
<FormField
|
||||
@@ -37,12 +33,12 @@ function RerankFormField() {
|
||||
<FormItem>
|
||||
<FormLabel tooltip={t('rerankTip')}>{t('rerankModel')}</FormLabel>
|
||||
<FormControl>
|
||||
<SelectWithSearch
|
||||
<ModelTreeSelect
|
||||
modelTypes={['rerank']}
|
||||
allowClear
|
||||
placeholder={t('rerankPlaceholder')}
|
||||
{...field}
|
||||
options={options}
|
||||
></SelectWithSearch>
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ChevronDown, ChevronRight, Search, X } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface TreeSelectNode {
|
||||
id: string;
|
||||
title: string;
|
||||
label?: React.ReactNode;
|
||||
children?: TreeSelectNode[];
|
||||
disabled?: boolean;
|
||||
data?: Record<string, any>;
|
||||
}
|
||||
|
||||
interface TreeSelectProps {
|
||||
data: TreeSelectNode[];
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
allowClear?: boolean;
|
||||
showSearch?: boolean;
|
||||
className?: string;
|
||||
defaultExpandAll?: boolean;
|
||||
renderSelected?: (node: TreeSelectNode | undefined) => React.ReactNode;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
export function TreeSelect({
|
||||
data,
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
disabled,
|
||||
allowClear,
|
||||
showSearch,
|
||||
className,
|
||||
defaultExpandAll,
|
||||
renderSelected,
|
||||
testId,
|
||||
}: TreeSelectProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!defaultExpandAll) return;
|
||||
const ids = new Set<string>();
|
||||
const walk = (nodes: TreeSelectNode[]) => {
|
||||
for (const node of nodes) {
|
||||
if (node.children?.length) {
|
||||
ids.add(node.id);
|
||||
walk(node.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(data);
|
||||
setExpandedIds(ids);
|
||||
}, [data, defaultExpandAll]);
|
||||
|
||||
const selectedNode = useMemo(() => {
|
||||
const find = (nodes: TreeSelectNode[]): TreeSelectNode | undefined => {
|
||||
for (const node of nodes) {
|
||||
if (node.id === value) return node;
|
||||
if (node.children) {
|
||||
const found = find(node.children);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
};
|
||||
return find(data);
|
||||
}, [data, value]);
|
||||
|
||||
const isLeaf = useCallback(
|
||||
(node: TreeSelectNode) => !node.children?.length,
|
||||
[],
|
||||
);
|
||||
|
||||
const handleToggle = useCallback((id: string) => {
|
||||
setExpandedIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(id)) {
|
||||
next.delete(id);
|
||||
} else {
|
||||
next.add(id);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(node: TreeSelectNode) => {
|
||||
if (node.disabled) return;
|
||||
if (isLeaf(node)) {
|
||||
onChange?.(node.id);
|
||||
setOpen(false);
|
||||
setSearchTerm('');
|
||||
} else {
|
||||
handleToggle(node.id);
|
||||
}
|
||||
},
|
||||
[isLeaf, onChange, handleToggle],
|
||||
);
|
||||
|
||||
const handleClear = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
onChange?.('');
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
const filterTree = useCallback(
|
||||
(nodes: TreeSelectNode[], term: string): TreeSelectNode[] => {
|
||||
if (!term) return nodes;
|
||||
return nodes.reduce<TreeSelectNode[]>((acc, node) => {
|
||||
const titleMatch = node.title
|
||||
.toLowerCase()
|
||||
.includes(term.toLowerCase());
|
||||
const filteredChildren = node.children
|
||||
? filterTree(node.children, term)
|
||||
: undefined;
|
||||
if (titleMatch || filteredChildren?.length) {
|
||||
acc.push({ ...node, children: filteredChildren ?? node.children });
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const filteredData = useMemo(
|
||||
() => filterTree(data, searchTerm),
|
||||
[data, searchTerm, filterTree],
|
||||
);
|
||||
|
||||
const visibleExpandedIds = useMemo(() => {
|
||||
if (!searchTerm) return expandedIds;
|
||||
const ids = new Set<string>();
|
||||
const walk = (nodes: TreeSelectNode[]) => {
|
||||
for (const node of nodes) {
|
||||
if (node.children?.length) {
|
||||
ids.add(node.id);
|
||||
walk(node.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(filteredData);
|
||||
return ids;
|
||||
}, [searchTerm, expandedIds, filteredData]);
|
||||
|
||||
const renderTree = useCallback(
|
||||
(nodes: TreeSelectNode[], level = 0): React.ReactNode => {
|
||||
return nodes.map((node) => {
|
||||
const leaf = isLeaf(node);
|
||||
const expanded = visibleExpandedIds.has(node.id);
|
||||
const selected = value === node.id;
|
||||
|
||||
return (
|
||||
<div key={node.id}>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center rounded-sm cursor-pointer text-sm',
|
||||
'hover:bg-accent transition-colors',
|
||||
!leaf && 'text-text-primary font-medium',
|
||||
selected && 'bg-accent text-accent-foreground',
|
||||
node.disabled && 'opacity-50 pointer-events-none',
|
||||
)}
|
||||
style={{
|
||||
paddingLeft: `${level * 20 + 4}px`,
|
||||
paddingRight: '8px',
|
||||
paddingTop: '6px',
|
||||
paddingBottom: '6px',
|
||||
}}
|
||||
onClick={() => handleSelect(node)}
|
||||
>
|
||||
<span className="w-4 h-4 mr-0.5 flex-shrink-0 flex items-center justify-center">
|
||||
{!leaf && (
|
||||
<>
|
||||
{expanded ? (
|
||||
<ChevronDown className="h-3.5 w-3.5 text-text-secondary" />
|
||||
) : (
|
||||
<ChevronRight className="h-3.5 w-3.5 text-text-secondary" />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
<span className="truncate">{node.label ?? node.title}</span>
|
||||
</div>
|
||||
{!leaf && expanded && node.children && (
|
||||
<div>{renderTree(node.children, level + 1)}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
},
|
||||
[isLeaf, visibleExpandedIds, value, handleSelect],
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild disabled={disabled}>
|
||||
<button
|
||||
type="button"
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
'flex items-center justify-between border border-border-button rounded-md px-3 py-1.5 w-full',
|
||||
'bg-bg-input text-sm',
|
||||
'hover:bg-border-button transition-colors',
|
||||
disabled && 'opacity-50 cursor-not-allowed',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<span className={cn('truncate', !selectedNode && 'text-slate-400')}>
|
||||
{renderSelected
|
||||
? renderSelected(selectedNode)
|
||||
: selectedNode?.title || placeholder || t('common.pleaseSelect')}
|
||||
</span>
|
||||
<div className="flex items-center ml-2 flex-shrink-0">
|
||||
{allowClear && value ? (
|
||||
<X
|
||||
className="h-4 w-4 opacity-50 hover:opacity-100"
|
||||
onClick={handleClear}
|
||||
/>
|
||||
) : (
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0 w-[var(--radix-popover-trigger-width)]"
|
||||
align="start"
|
||||
sideOffset={4}
|
||||
>
|
||||
{showSearch && (
|
||||
<div className="flex items-center border-b px-3 py-2">
|
||||
<Search className="h-4 w-4 text-slate-400 mr-2 flex-shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
className="flex-1 bg-transparent text-sm outline-none placeholder:text-slate-400"
|
||||
placeholder={t('common.search')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="max-h-60 overflow-auto p-1"
|
||||
onWheel={(e) => e.stopPropagation()}
|
||||
>
|
||||
{filteredData.length > 0 ? (
|
||||
renderTree(filteredData)
|
||||
) : (
|
||||
<div className="py-6 text-center text-sm text-slate-400">
|
||||
{t('common.noData')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
export enum ModelStatus {
|
||||
Active = 'active',
|
||||
Inactive = 'inactive',
|
||||
}
|
||||
|
||||
export enum LLMFactory {
|
||||
TongYiQianWen = 'Tongyi-Qianwen',
|
||||
Moonshot = 'Moonshot',
|
||||
@@ -140,6 +145,24 @@ export const IconMap = {
|
||||
[LLMFactory.Perplexity]: 'perplexity',
|
||||
};
|
||||
|
||||
export const ModelTypeToField: Record<string, string> = {
|
||||
chat: 'llm_id',
|
||||
embedding: 'embd_id',
|
||||
image2text: 'img2txt_id',
|
||||
speech2text: 'asr_id',
|
||||
rerank: 'rerank_id',
|
||||
tts: 'tts_id',
|
||||
};
|
||||
|
||||
export const FieldToModelType: Record<string, string> = {
|
||||
llm_id: 'chat',
|
||||
embd_id: 'embedding',
|
||||
img2txt_id: 'vision',
|
||||
asr_id: 'asr',
|
||||
rerank_id: 'rerank',
|
||||
tts_id: 'tts',
|
||||
};
|
||||
|
||||
export const APIMapUrl = {
|
||||
[LLMFactory.OpenAI]: 'https://platform.openai.com/api-keys',
|
||||
[LLMFactory.Anthropic]: 'https://console.anthropic.com/settings/keys',
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { useTranslate } from './common-hooks';
|
||||
import { useSetPaginationParams } from './route-hook';
|
||||
import { useFetchTenantInfo, useSaveSetting } from './use-user-setting-request';
|
||||
import { useSaveSetting } from './use-user-setting-request';
|
||||
|
||||
export function usePrevious<T>(value: T) {
|
||||
const ref = useRef<T>();
|
||||
@@ -748,12 +748,6 @@ export const useSelectItem = (defaultId?: string) => {
|
||||
return { selectedId, handleItemClick };
|
||||
};
|
||||
|
||||
export const useFetchModelId = () => {
|
||||
const { data: tenantInfo } = useFetchTenantInfo(true);
|
||||
|
||||
return tenantInfo?.llm_id ?? '';
|
||||
};
|
||||
|
||||
const ChunkTokenNumMap = {
|
||||
naive: 128,
|
||||
knowledge_graph: 8192,
|
||||
|
||||
@@ -197,6 +197,10 @@ export const useNavigatePage = () => {
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToModelSetting = useCallback(() => {
|
||||
navigate(`${Routes.UserSetting}${Routes.Model}`);
|
||||
}, [navigate]);
|
||||
|
||||
return {
|
||||
navigateToDatasetList,
|
||||
navigateToDataset,
|
||||
@@ -223,5 +227,6 @@ export const useNavigatePage = () => {
|
||||
navigateToDataSourceDetail,
|
||||
navigateToMemory,
|
||||
navigateToMemoryList,
|
||||
navigateToModelSetting,
|
||||
};
|
||||
};
|
||||
+275
-344
@@ -1,197 +1,68 @@
|
||||
import { LlmIcon } from '@/components/svg-icon';
|
||||
import message from '@/components/ui/message';
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { DefaultOptionType } from '@/interfaces/antd-compat';
|
||||
import { ResponseGetType } from '@/interfaces/database/base';
|
||||
import { ModelTypeToField } from '@/constants/llm';
|
||||
import {
|
||||
IFactory,
|
||||
IAddedModel,
|
||||
IAvailableProvider,
|
||||
IDefaultModel,
|
||||
IInstanceModel,
|
||||
IMyLlmValue,
|
||||
IThirdOAIModelCollection as IThirdAiModelCollection,
|
||||
IThirdOAIModel,
|
||||
IThirdOAIModelCollection,
|
||||
IProviderInstance,
|
||||
} from '@/interfaces/database/llm';
|
||||
import {
|
||||
IAddLlmRequestBody,
|
||||
IDeleteLlmRequestBody,
|
||||
IAddInstanceModelRequestBody,
|
||||
IAddProviderInstanceRequestBody,
|
||||
IAddProviderRequestBody,
|
||||
IDeleteProviderInstanceRequestBody,
|
||||
IListAllModelsRequestParams,
|
||||
IListProvidersRequestParams,
|
||||
ISetDefaultModelRequestBody,
|
||||
IUpdateModelStatusRequestBody,
|
||||
} from '@/interfaces/request/llm';
|
||||
import userService from '@/services/user-service';
|
||||
import { getLLMIconName, getRealModelName } from '@/utils/llm-util';
|
||||
import llmService from '@/services/llm-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { buildLlmUuid } from '@/utils/llm-util';
|
||||
import { buildModelValue, parseModelValue } from '@/utils/llm-util';
|
||||
import { useWarnEmptyModel } from './use-warn-empty-model';
|
||||
|
||||
export const enum LLMApiAction {
|
||||
LlmList = 'llmList',
|
||||
MyLlmList = 'myLlmList',
|
||||
MyLlmListDetailed = 'myLlmListDetailed',
|
||||
FactoryList = 'factoryList',
|
||||
SaveApiKey = 'saveApiKey',
|
||||
SaveTenantInfo = 'saveTenantInfo',
|
||||
AddLlm = 'addLlm',
|
||||
DeleteLlm = 'deleteLlm',
|
||||
EnableLlm = 'enableLlm',
|
||||
DeleteFactory = 'deleteFactory',
|
||||
AllModels = 'allModels',
|
||||
AvailableProviders = 'availableProviders',
|
||||
AddedProviders = 'addedProviders',
|
||||
AddProvider = 'addProvider',
|
||||
AddProviderInstance = 'addProviderInstance',
|
||||
AddInstanceModel = 'addInstanceModel',
|
||||
DeleteProviderInstance = 'deleteProviderInstance',
|
||||
ListDefaultModels = 'listDefaultModels',
|
||||
SetDefaultModel = 'setDefaultModel',
|
||||
}
|
||||
|
||||
export const useFetchLlmList = (modelType?: LlmModelType) => {
|
||||
const { data } = useQuery<IThirdAiModelCollection>({
|
||||
queryKey: [LLMApiAction.LlmList],
|
||||
initialData: {},
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.llmList({ model_type: modelType });
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
export const LlmKeys = {
|
||||
availableProviders: () => [LLMApiAction.AvailableProviders] as const,
|
||||
addedProviders: () => [LLMApiAction.AddedProviders] as const,
|
||||
allModels: (modelType?: string) =>
|
||||
[LLMApiAction.AllModels, modelType] as const,
|
||||
providerInstances: (providerName: string) =>
|
||||
[LLMApiAction.AddedProviders, providerName, 'instances'] as const,
|
||||
instanceModels: (providerName: string, instanceName: string) =>
|
||||
[
|
||||
LLMApiAction.AddedProviders,
|
||||
providerName,
|
||||
instanceName,
|
||||
'models',
|
||||
] as const,
|
||||
defaultModels: () => [LLMApiAction.ListDefaultModels] as const,
|
||||
};
|
||||
|
||||
type IThirdOAIModelWithUuid = IThirdOAIModel & { uuid: string };
|
||||
|
||||
export function useSelectFlatLlmList(modelType?: LlmModelType) {
|
||||
const llmList = useFetchLlmList(modelType);
|
||||
|
||||
return Object.values(llmList).reduce<IThirdOAIModelWithUuid[]>((pre, cur) => {
|
||||
pre.push(...cur.map((x) => ({ ...x, uuid: buildLlmUuid(x) })));
|
||||
|
||||
return pre;
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function useFindLlmByUuid(modelType?: LlmModelType) {
|
||||
const flatList = useSelectFlatLlmList(modelType);
|
||||
|
||||
return (uuid: string) => {
|
||||
return flatList.find((x) => x.uuid === uuid);
|
||||
};
|
||||
}
|
||||
|
||||
function buildLlmOptionsWithIcon(x: IThirdOAIModel) {
|
||||
return {
|
||||
label: (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<LlmIcon
|
||||
name={getLLMIconName(x.fid, x.llm_name)}
|
||||
width={24}
|
||||
height={24}
|
||||
size={'small'}
|
||||
imgClass="size-6"
|
||||
/>
|
||||
<span>{getRealModelName(x.llm_name)}</span>
|
||||
</div>
|
||||
),
|
||||
value: `${x.llm_name}@${x.fid}`,
|
||||
disabled: !x.available,
|
||||
is_tools: x.is_tools,
|
||||
};
|
||||
}
|
||||
|
||||
export const useSelectLlmOptionsByModelType = () => {
|
||||
const llmInfo: IThirdOAIModelCollection = useFetchLlmList();
|
||||
|
||||
const groupImage2TextOptions = useCallback(() => {
|
||||
const modelType = LlmModelType.Image2text;
|
||||
const modelTag = modelType.toUpperCase();
|
||||
return Object.entries(llmInfo)
|
||||
.map(([key, value]) => {
|
||||
return {
|
||||
label: key,
|
||||
options: value
|
||||
.filter(
|
||||
(x) =>
|
||||
(x.model_type.includes(modelType) ||
|
||||
(x.tags && x.tags.includes(modelTag))) &&
|
||||
x.available &&
|
||||
x.status === '1',
|
||||
)
|
||||
.map(buildLlmOptionsWithIcon),
|
||||
};
|
||||
})
|
||||
.filter((x) => x.options.length > 0);
|
||||
}, [llmInfo]);
|
||||
|
||||
const groupOptionsByModelType = useCallback(
|
||||
(modelType: LlmModelType) => {
|
||||
return Object.entries(llmInfo)
|
||||
.filter(([, value]) =>
|
||||
modelType
|
||||
? value.some((x) => x.model_type.includes(modelType))
|
||||
: true,
|
||||
)
|
||||
.map(([key, value]) => {
|
||||
return {
|
||||
label: key,
|
||||
options: value
|
||||
.filter(
|
||||
(x) =>
|
||||
(modelType ? x.model_type.includes(modelType) : true) &&
|
||||
x.available,
|
||||
)
|
||||
.map(buildLlmOptionsWithIcon),
|
||||
};
|
||||
})
|
||||
.filter((x) => x.options.length > 0);
|
||||
},
|
||||
[llmInfo],
|
||||
);
|
||||
|
||||
return {
|
||||
[LlmModelType.Chat]: groupOptionsByModelType(LlmModelType.Chat),
|
||||
[LlmModelType.Embedding]: groupOptionsByModelType(LlmModelType.Embedding),
|
||||
[LlmModelType.Image2text]: groupImage2TextOptions(),
|
||||
[LlmModelType.Speech2text]: groupOptionsByModelType(
|
||||
LlmModelType.Speech2text,
|
||||
),
|
||||
[LlmModelType.Rerank]: groupOptionsByModelType(LlmModelType.Rerank),
|
||||
[LlmModelType.TTS]: groupOptionsByModelType(LlmModelType.TTS),
|
||||
[LlmModelType.Ocr]: groupOptionsByModelType(LlmModelType.Ocr),
|
||||
};
|
||||
};
|
||||
|
||||
// Merge different types of models from the same manufacturer under one manufacturer
|
||||
export const useComposeLlmOptionsByModelTypes = (
|
||||
modelTypes: LlmModelType[],
|
||||
) => {
|
||||
const allOptions = useSelectLlmOptionsByModelType();
|
||||
return modelTypes.reduce<
|
||||
(DefaultOptionType & {
|
||||
options: {
|
||||
label: JSX.Element;
|
||||
value: string;
|
||||
disabled: boolean;
|
||||
is_tools: boolean;
|
||||
}[];
|
||||
})[]
|
||||
>((pre, cur) => {
|
||||
const options = allOptions[cur];
|
||||
options.forEach((x) => {
|
||||
const item = pre.find((y) => y.label === x.label);
|
||||
if (item) {
|
||||
x.options.forEach((y) => {
|
||||
// A model that is both an image2text and speech2text model
|
||||
if (!item.options.some((z) => z.value === y.value)) {
|
||||
item.options.push(y);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
pre.push(x);
|
||||
}
|
||||
});
|
||||
|
||||
return pre;
|
||||
}, []);
|
||||
};
|
||||
|
||||
export const useFetchLlmFactoryList = (): ResponseGetType<IFactory[]> => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [LLMApiAction.FactoryList],
|
||||
export const useFetchAvailableProviders = () => {
|
||||
const { data, isFetching: loading } = useQuery<IAvailableProvider[]>({
|
||||
queryKey: LlmKeys.availableProviders(),
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.factoriesList();
|
||||
const params: IListProvidersRequestParams = { available: true };
|
||||
const { data } = await llmService.listProviders({ params }, true);
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
@@ -200,190 +71,198 @@ export const useFetchLlmFactoryList = (): ResponseGetType<IFactory[]> => {
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchAddedProviders = () => {
|
||||
const { data, isFetching: loading } = useQuery<IAvailableProvider[]>({
|
||||
queryKey: LlmKeys.addedProviders(),
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await llmService.listProviders({ params: {} }, true);
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchAllAddedModels = (modelType?: string) => {
|
||||
const { data, isFetching: loading } = useQuery<IAddedModel[]>({
|
||||
queryKey: LlmKeys.allModels(modelType),
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const params: IListAllModelsRequestParams = {};
|
||||
if (modelType) {
|
||||
params.type = modelType;
|
||||
}
|
||||
const { data } = await llmService.listAllAddedModels({ params }, true);
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export function useFindLlmByUuid() {
|
||||
const { data: models } = useFetchAllAddedModels();
|
||||
|
||||
return (uuid: string) => {
|
||||
const parsed = parseModelValue(uuid);
|
||||
if (parsed) {
|
||||
return models.find(
|
||||
(m) =>
|
||||
m.name === parsed.model_name &&
|
||||
m.instance_name === parsed.model_instance &&
|
||||
m.provider_name === parsed.model_provider,
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
export const useFetchProviderInstances = (providerName: string) => {
|
||||
const { data, isFetching: loading } = useQuery<IProviderInstance[]>({
|
||||
queryKey: LlmKeys.providerInstances(providerName),
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
enabled: !!providerName,
|
||||
queryFn: async () => {
|
||||
const { data } = await llmService.listProviderInstances(
|
||||
{ provider_name: providerName },
|
||||
true,
|
||||
);
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchInstanceModels = (
|
||||
providerName: string,
|
||||
instanceName: string,
|
||||
) => {
|
||||
const { data, isFetching: loading } = useQuery<IInstanceModel[]>({
|
||||
queryKey: LlmKeys.instanceModels(providerName, instanceName),
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
enabled: !!providerName && !!instanceName,
|
||||
queryFn: async () => {
|
||||
const { data } = await llmService.listInstanceModels(
|
||||
{ provider_name: providerName, instance_name: instanceName },
|
||||
true,
|
||||
);
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export type LlmItem = { name: string; logo: string } & IMyLlmValue;
|
||||
|
||||
export const useFetchMyLlmList = (): ResponseGetType<
|
||||
Record<string, IMyLlmValue>
|
||||
> => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [LLMApiAction.MyLlmList],
|
||||
initialData: {},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.myLlm();
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchMyLlmListDetailed = (): ResponseGetType<
|
||||
Record<string, any>
|
||||
> => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
initialData: {},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.myLlm({ include_details: true });
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useSelectLlmList = () => {
|
||||
const { data: myLlmList, loading: myLlmListLoading } = useFetchMyLlmList();
|
||||
const { data: factoryList, loading: factoryListLoading } =
|
||||
useFetchLlmFactoryList();
|
||||
|
||||
const nextMyLlmList: Array<LlmItem> = useMemo(() => {
|
||||
return Object.entries(myLlmList).map(([key, value]) => ({
|
||||
name: key,
|
||||
logo: factoryList.find((x) => x.name === key)?.logo ?? '',
|
||||
...value,
|
||||
llm: value.llm?.map((x) => ({ ...x, name: x.name })),
|
||||
}));
|
||||
}, [myLlmList, factoryList]);
|
||||
|
||||
const nextFactoryList = useMemo(() => {
|
||||
const currentList = factoryList.filter((x) =>
|
||||
Object.keys(myLlmList).every((y) => y !== x.name),
|
||||
);
|
||||
return currentList;
|
||||
// return sortLLmFactoryListBySpecifiedOrder(currentList);
|
||||
}, [factoryList, myLlmList]);
|
||||
|
||||
return {
|
||||
myLlmList: nextMyLlmList,
|
||||
factoryList: nextFactoryList,
|
||||
loading: myLlmListLoading || factoryListLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export interface IApiKeySavingParams {
|
||||
llm_factory: string;
|
||||
api_key: string;
|
||||
llm_name?: string;
|
||||
model_type?: string;
|
||||
base_url?: string;
|
||||
source_fid?: string;
|
||||
verify?: boolean;
|
||||
}
|
||||
|
||||
export const useSaveApiKey = () => {
|
||||
const queryClient = useQueryClient();
|
||||
// const { t } = useTranslation();
|
||||
export const useAddProvider = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [LLMApiAction.SaveApiKey],
|
||||
mutationFn: async (params: IApiKeySavingParams) => {
|
||||
const { data } = await userService.setApiKey(params);
|
||||
if (data.code === 0) {
|
||||
// message.success(t('message.modified'));
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] });
|
||||
mutationKey: [LLMApiAction.AddProvider],
|
||||
mutationFn: async (params: IAddProviderRequestBody) => {
|
||||
try {
|
||||
const { data: listRes } = await llmService.listProviders(
|
||||
{ params: {} },
|
||||
true,
|
||||
);
|
||||
const isProviderAdded = listRes?.data?.some(
|
||||
(p: IAvailableProvider) => p.name === params.provider_name,
|
||||
);
|
||||
if (isProviderAdded) {
|
||||
return { code: 0, data: null };
|
||||
}
|
||||
} catch {
|
||||
// ignore list failure and proceed to add
|
||||
}
|
||||
const { data } = await llmService.addProvider(params);
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, saveApiKey: mutateAsync };
|
||||
return { data, loading, addProvider: mutateAsync };
|
||||
};
|
||||
|
||||
export interface ISystemModelSettingSavingParams {
|
||||
tenant_id: string;
|
||||
name?: string;
|
||||
asr_id: string;
|
||||
embd_id: string;
|
||||
img2txt_id: string;
|
||||
llm_id: string;
|
||||
}
|
||||
|
||||
export const useSaveTenantInfo = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [LLMApiAction.SaveTenantInfo],
|
||||
mutationFn: async (params: ISystemModelSettingSavingParams) => {
|
||||
const { data } = await userService.setTenantInfo(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.modified'));
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, saveTenantInfo: mutateAsync };
|
||||
};
|
||||
|
||||
export const useAddLlm = () => {
|
||||
export const useAddProviderInstance = () => {
|
||||
const { addProvider } = useAddProvider();
|
||||
const queryClient = useQueryClient();
|
||||
// const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [LLMApiAction.AddLlm],
|
||||
mutationFn: async (params: IAddLlmRequestBody & { verify?: boolean }) => {
|
||||
const { data } = await userService.addLlm(params);
|
||||
mutationKey: [LLMApiAction.AddProviderInstance],
|
||||
mutationFn: async (
|
||||
params: IAddProviderInstanceRequestBody & { verify?: boolean },
|
||||
) => {
|
||||
try {
|
||||
await addProvider({ provider_name: params.llm_factory });
|
||||
|
||||
const { data: instancesRes } = await llmService.listProviderInstances(
|
||||
{ provider_name: params.llm_factory },
|
||||
true,
|
||||
);
|
||||
const instanceExists = instancesRes?.data?.some(
|
||||
(i: IProviderInstance) => i.instance_name === params.instance_name,
|
||||
);
|
||||
if (instanceExists && !params.verify) {
|
||||
return { code: 0, data: null };
|
||||
}
|
||||
} catch {
|
||||
// ignore list failure and proceed to add
|
||||
}
|
||||
|
||||
const { data } = await llmService.addProviderInstance(params);
|
||||
if (data.code === 0 && !params.verify) {
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
queryKey: LlmKeys.addedProviders(),
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] });
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.LlmList] });
|
||||
// message.success(t('message.modified'));
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, addLlm: mutateAsync };
|
||||
return { data, loading, addProviderInstance: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteLlm = () => {
|
||||
export const useAddInstanceModel = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [LLMApiAction.DeleteLlm],
|
||||
mutationFn: async (params: IDeleteLlmRequestBody) => {
|
||||
const { data } = await userService.deleteLlm(params);
|
||||
mutationKey: [LLMApiAction.AddInstanceModel],
|
||||
mutationFn: async (
|
||||
params: {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
} & IAddInstanceModelRequestBody,
|
||||
) => {
|
||||
const { data } = await llmService.addInstanceModel(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
queryKey: LlmKeys.addedProviders(),
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] });
|
||||
message.success(t('message.deleted'));
|
||||
}
|
||||
return data.code;
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteLlm: mutateAsync };
|
||||
return { data, loading, addInstanceModel: mutateAsync };
|
||||
};
|
||||
|
||||
export const useEnableLlm = () => {
|
||||
export const useDeleteProviderInstance = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
@@ -391,52 +270,104 @@ export const useEnableLlm = () => {
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [LLMApiAction.EnableLlm],
|
||||
mutationFn: async (params: IDeleteLlmRequestBody & { enable: boolean }) => {
|
||||
const reqParam: IDeleteLlmRequestBody & {
|
||||
enable?: boolean;
|
||||
status?: 1 | 0;
|
||||
} = { ...params, status: params.enable ? 1 : 0 };
|
||||
delete reqParam.enable;
|
||||
const { data } = await userService.enableLlm(reqParam);
|
||||
mutationKey: [LLMApiAction.DeleteProviderInstance],
|
||||
mutationFn: async (params: IDeleteProviderInstanceRequestBody) => {
|
||||
const { data } = await llmService.deleteProviderInstance(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
queryKey: LlmKeys.addedProviders(),
|
||||
exact: true,
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: LlmKeys.providerInstances(params.provider_name),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: LlmKeys.defaultModels(),
|
||||
});
|
||||
|
||||
message.success(t('message.deleted'));
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteProviderInstance: mutateAsync };
|
||||
};
|
||||
|
||||
export const useUpdateModelStatus = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const { isPending: loading, mutateAsync } = useMutation({
|
||||
mutationKey: [LLMApiAction.AddedProviders, 'updateModelStatus'],
|
||||
mutationFn: async (params: IUpdateModelStatusRequestBody) => {
|
||||
const { data } = await llmService.updateModelStatus(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.modified'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: LlmKeys.defaultModels(),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: LlmKeys.instanceModels(
|
||||
params.provider_name,
|
||||
params.instance_name,
|
||||
),
|
||||
});
|
||||
}
|
||||
return data.code;
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, enableLlm: mutateAsync };
|
||||
return { loading, updateModelStatus: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteFactory = () => {
|
||||
export const useFetchDefaultModels = () => {
|
||||
const { data, isFetching: loading } = useQuery<IDefaultModel[]>({
|
||||
queryKey: LlmKeys.defaultModels(),
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await llmService.listDefaultModels({}, true);
|
||||
return data?.data?.models ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchDefaultModelDictionary = (showEmptyModelWarn = false) => {
|
||||
const { data: defaultModels } = useFetchDefaultModels();
|
||||
|
||||
const result = useMemo(() => {
|
||||
const dict: Record<string, string> = {};
|
||||
Object.entries(ModelTypeToField).forEach(([key, field]) => {
|
||||
const model = defaultModels.find((m) => m.model_type === key);
|
||||
dict[field] = model && model.enable ? buildModelValue(model) : '';
|
||||
});
|
||||
return dict;
|
||||
}, [defaultModels]);
|
||||
|
||||
useWarnEmptyModel(showEmptyModelWarn, result.embd_id, result.llm_id);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const useSetDefaultModel = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [LLMApiAction.DeleteFactory],
|
||||
mutationFn: async (params: IDeleteLlmRequestBody) => {
|
||||
const { data } = await userService.deleteFactory(params);
|
||||
|
||||
const { isPending: loading, mutateAsync } = useMutation({
|
||||
mutationKey: [LLMApiAction.SetDefaultModel],
|
||||
mutationFn: async (params: ISetDefaultModelRequestBody) => {
|
||||
const { data } = await llmService.setDefaultModel(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] });
|
||||
message.success(t('message.modified'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
queryKey: LlmKeys.defaultModels(),
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] });
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.LlmList] });
|
||||
message.success(t('message.deleted'));
|
||||
}
|
||||
return data.code;
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteFactory: mutateAsync };
|
||||
return { loading, setDefaultModel: mutateAsync };
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { ResponseGetType } from '@/interfaces/database/base';
|
||||
import { IToken } from '@/interfaces/database/chat';
|
||||
import { ITenantInfo } from '@/interfaces/database/dataset';
|
||||
@@ -11,7 +10,6 @@ import {
|
||||
} from '@/interfaces/database/user-setting';
|
||||
import { ISetLangfuseConfigRequestBody } from '@/interfaces/request/system';
|
||||
import { DEFAULT_LANGUAGE_CODE, supportedLanguages } from '@/locales/config';
|
||||
import { Routes } from '@/routes';
|
||||
import userService, {
|
||||
addTenantUser,
|
||||
agreeTenant,
|
||||
@@ -20,11 +18,10 @@ import userService, {
|
||||
listTenantUser,
|
||||
} from '@/services/user-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { useWarnEmptyModel } from './use-warn-empty-model';
|
||||
|
||||
export const enum UserSettingApiAction {
|
||||
UserInfo = 'userInfo',
|
||||
@@ -69,11 +66,10 @@ export const useFetchUserInfo = (): ResponseGetType<IUserInfo> => {
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
// Stop using this interface to retrieve the default model; instead, directly call `useFetchDefaultModelDictionary`.
|
||||
export const useFetchTenantInfo = (
|
||||
showEmptyModelWarn = false,
|
||||
): ResponseGetType<ITenantInfo> => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [UserSettingApiAction.TenantInfo, showEmptyModelWarn],
|
||||
initialData: {},
|
||||
@@ -84,27 +80,6 @@ export const useFetchTenantInfo = (
|
||||
// llm_id is chat_id
|
||||
// asr_id is speech2txt
|
||||
const { data } = res;
|
||||
if (
|
||||
showEmptyModelWarn &&
|
||||
(isEmpty(data.embd_id) || isEmpty(data.llm_id))
|
||||
) {
|
||||
Modal.warning({
|
||||
title: t('common.warn'),
|
||||
content: (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(t('setting.modelProvidersWarn')),
|
||||
}}
|
||||
></div>
|
||||
),
|
||||
closable: false,
|
||||
showCancel: false,
|
||||
onOk() {
|
||||
// window.open('/user-setting/model', '_self');
|
||||
navigate(`${Routes.UserSetting}${Routes.Model}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
data.chat_id = data.llm_id;
|
||||
data.speech2text_id = data.asr_id;
|
||||
|
||||
@@ -115,6 +90,8 @@ export const useFetchTenantInfo = (
|
||||
},
|
||||
});
|
||||
|
||||
useWarnEmptyModel(showEmptyModelWarn, data?.embd_id, data?.llm_id);
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigatePage } from './logic-hooks/navigate-hooks';
|
||||
|
||||
export const useWarnEmptyModel = (
|
||||
showEmptyModelWarn: boolean,
|
||||
embdId?: string,
|
||||
llmId?: string,
|
||||
) => {
|
||||
const { t } = useTranslation();
|
||||
const warnedRef = useRef(false);
|
||||
const { navigateToModelSetting } = useNavigatePage();
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
showEmptyModelWarn &&
|
||||
!warnedRef.current &&
|
||||
(isEmpty(embdId) || isEmpty(llmId)) &&
|
||||
typeof embdId === 'string' &&
|
||||
typeof llmId === 'string'
|
||||
) {
|
||||
warnedRef.current = true;
|
||||
Modal.warning({
|
||||
title: t('common.warn'),
|
||||
content: (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(t('setting.modelProvidersWarn')),
|
||||
}}
|
||||
></div>
|
||||
),
|
||||
closable: false,
|
||||
showCancel: false,
|
||||
onOk() {
|
||||
navigateToModelSetting();
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [showEmptyModelWarn, embdId, llmId, navigateToModelSetting, t]);
|
||||
};
|
||||
@@ -40,3 +40,41 @@ export interface Llm {
|
||||
status: '0' | '1';
|
||||
used_token: number;
|
||||
}
|
||||
|
||||
export interface IAvailableProvider {
|
||||
name: string;
|
||||
model_types: string[];
|
||||
url: { default?: string; [key: string]: string | undefined };
|
||||
}
|
||||
|
||||
export interface IProviderInstance {
|
||||
api_key: string;
|
||||
id: string;
|
||||
instance_name: string;
|
||||
provider_id: string;
|
||||
region: string;
|
||||
status: string;
|
||||
}
|
||||
export interface IAddedModel {
|
||||
model_type: string[];
|
||||
name: string;
|
||||
provider_id: string;
|
||||
provider_name: string;
|
||||
instance_id: string;
|
||||
instance_name: string;
|
||||
}
|
||||
|
||||
export interface IInstanceModel {
|
||||
max_tokens: number;
|
||||
model_type: string[];
|
||||
name: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface IDefaultModel {
|
||||
enable: boolean;
|
||||
model_instance: string;
|
||||
model_name: string;
|
||||
model_provider: string;
|
||||
model_type: string;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
export interface IAddLlmRequestBody {
|
||||
llm_factory: string; // Ollama
|
||||
llm_name: string;
|
||||
model_type: string;
|
||||
model_type: string | string[];
|
||||
api_base?: string; // chat|embedding|speech2text|image2text
|
||||
api_key?: string | Record<string, any>;
|
||||
max_tokens: number;
|
||||
@@ -12,3 +12,50 @@ export interface IDeleteLlmRequestBody {
|
||||
llm_factory: string; // Ollama
|
||||
llm_name?: string;
|
||||
}
|
||||
|
||||
export interface IListProvidersRequestParams {
|
||||
available?: boolean;
|
||||
}
|
||||
|
||||
export interface IAddProviderRequestBody {
|
||||
provider_name: string;
|
||||
}
|
||||
|
||||
export type IAddProviderInstanceRequestBody = IAddLlmRequestBody & {
|
||||
instance_name: string;
|
||||
};
|
||||
|
||||
export interface IDeleteProviderInstanceRequestBody {
|
||||
provider_name: string;
|
||||
instances: string[];
|
||||
}
|
||||
|
||||
export interface IShowProviderInstanceRequestParams {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
}
|
||||
|
||||
export interface IAddInstanceModelRequestBody {
|
||||
model_name: string;
|
||||
model_type: string[];
|
||||
max_tokens: number;
|
||||
extra?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface IListAllModelsRequestParams {
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface IUpdateModelStatusRequestBody {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
model_name: string;
|
||||
status: 'active' | 'inactive';
|
||||
}
|
||||
|
||||
export interface ISetDefaultModelRequestBody {
|
||||
model_provider: string;
|
||||
model_instance: string;
|
||||
model_type: string;
|
||||
model_name: string;
|
||||
}
|
||||
@@ -1514,6 +1514,10 @@ Example: Virtual Hosted Style`,
|
||||
addLlmTitle: 'Add LLM',
|
||||
editLlmTitle: 'Edit {{name}} model',
|
||||
editModel: 'Edit model',
|
||||
instanceName: 'Instance name',
|
||||
instanceNameMessage: 'Please input the instance name!',
|
||||
instanceNameTip:
|
||||
'A unique name to identify this provider instance under the same factory.',
|
||||
modelName: 'Model name',
|
||||
modelID: 'Model ID',
|
||||
modelUid: 'Model UID',
|
||||
|
||||
@@ -1099,7 +1099,8 @@ NER:使用 spaCy NER 和基于规则的关键词提取来抽取实体和关系
|
||||
gmailTokenTip:
|
||||
'请上传由 Google Console 生成的 OAuth JSON。如果仅包含 client credentials,请通过浏览器授权一次以获取长期有效的刷新 Token。',
|
||||
dropboxDescription: '连接 Dropbox,同步指定账号下的文件与文件夹。',
|
||||
teamsDescription: '通过 Microsoft Graph 连接 Microsoft Teams,同步频道帖子与回复。',
|
||||
teamsDescription:
|
||||
'通过 Microsoft Graph 连接 Microsoft Teams,同步频道帖子与回复。',
|
||||
teamsTenantIdTip:
|
||||
'Azure AD 租户 ID。需要具备 Team.ReadBasic.All 与 ChannelMessage.Read.All 应用权限(管理员同意)的应用。',
|
||||
slackDescription: '连接你的 Slack 工作区,同步频道消息与讨论串。',
|
||||
@@ -1107,7 +1108,8 @@ NER:使用 spaCy NER 和基于规则的关键词提取来抽取实体和关系
|
||||
'Slack 机器人用户 OAuth Token(以 xoxb- 开头)。应用需具备 channels:read、channels:history 和 users:read 权限。',
|
||||
slackChannelsTip:
|
||||
'可选:需要同步的频道名称(例如 general)。留空则同步所有可访问的频道。',
|
||||
sharepointDescription: '通过 Microsoft Graph 连接 SharePoint 站点,同步其文档库。',
|
||||
sharepointDescription:
|
||||
'通过 Microsoft Graph 连接 SharePoint 站点,同步其文档库。',
|
||||
sharepointSiteUrlTip:
|
||||
'要索引的 SharePoint 站点完整 URL,例如 https://contoso.sharepoint.com/sites/MySite。需要具备 Sites.Read.All 与 Files.Read.All 应用权限(管理员同意)的 Azure AD 应用。',
|
||||
boxDescription: '连接你的 Box 云盘以同步文件和文件夹。',
|
||||
@@ -1238,6 +1240,9 @@ NER:使用 spaCy NER 和基于规则的关键词提取来抽取实体和关系
|
||||
addLlmTitle: '添加 LLM',
|
||||
editLlmTitle: '编辑 {{name}} 模型',
|
||||
editModel: '编辑模型',
|
||||
instanceName: '实例名称',
|
||||
instanceNameMessage: '请输入实例名称!',
|
||||
instanceNameTip: '用于在同一厂商下唯一标识该实例的名称。',
|
||||
modelName: '模型名称',
|
||||
modelID: '模型ID',
|
||||
modelUid: '模型UID',
|
||||
|
||||
@@ -17,8 +17,9 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { useSelectFlatLlmList } from '@/hooks/use-llm-request';
|
||||
import { useFetchAllAddedModels } from '@/hooks/use-llm-request';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { parseModelValue } from '@/utils/llm-util';
|
||||
import { PropsWithChildren, useMemo } from 'react';
|
||||
|
||||
export function CardWithForm() {
|
||||
@@ -80,12 +81,23 @@ export function LabelCard({ children, className, ...props }: LabelCardProps) {
|
||||
}
|
||||
|
||||
export function LLMLabelCard({ llmId }: { llmId?: string }) {
|
||||
const flatLlmList = useSelectFlatLlmList();
|
||||
const { data: allAddedModels } = useFetchAllAddedModels();
|
||||
|
||||
const isValidLlm = useMemo(() => {
|
||||
if (!llmId) return false;
|
||||
return flatLlmList.some((llm) => llm.uuid === llmId);
|
||||
}, [flatLlmList, llmId]);
|
||||
|
||||
const parsed = parseModelValue(llmId);
|
||||
if (parsed) {
|
||||
return allAddedModels.some(
|
||||
(m) =>
|
||||
m.name === parsed.model_name &&
|
||||
m.instance_name === parsed.model_instance &&
|
||||
m.provider_name === parsed.model_provider,
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [allAddedModels, llmId]);
|
||||
|
||||
return (
|
||||
<LabelCard
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Input, NumberInput } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useFindLlmByUuid } from '@/hooks/use-llm-request';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { get } from 'lodash';
|
||||
@@ -158,7 +159,9 @@ function AgentForm({ node }: INextOperatorForm) {
|
||||
<FormWrapper>
|
||||
{isSubAgent && <DescriptionField></DescriptionField>}
|
||||
<LargeModelFormField showSpeech2TextModel></LargeModelFormField>
|
||||
{findLlmByUuid(llmId)?.tags?.includes('IMAGE2TEXT') && (
|
||||
{findLlmByUuid(llmId)?.model_type?.includes(
|
||||
LlmModelType.Image2text,
|
||||
) && (
|
||||
<QueryVariable
|
||||
name="visual_files_var"
|
||||
label="Visual Input File"
|
||||
|
||||
Loaded 100 of 148 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user