"""호서대 IR 챗봇 — VOC + KPI 통합. 두 가지 모드: - VOC: 에브리타임 게시글 RAG (Haiku 4.5 + ChromaDB) - KPI: 38개 핵심성과지표 정형 데이터 (Haiku 4.5, 對外秘) 자동 라우팅: 질문 키워드로 KPI vs VOC 결정. 사용자가 라디오로 강제 선택도 가능. 로컬: python app.py HF Space: sdk=gradio 프론트매터로 자동 기동 """ from __future__ import annotations import os import gradio as gr from dotenv import load_dotenv load_dotenv() if not os.getenv("ANTHROPIC_API_KEY"): raise SystemExit( "환경변수 ANTHROPIC_API_KEY가 없습니다. " "로컬: .env 파일. HF Space: Settings → Variables and secrets에서 추가." ) from rag import Chatbot as VocChatbot from kpi_chatbot import KpiChatbot, is_kpi_question _voc = VocChatbot() _kpi = KpiChatbot() _TOTAL = _voc._aggregates["total_posts_indexed"] _PERIOD = _voc._aggregates["period"] TOP_K = 8 def _resolve_mode(message: str, mode_choice: str) -> str: if mode_choice == "VOC (에브리타임)": return "voc" if mode_choice == "KPI (핵심성과지표)": return "kpi" # auto return "kpi" if is_kpi_question(message) else "voc" def chat_fn(message: str, history: list, mode_choice: str): mode = _resolve_mode(message, mode_choice) if mode == "kpi": prefix = "🎯 **KPI 모드** (對外秘 IR-2025-01 · 38개 지표 컨텍스트 · Haiku 4.5)\n\n" response = prefix yield response for chunk in _kpi.answer_stream(message, history=history): response += chunk yield response return # VOC mode (default) prefix = "💬 **VOC 모드** (에브리타임 12K건 RAG · Haiku 4.5)\n\n" hits, stream = _voc.answer_stream(message, k=TOP_K) response = prefix yield response for chunk in stream: response += chunk yield response sources = "\n\n---\n**근거 게시글 (의미 검색 Top-5)**" for h in hits[:5]: preview = h.text[:120].replace("\n", " ") sources += ( f"\n- `[id:{h.id}]` {h.month} · {h.primary_cat} · " f"추천 {h.posvote}·댓글 {h.comment_count} · 유사도 {1 - h.distance:.2f}\n" f" *{h.title[:80]}* — {preview}..." ) yield response + sources INFO_MD = f""" # 호서대 IR 챗봇 (VOC + KPI 통합) | 모드 | 데이터 | 모델 | 비고 | |---|---|---|---| | 💬 **VOC** | 에브리타임 자유게시판 {_TOTAL:,}건 ({_PERIOD['start'][:10]}~{_PERIOD['end'][:10]}) | Haiku 4.5 + ChromaDB | 학생 VOC 정성 분석 | | 🎯 **KPI** | 2025학년도 핵심성과지표 38개 (對外秘 IR-2025-01) | Haiku 4.5 + 정형 컨텍스트 | 보고서 페이지 인용 | 기본은 **자동 라우팅**입니다. 질문에 충원율·취업률·순위 등 KPI 키워드가 있으면 KPI 모드, 그 외에는 VOC 모드. 라디오로 강제 선택도 가능합니다. ⚠️ KPI 모드는 對外秘 자료입니다. 외부 공개·인용·제3자 공유 금지. """ EXAMPLES = [ ["재학생충원율은 어떻게 되고 있어?", "KPI (핵심성과지표)"], ["충청권 32개 대학 중 우리 대학 순위가 가장 좋은 KPI는?", "KPI (핵심성과지표)"], ["재정 영역에서 가장 빠르게 개선 중인 지표는?", "KPI (핵심성과지표)"], ["교사확보율과 교지확보율을 함께 비교해 줘.", "KPI (핵심성과지표)"], ["호서대 1학년 학생들은 주로 뭐에 관심이 많던가요?", "VOC (에브리타임)"], ["학생들이 가장 자주 제기하는 불만은 무엇인가요?", "VOC (에브리타임)"], ["셔틀버스나 교통 관련 민원이 많은 시기는?", "VOC (에브리타임)"], ] with gr.Blocks( title="호서대 IR 챗봇", theme=gr.themes.Soft(primary_hue="blue"), ) as demo: gr.Markdown(INFO_MD) mode_radio = gr.Radio( choices=["자동 라우팅", "VOC (에브리타임)", "KPI (핵심성과지표)"], value="자동 라우팅", label="응답 모드", info="자동 라우팅: 질문 키워드로 결정. 강제하려면 직접 선택.", ) gr.ChatInterface( fn=chat_fn, type="messages", examples=EXAMPLES, cache_examples=False, submit_btn="질문", stop_btn="중단", fill_height=True, additional_inputs=[mode_radio], ) if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860)