""" Square Theory Generator (Markdown‑only, up to 20 ideas) ===================================================== 2025‑05‑28 v10 ● ImportError fix (OpenAI error module) --------------------------------------------------- 변경 요약 --------- * **문제**: `from openai import OpenAI, error as oai_err` → v1 SDK에는 `error` 서브모듈이 없음 ⇒ `ImportError`. * **해결**: `import openai` 후 `openai.OpenAIError` 참조. `except` 절을 `openai.OpenAIError` 로 교체. 실행법 ------ ```bash pip install --upgrade gradio openai export OPENAI_API_KEY="sk-..." python square_theory_gradio.py ``` """ import os import json import gradio as gr import openai from openai import OpenAI # ------------------------------------------------- # 0. OpenAI 클라이언트 # ------------------------------------------------- if not os.getenv("OPENAI_API_KEY"): raise EnvironmentError("OPENAI_API_KEY 환경 변수를 설정하세요.") client = OpenAI() # ------------------------------------------------- # 1. LLM Prompt & Utilities # ------------------------------------------------- SYSTEM_PROMPT = ( "너는 한국어 카피·브랜드 네이밍 전문가이자 Square Theory 도우미다. " "사용자가 입력한 하나의 단어(tl)를 기반으로 품질이 뛰어난 순서대로 최대 20개의 제안을 JSON 배열로 반환해라. " "**중요**: tl, tr, br, bl 각 필드는 반드시 공백이 없는 **단일 단어**(한글 1~2어절 또는 영어 1단어)여야 한다. " "각 원소는 tl, tr, br, bl, top_phrase, bottom_phrase, slogan, brand 필드를 가진다. " "배열 첫 번째 원소가 가장 우수해야 하며 JSON 외 문자는 금지한다." ) "너는 한국어 카피·브랜드 네이밍 전문가이자 Square Theory 도우미다. " "사용자가 입력한 하나의 단어(tl)를 기반으로 품질이 뛰어난 순서대로 최대 20개의 제안을 JSON 배열로 반환해라. " "각 원소는 tl, tr, br, bl, top_phrase, bottom_phrase, slogan, brand 필드를 가진다. " "배열 첫 번째 원소가 가장 우수해야 한다. JSON 외 문자는 금지한다." ) FALLBACK_MODELS = ["gpt-4o-mini", "gpt-4o", "gpt-4o-preview", "gpt-4-turbo"] def _clean_json_block(text: str) -> str: text = text.strip() if text.startswith("```"): text = text.split("\n", 1)[1] if "\n" in text else text[3:] if text.endswith("```"): text = text[:-3] return text.strip() def _call_llm(seed: str): last_exc = None for model in FALLBACK_MODELS: try: resp = client.chat.completions.create( model=model, messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": seed}, ], temperature=0.9, max_tokens=2048, ) cleaned = _clean_json_block(resp.choices[0].message.content) data = json.loads(cleaned) if cleaned else [] if isinstance(data, dict): data = [data] if not isinstance(data, list) or not (1 <= len(data) <= 20): raise ValueError("LLM 응답이 올바른 1‑20개 리스트가 아님") return data except (openai.OpenAIError, json.JSONDecodeError, ValueError, TypeError) as exc: last_exc = exc continue raise RuntimeError(f"LLM 호출 실패: {last_exc}") # ------------------------------------------------- # 2. Gradio callback # ------------------------------------------------- def generate(seed_word: str): seed_word = seed_word.strip() if not seed_word: return "⚠️ **시드 단어를 입력해 주세요.**" try: results = _call_llm(seed_word) md = [f"## 총 {len(results)}개 제안"] for idx, item in enumerate(results, 1): md.append( f"### {idx}. 브랜드 네임: {item['brand']}\n" f"**메인 카피** \ • 상단: {item['top_phrase']} \ • 하단: {item['bottom_phrase']}\n\n" f"**슬로건** \ > {item['slogan']}\n\n" f"**사각형 키워드** \ TL: {item['tl']} · TR: {item['tr']} · BR: {item['br']} · BL: {item['bl']}\n" ) return "\n".join(md) except Exception as exc: return f"❌ **오류:** {exc}" # ------------------------------------------------- # 3. UI # ------------------------------------------------- with gr.Blocks(title="Square Theory – 최대 20개 🇰🇷") as demo: gr.Markdown("""# 🟧 Square Theory 제안 (최대 20개)\n단어 하나 입력 → LLM이 정렬한 사각형/카피/브랜드 네임""") seed = gr.Textbox(label="시드 단어(TL)", placeholder="예: 골든") run = gr.Button("생성") md_out = gr.Markdown() run.click(generate, inputs=seed, outputs=md_out) demo.queue().launch() # ------------------------------------------------- # 4. (Optional) 추가 기능 아이디어 # ------------------------------------------------- # - JSON에 "why" 필드 요청 — 각 제안의 선정 이유를 표시해 설득력 강화 # - 타깃 채널별 태그라인 변형 옵션(OHH·SNS·TV) # - CSV 다운로드 버튼으로 결과 일괄 저장