anudayo commited on
Commit
06ed346
·
verified ·
1 Parent(s): df0b99b

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +443 -0
app.py ADDED
@@ -0,0 +1,443 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ from langchain_groq import ChatGroq
4
+ from langchain.chains import RetrievalQA
5
+ from langchain.prompts import PromptTemplate
6
+ from langchain.vectorstores import FAISS
7
+ from langchain.embeddings import HuggingFaceEmbeddings
8
+
9
+ # Groq API 키 (Hugging Face Secrets에서 가져옴)
10
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
11
+
12
+ # 전역 변수들
13
+ vectorstores = {}
14
+ embeddings = None
15
+ combined_vectorstore = None
16
+
17
+ def debug_file_system():
18
+ """파일 시스템 상태를 자세히 확인하는 함수"""
19
+ import os
20
+ print("=" * 50)
21
+ print("🔍 파일 시스템 디버깅 시작")
22
+ print("=" * 50)
23
+
24
+ # 현재 디렉토리
25
+ current_dir = os.getcwd()
26
+ print(f"📂 현재 작업 디렉토리: {current_dir}")
27
+
28
+ # 루트 디렉토리의 모든 항목
29
+ try:
30
+ all_items = os.listdir('.')
31
+ print(f"📋 루트 디렉토리 내용: {all_items}")
32
+
33
+ # 각 항목의 타입 확인
34
+ for item in all_items:
35
+ item_path = os.path.join('.', item)
36
+ if os.path.isdir(item_path):
37
+ print(f"📁 {item} (디렉토리)")
38
+ try:
39
+ sub_items = os.listdir(item_path)
40
+ print(f" └── 내용: {sub_items}")
41
+ except Exception as e:
42
+ print(f" └── 접근 불가: {e}")
43
+ else:
44
+ print(f"📄 {item} (파일)")
45
+
46
+ except Exception as e:
47
+ print(f"❌ 디렉토리 읽기 오류: {e}")
48
+
49
+ # 환경 변수 확인
50
+ print(f"🔑 GROQ_API_KEY 설정됨: {'GROQ_API_KEY' in os.environ}")
51
+
52
+ print("=" * 50)
53
+
54
+ # 앱 시작 시 디버깅 실행
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):
86
+ try:
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}")
106
+ else:
107
+ print(f"❌ {folder_name} 폴더가 존재하지 않음")
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="jhgan/ko-sbert-nli"
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
+ '교원 연구년 기간은 어떻게 되나요?',
245
+ '조교 신규 임용 기준은 무엇인가요?',
246
+ '교직원의 평일 근무시간은 어떻게 되나요?',
247
+ '직원 신규 임용 원칙은 무엇인가요?',
248
+ '직원 임용시 가산점이 있나요?',
249
+ '교원 업적의 심사 내용은 무엇인가요?',
250
+ '외국인 교원의 임기는 어떻게 되나요?',
251
+ '외국인 교원의 면��� 기준은 무엇인가요?',
252
+ '기간제 계약직의 임기는 얼마정도인가요?',
253
+ '등록금 납부 방법은 무엇인가요?',
254
+ '교직 이수는 언제 신청이 가능한가요?',
255
+ '해외교류유학 지원자격은 어떻게 되나요?',
256
+ '만족도 조사 실행 대상은 누구인가요?',
257
+ '마이크로디그리의 유형은 무엇이 있나요?',
258
+ '장학금 관리 기관은 어디인가요?',
259
+ '학생 단체는 어떻게 등록하나요?',
260
+ '학생 설치물 중 금지된 설치물이 있나요?',
261
+ '비교과 교육과정의 종류는 무엇이 있나요?',
262
+ '안전사고예방계획은 어디에 제출해야 하나요?'
263
+ ]
264
+
265
+ # 프롬프트 템플릿
266
+ prompt_template = """당신은 한남대학교 규정집 도우미입니다.
267
+ 반드시 한국어로만 답변해주세요. 영어나 다른 언어는 절대 사용하지 마세요.
268
+ 주어진 문서 내용을 바탕으로 질문에 대해 정확하고 친절하게 한국어로 답변해주세요.
269
+ 참고 문서:
270
+ {context}
271
+ 질문: {question}
272
+ 답변 지침:
273
+ - 이용자를 반기는 인사로 시작하세요
274
+ - 반드시 한국어로만 답변하세요
275
+ - 정중하고 친근한 말투를 사용하세요
276
+ - 구체적이고 도움이 되는 정보를 제공하세요
277
+ - 문서에서 답을 찾을 수 없으면 "죄송하지만 해당 정보를 규정집에서 찾을 수 없습니다"라고 답변하세요
278
+ 한국어 답변:"""
279
+
280
+ prompt = PromptTemplate(
281
+ template=prompt_template,
282
+ input_variables=["context", "question"]
283
+ )
284
+
285
+ def respond_with_groq(question, selected_q, model):
286
+ """질문에 대한 답변을 생성하는 함수 - 개선 버전"""
287
+
288
+ # 선택된 질문이 있으면 그것을 사용
289
+ if selected_q != "직접 입력":
290
+ question = selected_q
291
+
292
+ if not question.strip():
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 설정
322
+ llm = ChatGroq(
323
+ groq_api_key=GROQ_API_KEY,
324
+ model_name=model,
325
+ temperature=0.1,
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,
341
+ chain_type="stuff",
342
+ retriever=combined_vectorstore.as_retriever(search_kwargs={"k": 5}),
343
+ chain_type_kwargs={"prompt": prompt},
344
+ return_source_documents=True
345
+ )
346
+
347
+ # 답변 생성
348
+ result = qa_chain({"query": question})
349
+ return result['result']
350
+
351
+ except Exception as e:
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
+ """드롭다운 선택 시 질문을 업데이트하는 함수"""
366
+ if selected != "직접 입력":
367
+ return selected
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):
400
+ question_dropdown = gr.Dropdown(
401
+ choices=["직접 입력"] + suggested_questions,
402
+ label="💡 자주 묻는 질문",
403
+ value="직접 입력"
404
+ )
405
+
406
+ question_input = gr.Textbox(
407
+ label="❓ 질문을 입력하세요",
408
+ placeholder="예: 졸업 요건은 무엇인가요?",
409
+ lines=3
410
+ )
411
+
412
+ submit_btn = gr.Button("답변 받기", variant="primary", size="lg")
413
+
414
+ model_choice = gr.Radio(
415
+ choices=["llama3-70b-8192", "llama3-8b-8192"],
416
+ label="🤖 AI 모델 선택",
417
+ value="llama3-70b-8192"
418
+ )
419
+
420
+ with gr.Column(scale=2):
421
+ output = gr.Textbox(
422
+ label="💬 답변",
423
+ lines=15,
424
+ max_lines=20,
425
+ show_copy_button=True
426
+ )
427
+
428
+ # 이벤트 연결
429
+ submit_btn.click(
430
+ fn=respond_with_groq,
431
+ inputs=[question_input, question_dropdown, model_choice],
432
+ outputs=output
433
+ )
434
+
435
+ question_dropdown.change(
436
+ fn=update_question,
437
+ inputs=question_dropdown,
438
+ outputs=question_input
439
+ )
440
+
441
+ # 앱 실행
442
+ if __name__ == "__main__":
443
+ interface.launch()