"""Prompt contract for grounded homepage copy.""" from typing import Optional, Protocol, Sequence from common.enums import PlaceCategory class FactLike(Protocol): key: str label: str value: str unit: Optional[str] RESPONSE_SCHEMA = { "type": "object", "properties": { "intro": {"type": "string"}, "intro_fact_keys": {"type": "array", "items": {"type": "string"}}, "meta_description": {"type": "string"}, "faqs": { "type": "array", "items": { "type": "object", "properties": { "question": {"type": "string"}, "answer": {"type": "string"}, "fact_keys": {"type": "array", "items": {"type": "string"}}, }, "required": ["question", "answer", "fact_keys"], }, }, }, "required": ["intro", "intro_fact_keys", "meta_description", "faqs"], } _CATEGORY_LABEL = { PlaceCategory.LODGING: "숙박업소", PlaceCategory.CAFE: "카페", PlaceCategory.RESTAURANT: "음식점", PlaceCategory.CLINIC: "관광·체험 시설", } def _fact_lines(facts: Sequence[FactLike]) -> str: return "\n".join( f"- {fact.key} ({fact.label}) = {fact.value}" + (f" {fact.unit}" if fact.unit else "") for fact in facts ) def build_prompt( place_name: str, category: PlaceCategory, facts: Sequence[FactLike], max_faqs: int, unit_facts: Optional[Sequence[FactLike]] = None, records: Optional[Sequence[str]] = None, ) -> str: """소개문·메타·FAQ 생성 프롬프트. ★ `records` 는 fact 가 아니라 **글**이다(수집 원문 · 업소 조사 결과). 예전에는 이것도 fact 목록에 `- source: (수집 원문) = …` 로 섞여 들어갔다. 모델은 그걸 값 하나로 읽고 거의 쓰지 않았다 — 실측(2026-09-10, 스테이,머뭄): 조사 근거 448자를 넣어도 소개문은 "주방 시설을 갖춘 독채형 객실을 운영하는 숙박업소입니다" 에서 한 발도 못 나갔다. 재료를 재료 자리에 놓아야 쓴다. """ sections = [ f"'{place_name}'({_CATEGORY_LABEL.get(category, '사업장')})의 공식 홈페이지 문구를 작성한다.", "", "확인된 사업장 사실:", _fact_lines(facts) if facts else "- 없음", ] if unit_facts: sections.extend(["", "확인된 객실·메뉴 사실:", _fact_lines(unit_facts)]) if records: sections.extend([ "", "업소에 대해 확인된 기록(출처가 있는 글):", *(f"- {line}" for line in records), ]) sections.extend([ "", "출력:", # ★ 100~250자였다. 그 길이로는 확인된 사실을 나열하면 끝나서, 기록이 있어도 # 들어갈 자리가 없었다. 시안(/s/stay)의 소개는 3문단 450자 안팎이다. "- intro: 소개문 200~600자. 사실이 충분하면 2~3문단으로 나눈다(문단 사이 빈 줄)", "- intro_fact_keys: 소개문의 근거 key", "- meta_description: 검색 요약 50~120자", f"- faqs: 최대 {max_faqs}개, 각 항목에 근거 fact_keys 포함", "", "규칙:", "- 위 사실에 없는 숫자·시설·지역 정보를 지어내지 마라.", "- **기록 절에 있는 내용은 적극적으로 쓴다.** 그것도 출처가 확인된 사실이다 —" " 건물의 내력·공간 구성·주변과의 관계처럼 이 업소만의 이야기가 거기 있다.", "- false·불가·없음 값을 가능하다고 표현하지 않는다.", "- 홍보성·평가성 표현을 쓰지 않는다.", "- 근거 없는 FAQ는 만들지 않는다.", "- 한국어 존댓말을 사용한다.", ]) return "\n".join(sections)