[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"}
)
📝 요약
- Flask: 웹 서버 구축
- ChatGPT API: AI 대화 기능
- JSON: 대화 이력 저장
- HTML/CSS/JS: 사용자 인터페이스
- 완성: 나만의 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
