GCP-A (34.143.170.20) 無 nomic-embed-text,改用 bge-m3:latest(專用 多語言 embedding 模型),產生 1024 維向量。 變更: - embedding_service.py: 加入 bge-m3:latest=1024 維到 MODEL_DIMENSIONS, 預設模型改為 bge-m3:latest,更新文件說明 - playbook_embedding_repository.py + interfaces.py: 更新維度說明 - migrations/embedding_bge_m3_1024.sql: pgvector schema 遷移 rag_chunks + playbook_embeddings vector(768) → vector(1024) - scripts/reembed_bge_m3.py: 遷移後重新嵌入現有資料的 script 遷移步驟: 1. 執行 embedding_bge_m3_1024.sql(清空現有 768 維向量,變更維度) 2. 執行 python scripts/reembed_bge_m3.py 重新嵌入 2026-05-04 ogt + Claude Sonnet 4.6 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
105 lines
3.4 KiB
Python
105 lines
3.4 KiB
Python
"""
|
||
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 儲存 bge-m3:latest 1024 維向量(ADR-110 2026-05-04 升級自 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: 1024 維浮點向量 (list[float]),bge-m3:latest
|
||
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
|