kimrang commited on
Commit
a238014
·
verified ·
1 Parent(s): 0cac5d2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +187 -33
app.py CHANGED
@@ -6,6 +6,8 @@ from langchain.chains import RetrievalQA
6
  from langchain.prompts import PromptTemplate
7
  from langchain.vectorstores import FAISS
8
  from langchain.embeddings import HuggingFaceEmbeddings
 
 
9
 
10
  # SSL 경고 제거
11
  urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
@@ -18,6 +20,94 @@ vectorstores = {}
18
  embeddings = None
19
  combined_vectorstore = None
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  def debug_file_system():
22
  """파일 시스템 상태를 자세히 확인하는 함수"""
23
  import os
@@ -326,17 +416,19 @@ prompt = PromptTemplate(
326
  )
327
 
328
  def respond_with_groq(question, selected_q, model):
329
- """질문에 대한 답변을 생성하는 함수 - 개선 버전"""
330
 
331
  # 선택된 질문이 있으면 그것을 사용
332
  if selected_q != "직접 입력":
333
  question = selected_q
334
 
335
  if not question.strip():
336
- return "질문을 입력해주세요."
337
 
338
  if not GROQ_API_KEY:
339
- return "❌ GROQ API 키가 설정되지 않았습니다. Hugging Face Spaces의 Settings에서 GROQ_API_KEY를 설정해주세요."
 
 
340
 
341
  # 통합된 벡터스토어가 로드되지 않은 경우 재시도
342
  if not combined_vectorstore:
@@ -345,7 +437,7 @@ def respond_with_groq(question, selected_q, model):
345
  if not success:
346
  # 디버깅 정보 출력
347
  debug_file_system()
348
- return """❌ 벡터스토어를 로드할 ��� 없습니다.
349
 
350
  가능한 원인:
351
  1. 벡터스토어 파일이 올바르게 업로드되지 않음
@@ -355,6 +447,8 @@ def respond_with_groq(question, selected_q, model):
355
  1. vectorstore 폴더들이 제대로 업로드되었는지 확인
356
  2. 각 폴더에 index.faiss와 index.pkl 파일이 있는지 확인
357
  3. Git LFS를 사용해 큰 파일들을 관리해보세요"""
 
 
358
 
359
  try:
360
  print(f"🔍 질문: {question}")
@@ -380,7 +474,9 @@ def respond_with_groq(question, selected_q, model):
380
  print(f"🔍 검색된 문서 수: {len(docs)}")
381
  except Exception as e:
382
  print(f"❌ 검색 오류: {e}")
383
- return f"❌ 문서 검색 중 오류가 발생했습니다: {str(e)}"
 
 
384
 
385
  # QA 체인 생성
386
  qa_chain = RetrievalQA.from_chain_type(
@@ -393,18 +489,38 @@ def respond_with_groq(question, selected_q, model):
393
 
394
  # 답변 생성
395
  result = qa_chain({"query": question})
396
- return result['result']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
 
398
  except Exception as e:
399
  import traceback
400
  error_details = traceback.format_exc()
401
  print(f"❌ 상세 오류 정보:\n{error_details}")
402
- return f"""❌ 답변 생성 중 오류가 발생했습니다: {str(e)}
403
  디버깅 정보:
404
  - 벡터스토어 로드됨: {combined_vectorstore is not None}
405
  - API 키 설정됨: {GROQ_API_KEY is not None}
406
  - 모델: {model}
407
  관리자에게 위 정보와 함께 문의해주세요."""
 
 
 
408
 
409
  def update_question(selected):
410
  """드롭다운 선택 시 질문을 업데이트하는 함수"""
@@ -412,6 +528,11 @@ def update_question(selected):
412
  return selected
413
  return ""
414
 
 
 
 
 
 
415
  # 앱 시작시 벡터스토어들 로드
416
  print("🚀 앱 시작 - 벡터스토어 로딩 중...")
417
  vectorstores_loaded = load_all_vectorstores()
@@ -440,41 +561,64 @@ with gr.Blocks(title="한남대학교 Q&A") as interface:
440
  </div>
441
  """)
442
 
443
- with gr.Row():
444
- with gr.Column(scale=1):
445
- question_dropdown = gr.Dropdown(
446
- choices=["직접 입력"] + suggested_questions,
447
- label="💡 자주 묻는 질문",
448
- value="직접 입력"
449
- )
 
 
 
 
450
 
451
- question_input = gr.Textbox(
452
- label="❓ 질문을 입력하세요",
453
- placeholder="예: 졸업 요건은 무엇인가요?",
454
- lines=3
455
- )
456
 
457
- submit_btn = gr.Button("답변 받기", variant="primary", size="lg")
458
 
459
- model_choice = gr.Radio(
460
- choices=["llama3-70b-8192", "llama3-8b-8192"],
461
- label="🤖 AI 모델 선택",
462
- value="llama3-70b-8192"
463
- )
464
 
465
- with gr.Column(scale=2):
466
- output = gr.Textbox(
467
- label="💬 답변",
468
- lines=15,
469
- max_lines=20,
470
- show_copy_button=True
471
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
472
 
473
  # 이벤트 연결
474
  submit_btn.click(
475
  fn=respond_with_groq,
476
  inputs=[question_input, question_dropdown, model_choice],
477
- outputs=output
478
  )
479
 
480
  question_dropdown.change(
@@ -482,6 +626,16 @@ with gr.Blocks(title="한남대학교 Q&A") as interface:
482
  inputs=question_dropdown,
483
  outputs=question_input
484
  )
 
 
 
 
 
 
 
 
 
 
485
 
486
  # 앱 실행
487
  if __name__ == "__main__":
 
6
  from langchain.prompts import PromptTemplate
7
  from langchain.vectorstores import FAISS
8
  from langchain.embeddings import HuggingFaceEmbeddings
9
+ from datetime import datetime
10
+ import json
11
 
12
  # SSL 경고 제거
13
  urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
 
20
  embeddings = None
21
  combined_vectorstore = None
22
 
23
+ # 대화 기록 저장을 위한 전역 변수 (메모리 기반)
24
+ conversation_history = []
25
+
26
+ def save_conversation(question, answer, sources=None):
27
+ """대화 기록을 저장하는 함수"""
28
+ global conversation_history
29
+
30
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
31
+
32
+ conversation_entry = {
33
+ "timestamp": timestamp,
34
+ "question": question,
35
+ "answer": answer,
36
+ "sources": sources if sources else []
37
+ }
38
+
39
+ conversation_history.append(conversation_entry)
40
+
41
+ # 최대 100개까지만 저장 (메모리 절약)
42
+ if len(conversation_history) > 100:
43
+ conversation_history = conversation_history[-100:]
44
+
45
+ def get_conversation_history():
46
+ """대화 기록을 반환하는 함수"""
47
+ global conversation_history
48
+
49
+ if not conversation_history:
50
+ return "아직 대화 기록이 없습니다."
51
+
52
+ history_text = "## 🕐 대화 기록\n\n"
53
+
54
+ # 최근 기록부터 표시
55
+ for i, entry in enumerate(reversed(conversation_history), 1):
56
+ history_text += f"### {i}. {entry['timestamp']}\n"
57
+ history_text += f"**❓ 질문:** {entry['question']}\n\n"
58
+ history_text += f"**💬 답변:** {entry['answer']}\n\n"
59
+
60
+ if entry['sources']:
61
+ history_text += f"**📚 출처:** {', '.join(entry['sources'])}\n\n"
62
+
63
+ history_text += "---\n\n"
64
+
65
+ return history_text
66
+
67
+ def clear_history():
68
+ """대화 기록을 삭제하는 함수"""
69
+ global conversation_history
70
+ conversation_history = []
71
+ return "대화 기록이 삭제되었습니다."
72
+
73
+ def extract_source_info(source_documents):
74
+ """소스 문서에서 출처 정보를 추출하는 함수"""
75
+ sources = []
76
+
77
+ for doc in source_documents:
78
+ # 메타데이터에서 소스 정보 추출
79
+ if hasattr(doc, 'metadata') and doc.metadata:
80
+ source = doc.metadata.get('source', '')
81
+ if source:
82
+ # 파일명에서 규정명 추출 (예: "학사규정.pdf" -> "학사규정")
83
+ source_name = os.path.basename(source).replace('.pdf', '').replace('.txt', '')
84
+ if source_name and source_name not in sources:
85
+ sources.append(source_name)
86
+
87
+ # 문서 내용에서 규정명 패턴 찾기
88
+ if hasattr(doc, 'page_content'):
89
+ content = doc.page_content
90
+ # 일반적인 규정명 패턴들
91
+ import re
92
+ patterns = [
93
+ r'([가-힣\s]+규정)',
94
+ r'([가-힣\s]+규칙)',
95
+ r'([가-힣\s]+세칙)',
96
+ r'([가-힣\s]+지침)',
97
+ r'([가-힣\s]+운영요강)',
98
+ r'([가-힣\s]+관리요령)'
99
+ ]
100
+
101
+ for pattern in patterns:
102
+ matches = re.findall(pattern, content)
103
+ for match in matches:
104
+ clean_match = match.strip()
105
+ if len(clean_match) > 2 and clean_match not in sources:
106
+ sources.append(clean_match)
107
+ break # 첫 번째 매치만 사용
108
+
109
+ return sources[:3] # 최대 3개까지만 반환
110
+
111
  def debug_file_system():
112
  """파일 시스템 상태를 자세히 확인하는 함수"""
113
  import os
 
416
  )
417
 
418
  def respond_with_groq(question, selected_q, model):
419
+ """질문에 대한 답변을 생성하는 함수 - 개선 버전 (출처 정보 포함)"""
420
 
421
  # 선택된 질문이 있으면 그것을 사용
422
  if selected_q != "직접 입력":
423
  question = selected_q
424
 
425
  if not question.strip():
426
+ return "질문을 입력해주세요.", ""
427
 
428
  if not GROQ_API_KEY:
429
+ error_msg = "❌ GROQ API 키가 설정되지 않았습니다. Hugging Face Spaces의 Settings에서 GROQ_API_KEY를 설정해주세요."
430
+ save_conversation(question, error_msg)
431
+ return error_msg, get_conversation_history()
432
 
433
  # 통합된 벡터스토어가 로드되지 않은 경우 재시도
434
  if not combined_vectorstore:
 
437
  if not success:
438
  # 디버깅 정보 출력
439
  debug_file_system()
440
+ error_msg = """❌ 벡터스토어를 로드할 없습니다.
441
 
442
  가능한 원인:
443
  1. 벡터스토어 파일이 올바르게 업로드되지 않음
 
447
  1. vectorstore 폴더들이 제대로 업로드되었는지 확인
448
  2. 각 폴더에 index.faiss와 index.pkl 파일이 있는지 확인
449
  3. Git LFS를 사용해 큰 파일들을 관리해보세요"""
450
+ save_conversation(question, error_msg)
451
+ return error_msg, get_conversation_history()
452
 
453
  try:
454
  print(f"🔍 질문: {question}")
 
474
  print(f"🔍 검색된 문서 수: {len(docs)}")
475
  except Exception as e:
476
  print(f"❌ 검색 오류: {e}")
477
+ error_msg = f"❌ 문서 검색 중 오류가 발생했습니다: {str(e)}"
478
+ save_conversation(question, error_msg)
479
+ return error_msg, get_conversation_history()
480
 
481
  # QA 체인 생성
482
  qa_chain = RetrievalQA.from_chain_type(
 
489
 
490
  # 답변 생성
491
  result = qa_chain({"query": question})
492
+ answer = result['result']
493
+ source_documents = result.get('source_documents', [])
494
+
495
+ # 출처 정보 추출
496
+ sources = extract_source_info(source_documents)
497
+
498
+ # 답변에 출처 정보 추가
499
+ if sources:
500
+ source_text = f"\n\n📚 **출처:** {', '.join(sources)}"
501
+ final_answer = answer + source_text
502
+ else:
503
+ final_answer = answer
504
+ sources = []
505
+
506
+ # 대화 기록 저장
507
+ save_conversation(question, final_answer, sources)
508
+
509
+ return final_answer, get_conversation_history()
510
 
511
  except Exception as e:
512
  import traceback
513
  error_details = traceback.format_exc()
514
  print(f"❌ 상세 오류 정보:\n{error_details}")
515
+ error_msg = f"""❌ 답변 생성 중 오류가 발생했습니다: {str(e)}
516
  디버깅 정보:
517
  - 벡터스토어 로드됨: {combined_vectorstore is not None}
518
  - API 키 설정됨: {GROQ_API_KEY is not None}
519
  - 모델: {model}
520
  관리자에게 위 정보와 함께 문의해주세요."""
521
+
522
+ save_conversation(question, error_msg)
523
+ return error_msg, get_conversation_history()
524
 
525
  def update_question(selected):
526
  """드롭다운 선택 시 질문을 업데이트하는 함수"""
 
528
  return selected
529
  return ""
530
 
531
+ def handle_clear_history():
532
+ """기록 삭제 버튼 핸들러"""
533
+ clear_msg = clear_history()
534
+ return clear_msg, ""
535
+
536
  # 앱 시작시 벡터스토어들 로드
537
  print("🚀 앱 시작 - 벡터스토어 로딩 중...")
538
  vectorstores_loaded = load_all_vectorstores()
 
561
  </div>
562
  """)
563
 
564
+ # 탭 생성
565
+ with gr.Tabs():
566
+ # 메인 Q&A 탭
567
+ with gr.TabItem("💬 질문하기"):
568
+ with gr.Row():
569
+ with gr.Column(scale=1):
570
+ question_dropdown = gr.Dropdown(
571
+ choices=["직접 입력"] + suggested_questions,
572
+ label="💡 자주 묻는 질문",
573
+ value="직접 입력"
574
+ )
575
 
576
+ question_input = gr.Textbox(
577
+ label="❓ 질문을 입력하세요",
578
+ placeholder="예: 졸업 요건은 무엇인가요?",
579
+ lines=3
580
+ )
581
 
582
+ submit_btn = gr.Button("답변 받기", variant="primary", size="lg")
583
 
584
+ model_choice = gr.Radio(
585
+ choices=["llama3-70b-8192", "llama3-8b-8192"],
586
+ label="🤖 AI 모델 선택",
587
+ value="llama3-70b-8192"
588
+ )
589
 
590
+ with gr.Column(scale=2):
591
+ output = gr.Textbox(
592
+ label="💬 답변",
593
+ lines=15,
594
+ max_lines=20,
595
+ show_copy_button=True
596
+ )
597
+
598
+ # 대화 기록 탭
599
+ with gr.TabItem("📋 대화 기록"):
600
+ with gr.Row():
601
+ with gr.Column():
602
+ gr.HTML("""
603
+ <div style="text-align: center; padding: 15px; background-color: #f8f9fa; border-radius: 8px; margin-bottom: 15px;">
604
+ <h3>🕐 이전 대화 기록</h3>
605
+ <p>지금까지의 질문과 답변 내역을 확인할 수 있습니다.</p>
606
+ </div>
607
+ """)
608
+
609
+ refresh_btn = gr.Button("🔄 기록 새로고침", variant="secondary")
610
+ clear_history_btn = gr.Button("🗑️ 기록 삭제", variant="stop")
611
+
612
+ history_output = gr.Markdown(
613
+ value="아직 대화 기록이 없습니다.",
614
+ label="대화 기록"
615
+ )
616
 
617
  # 이벤트 연결
618
  submit_btn.click(
619
  fn=respond_with_groq,
620
  inputs=[question_input, question_dropdown, model_choice],
621
+ outputs=[output, history_output]
622
  )
623
 
624
  question_dropdown.change(
 
626
  inputs=question_dropdown,
627
  outputs=question_input
628
  )
629
+
630
+ refresh_btn.click(
631
+ fn=get_conversation_history,
632
+ outputs=history_output
633
+ )
634
+
635
+ clear_history_btn.click(
636
+ fn=handle_clear_history,
637
+ outputs=[history_output, output]
638
+ )
639
 
640
  # 앱 실행
641
  if __name__ == "__main__":