"""이 숙소의 노래 — 가사 프롬프트와 응답 스키마. ★ 왜 가사를 **우리가** 쓰고 Suno 에는 작곡만 시키나 Suno 에 "군산 한옥 숙소 노래" 라고만 던지면 가사를 저쪽이 지어낸다. 그 가사에는 이 숙소에 없는 것(수영장·조식·오션뷰)이 섞이고, 우리는 그걸 검증할 방법이 없다 — 사이트의 다른 모든 문장은 확인된 fact 로만 쓰는데 노래만 지어낸 말을 싣는 꼴이 된다. 그래서 가사는 소개문과 **같은 재료**(확인된 fact + 소개문)로 여기서 쓰고, Suno 는 그 가사에 곡을 붙이기만 한다. ★ 그래도 가사는 사실 진술이 아니다 "밤이 깊어도 불이 켜져 있다" 같은 줄은 fact 가 아니라 분위기다. 그래서 `ground_check` 를 걸지 않는다 — 대신 프롬프트가 **없는 시설·없는 숫자를 말하지 말라**고 못 박는다. 요금·전화번호·주소를 가사에 넣지 않는 것도 같은 이유다(틀리면 예약 클레임이고, 노래는 고쳐 부르기도 어렵다). """ # Suno 가 받는 style 문자열. 장르를 모델이 고르게 두되 후보를 좁힌다 — # 열어 두면 숙소 사이트에 어울리지 않는 것(하드록·트랩)이 나온다. STYLE_CHOICES = [ "acoustic ballad", "city pop", "folk pop", "lo-fi", "bossa nova", "soft rock", "jazz", ] RESPONSE_SCHEMA = { "type": "object", "properties": { "title": {"type": "string", "description": "곡 제목. 상호를 그대로 쓰지 말고 한 구절로."}, "lyrics": {"type": "string", "description": "가사. [Verse]/[Chorus] 구조 태그를 포함한다."}, "style": {"type": "string", "description": f"장르·분위기. 다음 중 하나에서 시작한다: {', '.join(STYLE_CHOICES)}"}, }, "required": ["title", "lyrics", "style"], } def build_prompt(place_name: str, category_label: str, region: str, grounding: list[str], intro: str) -> str: """가사 프롬프트. 재료는 소개문 생성과 같은 것을 받는다.""" material = "\n".join(f"- {line}" for line in grounding) or "- (확인된 항목 없음)" intro_block = f"\n[이 숙소 소개문]\n{intro.strip()}\n" if (intro or "").strip() else "" return f"""너는 작은 가게의 노래를 쓰는 작사가다. 아래 업소의 노래 가사를 쓴다. [업소] - 상호: {place_name} - 업종: {category_label} - 지역: {region or "(모름)"} [확인된 항목 — 이 안에서만 말한다] {material} {intro_block} [쓰는 법] - 한국어. 40초 안에 불리는 길이다 — [Verse] 한 덩이 + [Chorus] 한 덩이면 충분하다. - 구조 태그([Verse], [Chorus])를 반드시 넣는다. Suno 가 이 태그로 곡을 나눈다. - **없는 것을 말하지 않는다.** 위 목록에 없는 시설·풍경·서비스를 지어내지 않는다. 지역과 계절, 머무는 마음처럼 사실 확인이 필요 없는 정서는 자유롭게 쓴다. - **숫자를 넣지 않는다.** 요금·전화번호·주소·객실 수는 가사에 쓰지 않는다. 틀리면 손님이 손해를 보고, 노래는 고쳐 부르기 어렵다. - 광고 문구처럼 쓰지 않는다("최고", "1등", "예약하세요"). 손님이 흥얼거릴 노래다. - 상호는 후렴에 한 번쯤 자연스럽게 넣는다. [출력] title(곡 제목) · lyrics(가사) · style(장르·분위기) 를 JSON 으로 준다. style 은 이 업소의 분위기에 맞는 것을 고른다: {', '.join(STYLE_CHOICES)}. """