Initial commit: QMDSearch 分层信息检索服务
- FastAPI + Qdrant + Redis + Ollama 技术栈 - L1→L2→L3→chunk 四层分层检索(dense + sparse RRF 融合) - 文档三级总结与 2.5 级回退 - query 解析路由与分类 - /admin 管理页面
This commit is contained in:
+79
@@ -0,0 +1,79 @@
|
||||
from collections.abc import AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
|
||||
from app.api.response import ApiError, error
|
||||
from app.api.v1.document import router as document_router
|
||||
from app.api.v1.knowledge import router as knowledge_router
|
||||
from app.api.v1.search import router as search_router
|
||||
from app.config import settings
|
||||
from app.services.qdrant import QdrantService
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
|
||||
"""应用生命周期:启动时初始化 Qdrant 集合
|
||||
|
||||
初始化失败仅记录日志、不阻止启动(本地开发可能无 Qdrant)。
|
||||
"""
|
||||
try:
|
||||
await QdrantService().ensure_collections()
|
||||
logger.info("Qdrant 集合初始化完成")
|
||||
except Exception:
|
||||
logger.error("Qdrant 集合初始化失败,跳过初始化继续启动")
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title=settings.app_name,
|
||||
description="AI Agent 分层信息检索服务",
|
||||
version="0.1.0",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
app.include_router(search_router)
|
||||
app.include_router(document_router)
|
||||
app.include_router(knowledge_router)
|
||||
|
||||
|
||||
@app.exception_handler(ApiError)
|
||||
async def api_error_handler(request: Request, exc: ApiError) -> JSONResponse:
|
||||
"""业务异常 → 统一错误响应(code 取自异常)"""
|
||||
return JSONResponse(content=error(exc.code, exc.message))
|
||||
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_error_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
|
||||
"""请求参数校验失败 → code 1001"""
|
||||
errors = exc.errors()
|
||||
message = f"请求参数校验失败: {errors[0]['msg']}" if errors else "请求参数校验失败"
|
||||
return JSONResponse(content=error(1001, message))
|
||||
|
||||
|
||||
@app.exception_handler(Exception)
|
||||
async def unhandled_error_handler(request: Request, exc: Exception) -> JSONResponse:
|
||||
"""未捕获异常 → code 2000"""
|
||||
logger.exception("未捕获异常", path=request.url.path)
|
||||
return JSONResponse(content=error(2000, "服务器内部错误"))
|
||||
|
||||
|
||||
@app.get("/api/v1/health")
|
||||
async def health() -> dict[str, str]:
|
||||
"""健康检查"""
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
_ADMIN_HTML = Path(__file__).resolve().parent / "static" / "admin.html"
|
||||
|
||||
|
||||
@app.get("/admin", include_in_schema=False)
|
||||
async def admin_page() -> FileResponse:
|
||||
"""管理后台单页(单文件静态 HTML,零外部依赖)"""
|
||||
return FileResponse(_ADMIN_HTML, media_type="text/html")
|
||||
Reference in New Issue
Block a user