feat(rag): 新增 RAG Router + 掛載到 main.py (Phase 33 ADR-067)
All checks were successful
CD Pipeline / build-and-deploy (push) Successful in 13m11s
All checks were successful
CD Pipeline / build-and-deploy (push) Successful in 13m11s
- rag.py: POST /index, POST /query, GET /stats 三端點 - stats 委派給 KnowledgeRAGService.get_stats()(leWOOOgo 合規) - main.py: include_router rag_v1.router Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
102
apps/api/src/api/v1/rag.py
Normal file
102
apps/api/src/api/v1/rag.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""
|
||||
RAG 知識庫 API Router - Phase 33
|
||||
===================================
|
||||
leWOOOgo 原則: Router 只做 HTTP 轉發,業務邏輯在 KnowledgeRAGService
|
||||
|
||||
版本: v1.0
|
||||
建立: 2026-04-10 (台北時區)
|
||||
建立者: Claude Code (Phase 33 ADR-067)
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from src.services.knowledge_rag_service import get_knowledge_rag_service
|
||||
|
||||
router = APIRouter(prefix="/rag", tags=["RAG Knowledge Base"])
|
||||
|
||||
|
||||
class RagQueryRequest(BaseModel):
|
||||
question: str
|
||||
top_k: int = 5
|
||||
|
||||
|
||||
class RagQueryResponse(BaseModel):
|
||||
answer: str
|
||||
question: str
|
||||
|
||||
|
||||
class RagIndexResponse(BaseModel):
|
||||
status: str
|
||||
message: str
|
||||
|
||||
|
||||
@router.post("/index", response_model=RagIndexResponse, summary="觸發知識庫全量索引")
|
||||
async def trigger_index(background_tasks: BackgroundTasks) -> RagIndexResponse:
|
||||
"""
|
||||
觸發文件向量化索引(背景執行)
|
||||
|
||||
索引來源:
|
||||
- docs/runbooks/*.md
|
||||
- docs/adr/*.md
|
||||
- docs/LOGBOOK.md
|
||||
- .agents/skills/*.md
|
||||
"""
|
||||
background_tasks.add_task(_run_index)
|
||||
return RagIndexResponse(
|
||||
status="accepted",
|
||||
message="索引已排程,背景執行中(nomic-embed-text @ Ollama 188)",
|
||||
)
|
||||
|
||||
|
||||
@router.post("/query", response_model=RagQueryResponse, summary="語義查詢知識庫")
|
||||
async def query_rag(request: RagQueryRequest) -> RagQueryResponse:
|
||||
"""語義搜尋知識庫,用 deepseek-r1:14b 生成回答"""
|
||||
svc = get_knowledge_rag_service()
|
||||
answer = await svc.query(request.question, top_k=request.top_k)
|
||||
return RagQueryResponse(answer=answer, question=request.question)
|
||||
|
||||
|
||||
@router.get("/stats", summary="索引統計")
|
||||
async def rag_stats() -> dict:
|
||||
"""取得知識庫索引統計(chunk 數量等)"""
|
||||
svc = get_knowledge_rag_service()
|
||||
return await svc.get_stats()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Background helper
|
||||
# ============================================================
|
||||
|
||||
async def _run_index() -> None:
|
||||
"""背景:掃描所有文件來源並向量化"""
|
||||
import structlog
|
||||
from pathlib import Path
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
svc = get_knowledge_rag_service()
|
||||
|
||||
sources = [
|
||||
Path("docs/runbooks"),
|
||||
Path("docs/adr"),
|
||||
Path("docs"),
|
||||
Path(".agents/skills"),
|
||||
]
|
||||
|
||||
total = 0
|
||||
for source_dir in sources:
|
||||
if not source_dir.exists():
|
||||
continue
|
||||
pattern = "*.md"
|
||||
for md_file in source_dir.glob(pattern):
|
||||
try:
|
||||
count = await svc.index_document(
|
||||
file_path=md_file,
|
||||
source_type=source_dir.parts[0] if len(source_dir.parts) == 1 else source_dir.parts[1],
|
||||
)
|
||||
total += count
|
||||
logger.debug("rag_indexed", file=str(md_file), chunks=count)
|
||||
except Exception as e:
|
||||
logger.warning("rag_index_file_failed", file=str(md_file), error=str(e))
|
||||
|
||||
logger.info("rag_index_complete", total_chunks=total)
|
||||
@@ -59,6 +59,7 @@ from src.api.v1 import (
|
||||
signoz_webhook as signoz_webhook_v1, # Phase 21: SignOz → Telegram (ADR-037)
|
||||
)
|
||||
from src.api.v1 import drift as drift_v1 # Phase 25 P2: Config Drift Detection
|
||||
from src.api.v1 import rag as rag_v1 # Phase 33 ADR-067: RAG 知識庫
|
||||
from src.api.v1 import monitoring as monitoring_v1 # 2026-04-03: 監控工具狀態
|
||||
from src.api.v1 import stats as stats_v1 # Phase 6.5: Statistics Analytics
|
||||
from src.api.v1 import telegram as telegram_v1 # Phase 5.4: Telegram Gateway
|
||||
@@ -478,6 +479,9 @@ app.include_router(
|
||||
app.include_router(
|
||||
drift_v1.router, prefix="/api/v1", tags=["Drift Detection"]
|
||||
) # Phase 25 P2: Config Drift Detection
|
||||
app.include_router(
|
||||
rag_v1.router, prefix="/api/v1", tags=["RAG Knowledge Base"]
|
||||
) # Phase 33 ADR-067: RAG 知識庫
|
||||
app.include_router(
|
||||
errors_v1.router, prefix="/api/v1", tags=["Errors"]
|
||||
) # #40: Sentry 錯誤 BFF API
|
||||
|
||||
Reference in New Issue
Block a user