GGGISfolio / src /openai_client.py
miniwa00's picture
κ΅¬ν˜„λ°©μ•ˆ, κΈ°λŒ€νš¨κ³Ό, κ·Όκ±° ν•„λ“œμ˜ 가독성을 κ°œμ„ ν•˜κΈ° μœ„ν•΄ ν•­λͺ©λ³„ κ°œν–‰ μΆ”κ°€ λ‘œμ§μ„ μˆ˜μ •ν•˜κ³ , ν…μŠ€νŠΈ ν¬λ§·νŒ… ν•¨μˆ˜λ₯Ό ν†΅ν•©ν•˜μ—¬ μ½”λ“œμ˜ 일관성을 λ†’μž„.
ada6925
Raw
History Blame Contribute Delete
10.6 kB
from openai import OpenAI
import os
from typing import List, Dict, Any
from dotenv import load_dotenv
load_dotenv()
class OpenAIClient:
def __init__(self, api_key: str = None):
"""
OpenAI ν΄λΌμ΄μ–ΈνŠΈ μ΄ˆκΈ°ν™”
api_keyκ°€ None이면 ν™˜κ²½λ³€μˆ˜ OPENAI_API_KEYμ—μ„œ κ°€μ Έμ˜΄
"""
self.api_key = api_key or os.getenv("OPENAI_API_KEY")
if not self.api_key:
raise ValueError("OpenAI API keyκ°€ ν•„μš”ν•©λ‹ˆλ‹€.")
self.client = OpenAI(api_key=self.api_key)
def generate_idea(
self, contest_info: Dict[str, str], nodes_data: List[Dict[str, Any]]
) -> Dict[str, str]:
"""
곡λͺ¨μ „ 정보와 λ…Έλ“œ 데이터λ₯Ό 기반으둜 아이디어 생성
"""
try:
# λ…Έλ“œ μš”μ•½
nodes_summary = self._format_nodes_for_prompt(nodes_data)
prompt = self._create_prompt(contest_info, nodes_summary)
# OpenAI GPT 호좜
response = self.client.chat.completions.create(
model="gpt-4o", # λ˜λŠ” "gpt-4", "gpt-3.5-turbo"
messages=[
{
"role": "system",
"content": """
당신은 μ•„μ΄λ””μ—μ΄μ…˜ μ „λ¬Έκ°€μ΄μž ν¬λ¦¬μ—μ΄ν‹°λΈŒ μ»¨μ„€ν„΄νŠΈμž…λ‹ˆλ‹€. μž…λ ₯으둜 μ£Όμ–΄μ§€λŠ” λ‹€μŒ λ„€ κ°€μ§€ μš”μ†Œλ₯Ό κ²°ν•©ν•΄, ꡬ체적이고 μ‹€ν–‰ κ°€λŠ₯ν•œ ν˜μ‹  아이디어λ₯Ό μ œμ•ˆν•΄μ•Ό ν•©λ‹ˆλ‹€.
1. 도메인(Domain): μ•„μ΄λ””μ—μ΄μ…˜μ˜ 좜발점이 λ˜λŠ” λΆ„μ•Ό (예: 농업, 의료, 법, μž„μ—… λ“±)
2. μ»¨ν…μŠ€νŠΈ(Context): ν•΄λ‹Ή 과제λ₯Ό μˆ˜ν–‰ν•΄μ•Ό ν•˜λŠ” μ΄μœ λ‚˜ λ°°κ²½ μ„€λͺ… (예: 곡λͺ¨μ „ 주제, μ£Όμ΅œκΈ°κ΄€μ˜ λͺ©ν‘œ, μ‹œμž₯ 동ν–₯)
3. μ΄κ·Έλ‚˜μ΄ν„°(Igniter): μ•„μ΄λ””μ–΄μ˜ 핡심 λ°©ν–₯성을 κ²°μ •ν•˜λŠ” ν‚€μ›Œλ“œλ‚˜ 질문 (예: 지속가λŠ₯μ„± κ·ΉλŒ€ν™”, 데이터 λ―Όμ£Όν™”, μ‚¬μš©μž μ°Έμ—¬ κ°•ν™” λ“±)
4. λ…Έλ“œ(Nodes): μ‚¬μš©μžμ˜ κ²½ν—˜, ν”„λ‘œμ νŠΈ 사둀, 기술 μŠ€νƒ λ“± Connecting the Dotsλ₯Ό μœ„ν•œ μžμ‚° μ»¬λ ‰μ…˜
""",
},
{"role": "user", "content": prompt},
],
temperature=0.7,
max_tokens=5000,
)
generated_text = response.choices[0].message.content
idea = self._parse_generated_idea(generated_text, contest_info)
return idea
except Exception as e:
return {
"error": f"아이디어 생성 쀑 였λ₯˜: {str(e)}",
"ai_name": "ChatGPT",
"title": "였λ₯˜",
"overview": "아이디어 생성 μ‹€νŒ¨",
"problem": "",
"solution": "",
"implementation": "",
"expected_effect": "",
"rationale": "",
}
def _format_nodes_for_prompt(self, nodes_data: List[Dict[str, Any]]) -> str:
"""λ…Έλ“œ 데이터λ₯Ό λ¬Έμžμ—΄λ‘œ ν¬λ§·νŒ…"""
if not nodes_data:
return "κΈ°μ‘΄ ν”„λ‘œμ νŠΈ 정보 μ—†μŒ."
formatted = []
for i, node in enumerate(nodes_data, 1):
formatted.append(
f"""
ν”„λ‘œμ νŠΈ {i}:
- 제λͺ©: {node.get('title', '제λͺ© μ—†μŒ')}
- μ„€λͺ…: {node.get('description', 'μ„€λͺ… μ—†μŒ')}
- ν…Œλ„ŒνŠΈ: {node.get('tenant', 'λ―Έμ§€μ •')}
- νƒœκ·Έ: {', '.join(node.get('tags', []))}
"""
)
return "\n".join(formatted)
def _create_prompt(self, contest_info: Dict[str, str], nodes_summary: str) -> str:
"""ChatGPT용 ν”„λ‘¬ν”„νŠΈ ꡬ성"""
return f"""
λ‹€μŒ μž…λ ₯값을 μ°Έκ³ ν•΄μ„œ κ°€μž₯ 졜고의 아이디어λ₯Ό **1κ°€μ§€** μ œμ•ˆν•΄μ£Όμ„Έμš”.
각 μ•„μ΄λ””μ–΄λŠ” 도메인 μ€‘μ‹¬μœΌλ‘œ μ»¨ν…μŠ€νŠΈΒ·μ΄κ·Έλ‚˜μ΄ν„°Β·λ…Έλ“œλ₯Ό κ²°ν•©ν•˜μ—¬ μž‘μ„±ν•©λ‹ˆλ‹€.
γ€νƒ€κ²Ÿ 곡λͺ¨μ „ 정보】
- 곡λͺ¨μ „ 제λͺ©: {contest_info.get('title', '')}
- 도메인: {contest_info.get('theme', '')}
- μ»¨ν…μŠ€νŠΈ: {contest_info.get('description', '')}
- μ΄κ·Έλ‚˜μ΄ν„°: {contest_info.get('context', '')}
【connecting the dots을 μœ„ν•œ λ…Έλ“œ 정보】
- λ…Έλ“œ: {nodes_summary}
μ˜ˆμ‹œ)
- 도메인: 슀마트 팜
- μ»¨ν…μŠ€νŠΈ: 농림뢀 주졜 β€˜μΉœν™˜κ²½ 슀마트 농업 곡λͺ¨μ „’, μ €νƒ„μ†Œ 배좜 μš°μˆ˜μ‚¬λ‘€ 발꡴
- μ΄κ·Έλ‚˜μ΄ν„°: β€œAI둜 ν† μ–‘ 건강 μ‹€μ‹œκ°„ λͺ¨λ‹ˆν„°λ§β€
- λ…Έλ“œ: OpenCV 기반 이미지 뢄석, AWS RDS λŒ€μ‹œλ³΄λ“œ 개발 κ²½ν—˜, IoT μ„Όμ„œ λ„€νŠΈμ›Œν¬ ꡬ좕 κ²½ν—˜
μ•„λž˜ ν˜•μ‹μ— 맞좰 μ‘λ‹΅ν•΄μ£Όμ„Έμš”:
제λͺ©: [아이디어 제λͺ©]
κ°œμš”: [κ°„λ‹¨ν•œ μ†Œκ°œ]
λ¬Έμ œμ˜μ‹: [ν•΄κ²°ν•˜κ³ μž ν•˜λŠ” 문제]
μ†”λ£¨μ…˜: [ꡬ체적인 ν•΄κ²° λ°©μ•ˆ]
κ΅¬ν˜„λ°©μ•ˆ: [기술적 κ΅¬ν˜„ λ˜λŠ” μ‹€ν–‰ κ³„νšμ„ λ‹¨κ³„λ³„λ‘œ λ‚˜μ—΄ (1. 2. 3. ν˜•νƒœ λ˜λŠ” - ν˜•νƒœλ‘œ)]
κΈ°λŒ€νš¨κ³Ό: [μ˜ˆμƒ μ„±κ³Ό λ˜λŠ” 효과λ₯Ό ν•­λͺ©λ³„λ‘œ λ‚˜μ—΄ (- ν˜•νƒœλ‘œ μž‘μ„±)]
κ·Όκ±°: [μœ„μ˜ 곡λͺ¨μ „ 정보와 λ…Έλ“œλ“€μ΄ μ–΄λ–»κ²Œ μ—°κ²°λ˜μ–΄ 이 아이디어가 λ„μΆœλ˜μ—ˆλŠ”μ§€λ₯Ό connecting the dots κ΄€μ μ—μ„œ 논리적 λ‹¨κ³„λ³„λ‘œ μ„€λͺ… (- ν˜•νƒœλ‘œ μž‘μ„±)]
"""
def _parse_generated_idea(
self, generated_text: str, contest_info: Dict[str, str]
) -> Dict[str, str]:
"""AI 생성 κ²°κ³Όλ₯Ό κ΅¬μ‘°ν™”λœ ν˜•νƒœλ‘œ νŒŒμ‹±"""
idea = {
"ai_name": "ChatGPT",
"title": "",
"overview": "",
"problem": "",
"solution": "",
"implementation": "",
"expected_effect": "",
"rationale": "",
"contest_info": contest_info,
"raw_response": generated_text,
}
try:
# 더 κ°•κ±΄ν•œ νŒŒμ‹±μ„ μœ„ν•œ ν‚€μ›Œλ“œ λ§€ν•‘
keyword_mappings = {
"제λͺ©": "title",
"κ°œμš”": "overview",
"λ¬Έμ œμ˜μ‹": "problem",
"μ†”λ£¨μ…˜": "solution",
"κ΅¬ν˜„λ°©μ•ˆ": "implementation",
"κΈ°λŒ€νš¨κ³Ό": "expected_effect",
"κ·Όκ±°": "rationale",
}
lines = generated_text.split("\n")
current_key = None
for line in lines:
line = line.strip()
if not line: # 빈 쀄 κ±΄λ„ˆλ›°κΈ°
continue
# ν‚€μ›Œλ“œ 검색 (콜둠 포함)
found_key = False
for keyword, key in keyword_mappings.items():
if (
line.startswith(f"{keyword}:")
or line.startswith(f"**{keyword}:**")
or line.startswith(f"#{keyword}")
):
current_key = key
# 콜둠 이후 λ‚΄μš© μΆ”μΆœ
content = line.split(":", 1)[1].strip() if ":" in line else ""
idea[current_key] = content
found_key = True
break
# ν‚€μ›Œλ“œκ°€ λ°œκ²¬λ˜μ§€ μ•Šμ•˜κ³  ν˜„μž¬ ν‚€κ°€ 있으면 λ‚΄μš© μΆ”κ°€
if not found_key and current_key and line:
if idea[current_key]:
idea[current_key] += " " + line
else:
idea[current_key] = line
except Exception as e:
print(f"[νŒŒμ‹±μ˜€λ₯˜] {e}")
print(f"[원본응닡] {generated_text}")
idea["title"] = "νŒŒμ‹± μ‹€νŒ¨"
idea["overview"] = f"κ²°κ³Ό νŒŒμ‹±μ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€. 였λ₯˜: {str(e)}"
# νŒŒμ‹± ν›„ 빈 ν•„λ“œκ°€ μžˆλŠ”μ§€ 확인 및 디버깅
empty_fields = [
key
for key, value in idea.items()
if key not in ["contest_info", "raw_response", "ai_name"] and not value
]
if empty_fields:
print(f"[κ²½κ³ ] 빈 ν•„λ“œ 발견: {empty_fields}")
print(f"[원본응닡 일뢀] {generated_text[:500]}...")
# μ΅œμ†Œν•œμ˜ 제λͺ©μ€ 확보
if not idea.get("title"):
idea["title"] = "제λͺ© μ—†μŒ"
# κ΅¬ν˜„λ°©μ•ˆ, κΈ°λŒ€νš¨κ³Ό, κ·Όκ±° ν•„λ“œ 가독성 κ°œμ„  (ν•­λͺ©λ³„ κ°œν–‰ μΆ”κ°€)
if idea.get("implementation"):
idea["implementation"] = format_list_text(idea["implementation"])
if idea.get("expected_effect"):
idea["expected_effect"] = format_list_text(idea["expected_effect"])
if idea.get("rationale"):
idea["rationale"] = format_list_text(idea["rationale"])
return idea
def format_list_text(text: str) -> str:
"""ν…μŠ€νŠΈμ˜ 가독성을 κ°œμ„ ν•˜μ—¬ 각 ν•­λͺ©λ³„λ‘œ κ°œν–‰ μΆ”κ°€"""
if not text:
return text
import re
# λ¨Όμ € ν…μŠ€νŠΈλ₯Ό 정리
text = text.strip()
# "- " νŒ¨ν„΄μ΄ 있으면 μš°μ„  처리
if "- " in text and not text.startswith("- "):
# "- " μ•žμ— κ°œν–‰ μΆ”κ°€ (첫 번째 ν•­λͺ© μ œμ™Έ)
formatted_text = re.sub(r"([^.\n])\s*(-\s)", r"\1\n\2", text)
else:
# "- " νŒ¨ν„΄μ΄ μ—†κ±°λ‚˜ 이미 첫 λ²ˆμ§Έκ°€ "- "둜 μ‹œμž‘ν•˜λ©΄ 숫자 νŒ¨ν„΄λ„ 처리
formatted_text = text
# "1. ", "2. " λ“±μ˜ νŒ¨ν„΄ μ•žμ— κ°œν–‰ μΆ”κ°€ (첫 번째 ν•­λͺ© μ œμ™Έ)
formatted_text = re.sub(r"([^.\n])(\d+\.\s)", r"\1\n\2", formatted_text)
# "- " 둜 μ‹œμž‘ν•˜λŠ” ν•­λͺ©λ“€ 처리
formatted_text = re.sub(r"([^.\n])\s*(-\s)", r"\1\n\2", formatted_text)
# "β€’ " 둜 μ‹œμž‘ν•˜λŠ” ν•­λͺ©λ“€ 처리
formatted_text = re.sub(r"([^.\n])\s*(β€’\s)", r"\1\n\2", formatted_text)
# μ²˜μŒμ— κ°œν–‰μ΄ μΆ”κ°€λœ 경우 제거
if formatted_text.startswith("\n"):
formatted_text = formatted_text[1:]
return formatted_text
def format_implementation_text(text: str) -> str:
"""κ΅¬ν˜„λ°©μ•ˆ ν…μŠ€νŠΈμ˜ 가독성을 κ°œμ„ ν•˜μ—¬ 각 ν•­λͺ©λ³„λ‘œ κ°œν–‰ μΆ”κ°€ (ν•˜μœ„ ν˜Έν™˜μ„±)"""
return format_list_text(text)
# μ‚¬μš© μ˜ˆμ‹œ
def create_openai_client(api_key: str = None) -> OpenAIClient:
return OpenAIClient(api_key)