Spaces:
Sleeping
Sleeping
import os | |
import gradio as gr | |
from langchain_groq import ChatGroq | |
from langchain.chains import RetrievalQA | |
from langchain.prompts import PromptTemplate | |
from langchain.vectorstores import FAISS | |
from langchain.embeddings import HuggingFaceEmbeddings | |
# Groq API 키 (Hugging Face Secrets에서 가져옴) | |
GROQ_API_KEY = os.environ.get("GROQ_API_KEY") | |
# 전역 변수들 | |
vectorstores = {} | |
embeddings = None | |
def comprehensive_debug(): | |
"""완전한 디버깅 함수""" | |
print("=" * 50) | |
print("🔍 벡터스토어 디버깅 시작") | |
print("=" * 50) | |
# 1. 현재 디렉토리 정보 | |
current_dir = os.getcwd() | |
print(f"📍 현재 작업 디렉토리: {current_dir}") | |
# 2. 모든 파일과 폴더 나열 | |
try: | |
all_items = os.listdir(current_dir) | |
print(f"📂 현재 디렉토리 내 모든 항목들:") | |
for item in sorted(all_items): | |
item_path = os.path.join(current_dir, item) | |
if os.path.isdir(item_path): | |
print(f" 📁 {item} (폴더)") | |
else: | |
print(f" 📄 {item} (파일)") | |
except Exception as e: | |
print(f"❌ 디렉토리 조회 실패: {e}") | |
return [] | |
# 3. 벡터스토어 폴더들 상세 검사 | |
expected_folders = ['vectorstore1', 'vectorstore2', 'vectorstore3'] | |
working_folders = [] | |
for folder_name in expected_folders: | |
print(f"\n🔍 {folder_name} 검사 중...") | |
folder_path = os.path.join(current_dir, folder_name) | |
# 폴더 존재 확인 | |
if not os.path.exists(folder_path): | |
print(f" ❌ 폴더가 존재하지 않음: {folder_path}") | |
continue | |
if not os.path.isdir(folder_path): | |
print(f" ❌ 폴더가 아님: {folder_path}") | |
continue | |
print(f" ✅ 폴더 존재함: {folder_path}") | |
# 폴더 내용 확인 | |
try: | |
folder_contents = os.listdir(folder_path) | |
print(f" 📋 폴더 내용: {folder_contents}") | |
# 필수 파일들 확인 | |
required_files = ['index.faiss', 'index.pkl'] | |
for req_file in required_files: | |
file_path = os.path.join(folder_path, req_file) | |
if os.path.exists(file_path): | |
file_size = os.path.getsize(file_path) | |
print(f" ✅ {req_file} 존재 (크기: {file_size} bytes)") | |
else: | |
print(f" ❌ {req_file} 없음") | |
# 모든 파일이 있으면 작업 목록에 추가 | |
if all(os.path.exists(os.path.join(folder_path, f)) for f in required_files): | |
working_folders.append(folder_name) | |
print(f" 🎯 {folder_name} 사용 가능!") | |
else: | |
print(f" ⚠️ {folder_name} 필수 파일 누락") | |
except Exception as e: | |
print(f" ❌ 폴더 내용 확인 실패: {e}") | |
print(f"\n📊 결과 요약:") | |
print(f" 총 검사한 폴더: {len(expected_folders)}") | |
print(f" 사용 가능한 폴더: {len(working_folders)}") | |
print(f" 사용 가능한 폴더 목록: {working_folders}") | |
return working_folders | |
def load_single_vectorstore(folder_name): | |
"""단일 벡터스토어 로드 테스트""" | |
global embeddings | |
print(f"\n🚀 {folder_name} 로드 시도...") | |
try: | |
# 임베딩 모델 초기화 | |
if embeddings is None: | |
print(" 📥 임베딩 모델 로드 중...") | |
embeddings = HuggingFaceEmbeddings( | |
model_name="sentence-transformers/all-MiniLM-L6-v2" | |
) | |
print(" ✅ 임베딩 모델 로드 완료") | |
# 벡터스토어 로드 | |
folder_path = f"./{folder_name}" | |
print(f" 📂 벡터스토어 로드 시도: {folder_path}") | |
vectorstore = FAISS.load_local( | |
folder_path, | |
embeddings, | |
allow_dangerous_deserialization=True | |
) | |
print(f" ✅ {folder_name} 로드 성공!") | |
# 간단한 테스트 | |
try: | |
# 벡터스토어 정보 확인 | |
index_size = vectorstore.index.ntotal if hasattr(vectorstore, 'index') else "알 수 없음" | |
print(f" 📊 벡터 개수: {index_size}") | |
# 간단한 검색 테스트 | |
test_results = vectorstore.similarity_search("테스트", k=1) | |
print(f" 🔍 검색 테스트 결과: {len(test_results)}개 문서 반환") | |
except Exception as test_e: | |
print(f" ⚠️ 벡터스토어 테스트 실패: {test_e}") | |
return vectorstore | |
except Exception as e: | |
print(f" ❌ {folder_name} 로드 실패: {e}") | |
print(f" ❌ 에러 타입: {type(e).__name__}") | |
# 상세 에러 정보 | |
import traceback | |
error_details = traceback.format_exc() | |
print(f" 📋 상세 에러:\n{error_details}") | |
return None | |
def load_all_vectorstores(): | |
"""모든 벡터스토어 로드""" | |
global vectorstores | |
print("\n" + "=" * 50) | |
print("🚀 모든 벡터스토어 로드 시작") | |
print("=" * 50) | |
# 1. 디버깅으로 사용 가능한 폴더 찾기 | |
available_folders = comprehensive_debug() | |
if not available_folders: | |
print("\n❌ 사용 가능한 벡터스토어 폴더가 없습니다!") | |
return [] | |
# 2. 각 폴더별로 로드 시도 | |
successful_loads = [] | |
for folder_name in available_folders: | |
vectorstore = load_single_vectorstore(folder_name) | |
if vectorstore is not None: | |
vectorstores[folder_name] = vectorstore | |
successful_loads.append(folder_name) | |
print(f"\n📊 최종 결과:") | |
print(f" 성공적으로 로드된 벡터스토어: {len(successful_loads)}") | |
print(f" 로드된 벡터스토어 목록: {successful_loads}") | |
return successful_loads | |
# 질문 리스트 (간소화) | |
suggested_questions = [ | |
'교원 신규 임용은 어떻게 하나요?', | |
'교직원의 평일 근무시간은 어떻게 되나요?', | |
'등록금 납부 방법은 무엇인가요?', | |
'장학금 관리 기관은 어디인가요?', | |
'학생 단체는 어떻게 등록하나요?' | |
] | |
# 프롬프트 템플릿 | |
prompt_template = """당신은 한남대학교 규정집 도우미입니다. | |
주어진 문서 내용을 바탕으로 질문에 대해 정확하고 친절하게 한국어로 답변해주세요. | |
참고 문서: | |
{context} | |
질문: {question} | |
한국어 답변:""" | |
prompt = PromptTemplate( | |
template=prompt_template, | |
input_variables=["context", "question"] | |
) | |
def respond_with_groq(question, selected_q, model, selected_vectorstore): | |
"""질문에 대한 답변을 생성하는 함수 (디버깅 버전)""" | |
print(f"\n🎯 답변 생성 시작") | |
print(f" 질문: {question}") | |
print(f" 선택된 벡터스토어: {selected_vectorstore}") | |
# 선택된 질문이 있으면 그것을 사용 | |
if selected_q != "직접 입력": | |
question = selected_q | |
print(f" 실제 사용할 질문: {question}") | |
if not question.strip(): | |
return "질문을 입력해주세요." | |
if not GROQ_API_KEY: | |
return "❌ API 키가 설정되지 않았습니다." | |
# 벡터스토어 상태 확인 | |
print(f" 현재 로드된 벡터스토어들: {list(vectorstores.keys())}") | |
if not vectorstores: | |
return "❌ 로드된 벡터스토어가 없습니다. 페이지를 새로고침해보세요." | |
if selected_vectorstore not in vectorstores: | |
available_stores = list(vectorstores.keys()) | |
return f"❌ 선택된 벡터스토어({selected_vectorstore})를 찾을 수 없습니다.\n사용 가능: {available_stores}" | |
try: | |
current_vectorstore = vectorstores[selected_vectorstore] | |
print(f" ✅ 벡터스토어 선택 완료: {selected_vectorstore}") | |
# LLM 설정 | |
llm = ChatGroq( | |
groq_api_key=GROQ_API_KEY, | |
model_name=model, | |
temperature=0.1, | |
max_tokens=1000 | |
) | |
print(f" ✅ LLM 설정 완료: {model}") | |
# QA 체인 생성 | |
qa_chain = RetrievalQA.from_chain_type( | |
llm=llm, | |
chain_type="stuff", | |
retriever=current_vectorstore.as_retriever(search_kwargs={"k": 3}), | |
chain_type_kwargs={"prompt": prompt}, | |
return_source_documents=True | |
) | |
print(f" ✅ QA 체인 생성 완료") | |
# 답변 생성 | |
print(f" 🤖 답변 생성 중...") | |
result = qa_chain({"query": question}) | |
print(f" ✅ 답변 생성 완료") | |
return result['result'] | |
except Exception as e: | |
print(f" ❌ 답변 생성 실패: {e}") | |
import traceback | |
error_details = traceback.format_exc() | |
print(f" 📋 상세 에러:\n{error_details}") | |
return f"❌ 오류가 발생했습니다: {str(e)}" | |
def update_question(selected): | |
if selected != "직접 입력": | |
return selected | |
return "" | |
# 앱 시작시 벡터스토어들 로드 | |
print("🚀 애플리케이션 시작...") | |
available_vectorstores = load_all_vectorstores() | |
# Gradio 인터페이스 생성 | |
with gr.Blocks(title="한남대학교 Q&A") as interface: | |
gr.HTML(""" | |
<div style="text-align: center; padding: 20px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;"> | |
<h1>🏫 한남대학교 규정집 Q&A (디버깅 버전)</h1> | |
<p>벡터스토어 로딩 상태를 확인할 수 있습니다.</p> | |
</div> | |
""") | |
# 상태 표시 | |
if available_vectorstores: | |
status_html = f"✅ <b>{len(available_vectorstores)}개의 벡터스토어가 성공적으로 로드되었습니다:</b> {', '.join(available_vectorstores)}" | |
else: | |
status_html = "❌ <b>벡터스토어를 로드할 수 없습니다. 로그를 확인해주세요.</b>" | |
gr.HTML(f'<div style="padding: 10px; background-color: #f0f0f0; border-radius: 5px; margin-bottom: 20px;">{status_html}</div>') | |
with gr.Row(): | |
with gr.Column(scale=1): | |
if available_vectorstores: | |
vectorstore_dropdown = gr.Dropdown( | |
choices=available_vectorstores, | |
label="📚 벡터스토어 선택", | |
value=available_vectorstores[0] | |
) | |
else: | |
vectorstore_dropdown = gr.Dropdown( | |
choices=["사용 불가"], | |
label="📚 벡터스토어 선택", | |
value="사용 불가" | |
) | |
question_dropdown = gr.Dropdown( | |
choices=["직접 입력"] + suggested_questions, | |
label="💡 자주 묻는 질문", | |
value="직접 입력" | |
) | |
question_input = gr.Textbox( | |
label="❓ 질문을 입력하세요", | |
placeholder="예: 졸업 요건은 무엇인가요?", | |
lines=3 | |
) | |
submit_btn = gr.Button( | |
"답변 받기", | |
variant="primary", | |
size="lg", | |
interactive=bool(available_vectorstores) | |
) | |
model_choice = gr.Radio( | |
choices=["llama3-70b-8192", "llama3-8b-8192"], | |
label="🤖 AI 모델 선택", | |
value="llama3-70b-8192" | |
) | |
with gr.Column(scale=2): | |
output = gr.Textbox( | |
label="💬 답변", | |
lines=15, | |
max_lines=20, | |
show_copy_button=True | |
) | |
# 이벤트 연결 | |
if available_vectorstores: | |
submit_btn.click( | |
fn=respond_with_groq, | |
inputs=[question_input, question_dropdown, model_choice, vectorstore_dropdown], | |
outputs=output | |
) | |
question_dropdown.change( | |
fn=update_question, | |
inputs=question_dropdown, | |
outputs=question_input | |
) | |
# 앱 실행 | |
if __name__ == "__main__": | |
interface.launch() |