프롬프트 처리 구조 변경 및 마케팅/가사 프롬프트 최신화

insta
jaehwang 2026-01-16 10:05:29 +09:00
parent ba26284451
commit 4e15e44cbe
15 changed files with 2180 additions and 1277 deletions

View File

@ -27,6 +27,7 @@ from app.utils.upload_blob_as_request import AzureBlobUploader
from app.utils.chatgpt_prompt import ChatgptService from app.utils.chatgpt_prompt import ChatgptService
from app.utils.common import generate_task_id from app.utils.common import generate_task_id
from app.utils.nvMapScraper import NvMapScraper, GraphQLException from app.utils.nvMapScraper import NvMapScraper, GraphQLException
from app.utils.prompts.prompts import marketing_prompt
# 로거 설정 # 로거 설정
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -172,34 +173,50 @@ async def crawling(request_body: CrawlingRequest):
try: try:
# Step 3-1: ChatGPT 서비스 초기화 # Step 3-1: ChatGPT 서비스 초기화
step3_1_start = time.perf_counter() step3_1_start = time.perf_counter()
chatgpt_service = ChatgptService( chatgpt_service = ChatgptService()
customer_name=customer_name,
region=region,
detail_region_info=road_address or "",
)
step3_1_elapsed = (time.perf_counter() - step3_1_start) * 1000 step3_1_elapsed = (time.perf_counter() - step3_1_start) * 1000
print(f"[crawling] Step 3-1: 서비스 초기화 완료 ({step3_1_elapsed:.1f}ms)") print(f"[crawling] Step 3-1: 서비스 초기화 완료 ({step3_1_elapsed:.1f}ms)")
# Step 3-2: 프롬프트 생성 # Step 3-2: 프롬프트 생성
step3_2_start = time.perf_counter() # step3_2_start = time.perf_counter()
prompt = chatgpt_service.build_market_analysis_prompt() input_marketing_data = {
step3_2_elapsed = (time.perf_counter() - step3_2_start) * 1000 "customer_name" : customer_name,
print(f"[crawling] Step 3-2: 프롬프트 생성 완료 - {len(prompt)}자 ({step3_2_elapsed:.1f}ms)") "region" : region,
"detail_region_info" : road_address or ""
}
# prompt = chatgpt_service.build_market_analysis_prompt()
# prompt1 = marketing_prompt.build_prompt(input_marketing_data)
# step3_2_elapsed = (time.perf_counter() - step3_2_start) * 1000
# print(f"[crawling] Step 3-2: 프롬프트 생성 완료 - ({step3_2_elapsed:.1f}ms)")
# Step 3-3: GPT API 호출 # Step 3-3: GPT API 호출
step3_3_start = time.perf_counter() step3_3_start = time.perf_counter()
raw_response = await chatgpt_service.generate(prompt) structured_report = await chatgpt_service.generate_structured_output(marketing_prompt, input_marketing_data)
step3_3_elapsed = (time.perf_counter() - step3_3_start) * 1000 step3_3_elapsed = (time.perf_counter() - step3_3_start) * 1000
logger.info(f"[crawling] Step 3-3: GPT API 호출 완료 - 응답 {len(raw_response)}자 ({step3_3_elapsed:.1f}ms)") logger.info(f"[crawling] Step 3-3: GPT API 호출 완료 - ({step3_3_elapsed:.1f}ms)")
print(f"[crawling] Step 3-3: GPT API 호출 완료 - 응답 {len(raw_response)}자 ({step3_3_elapsed:.1f}ms)") print(f"[crawling] Step 3-3: GPT API 호출 완료 - ({step3_3_elapsed:.1f}ms)")
# Step 3-4: 응답 파싱 (크롤링에서 가져온 facility_info 전달) # Step 3-4: 응답 파싱 (크롤링에서 가져온 facility_info 전달)
step3_4_start = time.perf_counter() step3_4_start = time.perf_counter()
print(f"[crawling] Step 3-4: 응답 파싱 시작 - facility_info: {scraper.facility_info}") print(f"[crawling] Step 3-4: 응답 파싱 시작 - facility_info: {scraper.facility_info}")
parsed = await chatgpt_service.parse_marketing_analysis(
raw_response, facility_info=scraper.facility_info # 요약 Deprecated / 20250115 / Selling points를 첫 prompt에서 추출 중
# parsed = await chatgpt_service.parse_marketing_analysis(
# structured_report, facility_info=scraper.facility_info
# )
# marketing_analysis = MarketingAnalysis(**parsed)
marketing_analysis = MarketingAnalysis(
report=structured_report["report"],
tags=structured_report["tags"],
facilities = list([sp['keywords'] for sp in structured_report["selling_points"]])# [json.dumps(structured_report["selling_points"])] # 나중에 Selling Points로 변수와 데이터구조 변경할 것
) )
marketing_analysis = MarketingAnalysis(**parsed) # Selling Points 구조
# print(sp['category'])
# print(sp['keywords'])
# print(sp['description'])
step3_4_elapsed = (time.perf_counter() - step3_4_start) * 1000 step3_4_elapsed = (time.perf_counter() - step3_4_start) * 1000
print(f"[crawling] Step 3-4: 응답 파싱 완료 ({step3_4_elapsed:.1f}ms)") print(f"[crawling] Step 3-4: 응답 파싱 완료 ({step3_4_elapsed:.1f}ms)")
@ -236,7 +253,7 @@ async def crawling(request_body: CrawlingRequest):
"image_list": scraper.image_link_list, "image_list": scraper.image_link_list,
"image_count": len(scraper.image_link_list) if scraper.image_link_list else 0, "image_count": len(scraper.image_link_list) if scraper.image_link_list else 0,
"processed_info": processed_info, "processed_info": processed_info,
"marketing_analysis": marketing_analysis, "marketing_analysis": marketing_analysis
} }

View File

@ -43,6 +43,8 @@ from app.lyric.worker.lyric_task import generate_lyric_background
from app.utils.chatgpt_prompt import ChatgptService from app.utils.chatgpt_prompt import ChatgptService
from app.utils.pagination import PaginatedResponse, get_paginated from app.utils.pagination import PaginatedResponse, get_paginated
from app.utils.prompts.prompts import lyric_prompt
router = APIRouter(prefix="/lyric", tags=["lyric"]) router = APIRouter(prefix="/lyric", tags=["lyric"])
@ -224,6 +226,7 @@ async def generate_lyric(
request_start = time.perf_counter() request_start = time.perf_counter()
task_id = request_body.task_id task_id = request_body.task_id
print(f"[generate_lyric] ========== START ==========") print(f"[generate_lyric] ========== START ==========")
print( print(
@ -237,16 +240,45 @@ async def generate_lyric(
step1_start = time.perf_counter() step1_start = time.perf_counter()
print(f"[generate_lyric] Step 1: 서비스 초기화 및 프롬프트 생성...") print(f"[generate_lyric] Step 1: 서비스 초기화 및 프롬프트 생성...")
service = ChatgptService( # service = ChatgptService(
customer_name=request_body.customer_name, # customer_name=request_body.customer_name,
region=request_body.region, # region=request_body.region,
detail_region_info=request_body.detail_region_info or "", # detail_region_info=request_body.detail_region_info or "",
language=request_body.language, # language=request_body.language,
) # )
prompt = service.build_lyrics_prompt()
# prompt = service.build_lyrics_prompt()
# 원래는 실제 사용할 프롬프트가 들어가야 하나, 로직이 변경되어 이 시점에서 이곳에서 프롬프트를 생성할 이유가 없어서 삭제됨.
# 기존 코드와의 호환을 위해 동일한 로직으로 프롬프트 생성
promotional_expressions = {
"Korean" : "인스타 감성, 사진같은 하루, 힐링, 여행, 감성 숙소",
"English" : "Instagram vibes, picture-perfect day, healing, travel, getaway",
"Chinese" : "网红打卡, 治愈系, 旅行, 度假, 拍照圣地",
"Japanese" : "インスタ映え, 写真のような一日, 癒し, 旅行, 絶景",
"Thai" : "ที่พักสวย, ฮีลใจ, เที่ยว, ถ่ายรูป, วิวสวย",
"Vietnamese" : "check-in đẹp, healing, du lịch, nghỉ dưỡng, view đẹp"
}# HARD CODED, 어디에 정리하지? 아직 정리되지 않음
timing_rules = {
"60s" : """
812 lines
Full verse flow, immersive mood
"""
}
lyric_input_data = {
"customer_name" : request_body.customer_name,
"region" : request_body.region,
"detail_region_info" : request_body.detail_region_info or "",
"marketing_intelligence_summary" : None, # task_idx 변경 후 marketing intelligence summary DB에 저장하고 사용할 필요가 있음
"language" : request_body.language,
"promotional_expression_example" : promotional_expressions[request_body.language],
"timing_rules" : timing_rules["60s"], # 아직은 선택지 하나
}
estimated_prompt = lyric_prompt.build_prompt(lyric_input_data)
step1_elapsed = (time.perf_counter() - step1_start) * 1000 step1_elapsed = (time.perf_counter() - step1_start) * 1000
print(f"[generate_lyric] Step 1 완료 - 프롬프트 {len(prompt)}자 ({step1_elapsed:.1f}ms)") #print(f"[generate_lyric] Step 1 완료 - 프롬프트 {len(prompt)}자 ({step1_elapsed:.1f}ms)")
# ========== Step 2: Project 테이블에 데이터 저장 ========== # ========== Step 2: Project 테이블에 데이터 저장 ==========
step2_start = time.perf_counter() step2_start = time.perf_counter()
@ -270,11 +302,12 @@ async def generate_lyric(
step3_start = time.perf_counter() step3_start = time.perf_counter()
print(f"[generate_lyric] Step 3: Lyric 저장 (processing)...") print(f"[generate_lyric] Step 3: Lyric 저장 (processing)...")
estimated_prompt = lyric_prompt.build_prompt(lyric_input_data)
lyric = Lyric( lyric = Lyric(
project_id=project.id, project_id=project.id,
task_id=task_id, task_id=task_id,
status="processing", status="processing",
lyric_prompt=prompt, lyric_prompt=estimated_prompt,
lyric_result=None, lyric_result=None,
language=request_body.language, language=request_body.language,
) )
@ -292,8 +325,8 @@ async def generate_lyric(
background_tasks.add_task( background_tasks.add_task(
generate_lyric_background, generate_lyric_background,
task_id=task_id, task_id=task_id,
prompt=prompt, prompt=lyric_prompt,
language=request_body.language, lyric_input_data=lyric_input_data,
) )
step4_elapsed = (time.perf_counter() - step4_start) * 1000 step4_elapsed = (time.perf_counter() - step4_start) * 1000

View File

@ -1,852 +0,0 @@
import random
from typing import List
from fastapi import Request, status
from fastapi.exceptions import HTTPException
from sqlalchemy import Connection, text
from sqlalchemy.exc import SQLAlchemyError
from app.lyric.schemas.lyrics_schema import (
AttributeData,
PromptTemplateData,
SongFormData,
SongSampleData,
StoreData,
)
from app.utils.chatgpt_prompt import chatgpt_api
async def get_store_info(conn: Connection) -> List[StoreData]:
try:
query = """SELECT * FROM store_default_info;"""
result = await conn.execute(text(query))
all_store_info = [
StoreData(
id=row[0],
store_info=row[1],
store_name=row[2],
store_category=row[3],
store_region=row[4],
store_address=row[5],
store_phone_number=row[6],
created_at=row[7],
)
for row in result
]
result.close()
return all_store_info
except SQLAlchemyError as e:
print(e)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.",
)
except Exception as e:
print(e)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="알수없는 이유로 서비스 오류가 발생하였습니다",
)
async def get_attribute(conn: Connection) -> List[AttributeData]:
try:
query = """SELECT * FROM attribute;"""
result = await conn.execute(text(query))
all_attribute = [
AttributeData(
id=row[0],
attr_category=row[1],
attr_value=row[2],
created_at=row[3],
)
for row in result
]
result.close()
return all_attribute
except SQLAlchemyError as e:
print(e)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.",
)
except Exception as e:
print(e)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="알수없는 이유로 서비스 오류가 발생하였습니다",
)
async def get_attribute(conn: Connection) -> List[AttributeData]:
try:
query = """SELECT * FROM attribute;"""
result = await conn.execute(text(query))
all_attribute = [
AttributeData(
id=row[0],
attr_category=row[1],
attr_value=row[2],
created_at=row[3],
)
for row in result
]
result.close()
return all_attribute
except SQLAlchemyError as e:
print(e)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.",
)
except Exception as e:
print(e)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="알수없는 이유로 서비스 오류가 발생하였습니다",
)
async def get_sample_song(conn: Connection) -> List[SongSampleData]:
try:
query = """SELECT * FROM song_sample;"""
result = await conn.execute(text(query))
all_sample_song = [
SongSampleData(
id=row[0],
ai=row[1],
ai_model=row[2],
genre=row[3],
sample_song=row[4],
)
for row in result
]
result.close()
return all_sample_song
except SQLAlchemyError as e:
print(e)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.",
)
except Exception as e:
print(e)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="알수없는 이유로 서비스 오류가 발생하였습니다",
)
async def get_prompt_template(conn: Connection) -> List[PromptTemplateData]:
try:
query = """SELECT * FROM prompt_template;"""
result = await conn.execute(text(query))
all_prompt_template = [
PromptTemplateData(
id=row[0],
description=row[1],
prompt=row[2],
)
for row in result
]
result.close()
return all_prompt_template
except SQLAlchemyError as e:
print(e)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.",
)
except Exception as e:
print(e)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="알수없는 이유로 서비스 오류가 발생하였습니다",
)
async def get_song_result(conn: Connection) -> List[PromptTemplateData]:
try:
query = """SELECT * FROM prompt_template;"""
result = await conn.execute(text(query))
all_prompt_template = [
PromptTemplateData(
id=row[0],
description=row[1],
prompt=row[2],
)
for row in result
]
result.close()
return all_prompt_template
except SQLAlchemyError as e:
print(e)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.",
)
except Exception as e:
print(e)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="알수없는 이유로 서비스 오류가 발생하였습니다",
)
async def make_song_result(request: Request, conn: Connection):
try:
# 1. Form 데이터 파싱
form_data = await SongFormData.from_form(request)
print(f"\n{'=' * 60}")
print(f"Store ID: {form_data.store_id}")
print(f"Lyrics IDs: {form_data.lyrics_ids}")
print(f"Prompt IDs: {form_data.prompts}")
print(f"{'=' * 60}\n")
# 2. Store 정보 조회
store_query = """
SELECT * FROM store_default_info WHERE id=:id;
"""
store_result = await conn.execute(text(store_query), {"id": form_data.store_id})
all_store_info = [
StoreData(
id=row[0],
store_info=row[1],
store_name=row[2],
store_category=row[3],
store_region=row[4],
store_address=row[5],
store_phone_number=row[6],
created_at=row[7],
)
for row in store_result
]
if not all_store_info:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Store not found: {form_data.store_id}",
)
store_info = all_store_info[0]
print(f"Store: {store_info.store_name}")
# 3. 속성 조회 -- 단계별 선택 프로세서시 구현 필요 없음
# 4. Sample Song 조회 및 결합
combined_sample_song = None
if form_data.lyrics_ids:
print(f"\n[샘플 가사 조회] - {len(form_data.lyrics_ids)}")
lyrics_query = """
SELECT sample_song FROM song_sample
WHERE id IN :ids
ORDER BY created_at;
"""
lyrics_result = await conn.execute(
text(lyrics_query), {"ids": tuple(form_data.lyrics_ids)}
)
sample_songs = [
row.sample_song for row in lyrics_result.fetchall() if row.sample_song
]
if sample_songs:
combined_sample_song = "\n\n".join(
[f"[샘플 {i + 1}]\n{song}" for i, song in enumerate(sample_songs)]
)
print(f"{len(sample_songs)}개의 샘플 가사 조회 완료")
else:
print("샘플 가사가 비어있습니다")
else:
print("선택된 lyrics가 없습니다")
# 5. 템플릿 가져오기
if not form_data.prompts:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="프롬프트 ID가 필요합니다",
)
print("템플릿 가져오기")
prompts_query = """
SELECT * FROM prompt_template WHERE id=:id;
"""
# ✅ 수정: store_query → prompts_query
prompts_result = await conn.execute(
text(prompts_query), {"id": form_data.prompts}
)
prompts_info = [
PromptTemplateData(
id=row[0],
description=row[1],
prompt=row[2],
)
for row in prompts_result
]
if not prompts_info:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt not found: {form_data.prompts}",
)
prompt = prompts_info[0]
print(f"Prompt Template: {prompt.prompt}")
# ✅ 6. 프롬프트 조합
updated_prompt = prompt.prompt.replace("###", form_data.attributes_str).format(
name=store_info.store_name or "",
address=store_info.store_address or "",
category=store_info.store_category or "",
description=store_info.store_info or "",
)
updated_prompt += f"""
다음은 참고해야 하는 샘플 가사 정보입니다.
샘플 가사를 참고하여 작곡을 해주세요.
{combined_sample_song}
"""
print(f"\n[업데이트된 프롬프트]\n{updated_prompt}\n")
# 7. 모델에게 요청
generated_lyrics = await chatgpt_api.generate(prompt=updated_prompt)
# 글자 수 계산
total_chars_with_space = len(generated_lyrics)
total_chars_without_space = len(
generated_lyrics.replace(" ", "")
.replace("\n", "")
.replace("\r", "")
.replace("\t", "")
)
# final_lyrics 생성
final_lyrics = f"""속성 {form_data.attributes_str}
전체 글자 (공백 포함): {total_chars_with_space}
전체 글자 (공백 제외): {total_chars_without_space}\r\n\r\n{generated_lyrics}"""
print("=" * 40)
print("[translate:form_data.attributes_str:] ", form_data.attributes_str)
print("[translate:total_chars_with_space:] ", total_chars_with_space)
print("[translate:total_chars_without_space:] ", total_chars_without_space)
print("[translate:final_lyrics:]")
print(final_lyrics)
print("=" * 40)
# 8. DB 저장
insert_query = """
INSERT INTO song_results_all (
store_info, store_name, store_category, store_address, store_phone_number,
description, prompt, attr_category, attr_value,
ai, ai_model, genre,
sample_song, result_song, created_at
) VALUES (
:store_info, :store_name, :store_category, :store_address, :store_phone_number,
:description, :prompt, :attr_category, :attr_value,
:ai, :ai_model, :genre,
:sample_song, :result_song, NOW()
);
"""
# ✅ attr_category, attr_value 추가
insert_params = {
"store_info": store_info.store_info or "",
"store_name": store_info.store_name,
"store_category": store_info.store_category or "",
"store_address": store_info.store_address or "",
"store_phone_number": store_info.store_phone_number or "",
"description": store_info.store_info or "",
"prompt": form_data.prompts,
"attr_category": ", ".join(form_data.attributes.keys())
if form_data.attributes
else "",
"attr_value": ", ".join(form_data.attributes.values())
if form_data.attributes
else "",
"ai": "ChatGPT",
"ai_model": form_data.llm_model,
"genre": "후크송",
"sample_song": combined_sample_song or "없음",
"result_song": final_lyrics,
}
await conn.execute(text(insert_query), insert_params)
await conn.commit()
print("결과 저장 완료")
print("\n전체 결과 조회 중...")
# 9. 생성 결과 가져오기 (created_at 역순)
select_query = """
SELECT * FROM song_results_all
ORDER BY created_at DESC;
"""
all_results = await conn.execute(text(select_query))
results_list = [
{
"id": row.id,
"store_info": row.store_info,
"store_name": row.store_name,
"store_category": row.store_category,
"store_address": row.store_address,
"store_phone_number": row.store_phone_number,
"description": row.description,
"prompt": row.prompt,
"attr_category": row.attr_category,
"attr_value": row.attr_value,
"ai": row.ai,
"ai_model": row.ai_model,
"genre": row.genre,
"sample_song": row.sample_song,
"result_song": row.result_song,
"created_at": row.created_at.isoformat() if row.created_at else None,
}
for row in all_results.fetchall()
]
print(f"전체 {len(results_list)}개의 결과 조회 완료\n")
return results_list
except HTTPException:
raise
except SQLAlchemyError as e:
print(f"Database Error: {e}")
import traceback
traceback.print_exc()
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="데이터베이스 연결에 문제가 발생했습니다.",
)
except Exception as e:
print(f"Unexpected Error: {e}")
import traceback
traceback.print_exc()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="서비스 처리 중 오류가 발생했습니다.",
)
async def get_song_result(conn: Connection): # 반환 타입 수정
try:
select_query = """
SELECT * FROM song_results_all
ORDER BY created_at DESC;
"""
all_results = await conn.execute(text(select_query))
results_list = [
{
"id": row.id,
"store_info": row.store_info,
"store_name": row.store_name,
"store_category": row.store_category,
"store_address": row.store_address,
"store_phone_number": row.store_phone_number,
"description": row.description,
"prompt": row.prompt,
"attr_category": row.attr_category,
"attr_value": row.attr_value,
"ai": row.ai,
"ai_model": row.ai_model,
"season": row.season,
"num_of_people": row.num_of_people,
"people_category": row.people_category,
"genre": row.genre,
"sample_song": row.sample_song,
"result_song": row.result_song,
"created_at": row.created_at.isoformat() if row.created_at else None,
}
for row in all_results.fetchall()
]
print(f"전체 {len(results_list)}개의 결과 조회 완료\n")
return results_list
except HTTPException: # HTTPException은 그대로 raise
raise
except SQLAlchemyError as e:
print(f"Database Error: {e}")
import traceback
traceback.print_exc()
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="데이터베이스 연결에 문제가 발생했습니다.",
)
except Exception as e:
print(f"Unexpected Error: {e}")
import traceback
traceback.print_exc()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="서비스 처리 중 오류가 발생했습니다.",
)
async def make_automation(request: Request, conn: Connection):
try:
# 1. Form 데이터 파싱
form_data = await SongFormData.from_form(request)
print(f"\n{'=' * 60}")
print(f"Store ID: {form_data.store_id}")
print(f"{'=' * 60}\n")
# 2. Store 정보 조회
store_query = """
SELECT * FROM store_default_info WHERE id=:id;
"""
store_result = await conn.execute(text(store_query), {"id": form_data.store_id})
all_store_info = [
StoreData(
id=row[0],
store_info=row[1],
store_name=row[2],
store_category=row[3],
store_region=row[4],
store_address=row[5],
store_phone_number=row[6],
created_at=row[7],
)
for row in store_result
]
if not all_store_info:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Store not found: {form_data.store_id}",
)
store_info = all_store_info[0]
print(f"Store: {store_info.store_name}")
# 3. 속성 조회 -- 단계별 선택 프로세서시 구현 필요 없음
attribute_query = """
SELECT * FROM attribute;
"""
attribute_results = await conn.execute(text(attribute_query))
# 결과 가져오기
attribute_rows = attribute_results.fetchall()
formatted_attributes = ""
selected_categories = []
selected_values = []
if attribute_rows:
attribute_list = [
AttributeData(
id=row[0],
attr_category=row[1],
attr_value=row[2],
created_at=row[3],
)
for row in attribute_rows
]
# ✅ 각 category에서 하나의 value만 랜덤 선택
formatted_pairs = []
for attr in attribute_list:
# 쉼표로 분리 및 공백 제거
values = [v.strip() for v in attr.attr_value.split(",") if v.strip()]
if values:
# 랜덤하게 하나만 선택
selected_value = random.choice(values)
formatted_pairs.append(f"{attr.attr_category} : {selected_value}")
# ✅ 선택된 category와 value 저장
selected_categories.append(attr.attr_category)
selected_values.append(selected_value)
# 최종 문자열 생성
formatted_attributes = "\n".join(formatted_pairs)
print(f"\n[포맷팅된 문자열 속성 정보]\n{formatted_attributes}\n")
else:
print("속성 데이터가 없습니다")
formatted_attributes = ""
# 4. 템플릿 가져오기
print("템플릿 가져오기 (ID=1)")
prompts_query = """
SELECT * FROM prompt_template WHERE id=1;
"""
prompts_result = await conn.execute(text(prompts_query))
row = prompts_result.fetchone()
if not row:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Prompt ID 1 not found",
)
prompt = PromptTemplateData(
id=row[0],
description=row[1],
prompt=row[2],
)
print(f"Prompt Template: {prompt.prompt}")
# 5. 템플릿 조합
updated_prompt = prompt.prompt.replace("###", formatted_attributes).format(
name=store_info.store_name or "",
address=store_info.store_address or "",
category=store_info.store_category or "",
description=store_info.store_info or "",
)
print("\n" + "=" * 80)
print("업데이트된 프롬프트")
print("=" * 80)
print(updated_prompt)
print("=" * 80 + "\n")
# 4. Sample Song 조회 및 결합
combined_sample_song = None
if form_data.lyrics_ids:
print(f"\n[샘플 가사 조회] - {len(form_data.lyrics_ids)}")
lyrics_query = """
SELECT sample_song FROM song_sample
WHERE id IN :ids
ORDER BY created_at;
"""
lyrics_result = await conn.execute(
text(lyrics_query), {"ids": tuple(form_data.lyrics_ids)}
)
sample_songs = [
row.sample_song for row in lyrics_result.fetchall() if row.sample_song
]
if sample_songs:
combined_sample_song = "\n\n".join(
[f"[샘플 {i + 1}]\n{song}" for i, song in enumerate(sample_songs)]
)
print(f"{len(sample_songs)}개의 샘플 가사 조회 완료")
else:
print("샘플 가사가 비어있습니다")
else:
print("선택된 lyrics가 없습니다")
# 1. song_sample 테이블의 모든 ID 조회
print("\n[샘플 가사 랜덤 선택]")
all_ids_query = """
SELECT id FROM song_sample;
"""
ids_result = await conn.execute(text(all_ids_query))
all_ids = [row.id for row in ids_result.fetchall()]
print(f"전체 샘플 가사 개수: {len(all_ids)}")
# 2. 랜덤하게 3개 선택 (또는 전체 개수가 3개 미만이면 전체)
combined_sample_song = None
if all_ids:
# 3개 또는 전체 개수 중 작은 값 선택
sample_count = min(3, len(all_ids))
selected_ids = random.sample(all_ids, sample_count)
print(f"랜덤 선택된 ID: {selected_ids}")
# 3. 선택된 ID로 샘플 가사 조회
lyrics_query = """
SELECT sample_song FROM song_sample
WHERE id IN :ids
ORDER BY created_at;
"""
lyrics_result = await conn.execute(
text(lyrics_query), {"ids": tuple(selected_ids)}
)
sample_songs = [
row.sample_song for row in lyrics_result.fetchall() if row.sample_song
]
# 4. combined_sample_song 생성
if sample_songs:
combined_sample_song = "\n\n".join(
[f"[샘플 {i + 1}]\n{song}" for i, song in enumerate(sample_songs)]
)
print(f"{len(sample_songs)}개의 샘플 가사 조회 완료")
else:
print("샘플 가사가 비어있습니다")
else:
print("song_sample 테이블에 데이터가 없습니다")
# 5. 프롬프트에 샘플 가사 추가
if combined_sample_song:
updated_prompt += f"""
다음은 참고해야 하는 샘플 가사 정보입니다.
샘플 가사를 참고하여 작곡을 해주세요.
{combined_sample_song}
"""
print("샘플 가사 정보가 프롬프트에 추가되었습니다")
else:
print("샘플 가사가 없어 기본 프롬프트만 사용합니다")
print(f"\n[최종 프롬프트 길이: {len(updated_prompt)} 자]\n")
# 7. 모델에게 요청
generated_lyrics = await chatgpt_api.generate(prompt=updated_prompt)
# 글자 수 계산
total_chars_with_space = len(generated_lyrics)
total_chars_without_space = len(
generated_lyrics.replace(" ", "")
.replace("\n", "")
.replace("\r", "")
.replace("\t", "")
)
# final_lyrics 생성
final_lyrics = f"""속성 {formatted_attributes}
전체 글자 (공백 포함): {total_chars_with_space}
전체 글자 (공백 제외): {total_chars_without_space}\r\n\r\n{generated_lyrics}"""
# 8. DB 저장
insert_query = """
INSERT INTO song_results_all (
store_info, store_name, store_category, store_address, store_phone_number,
description, prompt, attr_category, attr_value,
ai, ai_model, genre,
sample_song, result_song, created_at
) VALUES (
:store_info, :store_name, :store_category, :store_address, :store_phone_number,
:description, :prompt, :attr_category, :attr_value,
:ai, :ai_model, :genre,
:sample_song, :result_song, NOW()
);
"""
print("\n[insert_params 선택된 속성 확인]")
print(f"Categories: {selected_categories}")
print(f"Values: {selected_values}")
print()
# attr_category, attr_value
insert_params = {
"store_info": store_info.store_info or "",
"store_name": store_info.store_name,
"store_category": store_info.store_category or "",
"store_address": store_info.store_address or "",
"store_phone_number": store_info.store_phone_number or "",
"description": store_info.store_info or "",
"prompt": prompt.id,
# 랜덤 선택된 category와 value 사용
"attr_category": ", ".join(selected_categories)
if selected_categories
else "",
"attr_value": ", ".join(selected_values) if selected_values else "",
"ai": "ChatGPT",
"ai_model": "gpt-5-mini",
"genre": "후크송",
"sample_song": combined_sample_song or "없음",
"result_song": final_lyrics,
}
await conn.execute(text(insert_query), insert_params)
await conn.commit()
print("결과 저장 완료")
print("\n전체 결과 조회 중...")
# 9. 생성 결과 가져오기 (created_at 역순)
select_query = """
SELECT * FROM song_results_all
ORDER BY created_at DESC;
"""
all_results = await conn.execute(text(select_query))
results_list = [
{
"id": row.id,
"store_info": row.store_info,
"store_name": row.store_name,
"store_category": row.store_category,
"store_address": row.store_address,
"store_phone_number": row.store_phone_number,
"description": row.description,
"prompt": row.prompt,
"attr_category": row.attr_category,
"attr_value": row.attr_value,
"ai": row.ai,
"ai_model": row.ai_model,
"genre": row.genre,
"sample_song": row.sample_song,
"result_song": row.result_song,
"created_at": row.created_at.isoformat() if row.created_at else None,
}
for row in all_results.fetchall()
]
print(f"전체 {len(results_list)}개의 결과 조회 완료\n")
return results_list
except HTTPException:
raise
except SQLAlchemyError as e:
print(f"Database Error: {e}")
import traceback
traceback.print_exc()
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="데이터베이스 연결에 문제가 발생했습니다.",
)
except Exception as e:
print(f"Unexpected Error: {e}")
import traceback
traceback.print_exc()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="서비스 처리 중 오류가 발생했습니다.",
)

View File

@ -13,6 +13,7 @@ from sqlalchemy.exc import SQLAlchemyError
from app.database.session import BackgroundSessionLocal from app.database.session import BackgroundSessionLocal
from app.lyric.models import Lyric from app.lyric.models import Lyric
from app.utils.chatgpt_prompt import ChatgptService from app.utils.chatgpt_prompt import ChatgptService
from app.utils.prompts.prompts import Prompt
# 로거 설정 # 로거 설정
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -68,8 +69,8 @@ async def _update_lyric_status(
async def generate_lyric_background( async def generate_lyric_background(
task_id: str, task_id: str,
prompt: str, prompt: Prompt,
language: str, lyric_input_data: dict, # 프롬프트 메타데이터에서 정의된 Input
) -> None: ) -> None:
"""백그라운드에서 ChatGPT를 통해 가사를 생성하고 Lyric 테이블을 업데이트합니다. """백그라운드에서 ChatGPT를 통해 가사를 생성하고 Lyric 테이블을 업데이트합니다.
@ -84,20 +85,22 @@ async def generate_lyric_background(
logger.info(f"[generate_lyric_background] START - task_id: {task_id}") logger.info(f"[generate_lyric_background] START - task_id: {task_id}")
print(f"[generate_lyric_background] ========== START ==========") print(f"[generate_lyric_background] ========== START ==========")
print(f"[generate_lyric_background] task_id: {task_id}") print(f"[generate_lyric_background] task_id: {task_id}")
print(f"[generate_lyric_background] language: {language}") print(f"[generate_lyric_background] language: {lyric_input_data['language']}")
print(f"[generate_lyric_background] prompt length: {len(prompt)}") #print(f"[generate_lyric_background] prompt length: {len(prompt)}자")
try: try:
# ========== Step 1: ChatGPT 서비스 초기화 ========== # ========== Step 1: ChatGPT 서비스 초기화 ==========
step1_start = time.perf_counter() step1_start = time.perf_counter()
print(f"[generate_lyric_background] Step 1: ChatGPT 서비스 초기화...") print(f"[generate_lyric_background] Step 1: ChatGPT 서비스 초기화...")
service = ChatgptService( # service = ChatgptService(
customer_name="", # 프롬프트가 이미 생성되었으므로 빈 값 # customer_name="", # 프롬프트가 이미 생성되었으므로 빈 값
region="", # region="",
detail_region_info="", # detail_region_info="",
language=language, # language=language,
) # )
chatgpt = ChatgptService()
step1_elapsed = (time.perf_counter() - step1_start) * 1000 step1_elapsed = (time.perf_counter() - step1_start) * 1000
print(f"[generate_lyric_background] Step 1 완료 ({step1_elapsed:.1f}ms)") print(f"[generate_lyric_background] Step 1 완료 ({step1_elapsed:.1f}ms)")
@ -107,8 +110,9 @@ async def generate_lyric_background(
logger.info(f"[generate_lyric_background] Step 2: ChatGPT API 호출 시작 - task_id: {task_id}") logger.info(f"[generate_lyric_background] Step 2: ChatGPT API 호출 시작 - task_id: {task_id}")
print(f"[generate_lyric_background] Step 2: ChatGPT API 호출 시작...") print(f"[generate_lyric_background] Step 2: ChatGPT API 호출 시작...")
result = await service.generate(prompt=prompt) #result = await service.generate(prompt=prompt)
result_response = await chatgpt.generate_structured_output(prompt, lyric_input_data)
result = result_response['lyric']
step2_elapsed = (time.perf_counter() - step2_start) * 1000 step2_elapsed = (time.perf_counter() - step2_start) * 1000
logger.info(f"[generate_lyric_background] Step 2 완료 - 응답 {len(result)}자 ({step2_elapsed:.1f}ms)") logger.info(f"[generate_lyric_background] Step 2 완료 - 응답 {len(result)}자 ({step2_elapsed:.1f}ms)")
print(f"[generate_lyric_background] Step 2 완료 - 응답 {len(result)}자 ({step2_elapsed:.1f}ms)") print(f"[generate_lyric_background] Step 2 완료 - 응답 {len(result)}자 ({step2_elapsed:.1f}ms)")

View File

@ -5,207 +5,11 @@ import re
from openai import AsyncOpenAI from openai import AsyncOpenAI
from config import apikey_settings from config import apikey_settings
from app.utils.prompts.prompts import Prompt
# 로거 설정 # 로거 설정
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# fmt: off
LYRICS_PROMPT_TEMPLATE_ORI = """
1.Act as a content marketing expert with domain knowledges in [pension/staying services] in Korea, Goal: plan viral content creation that lead online reservations and promotion
2.Conduct an in-depth analysis of [업체명:{customer_name}] in [지역명:{region}] by examining their official website or informations, photos on never map and online presence. Create a comprehensive "[지역 상세: {detail_region_info}]_Brand & Marketing Intelligence Report in Korean, that includes:
**Core Analysis:**
- Target customer segments & personas
- Unique Selling Propositions (USPs) and competitive differentiators
- Comprehensive competitor landscape analysis (direct & indirect competitors)
- Market positioning assessment
**Content Strategy Framework:**
- Seasonal content calendar with trend integration
- Visual storytelling direction (shot-by-shot creative guidance)
- Brand tone & voice guidelines
- Content themes aligned with target audience behaviors
**SEO & AEO Optimization:**
- Recommended primary and long-tail keywords
- SEO-optimized taglines and meta descriptions
- Answer Engine Optimization (AEO) content suggestions
- Local search optimization strategies
**Actionable Recommendations:**
- Content distribution strategy across platforms
- KPI measurement framework
- Budget allocation recommendations by content type
콘텐츠 기획(Lyrics, Prompt for SUNO)
1. Based on the Brand & Marketing Intelligence Report for [업체명 + 지역명 / {customer_name} ({region})], create original lyrics and define music attributes (song mood, BPM, genres, and key musical motifs, Prompt for Suno.com) specifically tailored for viral content.
2. The lyrics should include, the name of [ Promotion Subject], [location], [main target],[Famous place, accessible in 10min], promotional words including but not limited to [인스타 감성], [사진같은 하루]
Deliver outputs optimized for three formats:1 minute. Ensure that each version aligns with the brand's core identity and is suitable for use in digital marketing and social media campaigns, in Korean
""".strip()
# fmt: on
LYRICS_PROMPT_TEMPLATE = """
[ROLE]
Content marketing expert and creative songwriter specializing in pension/accommodation services
[INPUT]
- Business Name: {customer_name}
- Region: {region}
- Region Details: {detail_region_info}
- Output Language: {language}
[INTERNAL ANALYSIS - DO NOT OUTPUT]
Analyze the following internally to inform lyrics creation:
- Target customer segments and personas
- Unique Selling Propositions (USPs)
- Regional characteristics and nearby attractions (within 10 min access)
- Seasonal appeal points
- Emotional triggers for the target audience
[LYRICS REQUIREMENTS]
1. Must Include Elements:
- Business name (TRANSLATED or TRANSLITERATED to {language})
- Region name (TRANSLATED or TRANSLITERATED to {language})
- Main target audience appeal
- Nearby famous places or regional characteristics
2. Keywords to Incorporate (use language-appropriate trendy expressions):
- Korean: 인스타 감성, 사진같은 하루, 힐링, 여행, 감성 숙소
- English: Instagram vibes, picture-perfect day, healing, travel, getaway
- Chinese: 网红打卡, 治愈系, 旅行, 度假, 拍照圣地
- Japanese: インスタ映え, 写真のような一日, 癒し, 旅行, 絶景
- Thai: กสวย, ลใจ, เทยว, ายร, วสวย
- Vietnamese: check-in đẹp, healing, du lịch, nghỉ dưỡng, view đẹp
3. Structure:
- Length: For 1-minute video (approximately 8-12 lines)
- Flow: Verse structure suitable for music
- Rhythm: Natural speech rhythm in the specified language
4. Tone:
- Emotional and heartfelt
- Trendy and viral-friendly
- Relatable to target audience
[CRITICAL LANGUAGE REQUIREMENT - ABSOLUTE RULE]
ALL OUTPUT MUST BE 100% WRITTEN IN {language} - NO EXCEPTIONS
- ALL lyrics content: {language} ONLY
- ALL proper nouns (business names, region names, place names): MUST be translated or transliterated to {language}
- Korean input like "군산" must become "Gunsan" in English, "群山" in Chinese, "グンサン" in Japanese, etc.
- Korean input like "스테이 머뭄" must become "Stay Meoum" in English, "住留" in Chinese, "ステイモーム" in Japanese, etc.
- ZERO Korean characters (한글) allowed when output language is NOT Korean
- ZERO mixing of languages - the entire output must be monolingual in {language}
- This is a NON-NEGOTIABLE requirement
- Any output containing characters from other languages is considered a COMPLETE FAILURE
- Violation of this rule invalidates the entire response
[OUTPUT RULES - STRICTLY ENFORCED]
- Output lyrics ONLY
- Lyrics MUST be written ENTIRELY in {language} - NO EXCEPTIONS
- ALL names and places MUST be in {language} script/alphabet
- NO Korean (한글), Chinese (漢字), Japanese (仮名), Thai (ไทย), or Vietnamese (Tiếng Việt) characters unless that is the selected output language
- NO titles, descriptions, analysis, or explanations
- NO greetings or closing remarks
- NO additional commentary before or after lyrics
- NO line numbers or labels
- Follow the exact format below
[OUTPUT FORMAT - SUCCESS]
---
[Lyrics ENTIRELY in {language} here - no other language characters allowed]
---
[OUTPUT FORMAT - FAILURE]
If you cannot generate lyrics due to insufficient information, invalid input, or any other reason:
---
ERROR: [Brief reason for failure in English]
---
""".strip()
# fmt: on
MARKETING_ANALYSIS_PROMPT_TEMPLATE = """
[ROLE]
Content marketing expert specializing in pension/accommodation services in Korea
[INPUT]
- Business Name: {customer_name}
- Region: {region}
- Region Details: {detail_region_info}
[ANALYSIS REQUIREMENTS]
Provide comprehensive marketing analysis including:
1. Target Customer Segments
- Primary and secondary target personas
- Age groups, travel preferences, booking patterns
2. Unique Selling Propositions (USPs)
- Key differentiators based on location and region details
- Competitive advantages
3. Regional Characteristics
- Nearby attractions and famous places (within 10 min access)
- Local food, activities, and experiences
- Transportation accessibility
4. Seasonal Appeal Points
- Best seasons to visit
- Seasonal activities and events
- Peak/off-peak marketing opportunities
5. Marketing Keywords
- Recommended hashtags and search keywords
- Trending terms relevant to the property
[ADDITIONAL REQUIREMENTS]
1. Recommended Tags
- Generate 5 recommended hashtags/tags based on the business characteristics
- Tags should be trendy, searchable, and relevant to accommodation marketing
- Return as JSON with key "tags"
- **MUST be written in Korean (한국어)**
[CRITICAL LANGUAGE REQUIREMENT - ABSOLUTE RULE]
ALL OUTPUT MUST BE WRITTEN IN KOREAN (한국어)
- Analysis sections: Korean only
- Tags: Korean only
- This is a NON-NEGOTIABLE requirement
- Any output in English or other languages is considered a FAILURE
- Violation of this rule invalidates the entire response
[OUTPUT RULES - STRICTLY ENFORCED]
- Output analysis ONLY
- ALL content MUST be written in Korean (한국어) - NO EXCEPTIONS
- NO greetings or closing remarks
- NO additional commentary before or after analysis
- Follow the exact format below
[OUTPUT FORMAT - SUCCESS]
---
## 타겟 고객 분석
[한국어로 작성된 타겟 고객 분석]
## 핵심 차별점 (USP)
[한국어로 작성된 USP 분석]
## 지역 특성
[한국어로 작성된 지역 특성 분석]
## 시즌별 매력 포인트
[한국어로 작성된 시즌별 분석]
## 마케팅 키워드
[한국어로 작성된 마케팅 키워드]
## JSON Data
```json
{{
"tags": ["태그1", "태그2", "태그3", "태그4", "태그5"]
}}
```
---
[OUTPUT FORMAT - FAILURE]
If you cannot generate analysis due to insufficient information, invalid input, or any other reason:
---
ERROR: [Brief reason for failure in English]
---
""".strip()
# fmt: on # fmt: on
@ -215,190 +19,30 @@ class ChatgptService:
GPT 5.0 모델을 사용하여 마케팅 가사 분석을 생성합니다. GPT 5.0 모델을 사용하여 마케팅 가사 분석을 생성합니다.
""" """
def __init__( def __init__(self):
self,
customer_name: str,
region: str,
detail_region_info: str = "",
language: str = "Korean",
):
# 최신 모델: gpt-5-mini
self.model = "gpt-5-mini"
self.client = AsyncOpenAI(api_key=apikey_settings.CHATGPT_API_KEY) self.client = AsyncOpenAI(api_key=apikey_settings.CHATGPT_API_KEY)
self.customer_name = customer_name
self.region = region async def _call_structured_output_with_response_gpt_api(self, prompt: str, output_format : dict, model:str) -> dict:
self.detail_region_info = detail_region_info content = [{"type": "input_text", "text": prompt}]
self.language = language response = await self.client.responses.create(
model=model,
def build_lyrics_prompt(self) -> str: input=[{"role": "user", "content": content}],
"""LYRICS_PROMPT_TEMPLATE에 고객 정보를 대입하여 완성된 프롬프트 반환""" text = output_format
return LYRICS_PROMPT_TEMPLATE.format(
customer_name=self.customer_name,
region=self.region,
detail_region_info=self.detail_region_info,
language=self.language,
) )
structured_output = json.loads(response.output_text)
return structured_output or {}
def build_market_analysis_prompt(self) -> str: async def generate_structured_output(
"""MARKETING_ANALYSIS_PROMPT_TEMPLATE에 고객 정보를 대입하여 완성된 프롬프트 반환"""
return MARKETING_ANALYSIS_PROMPT_TEMPLATE.format(
customer_name=self.customer_name,
region=self.region,
detail_region_info=self.detail_region_info,
)
async def _call_gpt_api(self, prompt: str) -> str:
"""GPT API를 직접 호출합니다 (내부 메서드).
Args:
prompt: GPT에 전달할 프롬프트
Returns:
GPT 응답 문자열
Raises:
APIError, APIConnectionError, RateLimitError: OpenAI API 오류
"""
completion = await self.client.chat.completions.create(
model=self.model, messages=[{"role": "user", "content": prompt}]
)
message = completion.choices[0].message.content
return message or ""
async def generate(
self, self,
prompt: str | None = None, prompt : Prompt,
input_data : dict,
) -> str: ) -> str:
"""GPT에게 프롬프트를 전달하여 결과를 반환합니다. prompt_text = prompt.build_prompt(input_data)
Args: print(f"[ChatgptService] Generated Prompt (length: {len(prompt_text)})")
prompt: GPT에 전달할 프롬프트 (None이면 기본 가사 프롬프트 사용) logger.info(f"[ChatgptService] Starting GPT request with structured output with model: {prompt.prompt_model}")
Returns:
GPT 응답 문자열
Raises:
APIError, APIConnectionError, RateLimitError: OpenAI API 오류
"""
if prompt is None:
prompt = self.build_lyrics_prompt()
print(f"[ChatgptService] Generated Prompt (length: {len(prompt)})")
logger.info(f"[ChatgptService] Starting GPT request with model: {self.model}")
# GPT API 호출 # GPT API 호출
response = await self._call_gpt_api(prompt) response = await self._call_structured_output_with_response_gpt_api(prompt_text, prompt.prompt_output, prompt.prompt_model)
print(f"[ChatgptService] SUCCESS - Response length: {len(response)}")
logger.info(f"[ChatgptService] SUCCESS - Response length: {len(response)}")
return response return response
async def summarize_marketing(self, text: str) -> str:
"""마케팅 텍스트를 항목으로 구분하여 500자로 요약 정리.
Args:
text: 요약할 마케팅 텍스트
Returns:
요약된 텍스트
Raises:
APIError, APIConnectionError, RateLimitError: OpenAI API 오류
"""
prompt = f"""[ROLE]
마케팅 콘텐츠 요약 전문가
[INPUT]
{text}
[TASK]
텍스트를 분석하여 핵심 내용을 항목별로 구분하여 500 이내로 요약해주세요.
[OUTPUT REQUIREMENTS]
- 5 항목으로 구분: 타겟 고객, 핵심 차별점, 지역 특성, 시즌별 포인트, 추천 키워드
- 항목은 줄바꿈으로 구분
- 500 이내로 요약
- 핵심 정보만 간결하게 포함
- 한국어로 작성
- 특수문자 사용 금지 (괄호, 슬래시, 하이픈, 물결표 제외)
- 쉼표와 마침표만 사용하여 자연스러운 문장으로 작성
[OUTPUT FORMAT - 반드시 아래 형식 준수]
---
타겟 고객
[대상 고객층을 자연스러운 문장으로 설명]
핵심 차별점
[숙소의 차별화 포인트를 자연스러운 문장으로 설명]
지역 특성
[주변 관광지와 지역 특색을 자연스러운 문장으로 설명]
시즌별 포인트
[계절별 매력 포인트를 자연스러운 문장으로 설명]
추천 키워드
[마케팅에 활용할 키워드를 쉼표로 구분하여 나열]
---
"""
result = await self.generate(prompt=prompt)
# --- 구분자 제거
if result.startswith("---"):
result = result[3:].strip()
if result.endswith("---"):
result = result[:-3].strip()
return result
async def parse_marketing_analysis(
self, raw_response: str, facility_info: str | None = None
) -> dict:
"""ChatGPT 마케팅 분석 응답을 파싱하고 요약하여 딕셔너리로 반환
Args:
raw_response: ChatGPT 마케팅 분석 응답 원문
facility_info: 크롤링에서 가져온 편의시설 정보 문자열
Returns:
dict: {"report": str, "tags": list[str], "facilities": list[str]}
"""
tags: list[str] = []
facilities: list[str] = []
report = raw_response
# JSON 블록 추출 시도
json_match = re.search(r"```json\s*(\{.*?\})\s*```", raw_response, re.DOTALL)
if json_match:
try:
json_data = json.loads(json_match.group(1))
tags = json_data.get("tags", [])
print(f"[parse_marketing_analysis] GPT 응답에서 tags 파싱 완료: {tags}")
# JSON 블록을 제외한 리포트 부분 추출
report = raw_response[: json_match.start()].strip()
# --- 구분자 제거
if report.startswith("---"):
report = report[3:].strip()
if report.endswith("---"):
report = report[:-3].strip()
except json.JSONDecodeError:
print("[parse_marketing_analysis] JSON 파싱 실패")
pass
# 크롤링에서 가져온 facility_info로 facilities 설정
print(f"[parse_marketing_analysis] 크롤링 facility_info 원본: {facility_info}")
if facility_info:
# 쉼표로 구분된 편의시설 문자열을 리스트로 변환
facilities = [f.strip() for f in facility_info.split(",") if f.strip()]
print(f"[parse_marketing_analysis] facility_info 파싱 결과: {facilities}")
else:
facilities = ["등록된 정보 없음"]
print("[parse_marketing_analysis] facility_info 없음 - '등록된 정보 없음' 설정")
# 리포트 내용을 500자로 요약
if report:
report = await self.summarize_marketing(report)
print(f"[parse_marketing_analysis] 최종 facilities: {facilities}")
return {"report": report, "tags": tags, "facilities": facilities}

View File

@ -0,0 +1 @@
{"model": "gpt-5-mini", "prompt_variables": ["customer_name", "region", "detail_region_info", "marketing_intelligence_summary", "language", "promotional_expression_example", "timing_rules"], "output_format": {"format": {"type": "json_schema", "name": "lyric", "schema": {"type": "object", "properties": {"lyric": {"type": "string"}}, "required": ["lyric"], "additionalProperties": false}, "strict": true}}}

View File

@ -0,0 +1,78 @@
[ROLE]
You are a content marketing expert, brand strategist, and creative songwriter
specializing in Korean pension / accommodation businesses.
You create lyrics strictly based on Brand & Marketing Intelligence analysis
and optimized for viral short-form video content.
[INPUT]
Business Name: {customer_name}
Region: {region}
Region Details: {detail_region_info}
Brand & Marketing Intelligence Report: {marketing_intelligence_summary}
Output Language: {language}
[INTERNAL ANALYSIS DO NOT OUTPUT]
Internally analyze the following to guide all creative decisions:
- Core brand identity and positioning
- Emotional hooks derived from selling points
- Target audience lifestyle, desires, and travel motivation
- Regional atmosphere and symbolic imagery
- How the stay converts into “shareable moments”
- Which selling points must surface implicitly in lyrics
[LYRICS & MUSIC CREATION TASK]
Based on the Brand & Marketing Intelligence Report for [{customer_name} ({region})], generate:
- Original promotional lyrics
- Music attributes for AI music generation (Suno-compatible prompt)
The output must be designed for VIRAL DIGITAL CONTENT
(short-form video, reels, ads).
[LYRICS REQUIREMENTS]
Mandatory Inclusions:
- Business name
- Region name
- Promotion subject
- Promotional expressions including:
{promotional_expressions[language]}
Content Rules:
- Lyrics must be emotionally driven, not descriptive listings
- Selling points must be IMPLIED, not explained
- Must sound natural when sung
- Must feel like a lifestyle moment, not an advertisement
Tone & Style:
- Warm, emotional, and aspirational
- Trendy, viral-friendly phrasing
- Calm but memorable hooks
- Suitable for travel / stay-related content
[SONG & MUSIC ATTRIBUTES FOR SUNO PROMPT]
After the lyrics, generate a concise music prompt including:
Song mood (emotional keywords)
BPM range
Recommended genres (max 2)
Key musical motifs or instruments
Overall vibe (1 short sentence)
[CRITICAL LANGUAGE REQUIREMENT ABSOLUTE RULE]
ALL OUTPUT MUST BE 100% WRITTEN IN {language}.
no mixed languages
All names, places, and expressions must be in {language}
Any violation invalidates the entire output
[OUTPUT RULES STRICT]
{timing_rules}
812 lines
Full verse flow, immersive mood
No explanations
No headings
No bullet points
No analysis
No extra text
[FAILURE FORMAT]
If generation is impossible:
ERROR: Brief reason in English

View File

@ -0,0 +1 @@
{"model": "gpt-5-mini", "prompt_variables": ["customer_name", "region", "detail_region_info"], "output_format": {"format": {"type": "json_schema", "name": "report", "schema": {"type": "object", "properties": {"report": {"type": "string"}, "selling_points": {"type": "array", "items": {"type": "object", "properties": {"category": {"type": "string"}, "keywords": {"type": "string"}, "description": {"type": "string"}}, "required": ["category", "keywords", "description"], "additionalProperties": false}}, "tags": {"type": "array", "items": {"type": "string"}}}, "required": ["report", "selling_points", "tags"], "additionalProperties": false}, "strict": true}}}

View File

@ -0,0 +1,62 @@
[Role & Objective]
Act as a content marketing expert with strong domain knowledge in the Korean pension / stay-accommodation industry.
Your goal is to produce a Marketing Intelligence Report that will be shown to accommodation owners BEFORE any content is generated.
The report must clearly explain what makes the property sellable, marketable, and scalable through content.
[INPUT]
- Business Name: {customer_name}
- Region: {region}
- Region Details: {detail_region_info}
[Core Analysis Requirements]
Analyze the property based on:
Location, concept, photos, online presence, and nearby environment
Target customer behavior and reservation decision factors
Include:
- Target customer segments & personas
- Unique Selling Propositions (USPs)
- Competitive landscape (direct & indirect competitors)
- Market positioning
[Key Selling Point Structuring UI Optimized]
From the analysis above, extract the main Key Selling Points using the structure below.
Rules:
Focus only on factors that directly influence booking decisions
Each selling point must be concise and visually scannable
Language must be reusable for ads, short-form videos, and listing headlines
Avoid full sentences in descriptions; use short selling phrases
Output format:
[Category]
(Tag keyword 5~8 words, noun-based, UI oval-style)
One-line selling phrase (not a full sentence)
Limit:
5 to 8 Key Selling Points only
[Content & Automation Readiness Check]
Ensure that:
Each tag keyword can directly map to a content theme
Each selling phrase can be used as:
- Video hook
- Image headline
- Ad copy snippet
[Tag Generation Rules]
- Tags must include **only core keywords that can be directly used for viral video song lyrics**
- Each tag should be selected with **search discovery + emotional resonance + reservation conversion** in mind
- The number of tags must be **exactly 5**
- Tags must be **nouns or short keyword phrases**; full sentences are strictly prohibited
- The following categories must be **balanced and all represented**:
1) **Location / Local context** (region name, neighborhood, travel context)
2) **Accommodation positioning** (emotional stay, private stay, boutique stay, etc.)
3) **Emotion / Experience** (healing, rest, one-day escape, memory, etc.)
4) **SNS / Viral signals** (Instagram vibes, picture-perfect day, aesthetic travel, etc.)
5) **Travel & booking intent** (travel, getaway, stay, relaxation, etc.)
- If a brand name exists, **at least one tag must include the brand name or a brand-specific expression**
- Avoid overly generic keywords (e.g., “hotel”, “travel” alone); **prioritize distinctive, differentiating phrases**
- The final output must strictly follow the JSON format below, with no additional text
"tags": ["Tag1", "Tag2", "Tag3", "Tag4", "Tag5"]

View File

@ -0,0 +1,69 @@
import os, json
from abc import ABCMeta
from config import prompt_settings
class Prompt():
prompt_name : str # ex) marketing_prompt
prompt_template_path : str #프롬프트 경로
prompt_template : str # fstring 포맷
prompt_input : list
prompt_output : dict
prompt_model : str
def __init__(self, prompt_name, prompt_template_path):
self.prompt_name = prompt_name
self.prompt_template_path = prompt_template_path
self.prompt_template, prompt_dict = self.read_prompt()
self.prompt_input = prompt_dict['prompt_variables']
self.prompt_output = prompt_dict['output_format']
self.prompt_model = prompt_dict.get('model', "gpt-5-mini")
def _reload_prompt(self):
self.prompt_template, prompt_dict = self.read_prompt()
self.prompt_input = prompt_dict['prompt_variables']
self.prompt_output = prompt_dict['output_format']
self.prompt_model = prompt_dict.get('model', "gpt-5-mini")
def read_prompt(self) -> tuple[str, dict]:
template_text_path = self.prompt_template_path + ".txt"
prompt_dict_path = self.prompt_template_path + ".json"
with open(template_text_path, "r") as fp:
prompt_template = fp.read()
with open(prompt_dict_path, "r") as fp:
prompt_dict = json.load(fp)
return prompt_template, prompt_dict
def build_prompt(self, input_data:dict) -> str:
self.check_input(input_data)
build_template = self.prompt_template
print("build_template", build_template)
print("input_data", input_data)
build_template = build_template.format(**input_data)
return build_template
def check_input(self, input_data:dict) -> bool:
missing_variables = input_data.keys() - set(self.prompt_input)
if missing_variables:
raise Exception(f"missing_variable for prompt {self.prompt_name} : {missing_variables}")
return True
marketing_prompt = Prompt(
prompt_name=prompt_settings.MARKETING_PROMPT_NAME,
prompt_template_path=os.path.join(prompt_settings.PROMPT_FOLDER_ROOT, prompt_settings.MARKETING_PROMPT_NAME)
)
summarize_prompt = Prompt(
prompt_name=prompt_settings.SUMMARIZE_PROMPT_NAME,
prompt_template_path=os.path.join(prompt_settings.PROMPT_FOLDER_ROOT, prompt_settings.SUMMARIZE_PROMPT_NAME)
)
lyric_prompt = Prompt(
prompt_name=prompt_settings.LYLIC_PROMPT_NAME,
prompt_template_path=os.path.join(prompt_settings.PROMPT_FOLDER_ROOT, prompt_settings.LYLIC_PROMPT_NAME)
)
def reload_all_prompt():
marketing_prompt._reload_prompt()
summarize_prompt._reload_prompt()
lyric_prompt._reload_prompt()

View File

@ -0,0 +1,33 @@
{
"prompt_variables": [
"report",
"selling_points"
],
"output_format": {
"format": {
"type": "json_schema",
"name": "tags",
"schema": {
"type": "object",
"properties": {
"category": {
"type": "string"
},
"tag_keywords": {
"type": "string"
},
"description": {
"type": "string"
}
},
"required": [
"category",
"tag_keywords",
"description"
],
"additionalProperties": false
},
"strict": true
}
}
}

View File

@ -0,0 +1,53 @@
입력 :
분석 보고서
{report}
셀링 포인트
{selling_points}
위 분석 결과를 바탕으로, 주요 셀링 포인트를 다음 구조로 재정리하라.
조건:
각 셀링 포인트는 반드시 ‘카테고리 → 태그 키워드 → 한 줄 설명’ 구조를 가질 것
태그 키워드는 UI 상에서 타원(oval) 형태의 시각적 태그로 사용될 것을 가정하여
- 3 ~ 6단어 이내
- 명사 또는 명사형 키워드로 작성
- 설명은 문장이 아닌, 짧은 ‘셀링 문구’ 형태로 작성할 것
- 광고·숏폼·상세페이지 어디에도 바로 재사용 가능해야 함
- 전체 셀링 포인트 개수는 5~7개로 제한
출력 형식:
[카테고리명]
(태그 키워드)
- 한 줄 설명 문구
예시:
[공간 정체성]
(100년 적산가옥 · 시간의 결)
- 하루를 ‘숙박’이 아닌 ‘체류’로 바꾸는 공간
[입지 & 희소성]
(말랭이마을 · 로컬 히든플레이스)
- 관광지가 아닌, 군산을 아는 사람의 선택
[프라이버시]
(독채 숙소 · 프라이빗 스테이)
- 누구의 방해도 없는 완전한 휴식 구조
[비주얼 경쟁력]
(감성 인테리어 · 자연광 스폿)
- 찍는 순간 콘텐츠가 되는 공간 설계
[타깃 최적화]
(커플 · 소규모 여행)
- 둘에게 가장 이상적인 공간 밀도
[체류 경험]
(아무것도 안 해도 되는 하루)
- 일정 없이도 만족되는 하루 루틴
[브랜드 포지션]
(호텔도 펜션도 아닌 아지트)
- 다시 돌아오고 싶은 개인적 장소

View File

@ -167,6 +167,13 @@ class CreatomateSettings(BaseSettings):
model_config = _base_config model_config = _base_config
class PromptSettings(BaseSettings):
PROMPT_FOLDER_ROOT : str = Field(default="./app/utils/prompts")
MARKETING_PROMPT_NAME : str = Field(default="marketing_prompt")
SUMMARIZE_PROMPT_NAME : str = Field(default="summarize_prompt")
LYLIC_PROMPT_NAME : str = Field(default="lyric_prompt")
model_config = _base_config
prj_settings = ProjectSettings() prj_settings = ProjectSettings()
apikey_settings = APIKeySettings() apikey_settings = APIKeySettings()
@ -177,3 +184,4 @@ cors_settings = CORSSettings()
crawler_settings = CrawlerSettings() crawler_settings = CrawlerSettings()
azure_blob_settings = AzureBlobSettings() azure_blob_settings = AzureBlobSettings()
creatomate_settings = CreatomateSettings() creatomate_settings = CreatomateSettings()
prompt_settings = PromptSettings()

723
main_tester.ipynb Normal file
View File

@ -0,0 +1,723 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"id": "e7af5103-62db-4a32-b431-6395c85d7ac9",
"metadata": {},
"outputs": [],
"source": [
"from app.home.api.routers.v1.home import crawling\n",
"from app.utils.prompts import prompts"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "6cf7ae9b-3ffe-4046-9cab-f33bc071b288",
"metadata": {},
"outputs": [],
"source": [
"from config import crawler_settings"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "4c4ec4c5-9efb-470f-99cf-a18a5b80352f",
"metadata": {},
"outputs": [],
"source": [
"from app.home.schemas.home_schema import (\n",
" CrawlingRequest,\n",
" CrawlingResponse,\n",
" ErrorResponse,\n",
" ImageUploadResponse,\n",
" ImageUploadResultItem,\n",
" ImageUrlItem,\n",
" MarketingAnalysis,\n",
" ProcessedInfo,\n",
")\n",
"import json"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "be5d0e16-8cc6-44d4-ae93-8252caa09940",
"metadata": {},
"outputs": [],
"source": [
"val1 = CrawlingRequest(**{\"url\" : 'https://map.naver.com/p/entry/place/1903455560?placePath=/home?from=map&fromPanelNum=1&additionalHeight=76&timestamp=202601131552&locale=ko&svcName=map_pcv5&businessCategory=pension&c=15.00,0,0,0,dh'})"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "c13742d7-70f4-4a6d-90c2-8b84f245a08c",
"metadata": {},
"outputs": [],
"source": [
"from app.utils.prompts.prompts import reload_all_prompt\n",
"reload_all_prompt()"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "d4db2ec1-b2af-4993-8832-47f380c17015",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[crawling] ========== START ==========\n",
"[crawling] URL: https://map.naver.com/p/entry/place/1903455560?placePath=/home?from=map&fromPane...\n",
"[crawling] Step 1: 네이버 지도 크롤링 시작...\n",
"[NvMapScraper] Requesting place_id: 1903455560\n",
"[NvMapScraper] SUCCESS - place_id: 1903455560\n",
"[crawling] Step 1 완료 - 이미지 44개 (659.9ms)\n",
"[crawling] Step 2: 정보 가공 시작...\n",
"[crawling] Step 2 완료 - 오블로모프, 군산시 (0.7ms)\n",
"[crawling] Step 3: ChatGPT 마케팅 분석 시작...\n",
"[crawling] Step 3-1: 서비스 초기화 완료 (59.1ms)\n",
"build_template \n",
"[Role & Objective]\n",
"Act as a content marketing expert with strong domain knowledge in the Korean pension / stay-accommodation industry.\n",
"Your goal is to produce a Marketing Intelligence Report that will be shown to accommodation owners BEFORE any content is generated.\n",
"The report must clearly explain what makes the property sellable, marketable, and scalable through content.\n",
"\n",
"[INPUT]\n",
"- Business Name: {customer_name}\n",
"- Region: {region}\n",
"- Region Details: {detail_region_info}\n",
"\n",
"[Core Analysis Requirements]\n",
"Analyze the property based on:\n",
"Location, concept, photos, online presence, and nearby environment\n",
"Target customer behavior and reservation decision factors\n",
"Include:\n",
"- Target customer segments & personas\n",
"- Unique Selling Propositions (USPs)\n",
"- Competitive landscape (direct & indirect competitors)\n",
"- Market positioning\n",
"\n",
"[Key Selling Point Structuring UI Optimized]\n",
"From the analysis above, extract the main Key Selling Points using the structure below.\n",
"Rules:\n",
"Focus only on factors that directly influence booking decisions\n",
"Each selling point must be concise and visually scannable\n",
"Language must be reusable for ads, short-form videos, and listing headlines\n",
"Avoid full sentences in descriptions; use short selling phrases\n",
"\n",
"Output format:\n",
"[Category]\n",
"(Tag keyword 5~8 words, noun-based, UI oval-style)\n",
"One-line selling phrase (not a full sentence)\n",
"Limit:\n",
"5 to 8 Key Selling Points only\n",
"\n",
"[Content & Automation Readiness Check]\n",
"Ensure that:\n",
"Each tag keyword can directly map to a content theme\n",
"Each selling phrase can be used as:\n",
"- Video hook\n",
"- Image headline\n",
"- Ad copy snippet\n",
"\n",
"\n",
"[Tag Generation Rules]\n",
"- Tags must include **only core keywords that can be directly used for viral video song lyrics**\n",
"- Each tag should be selected with **search discovery + emotional resonance + reservation conversion** in mind\n",
"- The number of tags must be **exactly 5**\n",
"- Tags must be **nouns or short keyword phrases**; full sentences are strictly prohibited\n",
"- The following categories must be **balanced and all represented**:\n",
" 1) **Location / Local context** (region name, neighborhood, travel context)\n",
" 2) **Accommodation positioning** (emotional stay, private stay, boutique stay, etc.)\n",
" 3) **Emotion / Experience** (healing, rest, one-day escape, memory, etc.)\n",
" 4) **SNS / Viral signals** (Instagram vibes, picture-perfect day, aesthetic travel, etc.)\n",
" 5) **Travel & booking intent** (travel, getaway, stay, relaxation, etc.)\n",
"\n",
"- If a brand name exists, **at least one tag must include the brand name or a brand-specific expression**\n",
"- Avoid overly generic keywords (e.g., “hotel”, “travel” alone); **prioritize distinctive, differentiating phrases**\n",
"- The final output must strictly follow the JSON format below, with no additional text\n",
"\n",
" \"tags\": [\"Tag1\", \"Tag2\", \"Tag3\", \"Tag4\", \"Tag5\"]\n",
"\n",
"input_data {'customer_name': '오블로모프', 'region': '군산시', 'detail_region_info': '전북 군산시 절골길 16'}\n",
"[ChatgptService] Generated Prompt (length: 2766)\n",
"[crawling] Step 3-3: GPT API 호출 완료 - (59060.2ms)\n",
"[crawling] Step 3-4: 응답 파싱 시작 - facility_info: 무선 인터넷, 예약, 주차\n",
"[crawling] Step 3-4: 응답 파싱 완료 (0.1ms)\n",
"[crawling] Step 3 완료 - 마케팅 분석 성공 (59119.8ms)\n",
"[crawling] ========== COMPLETE ==========\n",
"[crawling] 총 소요시간: 59782.3ms\n",
"[crawling] - Step 1 (크롤링): 659.9ms\n",
"[crawling] - Step 2 (정보가공): 0.7ms\n",
"[crawling] - Step 3 (GPT 분석): 59119.8ms\n",
"[crawling] - GPT API 호출: 59060.2ms\n"
]
}
],
"source": [
"var2 = await crawling(val1)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "79f093f0-d7d2-4ed1-ba43-da06e4ee2073",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'image_list': ['https://ldb-phinf.pstatic.net/20230515_163/1684090233619kRU3v_JPEG/20230513_154207.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20250811_213/17548982879808X4MH_PNG/1.png',\n",
" 'https://ldb-phinf.pstatic.net/20240409_34/1712622373542UY8aC_JPEG/20231007_051403.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20230515_37/1684090234513tT89X_JPEG/20230513_152018.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20241231_272/1735620966755B9XgT_PNG/DSC09054.png',\n",
" 'https://ldb-phinf.pstatic.net/20240409_100/1712622410472zgP15_JPEG/20230523_153219.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_151/1712623034401FzQbd_JPEG/Screenshot_20240409_093158_Airbnb.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_169/1712622316504ReKji_JPEG/20230728_125946.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20230521_279/1684648422643NI2oj_JPEG/20230521_144343.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_52/1712622993632WR1sT_JPEG/Screenshot_20240409_093237_Airbnb.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20250811_151/1754898220223TNtvB_PNG/2.png',\n",
" 'https://ldb-phinf.pstatic.net/20240409_70/1712622381167p9QOI_JPEG/20230608_175722.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20230515_144/1684090233161cR5mr_JPEG/20230513_180151.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_158/1712621983956CCqdo_JPEG/20240407_121826.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20250811_187/1754893113769iGO5X_JPEG/%B0%C5%BD%C7_01.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_31/17126219901822nnR4_JPEG/20240407_121615.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_94/1712621993863AWMKi_JPEG/20240407_121520.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20230515_165/1684090236297fVhJM_JPEG/20230513_165348.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20230515_102/1684090230350e1v0E_JPEG/20230513_162718.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20230515_26/1684090232743arN2y_JPEG/20230513_174246.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20250811_273/1754893072358V3WcL_JPEG/%B5%F0%C5%D7%C0%CF%C4%C6_02.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_160/1712621974438LLNbD_JPEG/20240407_121848.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_218/1712623006036U39zE_JPEG/Screenshot_20240409_093114_Airbnb.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20230515_210/16840902342654EkeL_JPEG/20230513_152107.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_216/1712623058832HBulg_JPEG/Screenshot_20240409_093309_Airbnb.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20230515_184/1684090223226nO2Az_JPEG/20230514_143325.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20230515_209/1684090697642BHNVR_JPEG/20230514_143528.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_16/1712623029052VNeaz_JPEG/Screenshot_20240409_093141_Airbnb.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20230515_141/1684090233092KwtWy_JPEG/20230513_180105.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_177/1712623066424dcwJ2_JPEG/Screenshot_20240409_093511_Airbnb.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20230515_181/16840902259407iA5Q_JPEG/20230514_144814.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20230515_153/1684090224581Ih4ft_JPEG/20230514_143552.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20230515_205/1684090231467WmulO_JPEG/20230513_180254.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20230515_120/1684090231233PkqCf_JPEG/20230513_152550.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_188/1712623039909sflvy_JPEG/Screenshot_20240409_093209_Airbnb.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_165/1712623049073j0TzM_JPEG/Screenshot_20240409_093254_Airbnb.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_3/17126230950579050V_JPEG/Screenshot_20240409_093412_Airbnb.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_270/1712623091524YX4E6_JPEG/Screenshot_20240409_093355_Airbnb.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_22/1712623083348btwTB_JPEG/Screenshot_20240409_093331_Airbnb.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_242/1712623087423Q7tHk_JPEG/Screenshot_20240409_093339_Airbnb.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_173/1712623098958aFhiB_JPEG/Screenshot_20240409_093422_Airbnb.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_113/1712623103270DOGKI_JPEG/Screenshot_20240409_093435_Airbnb.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_295/17126230704056BTRg_JPEG/Screenshot_20240409_093448_Airbnb.jpg',\n",
" 'https://ldb-phinf.pstatic.net/20240409_178/1712623075172JEt43_JPEG/Screenshot_20240409_093457_Airbnb.jpg'],\n",
" 'image_count': 44,\n",
" 'processed_info': ProcessedInfo(customer_name='오블로모프', region='군산시', detail_region_info='전북 군산시 절골길 16'),\n",
" 'marketing_analysis': MarketingAnalysis(report='요약\\n오블로모프(군산 절골길)는 ‘느림의 미학’을 콘셉트로 한 소규모 부티크 스테이로 포지셔닝할 때 강점이 큽니다. 군산의 근대문화·항구·로컬 카페·해산물 레퍼런스가 결합되면 ‘주말 힐링 + 인생샷’ 수요를 끌어올 수 있습니다. 사진·영상 중심의 콘텐츠, 지역 연계 체험, 예약 편의성(주차·즉시예약·정책 명확화)을 우선 강화하면 전환율 개선과 확장성(패키지, 시즌 프로모션)이 용이합니다.\\n위치 분석\\n- 주소: 전북 군산시 절골길 16 — 주거 밀집·골목형 동선으로 ‘조용한 휴식’ 기대 요소\\n- 인근: 근대문화·항구권 관광지·로컬 카페·해산물 식당 밀집(도보·단거리 이동권 장점)\\n- 교통: 자차 접근성·주차 여부가 예약 결정 핵심(대중교통 이용객 대비 자가용 고객 타깃화 필요)\\n콘셉트·포토·온라인 대비\\n- 콘셉트 잠재력: ‘오블로모프=느림·휴식’ 내러티브 활용 가능(브랜드 스토리텔링 유리)\\n- 포토 포인트 제안: 테라스 일출·실내 빈티지 소품·침구 근접 샷·로컬 푸드 플래팅\\n- 온라인: 네이버 예약, 인스타그램, 블로그(지역 키워드) 우선 등재 필요. 리뷰·FAQ·즉시예약 정보 노출 필수\\n주변 환경 영향요인\\n- 식음·체험: 해산물 전문점·카페투어·공방/산책 루트 연계로 1박 체류 가치 강화\\n- 시즌성: 주중 장기 체류보다는 주말·연휴 수요 집중, 계절별 촬영 포인트로 프로모션\\n타깃 고객 행동·예약 결정 요인\\n- 시각 요소 우선: 사진 퀄리티가 예약 전환을 좌우\\n- 프라이버시·편리성: 전용 테라스·주차·와이파이·편의시설(개인화된 체크인)이 중요\\n- 정책: 유연한 취소·즉시예약·가격 패키징(주말/주중/연박)로 예약장벽 완화\\n타깃 세그먼트(페르소나)\\n1) SNS 커플(2535) — 인생샷·감성카페·주말 데이트 용도\\n2) 휴식형 성인(3050) — ‘힐링’·프라이버시·느긋한 체류 선호\\n3) 콘텐츠 크리에이터·프리랜서(2040) — 사진·영상 소재·원데이 촬영 스팟 수요\\n4) 가족·소규모 그룹(3045) — 주차·편의시설·근거리 식사 옵션 필요\\nUSP(핵심 가치 제안)\\n- 절골길의 조용한 골목 위치로 프라이빗한 휴식 보장\\n- ‘오블로모프’ 감성의 느림·회복 스토리로 차별화\\n- 사진·영상 친화적 인테리어와 야외 테라스(콘텐츠 제작 가치 높음)\\n- 지역 미식·카페 루트와 연계한 플레이스 기반 체류 설계 가능\\n경쟁구도\\n- 직접 경쟁: 군산 내 소규모 펜션·게스트하우스·부티크 스테이\\n- 간접 경쟁: 지역 호텔·에어비앤비·당일치기 여행 코스\\n- 차별화 포인트: 브랜드 스토리(느림)·콘텐츠 친화성·로컬 연계 프로그램\\n시장 포지셔닝 제안\\n- 포지셔닝: ‘군산 절골의 느림 감성 부티크 스테이’ — mid-premium 티어\\n- 가격·프로모션: 주말 프리미엄, 주중 패키지·장기 할인 고려\\n콘텐츠·자동화 준비 체크리스트\\n- 필요 자산: ①외관 황금시간(골목샷) ②테라스/일몰 ③침실·욕실 클로즈업 ④로컬 푸드 컷 ⑤리뷰·게스트 스토리\\n- 콘텐츠 기획: 로컬 루트(카페·식당)·‘하루 힐링’ 숏폼(1530s)·인테리어 B-roll(10s 반복 가능한 클립)\\n- 예약 전환 포인트 템플릿: 사진 헤로·편의 아이콘(주차·와이파이)·간단 정책·CTA\\n- 태그 매핑: 지역·브랜드·힐링·SNS·여행 의도(ads/shorts/리스트 헤드라인 직접 활용)\\n권장 다음 단계\\n1) 사진 촬영 10컷(상기 항목) 2) 인스타 12포스트 + 숏폼 6개 제작 3) 예약 페이지(네이버·에어비앤비) 표준화 및 FAQ 업데이트\\n', tags=['군산절골', '오블로모프스테이', '힐링스테이', '인생샷스폿', '주말여행'], facilities=['군산 절골길 근대문화 항구 카페거리 해산물', '오블로모프 느림의미학 프라이빗 부티크 스테이', '힐링 휴식 일상탈출 온전한쉼 감성여행', '감성포토 인생샷 테라스 일몰 빈티지인테리어', '주차편의 빠른예약 유연취소 와이파이 원데이스테이', '로컬식당 해산물 카페투어 산책코스 공방체험'])}"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"var2"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "f3bf1d76-bd2a-43d5-8d39-f0ab2459701a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Location\n",
"군산 절골 골목 스팟\n",
"골목 깊숙한 전원 감성, 조용한 휴식\n",
"Concept\n",
"프라이빗 전원스테이\n",
"소규모 전용 공간, 맞춤형 프라이버시\n",
"Visuals\n",
"인생샷 포토존\n",
"포토제닉 실내·외 컷, 밤 조명 무드\n",
"Experience\n",
"힐링 리셋 스테이\n",
"일상 탈출·짧은 리셋, 주말 최적\n",
"Digital\n",
"SNS 바이럴 감성\n",
"쇼트폼 영상·릴스용 비주얼 중심\n",
"Booking\n",
"주말 스테이케이션 패키지\n",
"주말 1박 패키지, 조식·체험 옵션\n"
]
}
],
"source": [
"for i in var2[\"selling_points\"]:\n",
" print(i['category'])\n",
" print(i['keywords'])\n",
" print(i['description'])"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "c89cf2eb-4f16-4dc5-90c6-df89191b4e39",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'category': 'Location',\n",
" 'keywords': '군산 절골 골목 스팟',\n",
" 'description': '골목 깊숙한 전원 감성, 조용한 휴식'},\n",
" {'category': 'Concept',\n",
" 'keywords': '프라이빗 전원스테이',\n",
" 'description': '소규모 전용 공간, 맞춤형 프라이버시'},\n",
" {'category': 'Visuals',\n",
" 'keywords': '인생샷 포토존',\n",
" 'description': '포토제닉 실내·외 컷, 밤 조명 무드'},\n",
" {'category': 'Experience',\n",
" 'keywords': '힐링 리셋 스테이',\n",
" 'description': '일상 탈출·짧은 리셋, 주말 최적'},\n",
" {'category': 'Digital',\n",
" 'keywords': 'SNS 바이럴 감성',\n",
" 'description': '쇼트폼 영상·릴스용 비주얼 중심'},\n",
" {'category': 'Booking',\n",
" 'keywords': '주말 스테이케이션 패키지',\n",
" 'description': '주말 1박 패키지, 조식·체험 옵션'}]"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"var2[\"selling_points\"]"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "231963d6-e209-41b3-8e78-2ad5d06943fe",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['오블로모프 군산 절골', '전원감성스테이', '힐링리셋', '인생샷 명소', '주말스테이케이션']"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"var2[\"tags\"]"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "f8260222-d5a2-4018-b465-a4943c82bd3f",
"metadata": {},
"outputs": [],
"source": [
"lyric_prompt = \"\"\"\n",
"[ROLE]\n",
"You are a content marketing expert, brand strategist, and creative songwriter\n",
"specializing in Korean pension / accommodation businesses.\n",
"You create lyrics strictly based on Brand & Marketing Intelligence analysis\n",
"and optimized for viral short-form video content.\n",
"\n",
"[INPUT]\n",
"Business Name: {customer_name}\n",
"Region: {region}\n",
"Region Details: {detail_region_info}\n",
"Brand & Marketing Intelligence Report: {marketing_intelligence_summary}\n",
"Output Language: {language}\n",
"\n",
"[INTERNAL ANALYSIS DO NOT OUTPUT]\n",
"Internally analyze the following to guide all creative decisions:\n",
"- Core brand identity and positioning\n",
"- Emotional hooks derived from selling points\n",
"- Target audience lifestyle, desires, and travel motivation\n",
"- Regional atmosphere and symbolic imagery\n",
"- How the stay converts into “shareable moments”\n",
"- Which selling points must surface implicitly in lyrics\n",
"\n",
"[LYRICS & MUSIC CREATION TASK]\n",
"Based on the Brand & Marketing Intelligence Report for [{customer_name} ({region})], generate:\n",
"- Original promotional lyrics\n",
"- Music attributes for AI music generation (Suno-compatible prompt)\n",
"The output must be designed for VIRAL DIGITAL CONTENT\n",
"(short-form video, reels, ads).\n",
"\n",
"[LYRICS REQUIREMENTS]\n",
"Mandatory Inclusions:\n",
"- Business name\n",
"- Region name\n",
"- Promotion subject\n",
"- Promotional expressions including:\n",
"{promotional_expressions[language]}\n",
"\n",
"Content Rules:\n",
"- Lyrics must be emotionally driven, not descriptive listings\n",
"- Selling points must be IMPLIED, not explained\n",
"- Must sound natural when sung\n",
"- Must feel like a lifestyle moment, not an advertisement\n",
"\n",
"Tone & Style:\n",
"- Warm, emotional, and aspirational\n",
"- Trendy, viral-friendly phrasing\n",
"- Calm but memorable hooks\n",
"- Suitable for travel / stay-related content\n",
"\n",
"[SONG & MUSIC ATTRIBUTES FOR SUNO PROMPT]\n",
"After the lyrics, generate a concise music prompt including:\n",
"Song mood (emotional keywords)\n",
"BPM range\n",
"Recommended genres (max 2)\n",
"Key musical motifs or instruments\n",
"Overall vibe (1 short sentence)\n",
"\n",
"[CRITICAL LANGUAGE REQUIREMENT ABSOLUTE RULE]\n",
"ALL OUTPUT MUST BE 100% WRITTEN IN {language}.\n",
"no mixed languages\n",
"All names, places, and expressions must be in {language} \n",
"Any violation invalidates the entire output\n",
"\n",
"[OUTPUT RULES STRICT]\n",
"{timing_rules}\n",
"812 lines\n",
"Full verse flow, immersive mood\n",
"\n",
"No explanations\n",
"No headings\n",
"No bullet points\n",
"No analysis\n",
"No extra text\n",
"\n",
"[FAILURE FORMAT]\n",
"If generation is impossible:\n",
"ERROR: Brief reason in English\n",
"\"\"\"\n",
"lyric_prompt_dict = {\n",
" \"prompt_variables\" :\n",
" [\n",
" \"customer_name\",\n",
" \"region\",\n",
" \"detail_region_info\",\n",
" \"marketing_intelligence_summary\",\n",
" \"language\",\n",
" \"promotional_expression_example\",\n",
" \"timing_rules\",\n",
" \n",
" ],\n",
" \"output_format\" : {\n",
" \"format\": {\n",
" \"type\": \"json_schema\",\n",
" \"name\": \"lyric\",\n",
" \"schema\": {\n",
" \"type\":\"object\",\n",
" \"properties\" : {\n",
" \"lyric\" : { \n",
" \"type\" : \"string\"\n",
" }\n",
" },\n",
" \"required\": [\"lyric\"],\n",
" \"additionalProperties\": False,\n",
" },\n",
" \"strict\": True\n",
" }\n",
" }\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "79edd82b-6f4c-43c7-9205-0b970afe06d7",
"metadata": {},
"outputs": [],
"source": [
"\n",
"with open(\"./app/utils/prompts/marketing_prompt.txt\", \"w\") as fp:\n",
" fp.write(marketing_prompt)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "65a5a2a6-06a5-4ee1-a796-406c86aefc20",
"metadata": {},
"outputs": [],
"source": [
"with open(\"prompts/summarize_prompt.json\", \"r\") as fp:\n",
" p = json.load(fp)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "454d920f-e9ed-4fb2-806c-75b8f7033db9",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'prompt_variables': ['report', 'selling_points'],\n",
" 'prompt': '\\n입력 : \\n분석 보고서\\n{report}\\n\\n셀링 포인트\\n{selling_points}\\n\\n위 분석 결과를 바탕으로, 주요 셀링 포인트를 다음 구조로 재정리하라.\\n\\n조건:\\n각 셀링 포인트는 반드시 ‘카테고리 → 태그 키워드 → 한 줄 설명’ 구조를 가질 것\\n태그 키워드는 UI 상에서 타원(oval) 형태의 시각적 태그로 사용될 것을 가정하여\\n- 3 ~ 6단어 이내\\n- 명사 또는 명사형 키워드로 작성\\n- 설명은 문장이 아닌, 짧은 ‘셀링 문구’ 형태로 작성할 것\\n- 광고·숏폼·상세페이지 어디에도 바로 재사용 가능해야 함\\n- 전체 셀링 포인트 개수는 5~7개로 제한\\n\\n출력 형식:\\n[카테고리명]\\n(태그 키워드)\\n- 한 줄 설명 문구\\n\\n예시: \\n[공간 정체성]\\n(100년 적산가옥 · 시간의 결)\\n- 하루를 ‘숙박’이 아닌 ‘체류’로 바꾸는 공간\\n\\n[입지 & 희소성]\\n(말랭이마을 · 로컬 히든플레이스)\\n- 관광지가 아닌, 군산을 아는 사람의 선택\\n\\n[프라이버시]\\n(독채 숙소 · 프라이빗 스테이)\\n- 누구의 방해도 없는 완전한 휴식 구조\\n\\n[비주얼 경쟁력]\\n(감성 인테리어 · 자연광 스폿)\\n- 찍는 순간 콘텐츠가 되는 공간 설계\\n\\n[타깃 최적화]\\n(커플 · 소규모 여행)\\n- 둘에게 가장 이상적인 공간 밀도\\n\\n[체류 경험]\\n(아무것도 안 해도 되는 하루)\\n- 일정 없이도 만족되는 하루 루틴\\n\\n[브랜드 포지션]\\n(호텔도 펜션도 아닌 아지트)\\n- 다시 돌아오고 싶은 개인적 장소\\n ',\n",
" 'output_format': {'format': {'type': 'json_schema',\n",
" 'name': 'tags',\n",
" 'schema': {'type': 'object',\n",
" 'properties': {'category': {'type': 'string'},\n",
" 'tag_keywords': {'type': 'string'},\n",
" 'description': {'type': 'string'}},\n",
" 'required': ['category', 'tag_keywords', 'description'],\n",
" 'additionalProperties': False},\n",
" 'strict': True}}}"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"p"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "c46abcda-d6a8-485e-92f1-526fb28c6b53",
"metadata": {},
"outputs": [],
"source": [
"marketing_prompt_dict = {\n",
" \"model\" : \"gpt-5-mini\",\n",
" \"prompt_variables\" :\n",
" [\n",
" \"customer_name\",\n",
" \"region\",\n",
" \"detail_region_info\"\n",
" ],\n",
" \"output_format\" : {\n",
" \"format\": {\n",
" \"type\": \"json_schema\",\n",
" \"name\": \"report\",\n",
" \"schema\": {\n",
" \"type\" : \"object\",\n",
" \"properties\" : {\n",
" \"report\" : {\n",
" \"type\": \"string\"\n",
" },\n",
" \"selling_points\" : {\n",
" \"type\": \"array\",\n",
" \"items\": {\n",
" \"type\": \"object\",\n",
" \"properties\" : {\n",
" \"category\" : {\"type\" : \"string\"},\n",
" \"keywords\" : {\"type\" : \"string\"},\n",
" \"description\" : {\"type\" : \"string\"}\n",
" },\n",
" \"required\": [\"category\", \"keywords\", \"description\"],\n",
" \"additionalProperties\": False,\n",
" },\n",
" },\n",
" \"tags\" : {\n",
" \"type\": \"array\",\n",
" \"items\": {\n",
" \"type\": \"string\"\n",
" },\n",
" },\n",
" },\n",
" \"required\": [\"report\", \"selling_points\", \"tags\"],\n",
" \"additionalProperties\": False,\n",
" },\n",
" \"strict\": True\n",
" }\n",
" }\n",
"}\n",
"with open(\"./app/utils/prompts/marketing_prompt.json\", \"w\") as fp:\n",
" json.dump(marketing_prompt_dict, fp, ensure_ascii=False)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "c3867dab-0c4e-46be-ad12-a9c02b5edb68",
"metadata": {},
"outputs": [],
"source": [
"lyric_prompt = \"\"\"\n",
"[ROLE]\n",
"You are a content marketing expert, brand strategist, and creative songwriter\n",
"specializing in Korean pension / accommodation businesses.\n",
"You create lyrics strictly based on Brand & Marketing Intelligence analysis\n",
"and optimized for viral short-form video content.\n",
"\n",
"[INPUT]\n",
"Business Name: {customer_name}\n",
"Region: {region}\n",
"Region Details: {detail_region_info}\n",
"Brand & Marketing Intelligence Report: {marketing_intelligence_summary}\n",
"Output Language: {language}\n",
"\n",
"[INTERNAL ANALYSIS DO NOT OUTPUT]\n",
"Internally analyze the following to guide all creative decisions:\n",
"- Core brand identity and positioning\n",
"- Emotional hooks derived from selling points\n",
"- Target audience lifestyle, desires, and travel motivation\n",
"- Regional atmosphere and symbolic imagery\n",
"- How the stay converts into “shareable moments”\n",
"- Which selling points must surface implicitly in lyrics\n",
"\n",
"[LYRICS & MUSIC CREATION TASK]\n",
"Based on the Brand & Marketing Intelligence Report for [{customer_name} ({region})], generate:\n",
"- Original promotional lyrics\n",
"- Music attributes for AI music generation (Suno-compatible prompt)\n",
"The output must be designed for VIRAL DIGITAL CONTENT\n",
"(short-form video, reels, ads).\n",
"\n",
"[LYRICS REQUIREMENTS]\n",
"Mandatory Inclusions:\n",
"- Business name\n",
"- Region name\n",
"- Promotion subject\n",
"- Promotional expressions including:\n",
"{promotional_expressions[language]}\n",
"\n",
"Content Rules:\n",
"- Lyrics must be emotionally driven, not descriptive listings\n",
"- Selling points must be IMPLIED, not explained\n",
"- Must sound natural when sung\n",
"- Must feel like a lifestyle moment, not an advertisement\n",
"\n",
"Tone & Style:\n",
"- Warm, emotional, and aspirational\n",
"- Trendy, viral-friendly phrasing\n",
"- Calm but memorable hooks\n",
"- Suitable for travel / stay-related content\n",
"\n",
"[SONG & MUSIC ATTRIBUTES FOR SUNO PROMPT]\n",
"After the lyrics, generate a concise music prompt including:\n",
"Song mood (emotional keywords)\n",
"BPM range\n",
"Recommended genres (max 2)\n",
"Key musical motifs or instruments\n",
"Overall vibe (1 short sentence)\n",
"\n",
"[CRITICAL LANGUAGE REQUIREMENT ABSOLUTE RULE]\n",
"ALL OUTPUT MUST BE 100% WRITTEN IN {language}.\n",
"no mixed languages\n",
"All names, places, and expressions must be in {language} \n",
"Any violation invalidates the entire output\n",
"\n",
"[OUTPUT RULES STRICT]\n",
"{timing_rules}\n",
"812 lines\n",
"Full verse flow, immersive mood\n",
"\n",
"No explanations\n",
"No headings\n",
"No bullet points\n",
"No analysis\n",
"No extra text\n",
"\n",
"[FAILURE FORMAT]\n",
"If generation is impossible:\n",
"ERROR: Brief reason in English\n",
"\"\"\"\n",
"with open(\"./app/utils/prompts/lyric_prompt.txt\", \"w\") as fp:\n",
" fp.write(lyric_prompt)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "5736ca4b-c379-4cae-84a9-534cad9576c7",
"metadata": {},
"outputs": [],
"source": [
"lyric_prompt_dict = {\n",
" \"model\" : \"gpt-5-mini\",\n",
" \"prompt_variables\" :\n",
" [\n",
" \"customer_name\",\n",
" \"region\",\n",
" \"detail_region_info\",\n",
" \"marketing_intelligence_summary\",\n",
" \"language\",\n",
" \"promotional_expression_example\",\n",
" \"timing_rules\",\n",
" \n",
" ],\n",
" \"output_format\" : {\n",
" \"format\": {\n",
" \"type\": \"json_schema\",\n",
" \"name\": \"lyric\",\n",
" \"schema\": {\n",
" \"type\":\"object\",\n",
" \"properties\" : {\n",
" \"lyric\" : { \n",
" \"type\" : \"string\"\n",
" }\n",
" },\n",
" \"required\": [\"lyric\"],\n",
" \"additionalProperties\": False,\n",
" },\n",
" \"strict\": True\n",
" }\n",
" }\n",
"}\n",
"with open(\"./app/utils/prompts/lyric_prompt.json\", \"w\") as fp:\n",
" json.dump(lyric_prompt_dict, fp, ensure_ascii=False)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "430c8914-4e6a-4b53-8903-f454e7ccb8e2",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

File diff suppressed because it is too large Load Diff