""" Playbook Embedding Service — Phase 4 飛輪冷啟動修復 ==================================================== ADR-067 延伸: Playbook 向量持久化到 PostgreSQL playbook_embeddings 表 職責: - 啟動時掃描 APPROVED Playbooks,重建 Redis 向量快取 - 同步持久化到 playbook_embeddings (pgvector) 供跨重啟使用 呼叫方: main.py lifespan (asyncio.create_task — 非阻塞) 2026-04-10 Claude Sonnet 4.6 Asia/Taipei 修正 (首席架構師審查 2026-04-10): C1: _persist_embeddings_to_db 改用 PlaybookEmbeddingRepository (積木化修復) I2: 補齊 _persist_embeddings_to_db 型別標注 I3: embedding 格式顯式格式化,防止 pgvector 解析錯誤 """ from __future__ import annotations from typing import TYPE_CHECKING import structlog if TYPE_CHECKING: from src.models.playbook import Playbook from src.services.playbook_rag import PlaybookRAGService logger = structlog.get_logger(__name__) async def ensure_playbook_embeddings_indexed() -> None: """ 確保所有 APPROVED Playbooks 都有向量索引。 執行步驟: 1. 從 PlaybookService 取得所有 APPROVED Playbooks 2. 呼叫 PlaybookRAGService.reindex_all_playbooks → 更新 Redis 向量快取 3. 將向量持久化到 playbook_embeddings (pgvector) 表 """ try: from src.models.playbook import PlaybookStatus from src.services.playbook_service import get_playbook_service from src.services.playbook_rag import get_playbook_rag_service playbook_service = get_playbook_service() playbooks, _total = await playbook_service.list_playbooks( status=PlaybookStatus.APPROVED, limit=500 ) if not playbooks: logger.info("playbook_embedding_indexing_skipped", reason="no approved playbooks") return logger.info("playbook_embedding_indexing_start", count=len(playbooks)) # Step 1: 重建 Redis 向量快取 rag_service = await get_playbook_rag_service() success, failed = await rag_service.reindex_all_playbooks(playbooks) logger.info("playbook_embedding_redis_done", success=success, failed=failed) # Step 2: 持久化到 PostgreSQL playbook_embeddings 表 await _persist_embeddings_to_db(rag_service, playbooks) except Exception as e: logger.warning("playbook_embedding_indexing_error", error=str(e)) async def _persist_embeddings_to_db( rag_service: "PlaybookRAGService", playbooks: "list[Playbook]", ) -> None: """ 將 Redis 向量快取同步寫入 playbook_embeddings DB 表 (持久化層)。 C1 修正: 改用 PlaybookEmbeddingRepository,Service 不直接操作 SQL。 I3 修正: embedding 格式由 Repository 層統一處理,防止 pgvector 解析錯誤。 """ try: from src.db.base import get_db_context from src.repositories.playbook_embedding_repository import PlaybookEmbeddingRepository persisted = 0 skipped = 0 async with get_db_context() as db: repo = PlaybookEmbeddingRepository(db) for playbook in playbooks: try: embedding = await rag_service.get_playbook_embedding(playbook.playbook_id) if not embedding: skipped += 1 continue sp = playbook.symptom_pattern alert_names = list(sp.alert_names) if sp else [] keywords = list(sp.keywords) if sp else [] ok = await repo.upsert( playbook_id=playbook.playbook_id, embedding=embedding, alert_names=alert_names, keywords=keywords, ) if ok: persisted += 1 else: skipped += 1 except Exception as e: logger.warning( "playbook_embedding_persist_error", playbook_id=playbook.playbook_id, error=str(e), ) skipped += 1 await db.commit() logger.info("playbook_embedding_db_done", persisted=persisted, skipped=skipped) except Exception as e: logger.warning("playbook_embedding_db_error", error=str(e))