Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -55,18 +55,31 @@ def debug_file_system():
|
|
55 |
debug_file_system()
|
56 |
|
57 |
def find_vectorstore_folders():
|
58 |
-
"""현재 디렉토리에서 벡터스토어 폴더들을 찾는 함수"""
|
59 |
current_dir = os.getcwd()
|
60 |
print(f"현재 디렉토리: {current_dir}")
|
61 |
|
62 |
# 모든 파일과 폴더 확인
|
63 |
-
|
64 |
-
|
|
|
|
|
|
|
|
|
65 |
|
66 |
# 예상되는 벡터스토어 폴더들
|
67 |
expected_folders = ['vectorstore1', 'vectorstore2', 'vectorstore3']
|
68 |
vectorstore_folders = []
|
69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
70 |
for folder_name in expected_folders:
|
71 |
folder_path = os.path.join(current_dir, folder_name)
|
72 |
if os.path.exists(folder_path) and os.path.isdir(folder_path):
|
@@ -74,16 +87,19 @@ def find_vectorstore_folders():
|
|
74 |
folder_contents = os.listdir(folder_path)
|
75 |
print(f"📁 {folder_name} 폴더 내용: {folder_contents}")
|
76 |
|
77 |
-
#
|
78 |
-
|
79 |
-
|
80 |
|
81 |
-
if
|
|
|
|
|
|
|
|
|
|
|
82 |
vectorstore_folders.append(folder_name)
|
83 |
-
print(f"✅ {folder_name} - 모든 필수 파일 존재")
|
84 |
else:
|
85 |
-
|
86 |
-
print(f"❌ {folder_name} - 누락된 파일: {missing_files}")
|
87 |
|
88 |
except Exception as e:
|
89 |
print(f"❌ {folder_name} 폴더 확인 중 오류: {e}")
|
@@ -92,52 +108,137 @@ def find_vectorstore_folders():
|
|
92 |
|
93 |
if not vectorstore_folders:
|
94 |
print("❌ 사용 가능한 벡터스토어 폴더를 찾을 수 없습니다")
|
|
|
|
|
|
|
|
|
|
|
95 |
else:
|
96 |
print(f"✅ 총 {len(vectorstore_folders)}개의 벡터스토어 폴더를 찾았습니다: {vectorstore_folders}")
|
97 |
|
98 |
return vectorstore_folders
|
99 |
|
100 |
def load_all_vectorstores():
|
101 |
-
"""모든 벡터스토어를 로드하고 통합하는 함수"""
|
102 |
global vectorstores, embeddings, combined_vectorstore
|
103 |
|
|
|
|
|
|
|
104 |
if not embeddings:
|
105 |
-
|
106 |
-
|
107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
|
109 |
# 벡터스토어 폴더들 찾기
|
110 |
folders = find_vectorstore_folders()
|
111 |
|
|
|
|
|
|
|
|
|
112 |
loaded_vectorstores = []
|
113 |
|
114 |
for folder_name in folders:
|
|
|
115 |
try:
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
except Exception as e:
|
125 |
-
print(f"❌ {folder_name} 로드
|
|
|
|
|
126 |
|
127 |
-
# 벡터스토어들을 통합
|
128 |
if loaded_vectorstores:
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
137 |
|
138 |
-
|
|
|
139 |
|
140 |
-
# 질문 리스트
|
141 |
suggested_questions = [
|
142 |
'교원 신규 임용은 어떻게 하나요?',
|
143 |
'교원 연구년 기간은 어떻게 되나요?',
|
@@ -161,7 +262,7 @@ suggested_questions = [
|
|
161 |
'안전사고예방계획은 어디에 제출해야 하나요?'
|
162 |
]
|
163 |
|
164 |
-
# 프롬프트 템플릿
|
165 |
prompt_template = """당신은 한남대학교 규정집 도우미입니다.
|
166 |
반드시 한국어로만 답변해주세요. 영어나 다른 언어는 절대 사용하지 마세요.
|
167 |
주어진 문서 내용을 바탕으로 질문에 대해 정확하고 친절하게 한국어로 답변해주세요.
|
@@ -182,7 +283,7 @@ prompt = PromptTemplate(
|
|
182 |
)
|
183 |
|
184 |
def respond_with_groq(question, selected_q, model):
|
185 |
-
"""질문에 대한 답변을 생성하는 함수"""
|
186 |
|
187 |
# 선택된 질문이 있으면 그것을 사용
|
188 |
if selected_q != "직접 입력":
|
@@ -192,13 +293,29 @@ def respond_with_groq(question, selected_q, model):
|
|
192 |
return "질문을 입력해주세요."
|
193 |
|
194 |
if not GROQ_API_KEY:
|
195 |
-
return "❌ API 키가 설정되지 않았습니다.
|
196 |
|
197 |
-
# 통합된 벡터스토어가 로드되지 않은 경우
|
198 |
if not combined_vectorstore:
|
199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
200 |
|
201 |
try:
|
|
|
202 |
print(f"✅ 통합된 벡터스토어를 사용하여 검색 중...")
|
203 |
|
204 |
# LLM 설정
|
@@ -209,6 +326,15 @@ def respond_with_groq(question, selected_q, model):
|
|
209 |
max_tokens=1000
|
210 |
)
|
211 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
212 |
# QA 체인 생성
|
213 |
qa_chain = RetrievalQA.from_chain_type(
|
214 |
llm=llm,
|
@@ -226,7 +352,14 @@ def respond_with_groq(question, selected_q, model):
|
|
226 |
import traceback
|
227 |
error_details = traceback.format_exc()
|
228 |
print(f"❌ 상세 오류 정보:\n{error_details}")
|
229 |
-
return f"❌ 오류가 발생했습니다: {str(e)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
230 |
|
231 |
def update_question(selected):
|
232 |
"""드롭다운 선택 시 질문을 업데이트하는 함수"""
|
@@ -235,16 +368,32 @@ def update_question(selected):
|
|
235 |
return ""
|
236 |
|
237 |
# 앱 시작시 벡터스토어들 로드
|
|
|
238 |
vectorstores_loaded = load_all_vectorstores()
|
239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
240 |
# Gradio 인터페이스 생성
|
241 |
with gr.Blocks(title="한남대학교 Q&A") as interface:
|
242 |
-
gr.HTML("""
|
243 |
<div style="text-align: center; padding: 20px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
|
244 |
<h1>🏫 한남대학교 규정집 Q&A</h1>
|
245 |
<p>한남대학교 규정집에 대한 질문에 답변해드립니다.</p>
|
246 |
</div>
|
247 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
248 |
|
249 |
with gr.Row():
|
250 |
with gr.Column(scale=1):
|
|
|
55 |
debug_file_system()
|
56 |
|
57 |
def find_vectorstore_folders():
|
58 |
+
"""현재 디렉토리에서 벡터스토어 폴더들을 찾는 함수 - 개선 버전"""
|
59 |
current_dir = os.getcwd()
|
60 |
print(f"현재 디렉토리: {current_dir}")
|
61 |
|
62 |
# 모든 파일과 폴더 확인
|
63 |
+
try:
|
64 |
+
all_items = os.listdir(current_dir)
|
65 |
+
print(f"현재 디렉토리 내 모든 항목들: {all_items}")
|
66 |
+
except Exception as e:
|
67 |
+
print(f"디렉토리 읽기 오류: {e}")
|
68 |
+
return []
|
69 |
|
70 |
# 예상되는 벡터스토어 폴더들
|
71 |
expected_folders = ['vectorstore1', 'vectorstore2', 'vectorstore3']
|
72 |
vectorstore_folders = []
|
73 |
|
74 |
+
# 실제로 존재하는 벡터스토어 관련 폴더들도 찾기
|
75 |
+
for item in all_items:
|
76 |
+
if os.path.isdir(item) and ('vectorstore' in item.lower() or 'vector' in item.lower()):
|
77 |
+
expected_folders.append(item)
|
78 |
+
|
79 |
+
# 중복 제거
|
80 |
+
expected_folders = list(set(expected_folders))
|
81 |
+
print(f"확인할 폴더들: {expected_folders}")
|
82 |
+
|
83 |
for folder_name in expected_folders:
|
84 |
folder_path = os.path.join(current_dir, folder_name)
|
85 |
if os.path.exists(folder_path) and os.path.isdir(folder_path):
|
|
|
87 |
folder_contents = os.listdir(folder_path)
|
88 |
print(f"📁 {folder_name} 폴더 내용: {folder_contents}")
|
89 |
|
90 |
+
# FAISS 파일들 확인 (더 유연하게)
|
91 |
+
has_faiss = any('.faiss' in file for file in folder_contents)
|
92 |
+
has_pkl = any('.pkl' in file for file in folder_contents)
|
93 |
|
94 |
+
if has_faiss and has_pkl:
|
95 |
+
vectorstore_folders.append(folder_name)
|
96 |
+
print(f"✅ {folder_name} - FAISS 파일들 존재")
|
97 |
+
elif has_faiss or has_pkl:
|
98 |
+
print(f"⚠️ {folder_name} - 일부 파일만 존재 (faiss: {has_faiss}, pkl: {has_pkl})")
|
99 |
+
# 일부만 있어도 시도해보기
|
100 |
vectorstore_folders.append(folder_name)
|
|
|
101 |
else:
|
102 |
+
print(f"❌ {folder_name} - FAISS 파일들 없음")
|
|
|
103 |
|
104 |
except Exception as e:
|
105 |
print(f"❌ {folder_name} 폴더 확인 중 오류: {e}")
|
|
|
108 |
|
109 |
if not vectorstore_folders:
|
110 |
print("❌ 사용 가능한 벡터스토어 폴더를 찾을 수 없습니다")
|
111 |
+
# 디버깅을 위해 현재 디렉토리의 모든 하위 폴더 출력
|
112 |
+
print("📋 현재 디렉토리의 모든 폴더들:")
|
113 |
+
for item in all_items:
|
114 |
+
if os.path.isdir(item):
|
115 |
+
print(f" 📁 {item}")
|
116 |
else:
|
117 |
print(f"✅ 총 {len(vectorstore_folders)}개의 벡터스토어 폴더를 찾았습니다: {vectorstore_folders}")
|
118 |
|
119 |
return vectorstore_folders
|
120 |
|
121 |
def load_all_vectorstores():
|
122 |
+
"""모든 벡터스토어를 로드하고 통합하는 함수 - 개선 버전"""
|
123 |
global vectorstores, embeddings, combined_vectorstore
|
124 |
|
125 |
+
print("🔄 벡터스토어 로딩 시작...")
|
126 |
+
|
127 |
+
# 임베딩 모델 초기화
|
128 |
if not embeddings:
|
129 |
+
try:
|
130 |
+
print("🤖 임베딩 모델 로딩 중...")
|
131 |
+
embeddings = HuggingFaceEmbeddings(
|
132 |
+
model_name="sentence-transformers/all-MiniLM-L6-v2"
|
133 |
+
)
|
134 |
+
print("✅ 임베딩 모델 로딩 완료")
|
135 |
+
except Exception as e:
|
136 |
+
print(f"❌ 임베딩 모델 로딩 실패: {e}")
|
137 |
+
return False
|
138 |
|
139 |
# 벡터스토어 폴더들 찾기
|
140 |
folders = find_vectorstore_folders()
|
141 |
|
142 |
+
if not folders:
|
143 |
+
print("❌ 벡터스토어 폴더를 찾을 수 없습니다")
|
144 |
+
return False
|
145 |
+
|
146 |
loaded_vectorstores = []
|
147 |
|
148 |
for folder_name in folders:
|
149 |
+
folder_path = f"./{folder_name}"
|
150 |
try:
|
151 |
+
print(f"📂 {folder_name} 로딩 시도 중...")
|
152 |
+
|
153 |
+
# 다양한 방법으로 로딩 시도
|
154 |
+
vectorstore = None
|
155 |
+
|
156 |
+
# 방법 1: 기본 로딩
|
157 |
+
try:
|
158 |
+
vectorstore = FAISS.load_local(
|
159 |
+
folder_path,
|
160 |
+
embeddings,
|
161 |
+
allow_dangerous_deserialization=True
|
162 |
+
)
|
163 |
+
print(f"✅ {folder_name} 로드 완료 (방법 1)")
|
164 |
+
except Exception as e1:
|
165 |
+
print(f"⚠️ 방법 1 실패: {e1}")
|
166 |
+
|
167 |
+
# 방법 2: 절대 경로로 시도
|
168 |
+
try:
|
169 |
+
abs_path = os.path.abspath(folder_name)
|
170 |
+
vectorstore = FAISS.load_local(
|
171 |
+
abs_path,
|
172 |
+
embeddings,
|
173 |
+
allow_dangerous_deserialization=True
|
174 |
+
)
|
175 |
+
print(f"✅ {folder_name} 로드 완료 (방법 2)")
|
176 |
+
except Exception as e2:
|
177 |
+
print(f"⚠️ 방법 2 실패: {e2}")
|
178 |
+
|
179 |
+
# 방법 3: 직접 파일 지정
|
180 |
+
try:
|
181 |
+
index_file = os.path.join(folder_name, "index.faiss")
|
182 |
+
pkl_file = os.path.join(folder_name, "index.pkl")
|
183 |
+
|
184 |
+
if os.path.exists(index_file) and os.path.exists(pkl_file):
|
185 |
+
vectorstore = FAISS.load_local(
|
186 |
+
folder_name,
|
187 |
+
embeddings,
|
188 |
+
allow_dangerous_deserialization=True
|
189 |
+
)
|
190 |
+
print(f"✅ {folder_name} 로드 완료 (방법 3)")
|
191 |
+
else:
|
192 |
+
print(f"❌ 필수 파일 없음: {index_file}, {pkl_file}")
|
193 |
+
except Exception as e3:
|
194 |
+
print(f"❌ 방법 3 실패: {e3}")
|
195 |
+
|
196 |
+
if vectorstore:
|
197 |
+
vectorstores[folder_name] = vectorstore
|
198 |
+
loaded_vectorstores.append(vectorstore)
|
199 |
+
|
200 |
+
# 벡터스토어 정보 출력
|
201 |
+
try:
|
202 |
+
doc_count = vectorstore.index.ntotal
|
203 |
+
print(f"📊 {folder_name}: {doc_count}개 문서")
|
204 |
+
except:
|
205 |
+
print(f"📊 {folder_name}: 문서 수 확인 불가")
|
206 |
+
else:
|
207 |
+
print(f"❌ {folder_name} 로드 실패")
|
208 |
+
|
209 |
except Exception as e:
|
210 |
+
print(f"❌ {folder_name} 로드 중 예외 발생: {e}")
|
211 |
+
import traceback
|
212 |
+
traceback.print_exc()
|
213 |
|
214 |
+
# 벡터스토어들을 통합
|
215 |
if loaded_vectorstores:
|
216 |
+
try:
|
217 |
+
print("🔗 벡터스토어 통합 시작...")
|
218 |
+
combined_vectorstore = loaded_vectorstores[0]
|
219 |
+
|
220 |
+
for i, vs in enumerate(loaded_vectorstores[1:], 1):
|
221 |
+
try:
|
222 |
+
combined_vectorstore.merge_from(vs)
|
223 |
+
print(f"✅ 벡터스토어 {i+1} 통합 완료")
|
224 |
+
except Exception as e:
|
225 |
+
print(f"❌ 벡터스토어 {i+1} 통합 실패: {e}")
|
226 |
+
|
227 |
+
print(f"🎉 총 {len(loaded_vectorstores)}개의 벡터스토어가 로드되고 통합되었습니다")
|
228 |
+
return True
|
229 |
+
|
230 |
+
except Exception as e:
|
231 |
+
print(f"❌ 벡터스토어 통합 중 오류: {e}")
|
232 |
+
# 통합 실패시 첫 번째 것만 사용
|
233 |
+
if loaded_vectorstores:
|
234 |
+
combined_vectorstore = loaded_vectorstores[0]
|
235 |
+
print("⚠️ 첫 번째 벡터스토어만 사용합니다")
|
236 |
+
return True
|
237 |
|
238 |
+
print("❌ 사용 가능한 벡터스토어가 없습니다")
|
239 |
+
return False
|
240 |
|
241 |
+
# 질문 리스트
|
242 |
suggested_questions = [
|
243 |
'교원 신규 임용은 어떻게 하나요?',
|
244 |
'교원 연구년 기간은 어떻게 되나요?',
|
|
|
262 |
'안전사고예방계획은 어디에 제출해야 하나요?'
|
263 |
]
|
264 |
|
265 |
+
# 프롬프트 템플릿
|
266 |
prompt_template = """당신은 한남대학교 규정집 도우미입니다.
|
267 |
반드시 한국어로만 답변해주세요. 영어나 다른 언어는 절대 사용하지 마세요.
|
268 |
주어진 문서 내용을 바탕으로 질문에 대해 정확하고 친절하게 한국어로 답변해주세요.
|
|
|
283 |
)
|
284 |
|
285 |
def respond_with_groq(question, selected_q, model):
|
286 |
+
"""질문에 대한 답변을 생성하는 함수 - 개선 버전"""
|
287 |
|
288 |
# 선택된 질문이 있으면 그것을 사용
|
289 |
if selected_q != "직접 입력":
|
|
|
293 |
return "질문을 입력해주세요."
|
294 |
|
295 |
if not GROQ_API_KEY:
|
296 |
+
return "❌ GROQ API 키가 설정되지 않았습니다. Hugging Face Spaces의 Settings에서 GROQ_API_KEY를 설정해주세요."
|
297 |
|
298 |
+
# 통합된 벡터스토어가 로드되지 않은 경우 재시도
|
299 |
if not combined_vectorstore:
|
300 |
+
print("⚠️ 벡터스토어가 로드되지 않음. 재로딩 시도...")
|
301 |
+
success = load_all_vectorstores()
|
302 |
+
if not success:
|
303 |
+
# 디버깅 정보 출력
|
304 |
+
debug_file_system()
|
305 |
+
return """❌ 벡터스토어를 로드할 수 없습니다.
|
306 |
+
|
307 |
+
가능한 원인:
|
308 |
+
1. 벡터스토어 파일이 올바르게 업로드되지 않음
|
309 |
+
2. 파일 권한 문제
|
310 |
+
3. Git LFS 설정 필요 (큰 파일의 경우)
|
311 |
+
|
312 |
+
해결 방법:
|
313 |
+
1. vectorstore 폴더들이 제대로 업로드되었는지 확인
|
314 |
+
2. 각 폴더에 index.faiss와 index.pkl 파일이 있는지 확인
|
315 |
+
3. Git LFS를 사용해 큰 파일들을 관리해보세요"""
|
316 |
|
317 |
try:
|
318 |
+
print(f"🔍 질문: {question}")
|
319 |
print(f"✅ 통합된 벡터스토어를 사용하여 검색 중...")
|
320 |
|
321 |
# LLM 설정
|
|
|
326 |
max_tokens=1000
|
327 |
)
|
328 |
|
329 |
+
# 검색 테스트
|
330 |
+
try:
|
331 |
+
retriever = combined_vectorstore.as_retriever(search_kwargs={"k": 5})
|
332 |
+
docs = retriever.get_relevant_documents(question)
|
333 |
+
print(f"🔍 검색된 문서 수: {len(docs)}")
|
334 |
+
except Exception as e:
|
335 |
+
print(f"❌ 검색 오류: {e}")
|
336 |
+
return f"❌ 문서 검색 중 오류가 발생했습니다: {str(e)}"
|
337 |
+
|
338 |
# QA 체인 생성
|
339 |
qa_chain = RetrievalQA.from_chain_type(
|
340 |
llm=llm,
|
|
|
352 |
import traceback
|
353 |
error_details = traceback.format_exc()
|
354 |
print(f"❌ 상세 오류 정보:\n{error_details}")
|
355 |
+
return f"""❌ 답변 생성 중 오류가 발생했습니다: {str(e)}
|
356 |
+
|
357 |
+
디버깅 정보:
|
358 |
+
- 벡터스토어 로드됨: {combined_vectorstore is not None}
|
359 |
+
- API 키 설정됨: {GROQ_API_KEY is not None}
|
360 |
+
- 모델: {model}
|
361 |
+
|
362 |
+
관리자에게 위 정보와 함께 문의해주세요."""
|
363 |
|
364 |
def update_question(selected):
|
365 |
"""드롭다운 선택 시 질문을 업데이트하는 함수"""
|
|
|
368 |
return ""
|
369 |
|
370 |
# 앱 시작시 벡터스토어들 로드
|
371 |
+
print("🚀 앱 시작 - 벡터스토어 로딩 중...")
|
372 |
vectorstores_loaded = load_all_vectorstores()
|
373 |
|
374 |
+
# 상태 메시지 생성
|
375 |
+
if vectorstores_loaded:
|
376 |
+
status_message = "✅ 벡터스토어가 성공적으로 로드되었습니다!"
|
377 |
+
status_color = "green"
|
378 |
+
else:
|
379 |
+
status_message = "❌ 벡터스토어 로딩에 실패했습니다. 관리자에게 문의하세요."
|
380 |
+
status_color = "red"
|
381 |
+
|
382 |
# Gradio 인터페이스 생성
|
383 |
with gr.Blocks(title="한남대학교 Q&A") as interface:
|
384 |
+
gr.HTML(f"""
|
385 |
<div style="text-align: center; padding: 20px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
|
386 |
<h1>🏫 한남대학교 규정집 Q&A</h1>
|
387 |
<p>한남대학교 규정집에 대한 질문에 답변해드립니다.</p>
|
388 |
</div>
|
389 |
""")
|
390 |
+
|
391 |
+
# 상태 표시 추가
|
392 |
+
gr.HTML(f"""
|
393 |
+
<div style="text-align: center; padding: 10px; background-color: {status_color}; color: white; border-radius: 5px; margin-bottom: 10px;">
|
394 |
+
<strong>{status_message}</strong>
|
395 |
+
</div>
|
396 |
+
""")
|
397 |
|
398 |
with gr.Row():
|
399 |
with gr.Column(scale=1):
|