diff --git a/backend/api/chat_router.py b/backend/api/chat_router.py index 9376aa6..c038292 100644 --- a/backend/api/chat_router.py +++ b/backend/api/chat_router.py @@ -632,6 +632,29 @@ async def chat_completion( ) +def _wrap_mcp_tool_safe(tool): + """ + 包装 MCP 工具,确保第三方服务异常(网络不可达、超时等)被捕获并以 + 错误字符串返回,而非向上抛出。 + + 这样 LangChain Agent 在工具调用失败时仍能写入对应的 ToolMessage, + 保持 checkpoint 中消息历史的完整性,避免下次对话因 tool_calls 缺少 + 配对回复而触发 LLM 400 BadRequest 错误。 + """ + original_arun = tool._arun + + async def safe_arun(*args, **kwargs): + try: + return await original_arun(*args, **kwargs) + except Exception as exc: + error_msg = f"工具 [{tool.name}] 调用失败: {exc}" + logger.warning(f"MCP 工具异常已捕获,返回错误字符串以维持对话完整性: {error_msg}") + return error_msg + + tool._arun = safe_arun + return tool + + async def _create_agent_for_request( request: ChatRequest, current_user: User, @@ -699,6 +722,7 @@ async def _create_agent_for_request( # 普通聊天模式 mcp_client = await get_mcp_client() mcp_tools = await mcp_client.get_tools() + # mcp_tools = [_wrap_mcp_tool_safe(t) for t in mcp_tools] logger.info(f"成功加载 {len(mcp_tools)} 个 MCP 工具") # 查询用户设置(深度思考是否与模型侧一致取决于 user_list.is_reasoner, diff --git a/backend/api/knowledge_graph_router.py b/backend/api/knowledge_graph_router.py index de50a00..b9f180b 100644 --- a/backend/api/knowledge_graph_router.py +++ b/backend/api/knowledge_graph_router.py @@ -277,7 +277,7 @@ async def _fetch_graph_or_404(conn: asyncpg.Connection, graph_pk: int, user: Use creator_id=raw.get("creator_id"), visibility=raw.get("visibility") or "private", ) - if not can_view_graph(user, gr): + if not await can_view_graph(conn, user, gr): raise HTTPException(status_code=404, detail="图谱不存在或无权访问") return raw diff --git a/backend/core/permissions.py b/backend/core/permissions.py index 03a45d4..a63e968 100644 --- a/backend/core/permissions.py +++ b/backend/core/permissions.py @@ -159,17 +159,19 @@ async def can_upload_to_kb(conn: asyncpg.Connection, user: User, kb: KnowledgeBa # ────────────────────────────────────────────── -# 知识图谱级权限(保持不变) +# 知识图谱级权限(与知识库规则完全一致) # ────────────────────────────────────────────── -def can_view_graph(user: User, g: GraphRecord) -> bool: - """判断用户是否可查看该知识图谱(规则与知识库一致)。""" +async def can_view_graph(conn: asyncpg.Connection, user: User, g: GraphRecord) -> bool: + """判断用户是否可查看该知识图谱。leader 权限覆盖本部门及所有子孙部门(与知识库一致)。""" if user.role == "admin": return True if g.creator_id is not None and user.id == g.creator_id: return True - if user.role == "leader" and user.department_id is not None and g.department_id == user.department_id: - return True + if user.role == "leader" and user.department_id is not None and g.department_id is not None: + managed = await get_managed_dept_ids(conn, user) + if g.department_id in managed: + return True vis = g.visibility or "private" if vis == "private": return False diff --git a/backend/services/chat_thread_service.py b/backend/services/chat_thread_service.py index 93f5e9f..917186c 100644 --- a/backend/services/chat_thread_service.py +++ b/backend/services/chat_thread_service.py @@ -601,7 +601,7 @@ async def check_knowledge_graph_has_rag(knowledge_graph_id: int, user: User) -> creator_id=raw.get("creator_id"), visibility=raw.get("visibility") or "private", ) - if not can_view_graph(user, gr): + if not await can_view_graph(conn, user, gr): return False return ( raw.get("build_status") == "completed" @@ -633,7 +633,7 @@ async def get_knowledge_graph_tool_flags(user: User, graph_id: int) -> Dict[str, creator_id=raw.get("creator_id"), visibility=raw.get("visibility") or "private", ) - if not can_view_graph(user, gr): + if not await can_view_graph(conn, user, gr): return out if raw.get("build_status") != "completed": return out diff --git a/backend/services/knowledge_graph_service.py b/backend/services/knowledge_graph_service.py index 6d45b51..05f85a8 100644 --- a/backend/services/knowledge_graph_service.py +++ b/backend/services/knowledge_graph_service.py @@ -8,7 +8,7 @@ from typing import Any, Dict, List, Optional, Tuple import asyncpg from core.graph_metadata import graph_table_sql -from core.permissions import can_manage_graph, can_view_graph +from core.permissions import can_manage_graph, can_view_graph, get_managed_dept_ids from models.graph_metadata import GraphRecord from models.user import User from logger.logging import get_logger @@ -95,13 +95,18 @@ class KnowledgeGraphService: dept_id = user.department_id uid = user.id + # leader 需要获取本部门及所有子孙部门 ID,与知识库列表保持一致 + managed_dept_ids: List[int] = [] + if role == "leader" and dept_id is not None: + managed_dept_ids = await get_managed_dept_ids(conn, user) + where_sql = """ g.enterprise_id = $1 AND ( $2::text = 'admin' OR g.creator_id = $3 - OR ($2::text = 'leader' AND g.department_id IS NOT NULL AND g.department_id = $4) - OR (g.visibility = 'department' AND g.department_id IS NOT NULL AND g.department_id = $4) + OR ($2::text = 'leader' AND g.department_id IS NOT NULL AND g.department_id = ANY($4::int[])) + OR (g.visibility = 'department' AND g.department_id IS NOT NULL AND g.department_id = $5) OR (g.visibility = 'enterprise') ) """ @@ -114,6 +119,7 @@ class KnowledgeGraphService: enterprise_id, role, uid, + managed_dept_ids, dept_id, ) @@ -131,11 +137,12 @@ class KnowledgeGraphService: LEFT JOIN department d ON d.id = g.department_id WHERE {where_sql} ORDER BY g.created_at DESC - LIMIT $5 OFFSET $6 + LIMIT $6 OFFSET $7 """, enterprise_id, role, uid, + managed_dept_ids, dept_id, page_size, offset, @@ -182,6 +189,6 @@ class KnowledgeGraphService: gr = KnowledgeGraphService._row_to_graph_record(raw) except Exception: return None - if not can_view_graph(user, gr): + if not await can_view_graph(conn, user, gr): return None return await KnowledgeGraphService.enrich_graph_for_response(conn, raw, user)