From 4c592bf7e3f0132c72a4e94197222ed67360f773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=90=E5=8A=9B=E9=BD=90?= <162269739+lanceyq@users.noreply.github.com> Date: Sat, 28 Feb 2026 18:58:33 +0800 Subject: [PATCH] Feature/default ontology (#424) * [add]Create a workspace and initialize the default ontology engineering scenario * [add]The language parameters for creating the workspace determine the default language for switching in the ontology project. * [changes]Standardized return format * [add]The default ontology is associated with the default configuration. * [add]Create a workspace and initialize the default ontology engineering scenario * [add]The language parameters for creating the workspace determine the default language for switching in the ontology project. * [changes]Standardized return format * [add]The default ontology is associated with the default configuration. --- api/app/config/__init__.py | 1 + api/app/config/default_ontology_config.py | 239 +++++++++++++++++ .../config/default_ontology_initializer.py | 249 ++++++++++++++++++ .../controllers/memory_agent_controller.py | 4 +- api/app/controllers/ontology_controller.py | 31 ++- .../controllers/ontology_secondary_routes.py | 32 ++- api/app/controllers/workspace_controller.py | 21 +- api/app/models/ontology_class.py | 5 +- api/app/models/ontology_scene.py | 5 +- api/app/services/memory_agent_service.py | 12 +- api/app/services/workspace_service.py | 117 +++++++- redbear-mem-benchmark | 2 +- 12 files changed, 696 insertions(+), 22 deletions(-) create mode 100644 api/app/config/__init__.py create mode 100644 api/app/config/default_ontology_config.py create mode 100644 api/app/config/default_ontology_initializer.py diff --git a/api/app/config/__init__.py b/api/app/config/__init__.py new file mode 100644 index 00000000..df675a16 --- /dev/null +++ b/api/app/config/__init__.py @@ -0,0 +1 @@ +"""Configuration module for application settings.""" diff --git a/api/app/config/default_ontology_config.py b/api/app/config/default_ontology_config.py new file mode 100644 index 00000000..157aa73e --- /dev/null +++ b/api/app/config/default_ontology_config.py @@ -0,0 +1,239 @@ +"""默认本体场景配置 + +本模块定义系统预设的本体场景和实体类型配置。 +这些配置用于在工作空间创建时自动初始化默认场景。 +支持中英文双语配置,根据用户语言偏好创建对应语言的场景。 +""" + +# 在线教育场景配置 +ONLINE_EDUCATION_SCENE = { + "name_chinese": "在线教育", + "name_english": "Online Education", + "description_chinese": "适用于在线教育平台的本体建模,包含学生、教师、课程等核心实体类型", + "description_english": "Ontology modeling for online education platforms, including core entity types such as students, teachers, and courses", + "types": [ + { + "name_chinese": "学生", + "name_english": "Student", + "description_chinese": "在教育系统中接受教育的个体,包含姓名、学号、年级、班级等属性", + "description_english": "Individuals receiving education in the education system, including attributes such as name, student ID, grade, and class" + }, + { + "name_chinese": "教师", + "name_english": "Teacher", + "description_chinese": "在教育系统中提供教学服务的个体,包含姓名、工号、任教学科、职称等属性", + "description_english": "Individuals providing teaching services in the education system, including attributes such as name, employee ID, teaching subject, and title" + }, + { + "name_chinese": "课程", + "name_english": "Course", + "description_chinese": "教育系统中的教学内容单元,包含课程名称、课程代码、学分、学时等属性", + "description_english": "Teaching content units in the education system, including attributes such as course name, course code, credits, and class hours" + }, + { + "name_chinese": "作业", + "name_english": "Assignment", + "description_chinese": "课程中布置的学习任务,包含作业标题、截止日期、所属课程、提交状态等属性", + "description_english": "Learning tasks assigned in courses, including attributes such as assignment title, deadline, course, and submission status" + }, + { + "name_chinese": "成绩", + "name_english": "Grade", + "description_chinese": "学生学习成果的评价结果,包含分数、评级、考试类型、所属课程等属性", + "description_english": "Evaluation results of student learning outcomes, including attributes such as score, rating, exam type, and course" + }, + { + "name_chinese": "考试", + "name_english": "Exam", + "description_chinese": "评估学生学习成果的测试活动,包含考试名称、时间、地点、科目等属性", + "description_english": "Test activities to assess student learning outcomes, including attributes such as exam name, time, location, and subject" + }, + { + "name_chinese": "教室", + "name_english": "Classroom", + "description_chinese": "进行教学活动的物理或虚拟空间,包含教室编号、容量、设备等属性", + "description_english": "Physical or virtual spaces for teaching activities, including attributes such as classroom number, capacity, and equipment" + }, + { + "name_chinese": "学科", + "name_english": "Subject", + "description_chinese": "知识的分类领域,包含学科名称、代码、所属院系等属性", + "description_english": "Classification domains of knowledge, including attributes such as subject name, code, and department" + }, + { + "name_chinese": "教材", + "name_english": "Textbook", + "description_chinese": "教学使用的书籍或资料,包含书名、作者、出版社、ISBN等属性", + "description_english": "Books or materials used for teaching, including attributes such as title, author, publisher, and ISBN" + }, + { + "name_chinese": "班级", + "name_english": "Class", + "description_chinese": "学生的组织单位,包含班级名称、年级、人数、班主任等属性", + "description_english": "Organizational units of students, including attributes such as class name, grade, number of students, and class teacher" + }, + { + "name_chinese": "学期", + "name_english": "Semester", + "description_chinese": "教学时间的划分单位,包含学期名称、开始时间、结束时间等属性", + "description_english": "Time division units for teaching, including attributes such as semester name, start time, and end time" + }, + { + "name_chinese": "课时", + "name_english": "Class Hour", + "description_chinese": "课程的时间单位,包含上课时间、地点、教师、课程等属性", + "description_english": "Time units of courses, including attributes such as class time, location, teacher, and course" + }, + { + "name_chinese": "教学计划", + "name_english": "Teaching Plan", + "description_chinese": "课程的教学安排,包含教学目标、内容安排、进度计划等属性", + "description_english": "Teaching arrangements for courses, including attributes such as teaching objectives, content arrangement, and progress plan" + } + ] +} + +# 情感陪伴场景配置 +EMOTIONAL_COMPANION_SCENE = { + "name_chinese": "情感陪伴", + "name_english": "Emotional Companion", + "description_chinese": "适用于情感陪伴应用的本体建模,包含用户、情绪、活动等核心实体类型", + "description_english": "Ontology modeling for emotional companion applications, including core entity types such as users, emotions, and activities", + "types": [ + { + "name_chinese": "用户", + "name_english": "User", + "description_chinese": "使用情感陪伴服务的个体,包含姓名、昵称、性格特征、偏好等属性", + "description_english": "Individuals using emotional companion services, including attributes such as name, nickname, personality traits, and preferences" + }, + { + "name_chinese": "情绪", + "name_english": "Emotion", + "description_chinese": "用户的情感状态,包含情绪类型、强度、触发原因、持续时间等属性", + "description_english": "Emotional states of users, including attributes such as emotion type, intensity, trigger cause, and duration" + }, + { + "name_chinese": "活动", + "name_english": "Activity", + "description_chinese": "用户参与的各类活动,包含活动名称、类型、参与者、时间地点等属性", + "description_english": "Various activities users participate in, including attributes such as activity name, type, participants, time, and location" + }, + { + "name_chinese": "对话", + "name_english": "Conversation", + "description_chinese": "用户之间的交流记录,包含对话主题、参与者、时间、关键内容等属性", + "description_english": "Communication records between users, including attributes such as conversation topic, participants, time, and key content" + }, + { + "name_chinese": "兴趣爱好", + "name_english": "Hobby", + "description_chinese": "用户的兴趣和爱好,包含爱好名称、类别、熟练程度、相关活动等属性", + "description_english": "User interests and hobbies, including attributes such as hobby name, category, proficiency level, and related activities" + }, + { + "name_chinese": "日常事件", + "name_english": "Daily Event", + "description_chinese": "用户日常生活中的事件,包含事件描述、时间、地点、相关人物等属性", + "description_english": "Events in users' daily lives, including attributes such as event description, time, location, and related people" + }, + { + "name_chinese": "关系", + "name_english": "Relationship", + "description_chinese": "用户之间的社会关系,包含关系类型、亲密度、建立时间等属性", + "description_english": "Social relationships between users, including attributes such as relationship type, intimacy, and establishment time" + }, + { + "name_chinese": "回忆", + "name_english": "Memory", + "description_chinese": "用户的重要记忆片段,包含回忆内容、时间、地点、相关人物等属性", + "description_english": "Important memory fragments of users, including attributes such as memory content, time, location, and related people" + }, + { + "name_chinese": "地点", + "name_english": "Location", + "description_chinese": "用户活动的地理位置,包含地点名称、地址、类型、相关事件等属性", + "description_english": "Geographic locations of user activities, including attributes such as location name, address, type, and related events" + }, + { + "name_chinese": "时间节点", + "name_english": "Time Point", + "description_chinese": "重要的时间标记,包含日期、事件、意义等属性", + "description_english": "Important time markers, including attributes such as date, event, and significance" + }, + { + "name_chinese": "目标", + "name_english": "Goal", + "description_chinese": "用户设定的目标,包含目标描述、截止时间、完成状态、相关活动等属性", + "description_english": "Goals set by users, including attributes such as goal description, deadline, completion status, and related activities" + }, + { + "name_chinese": "成就", + "name_english": "Achievement", + "description_chinese": "用户获得的成就,包含成就名称、获得时间、描述、相关目标等属性", + "description_english": "Achievements obtained by users, including attributes such as achievement name, acquisition time, description, and related goals" + } + ] +} + +# 导出默认场景列表 +DEFAULT_SCENES = [ONLINE_EDUCATION_SCENE, EMOTIONAL_COMPANION_SCENE] + + +def get_scene_name(scene_config: dict, language: str = "zh") -> str: + """获取场景名称(根据语言) + + Args: + scene_config: 场景配置字典 + language: 语言类型 ("zh" 或 "en") + + Returns: + 对应语言的场景名称 + """ + if language == "en": + return scene_config.get("name_english", scene_config.get("name_chinese")) + return scene_config.get("name_chinese") + + +def get_scene_description(scene_config: dict, language: str = "zh") -> str: + """获取场景描述(根据语言) + + Args: + scene_config: 场景配置字典 + language: 语言类型 ("zh" 或 "en") + + Returns: + 对应语言的场景描述 + """ + if language == "en": + return scene_config.get("description_english", scene_config.get("description_chinese")) + return scene_config.get("description_chinese") + + +def get_type_name(type_config: dict, language: str = "zh") -> str: + """获取类型名称(根据语言) + + Args: + type_config: 类型配置字典 + language: 语言类型 ("zh" 或 "en") + + Returns: + 对应语言的类型名称 + """ + if language == "en": + return type_config.get("name_english", type_config.get("name_chinese")) + return type_config.get("name_chinese") + + +def get_type_description(type_config: dict, language: str = "zh") -> str: + """获取类型描述(根据语言) + + Args: + type_config: 类型配置字典 + language: 语言类型 ("zh" 或 "en") + + Returns: + 对应语言的类型描述 + """ + if language == "en": + return type_config.get("description_english", type_config.get("description_chinese")) + return type_config.get("description_chinese") diff --git a/api/app/config/default_ontology_initializer.py b/api/app/config/default_ontology_initializer.py new file mode 100644 index 00000000..3d06a352 --- /dev/null +++ b/api/app/config/default_ontology_initializer.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +"""默认本体场景初始化器 + +本模块提供默认本体场景和类型的自动初始化功能。 +在工作空间创建时,自动添加预设的本体场景和实体类型。 + +Classes: + DefaultOntologyInitializer: 默认本体场景初始化器 +""" + +import logging +from typing import List, Optional, Tuple +from uuid import UUID + +from sqlalchemy.orm import Session + +from app.config.default_ontology_config import ( + DEFAULT_SCENES, + get_scene_name, + get_scene_description, + get_type_name, + get_type_description, +) +from app.core.logging_config import get_business_logger +from app.repositories.ontology_scene_repository import OntologySceneRepository +from app.repositories.ontology_class_repository import OntologyClassRepository + + +class DefaultOntologyInitializer: + """默认本体场景初始化器 + + 负责在工作空间创建时自动初始化默认的本体场景和类型。 + 遵循最小侵入原则,确保初始化失败不阻止工作空间创建。 + + Attributes: + db: 数据库会话 + scene_repo: 场景Repository + class_repo: 类型Repository + logger: 业务日志记录器 + """ + + def __init__(self, db: Session): + """初始化 + + Args: + db: 数据库会话 + """ + self.db = db + self.scene_repo = OntologySceneRepository(db) + self.class_repo = OntologyClassRepository(db) + self.logger = get_business_logger() + + def initialize_default_scenes( + self, + workspace_id: UUID, + language: str = "zh" + ) -> Tuple[bool, str]: + """为工作空间初始化默认场景 + + 创建两个默认场景(在线教育、情感陪伴)及其对应的实体类型。 + 如果创建失败,记录错误日志但不抛出异常。 + + Args: + workspace_id: 工作空间ID + language: 语言类型 ("zh" 或 "en"),默认为 "zh" + + Returns: + Tuple[bool, str]: (是否成功, 错误信息) + """ + try: + self.logger.info( + f"开始初始化默认本体场景 - workspace_id={workspace_id}, language={language}" + ) + + scenes_created = 0 + total_types_created = 0 + + # 遍历默认场景配置 + for scene_config in DEFAULT_SCENES: + scene_name = get_scene_name(scene_config, language) + + # 创建场景及其类型 + scene_id = self._create_scene_with_types(workspace_id, scene_config, language) + + if scene_id: + scenes_created += 1 + # 统计类型数量 + types_count = len(scene_config.get("types", [])) + total_types_created += types_count + + self.logger.info( + f"场景创建成功 - scene_name={scene_name}, " + f"scene_id={scene_id}, types_count={types_count}, language={language}" + ) + else: + self.logger.warning( + f"场景创建失败 - scene_name={scene_name}, " + f"workspace_id={workspace_id}, language={language}" + ) + + # 记录总体结果 + self.logger.info( + f"默认场景初始化完成 - workspace_id={workspace_id}, " + f"language={language}, scenes_created={scenes_created}, " + f"total_types_created={total_types_created}" + ) + + # 如果至少创建了一个场景,视为成功 + if scenes_created > 0: + return True, "" + else: + error_msg = "所有默认场景创建失败" + self.logger.error( + f"默认场景初始化失败 - workspace_id={workspace_id}, " + f"language={language}, error={error_msg}" + ) + return False, error_msg + + except Exception as e: + error_msg = f"默认场景初始化异常: {str(e)}" + self.logger.error( + f"默认场景初始化异常 - workspace_id={workspace_id}, " + f"language={language}, error={str(e)}", + exc_info=True + ) + return False, error_msg + + def _create_scene_with_types( + self, + workspace_id: UUID, + scene_config: dict, + language: str = "zh" + ) -> Optional[UUID]: + """创建场景及其类型 + + Args: + workspace_id: 工作空间ID + scene_config: 场景配置字典 + language: 语言类型 ("zh" 或 "en") + + Returns: + Optional[UUID]: 创建的场景ID,失败返回None + """ + try: + scene_name = get_scene_name(scene_config, language) + scene_description = get_scene_description(scene_config, language) + + # 检查是否已存在同名场景(支持向后兼容) + existing_scene = self.scene_repo.get_by_name(scene_name, workspace_id) + if existing_scene: + self.logger.info( + f"场景已存在,跳过创建 - scene_name={scene_name}, " + f"workspace_id={workspace_id}, scene_id={existing_scene.scene_id}, " + f"language={language}" + ) + return None + + # 创建场景记录,设置 is_system_default=true + scene_data = { + "scene_name": scene_name, + "scene_description": scene_description + } + + scene = self.scene_repo.create(scene_data, workspace_id) + + # 设置系统默认标识 + scene.is_system_default = True + self.db.flush() + + self.logger.info( + f"场景创建成功 - scene_name={scene_name}, " + f"scene_id={scene.scene_id}, is_system_default=True, language={language}" + ) + + # 批量创建类型 + types_config = scene_config.get("types", []) + types_created = self._batch_create_types(scene.scene_id, types_config, language) + + self.logger.info( + f"场景类型创建完成 - scene_id={scene.scene_id}, " + f"types_created={types_created}/{len(types_config)}, language={language}" + ) + + return scene.scene_id + + except Exception as e: + scene_name = get_scene_name(scene_config, language) + self.logger.error( + f"场景创建失败 - scene_name={scene_name}, " + f"workspace_id={workspace_id}, language={language}, error={str(e)}", + exc_info=True + ) + return None + + def _batch_create_types( + self, + scene_id: UUID, + types_config: List[dict], + language: str = "zh" + ) -> int: + """批量创建实体类型 + + Args: + scene_id: 场景ID + types_config: 类型配置列表 + language: 语言类型 ("zh" 或 "en") + + Returns: + int: 成功创建的类型数量 + """ + created_count = 0 + + for type_config in types_config: + try: + type_name = get_type_name(type_config, language) + type_description = get_type_description(type_config, language) + + # 创建类型数据 + class_data = { + "class_name": type_name, + "class_description": type_description + } + + # 创建类型 + ontology_class = self.class_repo.create(class_data, scene_id) + + # 设置系统默认标识 + ontology_class.is_system_default = True + self.db.flush() + + created_count += 1 + + self.logger.debug( + f"类型创建成功 - class_name={type_name}, " + f"class_id={ontology_class.class_id}, " + f"scene_id={scene_id}, is_system_default=True, language={language}" + ) + + except Exception as e: + type_name = get_type_name(type_config, language) + self.logger.warning( + f"单个类型创建失败,继续创建其他类型 - " + f"class_name={type_name}, scene_id={scene_id}, " + f"language={language}, error={str(e)}" + ) + # 继续创建其他类型 + continue + + return created_count diff --git a/api/app/controllers/memory_agent_controller.py b/api/app/controllers/memory_agent_controller.py index 0e632fcc..ef65c679 100644 --- a/api/app/controllers/memory_agent_controller.py +++ b/api/app/controllers/memory_agent_controller.py @@ -633,11 +633,11 @@ async def get_knowledge_type_stats_api( current_user: User = Depends(get_current_user) ): """ - 统计当前空间下各知识库类型的数量,包含 General | Web | Third-party | Folder | memory。 + 统计当前空间下各知识库类型的数量,包含 General | Web | Third-party | Folder | Memory。 会对缺失类型补 0,返回字典形式。 可选按状态过滤。 - 知识库类型根据当前用户的 current_workspace_id 过滤 - - memory 是 Neo4j 中 Chunk 的数量,根据 end_user_id (end_user_id) 过滤 + - Memory 是 Neo4j 中 Chunk 的数量,根据 end_user_id (end_user_id) 过滤 - 如果用户没有当前工作空间或未提供 end_user_id,对应的统计返回 0 """ api_logger.info(f"Knowledge type stats requested for workspace_id: {current_user.current_workspace_id}, end_user_id: {end_user_id}") diff --git a/api/app/controllers/ontology_controller.py b/api/app/controllers/ontology_controller.py index 49a2fb3a..e4a87141 100644 --- a/api/app/controllers/ontology_controller.py +++ b/api/app/controllers/ontology_controller.py @@ -31,7 +31,7 @@ from sqlalchemy.orm import Session from app.core.config import settings from app.core.error_codes import BizCode from app.core.language_utils import get_language_from_header -from app.core.logging_config import get_api_logger +from app.core.logging_config import get_api_logger, get_business_logger from app.core.response_utils import fail, success from app.db import get_db from app.dependencies import get_current_user @@ -61,6 +61,7 @@ from app.repositories.ontology_scene_repository import OntologySceneRepository api_logger = get_api_logger() +business_logger = get_business_logger() logger = logging.getLogger(__name__) router = APIRouter( @@ -399,6 +400,20 @@ async def update_scene( api_logger.warning(f"User {current_user.id} has no current workspace") return fail(BizCode.BAD_REQUEST, "请求参数无效", "当前用户没有工作空间") + # 检查是否为系统默认场景 + scene_repo = OntologySceneRepository(db) + scene = scene_repo.get_by_id(scene_uuid) + if scene and scene.is_system_default: + business_logger.warning( + f"尝试修改系统默认场景: user_id={current_user.id}, " + f"scene_id={scene_id}, scene_name={scene.scene_name}" + ) + return fail( + BizCode.BAD_REQUEST, + "系统默认场景不可修改", + "该场景为系统预设场景,不允许修改" + ) + # 创建OntologyService实例 from app.core.memory.llm_tools.openai_client import OpenAIClient from app.core.models.base import RedBearModelConfig @@ -491,6 +506,20 @@ async def delete_scene( api_logger.warning(f"User {current_user.id} has no current workspace") return fail(BizCode.BAD_REQUEST, "请求参数无效", "当前用户没有工作空间") + # 检查是否为系统默认场景 + scene_repo = OntologySceneRepository(db) + scene = scene_repo.get_by_id(scene_uuid) + if scene and scene.is_system_default: + business_logger.warning( + f"尝试删除系统默认场景: user_id={current_user.id}, " + f"scene_id={scene_id}, scene_name={scene.scene_name}" + ) + return fail( + BizCode.BAD_REQUEST, + "系统默认场景不可删除", + "该场景为系统预设场景,不允许删除" + ) + # 创建OntologyService实例 from app.core.memory.llm_tools.openai_client import OpenAIClient from app.core.models.base import RedBearModelConfig diff --git a/api/app/controllers/ontology_secondary_routes.py b/api/app/controllers/ontology_secondary_routes.py index 99017eea..607a0739 100644 --- a/api/app/controllers/ontology_secondary_routes.py +++ b/api/app/controllers/ontology_secondary_routes.py @@ -11,7 +11,7 @@ from fastapi import Depends from sqlalchemy.orm import Session from app.core.error_codes import BizCode -from app.core.logging_config import get_api_logger +from app.core.logging_config import get_api_logger, get_business_logger from app.core.response_utils import fail, success from app.db import get_db from app.dependencies import get_current_user @@ -30,9 +30,11 @@ from app.schemas.response_schema import ApiResponse from app.services.ontology_service import OntologyService from app.core.memory.llm_tools.openai_client import OpenAIClient from app.core.models.base import RedBearModelConfig +from app.repositories.ontology_class_repository import OntologyClassRepository api_logger = get_api_logger() +business_logger = get_business_logger() def _get_dummy_ontology_service(db: Session) -> OntologyService: @@ -366,6 +368,20 @@ async def update_class_handler( api_logger.warning(f"User {current_user.id} has no current workspace") return fail(BizCode.BAD_REQUEST, "请求参数无效", "当前用户没有工作空间") + # 检查是否为系统默认类型 + class_repo = OntologyClassRepository(db) + ontology_class = class_repo.get_by_id(class_uuid) + if ontology_class and ontology_class.is_system_default: + business_logger.warning( + f"尝试修改系统默认类型: user_id={current_user.id}, " + f"class_id={class_id}, class_name={ontology_class.class_name}" + ) + return fail( + BizCode.BAD_REQUEST, + "系统默认类型不可修改", + "该类型为系统预设类型,不允许修改" + ) + # 创建Service service = _get_dummy_ontology_service(db) @@ -429,6 +445,20 @@ async def delete_class_handler( api_logger.warning(f"User {current_user.id} has no current workspace") return fail(BizCode.BAD_REQUEST, "请求参数无效", "当前用户没有工作空间") + # 检查是否为系统默认类型 + class_repo = OntologyClassRepository(db) + ontology_class = class_repo.get_by_id(class_uuid) + if ontology_class and ontology_class.is_system_default: + business_logger.warning( + f"尝试删除系统默认类型: user_id={current_user.id}, " + f"class_id={class_id}, class_name={ontology_class.class_name}" + ) + return fail( + BizCode.BAD_REQUEST, + "系统默认类型不可删除", + "该类型为系统预设类型,不允许删除" + ) + # 创建Service service = _get_dummy_ontology_service(db) diff --git a/api/app/controllers/workspace_controller.py b/api/app/controllers/workspace_controller.py index d2afb10f..9bcd8571 100644 --- a/api/app/controllers/workspace_controller.py +++ b/api/app/controllers/workspace_controller.py @@ -1,7 +1,7 @@ import uuid from typing import List, Optional -from fastapi import APIRouter, Depends, HTTPException, Query, status +from fastapi import APIRouter, Depends, Header, HTTPException, Query, status from sqlalchemy.orm import Session from app.core.logging_config import get_api_logger @@ -95,16 +95,29 @@ def get_workspaces( @router.post("", response_model=ApiResponse) def create_workspace( workspace: WorkspaceCreate, + language_type: str = Header(default="zh", alias="X-Language-Type"), db: Session = Depends(get_db), current_user: User = Depends(get_current_superuser), ): """创建新的工作空间""" - api_logger.info(f"用户 {current_user.username} 请求创建工作空间: {workspace.name}") + from app.core.language_utils import get_language_from_header + + # 验证并获取语言参数 + language = get_language_from_header(language_type) + + api_logger.info( + f"用户 {current_user.username} 请求创建工作空间: {workspace.name}, " + f"language={language}" + ) result = workspace_service.create_workspace( - db=db, workspace=workspace, user=current_user) + db=db, workspace=workspace, user=current_user, language=language + ) - api_logger.info(f"工作空间创建成功 - 名称: {workspace.name}, ID: {result.id}, 创建者: {current_user.username}") + api_logger.info( + f"工作空间创建成功 - 名称: {workspace.name}, ID: {result.id}, " + f"创建者: {current_user.username}, language={language}" + ) result_schema = WorkspaceResponse.model_validate(result) return success(data=result_schema, msg="工作空间创建成功") diff --git a/api/app/models/ontology_class.py b/api/app/models/ontology_class.py index 528d934e..a8468090 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 +from sqlalchemy import Column, String, DateTime, Text, ForeignKey, Boolean from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship from app.db import Base @@ -25,6 +25,9 @@ class OntologyClass(Base): # 类型信息 class_name = Column(String(200), nullable=False, comment="类型名称") class_description = Column(Text, nullable=True, comment="类型描述") + + # 系统默认标识 + is_system_default = Column(Boolean, default=False, nullable=False, comment="是否为系统默认类型") # 外键:关联到本体场景 scene_id = Column(UUID(as_uuid=True), ForeignKey("ontology_scene.scene_id", ondelete="CASCADE"), nullable=False, index=True, comment="所属场景ID") diff --git a/api/app/models/ontology_scene.py b/api/app/models/ontology_scene.py index 350bfdd6..3ce42cad 100644 --- a/api/app/models/ontology_scene.py +++ b/api/app/models/ontology_scene.py @@ -9,7 +9,7 @@ Classes: import datetime import uuid -from sqlalchemy import Column, String, DateTime, Integer, Text, ForeignKey, UniqueConstraint +from sqlalchemy import Column, String, DateTime, Integer, Text, ForeignKey, UniqueConstraint, Boolean from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship from app.db import Base @@ -28,6 +28,9 @@ class OntologyScene(Base): # 场景信息 scene_name = Column(String(200), nullable=False, comment="场景名称") scene_description = Column(Text, nullable=True, comment="场景描述") + + # 系统默认标识 + is_system_default = Column(Boolean, default=False, nullable=False, index=True, comment="是否为系统默认场景") # 外键:关联到工作空间 workspace_id = Column(UUID(as_uuid=True), ForeignKey("workspaces.id", ondelete="CASCADE"), nullable=False, index=True, comment="所属工作空间ID") diff --git a/api/app/services/memory_agent_service.py b/api/app/services/memory_agent_service.py index da8a8e06..ad295667 100644 --- a/api/app/services/memory_agent_service.py +++ b/api/app/services/memory_agent_service.py @@ -816,11 +816,11 @@ class MemoryAgentService: """ 统计知识库类型分布,包含: 1. PostgreSQL 中的知识库类型:General, Web, Third-party, Folder(根据 workspace_id 过滤) - 2. Neo4j 中的 memory 类型(仅统计 Chunk 数量,根据 end_user_id/end_user_id 过滤) + 2. Neo4j 中的 Memory 类型(仅统计 Chunk 数量,根据 end_user_id/end_user_id 过滤) 3. total: 所有类型的总和 参数: - - end_user_id: 用户组ID(可选,未提供时 memory 统计为 0) + - end_user_id: 用户组ID(可选,未提供时 Memory 统计为 0) - only_active: 是否仅统计有效记录 - current_workspace_id: 当前工作空间ID(可选,未提供时知识库统计为 0) - db: 数据库会话 @@ -831,7 +831,7 @@ class MemoryAgentService: "Web": count, "Third-party": count, "Folder": count, - "memory": chunk_count, + "Memory": chunk_count, "total": sum_of_all } """ @@ -912,17 +912,17 @@ class MemoryAgentService: total_chunks += chunk_count logger.debug(f"EndUser {end_user_id_str} Chunk数量: {chunk_count}") - result["memory"] = total_chunks + result["Memory"] = total_chunks logger.info(f"Neo4j memory统计成功: 总Chunk数={total_chunks}, 宿主数={len(end_users)}") else: # 没有 workspace_id 时,返回 0 - result["memory"] = 0 + result["Memory"] = 0 logger.info("未提供 workspace_id,memory 统计为 0") except Exception as e: logger.error(f"Neo4j memory统计失败: {e}", exc_info=True) # 如果 Neo4j 查询失败,memory 设为 0 - result["memory"] = 0 + result["Memory"] = 0 # 3. 计算知识库类型总和(不包括 memory) result["total"] = ( diff --git a/api/app/services/workspace_service.py b/api/app/services/workspace_service.py index 6f102695..2f8cdc70 100644 --- a/api/app/services/workspace_service.py +++ b/api/app/services/workspace_service.py @@ -30,6 +30,7 @@ from app.schemas.workspace_schema import ( WorkspaceModelsUpdate, WorkspaceUpdate, ) +from app.config.default_ontology_initializer import DefaultOntologyInitializer # 获取业务逻辑专用日志器 business_logger = get_business_logger() @@ -129,7 +130,7 @@ def _create_workspace_only( raise def create_workspace( - db: Session, workspace: WorkspaceCreate, user: User + db: Session, workspace: WorkspaceCreate, user: User, language: str = "zh" ) -> Workspace: business_logger.info( f"创建工作空间: {workspace.name}, 创建者: {user.username}, " @@ -145,10 +146,68 @@ def create_workspace( db=db, workspace=workspace, tenant_id=user.tenant_id ) business_logger.info(f"工作空间创建成功: {db_workspace.name} (ID: {db_workspace.id}), 创建者: {user.username}") - db.commit() + db.flush() # 使用 flush 而不是 commit,获取 ID 但不提交事务 db.refresh(db_workspace) + # Initialize default ontology scenes for the workspace (先创建本体场景) + default_scene_id = None + try: + initializer = DefaultOntologyInitializer(db) + success, error_msg = initializer.initialize_default_scenes( + db_workspace.id, language=language + ) + + if success: + business_logger.info( + f"为工作空间 {db_workspace.id} 创建默认本体场景成功 (language={language})" + ) + + # 获取默认场景ID,优先使用"在线教育"场景,如果不存在则使用"情感陪伴"场景 + from app.repositories.ontology_scene_repository import OntologySceneRepository + from app.config.default_ontology_config import ( + ONLINE_EDUCATION_SCENE, + EMOTIONAL_COMPANION_SCENE, + get_scene_name + ) + + scene_repo = OntologySceneRepository(db) + + # 优先尝试获取教育场景 + education_scene_name = get_scene_name(ONLINE_EDUCATION_SCENE, language) + education_scene = scene_repo.get_by_name(education_scene_name, db_workspace.id) + + if education_scene: + default_scene_id = education_scene.scene_id + business_logger.info( + f"获取到教育场景ID用于默认记忆配置: {default_scene_id} (scene_name={education_scene_name})" + ) + else: + # 如果教育场景不存在,尝试获取情感陪伴场景 + companion_scene_name = get_scene_name(EMOTIONAL_COMPANION_SCENE, language) + companion_scene = scene_repo.get_by_name(companion_scene_name, db_workspace.id) + + if companion_scene: + default_scene_id = companion_scene.scene_id + business_logger.info( + f"教育场景不存在,使用情感陪伴场景ID用于默认记忆配置: {default_scene_id} (scene_name={companion_scene_name})" + ) + else: + business_logger.warning( + f"未找到任何默认场景 (education={education_scene_name}, companion={companion_scene_name})" + ) + else: + business_logger.warning( + f"为工作空间 {db_workspace.id} 创建默认本体场景失败: {error_msg} (language={language})" + ) + except Exception as ontology_error: + business_logger.error( + f"为工作空间 {db_workspace.id} 创建默认本体场景异常: {str(ontology_error)} (language={language})" + ) + # Don't fail workspace creation if default ontology initialization fails + # The workspace can still function without default ontology scenes + # Create default memory config for the workspace (only for neo4j storage types) + # 将默认场景ID(教育场景或情感陪伴场景)关联到记忆配置 if workspace.storage_type == 'neo4j': try: _create_default_memory_config( @@ -158,9 +217,10 @@ def create_workspace( llm_id=llm, embedding_id=embedding, rerank_id=rerank, + scene_id=default_scene_id, # 传入默认场景ID(优先教育场景,其次情感陪伴场景) ) business_logger.info( - f"为工作空间 {db_workspace.id} 创建默认记忆配置成功" + f"为工作空间 {db_workspace.id} 创建默认记忆配置成功 (scene_id={default_scene_id})" ) except Exception as mc_error: business_logger.error( @@ -209,7 +269,6 @@ def create_workspace( db=db, knowledge=knowledge_data ) - db.commit() business_logger.info( f"为工作空间 {db_workspace.id} 自动创建知识库成功: " f"{db_knowledge.name} (ID: {db_knowledge.id})" @@ -224,6 +283,12 @@ def create_workspace( BizCode.INTERNAL_ERROR ) + # 统一提交所有更改 + db.commit() + business_logger.info( + f"工作空间 {db_workspace.id} 及相关资源创建完成并已提交" + ) + return db_workspace except Exception as e: @@ -919,6 +984,43 @@ def _ensure_default_memory_config(db: Session, workspace: Workspace) -> None: f"Workspace {workspace.id} missing default memory config, creating one" ) + # 尝试获取默认场景ID,优先教育场景,其次情感陪伴场景 + default_scene_id = None + try: + from app.repositories.ontology_scene_repository import OntologySceneRepository + from app.config.default_ontology_config import ( + ONLINE_EDUCATION_SCENE, + EMOTIONAL_COMPANION_SCENE, + get_scene_name + ) + + scene_repo = OntologySceneRepository(db) + # 尝试中文和英文场景名称 + for language in ["zh", "en"]: + # 优先尝试教育场景 + education_scene_name = get_scene_name(ONLINE_EDUCATION_SCENE, language) + education_scene = scene_repo.get_by_name(education_scene_name, workspace.id) + if education_scene: + default_scene_id = education_scene.scene_id + business_logger.info( + f"找到教育场景用于默认记忆配置: scene_id={default_scene_id}, scene_name={education_scene_name}" + ) + break + + # 如果教育场景不存在,尝试情感陪伴场景 + companion_scene_name = get_scene_name(EMOTIONAL_COMPANION_SCENE, language) + companion_scene = scene_repo.get_by_name(companion_scene_name, workspace.id) + if companion_scene: + default_scene_id = companion_scene.scene_id + business_logger.info( + f"教育场景不存在,找到情感陪伴场景用于默认记忆配置: scene_id={default_scene_id}, scene_name={companion_scene_name}" + ) + break + except Exception as scene_error: + business_logger.warning( + f"获取默认场景失败,将创建不关联场景的记忆配置: {str(scene_error)}" + ) + try: _create_default_memory_config( db=db, @@ -927,6 +1029,7 @@ def _ensure_default_memory_config(db: Session, workspace: Workspace) -> None: llm_id=uuid.UUID(workspace.llm) if workspace.llm else None, embedding_id=uuid.UUID(workspace.embedding) if workspace.embedding else None, rerank_id=uuid.UUID(workspace.rerank) if workspace.rerank else None, + scene_id=default_scene_id, # 传入默认场景ID(优先教育场景,其次情感陪伴场景) ) except Exception as e: business_logger.error( @@ -1008,6 +1111,7 @@ def _create_default_memory_config( llm_id: Optional[uuid.UUID] = None, embedding_id: Optional[uuid.UUID] = None, rerank_id: Optional[uuid.UUID] = None, + scene_id: Optional[uuid.UUID] = None, ) -> None: """Create a default memory config for a newly created workspace. @@ -1018,6 +1122,7 @@ def _create_default_memory_config( llm_id: Optional LLM model ID embedding_id: Optional embedding model ID rerank_id: Optional rerank model ID + scene_id: Optional ontology scene ID (默认关联教育场景) """ from app.models.memory_config_model import MemoryConfig @@ -1031,12 +1136,13 @@ def _create_default_memory_config( llm_id=str(llm_id) if llm_id else None, embedding_id=str(embedding_id) if embedding_id else None, rerank_id=str(rerank_id) if rerank_id else None, + scene_id=scene_id, # 关联本体场景ID state=True, # Active by default is_default=True, # Mark as workspace default ) db.add(default_config) - db.commit() + db.flush() # 使用 flush 而不是 commit,让调用者统一提交 business_logger.info( "Created default memory config for workspace", @@ -1044,5 +1150,6 @@ def _create_default_memory_config( "workspace_id": str(workspace_id), "config_id": str(config_id), "config_name": default_config.config_name, + "scene_id": str(scene_id) if scene_id else None, } ) diff --git a/redbear-mem-benchmark b/redbear-mem-benchmark index 4b0257bb..8494e824 160000 --- a/redbear-mem-benchmark +++ b/redbear-mem-benchmark @@ -1 +1 @@ -Subproject commit 4b0257bb4e7dc384b2aaf849b0bd6eae4b39835d +Subproject commit 8494e82498cb99c70ac67a64a544ff872432363a