Merge branch 'main' into fix-pr13295-conflicts

# Conflicts:
#	api/apps/canvas_app.py
#	api/apps/restful_apis/mcp_api.py
#	api/db/services/canvas_service.py
This commit is contained in:
conflict-resolver
2026-06-30 16:13:13 +08:00
3160 changed files with 775300 additions and 82681 deletions

View File

@@ -19,7 +19,6 @@ import functools
import inspect
import json
import logging
import os
import sys
import time
from copy import deepcopy
@@ -28,7 +27,6 @@ from typing import Any
import requests
from quart import (
Response,
jsonify,
request,
has_app_context,
@@ -42,8 +40,7 @@ except ImportError: # pragma: no cover - optional dependency
from peewee import OperationalError
from common.constants import ActiveEnum
from api.db.db_models import APIToken
from common.constants import ActiveEnum, LLMType
from api.utils.json_encode import CustomJSONEncoder
from common.mcp_tool_call_conn import MCPToolCallSession, close_multiple_mcp_toolcall_sessions
from api.db.services.tenant_llm_service import LLMFactoriesService
@@ -148,6 +145,9 @@ def server_error_response(e):
if repr(e).find("index_not_found_exception") >= 0:
return get_json_result(code=RetCode.EXCEPTION_ERROR, message="No chunk found, please upload file and parse it.")
if "not_found" in str(e):
return get_error_data_result(message="No chunk found! Check the chunk status please!")
return get_json_result(code=RetCode.EXCEPTION_ERROR, message=repr(e))
@@ -234,27 +234,22 @@ def active_required(func):
return wrapper
def add_tenant_id_to_kwargs(func):
@wraps(func)
async def wrapper(**kwargs):
from api.apps import current_user
kwargs["tenant_id"] = current_user.id
if inspect.iscoroutinefunction(func):
return await func(**kwargs)
return func(**kwargs)
return wrapper
def get_json_result(code: RetCode = RetCode.SUCCESS, message="success", data=None):
response = {"code": code, "message": message, "data": data}
return _safe_jsonify(response)
def apikey_required(func):
@wraps(func)
async def decorated_function(*args, **kwargs):
token = request.headers.get("Authorization").split()[1]
objs = APIToken.query(token=token)
if not objs:
return build_error_result(message="API-KEY is invalid!", code=RetCode.FORBIDDEN)
kwargs["tenant_id"] = objs[0].tenant_id
if inspect.iscoroutinefunction(func):
return await func(*args, **kwargs)
return func(*args, **kwargs)
return decorated_function
def build_error_result(code=RetCode.FORBIDDEN, message="success"):
response = {"code": code, "message": message}
response = _safe_jsonify(response)
@@ -269,42 +264,6 @@ def construct_json_result(code: RetCode = RetCode.SUCCESS, message="success", da
return _safe_jsonify({"code": code, "message": message, "data": data})
def token_required(func):
def get_tenant_id(**kwargs):
if os.environ.get("DISABLE_SDK"):
return False, get_json_result(data=False, message="`Authorization` can't be empty")
authorization_str = request.headers.get("Authorization")
if not authorization_str:
return False, get_json_result(data=False, message="`Authorization` can't be empty")
authorization_list = authorization_str.split()
if len(authorization_list) < 2:
return False, get_json_result(data=False, message="Please check your authorization format.")
token = authorization_list[1]
objs = APIToken.query(token=token)
if not objs:
return False, get_json_result(data=False, message="Authentication error: API key is invalid!", code=RetCode.AUTHENTICATION_ERROR)
kwargs["tenant_id"] = objs[0].tenant_id
return True, kwargs
@wraps(func)
def decorated_function(*args, **kwargs):
e, kwargs = get_tenant_id(**kwargs)
if not e:
return kwargs
return func(*args, **kwargs)
@wraps(func)
async def adecorated_function(*args, **kwargs):
e, kwargs = get_tenant_id(**kwargs)
if not e:
return kwargs
return await func(*args, **kwargs)
if inspect.iscoroutinefunction(func):
return adecorated_function
return decorated_function
def get_result(code=RetCode.SUCCESS, message="", data=None, total=None):
"""
Standard API response format:
@@ -396,6 +355,20 @@ def get_parser_config(chunk_method, parser_config):
"category",
],
"method": "light",
"batch_chunk_token_size": 4096,
"retry_attempts": 2,
"retry_backoff_seconds": 2.0,
"retry_backoff_max_seconds": 60.0,
"build_subgraph_timeout_per_chunk_seconds": 300,
"build_subgraph_min_timeout_seconds": 600,
"merge_timeout_seconds": 180,
"resolution_timeout_seconds": 1800,
"community_timeout_seconds": 1800,
"lock_acquire_timeout_seconds": 600,
},
"parent_child": {
"use_parent_child": False,
"children_delimiter": "\n",
},
},
"qa": {"raptor": {"use_raptor": False}, "graphrag": {"use_graphrag": False}},
@@ -424,16 +397,23 @@ def get_parser_config(chunk_method, parser_config):
# If no parser_config provided, return default merged with base defaults
if not parser_config:
if default_config is None:
return deep_merge(base_defaults, {})
return deep_merge(base_defaults, default_config)
merged_config = deep_merge(base_defaults, {})
else:
merged_config = deep_merge(base_defaults, default_config)
elif default_config is None:
# If parser_config is provided but no defaults for this method
merged_config = deep_merge(base_defaults, parser_config)
else:
# Ensure raptor and graph_rag fields have default values if not provided
merged_config = deep_merge(base_defaults, default_config)
merged_config = deep_merge(merged_config, parser_config)
# If parser_config is provided, merge with defaults to ensure required fields exist
if default_config is None:
return deep_merge(base_defaults, parser_config)
# Ensure raptor and graph_rag fields have default values if not provided
merged_config = deep_merge(base_defaults, default_config)
merged_config = deep_merge(merged_config, parser_config)
# Flatten parent_child config into children_delimiter for the execution layer
pc = merged_config.get("parent_child", {})
if pc.get("use_parent_child"):
merged_config["children_delimiter"] = pc.get("children_delimiter", "\n")
elif pc:
merged_config["children_delimiter"] = ""
return merged_config
@@ -511,9 +491,8 @@ def check_duplicate_ids(ids, id_type="item"):
return list(set(ids)), duplicate_messages
def verify_embedding_availability(embd_id: str, tenant_id: str) -> tuple[bool, Response | None]:
from api.db.services.llm_service import LLMService
from api.db.services.tenant_llm_service import TenantLLMService
def verify_embedding_availability(embd_id: str, tenant_id: str) -> tuple[bool, str | None]:
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.
@@ -549,21 +528,15 @@ def verify_embedding_availability(embd_id: str, tenant_id: str) -> tuple[bool, R
(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, get_error_argument_result(f"Unsupported model: <{embd_id}>")
if not (is_builtin_model or is_tenant_model):
return False, get_error_argument_result(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, get_error_data_result(message="Database operation failed")
return False, "Database operation failed"
except Exception as e:
logging.exception(e)
return False, "Internal server error"
return True, None