p763nx9tf / keyword_diversity_fix.py
ssboost's picture
Upload 11 files
1271db4 verified
"""
logger.info("Gemini API 호좜 μ‹œμž‘...")
# Gemini API 호좜 - μ˜¨λ„λ₯Ό λ†’μ—¬μ„œ 더 λ‹€μ–‘ν•œ κ²°κ³Ό 생성
response = client.models.generate_content(
model="gemini-2.0-flash",
contents=prompt,
config=GenerateContentConfig(
tools=config_tools, # 검색 엔진에 따라 도ꡬ μ„€μ •
response_modalities=["TEXT"],
temperature=0.95, # μ˜¨λ„λ₯Ό λ†’μ—¬μ„œ 더 λ‹€μ–‘ν•œ κ²°κ³Ό
max_output_tokens=2000,
top_p=0.9, # 더 λ‹€μ–‘ν•œ 토큰 선택
top_k=40 # 후보 토큰 수 증가
)
)
logger.info("Gemini API 응닡 μˆ˜μ‹  μ™„λ£Œ")
# μ‘λ‹΅μ—μ„œ ν…μŠ€νŠΈ μΆ”μΆœ
result_text = ""
for part in response.candidates[0].content.parts:
if hasattr(part, 'text'):
result_text += part.text
# κ²°κ³Ό μ •μ œ - 번호, μ„€λͺ…, 기호 제거 및 쀑볡 제거 κ°•ν™”
lines = result_text.strip().split('\n')
clean_keywords = []
seen_keywords = set() # 쀑볡 λ°©μ§€λ₯Ό μœ„ν•œ μ§‘ν•©
for line in lines:
# 빈 쀄 κ±΄λ„ˆλ›°κΈ°
if not line.strip():
continue
# μ •μ œ μž‘μ—…
clean_line = line.strip()
# 번호 제거 (1., 2., 3. λ“±)
import re
clean_line = re.sub(r'^\d+\.?\s*', '', clean_line)
# 뢈릿 포인트 제거 (-, *, β€’, βœ…, ❌ λ“±)
clean_line = re.sub(r'^[-*β€’βœ…βŒ]\s*', '', clean_line)
# κ΄„ν˜Έ μ•ˆ μ„€λͺ… 제거
clean_line = re.sub(r'\([^)]*\)', '', clean_line)
# μΆ”κ°€ μ„€λͺ… 제거 (: 이후 λ‚΄μš©)
if ':' in clean_line:
clean_line = clean_line.split(':')[0]
# 곡백 정리
clean_line = clean_line.strip()
# μœ νš¨ν•œ ν‚€μ›Œλ“œλ§Œ μΆ”κ°€ (2κΈ€μž 이상, 50κΈ€μž μ΄ν•˜)
if clean_line and 2 <= len(clean_line) <= 50:
# λΈŒλžœλ“œλͺ…μ΄λ‚˜ μ œμ‘°μ—…μ²΄λͺ… 필터링
brand_keywords = ['μ‚Όμ„±', 'μ—˜μ§€', 'LG', 'μ• ν”Œ', '아이폰', 'κ°€λŸ­μ‹œ', 'λ‚˜μ΄ν‚€', 'μ•„λ””λ‹€μŠ€', 'μŠ€νƒ€λ²…μŠ€']
if not any(brand in clean_line for brand in brand_keywords):
# 쀑볡 검사 - λŒ€μ†Œλ¬Έμž ꡬ뢄 없이 체크
clean_lower = clean_line.lower()
if clean_lower not in seen_keywords:
seen_keywords.add(clean_lower)
clean_keywords.append(clean_line)
# 50개둜 μ œν•œν•˜λ˜, λΆ€μ‘±ν•˜λ©΄ μΆ”κ°€ 생성 μš”μ²­
if len(clean_keywords) < 50:
logger.info(f"ν‚€μ›Œλ“œ λΆ€μ‘± ({len(clean_keywords)}개), μΆ”κ°€ 생성 ν•„μš”")
# λΆ€μ‘±ν•œ 만큼 μΆ”κ°€ 생성을 μœ„ν•œ 보쑰 ν”„λ‘¬ν”„νŠΈ
additional_prompt = f"""
기쑴에 μƒμ„±λœ ν‚€μ›Œλ“œ: {', '.join(clean_keywords)}
μœ„ ν‚€μ›Œλ“œλ“€κ³Ό μ ˆλŒ€ μ€‘λ³΅λ˜μ§€ μ•ŠλŠ” μ™„μ „νžˆ μƒˆλ‘œμš΄ {category} κ΄€λ ¨ μ‡Όν•‘ν‚€μ›Œλ“œλ₯Ό {50 - len(clean_keywords)}개 더 μƒμ„±ν•˜μ„Έμš”.
λ°˜λ“œμ‹œ μ§€μΌœμ•Ό ν•  κ·œμΉ™:
- κΈ°μ‘΄ ν‚€μ›Œλ“œμ™€ μ ˆλŒ€ μ€‘λ³΅λ˜μ§€ μ•ŠμŒ
- μ†Œμž¬, ν˜•νƒœ, κΈ°λŠ₯을 λ‹€μ–‘ν•˜κ²Œ μ‘°ν•©
- λΈŒλžœλ“œλͺ… μ ˆλŒ€ κΈˆμ§€
- 순수 ν‚€μ›Œλ“œλ§Œ 좜λ ₯ (번호, μ„€λͺ…, 기호 κΈˆμ§€)
μ˜ˆμ‹œ 좜λ ₯:
μŠ€ν…ŒμΈλ¦¬μŠ€ 쟁반
고무 맀트
유리 용기
"""
# μΆ”κ°€ 생성 μš”μ²­
additional_response = client.models.generate_content(
model="gemini-2.0-flash",
contents=additional_prompt,
config=GenerateContentConfig(
response_modalities=["TEXT"],
temperature=0.98, # 더 높은 μ˜¨λ„λ‘œ λ‹€μ–‘μ„± 보μž₯
max_output_tokens=1000,
top_p=0.95,
top_k=50
)
)
# μΆ”κ°€ ν‚€μ›Œλ“œ 처리
additional_text = ""
for part in additional_response.candidates[0].content.parts:
if hasattr(part, 'text'):
additional_text += part.text
additional_lines = additional_text.strip().split('\n')
for line in additional_lines:
if not line.strip():
continue
clean_line = line.strip()
clean_line = re.sub(r'^\d+\.?\s*', '', clean_line)
clean_line = re.sub(r'^[-*β€’βœ…βŒ]\s*', '', clean_line)
clean_line = re.sub(r'\([^)]*\)', '', clean_line)
if ':' in clean_line:
clean_line = clean_line.split(':')[0]
clean_line = clean_line.strip()
if clean_line and 2 <= len(clean_line) <= 50:
brand_keywords = ['μ‚Όμ„±', 'μ—˜μ§€', 'LG', 'μ• ν”Œ', '아이폰', 'κ°€λŸ­μ‹œ', 'λ‚˜μ΄ν‚€', 'μ•„λ””λ‹€μŠ€', 'μŠ€νƒ€λ²…μŠ€']
if not any(brand in clean_line for brand in brand_keywords):
clean_lower = clean_line.lower()
if clean_lower not in seen_keywords and len(clean_keywords) < 50:
seen_keywords.add(clean_lower)
clean_keywords.append(clean_line)
# 50개둜 μ œν•œ
clean_keywords = clean_keywords[:50]
# μ΅œμ’… μ…”ν”Œλ‘œ μˆœμ„œλ„ λ¬΄μž‘μœ„ν™”
random.shuffle(clean_keywords)
# μ΅œμ’… κ²°κ³Ό λ¬Έμžμ—΄ 생성
final_result = '\n'.join(clean_keywords)
logger.info(f"μ΅œμ’… μ •μ œλœ ν‚€μ›Œλ“œ 개수: {len(clean_keywords)}개")
logger.info(f"쀑볡 제거된 ν‚€μ›Œλ“œ 수: {len(seen_keywords)}개")
logger.info("=== λ‹€μ–‘μ„± κ°•ν™” μ‡Όν•‘ν‚€μ›Œλ“œ 생성 μ™„λ£Œ ===")
# κ·ΈλΌμš΄λ”© 메타데이터 둜그
if hasattr(response.candidates[0], 'grounding_metadata'):
logger.info("Google 검색 κ·ΈλΌμš΄λ”© 메타데이터 확인됨")
if hasattr(response.candidates[0].grounding_metadata, 'web_search_queries'):
queries = response.candidates[0].grounding_metadata.web_search_queries
logger.info(f"μ‹€ν–‰λœ 검색 쿼리: {queries}")
return final_result
except Exception as e:
error_msg = f"였λ₯˜ λ°œμƒ: {str(e)}"
logger.error(error_msg)
logger.error("GEMINI_API_KEY ν™˜κ²½λ³€μˆ˜κ°€ μ˜¬λ°”λ₯΄κ²Œ μ„€μ •λ˜μ—ˆλŠ”μ§€ ν™•μΈν•΄μ£Όμ„Έμš”.")
return f"{error_msg}\n\nGEMINI_API_KEY ν™˜κ²½λ³€μˆ˜κ°€ μ˜¬λ°”λ₯΄κ²Œ μ„€μ •λ˜μ—ˆλŠ”μ§€ ν™•μΈν•΄μ£Όμ„Έμš”."
def generate_with_logs(category, additional_request, launch_timing, seasonality, sales_target, sales_channel, competition_level, search_engine):
"""ν‚€μ›Œλ“œ 생성과 둜그λ₯Ό ν•¨κ»˜ λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜"""
logger.info("=== λ‹€μ–‘μ„± κ°•ν™” μ‡Όν•‘ν‚€μ›Œλ“œ 생성 μ‹œμž‘ ===")
# ν‚€μ›Œλ“œ 생성
result = generate_sourcing_keywords(category, additional_request, launch_timing, seasonality, sales_target, sales_channel, competition_level, search_engine)
# 졜근 둜그 κ°€μ Έμ˜€κΈ°
logs = get_recent_logs()
return result, logs
# Gradio μΈν„°νŽ˜μ΄μŠ€ ꡬ성
def create_interface():
with gr.Blocks(
title="🎯 λ‹€μ–‘μ„± κ°•ν™” μ‡Όν•‘ν‚€μ›Œλ“œ μ‹œμŠ€ν…œ",
theme=gr.themes.Soft(),
css="""
.gradio-container {
max-width: 1200px !important;
}
.title-header {
text-align: center;
background: linear-gradient(45deg, #FF6B6B, #4ECDC4, #45B7D1);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-size: 2.5em;
font-weight: bold;
margin-bottom: 20px;
}
.subtitle {
text-align: center;
color: #666;
font-size: 1.2em;
margin-bottom: 30px;
}
"""
) as demo:
# 헀더
gr.HTML("""
<div class="title-header">🎯 λ‹€μ–‘μ„± κ°•ν™” μ‡Όν•‘ν‚€μ›Œλ“œ μ‹œμŠ€ν…œ</div>
<div class="subtitle">πŸ”„ 맀번 μ™„μ „νžˆ λ‹€λ₯Έ κ²°κ³Ό! 쀑볡 μ—†λŠ” μ‡Όν•‘ν‚€μ›Œλ“œ μ „λ¬Έ 발꡴ ν”„λ‘œκ·Έλž¨</div>
""")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### πŸ“Š λ‹€μ–‘μ„± κ°•ν™” μ„€μ •")
# 검색 μ—”μ§„ 선택 μΆ”κ°€
search_engine = gr.Dropdown(
choices=[
"λͺ¨λ“  검색 μ—”μ§„ 톡합 뢄석 (μΆ”μ²œ)",
"Google 검색 κ·ΈλΌμš΄λ”©λ§Œ",
"넀이버 검색 API만",
"DuckDuckGo κ²€μƒ‰λ§Œ",
"검색 없이 AI만 μ‚¬μš©"
],
label="πŸ” 검색 μ—”μ§„ 선택",
value="λͺ¨λ“  검색 μ—”μ§„ 톡합 뢄석 (μΆ”μ²œ)"
)
# 1. μ‡Όν•‘ μΉ΄ν…Œκ³ λ¦¬ 선택
category = gr.Dropdown(
choices=["랜덀적용", "νŒ¨μ…˜μž‘ν™”", "μƒν™œ/건강", "μΆœμ‚°/μœ‘μ•„", "슀포츠/λ ˆμ €", "λ””μ§€ν„Έ/κ°€μ „", "가ꡬ/μΈν…Œλ¦¬μ–΄", "νŒ¨μ…˜μ˜λ₯˜", "ν™”μž₯ν’ˆ/미용"],
label="πŸ›οΈ μ‡Όν•‘ μΉ΄ν…Œκ³ λ¦¬",
value="μƒν™œ/건강"
)
# 2. μΆ”κ°€ μš”μ²­μ‚¬ν•­
additional_request = gr.Textbox(
label="πŸ“ μΆ”κ°€ μš”μ²­μ‚¬ν•­ (λ‹€μ–‘μ„± 쀑심)",
placeholder="예: λ‹€μ–‘ν•œ μ†Œμž¬, μƒˆλ‘œμš΄ ν˜•νƒœ, λ…νŠΉν•œ κΈ°λŠ₯ λ“±",
lines=2
)
# 3. μΆœμ‹œ 타이밍
launch_timing = gr.Radio(
choices=["랜덀적용", "μ¦‰μ‹œμ†Œμ‹±", "κΈ°νšν˜•"],
label="⏰ μΆœμ‹œ 타이밍",
value="μ¦‰μ‹œμ†Œμ‹±"
)
# 4. κ³„μ ˆμ„±
seasonality = gr.Radio(
choices=["랜덀적용", "λ΄„", "여름", "가을", "겨울", "λΉ„κ³„μ ˆ"],
label="🌱 κ³„μ ˆμ„±",
value="λΉ„κ³„μ ˆ"
)
with gr.Column(scale=1):
gr.Markdown("### πŸ’° λͺ©ν‘œ μ„€μ •")
# 5. 맀좜 λͺ©ν‘œ
sales_target = gr.Radio(
choices=["랜덀적용", "100λ§Œμ› μ΄ν•˜", "100-500λ§Œμ›", "500-1μ²œλ§Œμ›", "1천-5μ²œλ§Œμ›", "5μ²œλ§Œμ› 이상"],
label="πŸ’΅ 맀좜 λͺ©ν‘œ",
value="100-500λ§Œμ›"
)
# 6. 판맀 채널
sales_channel = gr.Radio(
choices=["랜덀적용", "μ˜€ν”ˆλ§ˆμΌ“", "SNSλ§ˆμΌ€νŒ…", "κ΄‘κ³ μ§‘ν–‰", "μ˜€ν”„λΌμΈ"],
label="πŸ“± 판맀 채널",
value="μ˜€ν”ˆλ§ˆμΌ“"
)
# 7. 경쟁 강도
competition_level = gr.Radio(
choices=[
"랜덀적용",
"초보",
"μ€‘μˆ˜",
"고수"
],
label="βš”οΈ 경쟁 강도",
value="초보"
)
# μ‹€ν–‰ λ²„νŠΌ
generate_btn = gr.Button(
"πŸš€ λ‹€μ–‘μ„± κ°•ν™” μ‡Όν•‘ν‚€μ›Œλ“œ 발꡴ μ‹œμž‘ (맀번 λ‹€λ₯Έ κ²°κ³Ό)",
variant="primary",
size="lg"
)
# κ²°κ³Ό 좜λ ₯
with gr.Row():
with gr.Column(scale=2):
gr.Markdown("### πŸ“‹ λ‹€μ–‘μ„± κ°•ν™” μ‡Όν•‘ν‚€μ›Œλ“œ (50개)")
output = gr.Textbox(
label="쀑볡 μ—†λŠ” μ‡Όν•‘ν‚€μ›Œλ“œ κ²°κ³Ό (맀번 μ™„μ „νžˆ 닀름)",
lines=30,
max_lines=50,
placeholder="여기에 맀번 λ‹€λ₯Έ 50개의 μ‡Όν•‘ν‚€μ›Œλ“œκ°€ 좜λ ₯λ©λ‹ˆλ‹€...",
show_copy_button=True
)
with gr.Column(scale=1):
gr.Markdown("### πŸ“Š μ‹€ν–‰ 둜그")
log_output = gr.Textbox(
label="μ‹œμŠ€ν…œ 둜그",
lines=30,
max_lines=50,
placeholder="μ‹œμŠ€ν…œ μ‹€ν–‰ λ‘œκ·Έκ°€ 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€...",
show_copy_button=True
)
# 이벀트 μ—°κ²°
generate_btn.click(
fn=generate_with_logs,
inputs=[
category,
additional_request,
launch_timing,
seasonality,
sales_target,
sales_channel,
competition_level,
search_engine
],
outputs=[output, log_output],
show_progress=True
)
# μ‚¬μš©λ²• μ•ˆλ‚΄
with gr.Accordion("πŸ“– λ‹€μ–‘μ„± κ°•ν™” μ‚¬μš©λ²• μ•ˆλ‚΄", open=False):
gr.Markdown("""
### 🎯 λ‹€μ–‘μ„± κ°•ν™” μ‡Όν•‘ν‚€μ›Œλ“œ μ‹œμŠ€ν…œ μ‚¬μš©λ²•
#### πŸš€ μ£Όμš” κ°œμ„  사항
- **μ™„μ „ν•œ 쀑볡 λ°©μ§€**: 맀번 μ‹€ν–‰ν•  λ•Œλ§ˆλ‹€ μ™„μ „νžˆ λ‹€λ₯Έ ν‚€μ›Œλ“œ 생성
- **랜덀 μ‹œλ“œ μ‹œμŠ€ν…œ**: ν˜„μž¬ μ‹œκ°μ„ 기반으둜 ν•œ 랜덀 μ‹œλ“œλ‘œ 예츑 λΆˆκ°€λŠ₯
- **λ‹€μ–‘ν•œ μ‘°ν•© 보μž₯**: μ†Œμž¬Γ—ν˜•νƒœΓ—κΈ°λŠ₯의 3차원 μ‘°ν•©μœΌλ‘œ λ¬΄ν•œ λ‹€μ–‘μ„±
- **쀑볡 검사 κ°•ν™”**: λŒ€μ†Œλ¬Έμž ꡬ뢄 μ—†λŠ” μ—„κ²©ν•œ 쀑볡 제거
- **μ˜¨λ„ 쑰절**: AI 생성 νŒŒλΌλ―Έν„° μ΅œμ ν™”λ‘œ μ°½μ˜μ„± κ·ΉλŒ€ν™”
#### πŸ”„ λ‹€μ–‘μ„± 보μž₯ λ©”μ»€λ‹ˆμ¦˜
1. **μ‹œλ“œ 기반 λžœλ€ν™”**: 마이크둜초 λ‹¨μœ„ μ‹œκ°„ 기반 랜덀 μ‹œλ“œ
2. **3차원 μ‘°ν•© μ‹œμŠ€ν…œ**:
- μ†Œμž¬: μ‹€λ¦¬μ½˜, μŠ€ν…ŒμΈλ¦¬μŠ€, 세라믹, λŒ€λ‚˜λ¬΄ λ“± 20μ’…
- ν˜•νƒœ: 접이식, μ›ν˜•, 슬림, νœ΄λŒ€μš© λ“± 20μ’…
- κΈ°λŠ₯: 방수, ν•­κ· , λ§ˆκ·Έλ„€ν‹±, 보온 λ“± 20μ’…
3. **쀑볡 λ°©μ§€ μ•Œκ³ λ¦¬μ¦˜**: 생성 쀑 μ‹€μ‹œκ°„ 쀑볡 검사
4. **μΆ”κ°€ 생성 μ‹œμŠ€ν…œ**: λΆ€μ‘±μ‹œ μžλ™μœΌλ‘œ μΆ”κ°€ ν‚€μ›Œλ“œ 생성
#### 🎲 랜덀 적용의 μ§„ν™”
- **ν‚€μ›Œλ“œλ³„ 독립 적용**: 각 ν‚€μ›Œλ“œλ§ˆλ‹€ λ‹€λ₯Έ 쑰건 μ‘°ν•©
- **예츑 λΆˆκ°€λŠ₯μ„±**: 같은 섀정이라도 맀번 λ‹€λ₯Έ κ²°κ³Ό
- **μ‘°ν•© 폭발**: 수천 κ°€μ§€ κ°€λŠ₯ν•œ μ‘°ν•©μœΌλ‘œ λ¬΄ν•œ λ‹€μ–‘μ„±
#### πŸ“ˆ 생성 ν’ˆimport gradio as gr
import os
import logging
import sys
import random
import requests
import json
from datetime import datetime
from google import genai
from google.genai.types import Tool, GenerateContentConfig, GoogleSearch
# λ‘œκΉ… μ„€μ •
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler('sourcing_app.log', encoding='utf-8')
]
)
logger = logging.getLogger(__name__)
# ν‚€μ›Œλ“œ 닀양성을 μœ„ν•œ μ‹œλ“œ ν’€ ν™•μž₯
DIVERSE_SEED_POOLS = {
"νŒ¨μ…˜μž‘ν™”": [
"μ•‘μ„Έμ„œλ¦¬", "μž₯신ꡬ", "κ°€λ°©", "μ§€κ°‘", "λͺ¨μž", "μŠ€μΉ΄ν”„", "벨트", "μ„ κΈ€λΌμŠ€", "ν—€μ–΄μ•‘μ„Έμ„œλ¦¬", "μ‹œκ³„μ€„",
"킀링", "브둜치", "λͺ©κ±Έμ΄", "νŒ”μ°Œ", "λ°˜μ§€", "귀걸이", "ν•Έλ“œν°μΌ€μ΄μŠ€", "파우치", "클러치", "ν† νŠΈλ°±"
],
"μƒν™œ/건강": [
"μ£Όλ°©μš©ν’ˆ", "μš•μ‹€μš©ν’ˆ", "μ²­μ†Œμš©ν’ˆ", "μˆ˜λ‚©μš©ν’ˆ", "κ±΄κ°•μš©ν’ˆ", "μ˜λ£Œμš©ν’ˆ", "λ§ˆμ‚¬μ§€μš©ν’ˆ", "μš΄λ™μš©ν’ˆ",
"λ‹€μ΄μ–΄νŠΈμš©ν’ˆ", "ν™”μž₯μ§€", "μ„Έμ œ", "샴푸", "μΉ˜μ•½", "λΉ„λˆ„", "수건", "베개", "이뢈", "μΏ μ…˜", "맀트"
],
"μΆœμ‚°/μœ‘μ•„": [
"μœ μ•„μš©ν’ˆ", "μœ‘μ•„μš©ν’ˆ", "μΆœμ‚°μš©ν’ˆ", "μž„μ‚°λΆ€μš©ν’ˆ", "μ‹ μƒμ•„μš©ν’ˆ", "μ΄μœ μ‹μš©ν’ˆ", "κΈ°μ €κ·€", "젖병",
"유λͺ¨μ°¨", "μΉ΄μ‹œνŠΈ", "μ•„κΈ°μ˜·", "μž₯λ‚œκ°", "κ΅μœ‘μš©ν’ˆ", "μ±…", "κ·Έλ¦Όμ±…", "퍼즐", "블둝", "μΈν˜•", "λ†€μ΄λ§€νŠΈ"
],
"슀포츠/λ ˆμ €": [
"μš΄λ™μš©ν’ˆ", "ν—¬μŠ€μš©ν’ˆ", "μš”κ°€μš©ν’ˆ", "μˆ˜μ˜μš©ν’ˆ", "λ“±μ‚°μš©ν’ˆ", "μΊ ν•‘μš©ν’ˆ", "λ‚šμ‹œμš©ν’ˆ", "κ³¨ν”„μš©ν’ˆ",
"μΆ•κ΅¬μš©ν’ˆ", "λ†κ΅¬μš©ν’ˆ", "λ°°λ“œλ―Όν„΄μš©ν’ˆ", "νƒκ΅¬μš©ν’ˆ", "ν…Œλ‹ˆμŠ€μš©ν’ˆ", "μžμ „κ±°μš©ν’ˆ", "μŠ€μΌ€μ΄νŠΈλ³΄λ“œμš©ν’ˆ"
],
"λ””μ§€ν„Έ/κ°€μ „": [
"μŠ€λ§ˆνŠΈν°μ•‘μ„Έμ„œλ¦¬", "μ»΄ν“¨ν„°μš©ν’ˆ", "νƒœλΈ”λ¦Ώμš©ν’ˆ", "이어폰", "μŠ€ν”Όμ»€", "μΆ©μ „κΈ°", "케이블", "λ§ˆμš°μŠ€νŒ¨λ“œ",
"ν‚€λ³΄λ“œ", "마우슀", "μ›ΉμΊ ", "마이크", "ν—€λ“œμ…‹", "κ²Œμž„νŒ¨λ“œ", "USB", "λ©”λͺ¨λ¦¬μΉ΄λ“œ", "νŒŒμ›Œλ±…ν¬"
],
"가ꡬ/μΈν…Œλ¦¬μ–΄": [
"μˆ˜λ‚©κ°€κ΅¬", "침싀가ꡬ", "거싀가ꡬ", "주방가ꡬ", "μš•μ‹€κ°€κ΅¬", "μ‚¬λ¬΄μš©κ°€κ΅¬", "μΈν…Œλ¦¬μ–΄μ†Œν’ˆ", "μ‘°λͺ…",
"컀튼", "λΈ”λΌμΈλ“œ", "카펫", "러그", "μ•‘μž", "거울", "μ‹œκ³„", "ν™”λΆ„", "꽃병", "μΊ”λ“€", "λ°©ν–₯제"
],
"νŒ¨μ…˜μ˜λ₯˜": [
"ν‹°μ…”μΈ ", "μ…”μΈ ", "λΈ”λΌμš°μŠ€", "μ›ν”ΌμŠ€", "슀컀트", "λ°”μ§€", "μ²­λ°”μ§€", "λ ˆκΉ…μŠ€", "μžμΌ“", "μ½”νŠΈ",
"점퍼", "가디건", "λ‹ˆνŠΈ", "ν›„λ“œ", "쑰끼", "μ†μ˜·", "잠옷", "양말", "μŠ€νƒ€ν‚Ή", "μš΄λ™λ³΅"
],
"ν™”μž₯ν’ˆ/미용": [
"μŠ€ν‚¨μΌ€μ–΄", "메이크업", "ν΄λ Œμ§•", "마슀크팩", "선크림", "λ‘œμ…˜", "μ—μ„ΌμŠ€", "크림", "립밀",
"λ¦½μŠ€ν‹±", "아이섀도", "마슀카라", "νŒŒμš΄λ°μ΄μ…˜", "μ»¨μ‹€λŸ¬", "λΈ”λŸ¬μ…”", "ν•˜μ΄λΌμ΄ν„°", "넀일", "ν–₯수"
]
}
# μ†Œμž¬λ³„ ν‚€μ›Œλ“œ ν’€
MATERIAL_KEYWORDS = [
"μ‹€λ¦¬μ½˜", "μŠ€ν…ŒμΈλ¦¬μŠ€", "세라믹", "유리", "λ‚˜λ¬΄", "λŒ€λ‚˜λ¬΄", "λ©΄", "린넨", "ν΄λ¦¬μ—μŠ€ν„°", "λ‚˜μΌλ‘ ",
"고무", "ν”ŒλΌμŠ€ν‹±", "쒅이", "κ°€μ£½", "인쑰가죽", "λ©”νƒˆ", "μ•Œλ£¨λ―ΈλŠ„", "μ² ", "ꡬ리", "황동"
]
# ν˜•νƒœλ³„ ν‚€μ›Œλ“œ ν’€
SHAPE_KEYWORDS = [
"접이식", "νœ΄λŒ€μš©", "λ―Έλ‹ˆ", "λŒ€ν˜•", "μ›ν˜•", "사각", "타원", "직사각", "삼각", "윑각",
"슬림", "λ‘κΊΌμš΄", "얇은", "κΈ΄", "짧은", "넓은", "쒁은", "κΉŠμ€", "얕은", "곑선"
]
# κΈ°λŠ₯별 ν‚€μ›Œλ“œ ν’€
FUNCTION_KEYWORDS = [
"방수", "λ―Έλ„λŸΌλ°©μ§€", "ν•­κ· ", "λƒ„μƒˆμ œκ±°", "보온", "보냉", "속건", "흑수", "차단", "보호",
"λ§ˆκ·Έλ„€ν‹±", "μžμ„", "끈적", "투λͺ…", "뢈투λͺ…", "λ°œκ΄‘", "λ°˜μ‚¬", "μ‹ μΆ•", "탄λ ₯", "κ³ μ •"
]
# Gemini API ν΄λΌμ΄μ–ΈνŠΈ μ΄ˆκΈ°ν™”
def initialize_gemini():
logger.info("Gemini API ν΄λΌμ΄μ–ΈνŠΈ μ΄ˆκΈ°ν™” μ‹œμž‘")
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
logger.error("GEMINI_API_KEY ν™˜κ²½λ³€μˆ˜κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
raise ValueError("GEMINI_API_KEY ν™˜κ²½λ³€μˆ˜κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
client = genai.Client(api_key=api_key)
logger.info("Gemini API ν΄λΌμ΄μ–ΈνŠΈ μ΄ˆκΈ°ν™” μ™„λ£Œ")
return client
def get_recent_logs():
"""졜근 둜그λ₯Ό κ°€μ Έμ˜€λŠ” ν•¨μˆ˜"""
try:
with open('sourcing_app.log', 'r', encoding='utf-8') as f:
lines = f.readlines()
# 졜근 50μ€„λ§Œ λ°˜ν™˜
return ''.join(lines[-50:])
except FileNotFoundError:
return "둜그 νŒŒμΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."
except Exception as e:
return f"둜그 읽기 였λ₯˜: {str(e)}"
def generate_diverse_keyword_combinations(category, count=60):
"""λ‹€μ–‘ν•œ ν‚€μ›Œλ“œ 쑰합을 μƒμ„±ν•˜λŠ” ν•¨μˆ˜"""
logger.info(f"λ‹€μ–‘ν•œ ν‚€μ›Œλ“œ μ‘°ν•© 생성 μ‹œμž‘: {category}, {count}개")
combinations = []
category_pool = DIVERSE_SEED_POOLS.get(category, DIVERSE_SEED_POOLS["μƒν™œ/건강"])
# 1. 단일 ν‚€μ›Œλ“œ (20%)
single_keywords = random.sample(category_pool, min(12, len(category_pool)))
combinations.extend(single_keywords)
# 2. μ†Œμž¬ + μΉ΄ν…Œκ³ λ¦¬ (30%)
for _ in range(18):
material = random.choice(MATERIAL_KEYWORDS)
item = random.choice(category_pool)
combinations.append(f"{material} {item}")
# 3. ν˜•νƒœ + μΉ΄ν…Œκ³ λ¦¬ (30%)
for _ in range(18):
shape = random.choice(SHAPE_KEYWORDS)
item = random.choice(category_pool)
combinations.append(f"{shape} {item}")
# 4. κΈ°λŠ₯ + μΉ΄ν…Œκ³ λ¦¬ (20%)
for _ in range(12):
function = random.choice(FUNCTION_KEYWORDS)
item = random.choice(category_pool)
combinations.append(f"{function} {item}")
# 쀑볡 제거 및 μ…”ν”Œ
combinations = list(set(combinations))
random.shuffle(combinations)
logger.info(f"μƒμ„±λœ μ‘°ν•© 수: {len(combinations)}개")
return combinations[:count]
def search_all_engines(query):
"""λͺ¨λ“  검색 엔진을 μ‚¬μš©ν•˜μ—¬ 데이터λ₯Ό μ·¨ν•©ν•˜λŠ” ν•¨μˆ˜"""
logger.info(f"λͺ¨λ“  검색 μ—”μ§„μœΌλ‘œ 검색 μ‹œμž‘: {query}")
all_results = {
"google": "",
"naver": "",
"duckduckgo": ""
}
# 1. 넀이버 검색 API
try:
naver_client_id = os.getenv("NAVER_CLIENT_ID")
naver_client_secret = os.getenv("NAVER_CLIENT_SECRET")
if naver_client_id and naver_client_secret:
url = "https://openapi.naver.com/v1/search/shop.json"
headers = {
"X-Naver-Client-Id": naver_client_id,
"X-Naver-Client-Secret": naver_client_secret
}
params = {"query": query, "display": 10}
response = requests.get(url, headers=headers, params=params, timeout=10)
if response.status_code == 200:
data = response.json()
naver_data = []
for item in data.get('items', [])[:5]:
naver_data.append(f"μƒν’ˆ: {item.get('title', '').replace('<b>', '').replace('</b>', '')}")
naver_data.append(f"가격: {item.get('lprice', '')}원")
naver_data.append(f"μΉ΄ν…Œκ³ λ¦¬: {item.get('category1', '')}")
all_results["naver"] = "\n".join(naver_data)
logger.info("넀이버 검색 μ™„λ£Œ")
else:
all_results["naver"] = "넀이버 API 검색 μ‹€νŒ¨"
else:
all_results["naver"] = "넀이버 API ν‚€κ°€ μ„€μ •λ˜μ§€ μ•ŠμŒ"
except Exception as e:
all_results["naver"] = f"넀이버 검색 였λ₯˜: {str(e)}"
# 2. DuckDuckGo 검색
try:
url = "https://api.duckduckgo.com/"
params = {
"q": query,
"format": "json",
"no_html": "1",
"skip_disambig": "1"
}
response = requests.get(url, params=params, timeout=10)
if response.status_code == 200:
data = response.json()
ddg_data = []
# Abstract 정보
if data.get('Abstract'):
ddg_data.append(f"μš”μ•½: {data['Abstract']}")
# Related Topics
for topic in data.get('RelatedTopics', [])[:3]:
if isinstance(topic, dict) and topic.get('Text'):
ddg_data.append(f"관련정보: {topic['Text']}")
all_results["duckduckgo"] = "\n".join(ddg_data) if ddg_data else "DuckDuckGoμ—μ„œ κ΄€λ ¨ 정보 μ—†μŒ"
logger.info("DuckDuckGo 검색 μ™„λ£Œ")
else:
all_results["duckduckgo"] = "DuckDuckGo 검색 μ‹€νŒ¨"
except Exception as e:
all_results["duckduckgo"] = f"DuckDuckGo 검색 였λ₯˜: {str(e)}"
# 3. Google 검색은 Geminiμ—μ„œ μžλ™ 처리됨
all_results["google"] = "Google 검색 κ·ΈλΌμš΄λ”© μžλ™ μ‹€ν–‰"
logger.info("λͺ¨λ“  검색 μ—”μ§„ 데이터 μˆ˜μ§‘ μ™„λ£Œ")
return all_results
def comprehensive_market_analysis(category, seasonality, sales_target):
"""λ‹€μ–‘μ„± κ°•ν™”λœ μ‹œμž₯ 뢄석"""
logger.info("λ‹€μ–‘μ„± κ°•ν™” μ‹œμž₯ 뢄석 μ‹œμž‘")
# 랜덀 μ‹œλ“œλ₯Ό ν˜„μž¬ μ‹œκ°„μœΌλ‘œ μ„€μ •ν•˜μ—¬ 맀번 λ‹€λ₯Έ κ²°κ³Ό 보μž₯
random.seed(datetime.now().microsecond)
# λ‹€μ–‘ν•œ 검색 κ°λ„λ‘œ 쿼리 생성
search_angles = [
"ν‹ˆμƒˆμƒν’ˆ", "μ‹ μƒν’ˆ", "μΈκΈ°μƒν’ˆ", "μ €κ°€μƒν’ˆ", "κ³ κΈ‰μƒν’ˆ", "ν• μΈμƒν’ˆ",
"κ°„νŽΈμƒν’ˆ", "μ‹€μš©μƒν’ˆ", "νŠΈλ Œλ“œμƒν’ˆ", "μˆ¨μ€μƒν’ˆ", "λ² μŠ€νŠΈμƒν’ˆ", "μΆ”μ²œμƒν’ˆ"
]
search_queries = []
# μΉ΄ν…Œκ³ λ¦¬λ³„ λ‹€μ–‘ν•œ 검색어 생성
category_pool = DIVERSE_SEED_POOLS.get(category, DIVERSE_SEED_POOLS["μƒν™œ/건강"])
for angle in search_angles:
for item in random.sample(category_pool, 3): # 각 μΉ΄ν…Œκ³ λ¦¬μ—μ„œ 3κ°œμ”©λ§Œ 선택
search_queries.append(f"{item} {angle}")
# μ†Œμž¬λ³„ 검색어 μΆ”κ°€
for material in random.sample(MATERIAL_KEYWORDS, 5):
search_queries.append(f"{material} {category} μƒν’ˆ")
# ν˜•νƒœλ³„ 검색어 μΆ”κ°€
for shape in random.sample(SHAPE_KEYWORDS, 5):
search_queries.append(f"{shape} {category} μ•„μ΄ν…œ")
# 검색어 μ…”ν”Œν•˜μ—¬ 예츑 λΆˆκ°€λŠ₯ν•˜κ²Œ λ§Œλ“€κΈ°
random.shuffle(search_queries)
search_queries = search_queries[:15] # 15개둜 μ œν•œ
comprehensive_data = {}
for i, query in enumerate(search_queries):
logger.info(f"λ‹€μ–‘μ„± 검색 {i+1}/15: {query}")
comprehensive_data[f"query_{i+1}"] = search_all_engines(query)
# API κ³ΌλΆ€ν•˜ λ°©μ§€λ₯Ό μœ„ν•œ λ”œλ ˆμ΄
import time
time.sleep(0.5)
# λ‹€μ–‘μ„± κ°•ν™” 데이터 μš”μ•½
summary = "=== λ‹€μ–‘μ„± κ°•ν™” μ‹œμž₯ 뢄석 κ²°κ³Ό ===\n\n"
# λ¬΄μž‘μœ„λ‘œ κ²°κ³Όλ₯Ό μ„žμ–΄μ„œ νŒ¨ν„΄ λ°©μ§€
result_keys = list(comprehensive_data.keys())
random.shuffle(result_keys)
summary += "πŸ” λ‹€μ–‘ν•œ μ‹œμž₯ 검색 κ²°κ³Ό:\n"
for key in result_keys[:10]:
results = comprehensive_data.get(key, {})
if results.get("naver"):
summary += f"β€’ {results['naver'][:60]}...\n"
summary += "\n"
logger.info("λ‹€μ–‘μ„± κ°•ν™” 뢄석 μ™„λ£Œ")
return summary
def search_with_api(query, search_engine="Google 검색 κ·ΈλΌμš΄λ”©λ§Œ"):
"""κ°œλ³„ 검색 μ—”μ§„μœΌλ‘œ κ²€μƒ‰ν•˜λŠ” ν•¨μˆ˜ (단일 μ—”μ§„ μ„ νƒμ‹œ μ‚¬μš©)"""
logger.info(f"검색 μ—”μ§„: {search_engine}, 쿼리: {query}")
search_results = ""
try:
if search_engine == "넀이버 검색 API만":
# 넀이버 검색 API μ‚¬μš©
naver_client_id = os.getenv("NAVER_CLIENT_ID")
naver_client_secret = os.getenv("NAVER_CLIENT_SECRET")
if naver_client_id and naver_client_secret:
url = "https://openapi.naver.com/v1/search/shop.json"
headers = {
"X-Naver-Client-Id": naver_client_id,
"X-Naver-Client-Secret": naver_client_secret
}
params = {"query": query, "display": 10}
response = requests.get(url, headers=headers, params=params)
if response.status_code == 200:
data = response.json()
for item in data.get('items', [])[:5]:
search_results += f"μƒν’ˆλͺ…: {item.get('title', '')}\n"
search_results += f"가격: {item.get('lprice', '')}원\n"
search_results += f"μΉ΄ν…Œκ³ λ¦¬: {item.get('category1', '')}\n\n"
else:
search_results = "넀이버 API 검색 μ‹€νŒ¨"
else:
search_results = "넀이버 API ν‚€κ°€ μ„€μ •λ˜μ§€ μ•ŠμŒ"
elif search_engine == "DuckDuckGo κ²€μƒ‰λ§Œ":
# DuckDuckGo 검색 (무료, API ν‚€ λΆˆν•„μš”)
try:
url = "https://api.duckduckgo.com/"
params = {
"q": query,
"format": "json",
"no_html": "1",
"skip_disambig": "1"
}
response = requests.get(url, params=params, timeout=10)
if response.status_code == 200:
data = response.json()
# Abstract 정보
if data.get('Abstract'):
search_results += f"μš”μ•½: {data['Abstract']}\n\n"
# Related Topics
for topic in data.get('RelatedTopics', [])[:5]:
if isinstance(topic, dict) and topic.get('Text'):
search_results += f"κ΄€λ ¨ 정보: {topic['Text']}\n"
if not search_results:
search_results = "DuckDuckGoμ—μ„œ κ΄€λ ¨ 정보λ₯Ό μ°Ύμ§€ λͺ»ν•¨"
else:
search_results = "DuckDuckGo 검색 μ‹€νŒ¨"
except Exception as e:
search_results = f"DuckDuckGo 검색 였λ₯˜: {str(e)}"
elif search_engine == "검색 없이 AI만 μ‚¬μš©":
search_results = "검색 없이 AI μ§€μ‹λ§Œ μ‚¬μš©ν•˜μ—¬ ν‚€μ›Œλ“œ 생성"
else:
# Google 검색 κ·ΈλΌμš΄λ”© (κΈ°λ³Έ)
search_results = "Google 검색 κ·ΈλΌμš΄λ”© μ‚¬μš©"
except Exception as e:
logger.error(f"검색 였λ₯˜: {str(e)}")
search_results = f"검색 였λ₯˜: {str(e)}"
logger.info(f"검색 κ²°κ³Ό 길이: {len(search_results)} 문자")
return search_results
def apply_random_selection_for_keywords(category, launch_timing, seasonality, sales_target, sales_channel, competition_level):
"""각 ν‚€μ›Œλ“œλ§ˆλ‹€ λžœλ€ν•˜κ²Œ 쑰건을 μ μš©ν•˜κΈ° μœ„ν•œ μ„€μ • λ¬Έμžμ—΄ 생성"""
# 각 ν•­λͺ©λ³„ 선택지 μ •μ˜
categories = ["νŒ¨μ…˜μž‘ν™”", "μƒν™œ/건강", "μΆœμ‚°/μœ‘μ•„", "슀포츠/λ ˆμ €", "λ””μ§€ν„Έ/κ°€μ „", "가ꡬ/μΈν…Œλ¦¬μ–΄", "νŒ¨μ…˜μ˜λ₯˜", "ν™”μž₯ν’ˆ/미용"]
launch_timings = ["μ¦‰μ‹œμ†Œμ‹±", "κΈ°νšν˜•"]
seasonalities = ["λ΄„", "여름", "가을", "겨울", "λΉ„κ³„μ ˆ"]
sales_targets = ["100λ§Œμ› μ΄ν•˜", "100-500λ§Œμ›", "500-1μ²œλ§Œμ›", "1천-5μ²œλ§Œμ›", "5μ²œλ§Œμ› 이상"]
sales_channels = ["μ˜€ν”ˆλ§ˆμΌ“", "SNSλ§ˆμΌ€νŒ…", "κ΄‘κ³ μ§‘ν–‰", "μ˜€ν”„λΌμΈ"]
competition_levels = ["초보", "μ€‘μˆ˜", "고수"]
# 랜덀적용 μ„€μ • 정보 생성
random_settings = {
'category_random': category == "랜덀적용",
'launch_timing_random': launch_timing == "랜덀적용",
'seasonality_random': seasonality == "랜덀적용",
'sales_target_random': sales_target == "랜덀적용",
'sales_channel_random': sales_channel == "랜덀적용",
'competition_level_random': competition_level == "랜덀적용",
'categories': categories,
'launch_timings': launch_timings,
'seasonalities': seasonalities,
'sales_targets': sales_targets,
'sales_channels': sales_channels,
'competition_levels': competition_levels
}
# κ³ μ •κ°’λ“€
fixed_values = {
'category': category if category != "랜덀적용" else None,
'launch_timing': launch_timing if launch_timing != "랜덀적용" else None,
'seasonality': seasonality if seasonality != "랜덀적용" else None,
'sales_target': sales_target if sales_target != "랜덀적용" else None,
'sales_channel': sales_channel if sales_channel != "랜덀적용" else None,
'competition_level': competition_level if competition_level != "랜덀적용" else None
}
logger.info("=== ν‚€μ›Œλ“œλ³„ 랜덀 μ„€μ • ===")
logger.info(f"μΉ΄ν…Œκ³ λ¦¬ 랜덀: {random_settings['category_random']}")
logger.info(f"μΆœμ‹œνƒ€μ΄λ° 랜덀: {random_settings['launch_timing_random']}")
logger.info(f"κ³„μ ˆμ„± 랜덀: {random_settings['seasonality_random']}")
logger.info(f"맀좜λͺ©ν‘œ 랜덀: {random_settings['sales_target_random']}")
logger.info(f"νŒλ§€μ±„λ„ 랜덀: {random_settings['sales_channel_random']}")
logger.info(f"κ²½μŸκ°•λ„ 랜덀: {random_settings['competition_level_random']}")
return random_settings, fixed_values
def generate_sourcing_keywords(category, additional_request, launch_timing, seasonality, sales_target, sales_channel, competition_level, search_engine="Google 검색 κ·ΈλΌμš΄λ”©λ§Œ"):
"""λ‹€μ–‘μ„± κ°•ν™”λœ μ‡Όν•‘ ν‚€μ›Œλ“œ 50개λ₯Ό μƒμ„±ν•˜λŠ” ν•¨μˆ˜"""
logger.info("=== λ‹€μ–‘μ„± κ°•ν™” μ‡Όν•‘ν‚€μ›Œλ“œ 생성 μ‹œμž‘ ===")
logger.info(f"μž…λ ₯ 쑰건 - 검색엔진: {search_engine}")
logger.info(f"μž…λ ₯ 쑰건 - μΉ΄ν…Œκ³ λ¦¬: {category}")
logger.info(f"μž…λ ₯ 쑰건 - μΆ”κ°€μš”μ²­: {additional_request}")
logger.info(f"μž…λ ₯ 쑰건 - μΆœμ‹œνƒ€μ΄λ°: {launch_timing}")
logger.info(f"μž…λ ₯ 쑰건 - κ³„μ ˆμ„±: {seasonality}")
logger.info(f"μž…λ ₯ 쑰건 - 맀좜λͺ©ν‘œ: {sales_target}")
logger.info(f"μž…λ ₯ 쑰건 - νŒλ§€μ±„λ„: {sales_channel}")
logger.info(f"μž…λ ₯ 쑰건 - κ²½μŸκ°•λ„: {competition_level}")
try:
logger.info("Gemini ν΄λΌμ΄μ–ΈνŠΈ μ΄ˆκΈ°ν™” 쀑...")
client = initialize_gemini()
# 맀번 λ‹€λ₯Έ μ‹œλ“œλ‘œ λžœλ€μ„± 보μž₯
current_time = datetime.now()
random_seed = current_time.microsecond + current_time.second * 1000
random.seed(random_seed)
logger.info(f"랜덀 μ‹œλ“œ μ„€μ •: {random_seed}")
# ν”„λ‘¬ν”„νŠΈ ꡬ성
logger.info("λ‹€μ–‘μ„± κ°•ν™” ν”„λ‘¬ν”„νŠΈ ꡬ성 쀑...")
# 랜덀 μ„€μ • 처리
random_settings, fixed_values = apply_random_selection_for_keywords(
category, launch_timing, seasonality, sales_target, sales_channel, competition_level
)
# λ‹€μ–‘ν•œ ν‚€μ›Œλ“œ μ‘°ν•© 미리 생성
diverse_combinations = generate_diverse_keyword_combinations(category, 60)
logger.info(f"λ‹€μ–‘ν•œ μ‘°ν•© 생성 μ™„λ£Œ: {len(diverse_combinations)}개")
# 검색 엔진별 처리
search_info = ""
config_tools = []
if search_engine == "λͺ¨λ“  검색 μ—”μ§„ 톡합 뢄석 (μΆ”μ²œ)":
logger.info("πŸ” λ‹€μ–‘μ„± κ°•ν™” 톡합 뢄석 μ‹œμž‘...")
# Google 검색 κ·ΈλΌμš΄λ”© 도ꡬ μ„€μ •
google_search_tool = Tool(google_search=GoogleSearch())
config_tools = [google_search_tool]
# λ‹€μ–‘μ„± κ°•ν™” μ’…ν•© μ‹œμž₯ 뢄석 μ‹€ν–‰
comprehensive_analysis = comprehensive_market_analysis(category, seasonality, sales_target)
search_info = f"""
πŸ” === λ‹€μ–‘μ„± κ°•ν™” 톡합 뢄석 κ²°κ³Ό ===
πŸ“ˆ Google 검색 κ·ΈλΌμš΄λ”©: μ‹€μ‹œκ°„ λ‹€μ–‘ν•œ μ‡Όν•‘ν‚€μ›Œλ“œ νŠΈλ Œλ“œ 뢄석 (μžλ™ μ‹€ν–‰)
πŸ›’ 넀이버 μ‡Όν•‘ API: ν•œκ΅­ μ‡Όν•‘λͺ° λ‹€μ–‘ν•œ ν‚€μ›Œλ“œ 데이터 뢄석
🌐 DuckDuckGo 검색: κΈ€λ‘œλ²Œ λ‹€μ–‘ν•œ μ‡Όν•‘ν‚€μ›Œλ“œ 정보 뢄석
{comprehensive_analysis}
πŸ’‘ μœ„ λͺ¨λ“  데이터λ₯Ό μ’…ν•©ν•˜μ—¬ 맀번 λ‹€λ₯Έ μ‘°ν•©μ˜ μ‡Όν•‘ν‚€μ›Œλ“œλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
🎲 랜덀 μ‹œλ“œ: {random_seed} (맀번 λ‹€λ₯Έ κ²°κ³Ό 보μž₯)
"""
elif search_engine == "Google 검색 κ·ΈλΌμš΄λ”©λ§Œ":
logger.info("Google 검색 도ꡬ μ„€μ • 쀑...")
google_search_tool = Tool(google_search=GoogleSearch())
config_tools = [google_search_tool]
search_info = f"Google 검색 κ·ΈλΌμš΄λ”©μ„ ν†΅ν•œ λ‹€μ–‘ν•œ μ‹€μ‹œκ°„ μ‡Όν•‘ν‚€μ›Œλ“œ 뢄석 (μ‹œλ“œ: {random_seed})"
elif search_engine in ["넀이버 검색 API만", "DuckDuckGo κ²€μƒ‰λ§Œ"]:
logger.info(f"{search_engine} μ‚¬μš©ν•˜μ—¬ λ‹€μ–‘ν•œ μ‡Όν•‘ν‚€μ›Œλ“œ 쑰사 쀑...")
# λ‹€μ–‘μ„± κ°•ν™”λ₯Ό μœ„ν•œ 검색 μ‹€ν–‰
search_queries = []
# λžœλ€ν•˜κ²Œ λ‹€μ–‘ν•œ 검색어 생성
base_items = random.sample(diverse_combinations, 8)
for item in base_items:
search_queries.append(f"{item} μ‡Όν•‘ν‚€μ›Œλ“œ")
search_results = ""
for query in search_queries:
result = search_with_api(query, search_engine)
search_results += f"[검색어: {query}]\n{result}\n\n"
search_info = f"{search_engine} λ‹€μ–‘ν•œ μ‡Όν•‘ν‚€μ›Œλ“œ 검색 κ²°κ³Ό (μ‹œλ“œ: {random_seed}):\n{search_results}"
else: # 검색 없이 AI만 μ‚¬μš©
logger.info("검색 없이 AI μ§€μ‹λ§Œ μ‚¬μš©")
search_info = f"AI λ‚΄μž₯ 지식을 기반으둜 λ‹€μ–‘ν•œ μ‡Όν•‘ν‚€μ›Œλ“œ 생성 (μ‹œλ“œ: {random_seed})"
# 닀양성을 κ°•ν™”ν•œ ν”„λ‘¬ν”„νŠΈ - 맀번 λ‹€λ₯Έ μ‘°ν•© μš”μ²­
diverse_sample = random.sample(diverse_combinations, 20)
prompt = f"""
🎯 λ‹€μ–‘μ„± κ°•ν™” μ‡Όν•‘ν‚€μ›Œλ“œ 발꡴ μ‹œμŠ€ν…œ v5.0
⚑ μ€‘μš”: μ ˆλŒ€ μ€‘λ³΅λ˜μ§€ μ•ŠλŠ” λ‹€μ–‘ν•œ ν‚€μ›Œλ“œλ§Œ μƒμ„±ν•˜μ„Έμš”!
πŸ”¬ μ—­ν•  μ •μ˜
당신은 맀번 μ™„μ „νžˆ λ‹€λ₯Έ μ‘°ν•©μ˜ μ‡Όν•‘ν‚€μ›Œλ“œλ₯Ό μƒμ„±ν•˜λŠ” μ „λ¬Έκ°€μž…λ‹ˆλ‹€.
🎯 λͺ©ν‘œ
μ£Όμ–΄μ§„ 쑰건에 λ§žλŠ” μ‹€μ œ μ‡Όν•‘ν‚€μ›Œλ“œ 50개λ₯Ό λ°œκ΅΄ν•˜λ˜, μ ˆλŒ€ μ€‘λ³΅λ˜μ§€ μ•Šκ³  맀번 λ‹€λ₯Έ μ‘°ν•©μœΌλ‘œ κ΅¬μ„±ν•˜μ‹­μ‹œμ˜€.
πŸ“‹ μž…λ ₯된 쑰건:
μΉ΄ν…Œκ³ λ¦¬: {category}
μΆ”κ°€ μš”μ²­μ‚¬ν•­: {additional_request}
μΆœμ‹œνƒ€μ΄λ°: {launch_timing}
κ³„μ ˆμ„±: {seasonality}
맀좜λͺ©ν‘œ: {sales_target}
νŒλ§€μ±„λ„: {sales_channel}
κ²½μŸκ°•λ„: {competition_level}
검색엔진: {search_engine}
πŸ” μ‡Όν•‘ν‚€μ›Œλ“œ 뢄석 정보:
{search_info}
🎲 λ‹€μ–‘μ„± 보μž₯ μ°Έκ³  μ‘°ν•© μ˜ˆμ‹œ (이것과 λ‹€λ₯΄κ²Œ μƒμ„±ν•˜μ„Έμš”):
{', '.join(diverse_sample[:10])}
⚠️ ν‚€μ›Œλ“œλ³„ 랜덀 적용 κ·œμΉ™:
각 ν‚€μ›Œλ“œλ§ˆλ‹€ λ‹€μŒκ³Ό 같이 μ μš©ν•˜μ„Έμš”:
{"- μΉ΄ν…Œκ³ λ¦¬: λ§€ ν‚€μ›Œλ“œλ§ˆλ‹€ " + str(random_settings['categories']) + " μ€‘μ—μ„œ 랜덀 선택" if random_settings['category_random'] else f"- μΉ΄ν…Œκ³ λ¦¬: {fixed_values['category']} κ³ μ •"}
{"- μΆœμ‹œνƒ€μ΄λ°: λ§€ ν‚€μ›Œλ“œλ§ˆλ‹€ " + str(random_settings['launch_timings']) + " μ€‘μ—μ„œ 랜덀 선택" if random_settings['launch_timing_random'] else f"- μΆœμ‹œνƒ€μ΄λ°: {fixed_values['launch_timing']} κ³ μ •"}
{"- κ³„μ ˆμ„±: λ§€ ν‚€μ›Œλ“œλ§ˆλ‹€ " + str(random_settings['seasonalities']) + " μ€‘μ—μ„œ 랜덀 선택" if random_settings['seasonality_random'] else f"- κ³„μ ˆμ„±: {fixed_values['seasonality']} κ³ μ •"}
{"- 맀좜λͺ©ν‘œ: λ§€ ν‚€μ›Œλ“œλ§ˆλ‹€ " + str(random_settings['sales_targets']) + " μ€‘μ—μ„œ 랜덀 선택" if random_settings['sales_target_random'] else f"- 맀좜λͺ©ν‘œ: {fixed_values['sales_target']} κ³ μ •"}
{"- νŒλ§€μ±„λ„: λ§€ ν‚€μ›Œλ“œλ§ˆλ‹€ " + str(random_settings['sales_channels']) + " μ€‘μ—μ„œ 랜덀 선택" if random_settings['sales_channel_random'] else f"- νŒλ§€μ±„λ„: {fixed_values['sales_channel']} κ³ μ •"}
{"- κ²½μŸκ°•λ„: λ§€ ν‚€μ›Œλ“œλ§ˆλ‹€ " + str(random_settings['competition_levels']) + " μ€‘μ—μ„œ 랜덀 선택" if random_settings['competition_level_random'] else f"- κ²½μŸκ°•λ„: {fixed_values['competition_level']} κ³ μ •"}
βš™οΈ λ‹€μ–‘μ„± κ°•ν™” μ›Œν¬ν”Œλ‘œμš°
1단계: μ™„μ „νžˆ μƒˆλ‘œμš΄ μ‘°ν•© 생성
- 이전 결과와 μ ˆλŒ€ μ€‘λ³΅λ˜μ§€ μ•ŠλŠ” ν‚€μ›Œλ“œ μ‘°ν•©
- μ†Œμž¬({', '.join(MATERIAL_KEYWORDS[:5])}) + μƒν’ˆλͺ… μ‘°ν•©
- ν˜•νƒœ({', '.join(SHAPE_KEYWORDS[:5])}) + μƒν’ˆλͺ… μ‘°ν•©
- κΈ°λŠ₯({', '.join(FUNCTION_KEYWORDS[:5])}) + μƒν’ˆλͺ… μ‘°ν•©
2단계: 쀑볡 λ°©μ§€ 필터링
- λ™μΌν•œ ν‚€μ›Œλ“œ μ‘°ν•© μ™„μ „ 배제
- μœ μ‚¬ν•œ 의미의 ν‚€μ›Œλ“œ μ‘°ν•© 배제
- 맀번 μƒˆλ‘œμš΄ κ°λ„λ‘œ μ ‘κ·Ό
3단계: 50개 λ‹€μ–‘ν•œ ν‚€μ›Œλ“œ 선별
- λΈŒλžœλ“œλͺ… μ ˆλŒ€ κΈˆμ§€
- λ³΅μž‘ν•œ 기술 μš©μ–΄ κΈˆμ§€
- μ΅œλŒ€ 2개 단어 μ‘°ν•©λ§Œ ν—ˆμš©
⚠️ λ‹€μ–‘μ„± κ°•ν™” ν‚€μ›Œλ“œ ꡬ성 κ·œμΉ™ (맀우 μ€‘μš”):
🚫 μ ˆλŒ€ κΈˆμ§€ 사항:
- λ™μΌν•˜κ±°λ‚˜ μœ μ‚¬ν•œ ν‚€μ›Œλ“œ 반볡
- λΈŒλžœλ“œλͺ… (μ‚Όμ„±, LG, λ‚˜μ΄ν‚€ λ“±)
- λ³΅μž‘ν•œ 기술 μš©μ–΄
- 3개 이상 볡합어
βœ… λ°˜λ“œμ‹œ λ‹€μ–‘ν•˜κ²Œ 포함해야 ν•  ν˜•νƒœ:
1. μ†Œμž¬λ³„ ν‚€μ›Œλ“œ (예: λŒ€λ‚˜λ¬΄ λ„λ§ˆ, ꡬ리 μ»΅)
2. ν˜•νƒœλ³„ ν‚€μ›Œλ“œ (예: μ›ν˜• μ ‘μ‹œ, 슬림 μΌ€μ΄μŠ€)
3. κΈ°λŠ₯별 ν‚€μ›Œλ“œ (예: 방수 파우치, ν•­κ·  수건)
4. μΉ΄ν…Œκ³ λ¦¬λ³„ ν‚€μ›Œλ“œ (예: μˆ˜λ‚©ν•¨, 쑰리도ꡬ)
🎯 λ‹€μ–‘μ„± 보μž₯ μ „λž΅:
- μ ˆλŒ€ 같은 μ†Œμž¬λ₯Ό 2번 이상 μ‚¬μš©ν•˜μ§€ λ§ˆμ„Έμš”
- μ ˆλŒ€ 같은 ν˜•νƒœλ₯Ό 2번 이상 μ‚¬μš©ν•˜μ§€ λ§ˆμ„Έμš”
- μ ˆλŒ€ 같은 κΈ°λŠ₯을 2번 이상 μ‚¬μš©ν•˜μ§€ λ§ˆμ„Έμš”
- λ§€ ν‚€μ›Œλ“œλ§ˆλ‹€ μ™„μ „νžˆ λ‹€λ₯Έ μ‘°ν•©μœΌλ‘œ μƒμ„±ν•˜μ„Έμš”
μ˜¬λ°”λ₯Έ λ‹€μ–‘ν•œ ν‚€μ›Œλ“œ μ˜ˆμ‹œ:
βœ… λŒ€λ‚˜λ¬΄ λ„λ§ˆ (μ†Œμž¬+μƒν’ˆ)
βœ… μ›ν˜• μ ‘μ‹œ (ν˜•νƒœ+μƒν’ˆ)
βœ… 방수 파우치 (κΈ°λŠ₯+μƒν’ˆ)
βœ… 세라믹 λ¨Έκ·Έμ»΅ (μ†Œμž¬+μƒν’ˆ)
βœ… 접이식 μ„ λ°˜ (ν˜•νƒœ+μƒν’ˆ)
βœ… ν•­κ·  수건 (κΈ°λŠ₯+μƒν’ˆ)
잘λͺ»λœ 반볡 ν‚€μ›Œλ“œ μ˜ˆμ‹œ:
❌ λŒ€λ‚˜λ¬΄ λ„λ§ˆ, λŒ€λ‚˜λ¬΄ 젓가락 (μ†Œμž¬ 반볡)
❌ μ›ν˜• μ ‘μ‹œ, μ›ν˜• 쟁반 (ν˜•νƒœ 반볡)
❌ 방수 파우치, 방수 μΌ€μ΄μŠ€ (κΈ°λŠ₯ 반볡)
πŸ“‹ 좜λ ₯ ν˜•μ‹:
였직 μ™„μ „νžˆ λ‹€λ₯Έ μ‡Όν•‘ν‚€μ›Œλ“œλ§Œ ν•œ 쀄씩 50개 좜λ ₯
- 번호 κΈˆμ§€
- μ„€λͺ… κΈˆμ§€
- κΈ°ν˜Έλ‚˜ 특수문자 κΈˆμ§€
- κ΄„ν˜Έ μ•ˆ μ„€λͺ… κΈˆμ§€
- 순수 ν‚€μ›Œλ“œλ§Œ 좜λ ₯
- μ ˆλŒ€ 쀑볡 κΈˆμ§€
μ˜ˆμ‹œ 좜λ ₯ ν˜•νƒœ (맀번 μ™„μ „νžˆ λ‹€λ₯΄κ²Œ):
유리 ν™”λΆ„
접이식 의자
ν•­κ·  λ„λ§ˆ
μ•Œλ£¨λ―ΈλŠ„ ν…€λΈ”λŸ¬
슬림 νŒŒμΌν•¨
⚑ μ§€κΈˆ λ°”λ‘œ μ ˆλŒ€ μ€‘λ³΅λ˜μ§€ μ•ŠλŠ” μ™„μ „νžˆ μƒˆλ‘œμš΄ μ‡Όν•‘ν‚€μ›Œλ“œ 50개λ₯Ό 각각 λ‹€λ₯Έ 랜덀 쑰건을 μ μš©ν•˜μ—¬ 좜λ ₯ν•˜μ„Έμš”.
맀번 μ‹€ν–‰ν•  λ•Œλ§ˆλ‹€ μ™„μ „νžˆ λ‹€λ₯Έ κ²°κ³Όκ°€ λ‚˜μ™€μ•Ό ν•©λ‹ˆλ‹€!