File size: 10,629 Bytes
ad8348b
 
 
 
 
 
 
6f6b7d0
ad8348b
 
 
 
 
 
 
 
 
6f6b7d0
ad8348b
 
6f6b7d0
 
 
ad8348b
 
 
 
 
 
 
 
 
 
b1f9932
ad8348b
6f6b7d0
 
602386e
 
 
 
 
 
 
 
6f6b7d0
 
ad8348b
 
b1f9932
ad8348b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6f6b7d0
b1f9932
ad8348b
 
 
 
 
 
 
 
6f6b7d0
 
 
 
b1f9932
 
6f6b7d0
 
 
ad8348b
 
 
 
 
602386e
 
ad8348b
602386e
ad8348b
602386e
 
 
 
 
 
ad8348b
602386e
 
 
 
 
ad8348b
 
 
 
 
 
 
ada6925
 
 
ad8348b
 
6f6b7d0
 
 
ad8348b
 
 
 
 
 
 
 
 
b1f9932
ad8348b
6f6b7d0
ad8348b
 
 
b1f9932
 
 
 
 
 
 
 
 
 
 
6f6b7d0
ad8348b
b1f9932
ad8348b
 
b1f9932
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ad8348b
 
b1f9932
ad8348b
b1f9932
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ada6925
b1f9932
ada6925
 
 
 
 
ad8348b
 
 
 
ada6925
 
b1f9932
 
 
 
 
ada6925
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b1f9932
 
 
 
 
ada6925
b1f9932
 
ada6925
 
 
b1f9932
 
ad8348b
 
6f6b7d0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
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)