Merge pull request #522 from SuanmoSuanyangTechnology/fix/bug-patch

feat(workspace, app, agent): add duplicate name validation and restrict model/memory config on agent publish
This commit is contained in:
Mark
2026-03-10 11:31:54 +08:00
committed by GitHub
5 changed files with 233 additions and 160 deletions

View File

@@ -1,7 +1,6 @@
import json
import os import os
from pathlib import Path from pathlib import Path
from typing import Annotated, Any, Dict, Optional from typing import Annotated, Optional
from dotenv import load_dotenv from dotenv import load_dotenv
from pydantic import Field, TypeAdapter from pydantic import Field, TypeAdapter

View File

@@ -1,10 +1,11 @@
from sqlalchemy.orm import Session
from typing import List, Optional
import uuid import uuid
from typing import List
from app.models.app_model import App from sqlalchemy import select
from sqlalchemy.orm import Session
from app.core.logging_config import get_db_logger from app.core.logging_config import get_db_logger
from app.models.app_model import App
# 获取数据库专用日志器 # 获取数据库专用日志器
db_logger = get_db_logger() db_logger = get_db_logger()
@@ -35,11 +36,27 @@ class AppRepository:
except Exception as e: except Exception as e:
raise raise
def get_apps_by_name(self, app_name: str, app_type: str, workspace_id: uuid.UUID) -> List[App]:
try:
stmt = select(App).where(
App.name == app_name,
App.workspace_id == workspace_id,
App.type == app_type,
App.is_active.is_(True),
)
apps = self.db.execute(stmt).scalars().all()
return list(apps)
except Exception as e:
db_logger.error(f"查询名称 {app_name} 应用异常: {str(e)}")
raise
def get_apps_by_workspace_id(db: Session, workspace_id: uuid.UUID) -> List[App]: def get_apps_by_workspace_id(db: Session, workspace_id: uuid.UUID) -> List[App]:
"""根据工作空间ID查询应用""" """根据工作空间ID查询应用"""
repo = AppRepository(db) repo = AppRepository(db)
return repo.get_apps_by_workspace_id(workspace_id) return repo.get_apps_by_workspace_id(workspace_id)
def get_apps_by_id(db: Session, app_id: uuid.UUID) -> App: def get_apps_by_id(db: Session, app_id: uuid.UUID) -> App:
"""根据工作空间ID查询应用""" """根据工作空间ID查询应用"""
repo = AppRepository(db) repo = AppRepository(db)

View File

@@ -1,10 +1,13 @@
from sqlalchemy.orm import Session, joinedload
from app.models.user_model import User
from typing import List, Optional
import uuid import uuid
from app.models.workspace_model import Workspace, WorkspaceMember, WorkspaceRole from typing import List, Optional
from app.schemas.workspace_schema import WorkspaceCreate, WorkspaceUpdate
from sqlalchemy.orm import Session, joinedload
from sqlalchemy import select
from app.core.logging_config import get_db_logger from app.core.logging_config import get_db_logger
from app.models.user_model import User
from app.models.workspace_model import Workspace, WorkspaceMember, WorkspaceRole
from app.schemas.workspace_schema import WorkspaceCreate
# 获取数据库专用日志器 # 获取数据库专用日志器
db_logger = get_db_logger() db_logger = get_db_logger()
@@ -19,7 +22,7 @@ class WorkspaceRepository:
def create_workspace(self, workspace_data: WorkspaceCreate, tenant_id: uuid.UUID) -> Workspace: def create_workspace(self, workspace_data: WorkspaceCreate, tenant_id: uuid.UUID) -> Workspace:
"""创建工作空间""" """创建工作空间"""
db_logger.debug(f"创建工作空间记录: name={workspace_data.name}, tenant_id={tenant_id}") db_logger.debug(f"创建工作空间记录: name={workspace_data.name}, tenant_id={tenant_id}")
try: try:
db_workspace = Workspace( db_workspace = Workspace(
name=workspace_data.name, name=workspace_data.name,
@@ -34,7 +37,8 @@ class WorkspaceRepository:
) )
self.db.add(db_workspace) self.db.add(db_workspace)
self.db.flush() self.db.flush()
db_logger.info(f"工作空间记录创建成功: {workspace_data.name} (ID: {db_workspace.id}), storage_type: {workspace_data.storage_type}") db_logger.info(
f"工作空间记录创建成功: {workspace_data.name} (ID: {db_workspace.id}), storage_type: {workspace_data.storage_type}")
return db_workspace return db_workspace
except Exception as e: except Exception as e:
db_logger.error(f"创建工作空间记录失败: name={workspace_data.name} - {str(e)}") db_logger.error(f"创建工作空间记录失败: name={workspace_data.name} - {str(e)}")
@@ -43,7 +47,7 @@ class WorkspaceRepository:
def get_workspace_by_id(self, workspace_id: uuid.UUID) -> Optional[Workspace]: def get_workspace_by_id(self, workspace_id: uuid.UUID) -> Optional[Workspace]:
"""根据ID获取工作空间""" """根据ID获取工作空间"""
db_logger.debug(f"根据ID查询工作空间: workspace_id={workspace_id}") db_logger.debug(f"根据ID查询工作空间: workspace_id={workspace_id}")
try: try:
workspace = self.db.query(Workspace).filter(Workspace.id == workspace_id).first() workspace = self.db.query(Workspace).filter(Workspace.id == workspace_id).first()
if workspace: if workspace:
@@ -65,7 +69,7 @@ class WorkspaceRepository:
包含 llm, embedding, rerank 的字典,如果工作空间不存在则返回 None 包含 llm, embedding, rerank 的字典,如果工作空间不存在则返回 None
""" """
db_logger.debug(f"查询工作空间模型配置: workspace_id={workspace_id}") db_logger.debug(f"查询工作空间模型配置: workspace_id={workspace_id}")
try: try:
workspace = self.db.query(Workspace).filter(Workspace.id == workspace_id).first() workspace = self.db.query(Workspace).filter(Workspace.id == workspace_id).first()
if workspace: if workspace:
@@ -89,7 +93,7 @@ class WorkspaceRepository:
def get_workspaces_by_user(self, user_id: uuid.UUID) -> List[Workspace]: def get_workspaces_by_user(self, user_id: uuid.UUID) -> List[Workspace]:
"""获取用户参与的所有工作空间(包括用户创建的和作为成员的)""" """获取用户参与的所有工作空间(包括用户创建的和作为成员的)"""
db_logger.debug(f"查询用户参与的工作空间: user_id={user_id}") db_logger.debug(f"查询用户参与的工作空间: user_id={user_id}")
try: try:
# 首先获取用户信息以获取 tenant_id # 首先获取用户信息以获取 tenant_id
from app.models.user_model import User from app.models.user_model import User
@@ -97,7 +101,7 @@ class WorkspaceRepository:
if not user: if not user:
db_logger.warning(f"用户不存在: user_id={user_id}") db_logger.warning(f"用户不存在: user_id={user_id}")
return [] return []
if user.is_superuser: if user.is_superuser:
# 超级用户获取对应tenantid所有工作空间 # 超级用户获取对应tenantid所有工作空间
workspaces = ( workspaces = (
@@ -109,7 +113,7 @@ class WorkspaceRepository:
) )
db_logger.debug(f"超用户查询所有工作空间: user_id={user_id}, 数量={len(workspaces)}") db_logger.debug(f"超用户查询所有工作空间: user_id={user_id}, 数量={len(workspaces)}")
return workspaces return workspaces
# 获取用户作为成员的工作空间 # 获取用户作为成员的工作空间
member_workspaces = ( member_workspaces = (
self.db.query(Workspace) self.db.query(Workspace)
@@ -120,7 +124,7 @@ class WorkspaceRepository:
.order_by(Workspace.updated_at.desc()) .order_by(Workspace.updated_at.desc())
.all() .all()
) )
db_logger.debug(f"用户工作空间查询成功: user_id={user_id}, 数量={len(member_workspaces)}") db_logger.debug(f"用户工作空间查询成功: user_id={user_id}, 数量={len(member_workspaces)}")
return member_workspaces return member_workspaces
except Exception as e: except Exception as e:
@@ -130,7 +134,7 @@ class WorkspaceRepository:
def get_workspaces_by_tenant(self, tenant_id: uuid.UUID) -> List[Workspace]: def get_workspaces_by_tenant(self, tenant_id: uuid.UUID) -> List[Workspace]:
"""获取租户的所有工作空间""" """获取租户的所有工作空间"""
db_logger.debug(f"查询租户的工作空间: tenant_id={tenant_id}") db_logger.debug(f"查询租户的工作空间: tenant_id={tenant_id}")
try: try:
workspaces = ( workspaces = (
self.db.query(Workspace) self.db.query(Workspace)
@@ -144,14 +148,32 @@ class WorkspaceRepository:
db_logger.error(f"查询租户工作空间失败: tenant_id={tenant_id} - {str(e)}") db_logger.error(f"查询租户工作空间失败: tenant_id={tenant_id} - {str(e)}")
raise raise
def add_member(self, workspace_id: uuid.UUID, user_id: uuid.UUID, role: WorkspaceRole = WorkspaceRole.member) -> WorkspaceMember: def get_workspaces_by_name(self, tenant_id: uuid.UUID, workspace_name: str) -> List[Workspace]:
try:
stmt = (
select(Workspace)
.where(
Workspace.tenant_id == tenant_id,
Workspace.name == workspace_name,
Workspace.is_active.is_(True)
)
)
workspaces = self.db.execute(stmt).scalars().all()
return list(workspaces)
except Exception as e:
db_logger.error(f"查询工作空间失败: workspace_name={workspace_name} - {str(e)}")
raise
def add_member(self, workspace_id: uuid.UUID, user_id: uuid.UUID,
role: WorkspaceRole = WorkspaceRole.member) -> WorkspaceMember:
"""添加工作空间成员""" """添加工作空间成员"""
db_logger.debug(f"添加工作空间成员: user_id={user_id}, workspace_id={workspace_id}, role={role}") db_logger.debug(f"添加工作空间成员: user_id={user_id}, workspace_id={workspace_id}, role={role}")
try: try:
db_member = WorkspaceMember( db_member = WorkspaceMember(
user_id=user_id, user_id=user_id,
workspace_id=workspace_id, workspace_id=workspace_id,
role=role role=role
) )
self.db.add(db_member) self.db.add(db_member)
@@ -165,7 +187,7 @@ class WorkspaceRepository:
def get_member(self, user_id: uuid.UUID, workspace_id: uuid.UUID) -> Optional[WorkspaceMember]: def get_member(self, user_id: uuid.UUID, workspace_id: uuid.UUID) -> Optional[WorkspaceMember]:
"""获取工作空间成员""" """获取工作空间成员"""
db_logger.debug(f"查询工作空间成员: user_id={user_id}, workspace_id={workspace_id}") db_logger.debug(f"查询工作空间成员: user_id={user_id}, workspace_id={workspace_id}")
try: try:
member = self.db.query(WorkspaceMember).filter( member = self.db.query(WorkspaceMember).filter(
WorkspaceMember.user_id == user_id, WorkspaceMember.user_id == user_id,
@@ -173,7 +195,8 @@ class WorkspaceRepository:
WorkspaceMember.is_active.is_(True), WorkspaceMember.is_active.is_(True),
).first() ).first()
if member: if member:
db_logger.debug(f"工作空间成员查询成功: user_id={user_id}, workspace_id={workspace_id}, role={member.role}") db_logger.debug(
f"工作空间成员查询成功: user_id={user_id}, workspace_id={workspace_id}, role={member.role}")
else: else:
db_logger.debug(f"工作空间成员不存在: user_id={user_id}, workspace_id={workspace_id}") db_logger.debug(f"工作空间成员不存在: user_id={user_id}, workspace_id={workspace_id}")
return member return member
@@ -199,7 +222,7 @@ class WorkspaceRepository:
except Exception as e: except Exception as e:
db_logger.error(f"查询成员列表失败: workspace_id={workspace_id} - {str(e)}") db_logger.error(f"查询成员列表失败: workspace_id={workspace_id} - {str(e)}")
raise raise
def get_member_by_id(self, member_id: uuid.UUID) -> WorkspaceMember: def get_member_by_id(self, member_id: uuid.UUID) -> WorkspaceMember:
"""按成员ID获取工作空间成员并预加载 user 与 workspace 关系""" """按成员ID获取工作空间成员并预加载 user 与 workspace 关系"""
db_logger.debug(f"查询成员的工作空间: member_id={member_id}") db_logger.debug(f"查询成员的工作空间: member_id={member_id}")
@@ -214,7 +237,8 @@ class WorkspaceRepository:
.first() .first()
) )
if member: if member:
db_logger.debug(f"成员查询成功: member_id={member_id}, workspace_id={member.workspace_id}, role={member.role}") db_logger.debug(
f"成员查询成功: member_id={member_id}, workspace_id={member.workspace_id}, role={member.role}")
else: else:
db_logger.debug(f"成员不存在: member_id={member_id}") db_logger.debug(f"成员不存在: member_id={member_id}")
return member return member
@@ -222,7 +246,8 @@ class WorkspaceRepository:
db_logger.error(f"查询成员列表失败: member_id={member_id} - {str(e)}") db_logger.error(f"查询成员列表失败: member_id={member_id} - {str(e)}")
raise raise
def update_member_role(self, workspace_id: uuid.UUID, user_id: uuid.UUID, role: WorkspaceRole) -> Optional[WorkspaceMember]: def update_member_role(self, workspace_id: uuid.UUID, user_id: uuid.UUID, role: WorkspaceRole) -> Optional[
WorkspaceMember]:
try: try:
member = self.db.query(WorkspaceMember).filter( member = self.db.query(WorkspaceMember).filter(
WorkspaceMember.workspace_id == workspace_id, WorkspaceMember.workspace_id == workspace_id,
@@ -255,7 +280,7 @@ class WorkspaceRepository:
except Exception as e: except Exception as e:
db_logger.error(f"删除成员失败: workspace_id={workspace_id}, user_id={user_id} - {str(e)}") db_logger.error(f"删除成员失败: workspace_id={workspace_id}, user_id={user_id} - {str(e)}")
raise raise
def delete_member_by_id(self, member_id: uuid.UUID) -> Optional[WorkspaceMember]: def delete_member_by_id(self, member_id: uuid.UUID) -> Optional[WorkspaceMember]:
try: try:
member = self.db.query(WorkspaceMember).filter( member = self.db.query(WorkspaceMember).filter(
@@ -271,7 +296,7 @@ class WorkspaceRepository:
except Exception as e: except Exception as e:
db_logger.error(f"删除成员失败: id={member_id} - {str(e)}") db_logger.error(f"删除成员失败: id={member_id} - {str(e)}")
raise raise
def update_member_role_by_id(self, id: uuid.UUID, role: WorkspaceRole) -> Optional[WorkspaceMember]: def update_member_role_by_id(self, id: uuid.UUID, role: WorkspaceRole) -> Optional[WorkspaceMember]:
try: try:
member = self.db.query(WorkspaceMember).filter( member = self.db.query(WorkspaceMember).filter(
@@ -288,12 +313,18 @@ class WorkspaceRepository:
db_logger.error(f"更新成员角色失败: id={id} - {str(e)}") db_logger.error(f"更新成员角色失败: id={id} - {str(e)}")
raise raise
# 保持向后兼容的函数 # 保持向后兼容的函数
def get_workspace_by_id(db: Session, workspace_id: uuid.UUID) -> Workspace | None: def get_workspace_by_id(db: Session, workspace_id: uuid.UUID) -> Workspace | None:
repo = WorkspaceRepository(db) repo = WorkspaceRepository(db)
return repo.get_workspace_by_id(workspace_id) return repo.get_workspace_by_id(workspace_id)
def get_workspaces_by_name(db: Session, tenant_id: uuid.UUID, name: str) -> List[Workspace]:
repo = WorkspaceRepository(db)
return repo.get_workspaces_by_name(tenant_id, name)
def get_workspaces_by_user(db: Session, user_id: uuid.UUID) -> List[Workspace]: def get_workspaces_by_user(db: Session, user_id: uuid.UUID) -> List[Workspace]:
repo = WorkspaceRepository(db) repo = WorkspaceRepository(db)
return repo.get_workspaces_by_user(user_id) return repo.get_workspaces_by_user(user_id)
@@ -315,7 +346,7 @@ def create_workspace(db: Session, workspace: WorkspaceCreate, tenant_id: uuid.UU
def add_member_to_workspace( def add_member_to_workspace(
db: Session, user_id: uuid.UUID, workspace_id: uuid.UUID, role: WorkspaceRole db: Session, user_id: uuid.UUID, workspace_id: uuid.UUID, role: WorkspaceRole
) -> WorkspaceMember: ) -> WorkspaceMember:
repo = WorkspaceRepository(db) repo = WorkspaceRepository(db)
return repo.add_member(workspace_id, user_id, role) return repo.add_member(workspace_id, user_id, role)
@@ -325,39 +356,43 @@ def get_members_by_workspace(db: Session, workspace_id: uuid.UUID) -> List[Works
repo = WorkspaceRepository(db) repo = WorkspaceRepository(db)
return repo.get_members_by_workspace(workspace_id) return repo.get_members_by_workspace(workspace_id)
def get_member_by_id(db: Session, member_id: uuid.UUID) -> WorkspaceMember | None: def get_member_by_id(db: Session, member_id: uuid.UUID) -> WorkspaceMember | None:
repo = WorkspaceRepository(db) repo = WorkspaceRepository(db)
return repo.get_member_by_id(member_id) return repo.get_member_by_id(member_id)
def update_member_role_in_workspace( def update_member_role_in_workspace(
db: Session, db: Session,
user_id: uuid.UUID, user_id: uuid.UUID,
workspace_id: uuid.UUID, workspace_id: uuid.UUID,
role: WorkspaceRole, role: WorkspaceRole,
) -> Optional[WorkspaceMember]: ) -> Optional[WorkspaceMember]:
repo = WorkspaceRepository(db) repo = WorkspaceRepository(db)
return repo.update_member_role(workspace_id, user_id, role) return repo.update_member_role(workspace_id, user_id, role)
def remove_member_from_workspace( def remove_member_from_workspace(
db: Session, db: Session,
user_id: uuid.UUID, user_id: uuid.UUID,
workspace_id: uuid.UUID, workspace_id: uuid.UUID,
) -> Optional[WorkspaceMember]: ) -> Optional[WorkspaceMember]:
repo = WorkspaceRepository(db) repo = WorkspaceRepository(db)
return repo.deactivate_member(workspace_id, user_id) return repo.deactivate_member(workspace_id, user_id)
def remove_member_from_workspace_by_id( def remove_member_from_workspace_by_id(
db: Session, db: Session,
member_id: uuid.UUID, member_id: uuid.UUID,
) -> Optional[WorkspaceMember]: ) -> Optional[WorkspaceMember]:
repo = WorkspaceRepository(db) repo = WorkspaceRepository(db)
return repo.delete_member_by_id(member_id) return repo.delete_member_by_id(member_id)
def update_member_role_by_id( def update_member_role_by_id(
db: Session, db: Session,
id: uuid.UUID, id: uuid.UUID,
role: WorkspaceRole, role: WorkspaceRole,
) -> Optional[WorkspaceMember]: ) -> Optional[WorkspaceMember]:
repo = WorkspaceRepository(db) repo = WorkspaceRepository(db)
return repo.update_member_role_by_id(id, role) return repo.update_member_role_by_id(id, role)

View File

@@ -33,7 +33,7 @@ from app.models import (
Workspace, Workspace,
) )
from app.models.app_model import AppStatus, AppType from app.models.app_model import AppStatus, AppType
from app.repositories.app_repository import get_apps_by_id from app.repositories.app_repository import get_apps_by_id, AppRepository
from app.repositories.workflow_repository import WorkflowConfigRepository from app.repositories.workflow_repository import WorkflowConfigRepository
from app.schemas import app_schema from app.schemas import app_schema
from app.schemas.workflow_schema import WorkflowConfigUpdate from app.schemas.workflow_schema import WorkflowConfigUpdate
@@ -59,6 +59,7 @@ class AppService:
db: 数据库会话 db: 数据库会话
""" """
self.db = db self.db = db
self.app_repo = AppRepository(self.db)
# ==================== 私有辅助方法 ==================== # ==================== 私有辅助方法 ====================
@@ -521,6 +522,9 @@ class AppService:
"创建应用", "创建应用",
extra={"app_name": data.name, "type": data.type, "workspace_id": str(workspace_id)} extra={"app_name": data.name, "type": data.type, "workspace_id": str(workspace_id)}
) )
apps = self.app_repo.get_apps_by_name(data.name, data.type, workspace_id)
if apps:
raise BusinessException(message="已存在同名应用", code=BizCode.RESOURCE_ALREADY_EXISTS)
try: try:
now = datetime.datetime.now() now = datetime.datetime.now()
@@ -1368,6 +1372,15 @@ class AppService:
if not agent_cfg: if not agent_cfg:
raise BusinessException("Agent 应用缺少配置,无法发布", BizCode.AGENT_CONFIG_MISSING) raise BusinessException("Agent 应用缺少配置,无法发布", BizCode.AGENT_CONFIG_MISSING)
miss_params = []
if agent_cfg.default_model_config_id is None:
miss_params.append("model config")
if agent_cfg.memory.get("enabled") and not agent_cfg.memory.get("memory_config_id"):
miss_params.append("memory config")
if miss_params:
raise BusinessException(f"{', '.join(miss_params)} is required")
config = { config = {
"system_prompt": agent_cfg.system_prompt, "system_prompt": agent_cfg.system_prompt,
"model_parameters": model_parameters_to_dict(agent_cfg.model_parameters), "model_parameters": model_parameters_to_dict(agent_cfg.model_parameters),

View File

@@ -2,11 +2,11 @@ import datetime
import hashlib import hashlib
import secrets import secrets
import uuid import uuid
from os import getenv
from typing import List, Optional from typing import List, Optional
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.config.default_ontology_initializer import DefaultOntologyInitializer
from app.core.config import settings from app.core.config import settings
from app.core.error_codes import BizCode from app.core.error_codes import BizCode
from app.core.exceptions import BusinessException, PermissionDeniedException from app.core.exceptions import BusinessException, PermissionDeniedException
@@ -30,17 +30,15 @@ from app.schemas.workspace_schema import (
WorkspaceModelsUpdate, WorkspaceModelsUpdate,
WorkspaceUpdate, WorkspaceUpdate,
) )
from app.config.default_ontology_initializer import DefaultOntologyInitializer
# 获取业务逻辑专用日志器 # 获取业务逻辑专用日志器
business_logger = get_business_logger() business_logger = get_business_logger()
from dotenv import load_dotenv
load_dotenv()
def switch_workspace( def switch_workspace(
db: Session, db: Session,
workspace_id: uuid.UUID, workspace_id: uuid.UUID,
user: User, user: User,
): ):
"""切换工作空间""" """切换工作空间"""
business_logger.debug(f"用户 {user.username} 请求切换工作空间为 {workspace_id}") business_logger.debug(f"用户 {user.username} 请求切换工作空间为 {workspace_id}")
@@ -60,31 +58,32 @@ def switch_workspace(
raise BusinessException(f"切换工作空间失败: {str(e)}", BizCode.INTERNAL_ERROR) raise BusinessException(f"切换工作空间失败: {str(e)}", BizCode.INTERNAL_ERROR)
def delete_workspace_member( def delete_workspace_member(
db: Session, db: Session,
workspace_id: uuid.UUID, workspace_id: uuid.UUID,
member_id: uuid.UUID, member_id: uuid.UUID,
user: User, user: User,
): ):
"""删除工作空间成员""" """删除工作空间成员"""
business_logger.debug(f"用户 {user.username} 请求删除工作空间 {workspace_id} 的成员 {member_id}") business_logger.debug(f"用户 {user.username} 请求删除工作空间 {workspace_id} 的成员 {member_id}")
_check_workspace_admin_permission(db, workspace_id, user) _check_workspace_admin_permission(db, workspace_id, user)
workspace_member = workspace_repository.get_member_by_id(db=db, member_id=member_id) workspace_member = workspace_repository.get_member_by_id(db=db, member_id=member_id)
if not workspace_member: if not workspace_member:
raise BusinessException(f"工作空间成员 {member_id} 不存在", BizCode.WORKSPACE_NOT_FOUND) raise BusinessException(f"工作空间成员 {member_id} 不存在", BizCode.WORKSPACE_NOT_FOUND)
if workspace_member.workspace_id != workspace_id: if workspace_member.workspace_id != workspace_id:
raise BusinessException(f"工作空间成员 {member_id} 不存在于工作空间 {workspace_id}", BizCode.WORKSPACE_NOT_FOUND) raise BusinessException(f"工作空间成员 {member_id} 不存在于工作空间 {workspace_id}",
BizCode.WORKSPACE_NOT_FOUND)
try: try:
workspace_member.is_active = False workspace_member.is_active = False
workspace_member.user.current_workspace_id = None workspace_member.user.current_workspace_id = None
db.commit() db.commit()
business_logger.info(f"用户 {user.username} 成功删除工作空间 {workspace_id} 的成员 {member_id}") business_logger.info(f"用户 {user.username} 成功删除工作空间 {workspace_id} 的成员 {member_id}")
except Exception as e: except Exception as e:
db.rollback() db.rollback()
business_logger.error(f"删除工作空间成员失败 - 工作空间: {workspace_id}, 成员: {member_id}, 错误: {str(e)}") business_logger.error(f"删除工作空间成员失败 - 工作空间: {workspace_id}, 成员: {member_id}, 错误: {str(e)}")
raise BusinessException(f"删除工作空间成员失败: {str(e)}", BizCode.INTERNAL_ERROR) raise BusinessException(f"删除工作空间成员失败: {str(e)}", BizCode.INTERNAL_ERROR)
def get_user_workspaces(db: Session, user: User) -> List[Workspace]: def get_user_workspaces(db: Session, user: User) -> List[Workspace]:
@@ -102,19 +101,19 @@ def get_user_workspaces(db: Session, user: User) -> List[Workspace]:
""" """
business_logger.debug(f"获取用户工作空间列表: {user.username} (ID: {user.id})") business_logger.debug(f"获取用户工作空间列表: {user.username} (ID: {user.id})")
workspaces = workspace_repository.get_workspaces_by_user(db=db, user_id=user.id) workspaces = workspace_repository.get_workspaces_by_user(db=db, user_id=user.id)
# Ensure each neo4j workspace has a default memory config # Ensure each neo4j workspace has a default memory config
for workspace in workspaces: for workspace in workspaces:
if workspace.storage_type == 'neo4j': if workspace.storage_type == 'neo4j':
_ensure_default_memory_config(db, workspace) _ensure_default_memory_config(db, workspace)
_ensure_default_ontology_scenes(db, workspace) _ensure_default_ontology_scenes(db, workspace)
business_logger.info(f"用户 {user.username} 的工作空间数量: {len(workspaces)}") business_logger.info(f"用户 {user.username} 的工作空间数量: {len(workspaces)}")
return workspaces return workspaces
def _create_workspace_only( def _create_workspace_only(
db: Session, workspace: WorkspaceCreate, owner: User db: Session, workspace: WorkspaceCreate, owner: User
) -> Workspace: ) -> Workspace:
business_logger.debug(f"创建工作空间: {workspace.name}, 创建者: {owner.username}") business_logger.debug(f"创建工作空间: {workspace.name}, 创建者: {owner.username}")
@@ -138,9 +137,14 @@ def create_workspace(
f"创建工作空间: {workspace.name}, 创建者: {user.username}, " f"创建工作空间: {workspace.name}, 创建者: {user.username}, "
f"storage_type: {workspace.storage_type}" f"storage_type: {workspace.storage_type}"
) )
llm=workspace.llm if workspace_repository.get_workspaces_by_name(db=db, name=workspace.name, tenant_id=user.tenant_id):
embedding=workspace.embedding raise BusinessException(
rerank=workspace.rerank message="同名工作空间已存在",
code=BizCode.RESOURCE_ALREADY_EXISTS
)
llm = workspace.llm
embedding = workspace.embedding
rerank = workspace.rerank
try: try:
# Create the workspace without adding any members # Create the workspace without adding any members
business_logger.debug(f"创建工作空间: {workspace.name}") business_logger.debug(f"创建工作空间: {workspace.name}")
@@ -159,26 +163,26 @@ def create_workspace(
success, error_msg = initializer.initialize_default_scenes( success, error_msg = initializer.initialize_default_scenes(
db_workspace.id, language=language db_workspace.id, language=language
) )
if success: if success:
business_logger.info( business_logger.info(
f"为工作空间 {db_workspace.id} 创建默认本体场景成功 (language={language})" f"为工作空间 {db_workspace.id} 创建默认本体场景成功 (language={language})"
) )
# 获取默认场景ID优先使用"在线教育"场景,如果不存在则使用"情感陪伴"场景 # 获取默认场景ID优先使用"在线教育"场景,如果不存在则使用"情感陪伴"场景
from app.repositories.ontology_scene_repository import OntologySceneRepository from app.repositories.ontology_scene_repository import OntologySceneRepository
from app.config.default_ontology_config import ( from app.config.default_ontology_config import (
ONLINE_EDUCATION_SCENE, ONLINE_EDUCATION_SCENE,
EMOTIONAL_COMPANION_SCENE, EMOTIONAL_COMPANION_SCENE,
get_scene_name get_scene_name
) )
scene_repo = OntologySceneRepository(db) scene_repo = OntologySceneRepository(db)
# 优先尝试获取教育场景 # 优先尝试获取教育场景
education_scene_name = get_scene_name(ONLINE_EDUCATION_SCENE, language) education_scene_name = get_scene_name(ONLINE_EDUCATION_SCENE, language)
education_scene = scene_repo.get_by_name(education_scene_name, db_workspace.id) education_scene = scene_repo.get_by_name(education_scene_name, db_workspace.id)
if education_scene: if education_scene:
default_scene_id = education_scene.scene_id default_scene_id = education_scene.scene_id
default_scene_name = education_scene.scene_name default_scene_name = education_scene.scene_name
@@ -189,7 +193,7 @@ def create_workspace(
# 如果教育场景不存在,尝试获取情感陪伴场景 # 如果教育场景不存在,尝试获取情感陪伴场景
companion_scene_name = get_scene_name(EMOTIONAL_COMPANION_SCENE, language) companion_scene_name = get_scene_name(EMOTIONAL_COMPANION_SCENE, language)
companion_scene = scene_repo.get_by_name(companion_scene_name, db_workspace.id) companion_scene = scene_repo.get_by_name(companion_scene_name, db_workspace.id)
if companion_scene: if companion_scene:
default_scene_id = companion_scene.scene_id default_scene_id = companion_scene.scene_id
default_scene_name = companion_scene.scene_name default_scene_name = companion_scene.scene_name
@@ -256,10 +260,10 @@ def create_workspace(
avatar='', avatar='',
type=KnowledgeType.General, type=KnowledgeType.General,
permission_id=PermissionType.Memory, permission_id=PermissionType.Memory,
embedding_id=uuid.UUID(getenv('KB_embedding_id')) if None else embedding, embedding_id=embedding,
reranker_id=uuid.UUID(getenv('KB_reranker_id')) if None else rerank, reranker_id=rerank,
llm_id=uuid.UUID(getenv('KB_llm_id')) if None else llm, llm_id=llm,
image2text_id=uuid.UUID(getenv('KB_llm_id')) if None else llm, image2text_id=llm,
parser_config={ parser_config={
"layout_recognize": "DeepDOC", "layout_recognize": "DeepDOC",
"chunk_token_num": 256, "chunk_token_num": 256,
@@ -294,7 +298,7 @@ def create_workspace(
business_logger.info( business_logger.info(
f"工作空间 {db_workspace.id} 及相关资源创建完成并已提交" f"工作空间 {db_workspace.id} 及相关资源创建完成并已提交"
) )
return db_workspace return db_workspace
except Exception as e: except Exception as e:
@@ -304,11 +308,11 @@ def create_workspace(
def update_workspace( def update_workspace(
db: Session, workspace_id: uuid.UUID, workspace_in: WorkspaceUpdate, user: User db: Session, workspace_id: uuid.UUID, workspace_in: WorkspaceUpdate, user: User
) -> Workspace: ) -> Workspace:
business_logger.info(f"更新工作空间: workspace_id={workspace_id}, 操作者: {user.username}") business_logger.info(f"更新工作空间: workspace_id={workspace_id}, 操作者: {user.username}")
db_workspace = _check_workspace_admin_permission(db,workspace_id,user) db_workspace = _check_workspace_admin_permission(db, workspace_id, user)
try: try:
# 更新工作空间 # 更新工作空间
business_logger.debug(f"执行工作空间更新: {db_workspace.name} (ID: {workspace_id})") business_logger.debug(f"执行工作空间更新: {db_workspace.name} (ID: {workspace_id})")
@@ -328,7 +332,7 @@ def update_workspace(
def get_workspace_members( def get_workspace_members(
db: Session, workspace_id: uuid.UUID, user: User db: Session, workspace_id: uuid.UUID, user: User
) -> List[WorkspaceMember]: ) -> List[WorkspaceMember]:
"""获取某工作空间的成员列表(关系序列化由模型关系支持)""" """获取某工作空间的成员列表(关系序列化由模型关系支持)"""
business_logger.info(f"获取工作空间成员: workspace_id={workspace_id}, 操作者: {user.username}") business_logger.info(f"获取工作空间成员: workspace_id={workspace_id}, 操作者: {user.username}")
@@ -372,7 +376,6 @@ def get_workspace_members(
return members return members
# ==================== 邀请相关服务方法 ==================== # ==================== 邀请相关服务方法 ====================
def _generate_invite_token() -> tuple[str, str]: def _generate_invite_token() -> tuple[str, str]:
@@ -465,13 +468,14 @@ def _check_workspace_admin_permission(db: Session, workspace_id: uuid.UUID, user
def create_workspace_invite( def create_workspace_invite(
db: Session, db: Session,
workspace_id: uuid.UUID, workspace_id: uuid.UUID,
invite_data: WorkspaceInviteCreate, invite_data: WorkspaceInviteCreate,
user: User user: User
) -> WorkspaceInviteResponse: ) -> WorkspaceInviteResponse:
"""创建工作空间邀请""" """创建工作空间邀请"""
business_logger.info(f"创建工作空间邀请: workspace_id={workspace_id}, email={invite_data.email}, 创建者: {user.username}") business_logger.info(
f"创建工作空间邀请: workspace_id={workspace_id}, email={invite_data.email}, 创建者: {user.username}")
try: try:
# 检查权限 # 检查权限
@@ -534,17 +538,18 @@ def create_workspace_invite(
except Exception as e: except Exception as e:
db.rollback() db.rollback()
business_logger.error(f"创建工作空间邀请失败: workspace_id={workspace_id}, email={invite_data.email} - {str(e)}") business_logger.error(
f"创建工作空间邀请失败: workspace_id={workspace_id}, email={invite_data.email} - {str(e)}")
raise raise
def get_workspace_invites( def get_workspace_invites(
db: Session, db: Session,
workspace_id: uuid.UUID, workspace_id: uuid.UUID,
user: User, user: User,
status: Optional[InviteStatus] = None, status: Optional[InviteStatus] = None,
limit: int = 50, limit: int = 50,
offset: int = 0 offset: int = 0
) -> List[WorkspaceInviteResponse]: ) -> List[WorkspaceInviteResponse]:
"""获取工作空间邀请列表""" """获取工作空间邀请列表"""
business_logger.info(f"获取工作空间邀请列表: workspace_id={workspace_id}, 操作者: {user.username}") business_logger.info(f"获取工作空间邀请列表: workspace_id={workspace_id}, 操作者: {user.username}")
@@ -605,9 +610,9 @@ def validate_invite_token(db: Session, token: str) -> InviteValidateResponse:
def accept_workspace_invite( def accept_workspace_invite(
db: Session, db: Session,
accept_request: InviteAcceptRequest, accept_request: InviteAcceptRequest,
user: User user: User
) -> dict: ) -> dict:
"""接受工作空间邀请""" """接受工作空间邀请"""
business_logger.info(f"接受工作空间邀请: 用户 {user.username}") business_logger.info(f"接受工作空间邀请: 用户 {user.username}")
@@ -695,7 +700,8 @@ def accept_workspace_invite(
# 获取工作空间信息 # 获取工作空间信息
workspace = workspace_repository.get_workspace_by_id(db=db, workspace_id=invite.workspace_id) workspace = workspace_repository.get_workspace_by_id(db=db, workspace_id=invite.workspace_id)
business_logger.info(f"用户成功加入工作空间: user={user.username}, workspace={workspace.name}, role={workspace_role}") business_logger.info(
f"用户成功加入工作空间: user={user.username}, workspace={workspace.name}, role={workspace_role}")
return { return {
"message": "Successfully joined the workspace", "message": "Successfully joined the workspace",
@@ -710,13 +716,14 @@ def accept_workspace_invite(
def revoke_workspace_invite( def revoke_workspace_invite(
db: Session, db: Session,
workspace_id: uuid.UUID, workspace_id: uuid.UUID,
invite_id: uuid.UUID, invite_id: uuid.UUID,
user: User user: User
) -> dict: ) -> dict:
"""撤销工作空间邀请""" """撤销工作空间邀请"""
business_logger.info(f"撤销工作空间邀请: workspace_id={workspace_id}, invite_id={invite_id}, 操作者: {user.username}") business_logger.info(
f"撤销工作空间邀请: workspace_id={workspace_id}, invite_id={invite_id}, 操作者: {user.username}")
try: try:
# 检查权限 # 检查权限
@@ -745,13 +752,14 @@ def revoke_workspace_invite(
def update_workspace_member_roles( def update_workspace_member_roles(
db: Session, db: Session,
workspace_id: uuid.UUID, workspace_id: uuid.UUID,
updates: List[WorkspaceMemberUpdate], updates: List[WorkspaceMemberUpdate],
user: User, user: User,
) -> List[WorkspaceMember]: ) -> List[WorkspaceMember]:
"""更新工作空间成员角色""" """更新工作空间成员角色"""
business_logger.info(f"更新工作空间成员角色: workspace_id={workspace_id}, 操作者: {user.username}, 更新数量: {len(updates)}") business_logger.info(
f"更新工作空间成员角色: workspace_id={workspace_id}, 操作者: {user.username}, 更新数量: {len(updates)}")
# 检查管理员权限 # 检查管理员权限
_check_workspace_admin_permission(db, workspace_id, user) _check_workspace_admin_permission(db, workspace_id, user)
@@ -765,7 +773,8 @@ def update_workspace_member_roles(
for upd in updates: for upd in updates:
# 检查成员是否存在 # 检查成员是否存在
if upd.id not in member_map: if upd.id not in member_map:
raise BusinessException(f"成员 {upd.id} 不存在于工作空间 {workspace_id}", BizCode.WORKSPACE_MEMBER_NOT_FOUND) raise BusinessException(f"成员 {upd.id} 不存在于工作空间 {workspace_id}",
BizCode.WORKSPACE_MEMBER_NOT_FOUND)
member = member_map[upd.id] member = member_map[upd.id]
@@ -917,10 +926,10 @@ def get_workspace_models_configs(
def update_workspace_models_configs( def update_workspace_models_configs(
db: Session, db: Session,
workspace_id: uuid.UUID, workspace_id: uuid.UUID,
models_update: WorkspaceModelsUpdate, models_update: WorkspaceModelsUpdate,
user: User, user: User,
) -> Workspace: ) -> Workspace:
"""更新工作空间的模型配置llm, embedding, rerank """更新工作空间的模型配置llm, embedding, rerank
@@ -968,8 +977,8 @@ def update_workspace_models_configs(
def _fill_workspace_configs_model_defaults( def _fill_workspace_configs_model_defaults(
db: Session, db: Session,
workspace: Workspace workspace: Workspace
) -> None: ) -> None:
"""Fill empty model fields for all memory configs in a workspace. """Fill empty model fields for all memory configs in a workspace.
@@ -981,43 +990,43 @@ def _fill_workspace_configs_model_defaults(
workspace: The workspace containing default model settings workspace: The workspace containing default model settings
""" """
from app.models.memory_config_model import MemoryConfig from app.models.memory_config_model import MemoryConfig
# Get all configs for this workspace # Get all configs for this workspace
configs = db.query(MemoryConfig).filter( configs = db.query(MemoryConfig).filter(
MemoryConfig.workspace_id == workspace.id MemoryConfig.workspace_id == workspace.id
).all() ).all()
if not configs: if not configs:
return return
# Map of memory_config field -> workspace field # Map of memory_config field -> workspace field
model_field_mappings = [ model_field_mappings = [
("llm_id", "llm"), ("llm_id", "llm"),
("embedding_id", "embedding"), ("embedding_id", "embedding"),
("rerank_id", "rerank"), ("rerank_id", "rerank"),
("reflection_model_id", "llm"), # reflection uses LLM ("reflection_model_id", "llm"), # reflection uses LLM
("emotion_model_id", "llm"), # emotion uses LLM ("emotion_model_id", "llm"), # emotion uses LLM
] ]
configs_updated = 0 configs_updated = 0
for memory_config in configs: for memory_config in configs:
updated_fields = [] updated_fields = []
for config_field, workspace_field in model_field_mappings: for config_field, workspace_field in model_field_mappings:
config_value = getattr(memory_config, config_field, None) config_value = getattr(memory_config, config_field, None)
workspace_value = getattr(workspace, workspace_field, None) workspace_value = getattr(workspace, workspace_field, None)
if not config_value and workspace_value: if not config_value and workspace_value:
setattr(memory_config, config_field, workspace_value) setattr(memory_config, config_field, workspace_value)
updated_fields.append(config_field) updated_fields.append(config_field)
if updated_fields: if updated_fields:
configs_updated += 1 configs_updated += 1
business_logger.debug( business_logger.debug(
f"Updated memory config {memory_config.config_id} fields: {updated_fields}" f"Updated memory config {memory_config.config_id} fields: {updated_fields}"
) )
if configs_updated > 0: if configs_updated > 0:
try: try:
db.commit() db.commit()
@@ -1032,14 +1041,14 @@ def _fill_workspace_configs_model_defaults(
def _create_default_memory_config( def _create_default_memory_config(
db: Session, db: Session,
workspace_id: uuid.UUID, workspace_id: uuid.UUID,
workspace_name: str, workspace_name: str,
llm_id: Optional[uuid.UUID] = None, llm_id: Optional[uuid.UUID] = None,
embedding_id: Optional[uuid.UUID] = None, embedding_id: Optional[uuid.UUID] = None,
rerank_id: Optional[uuid.UUID] = None, rerank_id: Optional[uuid.UUID] = None,
scene_id: Optional[uuid.UUID] = None, scene_id: Optional[uuid.UUID] = None,
pruning_scene_name: Optional[str] = None, pruning_scene_name: Optional[str] = None,
) -> None: ) -> None:
"""Create a default memory config for a newly created workspace. """Create a default memory config for a newly created workspace.
@@ -1054,9 +1063,9 @@ def _create_default_memory_config(
pruning_scene_name: Optional pruning scene name取自 ontology_scene.scene_name pruning_scene_name: Optional pruning scene name取自 ontology_scene.scene_name
""" """
from app.models.memory_config_model import MemoryConfig from app.models.memory_config_model import MemoryConfig
config_id = uuid.uuid4() config_id = uuid.uuid4()
default_config = MemoryConfig( default_config = MemoryConfig(
config_id=config_id, config_id=config_id,
config_name=f"{workspace_name} 默认配置", config_name=f"{workspace_name} 默认配置",
@@ -1070,10 +1079,10 @@ def _create_default_memory_config(
state=True, # Active by default state=True, # Active by default
is_default=True, # Mark as workspace default is_default=True, # Mark as workspace default
) )
db.add(default_config) db.add(default_config)
db.flush() # 使用 flush 而不是 commit让调用者统一提交 db.flush() # 使用 flush 而不是 commit让调用者统一提交
business_logger.info( business_logger.info(
"Created default memory config for workspace", "Created default memory config for workspace",
extra={ extra={
@@ -1084,6 +1093,7 @@ def _create_default_memory_config(
} }
) )
# ==================== 检查配置相关服务 ==================== # ==================== 检查配置相关服务 ====================
def _ensure_default_memory_config(db: Session, workspace: Workspace) -> None: def _ensure_default_memory_config(db: Session, workspace: Workspace) -> None:
@@ -1096,19 +1106,19 @@ def _ensure_default_memory_config(db: Session, workspace: Workspace) -> None:
workspace: The workspace to check workspace: The workspace to check
""" """
from app.models.memory_config_model import MemoryConfig from app.models.memory_config_model import MemoryConfig
# Check if default config exists for this workspace # Check if default config exists for this workspace
existing_default = db.query(MemoryConfig).filter( existing_default = db.query(MemoryConfig).filter(
MemoryConfig.workspace_id == workspace.id, MemoryConfig.workspace_id == workspace.id,
MemoryConfig.is_default == True MemoryConfig.is_default == True
).first() ).first()
if not existing_default: if not existing_default:
# No default config exists, create one # No default config exists, create one
business_logger.info( business_logger.info(
f"Workspace {workspace.id} missing default memory config, creating one" f"Workspace {workspace.id} missing default memory config, creating one"
) )
# 尝试获取默认场景ID优先教育场景其次情感陪伴场景 # 尝试获取默认场景ID优先教育场景其次情感陪伴场景
default_scene_id = None default_scene_id = None
try: try:
@@ -1118,7 +1128,7 @@ def _ensure_default_memory_config(db: Session, workspace: Workspace) -> None:
EMOTIONAL_COMPANION_SCENE, EMOTIONAL_COMPANION_SCENE,
get_scene_name get_scene_name
) )
scene_repo = OntologySceneRepository(db) scene_repo = OntologySceneRepository(db)
# 尝试中文和英文场景名称 # 尝试中文和英文场景名称
for language in ["zh", "en"]: for language in ["zh", "en"]:
@@ -1131,7 +1141,7 @@ def _ensure_default_memory_config(db: Session, workspace: Workspace) -> None:
f"找到教育场景用于默认记忆配置: scene_id={default_scene_id}, scene_name={education_scene_name}" f"找到教育场景用于默认记忆配置: scene_id={default_scene_id}, scene_name={education_scene_name}"
) )
break break
# 如果教育场景不存在,尝试情感陪伴场景 # 如果教育场景不存在,尝试情感陪伴场景
companion_scene_name = get_scene_name(EMOTIONAL_COMPANION_SCENE, language) companion_scene_name = get_scene_name(EMOTIONAL_COMPANION_SCENE, language)
companion_scene = scene_repo.get_by_name(companion_scene_name, workspace.id) companion_scene = scene_repo.get_by_name(companion_scene_name, workspace.id)
@@ -1145,7 +1155,7 @@ def _ensure_default_memory_config(db: Session, workspace: Workspace) -> None:
business_logger.warning( business_logger.warning(
f"获取默认场景失败,将创建不关联场景的记忆配置: {str(scene_error)}" f"获取默认场景失败,将创建不关联场景的记忆配置: {str(scene_error)}"
) )
try: try:
_create_default_memory_config( _create_default_memory_config(
db=db, db=db,
@@ -1160,7 +1170,7 @@ def _ensure_default_memory_config(db: Session, workspace: Workspace) -> None:
business_logger.error( business_logger.error(
f"Failed to create default memory config for workspace {workspace.id}: {str(e)}" f"Failed to create default memory config for workspace {workspace.id}: {str(e)}"
) )
# Fill empty model fields for ALL configs in this workspace # Fill empty model fields for ALL configs in this workspace
_fill_workspace_configs_model_defaults(db, workspace) _fill_workspace_configs_model_defaults(db, workspace)
@@ -1209,4 +1219,3 @@ def _ensure_default_ontology_scenes(db: Session, workspace: Workspace) -> None:
business_logger.error( business_logger.error(
f"为工作空间 {workspace.id} 补建默认本体场景异常: {str(e)}" f"为工作空间 {workspace.id} 补建默认本体场景异常: {str(e)}"
) )