Spaces:
Sleeping
Sleeping
| # -*- coding: utf-8 -*- | |
| # Mixed Prompt Composer โ Combo + Free-text + Strong Rationale + RAG(FAISS) | |
| # Gradio 4.x / Hugging Face Spaces ํธํ | |
| import os, re, json | |
| from typing import List, Dict, Tuple | |
| import gradio as gr | |
| # ===== RAG deps ===== | |
| import faiss | |
| import numpy as np | |
| import pandas as pd | |
| from sentence_transformers import SentenceTransformer | |
| from pypdf import PdfReader | |
| from docx import Document as Docx | |
| # =============== ๋ชจ๋ธ ์๋ฐ์ (์ต์ด ๋ก๋ฉ ์ง์ฐ ์ํ) =============== | |
| EMBED_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2" | |
| try: | |
| _WARMUP = SentenceTransformer(EMBED_MODEL_NAME) | |
| except Exception: | |
| _WARMUP = None | |
| # ----------------------------- | |
| # 0) ๋ฐ์ดํฐ์ /๊ธ๋ก์๋ฆฌ | |
| # ----------------------------- | |
| ALL_TECHS = [ | |
| "Persona Prompting","Few-shot Prompting","Self-consistency Prompting","Output Formatting", | |
| "Chain-of-Thought (CoT)","Constrained Prompting","RAG Prompting","Step-back Prompting","Role Prompting", | |
| ] | |
| TECH_GLOSSARY = { | |
| "Persona Prompting": { | |
| "desc": "๋์ ์ญํ /์ธ๊ทธ๋จผํธ์ ์ธ์ดยทKPIยท๊ด์ฌ์ฌ์ ๋ฉ์์ง๋ฅผ ์ ๋ ฌํด ๋ฐ์๋ฅ ยท๊ณต๊ฐ๋๋ฅผ ๋์.", | |
| "purpose": "๋๊ตฌ์๊ฒ ๋งํ๋์ง ๋ถ๋ช ํ ํด ๊ฐ์น๊ฐ โ๊ทธ๋ค์ ์ธ์ดโ๋ก ์ ๋ฌ๋๊ฒ ํจ.", | |
| "mechanics": [ | |
| "์ญํ /๊ถํ/๊ด์ฌ KPI ๋ช ์(์: Sales Leader=ํ์ดํ๋ผ์ธยท์น๋ฅ ยท๋ฆฌ๋ํ์).", | |
| "ํค/๊ธ์น์ด/์ ํธ ํํ ์ ์(์ง์คยท๊ฐ๊ฒฐยท์ซ์/ROI, ๋ชจํธ์ด ๊ธ์ง).", | |
| "ํ๋ฅด์๋๋ณ ๋ฌธ์ฅ ๋งคํ(๋ฌธ์ โ๊ฐ์นโ์ฆ๊ฑฐโCTA)." | |
| ], | |
| "example": "์) โ์ด๋ฒ ๋ถ๊ธฐ ํ์ดํ๋ผ์ธ 18% ๋ณด๊ฐ ์ํด ๋ ๊ฐ์ง ๋น ๋ฅธ ์ก์ ์ ์๋๋ฆฝ๋๋ค.โ" | |
| }, | |
| "Few-shot Prompting": { | |
| "desc": "์์์ ๊ณ ์ฑ๊ณผ ์์๋ฅผ ์ ๊ณตํด ํคยท๊ตฌ์กฐยท๋ฆฌ๋ฌ์ ๋ชจ์ฌ, ํ์ง ํธ์ฐจโยท์๋โ.", | |
| "purpose": "๊ฒ์ฆ๋ ํจํด ๋ณต์ ๋ก ์ผ๊ด์ฑ ํ๋ณด.", | |
| "mechanics": [ | |
| "์ํ 2~3๊ฐ๋ฅผ ํค๋๋ผ์ธ/์คํ๋/๊ทผ๊ฑฐ/CTA ํจํด์ผ๋ก ์ ๊ณต.", | |
| "โ์ด ํค์ ๋ชจ์ฌํด 3๊ฐ ๋ณํโ์ฒ๋ผ ๋ค๋ณ๋ ํ๋ณด ์์ฑ." | |
| ], | |
| "example": "[์ํ] {์ฐ์ } ํ๋ค์ {์ฑ๊ณผ}(์ฌ๋ก). ์ด๋ฒ์ฃผ {์/๋ชฉ} {11:00/16:00} 15๋ถ ํตํ ๊ฐ๋ฅํ์ค๊น์?" | |
| }, | |
| "Self-consistency Prompting": { | |
| "desc": "์ฌ๋ฌ ํ๋ณด์ ์์ฑ ํ ์์ฒด ์ฑ์ (์ฒดํฌ๋ฆฌ์คํธ/์ค์ฝ์ด๋ง)์ผ๋ก ์ต์ ์ ์ ํ.", | |
| "purpose": "A/B ํ ์คํธ ๋ด์ฅ์ผ๋ก ํ์ง ํฅ์.", | |
| "mechanics": [ | |
| "์ ๋ชฉ5ยท๋ฐ๋3ยทCTA3 ๋ฑ ๋ค๋ณ๋ ์์ฑ.", | |
| "์ฒดํฌ๋ฆฌ์คํธ(๊ฐ์ธํ/๋ช ๋ฃ/๊ฐ์น/์คํธํํผ/CTA) ์ฑ์ โTop-1 ์ถ๋ ฅ." | |
| ], | |
| "example": "์ถ๋ ฅ: ํ๋ณด ๋ฆฌ์คํธ + ์ฑ์ ํ + ์ต์ข ์ถ์ฒ 1~2์." | |
| }, | |
| "Output Formatting": { | |
| "desc": "์ฐ์ถ๋ฌผ์ ํ์/์น์ /ํ๋ ๊ณ ์ (ํ/์น์ /JSON ๋ฑ)์ผ๋ก ์๋ยท๊ฐ๋ ์ฑยท์ฌ์ฌ์ฉ์ฑ ํ๋ณด.", | |
| "purpose": "์น์ธ ๋ฃจํ ๋จ์ถ, ํ์คํ.", | |
| "mechanics": [ | |
| "์ ํ๋ณ ํ ํ๋ฆฟ ๊ฐ์ (์ด๋ฉ์ผ/์นดํผ/๋ณด๊ณ ์/PRD/API).", | |
| "ํ์ยท์ ํ ํ๋/๊ธธ์ด/๊ธ์น์ด ๋ช ์." | |
| ], | |
| "example": "์ด๋ฉ์ผ: ์ ๋ชฉ/์คํ๋/๊ฐ์น์ ์/์ฆ๊ฑฐ/CTA/PS" | |
| }, | |
| "Chain-of-Thought (CoT)": { | |
| "desc": "๋ชฉํโ์งํโ๋์โ์ ํโ์คํ์ ๋จ๊ณ์ ์ถ๋ก ์ผ๋ก ๋ ผ๋ฆฌ ๋น์ฝ ์ต์ํ.", | |
| "purpose": "๋ถ์/์ ๋ต ๋ฌธ์์ ๋ ผ๋ฆฌ ์ผ๊ด์ฑ.", | |
| "mechanics": ["๊ฐ ๋จ๊ณ์ ๊ฐ์ /๊ทผ๊ฑฐ/๋์/๋ฆฌ์คํฌ/๊ถ๊ณ ์ฒดํฌ ์ง๋ฌธ ๋ถ์ฌ."], | |
| "example": "์์ฝโํํฉโ๊ณผ์ โ๋ถ์โ์ธ์ฌ์ดํธโ๊ถ๊ณ โํ๊ณ" | |
| }, | |
| "Constrained Prompting": { | |
| "desc": "๊ธธ์ดยท๊ธ์น์ดยทํ์ํ๋ยทJSON ์คํค๋ง ๋ฑ ์ ์ฝ ์ค์.", | |
| "purpose": "๋ธ๋๋/๋ฒ๋ฌด/์ฌ์ ๋ฆฌ์คํฌ ์ต์ํ.", | |
| "mechanics": ["์ ๋ชฉ โค 30์, ๊ธ์น์ด ์ ๊ฑฐ, ํ์ ํค ๋๋ฝ ์ ์ฌ์์ฑ."], | |
| "example": "JSON ์คํค๋ง ์ค์ ์ถ๋ ฅ / ํ์ค ์ฒดํฌ๋ฆฌ์คํธ ํฌํจ" | |
| }, | |
| "RAG Prompting": { | |
| "desc": "์ธ๋ถ ๋ณด๊ณ ์/DB/๋ฌธ์์ ๊ทผ๊ฑฐ ์ฃผ์ ์ผ๋ก ์ต์ ์ฑยท์ ๋ขฐ์ฑ ํ๋ณด.", | |
| "purpose": "์ถ์ /ํ๊ฐ ๋ฐฉ์ง, ์ถ์ฒ ๊ธฐ๋ฐ ์์ .", | |
| "mechanics": ["โ๋ฌธ์์ ์๋ ๋ด์ฉ์ ์ถ์ ๊ธ์ง, ์ถ์ฒ ๋ฉ๋ชจโ ์ง์.", "์ธ์ฉ/๊ฐ์ฃผ/๋งํฌ ํ๊ธฐ."], | |
| "example": "์) Gartner MQ 2024, ๊ณต์ 2024Q3, Crunchbase 2024.07" | |
| }, | |
| "Step-back Prompting": { | |
| "desc": "์์ ๋ชฉ์ /์์น์์ ์ถ๋ฐํด ์๋ฏธ ์ค์ฌ์ผ๋ก ์ฌํด์.", | |
| "purpose": "โ์ ์ค์ํ๊ฐโ์ ๋จผ์ ๋ตํด ๊ฒฝ์ ์์ฌ์ ๊ฐํ.", | |
| "mechanics": ["์์ ๋ชฉํโํต์ฌ ์์นโํ์ฌ ์ ํ ์ ํฉ์ฑ ๊ฒ์ฆ."], | |
| "example": "โ๋น์ฉ 20%โโ๊ฐ ์ ๋ต KPI์ ์ด๋ป๊ฒ ๊ธฐ์ฌํ๋์ง ์ฐ๊ฒฐ" | |
| }, | |
| "Role Prompting": { | |
| "desc": "โ๋น์ ์ PM/์ ๋ต๊ฐ/UX ๋ผ์ดํฐโฆโ์ฒ๋ผ ๊ด์ ๊ณ ์ ์ผ๋ก ๋ชฉ์ ์ ํฉ์ฑโ.", | |
| "purpose": "์ง๋ฌด๋ณ ์ธ์ด/ํ๋จ ๊ธฐ์ค ์ผ์น.", | |
| "mechanics": ["์ญํ ยทKPIยท๊ฒฐ์ ๊ถยท๋ฆฌ์คํฌ ๊ด์ ๋ช ์."], | |
| "example": "PM: ๋ฌธ์ ์ ์/AC/๋ฆฌ์คํฌ | ์ ๋ต: ์ธ์ฌ์ดํธ/๊ถ๊ณ /๊ฑฐ๋ฒ๋์ค" | |
| }, | |
| } | |
| CATALOG = { | |
| "1 ์ธ๋ถ ์ปค๋ฎค๋์ผ์ด์ ": { | |
| "subdomains": ["์ด๋ฉ์ผ(์ฝ๋/์)", "๊ด๊ณ /๋๋ฉ", "PR/๋ณด๋์๋ฃ", "SNS/์์"], | |
| "pains": ["๋ฐ์๋ฅ ์ ์กฐ", "๋ฉ์์ง ๋ถ์ผ์น", "์์ฑ ์๊ฐ ๊ณผ๋ค", "A/B ํ ์คํธ ๋ถ๋ด"], | |
| "outputs": ["์ฝ๋๋ฉ์ผ", "๊ด๊ณ ์นดํผ", "๋ณด๋์๋ฃ", "๋๋ฉํ์ด์ง ์น์ "], | |
| "users": ["์ธ์ผ์ฆ", "๋ง์ผํ ํ", "PRํ", "์ฐฝ์ ์"] | |
| }, | |
| "2 ์์ฅยท๊ณ ๊ฐ ๋ฆฌ์์น": { | |
| "subdomains": ["์์ฅ๋ณด๊ณ ์", "๊ฒฝ์๋ถ์", "VOC ๋ถ์", "ํ๋ฅด์๋"], | |
| "pains": ["๊ฒฝ์์ฌ ์ ๋ณด ๋ถ์กฑ", "๊ณ ๊ฐ ์๊ตฌ ๋ถ๋ช ํ", "์ถ์ฒ/๊ธฐ๊ฐ ๋๋ฝ", "๋ฆฌ์์น ์์ฑ ๋ถ๋ด"], | |
| "outputs": ["์์ฅ์กฐ์ฌ ๋ณด๊ณ ์", "๊ฒฝ์์ฌ ๋ถ์", "ํ๋ฅด์๋", "VOC ์์ฝ"], | |
| "users": ["์ ๋ตํ", "๊ธฐํํ", "์ปจ์คํดํธ", "IR/ํฌ์์ค๋นํ"] | |
| }, | |
| "3 ์ ํยทUX ๋ฌธ์": { | |
| "subdomains": ["PRD/์๊ตฌ์ฌํญ", "์ ์ค์ผ์ด์ค", "UX ๋ง์ดํฌ๋ก์นดํผ", "๋ฆด๋ฆฌ์ค ๋ ธํธ"], | |
| "pains": ["์๊ตฌ์ฌํญ ์ธ์ดํ ๋ํญ", "UX ์นดํผ ๋ถ์ผ์น", "์์ฌ๊ฒฐ์ ๊ธฐ์ค ๋ถ๋ช ํ"], | |
| "outputs": ["PRD", "์ ์ค์ผ์ด์ค", "UX ์นดํผ", "๋ฆด๋ฆฌ์ค ๋ ธํธ"], | |
| "users": ["PM", "UX ๋์์ด๋", "๊ฐ๋ฐํ", "QA"] | |
| }, | |
| } | |
| PAIN_SUB = { | |
| "๋ฐ์๋ฅ ์ ์กฐ": ["์ ๋ชฉ/ํ๋ฆฌํค๋", "ํ๊ฒํ ", "CTA ์ฝํจ", "์ ๋ขฐ ๊ทผ๊ฑฐ ๋ถ์กฑ"], | |
| "๋ฉ์์ง ๋ถ์ผ์น": ["ํค/๋ณด์ด์ค", "ํฌ๋งท/๊ธธ์ด", "์ฑ๋/ํ์ด์ฑ"], | |
| "์์ฑ ์๊ฐ ๊ณผ๋ค": ["ํ ํ๋ฆฟ ๋ถ์ฌ", "์์ ๋ถ์กฑ", "์น์ธ ๋ฃจํ ์ง์ฐ"], | |
| "๊ฒฝ์์ฌ ์ ๋ณด ๋ถ์กฑ": ["์๋ฃ ์์ง", "์ ํฉ์ฑ ๊ฒ์ฆ", "์ต์ ์ฑ"], | |
| "๊ณ ๊ฐ ์๊ตฌ ๋ถ๋ช ํ": ["์ธ๊ทธ๋จผํธ ์ ์", "JTBD ๋ชจํธ", "ํ์ธ/๊ฒ์ธ"], | |
| } | |
| OUTPUT_SUB = { | |
| "์ฝ๋๋ฉ์ผ": ["์ ๊ท ์ธ๋ฐ์ด๋", "์ฝ๋ ์์๋ฐ์ด๋", "ํ์/๋ฆฌ๋ง์ธ๋", "์ดํ ์ฌ์ฐธ์ฌ"], | |
| "๊ด๊ณ ์นดํผ": ["์น ๋ฐฐ๋", "๊ฒ์๊ด๊ณ ", "SNS ์นด๋", "์ฑ ํธ์"], | |
| "์์ฅ์กฐ์ฌ ๋ณด๊ณ ์": ["ํ๋ค์ด(ํ์)", "๋ฐํ ์ ์ถ์ ", "ํผํฉ ์ ๊ทผ", "๋ฆฌ์์น ๋ธ๋ฆฌํ"], | |
| "๊ฒฝ์์ฌ ๋ถ์": ["๊ธฐ๋ฅ ๋น๊ตํ", "๊ฐ๊ฒฉ/ํจํค์ง", "ํฌ์ง์ ๋ ๋งต", "SWOT"], | |
| "PRD": ["๋ฌธ์ ์ ์", "๋ชฉํ/์งํ", "๋ฒ์/๋น๋ฒ์", "์์ฉ๊ธฐ์ค(AC)", "๋ฆฌ์คํฌ"], | |
| } | |
| USER_SUB = { | |
| "์ธ์ผ์ฆ": ["BDR", "AE", "AM", "Sales Leader"], | |
| "๋ง์ผํ ํ": ["ํผํฌ๋จผ์ค", "์ฝํ ์ธ ", "๋ธ๋๋", "๊ทธ๋ก์ค"], | |
| "PRํ": ["์ฝํผ๋ ์ดํธ", "ํ๋ก๋ํธ PR"], | |
| "์ฐฝ์ ์": ["Seed", "Series A+", "๋ถํธ์คํธ๋ฉ"], | |
| "์ ๋ตํ": ["Corp Strategy", "Biz Ops"], | |
| "PM": ["Jr PM", "Sr PM", "Group PM"], | |
| "UX ๋์์ด๋": ["UX Writer", "Product Designer"], | |
| "๊ฐ๋ฐํ": ["FE", "BE", "ML", "Infra"], | |
| "QA": ["QA ์์ง๋์ด", "QA ๋ฆฌ๋"] | |
| } | |
| # ----------------------------- | |
| # ์ ํธ | |
| # ----------------------------- | |
| def uniq(xs: List[str]) -> List[str]: | |
| if not xs: return [] | |
| s=set(); out=[] | |
| for x in xs: | |
| if x not in s: | |
| s.add(x); out.append(x) | |
| return out | |
| AUTO_RULES_PAIN = { | |
| "๋ฐ์๋ฅ ": ["Persona Prompting", "Few-shot Prompting", "Self-consistency Prompting"], | |
| "๋ถ์ผ์น": ["Output Formatting", "Constrained Prompting"], | |
| "์์ฑ": ["Output Formatting", "Few-shot Prompting"], | |
| "๊ฒฝ์": ["RAG Prompting", "Chain-of-Thought (CoT)"], | |
| "์๊ตฌ": ["Persona Prompting", "Step-back Prompting"], | |
| } | |
| AUTO_RULES_OUTPUT = { | |
| "์ด๋ฉ์ผ": ["Persona Prompting", "Output Formatting", "Few-shot Prompting", "Self-consistency Prompting"], | |
| "์นดํผ": ["Few-shot Prompting", "Self-consistency Prompting", "Output Formatting"], | |
| "๋ณด๊ณ ์": ["RAG Prompting", "Chain-of-Thought (CoT)", "Output Formatting"], | |
| "PRD": ["Role Prompting", "Constrained Prompting", "Chain-of-Thought (CoT)"], | |
| } | |
| AUTO_RULES_USER = { | |
| "์ธ์ผ์ฆ": ["Persona Prompting", "Few-shot Prompting", "Self-consistency Prompting"], | |
| "๋ง์ผํ ": ["Few-shot Prompting", "Self-consistency Prompting"], | |
| "์ ๋ต": ["Chain-of-Thought (CoT)", "Step-back Prompting", "RAG Prompting"], | |
| "PM": ["Role Prompting", "Constrained Prompting"], | |
| "๋ฐ์ดํฐ": ["RAG Prompting", "Constrained Prompting"], | |
| } | |
| def auto_recommend(domain_key, pains, outs, users): | |
| rec=[] | |
| if domain_key=="1 ์ธ๋ถ ์ปค๋ฎค๋์ผ์ด์ ": | |
| rec+=["Persona Prompting","Few-shot Prompting","Self-consistency Prompting","Output Formatting"] | |
| if domain_key=="2 ์์ฅยท๊ณ ๊ฐ ๋ฆฌ์์น": | |
| rec+=["RAG Prompting","Chain-of-Thought (CoT)","Output Formatting","Step-back Prompting"] | |
| if domain_key=="3 ์ ํยทUX ๋ฌธ์": | |
| rec+=["Role Prompting","Constrained Prompting","Chain-of-Thought (CoT)","Output Formatting"] | |
| for p in pains or []: | |
| for k,ts in AUTO_RULES_PAIN.items(): | |
| if k in p: rec+=ts | |
| for o in outs or []: | |
| for k,ts in AUTO_RULES_OUTPUT.items(): | |
| if k in o: rec+=ts | |
| for u in users or []: | |
| for k,ts in AUTO_RULES_USER.items(): | |
| if k in u: rec+=ts | |
| rec.append("Output Formatting") | |
| return uniq(rec) | |
| def guess_format_hint(outs: List[str], override: str="") -> str: | |
| if override.strip(): | |
| return override.strip() | |
| j=" ".join(outs or []) | |
| if any(k in j for k in ["PRD","API","ADR","์ ์ฑ ","SOP","์ ์ค์ผ์ด์ค","FAQ"]): return "JSON/ํ/๋ถ๋ฆฟ(ํ๋ ํค ๊ณ ์ )" | |
| if any(k in j for k in ["๋ณด๊ณ ์","๋ธ๋ฆฌํ","์์ฝ","๋ฌธ์","1-Pager"]): return "์์ฝ/ํํฉ/๊ฒฝ์/์ธ์ฌ์ดํธ/๊ถ๊ณ /ํ๊ณ" | |
| if any(k in j for k in ["์นดํผ","๊ด๊ณ ","๋๋ฉ"]): return "ํค๋๋ผ์ธ/์๋ธํค๋/๋ฐ๋/CTA" | |
| if any(k in j for k in ["์ด๋ฉ์ผ","๋ฉ์ผ","์ฝ๋๋ฉ์ผ"]): return "์ ๋ชฉ/์คํ๋/๊ฐ์น์ ์/์ฆ๊ฑฐ/CTA/PS" | |
| return "๋ชฉ์ฐจ/์์ฝ/๋ณธ๋ฌธ/๊ถ๊ณ /CTA" | |
| # ----------------------------- | |
| # Rationale(๊ฐํํ) | |
| # ----------------------------- | |
| def reason_from_pain(tech: str, pains: List[str], pain_subs: List[str]) -> List[str]: | |
| R=[] | |
| jp=" ".join(pains or []) + " " + " ".join(pain_subs or []) | |
| if "๋ฐ์๋ฅ " in jp: | |
| if tech=="Persona Prompting": R.append("๋ฐ์๋ฅ ์ ์กฐ โ ์ธ๊ทธ๋จผํธ ๋ง์ถค ์ดํ/์ด์กฐ๋ก ์ฒด๊ฐ ๊ฐ์น ์์น") | |
| if tech=="Few-shot Prompting": R.append("๋ฐ์๋ฅ ์ ์กฐ โ ๊ณ ์ฑ๊ณผ ์์ ํจํด ๋ณต์ ") | |
| if tech=="Self-consistency Prompting": R.append("๋ฐ์๋ฅ ์ ์กฐ โ ๋ค๋ณ๋ ํ๋ณด ์์ฑโ์์ฒด ์ฑ์ ์ผ๋ก Top-1") | |
| if "๋ถ์ผ์น" in jp or "ํค/๋ณด์ด์ค" in jp or "ํฌ๋งท" in jp: | |
| if tech=="Output Formatting": R.append("๋ฉ์์ง ๋ถ์ผ์น โ ํ์/์น์ ๊ณ ์ ์ผ๋ก ์ ํฉ์ฑโ") | |
| if tech=="Constrained Prompting": R.append("๋ฉ์์ง ๋ถ์ผ์น โ ๊ธ์น/๊ธธ์ด/ํ์ํ๋ ๊ฐ์ ") | |
| if "์์ฑ ์๊ฐ" in jp or "์์ ๋ถ์กฑ" in jp: | |
| if tech in {"Few-shot Prompting","Output Formatting"}: R.append("์์ฑ์๊ฐ ๊ณผ๋ค/์์ ๋ถ์กฑ โ ํ ํ๋ฆฟ + ์์ ๊ธฐ๋ฐ ์๋โ") | |
| if "๊ฒฝ์์ฌ ์ ๋ณด" in jp: | |
| if tech=="RAG Prompting": R.append("๊ฒฝ์์ฌ ์ ๋ณด ๋ถ์กฑ โ ์ธ๋ถ ๋ฐ์ดํฐ ๊ทผ๊ฑฐ ์ฃผ์ (์ต์ ์ฑ/์ ๋ขฐ์ฑ)") | |
| if tech=="Chain-of-Thought (CoT)": R.append("๊ฒฝ์์ฌ ๋ถ์ ๊ตฌ์กฐํ(CoT)๋ก ์ธ์ฌ์ดํธ ๋ช ๋ฃํ") | |
| if "์๊ตฌ ๋ถ๋ช ํ" in jp: | |
| if tech in {"Persona Prompting","Step-back Prompting"}: R.append("๊ณ ๊ฐ ์๊ตฌ ๋ถ๋ช ํ โ ์์ ๋ชฉ์ /JTBD ์ ๋ ฌ") | |
| return R | |
| def reason_from_output(tech: str, outs: List[str], out_subs: List[str]) -> List[str]: | |
| R=[] | |
| jo=" ".join(outs or []) + " " + " ".join(out_subs or []) | |
| if "๋ฉ์ผ" in jo or "์นดํผ" in jo or "๋๋ฉ" in jo: | |
| if tech=="Output Formatting": R.append("์นดํผ/๋ฉ์ผ โ ํค๋๋ผ์ธ/์๋ธ/๋ฐ๋/CTA ๊ณ ์ ์ด ์ฑ๊ณผ ์ข์ฐ") | |
| if tech=="Self-consistency Prompting": R.append("๋ฉ์์ง ํ๋ณด ๋ค๋ณ๋ ์์ฑโ์ต์ ์ ์ ํ ํ์") | |
| if tech=="Few-shot Prompting": R.append("์ฑ๋๋ณ ํค/๊ธธ์ด ์ฐจ์ด๋ฅผ ์์๋ก ๋น ๋ฅด๊ฒ ์ ์") | |
| if "๋ณด๊ณ ์" in jo or "๋ถ์" in jo: | |
| if tech=="RAG Prompting": R.append("๋ณด๊ณ ์/๋ถ์ โ ์์นยท์ถ์ฒ ์ต์ ์ฑ ๋ณด์ฅ") | |
| if tech=="Chain-of-Thought (CoT)": R.append("๋ณด๊ณ ์/๋ถ์ โ ๋จ๊ณ์ ๊ตฌ์กฐ(CoT)๋ก ๋ ผ๋ฆฌ ๊ฐํ") | |
| if "PRD" in jo or "API" in jo: | |
| if tech in {"Constrained Prompting","Role Prompting"}: R.append("PRD/API โ JSON/ํ๋ ๊ฐ์ + ์ง๋ฌด ๊ด์ ๊ณ ์ ํ์") | |
| return R | |
| def reason_from_user(tech: str, users: List[str], user_subs: List[str]) -> List[str]: | |
| R=[] | |
| ju=" ".join(users or []) + " " + " ".join(user_subs or []) | |
| if "์ธ์ผ์ฆ" in ju: | |
| if tech=="Persona Prompting": R.append("Sales๋ ํ์ดํ๋ผ์ธ/์น๋ฅ ์ธ์ด ์ ํธ โ ํ๋ฅด์๋ ํค ์ ์ฉ") | |
| if tech=="Few-shot Prompting": R.append("์์ ๋ ํผ๋ฐ์ค ์ฌ๋ก ๋ชจ์ฌ๊ฐ ์ค๋๋ ฅโ") | |
| if "์ ๋ต" in ju: | |
| if tech in {"Chain-of-Thought (CoT)","Step-back Prompting","RAG Prompting"}: | |
| R.append("์ ๋ต์กฐ์ง์ ๊ทผ๊ฑฐ/๋ ผ๋ฆฌ/์์ฌ์ ์ค์ โ CoT+RAG+Step-back ์ ํฉ") | |
| if "PM" in ju or "UX" in ju: | |
| if tech in {"Role Prompting","Constrained Prompting"}: | |
| R.append("PM/UX๋ ํ๋/AC/ํค ํ์ค ํ์ โ ์ญํ ๊ณ ์ + ์ ์ฝ ๊ฐ์ ") | |
| return R | |
| def domain_reco(tech: str, outs: List[str]) -> str: | |
| j=" ".join(outs or []) | |
| if tech=="Output Formatting": | |
| if "๋ฉ์ผ" in j: return "์ด๋ฉ์ผ: ์ ๋ชฉ/์คํ๋/๊ฐ์น์ ์/์ฆ๊ฑฐ/CTA/PS ํ์ ๊ณ ์ " | |
| if "์นดํผ" in j or "๋๋ฉ" in j: return "์นดํผ/๋๋ฉ: ํค๋๋ผ์ธ/์๋ธ/๋ฐ๋/CTA ๋ธ๋ก ๊ณ ์ " | |
| if "๋ณด๊ณ ์" in j: return "๋ณด๊ณ ์: ์์ฝ/ํํฉ/๊ฒฝ์/์ธ์ฌ์ดํธ/๊ถ๊ณ /ํ๊ณ ์น์ ๊ณ ์ " | |
| if "PRD" in j: return "PRD: ํ์ ํ๋(JSON/ํ) ๊ฐ์ " | |
| return "์ฐ์ถ๋ฌผ ํ์ค ์น์ ๊ณ ์ " | |
| if tech=="Persona Prompting": return "์ธ๊ทธ๋จผํธยท์ญํ KPI ์ธ์ด ์ ๋ ฌ" | |
| if tech=="Few-shot Prompting": return "๊ณ ์ฑ๊ณผ ์์ ๊ตฌ์กฐ/ํค ๋ชจ์ฌ" | |
| if tech=="Self-consistency Prompting": return "๋ค์ค ํ๋ณด ์์ฑโ์์ฒด ์ฑ์ โTop-1" | |
| if tech=="Constrained Prompting": return "๊ธธ์ด/๊ธ์น์ด/ํ์ํ๋ ๊ฐ์ " | |
| if tech=="RAG Prompting": return "์ธ๋ถ ๋ฆฌํฌํธ/๋ฐฑ์ ๊ทผ๊ฑฐ ์ฃผ์ " | |
| if tech=="Chain-of-Thought (CoT)": return "๋ชฉํโ์งํโ๋์โ๊ถ๊ณ ๋จ๊ณํ" | |
| if tech=="Step-back Prompting": return "์์ ๋ชฉ์ /์์น์์ ์๋ฏธ ์ฌํด์" | |
| if tech=="Role Prompting": return "์ญํ ๊ณ ์ ์ผ๋ก ๊ด์ ์ผ์น" | |
| return "-" | |
| def build_rationale_detailed( | |
| tech: str, domain_key: str, subdomains: List[str], | |
| pains: List[str], pain_subs: List[str], | |
| outs: List[str], out_subs: List[str], | |
| users: List[str], user_subs: List[str] | |
| ) -> str: | |
| if not tech: return "" | |
| g = TECH_GLOSSARY.get(tech, {}) | |
| desc, purpose, mechs, example = g.get("desc",""), g.get("purpose",""), g.get("mechanics",[]), g.get("example","") | |
| R = [] | |
| R += reason_from_pain(tech, pains, pain_subs) | |
| R += reason_from_output(tech, outs, out_subs) | |
| R += reason_from_user(tech, users, user_subs) | |
| R = uniq(R) | |
| parts = [ | |
| f"## {tech}", | |
| f"**์ค๋ช **: {desc}", | |
| f"- **์ ์ฉ ๋ชฉ์ **: {purpose}" if purpose else "", | |
| "- **์๋ ๋ฐฉ์**:\n - " + "\n - ".join(mechs) if mechs else "", | |
| f"- **๋๋ฉ์ธ ๊ถ์ฅ**: {domain_reco(tech, outs)}", | |
| f"- **์ ์ ๊ทผ๊ฑฐ(์ ํ ๋ฐ์)**:\n - " + "\n - ".join(R) if R else "- **์ ์ ๊ทผ๊ฑฐ**: (์ ํ ํญ๋ชฉ์ ๋ฐ๋ผ ์๋ ์์ฑ)", | |
| f"- **์์**: {example}" | |
| ] | |
| return "\n".join([p for p in parts if p.strip()]) | |
| # ----------------------------- | |
| # RAG: ์ ๋ก๋โ์ฒญํนโ์๋ฒ ๋ฉโFAISSโ๊ฒ์ | |
| # ----------------------------- | |
| _model = None | |
| _faiss = None | |
| _chunks = [] # [{id, text, meta}] | |
| _dim = 384 | |
| def get_model(): | |
| global _model | |
| if _model is None: | |
| _model = SentenceTransformer(EMBED_MODEL_NAME) | |
| return _model | |
| def embed_texts(texts: List[str]) -> np.ndarray: | |
| model = get_model() | |
| vecs = model.encode(texts, normalize_embeddings=True) | |
| return np.array(vecs, dtype="float32") | |
| def extract_text(path: str) -> Tuple[str, Dict]: | |
| name = os.path.basename(path) | |
| ext = os.path.splitext(path)[1].lower() | |
| meta = {"source": name} | |
| if ext == ".pdf": | |
| reader = PdfReader(path) | |
| pages = [] | |
| for i, p in enumerate(reader.pages): | |
| try: pages.append(p.extract_text() or "") | |
| except: pages.append("") | |
| return "\n".join(pages), meta | |
| if ext == ".docx": | |
| d = Docx(path) | |
| return "\n".join(p.text for p in d.paragraphs), meta | |
| if ext == ".csv": | |
| df = pd.read_csv(path, dtype=str).fillna("") | |
| return df.to_csv(index=False), meta | |
| if ext == ".txt": | |
| with open(path, "r", encoding="utf-8", errors="ignore") as f: | |
| return f.read(), meta | |
| raise ValueError("์ง์ ํ์ฅ์: pdf/docx/csv/txt") | |
| def chunk_text(t: str, chunk=800, overlap=200) -> List[str]: | |
| t = re.sub(r"\s+", " ", (t or "")).strip() | |
| if not t: return [] | |
| out=[]; s=0 | |
| while s < len(t): | |
| e=min(len(t), s+chunk) | |
| out.append(t[s:e]) | |
| if e==len(t): break | |
| s=max(0, e-overlap) | |
| return out | |
| def build_index(files, chunk=800, overlap=200): | |
| global _faiss, _chunks, _dim | |
| _chunks=[] | |
| all_texts=[] | |
| for f in files or []: | |
| txt, meta = extract_text(f.name) | |
| cks = chunk_text(txt, chunk, overlap) | |
| for ci, c in enumerate(cks): | |
| _chunks.append({"id": len(_chunks), "text": c, "meta": {"source": meta["source"], "chunk_id": ci}}) | |
| all_texts.append(c) | |
| if not all_texts: | |
| return "โ ๏ธ ์ถ์ถ ํ ์คํธ๊ฐ ์์ต๋๋ค." | |
| vecs = embed_texts(all_texts) | |
| _dim = vecs.shape[1] | |
| _faiss = faiss.IndexFlatIP(_dim) | |
| _faiss.add(vecs) | |
| return f"โ ์ธ๋ฑ์ค ๊ตฌ์ถ ์๋ฃ ยท ์ฒญํฌ {len(all_texts)}๊ฐ ยท dim={_dim}" | |
| def search_index(query: str, k=5) -> List[Dict]: | |
| if _faiss is None or not _chunks: return [] | |
| qv = embed_texts([query]) | |
| scores, idxs = _faiss.search(qv, k) | |
| res=[] | |
| for rank, (i, s) in enumerate(zip(idxs[0].tolist(), scores[0].tolist()), 1): | |
| if 0<=i<len(_chunks): | |
| item = _chunks[i] | |
| res.append({"rank": rank, "score": float(s), | |
| "text": item["text"], "source": item["meta"]["source"], "chunk_id": item["meta"]["chunk_id"]}) | |
| return res | |
| def make_context_block(query: str, k=5) -> Tuple[str, str]: | |
| hits = search_index(query, k) | |
| if not hits: return "(no RAG context)", "(no sources)" | |
| ctx_lines=[]; srcs=[] | |
| for h in hits: | |
| ctx_lines.append(f"[{h['rank']}|{h['source']}|#{h['chunk_id']}|{h['score']:.3f}] {h['text']}") | |
| srcs.append(f"{h['rank']}. {h['source']} (chunk {h['chunk_id']})") | |
| return "\n\n".join(ctx_lines), "\n".join(srcs) | |
| # ----------------------------- | |
| # ์ต์ข ํ ํ๋ฆฟ/ํ๋กฌํํธ | |
| # ----------------------------- | |
| TEMPLATE = """# ๋ชฉ์ (Purpose) | |
| - ์ฐ๋ฆฌ๋ [{domain}]์์ [{out}]์ ์ ์ยท์ ํยท์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค๊ณ ์ ํ๋ค. | |
| - ์ต์ข ๋ ์: [{user}] | ํด๊ฒฐํ Pain: [{pain}] | |
| # ์ ํ ์ปจํ ์คํธ(์์ ์ ๋ ฅ ํฌํจ) | |
| - ์ธ๋ถ ๋๋ฉ์ธ: {subdomain} | |
| - ๋๋ฉ์ธ ๋ฉ๋ชจ: {domain_note} | |
| - Pain ์ธ๋ถ/๋ฉ๋ชจ: {pain_sub} | {pain_note} | |
| - Output ์ธ๋ถ/์คํ: {out_sub} | {out_note} | |
| - User ์ธ๋ถ/๋ฉ๋ชจ: {user_sub} | {user_note} | |
| # ์ฐ์ถ๋ฌผ ์ ์(What to produce) | |
| - ์ฐ์ถ๋ฌผ ์ข ๋ฅ: [{out}] | |
| - ์ฌ์ฉ ๋งฅ๋ฝ/๋ชฉํ KPI: [{kpi}] | |
| - ์ฑ๊ณต ๊ธฐ์ค(ํต๊ณผ ์กฐ๊ฑด): | |
| 1) [{format_hint}]์ 100% ์ค์ | |
| 2) [{audience}]์๊ฒ ๊ฐ์น๊ฐ ์ฆ์ ๋ณด์ | |
| 3) ์คํธ/๊ณผ์ฅ/์ ๋งค์ด ์์ | |
| # ํ์/ํค ๊ฐ๋๋ ์ผ(Format & Tone) | |
| - ํ์: [{format_hint}] | |
| - ํค: [{tone}] | ๊ธ์ง: [{ng}] | |
| # ํผํฉ ํ๋กฌํํ ๊ธฐ์ (Why these techniques) | |
| {tech_blocks} | |
| {rag_section} | |
| # ์์ฑ ์์ (Tasks) | |
| 1) **์ด์ v1**: [{format_hint}]์ ๋ง์ถ ๋ณธ๋ฌธ ์์ฑ | |
| 2) **๋์ ์์ฑ**: ํต์ฌ ๋ฌธ๊ตฌ/์ ๋ชฉ/CTA ํ๋ณด N๊ฐ ์์ฑ | |
| 3) **์์ฒด ๊ฒ์ฆ**: ์คํธ์ด/๊ธ์น์ด/๊ธธ์ด/๊ฐ์ธํ ๋ณ์ ์ฒดํฌ๋ฆฌ์คํธ ํต๊ณผ | |
| 4) **์์ฝ v2**: 5์ค ์์ฝ(๋ฌธ์ โ๊ฐ์นโ์ฆ๊ฑฐโCTAโ๋ค์ ์ก์ ) | |
| # ์ถ๋ ฅ ํ์(Output) | |
| - ์น์ ๋ณ๋ก ๊ตฌ๋ถํด ๋งํฌ๋ค์ด์ผ๋ก ์ถ๋ ฅ(์ ๋ชฉ, ์คํ๋, ๊ฐ์น์ ์, ์ฆ๊ฑฐ, CTA, PS ๋ฑ) | |
| - ๋ง์ง๋ง: **๊ฒ์ฆ ์ฒดํฌ๋ฆฌ์คํธ**(โก ๊ฐ์ธํ ํ๋ โก ๊ธ์น์ด ์์ โก ๊ธธ์ด ์ค์ โก ๊ฐ์น/์ฆ๊ฑฐ/CTA ๋ช ํ) | |
| - **๋ค์ ์ก์ 3๊ฐ์ง**(์: ์บ๋ฆฐ๋ ๋งํฌ, ์ฌ๋ก PDF, 2์ฐจ ์ฐ๋ฝ ์ค์ผ์ค) | |
| """ | |
| def compose_final_prompt( | |
| domain_key, subdomains, pains, pain_subs, outs, out_subs, users, user_subs, techs, | |
| domain_note, pain_note, out_note, user_note, kpi_text, tone_text, ng_text, format_override, | |
| fewshot_text, rag_text, | |
| use_rag, rag_topk | |
| ): | |
| if not domain_key or not outs or not users or not techs: | |
| return "โ ๏ธ โ๊ตฌ๋ถ/Output/User/๊ธฐ์ โ์ ์ ํํ์ธ์." | |
| format_hint = guess_format_hint(outs, format_override) | |
| audience = ", ".join(user_subs or users) | |
| kpi = kpi_text.strip() or ("์คํ์จ/CTR/์๋ตยท๋ฏธํ ์" if "์ธ๋ถ" in domain_key else "์ ํ์ฑ/๊ทผ๊ฑฐ/๊ฐ๋ ์ฑ") | |
| tone = tone_text.strip() or ("์ง์ ์ ยท๊ฐ๊ฒฐยทROI ์ค์ฌ" if "์ธ์ผ์ฆ" in " ".join(users or []) else "๋ช ๋ฃยท๊ฐ๊ดยท๊ฐ๊ฒฐ") | |
| ng = ng_text.strip() or "๊ณผ์ฅยท๊ทผ๊ฑฐ ์๋ ์์นยท๋ชจํธํ ํํ" | |
| blocks=[] | |
| for t in techs: | |
| reasons = uniq(reason_from_pain(t, pains, pain_subs) + | |
| reason_from_output(t, outs, out_subs) + | |
| reason_from_user(t, users, user_subs)) | |
| head = f"- **{t}** โ {TECH_GLOSSARY.get(t,{}).get('desc','')}" | |
| tail = "" | |
| if reasons: | |
| tail = "\n - ์ ์ ๊ทผ๊ฑฐ: " + " / ".join(reasons[:4]) | |
| dom = domain_reco(t, outs) | |
| if dom: tail += f"\n - ๋๋ฉ์ธ ๊ถ์ฅ: {dom}" | |
| blocks.append((head + tail).rstrip()) | |
| rag_section = "" | |
| if use_rag: | |
| query = f"{' '.join(outs)} | {' '.join(out_subs or [])} | {' '.join(users)} | {' '.join(pains)} | {pain_note} | {out_note}" | |
| ctx, srcs = make_context_block(query, k=int(rag_topk)) | |
| rag_section = f"""# RAG ์ปจํ ์คํธ | |
| - ์ง์: {query} | |
| - Top-{rag_topk} ์ปจํ ์คํธ: | |
| {ctx} | |
| - ์ถ์ฒ: | |
| {srcs} | |
| - ์ง์: **์ปจํ ์คํธ ๋ฒ์ ๋ด์์๋ง** ์์ ํ๊ณ , ๋ฌธ์์ ์๋ ๋ด์ฉ์ โ๊ทผ๊ฑฐ ์์โ์ผ๋ก ๋ช ์.""" | |
| else: | |
| if rag_text.strip(): | |
| rag_section = f"# ์ฐธ๊ณ ์๋ฃ ๋ฉ๋ชจ(์๊ธฐ)\n{rag_text.strip()}" | |
| if fewshot_text.strip(): | |
| blocks.append(f"- **Few-shot ์์**: {fewshot_text.strip()}") | |
| return TEMPLATE.format( | |
| domain=domain_key, | |
| out=", ".join(outs), | |
| user=", ".join(users) + (" / " + ", ".join(user_subs) if user_subs else ""), | |
| pain=", ".join(pains) + (" / " + ", ".join(pain_subs) if pain_subs else ""), | |
| subdomain=", ".join(subdomains or ["-"]), | |
| domain_note=domain_note or "-", | |
| pain_sub=", ".join(pain_subs or ["-"]), | |
| pain_note=pain_note or "-", | |
| out_sub=", ".join(out_subs or ["-"]), | |
| out_note=out_note or "-", | |
| user_sub=", ".join(user_subs or ["-"]), | |
| user_note=user_note or "-", | |
| kpi=kpi, | |
| audience=audience, | |
| format_hint=format_hint, | |
| tone=tone, | |
| ng=ng, | |
| tech_blocks="\n\n".join(blocks), | |
| rag_section=rag_section or "" | |
| ) | |
| # ----------------------------- | |
| # ์ฌ๋ฌ ๊ธฐ์ ์ค๋ช ํ๊บผ๋ฒ์ ๋ณด๊ธฐ | |
| # ----------------------------- | |
| def render_multi_rationales(tech_list: List[str], | |
| domain_key, subdomains, pains, pain_subs, outs, out_subs, users, user_subs): | |
| if not tech_list: | |
| return "๊ธฐ์ ์ ํ๋ ์ด์ ์ ํํ์ธ์." | |
| sections=[] | |
| for t in tech_list: | |
| sec = build_rationale_detailed(t, domain_key, subdomains, pains, pain_subs, outs, out_subs, users, user_subs) | |
| if sec: | |
| sections.append(sec) | |
| return ("\n\n---\n\n".join(sections)).strip() | |
| # ----------------------------- | |
| # UI | |
| # ----------------------------- | |
| DEFAULT_DOMAIN = "2 ์์ฅยท๊ณ ๊ฐ ๋ฆฌ์์น" | |
| D = CATALOG[DEFAULT_DOMAIN] | |
| with gr.Blocks(title="Mixed Prompt Composer โ Rationale + RAG + Multi Preview") as demo: | |
| gr.Markdown("## ์ตํฉ ํ๋กฌํํ โ ์ฝค๋ณด + ์์ ์ ๋ ฅ + **๊ฐํ Rationale** + **RAG(FAISS)** + **์ฌ๋ฌ ๊ธฐ์ ํ๊บผ๋ฒ์ ๋ฏธ๋ฆฌ๋ณด๊ธฐ**") | |
| # 1) ๋๋ฉ์ธ/์ธ๋ถ + ์์ ์ ๋ ฅ | |
| with gr.Row(): | |
| domain = gr.Dropdown(label="๊ตฌ๋ถ(๋๋ถ๋ฅ)", choices=list(CATALOG.keys()), value=DEFAULT_DOMAIN) | |
| subdomain = gr.Dropdown(label="์ธ๋ถ ๋๋ฉ์ธ(๋ณต์ ์ ํ)", choices=D["subdomains"], multiselect=True, value=["์์ฅ๋ณด๊ณ ์"]) | |
| domain_note = gr.Textbox(label="๋๋ฉ์ธ ๋ฉ๋ชจ(์์ ์ ๋ ฅ)", placeholder="์: ๋ถ๋ฏธ SaaS B2B ์ค์ฌ, ์ต์ ๋ถ๊ธฐ ๊ธฐ์ค") | |
| # 2) Pain + ์์ ์ ๋ ฅ | |
| with gr.Row(): | |
| pains = gr.Dropdown(label="Pain Points(๋ณต์ ์ ํ)", choices=D["pains"], multiselect=True, value=["๊ฒฝ์์ฌ ์ ๋ณด ๋ถ์กฑ"]) | |
| pain_detail = gr.Dropdown(label="Pain ์ธ๋ถ(๋ณต์ ์ ํ)", choices=PAIN_SUB["๊ฒฝ์์ฌ ์ ๋ณด ๋ถ์กฑ"], multiselect=True, value=["์๋ฃ ์์ง"]) | |
| pain_note = gr.Textbox(label="Pain ๋ฉ๋ชจ(์์ ์ ๋ ฅ)", placeholder="์: ์ ๋ฃ ๋ฆฌํฌํธ ์ ๊ทผ ์ ํ, 2024 Q3 ๋ฐ์ดํฐ ํ์") | |
| # 3) Output + ์์ ์ ๋ ฅ | |
| with gr.Row(): | |
| outs = gr.Dropdown(label="Outputs(๋ณต์ ์ ํ)", choices=D["outputs"], multiselect=True, value=["์์ฅ์กฐ์ฌ ๋ณด๊ณ ์"]) | |
| out_detail = gr.Dropdown(label="Output ์ธ๋ถ(๋ณต์ ์ ํ)", choices=OUTPUT_SUB["์์ฅ์กฐ์ฌ ๋ณด๊ณ ์"], multiselect=True, value=["๋ฆฌ์์น ๋ธ๋ฆฌํ"]) | |
| out_note = gr.Textbox(label="Output ์คํ(์์ ์ ๋ ฅ)", placeholder="์: 8~10p, ํ/๊ทธ๋ํ 4๊ฐ, ๊ฒฝ์ 5์ฌ, ์ถ์ฒ ๊ฐ์ฃผ ํ์") | |
| # 4) User + ์์ ์ ๋ ฅ | |
| with gr.Row(): | |
| users = gr.Dropdown(label="Users(๋ณต์ ์ ํ)", choices=D["users"], multiselect=True, value=["์ ๋ตํ"]) | |
| user_detail = gr.Dropdown(label="User ์ธ๋ถ(๋ณต์ ์ ํ)", choices=USER_SUB["์ ๋ตํ"], multiselect=True, value=["Corp Strategy"]) | |
| user_note = gr.Textbox(label="User ๋ฉ๋ชจ(์์ ์ ๋ ฅ)", placeholder="์: ๊ฒฝ์์ง ๋ธ๋ฆฌํ์ฉ 1-pager ์์ฝ ์ถ๊ฐ ํ์") | |
| # 5) ์๋ ์ถ์ฒ & ๊ธฐ์ ์ ํ | |
| with gr.Row(): | |
| auto_btn = gr.Button("๐ฎ Mixed Prompts ์๋ ์ถ์ฒ") | |
| techs = gr.Dropdown(label="Mixed Prompts(๋ณต์ ์ ํ/์์ ๊ฐ๋ฅ)", choices=ALL_TECHS, multiselect=True) | |
| # 6) ๊ณ ๊ธ ์ค์ | |
| with gr.Accordion("๊ณ ๊ธ ์ค์ (์ค๋ฒ๋ผ์ด๋ & ์์/๊ทผ๊ฑฐ)", open=False): | |
| with gr.Row(): | |
| kpi_text = gr.Textbox(label="KPI(์ค๋ฒ๋ผ์ด๋)", placeholder="์: ์ธ์ฌ์ดํธ ์ ํ์ฑ, ๊ฒฝ์์ง ์์ฌ๊ฒฐ์ ์ง์") | |
| tone_text = gr.Textbox(label="ํค(์ค๋ฒ๋ผ์ด๋)", placeholder="์: ๊ฐ๊ดยท๊ฐ๊ฒฐยท๋ฐ์ดํฐ ์ค์ฌ") | |
| with gr.Row(): | |
| ng_text = gr.Textbox(label="๊ธ์ง์ด/NG(์ค๋ฒ๋ผ์ด๋)", placeholder="์: ๊ณผ์ฅ, ์ถ์ฒ ๋ฏธํ๊ธฐ, ์ถ์ ์์น ๋จ์ ํ") | |
| format_override = gr.Textbox(label="ํ์(์ค๋ฒ๋ผ์ด๋)", placeholder="์: ์์ฝ/ํํฉ/๊ฒฝ์/์ธ์ฌ์ดํธ/๊ถ๊ณ /ํ๊ณ") | |
| fewshot_text = gr.Textbox(label="Few-shot ์์(์์ ์ ๋ ฅ)", lines=4, placeholder="[์ํ] ์ ๋ชฉ/์คํ๋/๊ทผ๊ฑฐ/CTAโฆ") | |
| rag_text = gr.Textbox(label="์ฐธ๊ณ ์๋ฃ ๋ฉ๋ชจ(์๊ธฐ RAG ๋์ฒด/๋ณด์)", lines=3) | |
| # 7) ์ฌ๋ฌ ๊ธฐ์ โํ๊บผ๋ฒ์โ ์์ธ ๋ฏธ๋ฆฌ๋ณด๊ธฐ | |
| with gr.Accordion("๐ ์ ํํ ํ๋กฌํํธ ๊ธฐ์ โ ์ค๋ช & Rationale (๋ณต์ ํผ์ณ๋ณด๊ธฐ)", open=True): | |
| with gr.Row(): | |
| tech_preview = gr.Dropdown(label="๋ฏธ๋ฆฌ๋ณผ ๊ธฐ์ (๋ณต์ ์ ํ)", choices=ALL_TECHS, multiselect=True) | |
| from_selected_btn = gr.Button("ํ์ฌ Mixed Prompts ์ ์ฒด ์ค๋ช ๋ณด๊ธฐ") | |
| rationale_md = gr.Markdown("์ฌ๋ฌ ๊ธฐ์ ์ ์ ํํ๋ฉด **์ค๋ช & ์ ์ ๊ทผ๊ฑฐ**๋ฅผ ํ๊บผ๋ฒ์ ํผ์นฉ๋๋ค.") | |
| # 8) RAG ์ ๋ก๋/์ธ๋ฑ์ฑ | |
| gr.Markdown("### ๐ RAG โ ํ์ผ ์ ๋ก๋ โ ์ธ๋ฑ์ฑ โ ์ตํฉ ํ๋กฌํํ ์๋ ์ฃผ์ ") | |
| with gr.Row(): | |
| rag_files = gr.Files(label="๋ฌธ์ ์ ๋ก๋(pdf/docx/csv/txt ๋ณต์)", file_count="multiple", file_types=[".pdf",".docx",".csv",".txt"]) | |
| build_btn = gr.Button("๐จ ์ธ๋ฑ์ค ๊ตฌ์ถ") | |
| rag_status = gr.Markdown("์ํ: ์ธ๋ฑ์ค ์์") | |
| with gr.Row(): | |
| use_rag = gr.Checkbox(label="RAG ์ฌ์ฉ", value=False) | |
| rag_topk = gr.Slider(1,10,value=5,step=1,label="RAG Top-K") | |
| # 9) ์ต์ข ํ๋กฌํํธ | |
| gen_btn = gr.Button("๐ ๊ตฌ์กฐํ๋ ์ตํฉ ํ๋กฌํํ ์์ฑ") | |
| final_box = gr.Textbox(label="์ต์ข ์ตํฉ ํ๋กฌํํธ (๋ณต์ฌํ์ฌ Gemini/Claude/Perplexity/OpenAI์ ์ฌ์ฉ)", lines=28, show_copy_button=True) | |
| # ===== ์ด๋ฒคํธ ๋ฐ์ธ๋ฉ ===== | |
| def on_domain_change(dkey): | |
| cfg = CATALOG.get(dkey, {}) | |
| return (gr.update(choices=cfg.get("subdomains", []), value=[]), | |
| gr.update(choices=cfg.get("pains", []), value=[]), | |
| gr.update(choices=[], value=[]), | |
| gr.update(choices=cfg.get("outputs", []), value=[]), | |
| gr.update(choices=[], value=[]), | |
| gr.update(choices=cfg.get("users", []), value=[]), | |
| gr.update(choices=[], value=[])) | |
| domain.change(on_domain_change, inputs=[domain], | |
| outputs=[subdomain, pains, pain_detail, outs, out_detail, users, user_detail]) | |
| def on_pain_change(ps): | |
| ch=[]; [ch.extend(PAIN_SUB.get(p, [])) for p in (ps or [])] | |
| return gr.update(choices=uniq(ch), value=[]) | |
| pains.change(on_pain_change, inputs=[pains], outputs=[pain_detail]) | |
| def on_out_change(osel): | |
| ch=[]; [ch.extend(OUTPUT_SUB.get(o, [])) for o in (osel or [])] | |
| return gr.update(choices=uniq(ch), value=[]) | |
| outs.change(on_out_change, inputs=[outs], outputs=[out_detail]) | |
| def on_user_change(usel): | |
| ch=[]; [ch.extend(USER_SUB.get(u, [])) for u in (usel or [])] | |
| return gr.update(choices=uniq(ch), value=[]) | |
| users.change(on_user_change, inputs=[users], outputs=[user_detail]) | |
| def do_auto(dkey, ps, osel, usel): | |
| rec = auto_recommend(dkey, ps or [], osel or [], usel or []) | |
| return gr.update(value=rec, choices=uniq(ALL_TECHS + rec)) | |
| auto_btn.click(do_auto, inputs=[domain, pains, outs, users], outputs=[techs]) | |
| # ์ฌ๋ฌ ๊ธฐ์ โํ๊บผ๋ฒ์โ ์์ธ ๋ฏธ๋ฆฌ๋ณด๊ธฐ | |
| tech_preview.change( | |
| render_multi_rationales, | |
| inputs=[tech_preview, domain, subdomain, pains, pain_detail, outs, out_detail, users, user_detail], | |
| outputs=[rationale_md] | |
| ) | |
| from_selected_btn.click( | |
| lambda cur: gr.update(value=cur), | |
| inputs=[techs], | |
| outputs=[tech_preview] | |
| ).then( | |
| render_multi_rationales, | |
| inputs=[tech_preview, domain, subdomain, pains, pain_detail, outs, out_detail, users, user_detail], | |
| outputs=[rationale_md] | |
| ) | |
| # RAG ์ธ๋ฑ์ฑ | |
| build_btn.click(lambda files: build_index(files, 800, 200), | |
| inputs=[rag_files], outputs=[rag_status]) | |
| # ์ต์ข ํ๋กฌํํธ ์์ฑ | |
| gen_btn.click( | |
| compose_final_prompt, | |
| inputs=[ | |
| domain, subdomain, pains, pain_detail, outs, out_detail, users, user_detail, techs, | |
| # free text | |
| domain_note, pain_note, out_note, user_note, | |
| # overrides | |
| kpi_text, tone_text, ng_text, format_override, | |
| # extras | |
| fewshot_text, rag_text, | |
| # RAG | |
| use_rag, rag_topk | |
| ], | |
| outputs=[final_box] | |
| ) | |
| # ================== ๋ฐ์น (Spaces/๋ก์ปฌ ๊ณตํต) ================== | |
| if __name__ == "__main__": | |
| # (์ ํ) Basic Auth: Space Secrets์ HF_AUTH_LIST="alice:pw1,bob:pw2" | |
| auth_pairs = [] | |
| if os.getenv("HF_AUTH_LIST", "").strip(): | |
| for pair in os.getenv("HF_AUTH_LIST").split(","): | |
| if ":" in pair: | |
| u, p = pair.split(":", 1) | |
| auth_pairs.append((u.strip(), p.strip())) | |
| launch_kwargs = {"server_name": "0.0.0.0"} | |
| if auth_pairs: | |
| launch_kwargs["auth"] = auth_pairs | |
| demo.queue() # Gradio Queue ํ์ฑํ(๋์ ์ ์ ์์ ) | |
| demo.launch(**launch_kwargs) | |