feat(animal-chat): 로깅 시스템 구축
- SQLite DB: animal_chat_logs.db - 로거 모듈: utils/animal_chat_logger.py - 단계별 로깅: - MSSQL (보유 동물약): 개수, 소요시간 - PostgreSQL (RAG): 개수, 소요시간 - LanceDB (벡터 검색): 상위 N개, 유사도, 소스, 소요시간 - OpenAI: 모델, 토큰(입력/출력), 비용, 소요시간 - Admin 페이지: /admin/animal-chat-logs - API: /api/animal-chat-logs - 통계: 총 대화, 평균 응답시간, 총 토큰, 총 비용
This commit is contained in:
@@ -3142,6 +3142,13 @@ def api_animal_chat():
|
||||
}
|
||||
"""
|
||||
try:
|
||||
import time
|
||||
from utils.animal_chat_logger import ChatLogEntry, log_chat
|
||||
|
||||
# 로그 엔트리 초기화
|
||||
log_entry = ChatLogEntry()
|
||||
total_start = time.time()
|
||||
|
||||
if not OPENAI_AVAILABLE:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
@@ -3157,12 +3164,24 @@ def api_animal_chat():
|
||||
'message': '메시지를 입력해주세요.'
|
||||
}), 400
|
||||
|
||||
# 보유 동물약 목록 조회
|
||||
animal_drugs = _get_animal_drugs()
|
||||
# 입력 로깅
|
||||
last_user_msg = next((m['content'] for m in reversed(messages) if m.get('role') == 'user'), '')
|
||||
log_entry.user_message = last_user_msg
|
||||
log_entry.history_length = len(messages)
|
||||
log_entry.session_id = data.get('session_id', '')
|
||||
|
||||
# APC가 있는 제품의 상세 정보 조회 (RAG)
|
||||
# 보유 동물약 목록 조회 (MSSQL)
|
||||
mssql_start = time.time()
|
||||
animal_drugs = _get_animal_drugs()
|
||||
log_entry.mssql_drug_count = len(animal_drugs)
|
||||
log_entry.mssql_duration_ms = int((time.time() - mssql_start) * 1000)
|
||||
|
||||
# APC가 있는 제품의 상세 정보 조회 (PostgreSQL RAG)
|
||||
pgsql_start = time.time()
|
||||
apc_codes = [d['apc'] for d in animal_drugs if d.get('apc')]
|
||||
rag_data = _get_animal_drug_rag(apc_codes) if apc_codes else {}
|
||||
log_entry.pgsql_rag_count = len(rag_data)
|
||||
log_entry.pgsql_duration_ms = int((time.time() - pgsql_start) * 1000)
|
||||
|
||||
available_products_text = ""
|
||||
if animal_drugs:
|
||||
@@ -3194,15 +3213,19 @@ def api_animal_chat():
|
||||
|
||||
# 벡터 DB 검색 (LanceDB RAG)
|
||||
vector_context = ""
|
||||
vector_start = time.time()
|
||||
try:
|
||||
from utils.animal_rag import get_animal_rag
|
||||
# 마지막 사용자 메시지로 검색
|
||||
last_user_msg = next((m['content'] for m in reversed(messages) if m.get('role') == 'user'), '')
|
||||
if last_user_msg:
|
||||
rag = get_animal_rag()
|
||||
vector_results = rag.search(last_user_msg, n_results=3)
|
||||
log_entry.vector_results_count = len(vector_results)
|
||||
log_entry.vector_top_scores = [r.get('score', 0) for r in vector_results]
|
||||
log_entry.vector_sources = [f"{r.get('source', '')}#{r.get('section', '')}" for r in vector_results]
|
||||
vector_context = rag.get_context_for_chat(last_user_msg, n_results=3)
|
||||
except Exception as e:
|
||||
logging.warning(f"벡터 검색 실패 (무시): {e}")
|
||||
log_entry.vector_duration_ms = int((time.time() - vector_start) * 1000)
|
||||
|
||||
# System Prompt 구성
|
||||
system_prompt = ANIMAL_CHAT_SYSTEM_PROMPT.format(
|
||||
@@ -3211,6 +3234,7 @@ def api_animal_chat():
|
||||
)
|
||||
|
||||
# OpenAI API 호출
|
||||
openai_start = time.time()
|
||||
client = OpenAI(api_key=OPENAI_API_KEY)
|
||||
|
||||
api_messages = [{"role": "system", "content": system_prompt}]
|
||||
@@ -3229,6 +3253,13 @@ def api_animal_chat():
|
||||
|
||||
ai_response = response.choices[0].message.content
|
||||
|
||||
# OpenAI 로깅
|
||||
log_entry.openai_model = OPENAI_MODEL
|
||||
log_entry.openai_prompt_tokens = response.usage.prompt_tokens
|
||||
log_entry.openai_completion_tokens = response.usage.completion_tokens
|
||||
log_entry.openai_total_tokens = response.usage.total_tokens
|
||||
log_entry.openai_duration_ms = int((time.time() - openai_start) * 1000)
|
||||
|
||||
# 응답에서 언급된 보유 제품 찾기 (부분 매칭)
|
||||
mentioned_products = []
|
||||
# 공백 제거한 버전도 준비 (AI가 띄어쓰기 넣을 수 있음)
|
||||
@@ -3289,6 +3320,12 @@ def api_animal_chat():
|
||||
'category': drug.get('category') # 분류 (내부구충제, 심장사상충약 등)
|
||||
})
|
||||
|
||||
# 최종 로깅
|
||||
log_entry.assistant_response = ai_response
|
||||
log_entry.products_mentioned = [p['name'] for p in mentioned_products[:5]]
|
||||
log_entry.total_duration_ms = int((time.time() - total_start) * 1000)
|
||||
log_chat(log_entry)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': ai_response,
|
||||
@@ -3299,18 +3336,27 @@ def api_animal_chat():
|
||||
}
|
||||
})
|
||||
|
||||
except RateLimitError:
|
||||
except RateLimitError as e:
|
||||
log_entry.error = f"RateLimitError: {e}"
|
||||
log_entry.total_duration_ms = int((time.time() - total_start) * 1000)
|
||||
log_chat(log_entry)
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'AI 사용량 한도에 도달했습니다. 잠시 후 다시 시도해주세요.'
|
||||
}), 429
|
||||
except APITimeoutError:
|
||||
except APITimeoutError as e:
|
||||
log_entry.error = f"APITimeoutError: {e}"
|
||||
log_entry.total_duration_ms = int((time.time() - total_start) * 1000)
|
||||
log_chat(log_entry)
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'AI 응답 시간이 초과되었습니다. 다시 시도해주세요.'
|
||||
}), 504
|
||||
except Exception as e:
|
||||
logging.error(f"동물약 챗봇 오류: {e}")
|
||||
log_entry.error = str(e)
|
||||
log_entry.total_duration_ms = int((time.time() - total_start) * 1000)
|
||||
log_chat(log_entry)
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류가 발생했습니다: {str(e)}'
|
||||
@@ -8008,6 +8054,44 @@ def mobile_upload_page(session_id):
|
||||
''', session_id=session_id, barcode=barcode)
|
||||
|
||||
|
||||
# ============================================
|
||||
# 동물약 챗봇 로그 API
|
||||
# ============================================
|
||||
|
||||
@app.route('/admin/animal-chat-logs')
|
||||
def admin_animal_chat_logs():
|
||||
"""동물약 챗봇 로그 페이지"""
|
||||
return render_template('admin_animal_chat_logs.html')
|
||||
|
||||
|
||||
@app.route('/api/animal-chat-logs')
|
||||
def api_animal_chat_logs():
|
||||
"""동물약 챗봇 로그 조회 API"""
|
||||
from utils.animal_chat_logger import get_logs, get_stats
|
||||
|
||||
date_from = request.args.get('date_from')
|
||||
date_to = request.args.get('date_to')
|
||||
error_only = request.args.get('error_only') == 'true'
|
||||
limit = int(request.args.get('limit', 100))
|
||||
offset = int(request.args.get('offset', 0))
|
||||
|
||||
logs = get_logs(
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
date_from=date_from,
|
||||
date_to=date_to,
|
||||
error_only=error_only
|
||||
)
|
||||
|
||||
stats = get_stats(date_from=date_from, date_to=date_to)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'logs': logs,
|
||||
'stats': stats
|
||||
})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os
|
||||
|
||||
|
||||
Reference in New Issue
Block a user