LangChain/LlamaIndex 없이 '나만의 챗봇' 만들기
TL;DR — 프레임워크 없이 순수 코드로 AI 챗봇을 만드는 완벽 가이드. 백엔드(Python/FastAPI)와 프론트엔드(React) 구현부터 고급 기능까지 상세히 다룹니다.
LangChain/LlamaIndex 없이 '나만의 챗봇' 만들기
💡 "프레임워크 없이도 가능해요?"
안녕하세요! 시니어 개발자입니다. 최근 많은 분들이 이런 질문을 하시더군요.
"LangChain이나 LlamaIndex 없이는 챗봇을 못 만드나요?" "프레임워크가 너무 무거워서 부담스러워요..." "내가 원하는 기능만 딱 구현하고 싶은데..."
좋은 소식이 있습니다! 프레임워크 없이도 충분히 강력한 챗봇을 만들 수 있습니다. 오늘은 순수 코드로 나만의 챗봇을 구축하는 방법을 A부터 Z까지 공유하겠습니다.
프레임워크 없이 만드는 진짜 내 챗봇
🎯 왜 프레임워크 없이 만들까?
📊 프레임워크 vs 커스텀 구현 비교
| 항목 | 프레임워크 사용 | 커스텀 구현 |
|---|---|---|
| 초기 개발 속도 | 빠름 ⚡ | 보통 🚶 |
| 유연성 | 제한적 🔒 | 무한대 🚀 |
| 번들 크기 | 크다 (MB 단위) 📦 | 작다 (KB 단위) 🪶 |
| 학습 곡선 | 프레임워크 학습 필요 📚 | 기본 개념만 이해 🎯 |
| 유지보수 | 의존성 관리 필요 🔧 | 완전한 제어 가능 ⚙️ |
| 성능 최적화 | 제한적 ⚠️ | 자유로움 ✨ |
💪 커스텀 구현의 장점
- 가벼움: 필요한 기능만 구현
- 투명함: 모든 코드를 이해하고 제어
- 최적화: 특정 용도에 맞춰 성능 튜닝
- 학습 효과: AI 챗봇의 핵심 원리 습득
심플하지만 강력한 아키텍처
🏗️ 기본 아키텍처 설계
📐 핵심 컴포넌트
┌─────────────────────────────────────────┐
│ 사용자 인터페이스 │
│ (React/Vue/Vanilla JS) │
└────────────────┬────────────────────────┘
│
┌────────────────▼────────────────────────┐
│ API 게이트웨이 │
│ (Express/FastAPI) │
└────────────────┬────────────────────────┘
│
┌────────────────▼────────────────────────┐
│ 대화 관리자 │
│ (Context/Memory/Session) │
└────────────────┬────────────────────────┘
│
┌────────────────▼────────────────────────┐
│ AI 엔진 │
│ (OpenAI/Claude/Custom) │
└─────────────────────────────────────────┘
🔑 각 컴포넌트의 역할
- UI Layer: 사용자 입력/출력 처리
- API Gateway: 요청 라우팅, 인증, 속도 제한
- Conversation Manager: 대화 상태 관리
- AI Engine: 실제 응답 생성
💻 Step 1: 백엔드 구현 (Python)
🐍 기본 구조 설정
# chatbot.py
import os
import json
from datetime import datetime
from typing import List, Dict, Optional
import openai
from dataclasses import dataclass, asdict
# OpenAI API 설정
openai.api_key = os.getenv("OPENAI_API_KEY")
@dataclass
class Message:
"""메시지 데이터 클래스"""
role: str # 'user' or 'assistant'
content: str
timestamp: datetime = None
def __post_init__(self):
if self.timestamp is None:
self.timestamp = datetime.now()
def to_dict(self):
return {
"role": self.role,
"content": self.content,
"timestamp": self.timestamp.isoformat()
}
class ConversationMemory:
"""대화 메모리 관리 클래스"""
def __init__(self, max_messages: int = 10):
self.messages: List[Message] = []
self.max_messages = max_messages
def add_message(self, message: Message):
"""메시지 추가 (메모리 크기 제한)"""
self.messages.append(message)
if len(self.messages) > self.max_messages:
self.messages.pop(0)
def get_context(self) -> List[Dict]:
"""OpenAI API 형식으로 컨텍스트 반환"""
return [{"role": msg.role, "content": msg.content}
for msg in self.messages]
def clear(self):
"""대화 초기화"""
self.messages = []
🧠 챗봇 코어 엔진
class Chatbot:
"""커스텀 챗봇 클래스"""
def __init__(self,
model: str = "gpt-3.5-turbo",
temperature: float = 0.7,
max_tokens: int = 500,
system_prompt: str = None):
self.model = model
self.temperature = temperature
self.max_tokens = max_tokens
self.system_prompt = system_prompt or self._default_system_prompt()
self.memory = ConversationMemory()
def _default_system_prompt(self) -> str:
"""기본 시스템 프롬프트"""
return """당신은 친절하고 도움이 되는 AI 어시스턴트입니다.
사용자의 질문에 정확하고 유용한 답변을 제공하세요.
답변은 간결하면서도 충분한 정보를 포함해야 합니다."""
async def get_response(self, user_input: str) -> str:
"""사용자 입력에 대한 응답 생성"""
# 사용자 메시지 추가
user_message = Message(role="user", content=user_input)
self.memory.add_message(user_message)
# 전체 대화 컨텍스트 구성
messages = [
{"role": "system", "content": self.system_prompt}
] + self.memory.get_context()
try:
# OpenAI API 호출
response = await openai.ChatCompletion.acreate(
model=self.model,
messages=messages,
temperature=self.temperature,
max_tokens=self.max_tokens
)
# 응답 추출
assistant_response = response.choices[0].message.content
# 어시스턴트 메시지 메모리에 추가
assistant_message = Message(
role="assistant",
content=assistant_response
)
self.memory.add_message(assistant_message)
return assistant_response
except Exception as e:
return f"죄송합니다. 오류가 발생했습니다: {str(e)}"
def set_system_prompt(self, prompt: str):
"""시스템 프롬프트 변경"""
self.system_prompt = prompt
def clear_memory(self):
"""대화 기록 초기화"""
self.memory.clear()
🌐 FastAPI 서버 구현
# server.py
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Optional
import uvicorn
app = FastAPI()
# CORS 설정
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 요청/응답 모델
class ChatRequest(BaseModel):
message: str
session_id: Optional[str] = None
class ChatResponse(BaseModel):
response: str
session_id: str
# 세션별 챗봇 인스턴스 관리
sessions = {}
@app.post("/api/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
"""채팅 엔드포인트"""
# 세션 ID가 없으면 새로 생성
session_id = request.session_id or str(uuid.uuid4())
# 세션별 챗봇 인스턴스 가져오기 또는 생성
if session_id not in sessions:
sessions[session_id] = Chatbot()
chatbot = sessions[session_id]
try:
# 응답 생성
response = await chatbot.get_response(request.message)
return ChatResponse(
response=response,
session_id=session_id
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.delete("/api/chat/{session_id}")
async def clear_chat(session_id: str):
"""대화 기록 초기화"""
if session_id in sessions:
sessions[session_id].clear_memory()
return {"message": "대화가 초기화되었습니다."}
else:
raise HTTPException(status_code=404, detail="세션을 찾을 수 없습니다.")
@app.get("/api/health")
async def health_check():
"""헬스 체크"""
return {"status": "healthy"}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
깔끔한 백엔드 구조
🎨 Step 2: 프론트엔드 구현
⚛️ React 챗봇 UI
// Chatbot.jsx
import React, { useState, useEffect, useRef } from 'react';
import axios from 'axios';
import './Chatbot.css';
const Chatbot = () => {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [loading, setLoading] = useState(false);
const [sessionId, setSessionId] = useState(null);
const messagesEndRef = useRef(null);
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
// 자동 스크롤
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
// 메시지 전송
const sendMessage = async () => {
if (!input.trim()) return;
const userMessage = {
role: 'user',
content: input,
timestamp: new Date().toISOString()
};
setMessages(prev => [...prev, userMessage]);
setInput('');
setLoading(true);
try {
const response = await axios.post(`${API_URL}/api/chat`, {
message: input,
session_id: sessionId
});
const assistantMessage = {
role: 'assistant',
content: response.data.response,
timestamp: new Date().toISOString()
};
setMessages(prev => [...prev, assistantMessage]);
setSessionId(response.data.session_id);
} catch (error) {
console.error('Error:', error);
const errorMessage = {
role: 'assistant',
content: '죄송합니다. 오류가 발생했습니다. 다시 시도해주세요.',
timestamp: new Date().toISOString()
};
setMessages(prev => [...prev, errorMessage]);
} finally {
setLoading(false);
}
};
// Enter 키 처리
const handleKeyPress = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
};
// 대화 초기화
const clearChat = async () => {
if (sessionId) {
try {
await axios.delete(`${API_URL}/api/chat/${sessionId}`);
setMessages([]);
setSessionId(null);
} catch (error) {
console.error('Error clearing chat:', error);
}
}
};
return (
<div className="chatbot-container">
<div className="chatbot-header">
<h2>🤖 나만의 AI 챗봇</h2>
<button onClick={clearChat} className="clear-btn">
대화 초기화
</button>
</div>
<div className="messages-container">
{messages.map((message, index) => (
<div
key={index}
className={`message ${message.role}`}
>
<div className="message-content">
{message.content}
</div>
<div className="message-time">
{new Date(message.timestamp).toLocaleTimeString()}
</div>
</div>
))}
{loading && (
<div className="message assistant loading">
<div className="typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
<div className="input-container">
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="메시지를 입력하세요..."
rows="3"
disabled={loading}
/>
<button
onClick={sendMessage}
disabled={loading || !input.trim()}
>
전송
</button>
</div>
</div>
);
};
export default Chatbot;
🎨 스타일링 (CSS)
/* Chatbot.css */
.chatbot-container {
max-width: 800px;
margin: 0 auto;
height: 90vh;
display: flex;
flex-direction: column;
background: #f5f5f5;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.chatbot-header {
background: #2c3e50;
color: white;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.chatbot-header h2 {
margin: 0;
font-size: 1.5rem;
}
.clear-btn {
background: #e74c3c;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s;
}
.clear-btn:hover {
background: #c0392b;
}
.messages-container {
flex: 1;
overflow-y: auto;
padding: 1rem;
background: white;
}
.message {
margin-bottom: 1rem;
display: flex;
flex-direction: column;
}
.message.user {
align-items: flex-end;
}
.message.assistant {
align-items: flex-start;
}
.message-content {
max-width: 70%;
padding: 0.75rem 1rem;
border-radius: 10px;
word-wrap: break-word;
}
.message.user .message-content {
background: #3498db;
color: white;
}
.message.assistant .message-content {
background: #ecf0f1;
color: #2c3e50;
}
.message-time {
font-size: 0.75rem;
color: #7f8c8d;
margin-top: 0.25rem;
}
.typing-indicator {
display: flex;
align-items: center;
gap: 4px;
}
.typing-indicator span {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: #7f8c8d;
animation: typing 1.4s infinite;
}
.typing-indicator span:nth-child(2) {
animation-delay: 0.2s;
}
.typing-indicator span:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes typing {
0%, 60%, 100% {
transform: translateY(0);
}
30% {
transform: translateY(-10px);
}
}
.input-container {
display: flex;
padding: 1rem;
background: #ecf0f1;
gap: 1rem;
}
.input-container textarea {
flex: 1;
padding: 0.75rem;
border: 1px solid #bdc3c7;
border-radius: 5px;
resize: none;
font-family: inherit;
}
.input-container button {
padding: 0.75rem 2rem;
background: #3498db;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s;
}
.input-container button:hover:not(:disabled) {
background: #2980b9;
}
.input-container button:disabled {
background: #95a5a6;
cursor: not-allowed;
}
깔끔한 챗봇 UI
🚀 Step 3: 고급 기능 구현
💾 대화 내역 저장 (PostgreSQL)
# database.py
import asyncpg
from datetime import datetime
import json
class ChatDatabase:
"""대화 내역 데이터베이스 관리"""
def __init__(self, database_url: str):
self.database_url = database_url
self.pool = None
async def init(self):
"""데이터베이스 연결 풀 초기화"""
self.pool = await asyncpg.create_pool(self.database_url)
# 테이블 생성
async with self.pool.acquire() as conn:
await conn.execute('''
CREATE TABLE IF NOT EXISTS conversations (
id SERIAL PRIMARY KEY,
session_id VARCHAR(255) NOT NULL,
user_id VARCHAR(255),
message_role VARCHAR(50) NOT NULL,
message_content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_session_id
ON conversations(session_id);
''')
async def save_message(self, session_id: str, message: Message,
user_id: Optional[str] = None):
"""메시지 저장"""
async with self.pool.acquire() as conn:
await conn.execute('''
INSERT INTO conversations
(session_id, user_id, message_role, message_content)
VALUES ($1, $2, $3, $4)
''', session_id, user_id, message.role, message.content)
async def get_conversation_history(self, session_id: str) -> List[Message]:
"""대화 내역 조회"""
async with self.pool.acquire() as conn:
rows = await conn.fetch('''
SELECT message_role, message_content, created_at
FROM conversations
WHERE session_id = $1
ORDER BY created_at ASC
''', session_id)
return [
Message(
role=row['message_role'],
content=row['message_content'],
timestamp=row['created_at']
)
for row in rows
]
🔍 의미 검색 기능 (Embeddings)
# embeddings.py
import numpy as np
from typing import List, Tuple
class SemanticSearch:
"""임베딩 기반 의미 검색"""
def __init__(self):
self.embeddings = []
self.documents = []
async def get_embedding(self, text: str) -> List[float]:
"""텍스트를 임베딩 벡터로 변환"""
response = await openai.Embedding.acreate(
model="text-embedding-ada-002",
input=text
)
return response['data'][0]['embedding']
async def add_document(self, document: str):
"""문서 추가 및 임베딩 생성"""
embedding = await self.get_embedding(document)
self.embeddings.append(embedding)
self.documents.append(document)
def cosine_similarity(self, a: List[float], b: List[float]) -> float:
"""코사인 유사도 계산"""
a = np.array(a)
b = np.array(b)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
async def search(self, query: str, top_k: int = 5) -> List[Tuple[str, float]]:
"""쿼리와 가장 유사한 문서 검색"""
query_embedding = await self.get_embedding(query)
similarities = [
(doc, self.cosine_similarity(query_embedding, emb))
for doc, emb in zip(self.documents, self.embeddings)
]
# 유사도 기준으로 정렬
similarities.sort(key=lambda x: x[1], reverse=True)
return similarities[:top_k]
# 챗봇에 검색 기능 통합
class EnhancedChatbot(Chatbot):
"""검색 기능이 추가된 챗봇"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.search_engine = SemanticSearch()
self.knowledge_base_loaded = False
async def load_knowledge_base(self, documents: List[str]):
"""지식 베이스 로드"""
for doc in documents:
await self.search_engine.add_document(doc)
self.knowledge_base_loaded = True
async def get_response(self, user_input: str) -> str:
"""검색 결과를 활용한 응답 생성"""
context = ""
# 지식 베이스에서 관련 정보 검색
if self.knowledge_base_loaded:
search_results = await self.search_engine.search(user_input, top_k=3)
if search_results:
context = "\n\n관련 정보:\n"
for doc, score in search_results:
if score > 0.7: # 유사도 임계값
context += f"- {doc}\n"
# 컨텍스트를 포함한 프롬프트 생성
enhanced_input = user_input
if context:
enhanced_input = f"{user_input}\n{context}"
return await super().get_response(enhanced_input)
🔐 인증 및 권한 관리
# auth.py
from datetime import datetime, timedelta
import jwt
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
class AuthManager:
"""인증 관리자"""
def __init__(self, secret_key: str):
self.secret_key = secret_key
def create_access_token(self, user_id: str, expires_delta: timedelta = None):
"""JWT 액세스 토큰 생성"""
to_encode = {"sub": user_id}
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(hours=24)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, self.secret_key, algorithm="HS256")
return encoded_jwt
def verify_token(self, token: str):
"""토큰 검증"""
try:
payload = jwt.decode(token, self.secret_key, algorithms=["HS256"])
user_id: str = payload.get("sub")
if user_id is None:
return None
return user_id
except jwt.PyJWTError:
return None
# FastAPI에 인증 미들웨어 추가
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
security = HTTPBearer()
auth_manager = AuthManager(secret_key="your-secret-key")
async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
"""현재 사용자 가져오기"""
token = credentials.credentials
user_id = auth_manager.verify_token(token)
if user_id is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return user_id
# 보호된 엔드포인트
@app.post("/api/chat/protected")
async def protected_chat(
request: ChatRequest,
current_user: str = Depends(get_current_user)
):
"""인증이 필요한 채팅 엔드포인트"""
# 사용자별 챗봇 인스턴스 관리
user_session_id = f"{current_user}:{request.session_id}"
# ... 나머지 로직
고급 기능으로 더 똑똑한 챗봇
📊 성능 최적화
⚡ 응답 속도 개선
# 1. 응답 스트리밍
async def stream_response(self, user_input: str):
"""스트리밍 방식으로 응답 생성"""
messages = [
{"role": "system", "content": self.system_prompt}
] + self.memory.get_context()
response = await openai.ChatCompletion.acreate(
model=self.model,
messages=messages,
temperature=self.temperature,
max_tokens=self.max_tokens,
stream=True # 스트리밍 활성화
)
full_response = ""
async for chunk in response:
if chunk['choices'][0]['delta'].get('content'):
content = chunk['choices'][0]['delta']['content']
full_response += content
yield content
# 완성된 응답을 메모리에 저장
assistant_message = Message(role="assistant", content=full_response)
self.memory.add_message(assistant_message)
# 2. 캐싱 구현
from functools import lru_cache
import hashlib
class CachedChatbot(Chatbot):
"""캐싱이 적용된 챗봇"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.cache = {}
self.cache_ttl = 3600 # 1시간
def _get_cache_key(self, messages: List[Dict]) -> str:
"""캐시 키 생성"""
content = json.dumps(messages, sort_keys=True)
return hashlib.md5(content.encode()).hexdigest()
async def get_response(self, user_input: str) -> str:
"""캐시를 활용한 응답 생성"""
messages = [
{"role": "system", "content": self.system_prompt}
] + self.memory.get_context()
cache_key = self._get_cache_key(messages)
# 캐시 확인
if cache_key in self.cache:
cached_response, timestamp = self.cache[cache_key]
if datetime.now().timestamp() - timestamp < self.cache_ttl:
return cached_response
# 캐시 미스 시 일반 응답 생성
response = await super().get_response(user_input)
# 캐시 저장
self.cache[cache_key] = (response, datetime.now().timestamp())
return response
📈 모니터링 및 분석
# monitoring.py
from prometheus_client import Counter, Histogram, Gauge
import time
# 메트릭 정의
request_count = Counter('chatbot_requests_total', 'Total requests')
request_duration = Histogram('chatbot_request_duration_seconds', 'Request duration')
active_sessions = Gauge('chatbot_active_sessions', 'Active sessions')
error_count = Counter('chatbot_errors_total', 'Total errors')
class MonitoredChatbot(Chatbot):
"""모니터링이 적용된 챗봇"""
@request_duration.time()
async def get_response(self, user_input: str) -> str:
"""모니터링이 적용된 응답 생성"""
request_count.inc()
try:
response = await super().get_response(user_input)
return response
except Exception as e:
error_count.inc()
raise e
# 로깅 설정
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class LoggedChatbot(MonitoredChatbot):
"""로깅이 추가된 챗봇"""
async def get_response(self, user_input: str) -> str:
logger.info(f"User input: {user_input[:100]}...")
start_time = time.time()
response = await super().get_response(user_input)
duration = time.time() - start_time
logger.info(f"Response generated in {duration:.2f}s")
return response
🎯 실전 프로젝트: 도메인 특화 챗봇
💼 고객 서비스 챗봇
class CustomerServiceBot(EnhancedChatbot):
"""고객 서비스 특화 챗봇"""
def __init__(self):
system_prompt = """당신은 전문적인 고객 서비스 상담원입니다.
다음 원칙을 따라주세요:
1. 항상 공손하고 전문적인 태도 유지
2. 고객의 문제를 정확히 파악
3. 단계별로 명확한 해결책 제시
4. 추가 도움이 필요한지 확인
5. 긍정적인 마무리
"""
super().__init__(
system_prompt=system_prompt,
temperature=0.3 # 더 일관된 응답을 위해 낮은 temperature
)
# FAQ 데이터베이스 로드
self.load_faq_database()
async def load_faq_database(self):
"""FAQ 데이터베이스 로드"""
faqs = [
"Q: 배송은 얼마나 걸리나요? A: 일반 배송은 2-3일, 특급 배송은 당일 도착합니다.",
"Q: 반품은 어떻게 하나요? A: 구매 후 14일 이내 미개봉 상품에 한해 반품 가능합니다.",
"Q: 결제 방법은 무엇이 있나요? A: 신용카드, 계좌이체, 간편결제를 지원합니다.",
# ... 더 많은 FAQ
]
await self.load_knowledge_base(faqs)
async def get_response(self, user_input: str) -> str:
"""고객 서비스에 최적화된 응답"""
# 감정 분석
sentiment = await self.analyze_sentiment(user_input)
# 부정적 감정일 경우 더 신중한 응답
if sentiment == "negative":
self.temperature = 0.2
response = await super().get_response(user_input)
# 응답에 이모지 추가 (친근감)
if sentiment == "positive":
response = "😊 " + response
elif sentiment == "negative":
response = "🤝 " + response
return response
async def analyze_sentiment(self, text: str) -> str:
"""간단한 감정 분석"""
negative_words = ["화나", "짜증", "못", "안돼", "최악", "나쁜"]
positive_words = ["좋", "감사", "훌륭", "최고", "만족"]
text_lower = text.lower()
negative_score = sum(1 for word in negative_words if word in text_lower)
positive_score = sum(1 for word in positive_words if word in text_lower)
if negative_score > positive_score:
return "negative"
elif positive_score > negative_score:
return "positive"
else:
return "neutral"
전문적인 고객 서비스 봇
🚀 배포 및 운영
🐳 Docker 컨테이너화
# Dockerfile
FROM python:3.9-slim
WORKDIR /app
# 의존성 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 앱 복사
COPY . .
# 환경 변수
ENV PYTHONUNBUFFERED=1
# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD curl -f http://localhost:8000/api/health || exit 1
# 실행
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8000"]
☸️ Kubernetes 배포
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: chatbot-api
spec:
replicas: 3
selector:
matchLabels:
app: chatbot-api
template:
metadata:
labels:
app: chatbot-api
spec:
containers:
- name: chatbot-api
image: your-registry/chatbot-api:latest
ports:
- containerPort: 8000
env:
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: chatbot-secrets
key: openai-api-key
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: chatbot-api-service
spec:
selector:
app: chatbot-api
ports:
- protocol: TCP
port: 80
targetPort: 8000
type: LoadBalancer
📊 결론: 프레임워크 없이도 충분해요!
✅ 우리가 구현한 것들
핵심 챗봇 엔진 ✨
- 대화 컨텍스트 관리
- 메모리 최적화
- 세션 관리
고급 기능 🚀
- 의미 검색 (Embeddings)
- 대화 내역 저장
- 인증 및 권한 관리
성능 최적화 ⚡
- 응답 스트리밍
- 캐싱
- 모니터링
실전 응용 💼
- 도메인 특화 봇
- 감정 분석
- FAQ 통합
💡 핵심 교훈
- 프레임워크는 도구일 뿐: 핵심 원리를 이해하면 직접 구현 가능
- 필요한 것만 구현: 불필요한 복잡성 제거
- 완전한 제어권: 모든 부분을 커스터마이징 가능
- 학습 효과: AI 챗봇의 작동 원리 완벽 이해
🎯 이런 분들께 추천
- 가벼운 챗봇이 필요한 경우
- 특정 기능만 필요한 경우
- 완전한 커스터마이징이 필요한 경우
- AI 챗봇의 원리를 깊이 이해하고 싶은 경우
다음 포스트 예고: "단순 반복 업무를 자동화하는 'AI 에이전트' 구축 실전 가이드"
여러분이 만든 커스텀 챗봇 경험을 댓글로 공유해주세요! 🤖
태그: #Chatbot #CustomChatbot #NoPramework #OpenAI #Python #FastAPI #React #AIEngineering #챗봇개발 #커스텀챗봇