"""제목 교열 단일 호출 러너 — solar-pro2 고정. production 환경(extension) 이 solar-pro2 를 사용하므로 데모도 동일 모델로 고정. """ from __future__ import annotations import re import time from pathlib import Path from typing import Any MODEL = "solar-pro2" DEFAULT_PROMPT_DIR = Path(__file__).resolve().parent / "prompts" / "prompt_dev_v1" # solar-pro2 가 가끔 응답 본문 앞에 reasoning trace 를 emit 하고 `` 로 닫는 # 경우가 있음 (보통은 paired `...` 인데 unpaired 가 발생). upstage # provider 의 paired-tag strip 도 우회되므로, 데모 단에서 결정적으로 제거한다. _PAIRED_THINK = re.compile(r".*?", re.DOTALL) _ORPHAN_THINK_PREFIX = re.compile(r"^.*?\s*", re.DOTALL) def _strip_think(raw: str) -> str: """`...` 및 unpaired `` 앞부분 모두 제거. 가드 순서: 1. paired `...` 블록 제거 2. 그래도 `` 가 남아 있으면 → 첫 등장 위치 이전을 전부 reasoning trace 로 간주하고 잘라냄 (가장 흔한 누출 패턴) 3. 남은 `` / `` 토큰 잔존도 제거 """ s = _PAIRED_THINK.sub("", raw) if "" in s: s = _ORPHAN_THINK_PREFIX.sub("", s, count=1) s = s.replace("", "").replace("", "") return s.strip() def load_default_prompts() -> tuple[str, str]: """`prompt_dev_v1` 의 system.txt + user.txt 를 그대로 반환.""" system = (DEFAULT_PROMPT_DIR / "system.txt").read_text(encoding="utf-8") user = (DEFAULT_PROMPT_DIR / "user.txt").read_text(encoding="utf-8") return system, user def render_user_message(user_template: str, original: str, category: str) -> str: """`{{original}}`, `{{category}}` placeholder 치환.""" return user_template.replace("{{original}}", original).replace("{{category}}", category) def run_title_proofread( *, client: Any, original: str, category: str, system_prompt: str, user_template: str, temperature: float = 0.0, reasoning_effort: str = "low", max_tokens: int = 2000, ) -> dict[str, Any]: """단일 LLM 호출. 모델은 항상 `solar-pro2`. Returns: { "output": str, # 모델 응답 (strip + think-token 제거 후) "user_message": str, # placeholder 치환된 실 user content "model": str, "latency_ms": int, "usage": dict, # {prompt_tokens, completion_tokens, total_tokens} "error": str | None, } """ user_msg = render_user_message(user_template, original, category) start = time.time() try: kwargs: dict[str, Any] = { "model": MODEL, "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_msg}, ], "temperature": float(temperature), "max_tokens": int(max_tokens), } if reasoning_effort: kwargs["reasoning_effort"] = reasoning_effort resp = client.chat.completions.create(**kwargs) except Exception as exc: # noqa: BLE001 return { "output": "", "user_message": user_msg, "model": MODEL, "latency_ms": int((time.time() - start) * 1000), "usage": {}, "error": f"{type(exc).__name__}: {exc}", } elapsed_ms = int((time.time() - start) * 1000) raw = resp.choices[0].message.content or "" cleaned = _strip_think(raw) usage = getattr(resp, "usage", None) usage_dict: dict[str, int] = {} if usage: for k in ("prompt_tokens", "completion_tokens", "total_tokens"): v = getattr(usage, k, None) if v is not None: usage_dict[k] = v return { "output": cleaned, "user_message": user_msg, "model": MODEL, "latency_ms": elapsed_ms, "usage": usage_dict, "error": None, }