feat(rag): Phase 33 RAG API 端點 — /knowledge/rag/index + query + stats
All checks were successful
CD Pipeline / build-and-deploy (push) Successful in 14m35s
All checks were successful
CD Pipeline / build-and-deploy (push) Successful in 14m35s
ADR-067 Phase 33: pgvector RAG 三個 HTTP 端點 - POST /knowledge/rag/index — 索引文件到 rag_chunks - GET /knowledge/rag/query — embed→knn→生成答案 - GET /knowledge/rag/stats — chunks 統計 (透過 Service 層) - 修正: rag_stats 移至 KnowledgeRAGService.get_stats() (leWOOOgo 積木化) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -152,3 +152,48 @@ async def archive_entry(entry_id: str) -> None:
|
||||
success = await service.archive_entry(entry_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Knowledge entry not found")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Phase 33 (ADR-067 2026-04-10): RAG pgvector 端點
|
||||
# =============================================================================
|
||||
|
||||
@router.post("/rag/index", status_code=200)
|
||||
async def rag_index_document(body: dict) -> dict:
|
||||
"""
|
||||
索引文件到 RAG 知識庫 (pgvector rag_chunks 表)
|
||||
|
||||
body: {source, source_id, title, text, metadata?}
|
||||
"""
|
||||
from src.services.knowledge_rag_service import get_knowledge_rag_service
|
||||
svc = get_knowledge_rag_service()
|
||||
ok = await svc.index_document(
|
||||
source=body.get("source", "manual"),
|
||||
source_id=body.get("source_id", ""),
|
||||
title=body.get("title", ""),
|
||||
text=body.get("text", ""),
|
||||
metadata=body.get("metadata", {}),
|
||||
)
|
||||
return {"ok": ok, "title": body.get("title", "")}
|
||||
|
||||
|
||||
@router.get("/rag/query")
|
||||
async def rag_query(
|
||||
q: str = Query(..., min_length=1, description="RAG 查詢問題"),
|
||||
top_k: int = Query(5, ge=1, le=20),
|
||||
) -> dict:
|
||||
"""
|
||||
RAG 查詢 — embedding → pgvector knn → 生成繁中回答
|
||||
"""
|
||||
from src.services.knowledge_rag_service import get_knowledge_rag_service
|
||||
svc = get_knowledge_rag_service()
|
||||
answer = await svc.query(q, top_k=top_k)
|
||||
return {"question": q, "answer": answer}
|
||||
|
||||
|
||||
@router.get("/rag/stats")
|
||||
async def rag_stats() -> dict:
|
||||
"""RAG 知識庫統計"""
|
||||
from src.services.knowledge_rag_service import get_knowledge_rag_service
|
||||
svc = get_knowledge_rag_service()
|
||||
return await svc.get_stats()
|
||||
|
||||
@@ -215,6 +215,17 @@ class KnowledgeRAGService:
|
||||
start += chunk_size - overlap
|
||||
return [c for c in chunks if c.strip()]
|
||||
|
||||
async def get_stats(self) -> dict:
|
||||
"""RAG 知識庫統計"""
|
||||
from src.db.base import get_db_context
|
||||
from sqlalchemy import text
|
||||
async with get_db_context() as db:
|
||||
row = await db.execute(
|
||||
text("SELECT COUNT(*) as total, COUNT(DISTINCT source) as sources FROM rag_chunks")
|
||||
)
|
||||
r = row.one()
|
||||
return {"total_chunks": r.total, "sources": r.sources}
|
||||
|
||||
async def close(self) -> None:
|
||||
if self._http and not self._http.is_closed:
|
||||
await self._http.aclose()
|
||||
|
||||
Reference in New Issue
Block a user