119_ChatBot / gradio_interface.py
Muyeong Kim
Update to Gradio 5.0.0 for latest features and performance
adf1fbc
import gradio as gr
import time
from typing import List, Dict, Tuple
import os
from rag_chatbot import RAGChatbot, ChatResponse
class GradioInterface:
"""Gradio 웹 인터페이스 클래스"""
def __init__(self):
self.chatbot = RAGChatbot()
self.is_initialized = False
# 예시 질문
self.example_questions = [
"연차휴가 사용 방법을 알려주세요",
"정규근무시간은 어떻게 되나요?",
"당직근무 절차가 궁금합니다",
"인사평가는 언제 어떻게 진행되나요?",
"파견근무 신청 방법을 알려주세요",
"복무규정 위반 시 어떻게 되나요?"
]
def initialize_chatbot(self, docs_folder: str = None, force_rebuild: bool = False) -> str:
"""챗봇 초기화"""
try:
success = self.chatbot.initialize(docs_folder, force_rebuild)
if success:
self.is_initialized = True
return "✅ RAG 챗봇이 성공적으로 초기화되었습니다!"
else:
return "❌ 챗봇 초기화에 실패했습니다. documents 폴더에 파일이 있는지 확인해주세요."
except Exception as e:
return f"❌ 초기화 중 오류 발생: {str(e)}"
def format_chat_response(self, response: ChatResponse) -> Tuple[str, str]:
"""챗봇 응답을 채팅 형식으로 변환"""
# 메인 답변
answer_html = response.answer.replace('\n', '<br>')
# 신뢰도 색상
if response.confidence >= 0.8:
confidence_color = "green"
confidence_text = "높음"
elif response.confidence >= 0.5:
confidence_color = "orange"
confidence_text = "보통"
else:
confidence_color = "red"
confidence_text = "낮음"
# 정보 메시지
info_html = f"""
<div style="background-color: #f0f0f0; padding: 10px; border-radius: 5px; margin-top: 10px;">
<strong>📊 답변 정보</strong><br>
• 신뢰도: <span style="color: {confidence_color}; font-weight: bold;">{confidence_text} ({response.confidence:.2%})</span><br>
• 응답시간: {response.response_time:.2f}초<br>
• 참고문서: {len(response.sources)}
</div>
"""
# 출처 정보
if response.sources:
sources_html = "<br><strong>📚 참고자료:</strong><ul>"
for i, source in enumerate(response.sources, 1):
sources_html += f"<li><strong>{source['source']}</strong> (유사도: {source['similarity']})<br><small>{source['content'][:150]}...</small></li>"
sources_html += "</ul>"
info_html += sources_html
return answer_html, info_html
def chat_interface(self, message: str, history: List[List[str]]) -> List[List[str]]:
"""채팅 인터페이스 핸들러"""
if not self.is_initialized:
history.append([message, "⚠️ 챗봇이 초기화되지 않았습니다. 먼저 초기화를 눌러주세요."])
return history
if not message.strip():
return history
try:
# 답변 생성
response = self.chatbot.generate_answer(message, use_llm=False) # 템플릿 모드로 가볍게
answer, info = self.format_chat_response(response)
# 전체 응답
full_response = f"{answer}<br><br>{info}"
history.append([message, full_response])
except Exception as e:
error_response = f"❌ 답변 생성 중 오류 발생: {str(e)}"
history.append([message, error_response])
return history
def test_question(self, question: str) -> str:
"""예시 질문 테스트"""
if not self.is_initialized:
return "⚠️ 먼저 챗봇 초기화를 진행해주세요."
try:
response = self.chatbot.generate_answer(question, use_llm=False)
answer, info = self.format_chat_response(response)
return f"❓ 질문: {question}\n\n🤖 답변:\n{answer}\n\n{info}"
except Exception as e:
return f"❌ 테스트 중 오류 발생: {str(e)}"
def get_chatbot_stats(self) -> str:
"""챗봇 통계 정보"""
if not self.is_initialized:
return "챗봇이 초기화되지 않았습니다."
try:
stats = self.chatbot.get_stats()
stats_html = "<h3>🤖 챗봇 상태 정보</h3>"
if stats.get("status") == "initialized":
vector_stats = stats.get("vector_store", {})
stats_html += f"""
<ul>
<li>상태: <span style="color: green;">✅ 정상 작동 중</span></li>
<li>총 문서 수: {vector_stats.get('total_documents', 0)}개</li>
<li>임베딩 모델: {vector_stats.get('embedding_model', 'N/A')}</li>
<li>벡터 차원: {vector_stats.get('index_dimension', 'N/A')}</li>
<li>LLM 사용: {'✅' if stats.get('llm_available') else '❌ (템플릿 모드)'}</li>
</ul>
"""
else:
stats_html += "<p>⚠️ 챗봇이 초기화되지 않았습니다.</p>"
return stats_html
except Exception as e:
return f"❌ 통계 정보 조회 실패: {str(e)}"
def create_interface(self):
"""Gradio 인터페이스 생성"""
# 커스텀 CSS
custom_css = """
.chat-message {
padding: 15px;
border-radius: 10px;
margin: 10px 0;
}
.user-message {
background-color: #e3f2fd;
border-left: 4px solid #2196f3;
}
.assistant-message {
background-color: #f1f8e9;
border-left: 4px solid #4caf50;
}
.info-box {
background-color: #fff3e0;
border: 1px solid #ffb74d;
border-radius: 8px;
padding: 12px;
margin: 10px 0;
}
.stats-box {
background-color: #f5f5f5;
border-radius: 8px;
padding: 15px;
margin: 10px 0;
}
"""
with gr.Blocks(
title="소방 복무관리 RAG 챗봇",
theme=gr.themes.Soft(),
css=custom_css
) as interface:
gr.Markdown("# 🚒 소방 복무관리 RAG 챗봇")
gr.Markdown("소방업무 복무관리 규정 및 절차에 대한 질문에 답변해 드립니다.")
with gr.Tab("💬 채팅"):
with gr.Row():
with gr.Column(scale=4):
chatbot = gr.Chatbot(
height=500,
avatar_images=["👤", "🤖"]
)
msg = gr.Textbox(
placeholder="복무관리 관련 질문을 입력해주세요 (예: 연차휴가 사용 방법)",
label="질문 입력",
submit_btn="전송"
)
with gr.Row():
submit_btn = gr.Button("💬 전송", variant="primary")
clear_btn = gr.Button("🗑️ 대화 초기화")
with gr.Column(scale=1):
gr.Markdown("### 🚀 빠른 시작")
init_btn = gr.Button("🔧 챗봇 초기화", variant="secondary")
init_status = gr.HTML("⏳ 초기화를 눌러주세요.")
gr.Markdown("### 💡 예시 질문")
for question in self.example_questions:
gr.Button(question, size="sm").click(
fn=lambda q=question: q,
outputs=msg
)
gr.Markdown("### 📊 상태 정보")
stats_info = gr.HTML("상태 정보를 확인해주세요.")
with gr.Tab("🔍 테스트"):
gr.Markdown("### 예시 질문 테스트")
with gr.Row():
with gr.Column():
test_question = gr.Textbox(
placeholder="테스트할 질문을 입력하세요",
label="질문"
)
test_btn = gr.Button("🧪 테스트 실행", variant="primary")
with gr.Column():
test_result = gr.HTML("테스트 결과가 여기에 표시됩니다.")
gr.Markdown("### 통계 정보")
stats_display = gr.HTML(self.get_chatbot_stats())
with gr.Tab("⚙️ 설정"):
gr.Markdown("### 시스템 설정")
with gr.Row():
with gr.Column():
docs_folder = gr.Textbox(
value="documents",
label="문서 폴더 경로"
)
force_rebuild = gr.Checkbox(
label="강제 재구축",
info="체크 시 기존 인덱스를 새로构建"
)
with gr.Column():
rebuild_btn = gr.Button("🔄 인덱스 재구축", variant="primary")
gr.Markdown("### 시스템 정보")
system_info = gr.HTML("""
<div class="stats-box">
<h4>🤖 시스템 사양</h4>
<ul>
<li>임베딩 모델: jhgan/ko-sbert-nli</li>
<li>LLM 모델: beomi/Llama-3-Open-Ko-8B</li>
<li>청크 크기: 500</li>
<li>검색 문서 수: 3개</li>
</ul>
</div>
""")
# 이벤트 핸들러
def init_chatbot_wrapper():
status = self.initialize_chatbot()
return status, self.get_chatbot_stats()
init_btn.click(
fn=init_chatbot_wrapper,
outputs=[init_status, stats_info]
)
def rebuild_wrapper(docs_path, force):
return self.initialize_chatbot(docs_path, force)
rebuild_btn.click(
fn=rebuild_wrapper,
inputs=[docs_folder, force_rebuild],
outputs=stats_display
)
# 채팅 이벤트
def chat_wrapper(message, history):
return self.chat_interface(message, history), ""
msg.submit(
fn=chat_wrapper,
inputs=[msg, chatbot],
outputs=[chatbot, msg]
)
submit_btn.click(
fn=chat_wrapper,
inputs=[msg, chatbot],
outputs=[chatbot, msg]
)
clear_btn.click(
fn=lambda: ([], ""),
outputs=[chatbot, msg]
)
# 테스트 이벤트
test_btn.click(
fn=self.test_question,
inputs=test_question,
outputs=test_result
)
# 자동 새로고침 (통계 정보)
interface.load(
fn=self.get_chatbot_stats,
outputs=stats_info,
every=30 # 30초마다 새로고침
)
return interface
def launch(self, share: bool = False, server_port: int = 7860):
"""인터페이스 실행"""
interface = self.create_interface()
print("🚀 Gradio 웹 인터페이스 시작 중...")
print(f"📍 접속 주소: http://localhost:{server_port}")
interface.launch(
share=share,
server_port=server_port,
show_error=True,
show_tips=True,
inbrowser=True
)
# 메인 실행
if __name__ == "__main__":
# 웹 인터페이스 실행
gradio_app = GradioInterface()
gradio_app.launch(share=False, server_port=7860)