dce9e31bde
- 新增 JWT 认证模块,支持登录/注册/用户管理 - 新增文件上传接口,支持 .txt/.md/.html/.pdf/.docx 等格式解析入库 - 新增检索结果 AI 总结功能 - 新增文本去重缓存机制 - 新增全局认证夹具简化测试 - 新增配置项与环境变量支持 - 完善文档与测试覆盖
77 lines
2.7 KiB
Python
77 lines
2.7 KiB
Python
"""知识分类 API:GET /api/v1/knowledge/categories、GET /api/v1/knowledge/stats"""
|
||
|
||
from functools import lru_cache
|
||
from typing import Any
|
||
|
||
import structlog
|
||
from fastapi import APIRouter, Depends
|
||
|
||
from app.api.response import ApiError, ok
|
||
from app.config import settings
|
||
from app.core.auth import AuthUser, get_current_user
|
||
from app.models.knowledge import UNCATEGORIZED, TaxonomyCategory, load_taxonomy
|
||
from app.services.qdrant import ALL_COLLECTIONS, COLLECTION_L1, QdrantService
|
||
|
||
logger = structlog.get_logger()
|
||
|
||
router = APIRouter(prefix="/api/v1", tags=["knowledge"])
|
||
|
||
# 类目分布统计的分页大小
|
||
_STATS_SCROLL_PAGE_SIZE = 100
|
||
|
||
# 模块级懒加载单例,避免每请求重建 QdrantService
|
||
_qdrant: QdrantService | None = None
|
||
|
||
|
||
def _get_qdrant() -> QdrantService:
|
||
global _qdrant
|
||
if _qdrant is None:
|
||
_qdrant = QdrantService()
|
||
return _qdrant
|
||
|
||
|
||
@lru_cache(maxsize=1)
|
||
def _get_taxonomy() -> list[TaxonomyCategory]:
|
||
"""加载并缓存 taxonomy 类目集(进程内只加载一次)"""
|
||
return load_taxonomy(settings.taxonomy_path)
|
||
|
||
|
||
@router.get("/knowledge/categories")
|
||
async def list_categories(user: AuthUser = Depends(get_current_user)) -> dict[str, Any]:
|
||
"""返回完整知识分类类目集"""
|
||
categories = _get_taxonomy()
|
||
return ok({"categories": [c.model_dump() for c in categories], "count": len(categories)})
|
||
|
||
|
||
@router.get("/knowledge/stats")
|
||
async def knowledge_stats(user: AuthUser = Depends(get_current_user)) -> dict[str, Any]:
|
||
"""返回四层集合规模与 L1 类目分布统计"""
|
||
service = _get_qdrant()
|
||
try:
|
||
collections = {collection: await service.count(collection) for collection in ALL_COLLECTIONS}
|
||
categories = await _aggregate_l1_categories(service)
|
||
except Exception as exc:
|
||
logger.error("获取知识库统计失败", error=str(exc))
|
||
raise ApiError(2000, f"获取知识库统计失败: {exc}") from exc
|
||
return ok(
|
||
{
|
||
"collections": collections,
|
||
"categories": categories,
|
||
"uncategorized_count": categories.get(UNCATEGORIZED, 0),
|
||
"documents_total": collections[COLLECTION_L1],
|
||
}
|
||
)
|
||
|
||
|
||
async def _aggregate_l1_categories(service: QdrantService) -> dict[str, int]:
|
||
"""分页遍历 L1 文档,按 category 聚合文档数(空类目归入 uncategorized)"""
|
||
categories: dict[str, int] = {}
|
||
offset: str | None = None
|
||
while True:
|
||
items, offset = await service.scroll_l1(limit=_STATS_SCROLL_PAGE_SIZE, offset=offset)
|
||
for item in items:
|
||
category = item.get("category") or UNCATEGORIZED
|
||
categories[category] = categories.get(category, 0) + 1
|
||
if offset is None:
|
||
return categories
|