From 7f5a75e0a5b3f7a2dda49bc94ac65f7bff5afb6d Mon Sep 17 00:00:00 2001 From: jaehwang Date: Wed, 28 Jan 2026 16:44:51 +0900 Subject: [PATCH] change prompt format --- app/utils/chatgpt_prompt.py | 16 +++- app/utils/prompts-back/lyric_prompt.json | 34 ------- app/utils/prompts-back/marketing_prompt.json | 91 ------------------- .../marketing_prompt_20260116.txt | 62 ------------- .../marketing_prompt_20260119.json | 58 ------------ app/utils/prompts-back/prompts.py | 76 ---------------- app/utils/prompts-back/summarize_prompt.json | 33 ------- app/utils/prompts-back/summarize_prompt.txt | 53 ----------- app/utils/prompts/lyric_prompt.json | 34 ------- app/utils/prompts/lyric_prompt.txt | 76 ---------------- app/utils/prompts/marketing_prompt.json | 91 ------------------- app/utils/prompts/marketing_prompt.txt | 64 ------------- .../prompts/marketing_prompt_20260116.txt | 62 ------------- .../prompts/marketing_prompt_20260119.json | 58 ------------ app/utils/prompts/prompts.py | 67 +++++--------- app/utils/prompts/schemas/__init__.py | 2 + app/utils/prompts/schemas/lyric.py | 17 ++++ app/utils/prompts/schemas/marketing.py | 43 +++++++++ app/utils/prompts/summarize_prompt.json | 33 ------- app/utils/prompts/summarize_prompt.txt | 53 ----------- .../templates}/lyric_prompt.txt | 0 .../templates}/marketing_prompt.txt | 0 config.py | 11 ++- 23 files changed, 107 insertions(+), 927 deletions(-) delete mode 100644 app/utils/prompts-back/lyric_prompt.json delete mode 100644 app/utils/prompts-back/marketing_prompt.json delete mode 100644 app/utils/prompts-back/marketing_prompt_20260116.txt delete mode 100644 app/utils/prompts-back/marketing_prompt_20260119.json delete mode 100644 app/utils/prompts-back/prompts.py delete mode 100644 app/utils/prompts-back/summarize_prompt.json delete mode 100644 app/utils/prompts-back/summarize_prompt.txt delete mode 100644 app/utils/prompts/lyric_prompt.json delete mode 100644 app/utils/prompts/lyric_prompt.txt delete mode 100644 app/utils/prompts/marketing_prompt.json delete mode 100644 app/utils/prompts/marketing_prompt.txt delete mode 100644 app/utils/prompts/marketing_prompt_20260116.txt delete mode 100644 app/utils/prompts/marketing_prompt_20260119.json create mode 100644 app/utils/prompts/schemas/__init__.py create mode 100644 app/utils/prompts/schemas/lyric.py create mode 100644 app/utils/prompts/schemas/marketing.py delete mode 100644 app/utils/prompts/summarize_prompt.json delete mode 100644 app/utils/prompts/summarize_prompt.txt rename app/utils/{prompts-back => prompts/templates}/lyric_prompt.txt (100%) rename app/utils/{prompts-back => prompts/templates}/marketing_prompt.txt (100%) diff --git a/app/utils/chatgpt_prompt.py b/app/utils/chatgpt_prompt.py index 1a80d20..ac88b3b 100644 --- a/app/utils/chatgpt_prompt.py +++ b/app/utils/chatgpt_prompt.py @@ -1,12 +1,13 @@ import json import re - +from pydantic import BaseModel from openai import AsyncOpenAI from app.utils.logger import get_logger from config import apikey_settings from app.utils.prompts.prompts import Prompt + # 로거 설정 logger = get_logger("chatgpt") @@ -31,6 +32,16 @@ class ChatgptService: ) structured_output = json.loads(response.output_text) return structured_output or {} + + async def _call_pydantic_output(self, prompt : str, output_format : BaseModel, model : str) -> BaseModel: # 입력 output_format의 경우 Pydantic BaseModel Class를 상속한 Class 자체임에 유의할 것 + content = [{"type": "input_text", "text": prompt}] + response = await self.client.responses.parse( + model=model, + input=[{"role": "user", "content": content}], + text_format=output_format + ) + structured_output = response.output_parsed + return structured_output.model_dump() or {} async def generate_structured_output( self, @@ -43,5 +54,6 @@ class ChatgptService: logger.info(f"[ChatgptService] Starting GPT request with structured output with model: {prompt.prompt_model}") # GPT API 호출 - response = await self._call_structured_output_with_response_gpt_api(prompt_text, prompt.prompt_output, prompt.prompt_model) + #response = await self._call_structured_output_with_response_gpt_api(prompt_text, prompt.prompt_output, prompt.prompt_model) + response = await self._call_pydantic_output(prompt_text, prompt.prompt_output_class, prompt.prompt_model) return response diff --git a/app/utils/prompts-back/lyric_prompt.json b/app/utils/prompts-back/lyric_prompt.json deleted file mode 100644 index 14df2c4..0000000 --- a/app/utils/prompts-back/lyric_prompt.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "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" - }, - "suno_prompt":{ - "type" : "string" - } - }, - "required": [ - "lyric", "suno_prompt" - ], - "additionalProperties": false - }, - "strict": true - } - } -} \ No newline at end of file diff --git a/app/utils/prompts-back/marketing_prompt.json b/app/utils/prompts-back/marketing_prompt.json deleted file mode 100644 index d2f29b2..0000000 --- a/app/utils/prompts-back/marketing_prompt.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "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": "object", - "properties": { - "summary": { - "type": "string" - }, - "details": { - "type": "array", - "items": { - "type": "object", - "properties": { - "detail_title": { - "type": "string" - }, - "detail_description": { - "type": "string" - } - }, - "required": [ - "detail_title", - "detail_description" - ], - "additionalProperties": false - } - } - }, - "required": [ - "summary", - "details" - ], - "additionalProperties": false - }, - "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" - } - }, - "contents_advise": { - "type": "string" - } - }, - "required": [ - "report", - "selling_points", - "tags", - "contents_advise" - ], - "additionalProperties": false - }, - "strict": true - } - } -} \ No newline at end of file diff --git a/app/utils/prompts-back/marketing_prompt_20260116.txt b/app/utils/prompts-back/marketing_prompt_20260116.txt deleted file mode 100644 index 2cd0921..0000000 --- a/app/utils/prompts-back/marketing_prompt_20260116.txt +++ /dev/null @@ -1,62 +0,0 @@ - -[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"] diff --git a/app/utils/prompts-back/marketing_prompt_20260119.json b/app/utils/prompts-back/marketing_prompt_20260119.json deleted file mode 100644 index c84231a..0000000 --- a/app/utils/prompts-back/marketing_prompt_20260119.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "model": "gpt-5.2", - "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 - } - } -} \ No newline at end of file diff --git a/app/utils/prompts-back/prompts.py b/app/utils/prompts-back/prompts.py deleted file mode 100644 index ba2131f..0000000 --- a/app/utils/prompts-back/prompts.py +++ /dev/null @@ -1,76 +0,0 @@ -import os, json -from abc import ABCMeta -from config import prompt_settings -from app.utils.logger import get_logger - -logger = get_logger("prompt") - -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 - logger.debug(f"build_template: {build_template}") - logger.debug(f"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}") - - flooding_variables = set(self.prompt_input) - input_data.keys() - if flooding_variables: - raise Exception(f"flooding_variables for prompt {self.prompt_name} : {flooding_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() \ No newline at end of file diff --git a/app/utils/prompts-back/summarize_prompt.json b/app/utils/prompts-back/summarize_prompt.json deleted file mode 100644 index 873b060..0000000 --- a/app/utils/prompts-back/summarize_prompt.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "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 - } - } -} \ No newline at end of file diff --git a/app/utils/prompts-back/summarize_prompt.txt b/app/utils/prompts-back/summarize_prompt.txt deleted file mode 100644 index 2cf7828..0000000 --- a/app/utils/prompts-back/summarize_prompt.txt +++ /dev/null @@ -1,53 +0,0 @@ - -입력 : -분석 보고서 -{report} - -셀링 포인트 -{selling_points} - -위 분석 결과를 바탕으로, 주요 셀링 포인트를 다음 구조로 재정리하라. - -조건: -각 셀링 포인트는 반드시 ‘카테고리 → 태그 키워드 → 한 줄 설명’ 구조를 가질 것 -태그 키워드는 UI 상에서 타원(oval) 형태의 시각적 태그로 사용될 것을 가정하여 -- 3 ~ 6단어 이내 -- 명사 또는 명사형 키워드로 작성 -- 설명은 문장이 아닌, 짧은 ‘셀링 문구’ 형태로 작성할 것 -- 광고·숏폼·상세페이지 어디에도 바로 재사용 가능해야 함 -- 전체 셀링 포인트 개수는 5~7개로 제한 - -출력 형식: -[카테고리명] -(태그 키워드) -- 한 줄 설명 문구 - -예시: -[공간 정체성] -(100년 적산가옥 · 시간의 결) -- 하루를 ‘숙박’이 아닌 ‘체류’로 바꾸는 공간 - -[입지 & 희소성] -(말랭이마을 · 로컬 히든플레이스) -- 관광지가 아닌, 군산을 아는 사람의 선택 - -[프라이버시] -(독채 숙소 · 프라이빗 스테이) -- 누구의 방해도 없는 완전한 휴식 구조 - -[비주얼 경쟁력] -(감성 인테리어 · 자연광 스폿) -- 찍는 순간 콘텐츠가 되는 공간 설계 - -[타깃 최적화] -(커플 · 소규모 여행) -- 둘에게 가장 이상적인 공간 밀도 - -[체류 경험] -(아무것도 안 해도 되는 하루) -- 일정 없이도 만족되는 하루 루틴 - -[브랜드 포지션] -(호텔도 펜션도 아닌 아지트) -- 다시 돌아오고 싶은 개인적 장소 - \ No newline at end of file diff --git a/app/utils/prompts/lyric_prompt.json b/app/utils/prompts/lyric_prompt.json deleted file mode 100644 index 14df2c4..0000000 --- a/app/utils/prompts/lyric_prompt.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "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" - }, - "suno_prompt":{ - "type" : "string" - } - }, - "required": [ - "lyric", "suno_prompt" - ], - "additionalProperties": false - }, - "strict": true - } - } -} \ No newline at end of file diff --git a/app/utils/prompts/lyric_prompt.txt b/app/utils/prompts/lyric_prompt.txt deleted file mode 100644 index f434c6f..0000000 --- a/app/utils/prompts/lyric_prompt.txt +++ /dev/null @@ -1,76 +0,0 @@ - -[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_expression_example} - -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} - -No explanations -No headings -No bullet points -No analysis -No extra text - -[FAILURE FORMAT] -If generation is impossible: -ERROR: Brief reason in English diff --git a/app/utils/prompts/marketing_prompt.json b/app/utils/prompts/marketing_prompt.json deleted file mode 100644 index d2f29b2..0000000 --- a/app/utils/prompts/marketing_prompt.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "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": "object", - "properties": { - "summary": { - "type": "string" - }, - "details": { - "type": "array", - "items": { - "type": "object", - "properties": { - "detail_title": { - "type": "string" - }, - "detail_description": { - "type": "string" - } - }, - "required": [ - "detail_title", - "detail_description" - ], - "additionalProperties": false - } - } - }, - "required": [ - "summary", - "details" - ], - "additionalProperties": false - }, - "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" - } - }, - "contents_advise": { - "type": "string" - } - }, - "required": [ - "report", - "selling_points", - "tags", - "contents_advise" - ], - "additionalProperties": false - }, - "strict": true - } - } -} \ No newline at end of file diff --git a/app/utils/prompts/marketing_prompt.txt b/app/utils/prompts/marketing_prompt.txt deleted file mode 100644 index 2bf9307..0000000 --- a/app/utils/prompts/marketing_prompt.txt +++ /dev/null @@ -1,64 +0,0 @@ - -[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, 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 -Do not provide in report - -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 -Do not provide in report - -[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"] diff --git a/app/utils/prompts/marketing_prompt_20260116.txt b/app/utils/prompts/marketing_prompt_20260116.txt deleted file mode 100644 index 2cd0921..0000000 --- a/app/utils/prompts/marketing_prompt_20260116.txt +++ /dev/null @@ -1,62 +0,0 @@ - -[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"] diff --git a/app/utils/prompts/marketing_prompt_20260119.json b/app/utils/prompts/marketing_prompt_20260119.json deleted file mode 100644 index c84231a..0000000 --- a/app/utils/prompts/marketing_prompt_20260119.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "model": "gpt-5.2", - "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 - } - } -} \ No newline at end of file diff --git a/app/utils/prompts/prompts.py b/app/utils/prompts/prompts.py index ba2131f..d9f5020 100644 --- a/app/utils/prompts/prompts.py +++ b/app/utils/prompts/prompts.py @@ -1,76 +1,57 @@ import os, json -from abc import ABCMeta +from pydantic import BaseModel from config import prompt_settings from app.utils.logger import get_logger +from app.utils.prompts.schemas import * logger = get_logger("prompt") 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 + prompt_model : str + + prompt_input_class = BaseModel # pydantic class 자체를(instance 아님) 변수로 가짐 + prompt_output_class = BaseModel - def __init__(self, prompt_name, prompt_template_path): - self.prompt_name = prompt_name + def __init__(self, prompt_template_path, prompt_input_class, prompt_output_class, prompt_model): 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") + self.prompt_input_class = prompt_input_class + self.prompt_output_class = prompt_output_class + self.prompt_template = self.read_prompt() + self.prompt_model = prompt_model 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") + self.prompt_template = self.read_prompt() 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: + with open(self.prompt_template_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 + return prompt_template def build_prompt(self, input_data:dict) -> str: - self.check_input(input_data) + verified_input = self.prompt_input_class(**input_data) build_template = self.prompt_template + build_template = build_template.format(**verified_input.model_dump()) logger.debug(f"build_template: {build_template}") logger.debug(f"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}") - - flooding_variables = set(self.prompt_input) - input_data.keys() - if flooding_variables: - raise Exception(f"flooding_variables for prompt {self.prompt_name} : {flooding_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) + prompt_template_path = os.path.join(prompt_settings.PROMPT_FOLDER_ROOT, prompt_settings.MARKETING_PROMPT_FILE_NAME), + prompt_input_class = MarketingPromptInput, + prompt_output_class = MarketingPromptOutput, + prompt_model = prompt_settings.MARKETING_PROMPT_MODEL ) 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) + prompt_template_path=os.path.join(prompt_settings.PROMPT_FOLDER_ROOT, prompt_settings.LYRIC_PROMPT_FILE_NAME), + prompt_input_class = LyricPromptInput, + prompt_output_class = LyricPromptOutput, + prompt_model = prompt_settings.LYRIC_PROMPT_MODEL ) def reload_all_prompt(): marketing_prompt._reload_prompt() - summarize_prompt._reload_prompt() lyric_prompt._reload_prompt() \ No newline at end of file diff --git a/app/utils/prompts/schemas/__init__.py b/app/utils/prompts/schemas/__init__.py new file mode 100644 index 0000000..4080837 --- /dev/null +++ b/app/utils/prompts/schemas/__init__.py @@ -0,0 +1,2 @@ +from .lyric import LyricPromptInput, LyricPromptOutput +from .marketing import MarketingPromptInput, MarketingPromptOutput \ No newline at end of file diff --git a/app/utils/prompts/schemas/lyric.py b/app/utils/prompts/schemas/lyric.py new file mode 100644 index 0000000..9d4423f --- /dev/null +++ b/app/utils/prompts/schemas/lyric.py @@ -0,0 +1,17 @@ +from pydantic import BaseModel, Field +from typing import List + +# Input 정의 +class LyricPromptInput(BaseModel): + customer_name : str = Field(..., description = "마케팅 대상 사업체 이름") + region : str = Field(..., description = "마케팅 대상 지역") + detail_region_info : str = Field(..., description = "마케팅 대상 지역 상세") + marketing_intelligence_summary : str = Field(..., description = "마케팅 분석 정보 보고서") + language : str= Field(..., description = "가사 언어") + promotional_expression_example : str = Field(..., description = "판촉 가사 표현 예시") + timing_rules : str = Field(..., description = "시간 제어문") + +# Output 정의 +class LyricPromptOutput(BaseModel): + lyric: str = Field(..., description="생성된 가사") + suno_prompt: str = Field(..., description="Suno AI용 프롬프트") \ No newline at end of file diff --git a/app/utils/prompts/schemas/marketing.py b/app/utils/prompts/schemas/marketing.py new file mode 100644 index 0000000..041a029 --- /dev/null +++ b/app/utils/prompts/schemas/marketing.py @@ -0,0 +1,43 @@ +from pydantic import BaseModel, Field +from typing import List + +# Input 정의 +class MarketingPromptInput(BaseModel): + customer_name : str + region : str + detail_region_info : str + + +# Output 정의 +class BrandIdentity(BaseModel): + location_feature_analysis: str = Field(..., description="입지 특성 분석") + concept_scalability: str = Field(..., description="컨셉 확장성") + + +class MarketPositioning(BaseModel): + category_definition: str = Field(..., description="마케팅 카테고리") + core_value: str = Field(..., description="마케팅 포지션 핵심 가치") + +class AgeRange(BaseModel): + min_age : int = Field(..., ge=0, le=100) + max_age : int = Field(..., ge=0, le=100) + + +class TargetPersona(BaseModel): + persona: str = Field(..., description="타겟 페르소나 이름/설명") + age: AgeRange + favor_target: List[str] = Field(..., description="페르소나의 선호 요소") + decision_trigger: str = Field(..., description="구매 결정 트리거") + + +class SellingPoint(BaseModel): + category: str = Field(..., description="셀링포인트 카테고리") + description: str = Field(..., description="상세 설명") + score: int = Field(..., ge=0, le=100, description="점수 (100점 만점)") + +class MarketingPromptOutput(BaseModel): + brand_identity: BrandIdentity + market_positioning: MarketPositioning + target_persona: List[TargetPersona] + selling_points: List[SellingPoint] + target_keywords: List[str] = Field(..., description="타겟 키워드 리스트") diff --git a/app/utils/prompts/summarize_prompt.json b/app/utils/prompts/summarize_prompt.json deleted file mode 100644 index 873b060..0000000 --- a/app/utils/prompts/summarize_prompt.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "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 - } - } -} \ No newline at end of file diff --git a/app/utils/prompts/summarize_prompt.txt b/app/utils/prompts/summarize_prompt.txt deleted file mode 100644 index 2cf7828..0000000 --- a/app/utils/prompts/summarize_prompt.txt +++ /dev/null @@ -1,53 +0,0 @@ - -입력 : -분석 보고서 -{report} - -셀링 포인트 -{selling_points} - -위 분석 결과를 바탕으로, 주요 셀링 포인트를 다음 구조로 재정리하라. - -조건: -각 셀링 포인트는 반드시 ‘카테고리 → 태그 키워드 → 한 줄 설명’ 구조를 가질 것 -태그 키워드는 UI 상에서 타원(oval) 형태의 시각적 태그로 사용될 것을 가정하여 -- 3 ~ 6단어 이내 -- 명사 또는 명사형 키워드로 작성 -- 설명은 문장이 아닌, 짧은 ‘셀링 문구’ 형태로 작성할 것 -- 광고·숏폼·상세페이지 어디에도 바로 재사용 가능해야 함 -- 전체 셀링 포인트 개수는 5~7개로 제한 - -출력 형식: -[카테고리명] -(태그 키워드) -- 한 줄 설명 문구 - -예시: -[공간 정체성] -(100년 적산가옥 · 시간의 결) -- 하루를 ‘숙박’이 아닌 ‘체류’로 바꾸는 공간 - -[입지 & 희소성] -(말랭이마을 · 로컬 히든플레이스) -- 관광지가 아닌, 군산을 아는 사람의 선택 - -[프라이버시] -(독채 숙소 · 프라이빗 스테이) -- 누구의 방해도 없는 완전한 휴식 구조 - -[비주얼 경쟁력] -(감성 인테리어 · 자연광 스폿) -- 찍는 순간 콘텐츠가 되는 공간 설계 - -[타깃 최적화] -(커플 · 소규모 여행) -- 둘에게 가장 이상적인 공간 밀도 - -[체류 경험] -(아무것도 안 해도 되는 하루) -- 일정 없이도 만족되는 하루 루틴 - -[브랜드 포지션] -(호텔도 펜션도 아닌 아지트) -- 다시 돌아오고 싶은 개인적 장소 - \ No newline at end of file diff --git a/app/utils/prompts-back/lyric_prompt.txt b/app/utils/prompts/templates/lyric_prompt.txt similarity index 100% rename from app/utils/prompts-back/lyric_prompt.txt rename to app/utils/prompts/templates/lyric_prompt.txt diff --git a/app/utils/prompts-back/marketing_prompt.txt b/app/utils/prompts/templates/marketing_prompt.txt similarity index 100% rename from app/utils/prompts-back/marketing_prompt.txt rename to app/utils/prompts/templates/marketing_prompt.txt diff --git a/config.py b/config.py index 046d742..448bcd8 100644 --- a/config.py +++ b/config.py @@ -146,10 +146,13 @@ class CreatomateSettings(BaseSettings): 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") + PROMPT_FOLDER_ROOT : str = Field(default="./app/utils/prompts/templates") + + MARKETING_PROMPT_FILE_NAME : str = Field(default="marketing_prompt.txt") + MARKETING_PROMPT_MODEL : str = Field(default="gpt-5.2") + + LYRIC_PROMPT_FILE_NAME : str = Field(default="lyric_prompt.txt") + LYRIC_PROMPT_MODEL : str = Field(default="gpt-5-mini") model_config = _base_config