Files
MemoryBear/api/app/services/ontology_service.py

1170 lines
40 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""本体提取服务层
本模块提供本体提取的业务逻辑封装,协调OntologyExtractor和OWLValidator。
包括本体提取、OWL文件导出等功能。
Classes:
OntologyService: 本体提取服务类,封装业务逻辑
"""
import logging
import time
from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from app.core.memory.llm_tools.openai_client import OpenAIClient
from app.core.memory.models.ontology_scenario_models import (
OntologyClass,
OntologyExtractionResponse,
)
from app.core.memory.storage_services.extraction_engine.knowledge_extraction.ontology_extraction import (
OntologyExtractor,
)
from app.core.memory.utils.validation.owl_validator import OWLValidator
logger = logging.getLogger(__name__)
class OntologyService:
"""本体提取服务层
封装本体提取的业务逻辑,协调各个组件:
- OntologyExtractor: 执行LLM驱动的本体提取
- OWLValidator: OWL语义验证
Attributes:
extractor: 本体提取器实例
owl_validator: OWL验证器实例
db: 数据库会话
"""
# 默认配置参数
DEFAULT_MAX_CLASSES = 15
DEFAULT_MIN_CLASSES = 5
DEFAULT_MAX_DESCRIPTION_LENGTH = 500
DEFAULT_LLM_TEMPERATURE = 0.3
DEFAULT_LLM_MAX_TOKENS = 2000
DEFAULT_LLM_TIMEOUT = 30.0
DEFAULT_ENABLE_OWL_VALIDATION = True
# 从环境变量获取默认语言
from app.core.config import settings
DEFAULT_LANGUAGE = settings.DEFAULT_LANGUAGE
def __init__(
self,
llm_client: OpenAIClient,
db: Session
):
"""初始化本体提取服务
Args:
llm_client: OpenAI客户端实例
db: SQLAlchemy数据库会话
"""
self.extractor = OntologyExtractor(llm_client)
self.owl_validator = OWLValidator()
self.db = db
# 初始化Repository
from app.repositories.ontology_scene_repository import OntologySceneRepository
from app.repositories.ontology_class_repository import OntologyClassRepository
self.scene_repo = OntologySceneRepository(db)
self.class_repo = OntologyClassRepository(db)
logger.info("OntologyService initialized")
async def extract_ontology(
self,
scenario: str,
domain: Optional[str] = None,
scene_id: Optional[Any] = None,
workspace_id: Optional[Any] = None,
language: str = "zh"
) -> OntologyExtractionResponse:
"""执行本体提取
使用默认配置参数调用OntologyExtractor执行提取。
提取结果仅返回给前端,不会自动保存到数据库。
前端需要调用 /class 接口来保存选中的类型。
Args:
scenario: 场景描述文本
domain: 可选的领域提示
scene_id: 可选的场景ID,用于权限验证(不再用于自动保存)
workspace_id: 可选的工作空间ID,用于权限验证
language: 输出语言 ("zh" 中文, "en" 英文)
Returns:
OntologyExtractionResponse: 提取结果
Raises:
ValueError: 场景描述为空、场景不存在或无权限
RuntimeError: 提取过程失败
Examples:
>>> service = OntologyService(llm_client, db)
>>> response = await service.extract_ontology(
... scenario="医院管理患者记录...",
... domain="Healthcare",
... scene_id=scene_uuid,
... workspace_id=workspace_uuid
... )
>>> len(response.classes)
7
"""
# 开始计时
start_time = time.time()
# 验证输入
if not scenario or not scenario.strip():
logger.error("Scenario description is empty")
raise ValueError("Scenario description cannot be empty")
# 如果提供了scene_id,验证场景是否存在且有权限
if scene_id and workspace_id:
logger.info(f"Validating scene access - scene_id={scene_id}, workspace_id={workspace_id}")
scene = self.scene_repo.get_by_id(scene_id)
if not scene:
logger.warning(f"Scene not found: {scene_id}")
raise ValueError("场景不存在")
if not self.scene_repo.check_ownership(scene_id, workspace_id):
logger.warning(
f"Permission denied - scene_id={scene_id}, "
f"workspace_id={workspace_id}"
)
raise ValueError("无权限在该场景下创建类型")
logger.info(
f"Starting ontology extraction service - "
f"scenario_length={len(scenario)}, "
f"domain={domain}, "
f"scene_id={scene_id}"
)
try:
# 调用提取器执行提取(使用默认配置)
logger.info("Calling OntologyExtractor with default config")
extraction_start_time = time.time()
response = await self.extractor.extract_ontology_classes(
scenario=scenario,
domain=domain,
max_classes=self.DEFAULT_MAX_CLASSES,
min_classes=self.DEFAULT_MIN_CLASSES,
enable_owl_validation=self.DEFAULT_ENABLE_OWL_VALIDATION,
llm_temperature=self.DEFAULT_LLM_TEMPERATURE,
llm_max_tokens=self.DEFAULT_LLM_MAX_TOKENS,
max_description_length=self.DEFAULT_MAX_DESCRIPTION_LENGTH,
timeout=self.DEFAULT_LLM_TIMEOUT,
language=language,
)
extraction_duration = time.time() - extraction_start_time
# 检查是否成功提取到类
if not response.classes:
logger.error("Ontology extraction failed: No classes extracted (structured output may have failed)")
raise RuntimeError("本体提取失败:结构化输出失败,未能提取到任何本体类")
# 注释:提取结果仅返回给前端,不保存到数据库
# 前端将从返回结果中选择需要的类型,然后调用 /class 接口创建
logger.info(
f"Extraction completed. Classes will be saved to ontology_class "
f"via /class endpoint based on user selection"
)
total_duration = time.time() - start_time
# 记录提取统计
logger.info(
f"Ontology extraction service completed - "
f"extracted_classes={len(response.classes)}, "
f"domain={response.domain}, "
f"extraction_duration={extraction_duration:.2f}s, "
f"total_duration={total_duration:.2f}s"
)
return response
except ValueError:
# 重新抛出验证错误
total_duration = time.time() - start_time
logger.error(
f"Validation error after {total_duration:.2f}s",
exc_info=True
)
raise
except Exception as e:
total_duration = time.time() - start_time
error_msg = f"Ontology extraction failed after {total_duration:.2f}s: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
async def export_owl_file(
self,
classes: List[OntologyClass],
output_path: str,
format: str = "rdfxml",
) -> str:
"""导出OWL文件
将提取的本体类导出为OWL文件,支持多种格式。
Args:
classes: 本体类列表
output_path: 输出文件路径
format: 导出格式,可选值: "rdfxml", "turtle", "ntriples" (默认: "rdfxml")
Returns:
str: 导出的OWL文件内容
Raises:
ValueError: 类列表为空或格式不支持
RuntimeError: 导出失败
Examples:
>>> service = OntologyService(llm_client, db)
>>> owl_content = await service.export_owl_file(
... classes=response.classes,
... output_path="ontology.owl",
... format="rdfxml"
... )
"""
# 验证输入
if not classes:
logger.error("Classes list is empty")
raise ValueError("Classes list cannot be empty")
valid_formats = ["rdfxml", "turtle", "ntriples"]
if format not in valid_formats:
error_msg = f"Unsupported format '{format}'. Must be one of: {', '.join(valid_formats)}"
logger.error(error_msg)
raise ValueError(error_msg)
logger.info(
f"Starting OWL export - "
f"classes_count={len(classes)}, "
f"output_path={output_path}, "
f"format={format}"
)
try:
# 步骤1: 验证本体类
logger.debug("Validating ontology classes")
is_valid, errors, world = self.owl_validator.validate_ontology_classes(
classes=classes,
)
if not is_valid:
logger.warning(
f"OWL validation found {len(errors)} issues during export: {errors}"
)
# 继续导出,但记录警告
if not world:
error_msg = "Failed to create OWL world for export"
logger.error(error_msg)
raise RuntimeError(error_msg)
# 步骤2: 导出OWL文件
logger.info(f"Exporting to {format} format")
owl_content = self.owl_validator.export_to_owl(
world=world,
output_path=output_path,
format=format
)
logger.info(
f"OWL export completed - "
f"output_path={output_path}, "
f"content_length={len(owl_content)}"
)
return owl_content
except Exception as e:
error_msg = f"OWL export failed: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
# ==================== 本体场景管理方法 ====================
def create_scene(
self,
scene_name: str,
scene_description: Optional[str],
workspace_id: Any
):
"""创建本体场景
Args:
scene_name: 场景名称
scene_description: 场景描述
workspace_id: 所属工作空间ID
Returns:
OntologyScene: 创建的场景对象
Raises:
ValueError: 场景名称为空
RuntimeError: 创建失败
Examples:
>>> service = OntologyService(llm_client, db)
>>> scene = service.create_scene(
... "医疗场景",
... "用于医疗领域的本体建模",
... workspace_id
... )
"""
# 验证输入
if not scene_name or not scene_name.strip():
logger.error("Scene name is empty")
raise ValueError("场景名称不能为空")
logger.info(
f"Creating scene - "
f"name={scene_name}, workspace_id={workspace_id}"
)
try:
scene_data = {
"scene_name": scene_name.strip(),
"scene_description": scene_description
}
scene = self.scene_repo.create(scene_data, workspace_id)
self.db.commit()
logger.info(f"Scene created successfully: {scene.scene_id}")
return scene
except ValueError:
raise
except Exception as e:
self.db.rollback()
error_msg = f"Failed to create scene: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
def update_scene(
self,
scene_id: Any,
scene_name: Optional[str],
scene_description: Optional[str],
workspace_id: Any
):
"""更新本体场景
Args:
scene_id: 场景ID
scene_name: 场景名称(可选)
scene_description: 场景描述(可选)
workspace_id: 工作空间ID用于权限验证
Returns:
OntologyScene: 更新后的场景对象
Raises:
ValueError: 场景不存在或无权限
RuntimeError: 更新失败
Examples:
>>> service = OntologyService(llm_client, db)
>>> scene = service.update_scene(
... scene_id,
... "新名称",
... "新描述",
... workspace_id
... )
"""
logger.info(f"Updating scene: {scene_id}")
try:
# 检查场景是否存在
scene = self.scene_repo.get_by_id(scene_id)
if not scene:
logger.warning(f"Scene not found: {scene_id}")
raise ValueError("场景不存在")
# 检查权限
if not self.scene_repo.check_ownership(scene_id, workspace_id):
logger.warning(
f"Permission denied - scene_id={scene_id}, "
f"workspace_id={workspace_id}"
)
raise ValueError("无权限操作该场景")
# 准备更新数据
update_data = {}
if scene_name is not None:
if not scene_name.strip():
raise ValueError("场景名称不能为空")
update_data["scene_name"] = scene_name.strip()
if scene_description is not None:
update_data["scene_description"] = scene_description
# 如果没有更新数据,直接返回
if not update_data:
logger.info("No update data provided, returning existing scene")
return scene
# 执行更新
updated_scene = self.scene_repo.update(scene_id, update_data)
self.db.commit()
logger.info(f"Scene updated successfully: {scene_id}")
return updated_scene
except ValueError:
raise
except Exception as e:
self.db.rollback()
error_msg = f"Failed to update scene: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
def delete_scene(
self,
scene_id: Any,
workspace_id: Any
) -> bool:
"""删除本体场景
Args:
scene_id: 场景ID
workspace_id: 工作空间ID用于权限验证
Returns:
bool: 删除成功返回True
Raises:
ValueError: 场景不存在或无权限
RuntimeError: 删除失败
Examples:
>>> service = OntologyService(llm_client, db)
>>> success = service.delete_scene(scene_id, workspace_id)
"""
logger.info(f"Deleting scene: {scene_id}")
try:
# 检查场景是否存在
scene = self.scene_repo.get_by_id(scene_id)
if not scene:
logger.warning(f"Scene not found: {scene_id}")
raise ValueError("场景不存在")
# 检查权限
if not self.scene_repo.check_ownership(scene_id, workspace_id):
logger.warning(
f"Permission denied - scene_id={scene_id}, "
f"workspace_id={workspace_id}"
)
raise ValueError("无权限操作该场景")
# 执行删除
success = self.scene_repo.delete(scene_id)
self.db.commit()
logger.info(f"Scene deleted successfully: {scene_id}")
return success
except ValueError:
raise
except Exception as e:
self.db.rollback()
error_msg = f"Failed to delete scene: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
def get_scene_by_id(
self,
scene_id: Any,
workspace_id: Any
):
"""获取单个场景
Args:
scene_id: 场景ID
workspace_id: 工作空间ID用于权限验证
Returns:
Optional[OntologyScene]: 场景对象
Raises:
ValueError: 场景不存在或无权限
Examples:
>>> service = OntologyService(llm_client, db)
>>> scene = service.get_scene_by_id(scene_id, workspace_id)
"""
logger.debug(f"Getting scene by ID: {scene_id}")
try:
# 获取场景
scene = self.scene_repo.get_by_id(scene_id)
if not scene:
logger.warning(f"Scene not found: {scene_id}")
raise ValueError("场景不存在")
# 检查权限
if not self.scene_repo.check_ownership(scene_id, workspace_id):
logger.warning(
f"Permission denied - scene_id={scene_id}, "
f"workspace_id={workspace_id}"
)
raise ValueError("无权限访问该场景")
return scene
except ValueError:
raise
except Exception as e:
error_msg = f"Failed to get scene: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
def get_scene_by_name(
self,
scene_name: str,
workspace_id: Any
):
"""根据场景名称获取场景(精确匹配)
Args:
scene_name: 场景名称
workspace_id: 工作空间ID
Returns:
Optional[OntologyScene]: 场景对象
Raises:
ValueError: 场景不存在
Examples:
>>> service = OntologyService(llm_client, db)
>>> scene = service.get_scene_by_name("医疗场景", workspace_id)
"""
logger.debug(f"Getting scene by name: {scene_name}, workspace_id: {workspace_id}")
try:
# 获取场景
scene = self.scene_repo.get_by_name(scene_name, workspace_id)
if not scene:
logger.warning(f"Scene not found: {scene_name} in workspace {workspace_id}")
raise ValueError("场景不存在")
return scene
except ValueError:
raise
except Exception as e:
error_msg = f"Failed to get scene by name: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
def search_scenes_by_name(
self,
keyword: str,
workspace_id: Any
) -> List:
"""根据关键词模糊搜索场景
Args:
keyword: 搜索关键词
workspace_id: 工作空间ID
Returns:
List[OntologyScene]: 匹配的场景列表
Raises:
RuntimeError: 搜索失败
Examples:
>>> service = OntologyService(llm_client, db)
>>> scenes = service.search_scenes_by_name("医疗", workspace_id)
"""
logger.debug(f"Searching scenes by keyword: {keyword}, workspace_id: {workspace_id}")
try:
scenes = self.scene_repo.search_by_name(keyword, workspace_id)
logger.info(
f"Found {len(scenes)} scenes matching keyword '{keyword}' "
f"in workspace {workspace_id}"
)
return scenes
except Exception as e:
error_msg = f"Failed to search scenes by keyword: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
def list_scenes(
self,
workspace_id: Any,
page: Optional[int] = None,
page_size: Optional[int] = None
) -> tuple:
"""获取工作空间下的所有场景(支持分页)
Args:
workspace_id: 工作空间ID
page: 页码可选从1开始
page_size: 每页数量(可选)
Returns:
tuple: (场景列表, 总数量)
Raises:
RuntimeError: 查询失败
Examples:
>>> service = OntologyService(llm_client, db)
>>> scenes, total = service.list_scenes(workspace_id)
>>> scenes, total = service.list_scenes(workspace_id, page=1, page_size=10)
"""
logger.debug(f"Listing scenes for workspace: {workspace_id}, page={page}, page_size={page_size}")
try:
scenes, total = self.scene_repo.get_by_workspace(workspace_id, page, page_size)
logger.info(f"Found {len(scenes)} scenes (total: {total}) in workspace {workspace_id}")
return scenes, total
except Exception as e:
error_msg = f"Failed to list scenes: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
# ==================== 本体类型管理方法 ====================
def create_class(
self,
scene_id: Any,
class_name: str,
class_description: Optional[str],
workspace_id: Any
):
"""创建本体类型
Args:
scene_id: 所属场景ID
class_name: 类型名称
class_description: 类型描述
workspace_id: 工作空间ID用于权限验证
Returns:
OntologyClass: 创建的类型对象
Raises:
ValueError: 类型名称为空、场景不存在或无权限
RuntimeError: 创建失败
Examples:
>>> service = OntologyService(llm_client, db)
>>> ontology_class = service.create_class(
... scene_id,
... "患者",
... "医院患者信息",
... workspace_id
... )
"""
# 验证输入
if not class_name or not class_name.strip():
logger.error("Class name is empty")
raise ValueError("类型名称不能为空")
logger.info(
f"Creating class - "
f"name={class_name}, scene_id={scene_id}"
)
try:
# 检查场景是否存在且属于当前工作空间
scene = self.scene_repo.get_by_id(scene_id)
if not scene:
logger.warning(f"Scene not found: {scene_id}")
raise ValueError("所属场景不存在")
if not self.scene_repo.check_ownership(scene_id, workspace_id):
logger.warning(
f"Permission denied - scene_id={scene_id}, "
f"workspace_id={workspace_id}"
)
raise ValueError("无权限在该场景下创建类型")
# 创建类型
class_data = {
"class_name": class_name.strip(),
"class_description": class_description
}
ontology_class = self.class_repo.create(class_data, scene_id)
self.db.commit()
logger.info(f"Class created successfully: {ontology_class.class_id}")
return ontology_class
except ValueError:
raise
except Exception as e:
self.db.rollback()
error_msg = f"Failed to create class: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
def create_classes_batch(
self,
scene_id: Any,
classes: List[Dict[str, Optional[str]]],
workspace_id: Any
):
"""批量创建本体类型
Args:
scene_id: 所属场景ID
classes: 类型列表,每个元素包含 class_name 和 class_description
workspace_id: 工作空间ID用于权限验证
Returns:
Tuple[List, List[str]]: (成功创建的类型列表, 错误信息列表)
Raises:
ValueError: 场景不存在或无权限
Examples:
>>> service = OntologyService(llm_client, db)
>>> classes_data = [
... {"class_name": "患者", "class_description": "医院患者信息"},
... {"class_name": "医生", "class_description": "医院医生信息"}
... ]
>>> created_classes, errors = service.create_classes_batch(
... scene_id,
... classes_data,
... workspace_id
... )
"""
logger.info(
f"Batch creating classes - "
f"count={len(classes)}, scene_id={scene_id}"
)
# 检查场景是否存在且属于当前工作空间(只检查一次)
scene = self.scene_repo.get_by_id(scene_id)
if not scene:
logger.warning(f"Scene not found: {scene_id}")
raise ValueError("所属场景不存在")
if not self.scene_repo.check_ownership(scene_id, workspace_id):
logger.warning(
f"Permission denied - scene_id={scene_id}, "
f"workspace_id={workspace_id}"
)
raise ValueError("无权限在该场景下创建类型")
created_classes = []
errors = []
for idx, class_data in enumerate(classes):
class_name = class_data.get("class_name", "").strip()
class_description = class_data.get("class_description")
if not class_name:
error_msg = f"{idx + 1} 个类型名称为空,已跳过"
logger.warning(error_msg)
errors.append(error_msg)
continue
try:
# 创建类型(不需要再次检查权限)
create_data = {
"class_name": class_name,
"class_description": class_description
}
ontology_class = self.class_repo.create(create_data, scene_id)
created_classes.append(ontology_class)
logger.info(f"Class created successfully: {class_name}")
except Exception as e:
error_msg = f"创建类型 '{class_name}' 失败: {str(e)}"
logger.error(error_msg)
errors.append(error_msg)
# 统一提交所有成功的创建
try:
self.db.commit()
logger.info(
f"Batch creation completed - "
f"success={len(created_classes)}, failed={len(errors)}"
)
except Exception as e:
self.db.rollback()
error_msg = f"批量创建提交失败: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
return created_classes, errors
def update_class(
self,
class_id: Any,
class_name: Optional[str],
class_description: Optional[str],
workspace_id: Any
):
"""更新本体类型
Args:
class_id: 类型ID
class_name: 类型名称(可选)
class_description: 类型描述(可选)
workspace_id: 工作空间ID用于权限验证
Returns:
OntologyClass: 更新后的类型对象
Raises:
ValueError: 类型不存在或无权限
RuntimeError: 更新失败
Examples:
>>> service = OntologyService(llm_client, db)
>>> ontology_class = service.update_class(
... class_id,
... "新名称",
... "新描述",
... workspace_id
... )
"""
logger.info(f"Updating class: {class_id}")
try:
# 检查类型是否存在
ontology_class = self.class_repo.get_by_id(class_id)
if not ontology_class:
logger.warning(f"Class not found: {class_id}")
raise ValueError("类型不存在")
# 检查权限(通过场景关联)
if not self.class_repo.check_ownership(class_id, workspace_id):
logger.warning(
f"Permission denied - class_id={class_id}, "
f"workspace_id={workspace_id}"
)
raise ValueError("无权限操作该类型")
# 准备更新数据
update_data = {}
if class_name is not None:
if not class_name.strip():
raise ValueError("类型名称不能为空")
update_data["class_name"] = class_name.strip()
if class_description is not None:
update_data["class_description"] = class_description
# 如果没有更新数据,直接返回
if not update_data:
logger.info("No update data provided, returning existing class")
return ontology_class
# 执行更新
updated_class = self.class_repo.update(class_id, update_data)
self.db.commit()
logger.info(f"Class updated successfully: {class_id}")
return updated_class
except ValueError:
raise
except Exception as e:
self.db.rollback()
error_msg = f"Failed to update class: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
def delete_class(
self,
class_id: Any,
workspace_id: Any
) -> bool:
"""删除本体类型
Args:
class_id: 类型ID
workspace_id: 工作空间ID用于权限验证
Returns:
bool: 删除成功返回True
Raises:
ValueError: 类型不存在或无权限
RuntimeError: 删除失败
Examples:
>>> service = OntologyService(llm_client, db)
>>> success = service.delete_class(class_id, workspace_id)
"""
logger.info(f"Deleting class: {class_id}")
try:
# 检查类型是否存在
ontology_class = self.class_repo.get_by_id(class_id)
if not ontology_class:
logger.warning(f"Class not found: {class_id}")
raise ValueError("类型不存在")
# 检查权限(通过场景关联)
if not self.class_repo.check_ownership(class_id, workspace_id):
logger.warning(
f"Permission denied - class_id={class_id}, "
f"workspace_id={workspace_id}"
)
raise ValueError("无权限操作该类型")
# 执行删除
success = self.class_repo.delete(class_id)
self.db.commit()
logger.info(f"Class deleted successfully: {class_id}")
return success
except ValueError:
raise
except Exception as e:
self.db.rollback()
error_msg = f"Failed to delete class: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
def get_class_by_id(
self,
class_id: Any,
workspace_id: Any
):
"""获取单个类型
Args:
class_id: 类型ID
workspace_id: 工作空间ID用于权限验证
Returns:
Optional[OntologyClass]: 类型对象
Raises:
ValueError: 类型不存在或无权限
Examples:
>>> service = OntologyService(llm_client, db)
>>> ontology_class = service.get_class_by_id(class_id, workspace_id)
"""
logger.debug(f"Getting class by ID: {class_id}")
try:
# 获取类型
ontology_class = self.class_repo.get_by_id(class_id)
if not ontology_class:
logger.warning(f"Class not found: {class_id}")
raise ValueError("类型不存在")
# 检查权限(通过场景关联)
if not self.class_repo.check_ownership(class_id, workspace_id):
logger.warning(
f"Permission denied - class_id={class_id}, "
f"workspace_id={workspace_id}"
)
raise ValueError("无权限访问该类型")
return ontology_class
except ValueError:
raise
except Exception as e:
error_msg = f"Failed to get class: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
def get_class_by_name(
self,
class_name: str,
scene_id: Any,
workspace_id: Any
):
"""根据类型名称获取类型(精确匹配)
Args:
class_name: 类型名称
scene_id: 场景ID
workspace_id: 工作空间ID用于权限验证
Returns:
Optional[OntologyClass]: 类型对象
Raises:
ValueError: 类型不存在或无权限
Examples:
>>> service = OntologyService(llm_client, db)
>>> ontology_class = service.get_class_by_name("患者", scene_id, workspace_id)
"""
logger.debug(f"Getting class by name: {class_name}, scene_id: {scene_id}")
try:
# 检查场景是否存在且属于当前工作空间
scene = self.scene_repo.get_by_id(scene_id)
if not scene:
logger.warning(f"Scene not found: {scene_id}")
raise ValueError("场景不存在")
if not self.scene_repo.check_ownership(scene_id, workspace_id):
logger.warning(
f"Permission denied - scene_id={scene_id}, "
f"workspace_id={workspace_id}"
)
raise ValueError("无权限访问该场景")
# 获取类型
ontology_class = self.class_repo.get_by_name(class_name, scene_id)
if not ontology_class:
logger.warning(f"Class not found: {class_name} in scene {scene_id}")
raise ValueError("类型不存在")
return ontology_class
except ValueError:
raise
except Exception as e:
error_msg = f"Failed to get class by name: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
def search_classes_by_name(
self,
keyword: str,
scene_id: Any,
workspace_id: Any
) -> List:
"""根据关键词模糊搜索类型
Args:
keyword: 搜索关键词
scene_id: 场景ID
workspace_id: 工作空间ID用于权限验证
Returns:
List[OntologyClass]: 匹配的类型列表
Raises:
ValueError: 场景不存在或无权限
RuntimeError: 搜索失败
Examples:
>>> service = OntologyService(llm_client, db)
>>> classes = service.search_classes_by_name("患者", scene_id, workspace_id)
"""
logger.debug(
f"Searching classes by keyword: {keyword}, "
f"scene_id: {scene_id}, workspace_id: {workspace_id}"
)
try:
# 检查场景是否存在且属于当前工作空间
scene = self.scene_repo.get_by_id(scene_id)
if not scene:
logger.warning(f"Scene not found: {scene_id}")
raise ValueError("场景不存在")
if not self.scene_repo.check_ownership(scene_id, workspace_id):
logger.warning(
f"Permission denied - scene_id={scene_id}, "
f"workspace_id={workspace_id}"
)
raise ValueError("无权限访问该场景")
# 搜索类型
classes = self.class_repo.search_by_name(keyword, scene_id)
logger.info(
f"Found {len(classes)} classes matching keyword '{keyword}' "
f"in scene {scene_id}"
)
return classes
except ValueError:
raise
except Exception as e:
error_msg = f"Failed to search classes by keyword: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e
def list_classes_by_scene(
self,
scene_id: Any,
workspace_id: Any
) -> List:
"""获取场景下的所有类型
Args:
scene_id: 场景ID
workspace_id: 工作空间ID用于权限验证
Returns:
List[OntologyClass]: 类型列表
Raises:
ValueError: 场景不存在或无权限
RuntimeError: 查询失败
Examples:
>>> service = OntologyService(llm_client, db)
>>> classes = service.list_classes_by_scene(scene_id, workspace_id)
"""
logger.debug(f"Listing classes for scene: {scene_id}")
try:
# 检查场景是否存在且属于当前工作空间
scene = self.scene_repo.get_by_id(scene_id)
if not scene:
logger.warning(f"Scene not found: {scene_id}")
raise ValueError("场景不存在")
if not self.scene_repo.check_ownership(scene_id, workspace_id):
logger.warning(
f"Permission denied - scene_id={scene_id}, "
f"workspace_id={workspace_id}"
)
raise ValueError("无权限访问该场景的类型")
# 获取类型列表
classes = self.class_repo.get_classes_by_scene(scene_id)
logger.info(f"Found {len(classes)} classes in scene {scene_id}")
return classes
except ValueError:
raise
except Exception as e:
error_msg = f"Failed to list classes: {str(e)}"
logger.error(error_msg, exc_info=True)
raise RuntimeError(error_msg) from e