12_chatbot.py

Download
python 372 lines 10.2 KB
  1"""
  212. ์‹ค์ „ ์ฑ—๋ด‡ ์˜ˆ์ œ
  3
  4RAG ๊ธฐ๋ฐ˜ ๋Œ€ํ™”ํ˜• AI ์‹œ์Šคํ…œ
  5"""
  6
  7print("=" * 60)
  8print("์‹ค์ „ ์ฑ—๋ด‡")
  9print("=" * 60)
 10
 11
 12# ============================================
 13# 1. ๊ฐ„๋‹จํ•œ ๋Œ€ํ™” ์ฑ—๋ด‡ (๋ฉ”๋ชจ๋ฆฌ)
 14# ============================================
 15print("\n[1] ๊ฐ„๋‹จํ•œ ๋Œ€ํ™” ์ฑ—๋ด‡")
 16print("-" * 40)
 17
 18class SimpleChatbot:
 19    """ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์ฑ—๋ด‡"""
 20
 21    def __init__(self, system_prompt="You are a helpful assistant."):
 22        self.system_prompt = system_prompt
 23        self.history = []
 24
 25    def chat(self, user_message):
 26        """์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ (LLM ํ˜ธ์ถœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜)"""
 27        # ํžˆ์Šคํ† ๋ฆฌ์— ์ถ”๊ฐ€
 28        self.history.append({"role": "user", "content": user_message})
 29
 30        # ์‹ค์ œ๋กœ๋Š” LLM ํ˜ธ์ถœ
 31        # response = llm.invoke(messages)
 32        response = f"[์‘๋‹ต] {user_message}์— ๋Œ€ํ•œ ๋‹ต๋ณ€์ž…๋‹ˆ๋‹ค."
 33
 34        self.history.append({"role": "assistant", "content": response})
 35        return response
 36
 37    def get_messages(self):
 38        """์ „์ฒด ๋ฉ”์‹œ์ง€ ๊ตฌ์„ฑ"""
 39        messages = [{"role": "system", "content": self.system_prompt}]
 40        messages.extend(self.history)
 41        return messages
 42
 43    def clear_history(self):
 44        self.history = []
 45
 46# ํ…Œ์ŠคํŠธ
 47bot = SimpleChatbot()
 48print(bot.chat("์•ˆ๋…•ํ•˜์„ธ์š”"))
 49print(bot.chat("์˜ค๋Š˜ ๋‚ ์”จ ์–ด๋•Œ์š”?"))
 50print(f"ํžˆ์Šคํ† ๋ฆฌ ๊ธธ์ด: {len(bot.history)}")
 51
 52
 53# ============================================
 54# 2. RAG ์ฑ—๋ด‡
 55# ============================================
 56print("\n[2] RAG ์ฑ—๋ด‡")
 57print("-" * 40)
 58
 59import numpy as np
 60
 61class RAGChatbot:
 62    """๋ฌธ์„œ ๊ธฐ๋ฐ˜ RAG ์ฑ—๋ด‡"""
 63
 64    def __init__(self, documents):
 65        self.documents = documents
 66        self.history = []
 67        # ๊ฐ€์ƒ ์ž„๋ฒ ๋”ฉ (์‹ค์ œ๋กœ๋Š” ๋ชจ๋ธ ์‚ฌ์šฉ)
 68        self.embeddings = np.random.randn(len(documents), 128)
 69
 70    def retrieve(self, query, top_k=2):
 71        """๊ด€๋ จ ๋ฌธ์„œ ๊ฒ€์ƒ‰"""
 72        query_emb = np.random.randn(128)
 73        similarities = np.dot(self.embeddings, query_emb) / (
 74            np.linalg.norm(self.embeddings, axis=1) * np.linalg.norm(query_emb)
 75        )
 76        top_indices = np.argsort(similarities)[-top_k:][::-1]
 77        return [self.documents[i] for i in top_indices]
 78
 79    def chat(self, question):
 80        """RAG ๊ธฐ๋ฐ˜ ๋‹ต๋ณ€ ์ƒ์„ฑ"""
 81        # ๊ฒ€์ƒ‰
 82        relevant_docs = self.retrieve(question)
 83        context = "\n".join(relevant_docs)
 84
 85        # ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ
 86        prompt = f"""Context:
 87{context}
 88
 89History:
 90{self._format_history()}
 91
 92Question: {question}
 93
 94Answer:"""
 95
 96        # ์‹ค์ œ๋กœ๋Š” LLM ํ˜ธ์ถœ
 97        response = f"[์ปจํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ์‘๋‹ต] {relevant_docs[0][:50]}..."
 98
 99        # ํžˆ์Šคํ† ๋ฆฌ ์—…๋ฐ์ดํŠธ
100        self.history.append({"role": "user", "content": question})
101        self.history.append({"role": "assistant", "content": response})
102
103        return response
104
105    def _format_history(self, max_turns=3):
106        recent = self.history[-max_turns*2:]
107        return "\n".join([f"{m['role']}: {m['content']}" for m in recent])
108
109# ํ…Œ์ŠคํŠธ
110documents = [
111    "Python is a programming language created by Guido van Rossum.",
112    "Machine learning is a type of artificial intelligence.",
113    "Deep learning uses neural networks with many layers."
114]
115
116rag_bot = RAGChatbot(documents)
117print(rag_bot.chat("What is Python?"))
118print(rag_bot.chat("Tell me more about it"))
119
120
121# ============================================
122# 3. ์˜๋„ ๋ถ„๋ฅ˜
123# ============================================
124print("\n[3] ์˜๋„ ๋ถ„๋ฅ˜")
125print("-" * 40)
126
127class IntentClassifier:
128    """๊ทœ์น™ ๊ธฐ๋ฐ˜ ์˜๋„ ๋ถ„๋ฅ˜ (์‹ค์ œ๋กœ๋Š” LLM ์‚ฌ์šฉ)"""
129
130    def __init__(self):
131        self.intents = {
132            "greeting": ["hello", "hi", "hey", "์•ˆ๋…•"],
133            "goodbye": ["bye", "goodbye", "์ž˜๊ฐ€"],
134            "help": ["help", "๋„์›€", "how do i"],
135            "question": ["what", "why", "how", "when", "๋ฌด์—‡", "์™œ"]
136        }
137
138    def classify(self, text):
139        text_lower = text.lower()
140        for intent, keywords in self.intents.items():
141            if any(kw in text_lower for kw in keywords):
142                return intent
143        return "general"
144
145classifier = IntentClassifier()
146test_texts = ["Hello!", "What is AI?", "Goodbye", "Help me please"]
147for text in test_texts:
148    intent = classifier.classify(text)
149    print(f"  [{intent}] {text}")
150
151
152# ============================================
153# 4. ๋Œ€ํ™” ์ƒํƒœ ๊ด€๋ฆฌ
154# ============================================
155print("\n[4] ๋Œ€ํ™” ์ƒํƒœ ๊ด€๋ฆฌ")
156print("-" * 40)
157
158from enum import Enum
159from dataclasses import dataclass, field
160from typing import Dict, List, Any
161
162class State(Enum):
163    GREETING = "greeting"
164    COLLECTING = "collecting"
165    CONFIRMING = "confirming"
166    DONE = "done"
167
168@dataclass
169class ConversationState:
170    state: State = State.GREETING
171    slots: Dict[str, Any] = field(default_factory=dict)
172
173class StatefulBot:
174    def __init__(self):
175        self.context = ConversationState()
176        self.required_slots = ["name", "email"]
177
178    def process(self, message):
179        if self.context.state == State.GREETING:
180            self.context.state = State.COLLECTING
181            return "์•ˆ๋…•ํ•˜์„ธ์š”! ์ด๋ฆ„์„ ์•Œ๋ ค์ฃผ์„ธ์š”."
182
183        elif self.context.state == State.COLLECTING:
184            # ์Šฌ๋กฏ ์ถ”์ถœ (๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ)
185            if "name" not in self.context.slots:
186                self.context.slots["name"] = message
187                return "์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ์•Œ๋ ค์ฃผ์„ธ์š”."
188            elif "email" not in self.context.slots:
189                self.context.slots["email"] = message
190                self.context.state = State.CONFIRMING
191                return f"ํ™•์ธ: {self.context.slots}. ๋งž์Šต๋‹ˆ๊นŒ? (์˜ˆ/์•„๋‹ˆ์˜ค)"
192
193        elif self.context.state == State.CONFIRMING:
194            if "์˜ˆ" in message.lower() or "yes" in message.lower():
195                self.context.state = State.DONE
196                return "๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! ์ฒ˜๋ฆฌ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
197            else:
198                self.context = ConversationState()
199                return "์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฆ„์„ ์•Œ๋ ค์ฃผ์„ธ์š”."
200
201        return "๋ฌด์—‡์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”?"
202
203# ํ…Œ์ŠคํŠธ
204stateful_bot = StatefulBot()
205print(stateful_bot.process("์‹œ์ž‘"))
206print(stateful_bot.process("ํ™๊ธธ๋™"))
207print(stateful_bot.process("hong@example.com"))
208print(stateful_bot.process("์˜ˆ"))
209
210
211# ============================================
212# 5. OpenAI ์ฑ—๋ด‡ (์ฝ”๋“œ ์˜ˆ์‹œ)
213# ============================================
214print("\n[5] OpenAI ์ฑ—๋ด‡ (์ฝ”๋“œ)")
215print("-" * 40)
216
217openai_bot_code = '''
218from openai import OpenAI
219
220class OpenAIChatbot:
221    def __init__(self, system_prompt="You are a helpful assistant."):
222        self.client = OpenAI()
223        self.system_prompt = system_prompt
224        self.history = []
225
226    def chat(self, message):
227        # ๋ฉ”์‹œ์ง€ ๊ตฌ์„ฑ
228        messages = [{"role": "system", "content": self.system_prompt}]
229        messages.extend(self.history)
230        messages.append({"role": "user", "content": message})
231
232        # API ํ˜ธ์ถœ
233        response = self.client.chat.completions.create(
234            model="gpt-3.5-turbo",
235            messages=messages,
236            temperature=0.7
237        )
238
239        assistant_msg = response.choices[0].message.content
240
241        # ํžˆ์Šคํ† ๋ฆฌ ์—…๋ฐ์ดํŠธ
242        self.history.append({"role": "user", "content": message})
243        self.history.append({"role": "assistant", "content": assistant_msg})
244
245        return assistant_msg
246
247    def chat_stream(self, message):
248        """์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต"""
249        messages = [{"role": "system", "content": self.system_prompt}]
250        messages.extend(self.history)
251        messages.append({"role": "user", "content": message})
252
253        stream = self.client.chat.completions.create(
254            model="gpt-3.5-turbo",
255            messages=messages,
256            stream=True
257        )
258
259        full_response = ""
260        for chunk in stream:
261            if chunk.choices[0].delta.content:
262                content = chunk.choices[0].delta.content
263                full_response += content
264                yield content
265
266        self.history.append({"role": "user", "content": message})
267        self.history.append({"role": "assistant", "content": full_response})
268'''
269print(openai_bot_code)
270
271
272# ============================================
273# 6. FastAPI ์„œ๋ฒ„ (์ฝ”๋“œ)
274# ============================================
275print("\n[6] FastAPI ์„œ๋ฒ„ (์ฝ”๋“œ)")
276print("-" * 40)
277
278fastapi_code = '''
279from fastapi import FastAPI
280from pydantic import BaseModel
281
282app = FastAPI()
283sessions = {}
284
285class ChatRequest(BaseModel):
286    session_id: str
287    message: str
288
289@app.post("/chat")
290async def chat(request: ChatRequest):
291    if request.session_id not in sessions:
292        sessions[request.session_id] = OpenAIChatbot()
293
294    bot = sessions[request.session_id]
295    response = bot.chat(request.message)
296
297    return {"response": response}
298
299@app.delete("/session/{session_id}")
300async def clear_session(session_id: str):
301    if session_id in sessions:
302        del sessions[session_id]
303    return {"status": "cleared"}
304
305# ์‹คํ–‰: uvicorn main:app --reload
306'''
307print(fastapi_code)
308
309
310# ============================================
311# 7. Gradio UI (์ฝ”๋“œ)
312# ============================================
313print("\n[7] Gradio UI (์ฝ”๋“œ)")
314print("-" * 40)
315
316gradio_code = '''
317import gradio as gr
318
319def respond(message, history):
320    # ์ฑ—๋ด‡ ์‘๋‹ต ์ƒ์„ฑ
321    response = bot.chat(message)
322    return response
323
324demo = gr.ChatInterface(
325    fn=respond,
326    title="AI Chatbot",
327    description="Ask me anything!",
328    examples=["Hello!", "What is AI?"],
329    theme="soft"
330)
331
332demo.launch()
333'''
334print(gradio_code)
335
336
337# ============================================
338# ์ •๋ฆฌ
339# ============================================
340print("\n" + "=" * 60)
341print("์ฑ—๋ด‡ ์ •๋ฆฌ")
342print("=" * 60)
343
344summary = """
345์ฑ—๋ด‡ ๊ตฌ์„ฑ์š”์†Œ:
346    1. ๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ ๊ด€๋ฆฌ
347    2. ์˜๋„ ๋ถ„๋ฅ˜
348    3. ์Šฌ๋กฏ ์ถ”์ถœ
349    4. ์ƒํƒœ ๊ด€๋ฆฌ
350    5. RAG (๋ฌธ์„œ ๊ธฐ๋ฐ˜)
351    6. LLM ํ˜ธ์ถœ
352
353ํ•ต์‹ฌ ํŒจํ„ด:
354    # ๊ธฐ๋ณธ ๋Œ€ํ™”
355    messages = [system] + history + [user_message]
356    response = llm.invoke(messages)
357
358    # RAG
359    context = retrieve(query)
360    response = llm.invoke(context + query)
361
362    # ์ŠคํŠธ๋ฆฌ๋ฐ
363    for chunk in llm.stream(messages):
364        yield chunk
365
366๋ฐฐํฌ:
367    - FastAPI: REST API ์„œ๋ฒ„
368    - Gradio: ๋น ๋ฅธ UI ํ”„๋กœํ† ํƒ€์ž…
369    - Streamlit: ๋Œ€์‹œ๋ณด๋“œ ์Šคํƒ€์ผ
370"""
371print(summary)