o2o-negosium-original/lps/services/ai/keyword.py
민헌 7911ba1745 refactor(lps): 환경설정 TOML 단일화 — .env 제거, 시크릿도 config.local.toml 로 통합
설정이 .env(시크릿)+toml(설정)로 갈려 있던 것을 config.local.toml 하나로 통합.
DB 비번이 이미 toml 에 있어 분리 기준이 임의적이었고, backend(하우스 패턴)도
toml 단일이라 일관성 확보. 환경별로 바뀌는 값(DB_HOST 등)만 env override 유지.

- config_models: NaverConfig(keys 로테이션)·OpenAIConfig·DecodoConfig 추가
- server_configs: 3개 로드, load_dotenv 제거(python-dotenv 의존성도 제거)
- proxy/naver/similarity/keyword/worker_main: os.environ → config 객체 참조
- config.local.toml.example: [NaverConfig]/[OpenAIConfig]/[DecodoConfig] 섹션
- .env/.env.example 삭제, README/주석 갱신 (배포는 toml 마운트 or env override)
- tests: DecodoConfig 기반으로 갱신 → 전체 44/44

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 10:06:00 +09:00

50 lines
2.4 KiB
Python

"""LLM 검색어 생성 — 재정제 라운드용 정밀/광역 쿼리 생성.
원본 검색어가 0매칭일 때 사용한다:
- precise: 모델명/규격을 반영한 정밀 검색어("스탠리 텀블러" → "스탠리 퀜처 887ml") → 매칭률↑
- broad: 핵심 상품 명사만 남긴 광역 검색어 → 정밀도 0건이면 폭넓게 최종 확인
레퍼런스 keyword_maker 의 규칙(모델명 우선, 일반 카테고리어 제거)을 structured output 으로 이식.
"""
from openai import AsyncOpenAI
from pydantic import BaseModel, Field
from common.logger import LOG
from config.server_configs import openai_config
_SYSTEM = (
"너는 커머스 검색어 생성기다. 상품 정보를 받아 쇼핑몰 검색창에 넣을 한국어 검색어 2개를 만든다.\n"
"- precise: 모델명(있으면 최우선)과 핵심 규격(용량/사이즈/개입 등)을 포함한 정밀 검색어. "
"동일 상품을 정확히 찾기 위함. 일반 카테고리 단어만 나열하지 말 것.\n"
"- broad: 핵심 상품 명사(브랜드+제품군) 위주의 광역 검색어. precise 가 0건일 때 폭넓게 찾기 위함.\n"
"검색어에 따옴표/특수문자/불필요한 수식어를 넣지 말 것."
)
class Keywords(BaseModel):
precise: str = Field(description="모델/규격 포함 정밀 검색어")
broad: str = Field(description="핵심 명사 위주 광역 검색어")
class KeywordGenerator:
def __init__(self, model: str | None = None, api_key: str | None = None):
self._model = model or openai_config.model
self._client = AsyncOpenAI(api_key=api_key or openai_config.api_key)
async def generate(self, target: dict) -> Keywords:
user = (
f"상품명: {target.get('product_name', '')}\n"
f"모델: {target.get('model', '')}\n"
f"규격: {target.get('specification', '')}\n"
f"제조사/브랜드: {target.get('company', '')}"
)
resp = await self._client.beta.chat.completions.parse(
model=self._model,
messages=[{"role": "system", "content": _SYSTEM}, {"role": "user", "content": user}],
response_format=Keywords,
temperature=0,
)
kw = resp.choices[0].message.parsed or Keywords(precise="", broad="")
LOG.d(f"[ai] 검색어 생성 precise={kw.precise!r} broad={kw.broad!r}")
return kw