diff --git a/apps/api/src/api/v1/knowledge.py b/apps/api/src/api/v1/knowledge.py index a4f2d301..dcd6a2b6 100644 --- a/apps/api/src/api/v1/knowledge.py +++ b/apps/api/src/api/v1/knowledge.py @@ -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() diff --git a/apps/api/src/services/knowledge_rag_service.py b/apps/api/src/services/knowledge_rag_service.py index 3d49ee14..ecc0dc5b 100644 --- a/apps/api/src/services/knowledge_rag_service.py +++ b/apps/api/src/services/knowledge_rag_service.py @@ -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()