앞선 두 시리즈에서 RAG의 기본 개념과 실제 구현 사례를 살펴봤다면, 이번 마지막 시리즈에서는 운영 환경에서의 핵심 최적화 기법을 다뤄보겠습니다.
실제 운영에서는 다음 메트릭들을 반드시 모니터링해야 합니다:
@dataclass
class PerformanceMetrics:
response_time: float
retrieval_time: float
llm_time: float
total_tokens: int
similarity_scores: List[float]
memory_usage: float
class RAGMonitor:
def __init__(self):
self.metrics_history = deque(maxlen=1000)
self.thresholds = {
'response_time': 3.0, # 3초
'memory_usage': 85.0, # 85%
}
def get_performance_summary(self) -> Dict:
"""성능 요약 생성"""
recent = list(self.metrics_history)[-100:]
return {
"avg_response_time": sum(m.response_time for m in recent) / len(recent),
"avg_similarity_score": sum(sum(m.similarity_scores)/len(m.similarity_scores) for m in recent) / len(recent),
"total_tokens_used": sum(m.total_tokens for m in recent),
"avg_memory_usage": sum(m.memory_usage for m in recent) / len(recent)
}
간단한 HTML 대시보드로 실시간 모니터링:
def generate_dashboard_html(summary: Dict) -> str:
return f"""
<h1>RAG 시스템 성능 대시보드</h1>
<div>평균 응답 시간: {summary['avg_response_time']:.2f}s</div>
<div>메모리 사용량: {summary['avg_memory_usage']:.1f}%</div>
<div>총 토큰 사용량: {summary['total_tokens_used']:,}</div>
"""
쿼리 특성에 따라 검색 전략을 동적으로 선택:
class AdaptiveRetriever:
def __init__(self, vectorstore):
self.vectorstore = vectorstore
self.strategy_performance = {
'semantic': {'avg_time': 0, 'count': 0},
'hybrid': {'avg_time': 0, 'count': 0}
}
def adaptive_search(self, query: str, k: int = 5):
"""쿼리 분석 후 최적 전략 선택"""
query_analysis = self._analyze_query(query)
# 쿼리 길이가 길면 하이브리드, 짧으면 의미 검색
if query_analysis['length'] > 10:
return self._hybrid_search(query, k)
else:
return self._semantic_search(query, k)
def _analyze_query(self, query: str) -> Dict:
return {
'length': len(query.split()),
'has_keywords': any(word.isupper() for word in query.split()),
'question_type': self._classify_question(query)
}
def _classify_question(self, query: str) -> str:
query_lower = query.lower()
if any(word in query_lower for word in ['누구', '캐릭터']):
return 'character'
elif any(word in query_lower for word in ['어디', '장소']):
return 'location'
return 'general'
콘텐츠 타입에 따른 최적 청크 크기:
class DynamicChunking:
def __init__(self):
self.optimal_sizes = {
'character': 800, # NPC 정보
'location': 1200, # 장소 정보
'rule': 1500, # 게임 규칙
'general': 1000 # 기본값
}
def get_optimal_chunk_size(self, content_type: str) -> int:
return self.optimal_sizes.get(content_type, 1000)
토큰 사용량을 효율적으로 관리:
class ContextManager:
def __init__(self, max_tokens: int = 4000):
self.max_tokens = max_tokens
self.token_weights = {
'system_prompt': 0.2, # 20%
'retrieved_docs': 0.5, # 50%
'conversation': 0.2, # 20%
'current_query': 0.1 # 10%
}
def optimize_context(self, system_prompt: str, docs: List,
history: List, query: str) -> Dict:
"""컨텍스트 최적화"""
allocated = {
component: int(self.max_tokens * weight)
for component, weight in self.token_weights.items()
}
# 문서 우선순위 정렬 후 토큰 할당량에 맞춰 선택
optimized_docs = self._select_top_docs(docs, allocated['retrieved_docs'])
# 최근 대화만 포함
optimized_history = history[-5:] if len(history) > 5 else history
return {
'system_prompt': system_prompt,
'retrieved_docs': optimized_docs,
'conversation_history': optimized_history,
'current_query': query
}
상황별 맞춤형 프롬프트:
class PromptOptimizer:
def __init__(self):
self.templates = {
'character': "NPC {character_name}로서 {situation}에 반응해주세요.",
'combat': "전투 상황에서 {action}의 결과를 묘사해주세요.",
'general': "TRPG GM으로서 {context}를 바탕으로 응답해주세요."
}
def get_optimized_prompt(self, query_type: str, **kwargs) -> str:
template = self.templates.get(query_type, self.templates['general'])
return template.format(**kwargs)
쿼리 복잡도에 따른 적절한 모델 선택:
class LLMRouter:
def __init__(self):
self.models = {
'simple': {'name': 'deepseek', 'cost': 0.0000002, 'quality': 0.8},
'complex': {'name': 'claude-sonnet', 'cost': 0.000015, 'quality': 0.95}
}
def select_model(self, query_complexity: str, budget_mode: str = 'balanced'):
"""쿼리 복잡도와 예산에 따른 모델 선택"""
if budget_mode == 'cost_first':
return min(self.models.keys(), key=lambda x: self.models[x]['cost'])
elif query_complexity == 'high':
return 'complex'
else:
return 'simple'
def estimate_cost(self, model_type: str, tokens: int) -> float:
model_info = self.models[model_type]
return tokens * model_info['cost']
유사한 쿼리에 대한 캐시 활용:
class ResponseCache:
def __init__(self, ttl_hours: int = 24):
self.cache = {}
self.ttl = timedelta(hours=ttl_hours)
self.hits = 0
self.misses = 0
def get_cached_response(self, query: str) -> Optional[str]:
"""캐시된 응답 검색"""
query_hash = hashlib.md5(query.encode()).hexdigest()
if query_hash in self.cache:
cached_item = self.cache[query_hash]
if datetime.now() - cached_item['timestamp'] < self.ttl:
self.hits += 1
return cached_item['response']
self.misses += 1
return None
def store_response(self, query: str, response: str):
"""응답 캐시 저장"""
query_hash = hashlib.md5(query.encode()).hexdigest()
self.cache[query_hash] = {
'response': response,
'timestamp': datetime.now()
}
class FeedbackSystem:
def __init__(self):
self.feedback_data = []
def collect_feedback(self, query: str, response: str, rating: int, comments: str = ""):
"""피드백 수집"""
feedback = {
'timestamp': datetime.now(),
'query': query,
'response': response,
'rating': rating, # 1-5
'comments': comments,
'query_type': self._classify_query(query)
}
self.feedback_data.append(feedback)
# 낮은 평점 즉시 분석
if rating <= 2:
self._analyze_negative_feedback(feedback)
def generate_improvement_report(self, days: int = 7) -> Dict:
"""개선 리포트 생성"""
recent = [f for f in self.feedback_data
if f['timestamp'] > datetime.now() - timedelta(days=days)]
if not recent:
return {"message": "분석할 데이터 없음"}
avg_rating = sum(f['rating'] for f in recent) / len(recent)
# 타입별 성능
type_ratings = defaultdict(list)
for f in recent:
type_ratings[f['query_type']].append(f['rating'])
return {
'period': f"최근 {days}일",
'total_feedback': len(recent),
'average_rating': avg_rating,
'type_performance': {
qtype: sum(ratings)/len(ratings)
for qtype, ratings in type_ratings.items()
}
}
class AutoTuner:
def __init__(self, feedback_system):
self.feedback_system = feedback_system
self.current_config = {
'chunk_size': 1000,
'search_k': 5,
'temperature': 0.7
}
def auto_tune(self) -> Dict:
"""피드백 기반 자동 튜닝"""
report = self.feedback_system.generate_improvement_report()
if report.get('average_rating', 0) < 3.5:
# 성능이 낮으면 파라미터 조정
if report['average_rating'] < 3.0:
self.current_config['search_k'] = min(self.current_config['search_k'] + 1, 8)
self.current_config['chunk_size'] = max(self.current_config['chunk_size'] - 100, 600)
return {'tuned': True, 'new_config': self.current_config}
return {'tuned': False, 'reason': '성능 양호'}
FROM python:3.13-slim
WORKDIR /app
# 의존성 설치
COPY pyproject.toml ./
RUN pip install uv && uv sync
# 애플리케이션 복사
COPY . .
# 헬스체크
HEALTHCHECK --interval=30s --timeout=10s \
CMD curl -f http://localhost:8001/health || exit 1
EXPOSE 8001
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001"]
class HealthChecker:
async def check_system_health(self) -> Dict:
"""시스템 전체 상태 확인"""
checks = {
'database': self._check_db(),
'embedding': self._check_embedding(),
'llm': self._check_llm()
}
all_healthy = all(checks.values())
return {
'status': 'healthy' if all_healthy else 'unhealthy',
'services': checks,
'timestamp': datetime.now().isoformat()
}
모니터링: 응답시간, 메모리, 토큰 사용량 추적
검색 최적화: 적응형 전략, 동적 청크 크기
비용 관리: 스마트 라우팅, 응답 캐싱
품질 개선: 피드백 수집, 자동 튜닝
ㄴ 배포 관리: 컨테이너화, 헬스체크
RAG 시스템은 지속적인 모니터링과 개선을 통해 더욱 강력해집니다. 이 가이드가 여러분의 RAG 시스템 운영에 도움이 되기를 바랍니다!
참고 자료: