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"