"""카드 스크립트 → 임베딩 캐시 생성 (action-as-feature 준비, 1회 실행). card.nego_cards(11장)의 name+script 를 문장 임베딩으로 변환해 artifacts/card_embeddings.npz 에 저장. 새 카드가 추가되면 이 스크립트를 다시 돌리면 된다(그 카드만 임베딩돼 캐시에 합류). 실행: APP_ENV=local python -m tools.build_card_embeddings 출력: artifacts/card_embeddings.npz (numbers, names, strategy, tone, embeddings[N,384]) """ import asyncio import os import numpy as np _HERE = os.path.dirname(os.path.abspath(__file__)) ARTIFACTS = os.path.join(_HERE, "..", "artifacts") OUT_PATH = os.path.join(ARTIFACTS, "card_embeddings.npz") MODEL_NAME = "paraphrase-multilingual-MiniLM-L12-v2" # 384차원, 한국어 지원, 로컬/무료 async def load_cards(): """card.nego_cards 에서 (number, name, script, strategy_type, tone) 로드.""" import asyncpg conn = await asyncpg.connect( host="127.0.0.1", port=5432, user="postgres", password="password", database="negosium_db") try: rows = await conn.fetch( "SELECT number, name, script, strategy_type, tone FROM card.nego_cards " "WHERE deleted = FALSE ORDER BY number") return [(r["number"], r["name"], r["script"], r["strategy_type"], r["tone"]) for r in rows] finally: await conn.close() def main(): cards = asyncio.run(load_cards()) if not cards: raise SystemExit("card.nego_cards 가 비어있음 — DB 시드 확인 (docker start negosium-pg)") print(f"카드 {len(cards)}장 로드: {[c[0] for c in cards]}") from sentence_transformers import SentenceTransformer model = SentenceTransformer(MODEL_NAME) texts = [f"{name}. {script}" for _, name, script, _, _ in cards] emb = model.encode(texts, normalize_embeddings=True) # [N, 384], 단위벡터 print(f"임베딩 shape: {emb.shape}") os.makedirs(ARTIFACTS, exist_ok=True) np.savez( OUT_PATH, numbers=np.array([c[0] for c in cards]), names=np.array([c[1] for c in cards]), strategy=np.array([c[3] for c in cards], dtype=np.int64), tone=np.array([c[4] for c in cards], dtype=np.int64), embeddings=emb.astype(np.float32), ) print(f"저장: {OUT_PATH}") if __name__ == "__main__": main()