설정이 .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>
50 lines
2.4 KiB
Python
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
|