포스트

[Python 100일 챌린지] Day 99 - 종합 프로젝트 2: AI 챗봇 만들기

[Python 100일 챌린지] Day 99 - 종합 프로젝트 2: AI 챗봇 만들기

100일의 대미를 장식할 최종 프로젝트! ChatGPT API + Flask로 웹 기반 AI 챗봇을 만듭니다. 대화 이력 저장, 다양한 AI 페르소나, 실시간 스트리밍까지! 여러분만의 AI 비서를 만들어보세요!

(40분 완독 ⭐⭐⭐⭐)

🎯 프로젝트 목표

📚 사용할 기술 스택

graph LR
    A[HTML/CSS<br/>프론트엔드] --> B[Flask<br/>웹 서버]
    B --> C[OpenAI API<br/>ChatGPT]
    B --> D[JSON<br/>대화 저장]
    C --> E[완성된 챗봇]
    D --> E

프로젝트 구조

1
2
3
4
5
6
7
8
9
10
11
chatbot/
│
├── app.py              # Flask 서버
├── .env                # API 키
├── conversations.json  # 대화 이력
│
├── templates/
│   └── index.html     # 메인 페이지
│
└── static/
    └── style.css      # 스타일

💡 “웹 개발도 하고 AI도 쓰고… 너무 복잡할 것 같아요!” 괜찮습니다! 각 부분은 생각보다 간단해요. Flask는 10줄이면 시작되고, ChatGPT API는 3줄이면 됩니다. 하나씩 따라하면 반드시 완성됩니다! 화이팅! 😊

1단계: Flask 웹 서버

기본 Flask 앱 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# app.py
from flask import Flask, render_template, request, jsonify
from openai import OpenAI
from dotenv import load_dotenv
import os
import json
from datetime import datetime

# 환경 변수 로드
load_dotenv()

app = Flask(__name__)
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# 대화 이력 파일
CONVERSATIONS_FILE = 'conversations.json'

@app.route('/')
def index():
    """메인 페이지"""
    return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True, port=5000)

2단계: ChatGPT API 통합

AI 응답 생성 함수

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
def get_ai_response(messages, temperature=0.7):
    """ChatGPT로부터 응답 받기"""
    try:
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=messages,
            temperature=temperature,
            max_tokens=500
        )
        return response.choices[0].message.content
    except Exception as e:
        return f"오류 발생: {str(e)}"

# API 엔드포인트
@app.route('/chat', methods=['POST'])
def chat():
    """사용자 메시지 처리"""
    data = request.json
    user_message = data.get('message')
    conversation_history = data.get('history', [])

    # 시스템 메시지 추가 (첫 메시지인 경우)
    if not conversation_history:
        conversation_history = [
            {"role": "system", "content": "당신은 친절하고 유용한 AI 어시스턴트입니다."}
        ]

    # 사용자 메시지 추가
    conversation_history.append({"role": "user", "content": user_message})

    # AI 응답 생성
    ai_response = get_ai_response(conversation_history)

    # 응답을 대화에 추가
    conversation_history.append({"role": "assistant", "content": ai_response})

    return jsonify({
        'response': ai_response,
        'history': conversation_history
    })

3단계: 대화 이력 관리

대화 저장 및 불러오기

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
def load_conversations():
    """저장된 대화 불러오기"""
    try:
        with open(CONVERSATIONS_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    except FileNotFoundError:
        return []

def save_conversation(conversation):
    """대화 저장하기"""
    conversations = load_conversations()

    new_conv = {
        'id': len(conversations) + 1,
        'timestamp': datetime.now().isoformat(),
        'messages': conversation
    }

    conversations.append(new_conv)

    with open(CONVERSATIONS_FILE, 'w', encoding='utf-8') as f:
        json.dump(conversations, f, ensure_ascii=False, indent=2)

@app.route('/save', methods=['POST'])
def save():
    """현재 대화 저장"""
    data = request.json
    conversation = data.get('history', [])

    if conversation:
        save_conversation(conversation)
        return jsonify({'status': 'success', 'message': '대화가 저장되었습니다.'})
    else:
        return jsonify({'status': 'error', 'message': '저장할 대화가 없습니다.'})

@app.route('/history', methods=['GET'])
def history():
    """저장된 대화 목록 반환"""
    conversations = load_conversations()
    return jsonify(conversations)

4단계: 웹 인터페이스

HTML 템플릿

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
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI 챗봇</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <div class="container">
        <header>
            <h1>🤖 AI 챗봇</h1>
            <div class="controls">
                <button onclick="clearChat()">대화 초기화</button>
                <button onclick="saveChat()">대화 저장</button>
                <select id="persona" onchange="changePersona()">
                    <option value="default">기본 어시스턴트</option>
                    <option value="teacher">Python 선생님</option>
                    <option value="translator">번역가</option>
                    <option value="poet">시인</option>
                </select>
            </div>
        </header>

        <div id="chat-box" class="chat-box"></div>

        <div class="input-area">
            <input type="text" id="user-input" placeholder="메시지를 입력하세요..."
                   onkeypress="handleKeyPress(event)">
            <button onclick="sendMessage()">전송</button>
        </div>
    </div>

    <script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>

CSS 스타일

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
/* static/style.css */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
}

.container {
    background: white;
    width: 90%;
    max-width: 800px;
    height: 90vh;
    border-radius: 20px;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
    display: flex;
    flex-direction: column;
}

header {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 20px;
    border-radius: 20px 20px 0 0;
}

header h1 {
    margin-bottom: 10px;
}

.controls {
    display: flex;
    gap: 10px;
    margin-top: 10px;
}

.controls button, .controls select {
    padding: 8px 15px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    background: white;
    color: #667eea;
    font-weight: bold;
}

.chat-box {
    flex: 1;
    overflow-y: auto;
    padding: 20px;
    background: #f5f5f5;
}

.message {
    margin-bottom: 15px;
    display: flex;
    align-items: flex-start;
}

.message.user {
    justify-content: flex-end;
}

.message .bubble {
    max-width: 70%;
    padding: 12px 18px;
    border-radius: 18px;
    word-wrap: break-word;
}

.message.user .bubble {
    background: #667eea;
    color: white;
}

.message.ai .bubble {
    background: white;
    color: #333;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}

.input-area {
    display: flex;
    padding: 20px;
    background: white;
    border-radius: 0 0 20px 20px;
    gap: 10px;
}

.input-area input {
    flex: 1;
    padding: 12px;
    border: 2px solid #ddd;
    border-radius: 25px;
    font-size: 14px;
}

.input-area button {
    padding: 12px 30px;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    border: none;
    border-radius: 25px;
    cursor: pointer;
    font-weight: bold;
}

.input-area button:hover {
    opacity: 0.9;
}

JavaScript 로직

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
// static/script.js
let conversationHistory = [];
let currentPersona = 'default';

const personas = {
    default: "당신은 친절하고 유용한 AI 어시스턴트입니다.",
    teacher: "당신은 Python을 가르치는 열정적인 선생님입니다. 쉽고 재미있게 설명합니다.",
    translator: "당신은 전문 번역가입니다. 주어진 텍스트를 정확하게 번역합니다.",
    poet: "당신은 시인입니다. 사용자의 말을 시적으로 표현합니다."
};

function changePersona() {
    const select = document.getElementById('persona');
    currentPersona = select.value;
    conversationHistory = [];
    clearChatBox();
    addMessage('ai', `${select.options[select.selectedIndex].text}로 변경되었습니다.`);
}

function clearChat() {
    conversationHistory = [];
    clearChatBox();
}

function clearChatBox() {
    document.getElementById('chat-box').innerHTML = '';
}

function addMessage(role, content) {
    const chatBox = document.getElementById('chat-box');
    const messageDiv = document.createElement('div');
    messageDiv.className = `message ${role}`;

    const bubble = document.createElement('div');
    bubble.className = 'bubble';
    bubble.textContent = content;

    messageDiv.appendChild(bubble);
    chatBox.appendChild(messageDiv);

    // 스크롤 맨 아래로
    chatBox.scrollTop = chatBox.scrollHeight;
}

async function sendMessage() {
    const input = document.getElementById('user-input');
    const message = input.value.trim();

    if (!message) return;

    // 사용자 메시지 표시
    addMessage('user', message);
    input.value = '';

    // 시스템 메시지 추가 (첫 메시지인 경우)
    if (conversationHistory.length === 0) {
        conversationHistory.push({
            role: 'system',
            content: personas[currentPersona]
        });
    }

    // 서버에 요청
    try {
        const response = await fetch('/chat', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                message: message,
                history: conversationHistory
            })
        });

        const data = await response.json();

        // AI 응답 표시
        addMessage('ai', data.response);

        // 대화 이력 업데이트
        conversationHistory = data.history;

    } catch (error) {
        addMessage('ai', '오류가 발생했습니다: ' + error.message);
    }
}

async function saveChat() {
    if (conversationHistory.length === 0) {
        alert('저장할 대화가 없습니다.');
        return;
    }

    try {
        const response = await fetch('/save', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                history: conversationHistory
            })
        });

        const data = await response.json();
        alert(data.message);

    } catch (error) {
        alert('저장 중 오류 발생: ' + error.message);
    }
}

function handleKeyPress(event) {
    if (event.key === 'Enter') {
        sendMessage();
    }
}

💻 완성된 전체 코드 (app.py)

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
"""
AI 챗봇 웹 애플리케이션
- Flask 웹 서버
- ChatGPT API 통합
- 대화 이력 저장
"""

from flask import Flask, render_template, request, jsonify
from openai import OpenAI
from dotenv import load_dotenv
import os
import json
from datetime import datetime

# 환경 변수 로드
load_dotenv()

app = Flask(__name__)
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

CONVERSATIONS_FILE = 'conversations.json'

def get_ai_response(messages, temperature=0.7):
    """ChatGPT로부터 응답 받기"""
    try:
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=messages,
            temperature=temperature,
            max_tokens=500
        )
        return response.choices[0].message.content
    except Exception as e:
        return f"오류 발생: {str(e)}"

def load_conversations():
    """저장된 대화 불러오기"""
    try:
        with open(CONVERSATIONS_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    except FileNotFoundError:
        return []

def save_conversation(conversation):
    """대화 저장하기"""
    conversations = load_conversations()
    new_conv = {
        'id': len(conversations) + 1,
        'timestamp': datetime.now().isoformat(),
        'messages': conversation
    }
    conversations.append(new_conv)

    with open(CONVERSATIONS_FILE, 'w', encoding='utf-8') as f:
        json.dump(conversations, f, ensure_ascii=False, indent=2)

@app.route('/')
def index():
    """메인 페이지"""
    return render_template('index.html')

@app.route('/chat', methods=['POST'])
def chat():
    """사용자 메시지 처리"""
    data = request.json
    user_message = data.get('message')
    conversation_history = data.get('history', [])

    # 사용자 메시지 추가
    conversation_history.append({"role": "user", "content": user_message})

    # AI 응답 생성
    ai_response = get_ai_response(conversation_history)

    # 응답을 대화에 추가
    conversation_history.append({"role": "assistant", "content": ai_response})

    return jsonify({
        'response': ai_response,
        'history': conversation_history
    })

@app.route('/save', methods=['POST'])
def save():
    """현재 대화 저장"""
    data = request.json
    conversation = data.get('history', [])

    if conversation:
        save_conversation(conversation)
        return jsonify({'status': 'success', 'message': '대화가 저장되었습니다.'})
    else:
        return jsonify({'status': 'error', 'message': '저장할 대화가 없습니다.'})

@app.route('/history', methods=['GET'])
def history():
    """저장된 대화 목록 반환"""
    conversations = load_conversations()
    return jsonify(conversations)

if __name__ == '__main__':
    print("=" * 50)
    print("AI 챗봇 서버 시작!")
    print("브라우저에서 http://localhost:5000 접속")
    print("=" * 50)
    app.run(debug=True, port=5000)

🚀 실행 방법

1. 프로젝트 설정

1
2
3
4
5
6
7
8
9
10
# 디렉토리 생성
mkdir chatbot
cd chatbot

# 가상환경 생성 (선택사항)
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate

# 라이브러리 설치
pip install flask openai python-dotenv

2. .env 파일 생성

1
OPENAI_API_KEY=your-api-key-here

3. 폴더 구조 만들기

1
mkdir templates static

4. 서버 실행

1
python app.py

5. 브라우저 접속

1
http://localhost:5000

📊 추가 기능 아이디어

1. 음성 입력/출력

1
2
3
4
5
6
# Web Speech API 사용 (JavaScript)
const recognition = new webkitSpeechRecognition();
recognition.onresult = (event) => {
    const text = event.results[0][0].transcript;
    sendMessage(text);
};

2. 다크 모드

1
2
3
function toggleDarkMode() {
    document.body.classList.toggle('dark-mode');
}

3. 대화 내보내기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@app.route('/export/<int:conversation_id>')
def export(conversation_id):
    conversations = load_conversations()
    conv = conversations[conversation_id - 1]

    # TXT로 내보내기
    text = "\n\n".join([
        f"{msg['role']}: {msg['content']}"
        for msg in conv['messages']
    ])

    return Response(
        text,
        mimetype="text/plain",
        headers={"Content-disposition": "attachment; filename=conversation.txt"}
    )

📝 요약

  1. Flask: 웹 서버 구축
  2. ChatGPT API: AI 대화 기능
  3. JSON: 대화 이력 저장
  4. HTML/CSS/JS: 사용자 인터페이스
  5. 완성: 나만의 AI 챗봇!

🎉 축하합니다!

Day 99를 완료하셨습니다!

지금까지 배운 모든 것을 활용해 실전 AI 애플리케이션을 만들었습니다. 내일은 100일 챌린지의 마지막 날입니다!


🤔 자주 묻는 질문 (FAQ)

Q1: Flask 실행 시 포트가 이미 사용 중이라는 에러가 나요!

A: 다른 프로그램이 5000번 포트를 사용하고 있습니다! 🔌

해결 방법 1: 포트 변경

1
2
3
# app.py 마지막 줄
if __name__ == '__main__':
    app.run(debug=True, port=5001)  # 5000 → 5001

해결 방법 2: 기존 프로세스 종료 (Windows)

1
2
3
4
5
# 포트 사용 중인 프로세스 찾기
netstat -ano | findstr :5000

# PID 확인 후 종료
taskkill /PID <PID번호> /F

해결 방법 3: 기존 프로세스 종료 (Mac/Linux)

1
2
3
4
5
# 포트 사용 중인 프로세스 찾기
lsof -i :5000

# 프로세스 종료
kill -9 <PID번호>

Q2: 대화 내용이 저장되지 않아요!

A: 파일 권한 또는 경로 문제일 수 있습니다! 💾

체크리스트:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1. 파일 생성 확인
import os
print(os.path.exists('conversations.json'))  # True여야 함

# 2. 초기 파일 생성
import json

if not os.path.exists('conversations.json'):
    with open('conversations.json', 'w', encoding='utf-8') as f:
        json.dump([], f)
    print("conversations.json 생성됨!")

# 3. 쓰기 권한 확인
try:
    with open('conversations.json', 'a') as f:
        pass
    print("쓰기 권한 있음!")
except Exception as e:
    print(f"쓰기 권한 없음: {e}")

디버깅 팁:

1
2
3
4
5
6
7
8
9
10
# save_conversation() 함수에 로그 추가
def save_conversation(conversation):
    try:
        conversations = load_conversations()
        conversations.append(conversation)
        with open('conversations.json', 'w', encoding='utf-8') as f:
            json.dump(conversations, f, ensure_ascii=False, indent=2)
        print(f"✅ 저장 성공! 총 {len(conversations)}개 대화")  # 로그
    except Exception as e:
        print(f"❌ 저장 실패: {e}")  # 에러 로그

Q3: API 요청이 너무 느려요. 어떻게 빠르게 할 수 있나요?

A: 모델을 바꾸거나 스트리밍을 사용하세요! ⚡

방법 1: 더 빠른 모델 사용

1
2
3
4
5
# gpt-4 → gpt-3.5-turbo (10배 빠름!)
response = client.chat.completions.create(
    model="gpt-3.5-turbo",  # 빠르고 저렴
    messages=messages
)

속도 비교: | 모델 | 속도 | 비용 | 품질 | |——|——|——|——| | gpt-4-turbo | 보통 | 비쌈 | 최고 | | gpt-3.5-turbo | 빠름 | 저렴 | 좋음 |

방법 2: 스트리밍 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 답변이 오는 대로 바로 표시 (ChatGPT처럼)
@app.route('/chat', methods=['POST'])
def chat():
    user_message = request.json['message']
    messages.append({"role": "user", "content": user_message})

    # 스트리밍 모드
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=messages,
        stream=True  # 스트리밍 활성화
    )

    def generate():
        full_response = ""
        for chunk in response:
            if chunk.choices[0].delta.content:
                content = chunk.choices[0].delta.content
                full_response += content
                yield f"data: {content}\n\n"  # 실시간 전송

        messages.append({"role": "assistant", "content": full_response})

    return Response(generate(), mimetype='text/event-stream')

방법 3: 최대 토큰 제한

1
2
3
4
5
response = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=messages,
    max_tokens=500  # 답변 길이 제한 (빠름!)
)

Q4: 챗봇 캐릭터를 만들고 싶어요!

A: 시스템 프롬프트를 활용하세요! 🎭

예시 1: 친절한 선생님

1
2
3
4
5
6
7
8
9
10
11
messages = [
    {
        "role": "system",
        "content": """당신은 초등학교 선생님입니다.
- 항상 친절하고 쉽게 설명합니다
- 이모지를 많이 사용합니다 😊
- 어려운 개념은 비유로 설명합니다
- 칭찬을 아끼지 않습니다
"""
    }
]

예시 2: 프로그래밍 멘토

1
2
3
4
5
6
7
8
9
10
11
messages = [
    {
        "role": "system",
        "content": """당신은 경력 10년의 Python 개발자입니다.
- 코드 예시를 항상 제공합니다
- 베스트 프랙티스를 강조합니다
- 에러를 보면 원인과 해결책을 알려줍니다
- 말투는 친근하지만 전문적입니다
"""
    }
]

예시 3: 영어 선생님

1
2
3
4
5
6
7
8
9
10
11
messages = [
    {
        "role": "system",
        "content": """You are an English teacher.
- Always respond in English
- Correct grammar mistakes politely
- Provide pronunciation tips
- Use simple words for beginners
"""
    }
]

동적 캐릭터 선택:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CHARACTERS = {
    "teacher": "당신은 친절한 선생님입니다...",
    "mentor": "당신은 프로그래밍 멘토입니다...",
    "translator": "당신은 번역가입니다..."
}

@app.route('/set_character', methods=['POST'])
def set_character():
    character = request.json['character']
    messages.clear()
    messages.append({
        "role": "system",
        "content": CHARACTERS[character]
    })
    return jsonify({"status": "success"})

Q5: API 비용이 걱정되는데 제한을 둘 수 있나요?

A: 여러 방법으로 비용을 통제할 수 있습니다! 💰

방법 1: OpenAI 대시보드에서 제한 설정

1
2
3
1. platform.openai.com/account/limits
2. "Usage limits" 설정
3. 월 $10 제한 등 설정

방법 2: 코드에서 대화 길이 제한

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MAX_MESSAGES = 10  # 최근 10개 메시지만 유지

def trim_messages(messages):
    # 시스템 메시지는 유지
    system_msg = [m for m in messages if m['role'] == 'system']
    other_msgs = [m for m in messages if m['role'] != 'system']

    # 최근 메시지만 유지
    if len(other_msgs) > MAX_MESSAGES:
        other_msgs = other_msgs[-MAX_MESSAGES:]

    return system_msg + other_msgs

# 사용
messages = trim_messages(messages)

방법 3: 토큰 수 체크

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import tiktoken

def count_tokens(messages, model="gpt-3.5-turbo"):
    encoding = tiktoken.encoding_for_model(model)
    num_tokens = 0
    for message in messages:
        num_tokens += len(encoding.encode(message['content']))
    return num_tokens

# 사용
tokens = count_tokens(messages)
if tokens > 3000:  # 3000 토큰 초과 시
    print(f"⚠️ 토큰 수 많음: {tokens}")
    messages = trim_messages(messages)

방법 4: 비용 계산

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# GPT-3.5-turbo 가격 (2025년 기준)
COST_PER_1K_INPUT = 0.0005   # $0.0005 / 1K tokens
COST_PER_1K_OUTPUT = 0.0015  # $0.0015 / 1K tokens

def calculate_cost(input_tokens, output_tokens):
    input_cost = (input_tokens / 1000) * COST_PER_1K_INPUT
    output_cost = (output_tokens / 1000) * COST_PER_1K_OUTPUT
    total = input_cost + output_cost
    return total

# 예시
input_tokens = 100
output_tokens = 200
cost = calculate_cost(input_tokens, output_tokens)
print(f"이번 요청 비용: ${cost:.4f}")  # $0.0003

실전 팁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 일일 예산 체크
DAILY_BUDGET = 1.0  # $1/day
daily_spent = 0.0

@app.route('/chat', methods=['POST'])
def chat():
    global daily_spent

    if daily_spent >= DAILY_BUDGET:
        return jsonify({
            "error": "일일 예산 초과! 내일 다시 시도하세요."
        }), 429

    # API 호출...
    # daily_spent += cost

Q6: 이 챗봇을 배포해서 친구들과 공유하고 싶어요!

A: 무료 호스팅 서비스를 활용하세요! 🌐

방법 1: Replit (가장 쉬움)

1
2
3
4
5
6
7
1. replit.com 가입
2. "Create Repl" → Python
3. 파일 업로드 (app.py, templates, static)
4. Secrets에 API 키 추가
5. Run 버튼 클릭!

→ 자동으로 URL 생성 (예: mybot.username.repl.co)

방법 2: PythonAnywhere

1
2
3
4
5
6
7
8
# 1. pythonanywhere.com 가입 (무료)
# 2. Bash 콘솔에서:
git clone <your-repo>
cd <your-project>
pip install --user -r requirements.txt

# 3. Web 탭에서 Flask 앱 설정
# 4. 환경 변수에 API 키 추가

방법 3: Render (권장!)

1
2
3
4
5
6
7
8
9
10
# render.yaml 생성
services:
  - type: web
    name: my-chatbot
    env: python
    buildCommand: pip install -r requirements.txt
    startCommand: gunicorn app:app
    envVars:
      - key: OPENAI_API_KEY
        sync: false  # 수동 입력

주의사항:

1
2
3
4
5
6
7
8
9
10
11
12
13
# ⚠️ API 키 보안!

# ❌ 나쁜 예
api_key = "sk-proj-..."  # 코드에 직접

# ✅ 좋은 예
import os
api_key = os.environ.get('OPENAI_API_KEY')

# requirements.txt도 잊지 마세요!
flask==3.0.0
openai==1.3.0
python-dotenv==1.0.0

무료 플랜 제한: | 서비스 | 제한 | |——–|——| | Replit | 항상 켜짐, 느림 | | PythonAnywhere | 일일 CPU 제한 | | Render | 15분 비활동 시 슬립 |

비용 관리:

  • 무료 플랜 사용 (Hobby)
  • API 키는 개인만 사용
  • 비용 제한 설정 (OpenAI 대시보드)

📚 다음 학습

Day 100: 포트폴리오 완성과 다음 단계 ⭐⭐⭐⭐

드디어 마지막 날! 지금까지의 여정을 정리하고 다음 단계를 계획합니다!


“100일의 여정이 거의 끝났습니다. 여러분은 이제 AI 개발자입니다!” 🚀

Day 99/100 Phase 10: AI/ML 입문 #100DaysOfPython
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.