diff --git a/agent/component/agent_with_tools.py b/agent/component/agent_with_tools.py index a27504139..906a9eca3 100644 --- a/agent/component/agent_with_tools.py +++ b/agent/component/agent_with_tools.py @@ -30,7 +30,7 @@ from api.db.services.mcp_server_service import MCPServerService from common.connection_utils import timeout from rag.prompts.generator import next_step, COMPLETE_TASK, analyze_task, \ citation_prompt, reflect, rank_memories, kb_prompt, citation_plus, full_question, message_fit_in -from rag.utils.mcp_tool_call_conn import MCPToolCallSession, mcp_tool_metadata_to_openai_tool +from common.mcp_tool_call_conn import MCPToolCallSession, mcp_tool_metadata_to_openai_tool from agent.component.llm import LLMParam, LLM diff --git a/agent/tools/base.py b/agent/tools/base.py index a3d569694..791242d59 100644 --- a/agent/tools/base.py +++ b/agent/tools/base.py @@ -21,9 +21,8 @@ from functools import partial from typing import TypedDict, List, Any from agent.component.base import ComponentParamBase, ComponentBase from common.misc_utils import hash_str2int -from rag.llm.chat_model import ToolCallSession from rag.prompts.generator import kb_prompt -from rag.utils.mcp_tool_call_conn import MCPToolCallSession +from common.mcp_tool_call_conn import MCPToolCallSession, ToolCallSession from timeit import default_timer as timer diff --git a/api/apps/mcp_server_app.py b/api/apps/mcp_server_app.py index ad78735c5..583f721c4 100644 --- a/api/apps/mcp_server_app.py +++ b/api/apps/mcp_server_app.py @@ -25,7 +25,7 @@ from common.misc_utils import get_uuid from api.utils.api_utils import get_data_error_result, get_json_result, server_error_response, validate_request, \ get_mcp_tools from api.utils.web_utils import get_float, safe_json_parse -from rag.utils.mcp_tool_call_conn import MCPToolCallSession, close_multiple_mcp_toolcall_sessions +from common.mcp_tool_call_conn import MCPToolCallSession, close_multiple_mcp_toolcall_sessions @manager.route("/list", methods=["POST"]) # noqa: F821 diff --git a/api/apps/sdk/agents.py b/api/apps/sdk/agents.py index 034915f1f..dda696bf4 100644 --- a/api/apps/sdk/agents.py +++ b/api/apps/sdk/agents.py @@ -41,12 +41,12 @@ def list_agents(tenant_id): return get_error_data_result("The agent doesn't exist.") page_number = int(request.args.get("page", 1)) items_per_page = int(request.args.get("page_size", 30)) - orderby = request.args.get("orderby", "update_time") + order_by = request.args.get("orderby", "update_time") if request.args.get("desc") == "False" or request.args.get("desc") == "false": desc = False else: desc = True - canvas = UserCanvasService.get_list(tenant_id, page_number, items_per_page, orderby, desc, id, title) + canvas = UserCanvasService.get_list(tenant_id, page_number, items_per_page, order_by, desc, id, title) return get_result(data=canvas) diff --git a/api/db/services/knowledgebase_service.py b/api/db/services/knowledgebase_service.py index fbd193509..8c22ffa9c 100644 --- a/api/db/services/knowledgebase_service.py +++ b/api/db/services/knowledgebase_service.py @@ -24,9 +24,9 @@ from common.time_utils import current_timestamp, datetime_format from api.db.services import duplicate_name from api.db.services.user_service import TenantService from common.misc_utils import get_uuid -from common.constants import StatusEnum +from common.constants import StatusEnum, RetCode from api.constants import DATASET_NAME_LIMIT -from api.utils.api_utils import get_parser_config, get_data_error_result +from api.utils.api_utils import get_parser_config class KnowledgebaseService(CommonService): """Service class for managing knowledge base operations. @@ -396,7 +396,7 @@ class KnowledgebaseService(CommonService): if dataset_name == "": return False, get_data_error_result(message="Dataset name can't be empty.") if len(dataset_name.encode("utf-8")) > DATASET_NAME_LIMIT: - return get_data_error_result(message=f"Dataset name length is {len(dataset_name)} which is larger than {DATASET_NAME_LIMIT}") + return {"code": RetCode.DATA_ERROR, "message": f"Dataset name length is {len(dataset_name)} which is larger than {DATASET_NAME_LIMIT}"} # Deduplicate name within tenant dataset_name = duplicate_name( @@ -419,10 +419,10 @@ class KnowledgebaseService(CommonService): "tenant_id": tenant_id, "created_by": tenant_id, "parser_id": (parser_id or "naive"), - **kwargs + **kwargs # Includes optional fields such as description, language, permission, avatar, parser_config, etc. } - # Default parser_config (align with kb_app.create) — do not accept external overrides + # Update parser_config (always override with validated default/merged config) payload["parser_config"] = get_parser_config(parser_id, kwargs.get("parser_config")) return True, payload diff --git a/api/db/services/llm_service.py b/api/db/services/llm_service.py index 6ccbf5a94..4d4ccaa57 100644 --- a/api/db/services/llm_service.py +++ b/api/db/services/llm_service.py @@ -19,6 +19,7 @@ import re from common.token_utils import num_tokens_from_string from functools import partial from typing import Generator +from common.constants import LLMType 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 @@ -32,6 +33,14 @@ def get_init_tenant_llm(user_id): from common import settings tenant_llm = [] + model_configs = { + LLMType.CHAT: settings.CHAT_CFG, + LLMType.EMBEDDING: settings.EMBEDDING_CFG, + LLMType.SPEECH2TEXT: settings.ASR_CFG, + LLMType.IMAGE2TEXT: settings.IMAGE2TEXT_CFG, + LLMType.RERANK: settings.RERANK_CFG, + } + seen = set() factory_configs = [] for factory_config in [ @@ -54,8 +63,8 @@ def get_init_tenant_llm(user_id): "llm_factory": factory_config["factory"], "llm_name": llm.llm_name, "model_type": llm.model_type, - "api_key": factory_config["api_key"], - "api_base": factory_config["base_url"], + "api_key": model_configs.get(llm.model_type, {}).get("api_key", factory_config["api_key"]), + "api_base": model_configs.get(llm.model_type, {}).get("base_url", factory_config["base_url"]), "max_tokens": llm.max_tokens if llm.max_tokens else 8192, } ) @@ -80,8 +89,8 @@ class LLMBundle(LLM4Tenant): def encode(self, texts: list): if self.langfuse: - generation = self.langfuse.start_generation(trace_context=self.trace_context, name="encode", model=self.llm_name, input={"texts": texts}) - + generation = self.langfuse.start_generation(trace_context=self.trace_context, name="encode", model=self.llm_name, input={"texts": texts}) + safe_texts = [] for text in texts: token_size = num_tokens_from_string(text) @@ -90,7 +99,7 @@ class LLMBundle(LLM4Tenant): safe_texts.append(text[:target_len]) else: safe_texts.append(text) - + embeddings, used_tokens = self.mdl.encode(safe_texts) llm_name = getattr(self, "llm_name", None) diff --git a/api/ragflow_server.py b/api/ragflow_server.py index 4accbb8a2..852372d0b 100644 --- a/api/ragflow_server.py +++ b/api/ragflow_server.py @@ -40,7 +40,7 @@ from api.db.db_models import init_database_tables as init_web_db from api.db.init_data import init_web_data from common.versions import get_ragflow_version from common.config_utils import show_configs -from rag.utils.mcp_tool_call_conn import shutdown_all_mcp_sessions +from common.mcp_tool_call_conn import shutdown_all_mcp_sessions from rag.utils.redis_conn import RedisDistributedLock stop_event = threading.Event() diff --git a/api/utils/api_utils.py b/api/utils/api_utils.py index bd35a6c69..20dc88086 100644 --- a/api/utils/api_utils.py +++ b/api/utils/api_utils.py @@ -36,7 +36,7 @@ from peewee import OperationalError from common.constants import ActiveEnum from api.db.db_models import APIToken from api.utils.json_encode import CustomJSONEncoder -from rag.utils.mcp_tool_call_conn import MCPToolCallSession, close_multiple_mcp_toolcall_sessions +from common.mcp_tool_call_conn import MCPToolCallSession, close_multiple_mcp_toolcall_sessions from api.db.services.tenant_llm_service import LLMFactoriesService from common.connection_utils import timeout from common.constants import RetCode diff --git a/common/data_source/interfaces.py b/common/data_source/interfaces.py index 9c5f00141..5e5d3aa2e 100644 --- a/common/data_source/interfaces.py +++ b/common/data_source/interfaces.py @@ -69,7 +69,7 @@ class SlimConnectorWithPermSync(ABC): class CheckpointedConnectorWithPermSync(ABC): - """Checkpointed connector interface (with permission sync)""" + """Checkpoint connector interface (with permission sync)""" @abstractmethod def load_from_checkpoint( @@ -143,7 +143,7 @@ class CredentialsProviderInterface(abc.ABC, Generic[T]): @abc.abstractmethod def is_dynamic(self) -> bool: - """If dynamic, the credentials may change during usage ... maening the client + """If dynamic, the credentials may change during usage ... meaning the client needs to use the locking features of the credentials provider to operate correctly. diff --git a/rag/utils/mcp_tool_call_conn.py b/common/mcp_tool_call_conn.py similarity index 94% rename from rag/utils/mcp_tool_call_conn.py rename to common/mcp_tool_call_conn.py index 2093f7bc8..b19f063e1 100644 --- a/rag/utils/mcp_tool_call_conn.py +++ b/common/mcp_tool_call_conn.py @@ -21,7 +21,7 @@ import weakref from concurrent.futures import ThreadPoolExecutor from concurrent.futures import TimeoutError as FuturesTimeoutError from string import Template -from typing import Any, Literal +from typing import Any, Literal, Protocol from typing_extensions import override @@ -30,12 +30,15 @@ from mcp.client.session import ClientSession from mcp.client.sse import sse_client from mcp.client.streamable_http import streamablehttp_client from mcp.types import CallToolResult, ListToolsResult, TextContent, Tool -from rag.llm.chat_model import ToolCallSession MCPTaskType = Literal["list_tools", "tool_call"] MCPTask = tuple[MCPTaskType, dict[str, Any], asyncio.Queue[Any]] +class ToolCallSession(Protocol): + def tool_call(self, name: str, arguments: dict[str, Any]) -> str: ... + + class MCPToolCallSession(ToolCallSession): _ALL_INSTANCES: weakref.WeakSet["MCPToolCallSession"] = weakref.WeakSet() @@ -106,7 +109,8 @@ class MCPToolCallSession(ToolCallSession): await self._process_mcp_tasks(None, msg) else: - await self._process_mcp_tasks(None, f"Unsupported MCP server type: {self._mcp_server.server_type}, id: {self._mcp_server.id}") + await self._process_mcp_tasks(None, + f"Unsupported MCP server type: {self._mcp_server.server_type}, id: {self._mcp_server.id}") async def _process_mcp_tasks(self, client_session: ClientSession | None, error_message: str | None = None) -> None: while not self._close: @@ -164,7 +168,8 @@ class MCPToolCallSession(ToolCallSession): raise async def _call_mcp_tool(self, name: str, arguments: dict[str, Any], timeout: float | int = 10) -> str: - result: CallToolResult = await self._call_mcp_server("tool_call", name=name, arguments=arguments, timeout=timeout) + result: CallToolResult = await self._call_mcp_server("tool_call", name=name, arguments=arguments, + timeout=timeout) if result.isError: return f"MCP server error: {result.content}" @@ -283,7 +288,8 @@ def close_multiple_mcp_toolcall_sessions(sessions: list[MCPToolCallSession]) -> except Exception: logging.exception("Exception during MCP session cleanup thread management") - logging.info(f"{len(sessions)} MCP sessions has been cleaned up. {len(list(MCPToolCallSession._ALL_INSTANCES))} in global context.") + logging.info( + f"{len(sessions)} MCP sessions has been cleaned up. {len(list(MCPToolCallSession._ALL_INSTANCES))} in global context.") def shutdown_all_mcp_sessions(): @@ -298,7 +304,7 @@ def shutdown_all_mcp_sessions(): logging.info("All MCPToolCallSession instances have been closed.") -def mcp_tool_metadata_to_openai_tool(mcp_tool: Tool|dict) -> dict[str, Any]: +def mcp_tool_metadata_to_openai_tool(mcp_tool: Tool | dict) -> dict[str, Any]: if isinstance(mcp_tool, dict): return { "type": "function", diff --git a/deepdoc/parser/mineru_parser.py b/deepdoc/parser/mineru_parser.py index bb663de0d..6d3b292d0 100644 --- a/deepdoc/parser/mineru_parser.py +++ b/deepdoc/parser/mineru_parser.py @@ -434,7 +434,7 @@ class MinerUParser(RAGFlowPdfParser): if not section.strip(): section = "FAILED TO PARSE TABLE" case MinerUContentType.IMAGE: - section = "".join(output.get(["image_caption"],[])) + "\n" + "".join(output.get(["image_footnote"],[])) + section = "".join(output.get("image_caption", [])) + "\n" + "".join(output.get("image_footnote", [])) case MinerUContentType.EQUATION: section = output["text"] case MinerUContentType.CODE: diff --git a/docs/guides/accessing_admin_ui.md b/docs/guides/accessing_admin_ui.md index 23521244b..181cff5ac 100644 --- a/docs/guides/accessing_admin_ui.md +++ b/docs/guides/accessing_admin_ui.md @@ -15,7 +15,7 @@ To access the RAGFlow admin UI, append `/admin` to the web UI's address, e.g. `h ### Default Credentials | Username | Password | |----------|----------| -| admin@ragflow.io | admin | +| `admin@ragflow.io` | `admin` | ## Admin UI Overview diff --git a/rag/llm/chat_model.py b/rag/llm/chat_model.py index 17ddbc138..c9e3b29f7 100644 --- a/rag/llm/chat_model.py +++ b/rag/llm/chat_model.py @@ -22,7 +22,6 @@ import re import time from abc import ABC from copy import deepcopy -from typing import Any, Protocol from urllib.parse import urljoin import json_repair @@ -65,10 +64,6 @@ LENGTH_NOTIFICATION_CN = "······\n由于大模型的上下文窗口大小 LENGTH_NOTIFICATION_EN = "...\nThe answer is truncated by your chosen LLM due to its limitation on context length." -class ToolCallSession(Protocol): - def tool_call(self, name: str, arguments: dict[str, Any]) -> str: ... - - class Base(ABC): def __init__(self, key, model_name, base_url, **kwargs): timeout = int(os.environ.get("LM_TIMEOUT_SECONDS", 600)) diff --git a/rag/nlp/__init__.py b/rag/nlp/__init__.py index 80acf1d8f..de7c2ce60 100644 --- a/rag/nlp/__init__.py +++ b/rag/nlp/__init__.py @@ -155,13 +155,13 @@ def qbullets_category(sections): if re.match(pro, sec) and not not_bullet(sec): hits[i] += 1 break - maxium = 0 + maximum = 0 res = -1 for i, h in enumerate(hits): - if h <= maxium: + if h <= maximum: continue res = i - maxium = h + maximum = h return res, QUESTION_PATTERN[res] @@ -222,13 +222,13 @@ def bullets_category(sections): if re.match(p, sec) and not not_bullet(sec): hits[i] += 1 break - maxium = 0 + maximum = 0 res = -1 for i, h in enumerate(hits): - if h <= maxium: + if h <= maximum: continue res = i - maxium = h + maximum = h return res diff --git a/web/src/components/ui/modal/modal.tsx b/web/src/components/ui/modal/modal.tsx index acae6c147..af516b1e6 100644 --- a/web/src/components/ui/modal/modal.tsx +++ b/web/src/components/ui/modal/modal.tsx @@ -86,6 +86,9 @@ const Modal: ModalType = ({ onOk?.(); }, [onOk, onOpenChange]); const handleChange = (open: boolean) => { + if (!open && !maskClosable) { + return; + } onOpenChange?.(open); console.log('open', open, onOpenChange); if (open && !disabled) { @@ -185,6 +188,7 @@ const Modal: ModalType = ({ diff --git a/web/src/pages/user-setting/profile/index.tsx b/web/src/pages/user-setting/profile/index.tsx index dceb2cdf3..5c2741cf6 100644 --- a/web/src/pages/user-setting/profile/index.tsx +++ b/web/src/pages/user-setting/profile/index.tsx @@ -13,13 +13,7 @@ import { } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Modal } from '@/components/ui/modal/modal'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; +import { RAGFlowSelect } from '@/components/ui/select'; import { useTranslate } from '@/hooks/common-hooks'; import { TimezoneList } from '@/pages/user-setting/constants'; import { zodResolver } from '@hookform/resolvers/zod'; @@ -230,6 +224,7 @@ const ProfilePage: FC = () => { title={modalTitle[editType]} open={isEditing} showfooter={false} + maskClosable={false} titleClassName="text-base" onOpenChange={(open) => { if (!open) { @@ -281,23 +276,14 @@ const ProfilePage: FC = () => { {t('timezone')} - + />