"""
공공기관 사업제안서 RAG 챗봇
기능:
- 사용자 API 키 입력 및 검증
- 사용 가능한 GPT 모델 자동 조회 및 선택
- 모델 선택 (API/로컬 GGUF)
- Query Router (검색 vs 직접 답변)
- RAG 기반 질의응답 (Hybrid Search + Re-ranker)
- 조건부 참고 문서 표시
- 대화 히스토리 관리
- 검색 모드 선택
"""
import streamlit as st
import sys
import os
from pathlib import Path
from datetime import datetime
import json
# 프로젝트 루트 추가
root_dir = Path(__file__).parent.parent.parent
sys.path.insert(0, str(root_dir))
from src.utils.config import RAGConfig
from src.utils.conversation_manager import ConversationManager
# ===== 페이지 설정 =====
st.set_page_config(
page_title="공공기관 사업제안서 챗봇",
page_icon="🤖",
layout="wide",
initial_sidebar_state="expanded"
)
# ===== 스타일 =====
st.markdown("""
""", unsafe_allow_html=True)
# ===== 세션 상태 초기화 =====
if 'conv_manager' not in st.session_state:
st.session_state.conv_manager = ConversationManager()
if 'rag_pipeline' not in st.session_state:
st.session_state.rag_pipeline = None
if 'model_type' not in st.session_state:
st.session_state.model_type = None
if 'show_routing_info' not in st.session_state:
st.session_state.show_routing_info = False
if 'user_api_key' not in st.session_state:
st.session_state.user_api_key = None
if 'api_key_validated' not in st.session_state:
st.session_state.api_key_validated = False
if 'available_models' not in st.session_state:
st.session_state.available_models = []
if 'selected_gpt_model' not in st.session_state:
st.session_state.selected_gpt_model = "gpt-4o-mini"
# ===== API 키로 사용 가능한 모델 조회 함수 =====
def get_available_models(api_key: str) -> tuple:
"""
API 키로 실제 사용 가능한 모든 GPT/o 시리즈 모델 조회
Args:
api_key: OpenAI API 키
Returns:
(success, model_list, error_message)
"""
try:
from openai import OpenAI
client = OpenAI(api_key=api_key)
# 모델 목록 조회
models_response = client.models.list()
# Chat Completion 가능한 모델만 필터링
available_models = []
for model in models_response.data:
model_id = model.id
# GPT 시리즈, o1, o3 시리즈만 선택
if (model_id.startswith('gpt-') or
model_id.startswith('o1-') or
model_id.startswith('o3-')):
available_models.append(model_id)
if not available_models:
return False, [], "사용 가능한 모델을 찾을 수 없습니다."
# 우선순위 정렬 (최신/고급 모델 우선)
priority_map = {
'o3': 1,
'o1': 2,
'gpt-5': 3,
'gpt-4o': 4,
'gpt-4o-mini': 5,
'gpt-4-turbo': 6,
'gpt-4': 7,
'gpt-3.5-turbo': 8,
'gpt-3.5': 9
}
def get_priority(model_name):
for prefix, priority in priority_map.items():
if model_name.startswith(prefix):
return priority
return 99
available_models.sort(key=get_priority)
# 중복 제거 (날짜 버전 중 가장 최근 것만)
unique_models = []
seen_bases = {}
for model in available_models:
# 기본 모델명 추출 (날짜/버전 제거)
base = model
for suffix in ['-preview', '-latest']:
base = base.replace(suffix, '')
# 날짜 패턴 제거 (예: -20241120, -2024-11-20)
import re
base = re.sub(r'-\d{8}$', '', base)
base = re.sub(r'-\d{4}-\d{2}-\d{2}$', '', base)
# 같은 base가 이미 있으면 더 긴 이름 선택 (보통 최신)
if base not in seen_bases or len(model) > len(seen_bases[base]):
seen_bases[base] = model
unique_models = list(seen_bases.values())
unique_models.sort(key=get_priority)
return True, unique_models, ""
except Exception as e:
error_msg = str(e)
if "Incorrect API key" in error_msg:
return False, [], "❌ 잘못된 API 키입니다."
elif "insufficient_quota" in error_msg:
return False, [], "⚠️ API 크레딧이 부족합니다."
else:
return False, [], f"❌ 모델 조회 실패: {error_msg}"
# ===== API 키 검증 함수 =====
def validate_api_key(api_key: str) -> tuple:
"""
OpenAI API 키 유효성 검증 및 사용 가능한 모델 조회
Args:
api_key: 검증할 API 키
Returns:
(is_valid, message, available_models)
"""
try:
# 모델 목록 조회로 검증 (chat completion보다 권한 요구사항 낮음)
success, models, error = get_available_models(api_key)
if not success:
return False, error, []
if len(models) == 0:
return False, "❌ 사용 가능한 모델이 없습니다.", []
return True, f"✅ API 키가 유효합니다! ({len(models)}개 모델 사용 가능)", models
except Exception as e:
error_msg = str(e)
if "Incorrect API key" in error_msg or "invalid_api_key" in error_msg:
return False, "❌ 잘못된 API 키입니다. 다시 확인해주세요.", []
elif "insufficient_quota" in error_msg:
return False, "⚠️ API 키는 유효하지만 크레딧이 부족합니다.", []
elif "403" in error_msg or "Forbidden" in error_msg:
return False, "❌ API 키 권한이 부족합니다. 키 권한을 확인해주세요.", []
else:
return False, f"❌ API 키 검증 실패: {error_msg}", []
except Exception as e:
error_msg = str(e)
if "Incorrect API key" in error_msg or "invalid_api_key" in error_msg:
return False, "❌ 잘못된 API 키입니다. 다시 확인해주세요.", []
elif "insufficient_quota" in error_msg:
return False, "⚠️ API 키는 유효하지만 크레딧이 부족합니다.", []
else:
return False, f"❌ API 키 검증 실패: {error_msg}", []
# ===== RAG 파이프라인 초기화 =====
@st.cache_resource
def initialize_rag(model_type, _user_api_key=None, gpt_model_name=None):
"""
RAG 파이프라인 초기화
Args:
model_type: "API 모델 (GPT)" 또는 "로컬 모델 (GGUF)"
_user_api_key: 사용자가 입력한 API 키 (None이면 .env 사용)
gpt_model_name: 사용할 GPT 모델 이름 (예: "gpt-4o-mini")
Returns:
(rag_pipeline, error_message, model_name)
"""
try:
config = RAGConfig()
# 사용자 API 키가 있으면 덮어쓰기
if _user_api_key:
config.OPENAI_API_KEY = _user_api_key
os.environ["OPENAI_API_KEY"] = _user_api_key
# GPT 모델 이름 설정
if gpt_model_name:
config.LLM_MODEL_NAME = gpt_model_name
if model_type == "API 모델 (GPT)":
# API 모델 사용
from src.generator.generator import RAGPipeline
rag = RAGPipeline(config=config)
return rag, None, f"OpenAI {config.LLM_MODEL_NAME}"
elif model_type == "로컬 모델 (GGUF)":
# GGUF 모델 사용
from src.generator.generator_gguf import GGUFRAGPipeline
rag = GGUFRAGPipeline(
config=config,
n_gpu_layers=35,
n_ctx=8192,
n_threads=4,
max_new_tokens=512,
temperature=0.7,
top_p=0.9
)
return rag, None, "Llama-3-Ko-8B (GGUF)"
else:
return None, f"알 수 없는 모델 타입: {model_type}", None
except Exception as e:
import traceback
error_detail = traceback.format_exc()
return None, f"{str(e)}\n\n{error_detail}", None
# ===== 답변 생성 =====
def generate_answer(query: str, top_k: int = 10, search_mode: str = "hybrid_rerank", alpha: float = 0.5):
"""질의에 대한 답변 생성"""
try:
result = st.session_state.rag_pipeline.generate_answer(
query=query,
top_k=top_k,
search_mode=search_mode,
alpha=alpha
)
return result
except Exception as e:
import traceback
error_detail = traceback.format_exc()
return {
'answer': f"❌ 오류가 발생했습니다: {str(e)}\n\n{error_detail}",
'sources': [],
'used_retrieval': False,
'search_mode': search_mode,
'routing_info': None,
'usage': {'total_tokens': 0, 'prompt_tokens': 0, 'completion_tokens': 0}
}
# ===== 메시지 표시 =====
def display_message(
role: str,
content: str,
sources: list = None,
usage: dict = None,
search_mode: str = None,
used_retrieval: bool = None,
routing_info: dict = None
):
"""메시지를 화면에 표시"""
if role == 'user':
st.markdown(f"""
""", unsafe_allow_html=True)
else: # assistant
# 답변
st.markdown(f"""
""", unsafe_allow_html=True)
# 라우팅 정보 (개발 모드)
if st.session_state.show_routing_info and routing_info:
route_icon = "🔍" if routing_info.get('route') == 'rag' else "💬"
st.markdown(f"""
{route_icon} 라우팅: {routing_info.get('route', 'N/A').upper()}
(신뢰도: {routing_info.get('confidence', 0):.2f}) -
{routing_info.get('reason', 'N/A')}
""", unsafe_allow_html=True)
# 검색 모드 정보 (검색 사용 시만)
if used_retrieval and search_mode:
mode_display = {
'hybrid_rerank': '🔄 Hybrid + Re-ranker',
'hybrid': '🔀 Hybrid Search',
'embedding_rerank': '📊 임베딩 + Re-ranker',
'embedding': '📊 임베딩 검색',
'direct': '💬 Direct (검색 없음)'
}
st.markdown(f"""
검색 모드: {mode_display.get(search_mode, search_mode)}
""", unsafe_allow_html=True)
# 참고 문서 (검색 사용 시만)
if used_retrieval and sources and len(sources) > 0:
st.markdown("### 📚 참고 문서")
for i, source in enumerate(sources, 1):
metadata = source.get('metadata', {})
# 관련도 점수
score = source.get('score', 0)
score_type = source.get('score_type', '')
# 문서 내용 미리보기
content_preview = source.get('content', '')[:200] + "..."
st.markdown(f"""
{content_preview}
📁 파일: {metadata.get('파일명', 'N/A')}
🏢 발주기관: {metadata.get('발주 기관', 'N/A')}
📋 사업명: {metadata.get('사업명', 'N/A')}
""", unsafe_allow_html=True)
elif not used_retrieval:
# 검색을 사용하지 않은 경우 안내
st.info("💬 이 답변은 문서 검색 없이 생성되었습니다.")
# 토큰 사용량
if usage:
st.markdown(f"""
🔢 토큰 사용량: {usage.get('total_tokens', 0)}
(프롬프트: {usage.get('prompt_tokens', 0)},
완성: {usage.get('completion_tokens', 0)})
""", unsafe_allow_html=True)
# ===== 메인 앱 =====
def main():
# 헤더
st.markdown('🤖 공공기관 사업제안서 챗봇
', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
# ===== 사이드바 =====
with st.sidebar:
st.header("⚙️ 설정")
# ===== 🔑 API 키 설정 =====
st.markdown("### 🔑 API 키 설정")
config = RAGConfig()
has_env_key = bool(config.OPENAI_API_KEY and config.OPENAI_API_KEY != "")
if has_env_key:
st.success("✅ 서버 API 키 사용 중")
else:
st.warning("⚠️ 서버 API 키가 없습니다. 아래에 입력하세요.")
use_custom_key = st.checkbox(
"🔓 내 API 키 사용하기",
value=not has_env_key,
help="OpenAI API 키를 직접 입력하여 사용합니다."
)
if use_custom_key:
user_key_input = st.text_input(
"OpenAI API 키 입력",
type="password",
placeholder="sk-...",
help="https://platform.openai.com/api-keys 에서 발급받으세요"
)
col1, col2 = st.columns(2)
with col1:
validate_button = st.button(
"🔍 검증",
use_container_width=True,
disabled=not user_key_input
)
with col2:
apply_button = st.button(
"✅ 적용",
use_container_width=True,
disabled=not user_key_input,
type="primary"
)
# 검증 버튼
if validate_button and user_key_input:
with st.spinner("🔄 API 키 검증 및 모델 조회 중..."):
is_valid, message, models = validate_api_key(user_key_input)
if is_valid:
st.success(message)
st.session_state.api_key_validated = True
st.session_state.available_models = models
# 사용 가능한 모델 표시
if models:
st.info(f"📋 사용 가능한 모델: {', '.join(models)}")
else:
st.error(message)
st.session_state.api_key_validated = False
st.session_state.available_models = []
# 적용 버튼
if apply_button and user_key_input:
with st.spinner("🔄 API 키 적용 중..."):
is_valid, message, models = validate_api_key(user_key_input)
if is_valid:
st.session_state.user_api_key = user_key_input
st.session_state.api_key_validated = True
st.session_state.available_models = models
# RAG 파이프라인 재초기화 강제
st.session_state.rag_pipeline = None
st.session_state.model_type = None
st.success("✅ API 키가 적용되었습니다!")
if models:
st.info(f"💡 아래에서 사용할 모델을 선택하세요. ({len(models)}개 사용 가능)")
else:
st.error(message)
# API 키 입력 가이드
with st.expander("📖 API 키 발급 방법"):
st.markdown("""
1. [OpenAI Platform](https://platform.openai.com/api-keys) 접속
2. 로그인 후 "Create new secret key" 클릭
3. 생성된 키를 복사하여 위에 붙여넣기
**주의사항:**
- API 키는 안전하게 보관하세요
- 무료 크레딧이 소진되면 사용 불가
- 사용량에 따라 요금이 부과될 수 있습니다
**모델별 가격 (1M 토큰 기준):**
- gpt-4o: $2.50 (입력) / $10.00 (출력)
- gpt-4o-mini: $0.15 (입력) / $0.60 (출력)
- gpt-3.5-turbo: $0.50 (입력) / $1.50 (출력)
""")
else:
# 서버 키 사용 중
if has_env_key:
st.info("ℹ️ 서버에 설정된 API 키를 사용합니다.")
# 서버 키로 사용 가능한 모델 조회 (최초 1회)
if not st.session_state.available_models:
with st.spinner("🔄 사용 가능한 모델 조회 중..."):
success, models, error = get_available_models(config.OPENAI_API_KEY)
if success:
st.session_state.available_models = models
# 사용자가 입력한 키 초기화
if st.session_state.user_api_key:
st.session_state.user_api_key = None
st.session_state.rag_pipeline = None
st.session_state.model_type = None
st.markdown("---")
# ===== 🤖 모델 설정 =====
st.markdown("### 🤖 모델 설정")
can_use_gpt = has_env_key or (use_custom_key and st.session_state.api_key_validated)
model_options = ["API 모델 (GPT)", "로컬 모델 (GGUF)"]
if not can_use_gpt:
st.warning("⚠️ API 키를 입력해야 GPT 모델을 사용할 수 있습니다.")
default_index = 1
else:
default_index = 0
model_type = st.selectbox(
"생성 모델 선택",
options=model_options,
index=default_index,
help="OpenAI API 또는 로컬 GGUF 모델 선택"
)
# ===== GPT 모델 상세 선택 =====
selected_gpt_model = None
if model_type == "API 모델 (GPT)" and can_use_gpt:
available_models = st.session_state.available_models
if available_models:
# 모델 선택 UI
st.markdown("#### 📋 GPT 모델 선택")
# 모델 설명
model_descriptions = {
'o3': '🌟 o3 시리즈 (최첨단 추론 모델)',
'o3-mini': '🌟 o3-mini (경량 추론 모델)',
'o1': '🧠 o1 시리즈 (고급 추론 모델)',
'o1-mini': '🧠 o1-mini (경량 추론 모델)',
'o1-preview': '🧪 o1 프리뷰 (베타)',
'gpt-5': '⚡ GPT-5 (차세대 모델)',
'gpt-5-turbo': '⚡ GPT-5 Turbo (고속)',
'gpt-4o': '🚀 GPT-4o (가장 강력)',
'gpt-4o-mini': '⚡ GPT-4o-mini (빠르고 저렴, 권장)',
'gpt-4-turbo': '💎 GPT-4 Turbo (고성능)',
'gpt-4': '🏆 GPT-4 (높은 품질)',
'gpt-3.5-turbo': '💰 GPT-3.5 Turbo (가성비)',
'gpt-3.5': '💰 GPT-3.5 (기본)'
}
# format 함수: 모델명으로 설명 찾기
def get_model_display(model_name):
# 정확히 매칭되는 설명 찾기
if model_name in model_descriptions:
return f"{model_descriptions[model_name]} - {model_name}"
# 부분 매칭 (예: gpt-4o-2024-11-20 → gpt-4o)
for key in model_descriptions.keys():
if model_name.startswith(key):
return f"{model_descriptions[key]} - {model_name}"
# 매칭 안되면 모델명만
return model_name
# 기본값 설정
if st.session_state.selected_gpt_model not in available_models:
# 우선순위: gpt-4o-mini > gpt-3.5-turbo > 첫번째 모델
if 'gpt-4o-mini' in available_models:
st.session_state.selected_gpt_model = 'gpt-4o-mini'
elif 'gpt-3.5-turbo' in available_models:
st.session_state.selected_gpt_model = 'gpt-3.5-turbo'
else:
st.session_state.selected_gpt_model = available_models[0]
# 모델 선택
selected_gpt_model = st.selectbox(
"사용할 모델",
options=available_models,
index=available_models.index(st.session_state.selected_gpt_model),
format_func=get_model_display,
help="API 키로 사용 가능한 모델 중 선택하세요"
)
# 선택 저장
st.session_state.selected_gpt_model = selected_gpt_model
# 선택한 모델 정보 표시
# 설명 찾기
display_desc = "설명 없음"
for key, desc in model_descriptions.items():
if selected_gpt_model.startswith(key):
display_desc = desc
break
st.markdown(f"""
🎯 선택된 모델
• {selected_gpt_model}
• {display_desc}
""", unsafe_allow_html=True)
else:
st.warning("⚠️ 사용 가능한 모델을 조회하지 못했습니다.")
st.info("💡 '검증' 버튼을 눌러 모델 목록을 조회하세요.")
# 기본값 사용
selected_gpt_model = "gpt-4o-mini"
elif model_type == "로컬 모델 (GGUF)":
# GGUF 모델 정보 표시
st.markdown("""
🖥️ Llama-3-Ko-8B (GGUF)
• T4 GPU 가속
• 로컬 실행 (무료)
• 초기 로딩 시간 소요
• 35개 레이어 GPU 사용
""", unsafe_allow_html=True)
st.markdown("---")
# ===== 🔍 검색 설정 =====
st.markdown("### 🔍 검색 설정")
search_mode = st.selectbox(
"검색 모드",
options=["hybrid", "embedding"],
index=0,
format_func=lambda x: {
"hybrid": "🔀 Hybrid Search (BM25 + 임베딩)",
"embedding": "📊 임베딩 검색"
}[x],
help="Hybrid: 키워드 + 의미 검색 병행 (권장)"
)
# Reranker 토글
use_reranker = st.toggle(
"🔄 Re-ranker 사용",
value=True,
help="검색 결과를 CrossEncoder로 재정렬하여 정확도 향상 (권장)"
)
# 실제 검색 모드 결정
if use_reranker:
if search_mode == "hybrid":
actual_search_mode = "hybrid_rerank"
else: # embedding
actual_search_mode = "embedding_rerank"
else:
actual_search_mode = search_mode
top_k = st.slider(
"검색할 문서 개수 (Top-K)",
min_value=1,
max_value=20,
value=10,
help="검색할 문서 개수"
)
alpha = st.slider(
"임베딩 가중치 (alpha)",
min_value=0.0,
max_value=1.0,
value=0.5,
step=0.1,
help="0: BM25만, 1: 임베딩만, 0.5: 동일 가중치 (Hybrid 모드에서만 사용)",
disabled=(search_mode == "embedding")
)
st.markdown("---")
# ===== 🛠️ 개발자 옵션 =====
st.markdown("### 🛠️ 개발자 옵션")
show_routing = st.toggle(
"🔍 라우팅 정보 표시",
value=False,
help="Router의 판단 과정을 표시 (디버깅용)"
)
st.session_state.show_routing_info = show_routing
st.markdown("---")
# ===== 💬 대화 관리 =====
st.markdown("### 💬 대화 관리")
if st.button("🗑️ 대화 초기화", use_container_width=True):
st.session_state.conv_manager.clear()
st.rerun()
if st.button("💾 대화 다운로드", use_container_width=True):
if len(st.session_state.conv_manager) > 0:
json_str = st.session_state.conv_manager.export_to_json()
st.download_button(
label="📥 JSON 다운로드",
data=json_str,
file_name=f"chat_history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
mime="application/json",
use_container_width=True
)
st.markdown("---")
# ===== 📊 통계 =====
st.markdown("### 📊 통계")
stats = st.session_state.conv_manager.get_statistics()
st.metric("총 대화 수", stats.get('total', 0))
# 현재 설정 표시
st.markdown("---")
st.markdown("### 📋 현재 설정")
st.text(f"모델: {model_type}")
if model_type == "API 모델 (GPT)" and selected_gpt_model:
st.text(f"GPT 모델: {selected_gpt_model}")
st.text(f"검색 모드: {search_mode}")
st.text(f"Re-ranker: {'✅ ON' if use_reranker else '❌ OFF'}")
st.text(f"실제 모드: {actual_search_mode}")
st.text(f"Top-K: {top_k}")
if search_mode == "hybrid":
st.text(f"Alpha: {alpha}")
st.text(f"Router Info: {'✅ ON' if show_routing else '❌ OFF'}")
# ===== RAG 파이프라인 초기화 =====
# 모델 타입이 변경되었거나 GPT 모델이 변경되었거나 파이프라인이 없으면 재초기화
need_reinit = (
st.session_state.rag_pipeline is None or
st.session_state.model_type != model_type or
(model_type == "API 모델 (GPT)" and
selected_gpt_model and
hasattr(st.session_state.rag_pipeline, 'model') and
st.session_state.rag_pipeline.model != selected_gpt_model)
)
if need_reinit:
with st.spinner(f"🔄 {model_type} 초기화 중... (GGUF 모델은 1~2분 소요될 수 있습니다)"):
rag, error, rag_type = initialize_rag(
model_type,
_user_api_key=st.session_state.user_api_key,
gpt_model_name=selected_gpt_model
)
if error:
st.error(f"❌ RAG 파이프라인 초기화 실패")
with st.expander("🔍 에러 상세 정보"):
st.code(error)
st.info("""
### 💡 해결 방법
**GGUF 모델 실패 시:**
1. llama-cpp-python 설치 확인:
```bash
pip install llama-cpp-python
```
2. GGUF 모델 파일 확인:
- config.yaml의 GGUF_MODEL_PATH 또는
- MODEL_HUB_REPO 설정 확인
3. GPU 메모리 부족 시:
- n_gpu_layers 값 감소 (35 → 20)
**API 모델 실패 시:**
1. ChromaDB가 생성되었는지 확인:
```bash
python main.py --step embed
```
2. OpenAI API 키 확인:
```bash
# .env 파일
OPENAI_API_KEY=your-key-here
```
3. 필요한 패키지 설치:
```bash
pip install rank-bm25 sentence-transformers
```
""")
return
st.session_state.rag_pipeline = rag
st.session_state.model_type = model_type
# API 키 및 모델 사용 정보 표시
if st.session_state.user_api_key:
st.success(f"✅ {rag_type} 준비 완료! (사용자 API 키)")
else:
st.success(f"✅ {rag_type} 준비 완료!")
# ===== 대화 히스토리 표시 =====
st.markdown("---")
if len(st.session_state.conv_manager) == 0:
st.info("""
### 👋 환영합니다!
공공기관 사업제안서에 대해 질문해보세요.
**예시 질문:**
- "안녕하세요" (검색 안 함)
- "데이터 표준화 요구사항은 무엇인가요?" (검색 수행)
- "보안 관련 요구사항을 설명해주세요" (검색 수행)
- "고마워요" (검색 안 함)
""")
# 기존 메시지 표시
for msg in st.session_state.conv_manager.get_ui_history():
display_message(
role=msg['role'],
content=msg['content'],
sources=msg.get('sources'),
usage=msg.get('usage'),
search_mode=msg.get('search_mode'),
used_retrieval=msg.get('used_retrieval'),
routing_info=msg.get('routing_info')
)
# ===== 질문 입력 =====
st.markdown("---")
with st.form(key='question_form', clear_on_submit=True):
user_input = st.text_area(
"질문을 입력하세요:",
height=100,
placeholder="예: 데이터 표준화 요구사항은 무엇인가요?"
)
col1, col2, col3 = st.columns([1, 1, 4])
with col1:
submit_button = st.form_submit_button("📤 전송", use_container_width=True)
# ===== 질문 처리 =====
if submit_button and user_input:
# 답변 생성
with st.spinner("🤔 답변 생성 중..."):
result = generate_answer(
query=user_input,
top_k=top_k,
search_mode=actual_search_mode,
alpha=alpha
)
# 어시스턴트 메시지 추가
st.session_state.conv_manager.add_message(
user_msg=user_input,
ai_msg=result['answer'],
query_type=result.get('query_type', 'unknown'),
sources=result.get('sources', []),
usage=result.get('usage', {}),
search_mode=result.get('search_mode'),
used_retrieval=result.get('used_retrieval', False),
routing_info=result.get('routing_info')
)
# 화면 새로고침
st.rerun()
if __name__ == "__main__":
main()