refactor(flywheel): 首席架構師審查修正 C1/C2/I1/I2/I3/I4/M1
Some checks are pending
CD Pipeline / build-and-deploy (push) Has started running
Some checks are pending
CD Pipeline / build-and-deploy (push) Has started running
C1 — Repository 層修正 (積木化鐵律): 新增 PlaybookEmbeddingRepository (pgvector UPSERT) playbook_embedding_service 改透過 Repository 存取 DB,不再直接 db.execute(text(...)) C2 — Router 層業務邏輯移入 Service 層: create_incident_for_approval + extract_affected_services (去掉底線前綴) 移入 incident_service.py webhooks.py 改從 incident_service import,自身不再含業務邏輯 I1 — _infra_jobs 提升為 module-level frozenset (_INFRA_JOB_NAMES),避免每次呼叫重建 I2 — _persist_embeddings_to_db 補齊 PlaybookRAGService / list[Playbook] 型別標注 I3 — embedding 格式顯式化: "[" + ",".join(str(float(x)) for x in embedding) + "]" 防止 pgvector 因格式差異靜默解析失敗 I4 — import asyncio 移至 main.py 頂層,移除 try 區塊內重複 import M1 — similarity.py: 移除死代碼 `if union > 0 else 0.0` union 在兩個集合都非空時不可能為 0 2026-04-10 Asia/Taipei — Claude Sonnet 4.6 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
104
apps/api/src/repositories/playbook_embedding_repository.py
Normal file
104
apps/api/src/repositories/playbook_embedding_repository.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
Playbook Embedding Repository — pgvector 持久化層
|
||||
=================================================
|
||||
C1 修正 (首席架構師審查 2026-04-10 Claude Sonnet 4.6 Asia/Taipei):
|
||||
違規修復: playbook_embedding_service.py 直接 db.execute(text(...)) 繞過 Repository 層
|
||||
解法: 移至此 Repository,Service 層透過此介面存取
|
||||
|
||||
職責: playbook_embeddings 表的 CRUD (pgvector)
|
||||
對應遷移: migrations/flywheel_playbook_embeddings.sql
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import structlog
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class PlaybookEmbeddingRepository:
|
||||
"""
|
||||
Playbook Embedding Repository
|
||||
|
||||
職責: playbook_embeddings 表 CRUD
|
||||
使用 pgvector 儲存 nomic-embed-text 768 維向量
|
||||
|
||||
Args:
|
||||
db: SQLAlchemy AsyncSession (DI 注入)
|
||||
"""
|
||||
|
||||
def __init__(self, db: AsyncSession) -> None:
|
||||
self._db = db
|
||||
|
||||
async def upsert(
|
||||
self,
|
||||
playbook_id: str,
|
||||
embedding: list[float],
|
||||
alert_names: list[str],
|
||||
keywords: list[str],
|
||||
) -> bool:
|
||||
"""
|
||||
新增或更新 Playbook 向量 (UPSERT)。
|
||||
|
||||
pgvector 格式: '[x,y,z,...]' 字串
|
||||
ON CONFLICT (playbook_id) → 更新向量快照與 updated_at
|
||||
|
||||
Args:
|
||||
playbook_id: Playbook ID
|
||||
embedding: 768 維浮點向量 (list[float])
|
||||
alert_names: 索引時的 alert_names 快照
|
||||
keywords: 索引時的 keywords 快照
|
||||
|
||||
Returns:
|
||||
True 表示成功
|
||||
"""
|
||||
try:
|
||||
# 顯式格式化保證 pgvector 可解析,避免 str(list) 空格差異
|
||||
vec_str = "[" + ",".join(str(float(x)) for x in embedding) + "]"
|
||||
|
||||
await self._db.execute(
|
||||
text("""
|
||||
INSERT INTO playbook_embeddings
|
||||
(playbook_id, embedding, alert_names, keywords, indexed_at, updated_at)
|
||||
VALUES
|
||||
(:playbook_id, :embedding, :alert_names, :keywords, NOW(), NOW())
|
||||
ON CONFLICT (playbook_id) DO UPDATE SET
|
||||
embedding = EXCLUDED.embedding,
|
||||
alert_names = EXCLUDED.alert_names,
|
||||
keywords = EXCLUDED.keywords,
|
||||
updated_at = NOW()
|
||||
"""),
|
||||
{
|
||||
"playbook_id": playbook_id,
|
||||
"embedding": vec_str,
|
||||
"alert_names": alert_names,
|
||||
"keywords": keywords,
|
||||
},
|
||||
)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"playbook_embedding_upsert_failed",
|
||||
playbook_id=playbook_id,
|
||||
error=str(e),
|
||||
)
|
||||
return False
|
||||
|
||||
async def delete(self, playbook_id: str) -> bool:
|
||||
"""刪除 Playbook 向量記錄。"""
|
||||
try:
|
||||
await self._db.execute(
|
||||
text("DELETE FROM playbook_embeddings WHERE playbook_id = :pid"),
|
||||
{"pid": playbook_id},
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"playbook_embedding_delete_failed",
|
||||
playbook_id=playbook_id,
|
||||
error=str(e),
|
||||
)
|
||||
return False
|
||||
Reference in New Issue
Block a user