From 8422a05d74d5ebab024eb2181005c88e5aa609fb Mon Sep 17 00:00:00 2001 From: lanceyq <1982376970@qq.com> Date: Thu, 5 Mar 2026 17:22:18 +0800 Subject: [PATCH 1/7] [add] Added checks for idempotency of the ontology project --- api/app/services/workspace_service.py | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/api/app/services/workspace_service.py b/api/app/services/workspace_service.py index 2f8cdc70..e93c0c5c 100644 --- a/api/app/services/workspace_service.py +++ b/api/app/services/workspace_service.py @@ -107,6 +107,7 @@ def get_user_workspaces(db: Session, user: User) -> List[Workspace]: for workspace in workspaces: if workspace.storage_type == 'neo4j': _ensure_default_memory_config(db, workspace) + _ensure_default_ontology_scenes(db, workspace) business_logger.info(f"用户 {user.username} 的工作空间数量: {len(workspaces)}") return workspaces @@ -1104,6 +1105,52 @@ def _fill_workspace_configs_model_defaults( ) +def _ensure_default_ontology_scenes(db: Session, workspace: Workspace) -> None: + """Ensure a workspace has default ontology scenes, creating them if missing. + + Checks whether any is_system_default scene exists for the workspace. + If not, runs the DefaultOntologyInitializer to create them. + + Args: + db: Database session + workspace: The workspace to check + """ + from app.models.ontology_scene import OntologyScene + + # 幂等检查:是否已存在系统默认场景 + existing = db.query(OntologyScene).filter( + OntologyScene.workspace_id == workspace.id, + OntologyScene.is_system_default.is_(True) + ).first() + + if existing: + return + + business_logger.info( + f"Workspace {workspace.id} missing default ontology scenes, creating them" + ) + + try: + initializer = DefaultOntologyInitializer(db) + success, error_msg = initializer.initialize_default_scenes( + workspace.id, language="zh" + ) + if success: + db.commit() + business_logger.info( + f"为工作空间 {workspace.id} 补建默认本体场景成功" + ) + else: + business_logger.warning( + f"为工作空间 {workspace.id} 补建默认本体场景失败: {error_msg}" + ) + except Exception as e: + db.rollback() + business_logger.error( + f"为工作空间 {workspace.id} 补建默认本体场景异常: {str(e)}" + ) + + def _create_default_memory_config( db: Session, workspace_id: uuid.UUID, From a2ed335e59cd14034947f1ff16959ddaf7faed3f Mon Sep 17 00:00:00 2001 From: lanceyq <1982376970@qq.com> Date: Thu, 5 Mar 2026 18:04:46 +0800 Subject: [PATCH 2/7] [add] Repeatability test --- api/app/controllers/ontology_controller.py | 24 ++++++++++++++----- .../controllers/ontology_secondary_routes.py | 21 ++++++++++++---- api/app/models/ontology_class.py | 5 +++- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/api/app/controllers/ontology_controller.py b/api/app/controllers/ontology_controller.py index c892b013..3d2a1bdb 100644 --- a/api/app/controllers/ontology_controller.py +++ b/api/app/controllers/ontology_controller.py @@ -25,7 +25,7 @@ from typing import Dict, Optional, List from urllib.parse import quote from fastapi import APIRouter, Depends, HTTPException, File, UploadFile, Form, Header -from fastapi.responses import StreamingResponse +from fastapi.responses import StreamingResponse, JSONResponse from sqlalchemy.orm import Session from app.core.config import settings @@ -289,7 +289,8 @@ async def extract_ontology( async def create_scene( request: SceneCreateRequest, db: Session = Depends(get_db), - current_user: User = Depends(get_current_user) + current_user: User = Depends(get_current_user), + x_language_type: Optional[str] = Header(None, alias="X-Language-Type") ): """创建本体场景 @@ -360,8 +361,18 @@ async def create_scene( return fail(BizCode.BAD_REQUEST, "请求参数无效", str(e)) except RuntimeError as e: - api_logger.error(f"Runtime error in scene creation: {str(e)}", exc_info=True) - return fail(BizCode.INTERNAL_ERROR, "场景创建失败", str(e)) + err_str = str(e) + if "UniqueViolation" in err_str or "uq_workspace_scene_name" in err_str: + api_logger.warning(f"Duplicate scene name '{request.scene_name}' in workspace {current_user.current_workspace_id}") + from app.core.language_utils import get_language_from_header + lang = get_language_from_header(x_language_type) + if lang == "en": + msg = fail(BizCode.BAD_REQUEST, "Scene name already exists", f"A scene named \"{request.scene_name}\" already exists in the current workspace. Please use a different name.") + else: + msg = fail(BizCode.BAD_REQUEST, "场景名称已存在", f"当前工作空间下已存在名为「{request.scene_name}」的场景,请使用其他名称") + return JSONResponse(status_code=400, content=msg) + api_logger.error(f"Runtime error in scene creation: {err_str}", exc_info=True) + return fail(BizCode.INTERNAL_ERROR, "场景创建失败", err_str) except Exception as e: api_logger.error(f"Unexpected error in scene creation: {str(e)}", exc_info=True) @@ -661,7 +672,8 @@ async def get_scenes( async def create_class( request: ClassCreateRequest, db: Session = Depends(get_db), - current_user: User = Depends(get_current_user) + current_user: User = Depends(get_current_user), + x_language_type: Optional[str] = Header(None, alias="X-Language-Type") ): """创建本体类型 @@ -676,7 +688,7 @@ async def create_class( ApiResponse: 包含创建的类型信息 """ from app.controllers.ontology_secondary_routes import create_class_handler - return await create_class_handler(request, db, current_user) + return await create_class_handler(request, db, current_user, x_language_type) @router.put("/class/{class_id}", response_model=ApiResponse) diff --git a/api/app/controllers/ontology_secondary_routes.py b/api/app/controllers/ontology_secondary_routes.py index 607a0739..a0609605 100644 --- a/api/app/controllers/ontology_secondary_routes.py +++ b/api/app/controllers/ontology_secondary_routes.py @@ -7,7 +7,7 @@ from uuid import UUID from typing import Optional -from fastapi import Depends +from fastapi import Depends, Header from sqlalchemy.orm import Session from app.core.error_codes import BizCode @@ -238,7 +238,8 @@ async def scenes_handler( async def create_class_handler( request: ClassCreateRequest, db: Session = Depends(get_db), - current_user: User = Depends(get_current_user) + current_user: User = Depends(get_current_user), + x_language_type: Optional[str] = None ): """创建本体类型(统一使用列表形式,支持单个或批量)""" @@ -334,8 +335,20 @@ async def create_class_handler( return fail(BizCode.BAD_REQUEST, "请求参数无效", str(e)) except RuntimeError as e: - api_logger.error(f"Runtime error in class creation: {str(e)}", exc_info=True) - return fail(BizCode.INTERNAL_ERROR, "类型创建失败", str(e)) + err_str = str(e) + if "UniqueViolation" in err_str or "uq_scene_class_name" in err_str: + api_logger.warning(f"Duplicate class name in scene {request.scene_id}") + from app.core.language_utils import get_language_from_header + from fastapi.responses import JSONResponse + lang = get_language_from_header(x_language_type) + class_name = request.classes[0].class_name if request.classes else "" + if lang == "en": + msg = fail(BizCode.BAD_REQUEST, "Class name already exists", f"A class named \"{class_name}\" already exists in this scene. Please use a different name.") + else: + msg = fail(BizCode.BAD_REQUEST, "类型名称已存在", f"当前场景下已存在名为「{class_name}」的类型,请使用其他名称") + return JSONResponse(status_code=400, content=msg) + api_logger.error(f"Runtime error in class creation: {err_str}", exc_info=True) + return fail(BizCode.INTERNAL_ERROR, "类型创建失败", err_str) except Exception as e: api_logger.error(f"Unexpected error in class creation: {str(e)}", exc_info=True) diff --git a/api/app/models/ontology_class.py b/api/app/models/ontology_class.py index a8468090..eb38d06f 100644 --- a/api/app/models/ontology_class.py +++ b/api/app/models/ontology_class.py @@ -9,7 +9,7 @@ Classes: import datetime import uuid -from sqlalchemy import Column, String, DateTime, Text, ForeignKey, Boolean +from sqlalchemy import Column, String, DateTime, Text, ForeignKey, Boolean, UniqueConstraint from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship from app.db import Base @@ -18,6 +18,9 @@ from app.db import Base class OntologyClass(Base): """本体类型表 - 用于存储某个场景提取出来的本体类型信息""" __tablename__ = "ontology_class" + __table_args__ = ( + UniqueConstraint('scene_id', 'class_name', name='uq_scene_class_name'), + ) # 主键 class_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True, comment="类型ID") From 71fe35533dbc531d54a83c177b54dc4d79cc7f18 Mon Sep 17 00:00:00 2001 From: lanceyq <1982376970@qq.com> Date: Thu, 5 Mar 2026 18:15:31 +0800 Subject: [PATCH 3/7] [add] Memory configuration adds uniqueness detection --- api/app/controllers/memory_storage_controller.py | 16 +++++++++++++++- api/app/models/memory_config_model.py | 5 ++++- api/app/services/memory_storage_service.py | 11 +++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/api/app/controllers/memory_storage_controller.py b/api/app/controllers/memory_storage_controller.py index 826724c9..288b4265 100644 --- a/api/app/controllers/memory_storage_controller.py +++ b/api/app/controllers/memory_storage_controller.py @@ -2,7 +2,7 @@ from typing import Optional from uuid import UUID from fastapi import APIRouter, Depends, Query -from fastapi.responses import StreamingResponse +from fastapi.responses import StreamingResponse, JSONResponse from sqlalchemy.orm import Session from app.core.error_codes import BizCode @@ -85,6 +85,7 @@ def create_config( payload: ConfigParamsCreate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db), + x_language_type: Optional[str] = Header(None, alias="X-Language-Type"), ) -> dict: workspace_id = current_user.current_workspace_id # 检查用户是否已选择工作空间 @@ -99,6 +100,19 @@ def create_config( svc = DataConfigService(db) result = svc.create(payload) return success(data=result, msg="创建成功") + except ValueError as e: + err_str = str(e) + if err_str.startswith("DUPLICATE_CONFIG_NAME:"): + config_name = err_str.split(":", 1)[1] + api_logger.warning(f"重复的配置名称 '{config_name}' 在工作空间 {workspace_id}") + lang = get_language_from_header(x_language_type) + if lang == "en": + msg = fail(BizCode.BAD_REQUEST, "Config name already exists", f"A config named \"{config_name}\" already exists in the current workspace. Please use a different name.") + else: + msg = fail(BizCode.BAD_REQUEST, "配置名称已存在", f"当前工作空间下已存在名为「{config_name}」的记忆配置,请使用其他名称") + return JSONResponse(status_code=400, content=msg) + api_logger.error(f"Create config failed: {err_str}") + return fail(BizCode.INTERNAL_ERROR, "创建配置失败", err_str) except Exception as e: api_logger.error(f"Create config failed: {str(e)}") return fail(BizCode.INTERNAL_ERROR, "创建配置失败", str(e)) diff --git a/api/app/models/memory_config_model.py b/api/app/models/memory_config_model.py index 1095a386..b4b441d5 100644 --- a/api/app/models/memory_config_model.py +++ b/api/app/models/memory_config_model.py @@ -1,6 +1,6 @@ import datetime -from sqlalchemy import Boolean, Column, DateTime, Float, Integer, String +from sqlalchemy import Boolean, Column, DateTime, Float, Integer, String, UniqueConstraint from sqlalchemy.dialects.postgresql import UUID from app.db import Base @@ -9,6 +9,9 @@ from app.db import Base class MemoryConfig(Base): """记忆配置表 - 用于存储记忆系统的配置参数""" __tablename__ = "memory_config" + __table_args__ = ( + UniqueConstraint('workspace_id', 'config_name', name='uq_workspace_config_name'), + ) # 主键 config_id = Column(UUID(as_uuid=True), primary_key=True, comment="配置ID") diff --git a/api/app/services/memory_storage_service.py b/api/app/services/memory_storage_service.py index 1083f750..6546a143 100644 --- a/api/app/services/memory_storage_service.py +++ b/api/app/services/memory_storage_service.py @@ -115,6 +115,17 @@ class DataConfigService: # 数据配置服务类(PostgreSQL) # --- Create --- def create(self, params: ConfigParamsCreate) -> Dict[str, Any]: # 创建配置参数(仅名称与描述) + # 检查同一工作空间下是否已存在同名配置 + if params.workspace_id and params.config_name: + from app.models.memory_config_model import MemoryConfig + existing = ( + self.db.query(MemoryConfig) + .filter_by(workspace_id=params.workspace_id, config_name=params.config_name) + .first() + ) + if existing: + raise ValueError(f"DUPLICATE_CONFIG_NAME:{params.config_name}") + # 如果workspace_id存在且模型字段未全部指定,则自动获取 if params.workspace_id and not all([params.llm_id, params.embedding_id, params.rerank_id]): configs = self._get_workspace_configs(params.workspace_id) From a1fc0fd3949720bbb2a35f58c36eb05e14af3757 Mon Sep 17 00:00:00 2001 From: lanceyq <1982376970@qq.com> Date: Thu, 5 Mar 2026 17:22:18 +0800 Subject: [PATCH 4/7] [add] Added checks for idempotency of the ontology project --- api/app/services/workspace_service.py | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/api/app/services/workspace_service.py b/api/app/services/workspace_service.py index 2f8cdc70..e93c0c5c 100644 --- a/api/app/services/workspace_service.py +++ b/api/app/services/workspace_service.py @@ -107,6 +107,7 @@ def get_user_workspaces(db: Session, user: User) -> List[Workspace]: for workspace in workspaces: if workspace.storage_type == 'neo4j': _ensure_default_memory_config(db, workspace) + _ensure_default_ontology_scenes(db, workspace) business_logger.info(f"用户 {user.username} 的工作空间数量: {len(workspaces)}") return workspaces @@ -1104,6 +1105,52 @@ def _fill_workspace_configs_model_defaults( ) +def _ensure_default_ontology_scenes(db: Session, workspace: Workspace) -> None: + """Ensure a workspace has default ontology scenes, creating them if missing. + + Checks whether any is_system_default scene exists for the workspace. + If not, runs the DefaultOntologyInitializer to create them. + + Args: + db: Database session + workspace: The workspace to check + """ + from app.models.ontology_scene import OntologyScene + + # 幂等检查:是否已存在系统默认场景 + existing = db.query(OntologyScene).filter( + OntologyScene.workspace_id == workspace.id, + OntologyScene.is_system_default.is_(True) + ).first() + + if existing: + return + + business_logger.info( + f"Workspace {workspace.id} missing default ontology scenes, creating them" + ) + + try: + initializer = DefaultOntologyInitializer(db) + success, error_msg = initializer.initialize_default_scenes( + workspace.id, language="zh" + ) + if success: + db.commit() + business_logger.info( + f"为工作空间 {workspace.id} 补建默认本体场景成功" + ) + else: + business_logger.warning( + f"为工作空间 {workspace.id} 补建默认本体场景失败: {error_msg}" + ) + except Exception as e: + db.rollback() + business_logger.error( + f"为工作空间 {workspace.id} 补建默认本体场景异常: {str(e)}" + ) + + def _create_default_memory_config( db: Session, workspace_id: uuid.UUID, From 418844310149d574c2265b896a71e3c02dc89c83 Mon Sep 17 00:00:00 2001 From: lanceyq <1982376970@qq.com> Date: Thu, 5 Mar 2026 18:04:46 +0800 Subject: [PATCH 5/7] [add] Repeatability test --- api/app/controllers/ontology_controller.py | 24 ++++++++++++++----- .../controllers/ontology_secondary_routes.py | 21 ++++++++++++---- api/app/models/ontology_class.py | 5 +++- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/api/app/controllers/ontology_controller.py b/api/app/controllers/ontology_controller.py index c892b013..3d2a1bdb 100644 --- a/api/app/controllers/ontology_controller.py +++ b/api/app/controllers/ontology_controller.py @@ -25,7 +25,7 @@ from typing import Dict, Optional, List from urllib.parse import quote from fastapi import APIRouter, Depends, HTTPException, File, UploadFile, Form, Header -from fastapi.responses import StreamingResponse +from fastapi.responses import StreamingResponse, JSONResponse from sqlalchemy.orm import Session from app.core.config import settings @@ -289,7 +289,8 @@ async def extract_ontology( async def create_scene( request: SceneCreateRequest, db: Session = Depends(get_db), - current_user: User = Depends(get_current_user) + current_user: User = Depends(get_current_user), + x_language_type: Optional[str] = Header(None, alias="X-Language-Type") ): """创建本体场景 @@ -360,8 +361,18 @@ async def create_scene( return fail(BizCode.BAD_REQUEST, "请求参数无效", str(e)) except RuntimeError as e: - api_logger.error(f"Runtime error in scene creation: {str(e)}", exc_info=True) - return fail(BizCode.INTERNAL_ERROR, "场景创建失败", str(e)) + err_str = str(e) + if "UniqueViolation" in err_str or "uq_workspace_scene_name" in err_str: + api_logger.warning(f"Duplicate scene name '{request.scene_name}' in workspace {current_user.current_workspace_id}") + from app.core.language_utils import get_language_from_header + lang = get_language_from_header(x_language_type) + if lang == "en": + msg = fail(BizCode.BAD_REQUEST, "Scene name already exists", f"A scene named \"{request.scene_name}\" already exists in the current workspace. Please use a different name.") + else: + msg = fail(BizCode.BAD_REQUEST, "场景名称已存在", f"当前工作空间下已存在名为「{request.scene_name}」的场景,请使用其他名称") + return JSONResponse(status_code=400, content=msg) + api_logger.error(f"Runtime error in scene creation: {err_str}", exc_info=True) + return fail(BizCode.INTERNAL_ERROR, "场景创建失败", err_str) except Exception as e: api_logger.error(f"Unexpected error in scene creation: {str(e)}", exc_info=True) @@ -661,7 +672,8 @@ async def get_scenes( async def create_class( request: ClassCreateRequest, db: Session = Depends(get_db), - current_user: User = Depends(get_current_user) + current_user: User = Depends(get_current_user), + x_language_type: Optional[str] = Header(None, alias="X-Language-Type") ): """创建本体类型 @@ -676,7 +688,7 @@ async def create_class( ApiResponse: 包含创建的类型信息 """ from app.controllers.ontology_secondary_routes import create_class_handler - return await create_class_handler(request, db, current_user) + return await create_class_handler(request, db, current_user, x_language_type) @router.put("/class/{class_id}", response_model=ApiResponse) diff --git a/api/app/controllers/ontology_secondary_routes.py b/api/app/controllers/ontology_secondary_routes.py index 600aacc7..0d752006 100644 --- a/api/app/controllers/ontology_secondary_routes.py +++ b/api/app/controllers/ontology_secondary_routes.py @@ -7,7 +7,7 @@ from uuid import UUID from typing import Optional -from fastapi import Depends +from fastapi import Depends, Header from sqlalchemy.orm import Session from app.core.error_codes import BizCode @@ -231,7 +231,8 @@ async def scenes_handler( async def create_class_handler( request: ClassCreateRequest, db: Session = Depends(get_db), - current_user: User = Depends(get_current_user) + current_user: User = Depends(get_current_user), + x_language_type: Optional[str] = None ): """创建本体类型(统一使用列表形式,支持单个或批量)""" @@ -327,8 +328,20 @@ async def create_class_handler( return fail(BizCode.BAD_REQUEST, "请求参数无效", str(e)) except RuntimeError as e: - api_logger.error(f"Runtime error in class creation: {str(e)}", exc_info=True) - return fail(BizCode.INTERNAL_ERROR, "类型创建失败", str(e)) + err_str = str(e) + if "UniqueViolation" in err_str or "uq_scene_class_name" in err_str: + api_logger.warning(f"Duplicate class name in scene {request.scene_id}") + from app.core.language_utils import get_language_from_header + from fastapi.responses import JSONResponse + lang = get_language_from_header(x_language_type) + class_name = request.classes[0].class_name if request.classes else "" + if lang == "en": + msg = fail(BizCode.BAD_REQUEST, "Class name already exists", f"A class named \"{class_name}\" already exists in this scene. Please use a different name.") + else: + msg = fail(BizCode.BAD_REQUEST, "类型名称已存在", f"当前场景下已存在名为「{class_name}」的类型,请使用其他名称") + return JSONResponse(status_code=400, content=msg) + api_logger.error(f"Runtime error in class creation: {err_str}", exc_info=True) + return fail(BizCode.INTERNAL_ERROR, "类型创建失败", err_str) except Exception as e: api_logger.error(f"Unexpected error in class creation: {str(e)}", exc_info=True) diff --git a/api/app/models/ontology_class.py b/api/app/models/ontology_class.py index a8468090..eb38d06f 100644 --- a/api/app/models/ontology_class.py +++ b/api/app/models/ontology_class.py @@ -9,7 +9,7 @@ Classes: import datetime import uuid -from sqlalchemy import Column, String, DateTime, Text, ForeignKey, Boolean +from sqlalchemy import Column, String, DateTime, Text, ForeignKey, Boolean, UniqueConstraint from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship from app.db import Base @@ -18,6 +18,9 @@ from app.db import Base class OntologyClass(Base): """本体类型表 - 用于存储某个场景提取出来的本体类型信息""" __tablename__ = "ontology_class" + __table_args__ = ( + UniqueConstraint('scene_id', 'class_name', name='uq_scene_class_name'), + ) # 主键 class_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True, comment="类型ID") From 7afe5072961c085a647439b9a9a2a47b540e5ff9 Mon Sep 17 00:00:00 2001 From: lanceyq <1982376970@qq.com> Date: Thu, 5 Mar 2026 18:15:31 +0800 Subject: [PATCH 6/7] [add] Memory configuration adds uniqueness detection --- api/app/controllers/memory_storage_controller.py | 16 +++++++++++++++- api/app/models/memory_config_model.py | 5 ++++- api/app/services/memory_storage_service.py | 11 +++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/api/app/controllers/memory_storage_controller.py b/api/app/controllers/memory_storage_controller.py index 826724c9..288b4265 100644 --- a/api/app/controllers/memory_storage_controller.py +++ b/api/app/controllers/memory_storage_controller.py @@ -2,7 +2,7 @@ from typing import Optional from uuid import UUID from fastapi import APIRouter, Depends, Query -from fastapi.responses import StreamingResponse +from fastapi.responses import StreamingResponse, JSONResponse from sqlalchemy.orm import Session from app.core.error_codes import BizCode @@ -85,6 +85,7 @@ def create_config( payload: ConfigParamsCreate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db), + x_language_type: Optional[str] = Header(None, alias="X-Language-Type"), ) -> dict: workspace_id = current_user.current_workspace_id # 检查用户是否已选择工作空间 @@ -99,6 +100,19 @@ def create_config( svc = DataConfigService(db) result = svc.create(payload) return success(data=result, msg="创建成功") + except ValueError as e: + err_str = str(e) + if err_str.startswith("DUPLICATE_CONFIG_NAME:"): + config_name = err_str.split(":", 1)[1] + api_logger.warning(f"重复的配置名称 '{config_name}' 在工作空间 {workspace_id}") + lang = get_language_from_header(x_language_type) + if lang == "en": + msg = fail(BizCode.BAD_REQUEST, "Config name already exists", f"A config named \"{config_name}\" already exists in the current workspace. Please use a different name.") + else: + msg = fail(BizCode.BAD_REQUEST, "配置名称已存在", f"当前工作空间下已存在名为「{config_name}」的记忆配置,请使用其他名称") + return JSONResponse(status_code=400, content=msg) + api_logger.error(f"Create config failed: {err_str}") + return fail(BizCode.INTERNAL_ERROR, "创建配置失败", err_str) except Exception as e: api_logger.error(f"Create config failed: {str(e)}") return fail(BizCode.INTERNAL_ERROR, "创建配置失败", str(e)) diff --git a/api/app/models/memory_config_model.py b/api/app/models/memory_config_model.py index 1095a386..b4b441d5 100644 --- a/api/app/models/memory_config_model.py +++ b/api/app/models/memory_config_model.py @@ -1,6 +1,6 @@ import datetime -from sqlalchemy import Boolean, Column, DateTime, Float, Integer, String +from sqlalchemy import Boolean, Column, DateTime, Float, Integer, String, UniqueConstraint from sqlalchemy.dialects.postgresql import UUID from app.db import Base @@ -9,6 +9,9 @@ from app.db import Base class MemoryConfig(Base): """记忆配置表 - 用于存储记忆系统的配置参数""" __tablename__ = "memory_config" + __table_args__ = ( + UniqueConstraint('workspace_id', 'config_name', name='uq_workspace_config_name'), + ) # 主键 config_id = Column(UUID(as_uuid=True), primary_key=True, comment="配置ID") diff --git a/api/app/services/memory_storage_service.py b/api/app/services/memory_storage_service.py index beedaae9..71f4ff07 100644 --- a/api/app/services/memory_storage_service.py +++ b/api/app/services/memory_storage_service.py @@ -115,6 +115,17 @@ class DataConfigService: # 数据配置服务类(PostgreSQL) # --- Create --- def create(self, params: ConfigParamsCreate) -> Dict[str, Any]: # 创建配置参数(仅名称与描述) + # 检查同一工作空间下是否已存在同名配置 + if params.workspace_id and params.config_name: + from app.models.memory_config_model import MemoryConfig + existing = ( + self.db.query(MemoryConfig) + .filter_by(workspace_id=params.workspace_id, config_name=params.config_name) + .first() + ) + if existing: + raise ValueError(f"DUPLICATE_CONFIG_NAME:{params.config_name}") + # 如果workspace_id存在且模型字段未全部指定,则自动获取 if params.workspace_id and not all([params.llm_id, params.embedding_id, params.rerank_id]): configs = self._get_workspace_configs(params.workspace_id) From d052c31ac509c23a988410e8186f6cec9a709a24 Mon Sep 17 00:00:00 2001 From: lanceyq <1982376970@qq.com> Date: Thu, 5 Mar 2026 18:36:12 +0800 Subject: [PATCH 7/7] [changes] The pre-query at the service layer has been removed. The DB constraint ensures a unique single source of truth. --- api/app/controllers/memory_storage_controller.py | 16 ++++++---------- api/app/services/memory_storage_service.py | 11 ----------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/api/app/controllers/memory_storage_controller.py b/api/app/controllers/memory_storage_controller.py index 288b4265..9708b3a5 100644 --- a/api/app/controllers/memory_storage_controller.py +++ b/api/app/controllers/memory_storage_controller.py @@ -100,20 +100,16 @@ def create_config( svc = DataConfigService(db) result = svc.create(payload) return success(data=result, msg="创建成功") - except ValueError as e: - err_str = str(e) - if err_str.startswith("DUPLICATE_CONFIG_NAME:"): - config_name = err_str.split(":", 1)[1] - api_logger.warning(f"重复的配置名称 '{config_name}' 在工作空间 {workspace_id}") + except Exception as e: + from sqlalchemy.exc import IntegrityError + if isinstance(e, IntegrityError) and "uq_workspace_config_name" in str(getattr(e, 'orig', '')): + api_logger.warning(f"重复的配置名称 '{payload.config_name}' 在工作空间 {workspace_id}") lang = get_language_from_header(x_language_type) if lang == "en": - msg = fail(BizCode.BAD_REQUEST, "Config name already exists", f"A config named \"{config_name}\" already exists in the current workspace. Please use a different name.") + msg = fail(BizCode.BAD_REQUEST, "Config name already exists", f"A config named \"{payload.config_name}\" already exists in the current workspace. Please use a different name.") else: - msg = fail(BizCode.BAD_REQUEST, "配置名称已存在", f"当前工作空间下已存在名为「{config_name}」的记忆配置,请使用其他名称") + msg = fail(BizCode.BAD_REQUEST, "配置名称已存在", f"当前工作空间下已存在名为「{payload.config_name}」的记忆配置,请使用其他名称") return JSONResponse(status_code=400, content=msg) - api_logger.error(f"Create config failed: {err_str}") - return fail(BizCode.INTERNAL_ERROR, "创建配置失败", err_str) - except Exception as e: api_logger.error(f"Create config failed: {str(e)}") return fail(BizCode.INTERNAL_ERROR, "创建配置失败", str(e)) diff --git a/api/app/services/memory_storage_service.py b/api/app/services/memory_storage_service.py index 71f4ff07..beedaae9 100644 --- a/api/app/services/memory_storage_service.py +++ b/api/app/services/memory_storage_service.py @@ -115,17 +115,6 @@ class DataConfigService: # 数据配置服务类(PostgreSQL) # --- Create --- def create(self, params: ConfigParamsCreate) -> Dict[str, Any]: # 创建配置参数(仅名称与描述) - # 检查同一工作空间下是否已存在同名配置 - if params.workspace_id and params.config_name: - from app.models.memory_config_model import MemoryConfig - existing = ( - self.db.query(MemoryConfig) - .filter_by(workspace_id=params.workspace_id, config_name=params.config_name) - .first() - ) - if existing: - raise ValueError(f"DUPLICATE_CONFIG_NAME:{params.config_name}") - # 如果workspace_id存在且模型字段未全部指定,则自动获取 if params.workspace_id and not all([params.llm_id, params.embedding_id, params.rerank_id]): configs = self._get_workspace_configs(params.workspace_id)