검색이 소모하는 리소스/비용/시간을 잡 단위로 집계해 result.metrics 로 적재(API/FE 노출).
지금까진 타임스탬프만 있고 실제 비용 동인(AI 토큰·대역폭)은 버려지고 있었다.
- services/metrics.SearchMetrics: duration_ms + ai(calls/tokens/est_cost_usd, gpt-4o-mini 단가)
+ crawl(fetches/html_bytes/malls_crawled) + source_ms
- AI 클라이언트: resp.usage 를 last_usage 로 노출(그동안 폐기하던 토큰)
- 어댑터: last_bytes(처리 HTML 바이트) 노출 — naver/coupang/browser_base 공통
- handler: 각 fetch 타이밍+바이트, AI 호출 토큰을 metrics 로 누적 → 결과에 스냅샷
- FE: 작업 카드에 원가 4타일(소요/AI비용/토큰/크롤 트래픽)
- 테스트 2종. ⚠️ html_bytes 는 대역폭 근사(오픈마켓 리소스 미차단분 제외=하한), CDP 정확화는 백로그
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
52 lines
2.5 KiB
Python
52 lines
2.5 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)
|
|
self.last_usage = None # 직전 호출 토큰 usage(계측용)
|
|
|
|
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,
|
|
)
|
|
self.last_usage = getattr(resp, "usage", None)
|
|
kw = resp.choices[0].message.parsed or Keywords(precise="", broad="")
|
|
LOG.d(f"[ai] 검색어 생성 precise={kw.precise!r} broad={kw.broad!r}")
|
|
return kw
|