프롬프트 엑셀로 변경
parent
01c1cacb84
commit
f8c1738aa2
|
|
@ -4,69 +4,73 @@ from config import prompt_settings
|
|||
from app.utils.logger import get_logger
|
||||
from app.utils.prompts.schemas import *
|
||||
from functools import lru_cache
|
||||
import openpyxl
|
||||
|
||||
logger = get_logger("prompt")
|
||||
|
||||
class Prompt():
|
||||
prompt_template_path : str #프롬프트 경로
|
||||
prompt_template : str # fstring 포맷
|
||||
sheet_name: str
|
||||
prompt_template: str
|
||||
prompt_model: str
|
||||
|
||||
prompt_input_class = BaseModel # pydantic class 자체를(instance 아님) 변수로 가짐
|
||||
prompt_input_class = BaseModel
|
||||
prompt_output_class = BaseModel
|
||||
|
||||
def __init__(self, prompt_template_path, prompt_input_class, prompt_output_class, prompt_model):
|
||||
self.prompt_template_path = prompt_template_path
|
||||
def __init__(self, sheet_name, prompt_input_class, prompt_output_class):
|
||||
self.sheet_name = sheet_name
|
||||
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
|
||||
self.prompt_template, self.prompt_model = self._read_from_excel()
|
||||
|
||||
def _read_from_excel(self) -> tuple[str, str]:
|
||||
wb = openpyxl.load_workbook(prompt_settings.PROMPT_EXCEL_FILE, read_only=True)
|
||||
try:
|
||||
ws = wb[self.sheet_name]
|
||||
data = {}
|
||||
for row in ws.iter_rows(min_row=2, values_only=True):
|
||||
key, value = row[0], row[1]
|
||||
if key and value:
|
||||
data[key] = value
|
||||
finally:
|
||||
wb.close()
|
||||
|
||||
return data["input"], data["model"]
|
||||
|
||||
def _reload_prompt(self):
|
||||
self.prompt_template = self.read_prompt()
|
||||
|
||||
def read_prompt(self) -> tuple[str, dict]:
|
||||
with open(self.prompt_template_path, "r") as fp:
|
||||
prompt_template = fp.read()
|
||||
|
||||
return prompt_template
|
||||
self.prompt_template, self.prompt_model = self._read_from_excel()
|
||||
|
||||
def build_prompt(self, input_data: dict) -> str:
|
||||
verified_input = self.prompt_input_class(**input_data)
|
||||
build_template = self.prompt_template
|
||||
build_template = build_template.format(**verified_input.model_dump())
|
||||
build_template = self.prompt_template.format(**verified_input.model_dump())
|
||||
logger.debug(f"build_template: {build_template}")
|
||||
logger.debug(f"input_data: {input_data}")
|
||||
return build_template
|
||||
|
||||
marketing_prompt = Prompt(
|
||||
prompt_template_path = os.path.join(prompt_settings.PROMPT_FOLDER_ROOT, prompt_settings.MARKETING_PROMPT_FILE_NAME),
|
||||
sheet_name="marketing",
|
||||
prompt_input_class=MarketingPromptInput,
|
||||
prompt_output_class=MarketingPromptOutput,
|
||||
prompt_model = prompt_settings.MARKETING_PROMPT_MODEL
|
||||
)
|
||||
|
||||
lyric_prompt = Prompt(
|
||||
prompt_template_path=os.path.join(prompt_settings.PROMPT_FOLDER_ROOT, prompt_settings.LYRIC_PROMPT_FILE_NAME),
|
||||
sheet_name="lyric",
|
||||
prompt_input_class=LyricPromptInput,
|
||||
prompt_output_class=LyricPromptOutput,
|
||||
prompt_model = prompt_settings.LYRIC_PROMPT_MODEL
|
||||
)
|
||||
|
||||
yt_upload_prompt = Prompt(
|
||||
prompt_template_path=os.path.join(prompt_settings.PROMPT_FOLDER_ROOT, prompt_settings.YOUTUBE_PROMPT_FILE_NAME),
|
||||
sheet_name="yt_upload",
|
||||
prompt_input_class=YTUploadPromptInput,
|
||||
prompt_output_class=YTUploadPromptOutput,
|
||||
prompt_model = prompt_settings.YOUTUBE_PROMPT_MODEL
|
||||
)
|
||||
|
||||
@lru_cache()
|
||||
def create_dynamic_subtitle_prompt(length: int) -> Prompt:
|
||||
prompt_template_path=os.path.join(prompt_settings.PROMPT_FOLDER_ROOT, prompt_settings.SUBTITLE_PROMPT_FILE_NAME)
|
||||
prompt_input_class = SubtitlePromptInput
|
||||
prompt_output_class = SubtitlePromptOutput[length]
|
||||
prompt_model = prompt_settings.SUBTITLE_PROMPT_MODEL
|
||||
return Prompt(prompt_template_path, prompt_input_class, prompt_output_class, prompt_model)
|
||||
return Prompt(
|
||||
sheet_name="subtitle",
|
||||
prompt_input_class=SubtitlePromptInput,
|
||||
prompt_output_class=SubtitlePromptOutput[length],
|
||||
)
|
||||
|
||||
|
||||
def reload_all_prompt():
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
@ -1,77 +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.
|
||||
Marketing Intelligence Report is background reference.
|
||||
|
||||
[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
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
# Role
|
||||
Act as a Senior Brand Strategist and Marketing Data Analyst. Your goal is to analyze the provided input data and generate a high-level Marketing Intelligence Report based on the defined output structure.
|
||||
|
||||
# Input Data
|
||||
* **Customer Name:** {customer_name}
|
||||
* **Region:** {region}
|
||||
* **Detail Region Info:** {detail_region_info}
|
||||
|
||||
# Output Rules
|
||||
1. **Language:** All descriptive content must be written in **Korean (한국어)**.
|
||||
2. **Terminology:** Use professional marketing terminology suitable for the hospitality and stay industry.
|
||||
3. **Strict Selection for `selling_points.english_category` and `selling_points.korean_category`:** You must select the value for both category field in `selling_points` strictly from the following English - Korean set allowed list to ensure UI compatibility:
|
||||
* `LOCATION` (입지 환경), `CONCEPT` (브랜드 컨셉), `PRIVACY` (프라이버시), `NIGHT MOOD` (야간 감성), `HEALING` (힐링 요소), `PHOTO SPOT` (포토 스팟), `SHORT GETAWAY` (숏브레이크), `HOSPITALITY` (서비스), `SWIMMING POOL` (수영장), `JACUZZI` (자쿠지), `BBQ PARTY` (바베큐), `FIRE PIT` (불멍), `GARDEN` (정원), `BREAKFAST` (조식), `KIDS FRIENDLY` (키즈 케어), `PET FRIENDLY` (애견 동반), `OCEAN VIEW` (오션뷰), `PRIVATE POOL` (개별 수영장), `OCEAN VIEW`, `PRIVATE POOL`.
|
||||
|
||||
---
|
||||
|
||||
# Instruction per Output Field (Mapping Logic)
|
||||
|
||||
### 1. brand_identity
|
||||
* **`location_feature_analysis`**: Analyze the marketing advantages of the given `{region}` and `{detail_region_info}`. Explain why this specific location is attractive to travelers. summarize in 1-2 sentences. (e.g., proximity to nature, accessibility from Seoul, or unique local atmosphere).
|
||||
* **`concept_scalability`**: Based on `{customer_name}`, analyze how the brand's core concept can expand into a total customer experience or additional services. summarize in 1-2 sentences.
|
||||
|
||||
### 2. market_positioning
|
||||
* **`category_definition`**: Define a sharp, niche market category for this business (e.g., "Private Forest Cabin" or "Luxury Kids Pool Villa").
|
||||
* **`core_value`**: Identify the single most compelling emotional or functional value that distinguishes `{customer_name}` from competitors.
|
||||
|
||||
### 3. target_persona
|
||||
Generate a list of personas based on the following:
|
||||
* **`persona`**: Provide a descriptive name and profile for the target group. Must be **20 characters or fewer**.
|
||||
* **`age`**: Set `min_age` and `max_age` (Integer 0-100) that accurately reflects the segment.
|
||||
* **`favor_target`**: List specific elements or vibes this persona prefers (e.g., "Minimalist interior", "Pet-friendly facilities").
|
||||
* **`decision_trigger`**: Identify the specific "Hook" or facility that leads this persona to finalize a booking.
|
||||
|
||||
### 4. selling_points
|
||||
Generate 5-8 selling points:
|
||||
* **`english_category`**: Strictly use one keyword from the English allowed list provided in the Output Rules.
|
||||
* **`korean category`**: Strictly use one keyword from the Korean allowed list provided in the Output Rules . It must be matched with english category.
|
||||
* **`description`**: A short, punchy marketing phrase in Korean (15~30 characters).
|
||||
* **`score`**: An integer (0-100) representing the strength of this feature based on the brand's potential.
|
||||
|
||||
### 5. target_keywords
|
||||
* **`target_keywords`**: Provide a list of 10 highly relevant marketing keywords or hashtags for search engine optimization and social media targeting. Do not insert # in front of hashtag.
|
||||
Binary file not shown.
|
|
@ -1,87 +0,0 @@
|
|||
당신은 숙박 브랜드 숏폼 영상의 자막 콘텐츠를 추출하는 전문가입니다.
|
||||
|
||||
입력으로 주어지는 **1) 5가지 기준의 레이어 이름 리스트**와 **2) 마케팅 인텔리전스 분석 결과(JSON)**를 바탕으로, 각 레이어 이름의 의미에 정확히 1:1 매칭되는 텍스트 콘텐츠만을 추출하세요.
|
||||
|
||||
분석 결과에 없는 정보는 절대 지어내거나 추론하지 마세요. 오직 제공된 JSON 데이터 내에서만 텍스트를 구성해야 합니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 레이어 네이밍 규칙 해석 및 매핑 가이드
|
||||
|
||||
입력되는 모든 레이어 이름은 예외 없이 `<track_role>-<narrative_phase>-<content_type>-<tone>-<pair_id>` 의 5단계 구조로 되어 있습니다.
|
||||
마지막의 3자리 숫자 ID(`-001`, `-002` 등)는 모든 레이어에 필수적으로 부여됩니다.
|
||||
|
||||
### [1] track_role (텍스트 형태)
|
||||
- `subtitle`: 씬 상황을 설명하는 간결한 문장형 텍스트 (1줄 이내)
|
||||
- `keyword`: 씬을 상징하고 시선을 끄는 단답형/명사형 텍스트 (1~2단어)
|
||||
|
||||
### [2] narrative_phase (영상 흐름)
|
||||
- `intro`: 영상 도입부. 가장 시선을 끄는 정보를 배치.
|
||||
- `core`: 핵심 매력이나 주요 편의 시설 어필.
|
||||
- `highlight`: 세부적인 매력 포인트나 공간의 특별한 분위기 묘사.
|
||||
- `outro`: 영상 마무리. 브랜드 명칭 복기 및 타겟/위치 정보 제공.
|
||||
|
||||
### [3] content_type (데이터 매핑 대상)
|
||||
- `hook_claim` 👉 `selling_points`에서 점수가 가장 높은 1순위 소구점이나 `market_positioning.core_value`를 활용하여 가장 강력한 핵심 세일즈 포인트를 어필. (가장 강력한 셀링포인트를 의미함)
|
||||
- `selling_point` 👉 `selling_points`의 `description`, `korean_category` 등을 narrative 흐름에 맞춰 순차적으로 추출.
|
||||
- `brand_name` 👉 JSON의 `store_name`을 추출.
|
||||
- `location_info` 👉 JSON의 `detail_region_info`를 요약.
|
||||
- `target_tag` 👉 `target_persona`나 `target_keywords`에서 타겟 고객군 또는 해시태그 추출.
|
||||
|
||||
### [4] tone (텍스트 어조)
|
||||
- `sensory`: 직관적이고 감각적인 단어 사용
|
||||
- `factual`: 과장 없이 사실 정보를 담백하게 전달
|
||||
- `empathic`: 고객의 상황에 공감하는 따뜻한 어조
|
||||
- `aspirational`: 열망을 자극하고 기대감을 주는 느낌
|
||||
|
||||
### [5] pair_id (씬 묶음 식별 번호)
|
||||
- 텍스트 레이어는 `subtitle`과 `keyword`가 하나의 페어(Pair)를 이뤄 하나의 씬(Scene)에서 함께 등장합니다.
|
||||
- 따라서 **동일한 씬에 속하는 `subtitle`과 `keyword` 레이어는 동일한 3자리 순번 ID(예: `-001`)**를 공유합니다.
|
||||
- 영상 전반적인 씬 전개 순서에 따라 **다음 씬으로 넘어갈 때마다 ID가 순차적으로 증가**합니다. (예: 씬1은 `-001`, 씬2는 `-002`, 씬3은 `-003`...)
|
||||
- **중요**: ID가 달라진다는 것은 '새로운 씬' 혹은 '다른 텍스트 쌍'을 의미하므로, **ID가 바뀌면 반드시 JSON 내의 다른 소구점이나 데이터를 추출**하여 내용이 중복되지 않도록 해야 합니다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 콘텐츠 추출 시 주의사항
|
||||
|
||||
1. 각 입력 레이어 이름 1개당 **오직 1개의 텍스트 콘텐츠**만 매핑하여 출력합니다. (레이어명 이름 자체를 수정하거나 새로 만들지 마세요.)
|
||||
2. `content_type`이 `selling_point`로 동일하더라도, `narrative_phase`(core, highlight)나 `tone`이 달라지면 JSON 내의 2순위, 3순위 세일즈 포인트를 순차적으로 활용하여 내용 겹침을 방지하세요.
|
||||
3. 같은 씬에 속하는(같은 ID 번호를 가진) keyword는 핵심 단어로, subtitle은 적절한 마케팅 문구가 되어야 하며, 자연스럽게 이어지는 문맥을 형성하도록 구성하세요.
|
||||
4. keyword가 subtitle에 완전히 포함되는 단어가 되지 않도록 유의하세요.
|
||||
5. 정보 태그가 같더라도 ID가 다르다면 중복되지 않는 새로운 텍스트를 도출해야 합니다.
|
||||
6. 콘텐츠 추출 시 마케팅 인텔리전스의 내용을 그대로 사용하기보다는 paraphrase을 수행하세요.
|
||||
7. keyword는 공백 포함 전각 8자 / 반각 16자내, subtitle은 전각 15자 / 반각 30자 내로 구성하세요.
|
||||
|
||||
---
|
||||
|
||||
## 3. 출력 결과 포맷 및 예시
|
||||
|
||||
입력된 레이어 이름 순서에 맞춰, 매핑된 텍스트 콘텐츠만 작성하세요. (반드시 intro, core, highlight, outro 등 모든 씬 단계가 명확하게 매핑되어야 합니다.)
|
||||
|
||||
### 입력 레이어 리스트 예시 및 출력 예시
|
||||
|
||||
| Layer Name | Text Content |
|
||||
|---|---|
|
||||
| subtitle-intro-hook_claim-aspirational-001 | 반려견과 눈치 없이 온전하게 쉬는 완벽한 휴식 |
|
||||
| keyword-intro-brand_name-sensory-001 | 스테이펫 홍천 |
|
||||
| subtitle-core-selling_point-empathic-002 | 우리만의 독립된 공간감이 주는 진정한 쉼 |
|
||||
| keyword-core-selling_point-factual-002 | 프라이빗 독채 |
|
||||
| subtitle-highlight-selling_point-sensory-003 | 탁 트인 야외 무드존과 포토 스팟의 감성 컷 |
|
||||
| keyword-highlight-selling_point-factual-003 | 넓은 정원 |
|
||||
| subtitle-outro-target_tag-empathic-004 | #강원도애견동반 #주말숏브레이크 |
|
||||
| keyword-outro-location_info-factual-004 | 강원 홍천군 화촌면 |
|
||||
|
||||
|
||||
# 입력
|
||||
**입력 1: 레이어 이름 리스트**
|
||||
{pitching_tag_list_string}
|
||||
|
||||
**입력 2: 마케팅 인텔리전스 JSON**
|
||||
{marketing_intelligence}
|
||||
|
||||
**입력 3: 비즈니스 정보 **
|
||||
Business Name: {customer_name}
|
||||
Region Details: {detail_region_info}
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
[ROLE]
|
||||
You are a YouTube SEO/AEO content strategist specialized in local stay, pension, and accommodation brands in Korea.
|
||||
You create search-optimized, emotionally appealing, and action-driving titles and descriptions based on Brand & Marketing Intelligence.
|
||||
|
||||
Your goal is to:
|
||||
|
||||
Increase search visibility
|
||||
Improve click-through rate
|
||||
Reflect the brand’s positioning
|
||||
Trigger emotional interest
|
||||
Encourage booking or inquiry actions through subtle CTA
|
||||
|
||||
|
||||
[INPUT]
|
||||
Business Name: {customer_name}
|
||||
Region Details: {detail_region_info}
|
||||
Brand & Marketing Intelligence Report: {marketing_intelligence_summary}
|
||||
Target Keywords: {target_keywords}
|
||||
Output Language: {language}
|
||||
|
||||
|
||||
|
||||
[INTERNAL ANALYSIS – DO NOT OUTPUT]
|
||||
Analyze the following from the marketing intelligence:
|
||||
|
||||
Core brand concept
|
||||
Main emotional promise
|
||||
Primary target persona
|
||||
Top 2–3 USP signals
|
||||
Stay context (date, healing, local trip, etc.)
|
||||
Search intent behind the target keywords
|
||||
Main booking trigger
|
||||
Emotional moment that would make the viewer want to stay
|
||||
Use these to guide:
|
||||
|
||||
Title tone
|
||||
Opening CTA line
|
||||
Emotional hook in the first sentences
|
||||
|
||||
|
||||
[TITLE GENERATION RULES]
|
||||
|
||||
The title must:
|
||||
|
||||
Include the business name or region when natural
|
||||
Always wrap the business name in quotation marks
|
||||
Example: “스테이 머뭄”
|
||||
Include 1–2 high-intent keywords
|
||||
Reflect emotional positioning
|
||||
Suggest a desirable stay moment
|
||||
Sound like a natural YouTube title, not an advertisement
|
||||
Length rules:
|
||||
|
||||
Hard limit: 100 characters
|
||||
Target range: 45–65 characters
|
||||
Place primary keyword in the first half
|
||||
Avoid:
|
||||
|
||||
ALL CAPS
|
||||
Excessive symbols
|
||||
Price or promotion language
|
||||
Hard-sell expressions
|
||||
|
||||
|
||||
[DESCRIPTION GENERATION RULES]
|
||||
|
||||
Character rules:
|
||||
|
||||
Maximum length: 1,000 characters
|
||||
Critical information must appear within the first 150 characters
|
||||
Language style rules (mandatory):
|
||||
|
||||
Use polite Korean honorific style
|
||||
Replace “있나요?” with “있으신가요?”
|
||||
Do not start sentences with “이곳은”
|
||||
Replace “선택이 됩니다” with “추천 드립니다”
|
||||
Always wrap the business name in quotation marks
|
||||
Example: “스테이 머뭄”
|
||||
Avoid vague location words like “근대거리” alone
|
||||
Use specific phrasing such as:
|
||||
“군산 근대역사문화거리 일대”
|
||||
Structure:
|
||||
|
||||
Opening CTA (first line)
|
||||
Must be a question or gentle suggestion
|
||||
Must use honorific tone
|
||||
Example:
|
||||
“조용히 쉴 수 있는 군산숙소를 찾고 있으신가요?”
|
||||
Core Stay Introduction (within first 150 characters total)
|
||||
Mention business name with quotation marks
|
||||
Mention region
|
||||
Include main keyword
|
||||
Briefly describe the stay experience
|
||||
Brand Experience
|
||||
Core value and emotional promise
|
||||
Based on marketing intelligence positioning
|
||||
Key Highlights (3–4 short lines)
|
||||
Derived from USP signals
|
||||
Natural sentences
|
||||
Focus on booking-trigger moments
|
||||
Local Context
|
||||
Mention nearby experiences
|
||||
Use specific local references
|
||||
Example:
|
||||
“군산 근대역사문화거리 일대 산책이나 로컬 카페 투어”
|
||||
Soft Closing Line
|
||||
One gentle, non-salesy closing sentence
|
||||
Must end with a recommendation tone
|
||||
Example:
|
||||
“군산에서 조용한 시간을 보내고 싶다면 ‘스테이 머뭄’을 추천 드립니다.”
|
||||
|
||||
|
||||
[SEO & AEO RULES]
|
||||
|
||||
Naturally integrate 3–5 keywords from {target_keywords}
|
||||
Avoid keyword stuffing
|
||||
Use conversational, search-like phrasing
|
||||
Optimize for:
|
||||
YouTube search
|
||||
Google video results
|
||||
AI answer summaries
|
||||
Keywords should appear in:
|
||||
|
||||
Title (1–2)
|
||||
First 150 characters of description
|
||||
Highlight or context sections
|
||||
|
||||
|
||||
[LANGUAGE RULE]
|
||||
|
||||
All output must be written entirely in {language}.
|
||||
No mixed languages.
|
||||
|
||||
|
||||
|
||||
[OUTPUT FORMAT – STRICT]
|
||||
|
||||
title:
|
||||
description:
|
||||
|
||||
No explanations.
|
||||
No headings.
|
||||
No extra text.
|
||||
|
|
@ -170,7 +170,7 @@ async def generate_video(
|
|||
logger.info(f"[generate_video] Check subtitle done task_id: {task_id}")
|
||||
break
|
||||
await asyncio.sleep(5)
|
||||
if count > 12 :
|
||||
if count > 60 :
|
||||
raise Exception("subtitle 결과 생성 실패")
|
||||
count += 1
|
||||
|
||||
|
|
|
|||
14
config.py
14
config.py
|
|
@ -180,19 +180,7 @@ class CreatomateSettings(BaseSettings):
|
|||
model_config = _base_config
|
||||
|
||||
class PromptSettings(BaseSettings):
|
||||
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")
|
||||
|
||||
YOUTUBE_PROMPT_FILE_NAME : str = Field(default="yt_upload_prompt.txt")
|
||||
YOUTUBE_PROMPT_MODEL : str = Field(default="gpt-5-mini")
|
||||
|
||||
SUBTITLE_PROMPT_FILE_NAME : str = Field(...)
|
||||
SUBTITLE_PROMPT_MODEL : str = Field(...)
|
||||
PROMPT_EXCEL_FILE: str = Field(...)
|
||||
|
||||
model_config = _base_config
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ dependencies = [
|
|||
"fastapi-cli>=0.0.16",
|
||||
"fastapi[standard]>=0.125.0",
|
||||
"openai>=2.13.0",
|
||||
"openpyxl>=3.1.5",
|
||||
"playwright>=1.57.0",
|
||||
"pydantic-settings>=2.12.0",
|
||||
"python-dotenv>=1.0.0",
|
||||
|
|
|
|||
23
uv.lock
23
uv.lock
|
|
@ -283,6 +283,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "et-xmlfile"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.128.0"
|
||||
|
|
@ -655,6 +664,7 @@ dependencies = [
|
|||
{ name = "fastapi", extra = ["standard"] },
|
||||
{ name = "fastapi-cli" },
|
||||
{ name = "openai" },
|
||||
{ name = "openpyxl" },
|
||||
{ name = "playwright" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "python-dotenv" },
|
||||
|
|
@ -684,6 +694,7 @@ requires-dist = [
|
|||
{ name = "fastapi", extras = ["standard"], specifier = ">=0.125.0" },
|
||||
{ name = "fastapi-cli", specifier = ">=0.0.16" },
|
||||
{ name = "openai", specifier = ">=2.13.0" },
|
||||
{ name = "openpyxl", specifier = ">=3.1.5" },
|
||||
{ name = "playwright", specifier = ">=1.57.0" },
|
||||
{ name = "pydantic-settings", specifier = ">=2.12.0" },
|
||||
{ name = "python-dotenv", specifier = ">=1.0.0" },
|
||||
|
|
@ -722,6 +733,18 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/b5/df/c306f7375d42bafb379934c2df4c2fa3964656c8c782bac75ee10c102818/openai-2.15.0-py3-none-any.whl", hash = "sha256:6ae23b932cd7230f7244e52954daa6602716d6b9bf235401a107af731baea6c3", size = 1067879, upload-time = "2026-01-09T22:10:06.446Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openpyxl"
|
||||
version = "3.1.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "et-xmlfile" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "26.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue