"""지역 이야기 프롬프트 계약 — 문장은 여기서 만들지 않고 **읽어 온다**. ★ 단일 출처는 `solution/shared/src/lib/section-prompts.ts` 다. 사장님이 [콘텐츠] 탭에서 복사해 가는 프롬프트와 서버가 도는 프롬프트가 같아야 한다 — 갈리면 "빌더에서 뽑은 것과 자동으로 채워진 것의 모양이 다르다"가 조용히 생긴다. 이 파일이 읽는 `section_prompts.json` 은 `npm run export:prompts` 산출물이고 커밋된다. **손으로 고치지 않는다.** """ import json from functools import lru_cache from pathlib import Path _SPEC_PATH = Path(__file__).with_name("section_prompts.json") SYSTEM_PROMPT = ( "너는 지역 콘텐츠 리서처다. 검색으로 확인한 것만 쓰고, 확인하지 못한 값은 필드를 통째로 뺀다. " "JSON 하나만 출력한다." ) @lru_cache(maxsize=1) def _spec() -> dict: """산출물 로드. 없으면 즉시 터뜨린다 — 프롬프트 없이 도는 생성 잡은 빈 값을 쓴다.""" try: return json.loads(_SPEC_PATH.read_text(encoding="utf-8")) except FileNotFoundError as ex: # pragma: no cover - 배포 누락은 기동 시 바로 드러난다 raise RuntimeError( f"{_SPEC_PATH.name} 이 없다 — 레포 루트에서 `npm run export:prompts` 를 돌려야 한다" ) from ex def kinds() -> list[str]: return list(_spec()["specs"].keys()) def label(kind: str) -> str: return _spec()["specs"][kind]["label"] def max_items(kind: str) -> int: return int(_spec()["specs"][kind]["maxItems"]) def build_prompt(kind: str, region: str) -> str: """지역 하나 × 종류 하나의 프롬프트. ★ 업소 이름을 넣지 않는다. 이 값은 **지역**에 붙어 같은 지역 사이트가 나눠 쓴다 — 업소 하나를 골라 넣으면 그 집 이야기가 옆집 사이트에 실린다. `shared/section-prompts.ts` 의 `buildSectionPrompt` 가 같은 분기를 갖고 있다. """ spec = _spec()["specs"].get(kind) if spec is None: raise ValueError(f"모르는 지역 이야기 종류: {kind}") head = ( "너는 지역 콘텐츠 리서처다. 아래 조건에 맞는 JSON 하나만 출력한다.\n" f"\n[지역] {region}\n" f"\n아래 '해야 할 일'에서 [지역] = {region}. " "업소가 지정되지 않았으므로 특정 업소를 가리키는 문장(connection 등)은 쓰지 않는다.\n" ) return f"{head}\n{spec['task']}\n{_spec()['rules']}{spec['rules']}"