Files
2026-07-13 12:35:57 +08:00

237 lines
7.6 KiB
Python

from fastapi import APIRouter, Depends, HTTPException
from .models import RetrievalRequest, RetrievalResponse, ErrorResponse
from .auth import verify_api_key, validate_knowledge_id
from services.knowledge_service import KnowledgeService
import logging
logger = logging.getLogger(__name__)
router = APIRouter(tags=["Knowledge"])
knowledge_service = KnowledgeService()
@router.post(
"/retrieval",
response_model=RetrievalResponse,
responses={
403: {"model": ErrorResponse},
500: {"model": ErrorResponse},
404: {"model": ErrorResponse}
},
summary="知识库检索",
description="根据查询文本检索相关的知识库内容"
)
async def retrieval(
request: RetrievalRequest,
api_key: str = Depends(verify_api_key)
):
"""
知识库检索API - 兼容Dify外部知识库接口
"""
try:
logger.info(f"收到检索请求: knowledge_id={request.knowledge_id}, query='{request.query[:50]}...'")
# 验证知识库ID
if not validate_knowledge_id(request.knowledge_id):
raise HTTPException(
status_code=404,
detail={
"error_code": 2001,
"error_msg": "知识库不存在"
}
)
# 调用服务层处理
result = await knowledge_service.search(request)
logger.info(f"检索完成: 返回 {len(result.records)} 条记录")
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"检索过程中发生错误: {e}")
error_msg = str(e)
# 检查是否是知识库不存在的错误
if "不存在" in error_msg and "知识库" in error_msg:
raise HTTPException(
status_code=404,
detail={
"error_code": 2001,
"error_msg": error_msg
}
)
raise HTTPException(
status_code=500,
detail={
"error_code": 500,
"error_msg": f"内部服务器错误: {error_msg}"
}
)
@router.get("/collections", summary="列出所有知识库")
async def list_collections(api_key: str = Depends(verify_api_key)):
"""
列出所有可用的知识库集合
"""
try:
collections = await knowledge_service.list_collections()
return {"collections": collections}
except Exception as e:
logger.error(f"获取集合列表失败: {e}")
raise HTTPException(
status_code=500,
detail={
"error_code": 500,
"error_msg": f"获取集合列表失败: {str(e)}"
}
)
@router.get("/collections/{collection_name}/stats", summary="获取知识库统计信息")
async def get_collection_stats(
collection_name: str,
api_key: str = Depends(verify_api_key)
):
"""
获取指定知识库的统计信息
"""
try:
stats = await knowledge_service.get_collection_stats(collection_name)
return stats
except Exception as e:
logger.error(f"获取集合统计信息失败: {e}")
raise HTTPException(
status_code=500,
detail={
"error_code": 500,
"error_msg": f"获取统计信息失败: {str(e)}"
}
)
from services.similarity_service import SimilarityCalculator
from fastapi import HTTPException
from pydantic import BaseModel, Field
from typing import Optional
from config.settings import settings
import time
# 全局相似度计算器实例
similarity_calculator = None
# Pydantic模型定义
class SimilarityRequest(BaseModel):
x_collection: str = Field(..., description="X轴collection名称")
y_collection: str = Field(..., description="Y轴collection名称")
x_max_items: Optional[int] = Field(100, description="X轴最大项目数")
y_max_items: Optional[int] = Field(100, description="Y轴最大项目数")
max_items: Optional[int] = Field(100, description="最大项目数(向后兼容)")
def init_similarity_calculator():
"""初始化相似度计算器"""
global similarity_calculator
try:
similarity_calculator = SimilarityCalculator()
logger.info("✅ 相似度计算器初始化成功")
except Exception as e:
logger.error(f"❌ 相似度计算器初始化失败: {e}")
similarity_calculator = None
@router.get("/similarity/health")
async def similarity_health_check():
"""相似度服务健康检查"""
try:
calculator_ready = similarity_calculator is not None
connection_info = {}
if similarity_calculator:
connection_info = similarity_calculator.test_connection()
return {
"status": "healthy",
"calculator_ready": calculator_ready,
"timestamp": time.time(),
"connections": connection_info,
"vector_db_type": settings.vector_db_type
}
except Exception as e:
raise HTTPException(
status_code=500,
detail={
"status": "error",
"calculator_ready": False,
"error": str(e),
"timestamp": time.time()
}
)
@router.get("/similarity/collections")
async def get_similarity_collections():
"""获取所有collections"""
try:
if not similarity_calculator:
init_similarity_calculator()
if not similarity_calculator:
raise HTTPException(status_code=500, detail="相似度计算器未初始化")
collections = similarity_calculator.get_collections()
return {
"success": True,
"collections": collections,
"count": len(collections),
"vector_db_type": settings.vector_db_type
}
except Exception as e:
raise HTTPException(
status_code=500,
detail={"success": False, "error": str(e)}
)
@router.post("/similarity/calculate")
async def calculate_similarity_matrix(request: SimilarityRequest):
"""计算相似度矩阵"""
try:
if not similarity_calculator:
init_similarity_calculator()
if not similarity_calculator:
raise HTTPException(status_code=500, detail="相似度计算器未初始化")
# 解析参数
x_collection = request.x_collection
y_collection = request.y_collection
x_max_items = int(request.x_max_items or request.max_items or 100)
y_max_items = int(request.y_max_items or request.max_items or 100)
# 限制最大值
x_max_items = min(x_max_items, 3000)
y_max_items = min(y_max_items, 3000)
logger.info(f"🎯 收到相似度计算请求:")
logger.info(f" X: {x_collection} (最大{x_max_items}项)")
logger.info(f" Y: {y_collection} (最大{y_max_items}项)")
# 计算相似度矩阵
result = similarity_calculator.calculate_similarity_matrix(
x_collection=x_collection,
y_collection=y_collection,
x_max_items=x_max_items,
y_max_items=y_max_items
)
return {
"success": True,
"result": result,
"message": f"成功计算 {len(result['y_data'])} x {len(result['x_data'])} 相似度矩阵",
"vector_db_type": settings.vector_db_type
}
except Exception as e:
logger.error(f"❌ 相似度计算API失败: {e}")
raise HTTPException(
status_code=500,
detail={"success": False, "error": str(e)}
)