diff --git a/apps/api/src/api/v1/rag.py b/apps/api/src/api/v1/rag.py index 4b424b06..a385570a 100644 --- a/apps/api/src/api/v1/rag.py +++ b/apps/api/src/api/v1/rag.py @@ -94,6 +94,13 @@ async def rag_stats() -> dict: return await svc.get_stats() +@router.post("/optimize", summary="建立 ivfflat 向量索引(需 >100 chunks)", include_in_schema=False) +async def rag_optimize() -> dict: + """對 rag_chunks.embedding 建立 ivfflat 索引,加速向量搜尋""" + import src.repositories.rag_chunk_repository as rag_repo + return await rag_repo.create_ivfflat_index() + + # ============================================================ # Background helper # ============================================================ diff --git a/apps/api/src/repositories/rag_chunk_repository.py b/apps/api/src/repositories/rag_chunk_repository.py index 01997630..53e6542b 100644 --- a/apps/api/src/repositories/rag_chunk_repository.py +++ b/apps/api/src/repositories/rag_chunk_repository.py @@ -88,6 +88,29 @@ async def delete_by_source_id(source_id: str) -> None: logger.warning("rag_chunk_delete_failed", source_id=source_id, error=str(e)) +async def create_ivfflat_index() -> dict: + """建立 ivfflat 向量索引(需 >100 chunks,加速 KNN 搜尋)""" + try: + async with get_db_context() as db: + await db.execute( + text(""" + CREATE INDEX IF NOT EXISTS idx_rag_chunks_embedding + ON rag_chunks + USING ivfflat (embedding vector_cosine_ops) + WITH (lists = 100) + """) + ) + rows = await db.execute( + text("SELECT indexname FROM pg_indexes WHERE tablename = 'rag_chunks'") + ) + indexes = [r[0] for r in rows] + logger.info("rag_ivfflat_index_created", indexes=indexes) + return {"status": "ok", "indexes": indexes} + except Exception as e: + logger.error("rag_ivfflat_index_failed", error=str(e)) + return {"status": "error", "error": str(e)} + + async def get_stats() -> dict: """取得 chunk 數量與來源統計""" try: diff --git a/apps/api/src/services/telegram_gateway.py b/apps/api/src/services/telegram_gateway.py index 1e8cc0e1..ca49026e 100644 --- a/apps/api/src/services/telegram_gateway.py +++ b/apps/api/src/services/telegram_gateway.py @@ -3643,7 +3643,7 @@ class TelegramGateway: # ── 指令路由 (2026-04-03 ogt: 方案B slash commands) ────────────────── cmd = text.strip().split()[0].lower().split("@")[0] if text.strip() else "" if cmd.startswith("/"): - await self._handle_group_command(cmd, chat_id, message_id) + await self._handle_group_command(cmd, chat_id, message_id, full_text=text.strip()) return from src.services.chat_manager import get_chat_manager as _get_cm @@ -3729,7 +3729,7 @@ class TelegramGateway: logger.info("group_message_handled", user_id=user_id, text=text[:50]) - async def _handle_group_command(self, cmd: str, _chat_id: int, message_id: int | None) -> None: + async def _handle_group_command(self, cmd: str, _chat_id: int, message_id: int | None, full_text: str = "") -> None: """ SRE 群組 Slash Commands (2026-04-03 ogt: 方案B) @@ -3737,6 +3737,7 @@ class TelegramGateway: /incidents → 活躍告警列表 /cost → 本月 AI 費用統計 /pods → 異常 Pod 列表 + /rag → RAG 知識庫查詢 (ADR-067 Phase 33) /help → 指令說明 """ from src.repositories.k8s_repository import get_k8s_repository @@ -3810,6 +3811,31 @@ class TelegramGateway: msg = f"⚠️ 異常 Pod\n⚠️ 無法取得: {e}" await self.send_as_openclaw(text=msg, reply_to_message_id=message_id) + elif cmd == "/rag": + # /rag <查詢內容> — RAG 知識庫語義查詢 (ADR-067 Phase 33) + # 2026-04-10 Claude Sonnet 4.6 Asia/Taipei + parts = full_text.split(None, 1) + if len(parts) < 2 or not parts[1].strip(): + await self.send_as_openclaw( + text="📚 RAG 知識庫查詢\n用法: /rag 你的問題\n例如: /rag 什麼是 ADR-067?", + reply_to_message_id=message_id, + ) + return + question = parts[1].strip() + await self.send_as_openclaw( + text=f"📚 查詢知識庫中...\n{question[:80]}", + reply_to_message_id=message_id, + ) + try: + from src.services.knowledge_rag_service import get_knowledge_rag_service + svc = get_knowledge_rag_service() + answer = await svc.query(question, top_k=5) + msg = f"📚 RAG 知識庫\nQ: {question[:80]}\n\n{answer}" + except Exception as e: + logger.warning("rag_telegram_query_failed", error=str(e)) + msg = f"📚 RAG 查詢失敗\n{e}" + await self.send_as_openclaw(text=msg, reply_to_message_id=message_id) + elif cmd == "/help": msg = ( "🤖 SRE 戰情室指令\n\n" @@ -3817,6 +3843,7 @@ class TelegramGateway: "/incidents — 列出活躍告警\n" "/cost — 查詢本月 AI 費用\n" "/pods — 列出異常 Pod\n" + "/rag <問題> — 查詢 RAG 知識庫\n" "/help — 顯示此說明\n\n" "對話方式:\n" "• 直接輸入 → 小O + 小賀 同時回應\n"