hallu11 commited on
Commit
8ff3889
·
verified ·
1 Parent(s): 6b04904

Upload 20200288_신성환__챗봇ui_구현_(1).py

Browse files
20200288_신성환__챗봇ui_구현_(1).py ADDED
@@ -0,0 +1,686 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """20200288_신성환__챗봇UI_구현 (1).ipynb
3
+
4
+ Automatically generated by Colab.
5
+
6
+ Original file is located at
7
+ https://colab.research.google.com/drive/1MWLEePYm47EBEFapa42ysUbVuUOP9APh
8
+
9
+ # **✅ 0. 챗봇 기본 실행/설치 코드**
10
+
11
+ **그록 API KEY 정보**
12
+ """
13
+
14
+ # API_Key = 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
15
+
16
+ !pip install -q pypdf
17
+
18
+ pip install pymupdf
19
+
20
+ !pip install langchain_community
21
+
22
+ pip install faiss-cpu
23
+
24
+ pip install -qU "langchain[groq]"
25
+
26
+ !pip install -qU langchain langchain-community langchain-groq langchain-huggingface faiss-cpu PyMuPDF gradio
27
+
28
+ """# **✅ 1. 라이브러리 설치 및 환경 설정**"""
29
+
30
+ # 충돌나는 잘못된 fitz 제거
31
+ !pip uninstall -y fitz
32
+
33
+ # 필요한 패키지 설치
34
+ !pip install PyMuPDF openai
35
+
36
+ """# **✅ 1. 라이브러리 설치 및 import**"""
37
+
38
+ !pip uninstall -y fitz # 잘못된 fitz 제거
39
+ !pip install PyMuPDF openai
40
+
41
+ import fitz # PyMuPDF
42
+ from google.colab import files
43
+ import openai
44
+
45
+ """# **✅ 2. PDF 업로드 및 텍스트 추출**"""
46
+
47
+ uploaded = files.upload()
48
+ pdf_path = list(uploaded.keys())[0]
49
+
50
+ def extract_text_from_pdf(path):
51
+ doc = fitz.open(path)
52
+ return "".join(page.get_text() for page in doc)
53
+
54
+ document_text = extract_text_from_pdf(pdf_path)
55
+
56
+ """# **✅ 3. Groq API 기반 LLM 설정**"""
57
+
58
+ from openai import OpenAI
59
+
60
+ client = OpenAI(
61
+ base_url="https://api.groq.com/openai/v1", # 꼭 https 포함
62
+ api_key="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" # ⚠️ 반드시 자신의 키로 교체
63
+ )
64
+
65
+ """# **✅ 4. 질문 함수 정의 (document_text 기반 질의응답)**"""
66
+
67
+ def ask_question(question, context):
68
+ response = client.chat.completions.create(
69
+ model="llama3-8b-8192",
70
+ messages=[
71
+ {"role": "system", "content": "너는 업로드한 문서를 설명하는 한국어 도우미야. 항상 한국어로 대답해."},
72
+ {"role": "user", "content": f"{question}\n\n[문서 내용]\n{context}"}
73
+ ]
74
+ )
75
+ return response.choices[0].message.content
76
+
77
+ """# **✅ 5. 간단한 문서 기반 질의응답 예시**"""
78
+
79
+ question = "이 문서는 어떤 행사야?"
80
+ answer = ask_question(question, document_text[:1000])
81
+ print("🤖 답변:", answer)
82
+
83
+ """# **✅ 6. 특정 키워드 기반 문단 추출 + 질문**"""
84
+
85
+ def extract_relevant_passage(text, keyword="대추나무"):
86
+ passages = text.split('\n')
87
+ return "\n".join([p for p in passages if keyword in p])
88
+
89
+ relevant_text = extract_relevant_passage(document_text, "대추나무")
90
+ question = "대추나무 시집 보내기가 뭐야?"
91
+ answer = ask_question(question, relevant_text)
92
+ print("🤖 답변:", answer)
93
+
94
+ """# **✅ 전체를 함수화한 구조 (재사용 및 확장성)**"""
95
+
96
+ def pdf_to_text():
97
+ from google.colab import files
98
+ import fitz
99
+
100
+ uploaded = files.upload()
101
+ pdf_path = list(uploaded.keys())[0]
102
+ doc = fitz.open(pdf_path)
103
+ return "".join(page.get_text() for page in doc)
104
+
105
+ def setup_groq_client(api_key):
106
+ from openai import OpenAI
107
+ return OpenAI(
108
+ base_url="https://api.groq.com/openai/v1",
109
+ api_key=api_key
110
+ )
111
+
112
+ def ask_question(client, question, context):
113
+ response = client.chat.completions.create(
114
+ model="llama3-8b-8192",
115
+ messages=[
116
+ {"role": "system", "content": "너는 업로드한 문서를 설명하는 한국어 도우미야. 항상 한국어로 대답해."},
117
+ {"role": "user", "content": f"{question}\n\n[문서 내용]\n{context}"}
118
+ ]
119
+ )
120
+ return response.choices[0].message.content
121
+
122
+ def extract_relevant_passage(text, keyword):
123
+ return "\n".join([p for p in text.split('\n') if keyword in p])
124
+
125
+ """# **🎯 최종 사용 예시**
126
+
127
+ #✅ 1. 먼저 한 번만 실행: Groq 클라이언트 설정
128
+ """
129
+
130
+ from openai import OpenAI
131
+
132
+ client = OpenAI(
133
+ base_url="https://api.groq.com/openai/v1", # https 필수
134
+ api_key="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" # ⚠️ 본인의 키로 바꾸세요
135
+ )
136
+
137
+ """# ✅ 2. 질문 함수 정의 (한 번만 정의)"""
138
+
139
+ def ask_question(question):
140
+ response = client.chat.completions.create(
141
+ model="llama3-8b-8192",
142
+ messages=[
143
+ {"role": "system", "content": "너는 업로드한 문서를 설명하는 한국어 도우미야. 항상 한국어로 답변해."},
144
+ {"role": "user", "content": f"{question}\n\n[문서 내용 일부]\n{document_text[:3000]}"}
145
+ ]
146
+ )
147
+ return response.choices[0].message.content
148
+
149
+ """# ✅ 3. 질문만 하면 OK"""
150
+
151
+ question = "문서에 등장하는 발달장애인 교육사업에 대해 설명해줘"
152
+ answer = ask_question(question)
153
+ print("🤖 답변:", answer)
154
+
155
+ """# **1. 모든 업로드 문서를 처리하는 함수**"""
156
+
157
+ def extract_texts_from_uploaded_pdfs():
158
+ from google.colab import files
159
+ import fitz
160
+
161
+ uploaded = files.upload()
162
+ texts = []
163
+
164
+ for filename in uploaded.keys():
165
+ doc = fitz.open(filename)
166
+ text = "".join(page.get_text() for page in doc)
167
+ texts.append(text)
168
+
169
+ return "\n\n".join(texts) # 모든 문서를 하나의 문자열로 결합
170
+
171
+ document_text = extract_texts_from_uploaded_pdfs()
172
+
173
+ """# **2. 질문 함수 (document_text 전체 사용)**"""
174
+
175
+ def ask_question(question):
176
+ context_chunk = document_text[:3000] # 필요 시 슬라이싱 조절
177
+ response = client.chat.completions.create(
178
+ model="llama3-8b-8192",
179
+ messages=[
180
+ {"role": "system", "content": "너는 업로드한 모든 문서를 설명하는 한국어 도우미야. 항상 한국어로 답변해."},
181
+ {"role": "user", "content": f"{question}\n\n[문서 내용]\n{context_chunk}"}
182
+ ]
183
+ )
184
+ return response.choices[0].message.content
185
+
186
+ """# **3. 질문 예시**"""
187
+
188
+ print(ask_question("단오와 관련된 전통 풍습은 어떤 것이 있나요?"))
189
+
190
+ print(ask_question("성인문화학교 강좌 소개 해줘"))
191
+
192
+ print(ask_question("의왕단오축제 유래에 대해 알려줘 업로드된 문서 의왕단오축제_의왕문화원을 바탕으로 답변을 해줘"))
193
+
194
+ """# **✅ 1. 여러 문서 PDF → 리스트 형태로 텍스트 추출**"""
195
+
196
+ from google.colab import files
197
+ import fitz # PyMuPDF
198
+
199
+ def extract_texts_from_multiple_pdfs():
200
+ uploaded = files.upload()
201
+ pdf_texts = []
202
+
203
+ for filename in uploaded:
204
+ with fitz.open(filename) as doc:
205
+ text = "".join(page.get_text() for page in doc)
206
+ pdf_texts.append({"filename": filename, "text": text})
207
+
208
+ return pdf_texts
209
+
210
+ all_documents = extract_texts_from_multiple_pdfs()
211
+
212
+ """# **✅ 2. 키워드 기반 관련 문서 선택 함수**"""
213
+
214
+ def get_documents_by_keyword(documents, keyword):
215
+ matches = []
216
+ for doc in documents:
217
+ if keyword.lower() in doc["text"].lower():
218
+ matches.append(doc["text"])
219
+ return "\n\n".join(matches)
220
+
221
+ """# **✅ 3. 질문 함수 (선택된 문서로만)**"""
222
+
223
+ def ask_question_with_context(question, context):
224
+ response = client.chat.completions.create(
225
+ model="llama3-8b-8192",
226
+ messages=[
227
+ {"role": "system", "content": "너는 업로드한 문서를 바탕으로 설명하는 한국어 도우미야."},
228
+ {"role": "user", "content": f"{question}\n\n[문서 내용]\n{context[:3000]}"} # 길이 제한
229
+ ]
230
+ )
231
+ return response.choices[0].message.content
232
+
233
+ """# **✅ 4. 사용 예시**"""
234
+
235
+ keyword = "의왕단오축제"
236
+ context = get_documents_by_keyword(all_documents, keyword)
237
+ print(ask_question_with_context("의왕단오축제의 유래와 목적을 한국어로 알려줘", context))
238
+
239
+ keyword = "발달장애인 교육사업"
240
+ context = get_documents_by_keyword(all_documents, keyword)
241
+ print(ask_question_with_context("발달장애인 교육사업에 대해 한국어로 알려줘", context))
242
+
243
+ keyword = "성인 문화학교"
244
+ context = get_documents_by_keyword(all_documents, keyword)
245
+ print(ask_question_with_context("성인문화학교 프로그램에 대해 한국어로 알려줘", context))
246
+
247
+ """# **챗봇 UI**
248
+
249
+ # 마지막 챗봇Ui
250
+
251
+ # 📦 1단계: 필수 패키지 설치
252
+ """
253
+
254
+ !pip install -qU langchain langchain-community langchain-groq langchain-huggingface faiss-cpu PyMuPDF gradio
255
+
256
+ !pip install gradio langchain langchain-community huggingface_hub tiktoken groq
257
+
258
+ """# 🔐 2단계: Groq API 설정"""
259
+
260
+ import os
261
+ from groq import Groq
262
+ os.environ["GROQ_API_KEY"] = "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" # 본인의 Groq API Key로 교체
263
+ client = Groq(api_key=os.environ["GROQ_API_KEY"])
264
+
265
+ """#📄 3단계: PDF 업로드 및 텍스트 추출"""
266
+
267
+ from google.colab import files
268
+ import fitz # PyMuPDF
269
+
270
+ def extract_texts_from_multiple_pdfs():
271
+ uploaded = files.upload()
272
+ pdf_texts = []
273
+
274
+ for filename in uploaded:
275
+ with fitz.open(filename) as doc:
276
+ text = "".join(page.get_text() for page in doc)
277
+ pdf_texts.append({"filename": filename, "text": text})
278
+
279
+ return pdf_texts
280
+
281
+ all_documents = extract_texts_from_multiple_pdfs()
282
+
283
+ """# 📚 4단계: LangChain Document 객체 변환"""
284
+
285
+ from langchain_core.documents import Document
286
+
287
+ langchain_docs = [
288
+ Document(page_content=doc["text"], metadata={"source": doc["filename"]})
289
+ for doc in all_documents
290
+ ]
291
+
292
+ """# 🧠 5단계: 임베딩 + FAISS 벡터 스토어"""
293
+
294
+ from langchain_community.embeddings import HuggingFaceEmbeddings
295
+ from langchain_community.vectorstores import FAISS
296
+ from langchain_community.vectorstores.utils import DistanceStrategy
297
+
298
+ embedding_model = HuggingFaceEmbeddings(
299
+ model_name="jhgan/ko-sbert-nli",
300
+ model_kwargs={"device": "cpu"},
301
+ encode_kwargs={"normalize_embeddings": True}
302
+ )
303
+
304
+ # ✅ 키워드로 관련 문서 필터링 함수
305
+ def filter_documents_by_keyword(docs, keyword):
306
+ keyword_lower = keyword.lower()
307
+ return [doc for doc in docs if keyword_lower in doc.page_content.lower()]
308
+
309
+ """# 🤖 6단계: Groq + QA Chain 생성 함수 (동적으로 생성)"""
310
+
311
+ from langchain_groq import ChatGroq
312
+ from langchain.chains import RetrievalQA
313
+ from langchain.prompts import PromptTemplate
314
+
315
+ def build_qa_chain(filtered_docs):
316
+ if not filtered_docs:
317
+ return None
318
+
319
+ local_vs = FAISS.from_documents(
320
+ documents=filtered_docs,
321
+ embedding=embedding_model,
322
+ distance_strategy=DistanceStrategy.COSINE
323
+ )
324
+
325
+ retriever = local_vs.as_retriever(
326
+ search_type="mmr",
327
+ search_kwargs={"k": 5, "lambda_mult": 0.2}
328
+ )
329
+
330
+ llm = ChatGroq(model_name="llama3-8b-8192", temperature=0.1)
331
+
332
+ prompt = PromptTemplate(
333
+ input_variables=["context", "question"],
334
+ template="""
335
+ 당신은 문화 프로그램에 대해 친절하고 정확하게 설명하는 한국어 도우미입니다.
336
+
337
+ 문서 내용:
338
+ {context}
339
+
340
+ 질문: {question}
341
+
342
+ 지침:
343
+ - 반드시 한국어로 답변해주세요
344
+ - 문서에 없으면 "죄송하지만 해당 정보는 찾을 수 없습니다"라고 답변하세요
345
+ """
346
+ )
347
+
348
+ return RetrievalQA.from_chain_type(
349
+ llm=llm,
350
+ chain_type="stuff",
351
+ retriever=retriever,
352
+ chain_type_kwargs={"prompt": prompt},
353
+ return_source_documents=False
354
+ )
355
+
356
+ """# 💬 7단계: Gradio UI"""
357
+
358
+ !pip install gradio --quiet
359
+
360
+ import gradio as gr
361
+
362
+ # --- 전역 변수 ---
363
+ chat_history = []
364
+ uploaded_file = None
365
+ current_keyword = ""
366
+
367
+ def respond_with_groq(message, file, keyword):
368
+ global chat_history, uploaded_file, current_keyword
369
+
370
+ if file is not None:
371
+ uploaded_file = file.name
372
+
373
+ keyword = keyword.strip()
374
+ current_keyword = keyword
375
+
376
+ if keyword:
377
+ answer_text = f"키워드 '{keyword}' 기반으로 '{message}'에 대한 응답입니다."
378
+ else:
379
+ answer_text = f"전체 문서 기반으로 '{message}'에 대한 응답입니다."
380
+
381
+ chat_history.append({"role": "user", "content": message})
382
+ chat_history.append({"role": "assistant", "content": answer_text})
383
+
384
+ return "", chat_history
385
+
386
+ def clear_chat():
387
+ global chat_history
388
+ chat_history = []
389
+ return chat_history
390
+
391
+ with gr.Blocks(title="오아시스 문화 챗봇") as demo:
392
+ gr.HTML("""
393
+ <div style='display:flex; align-items:center; gap:10px; padding: 10px 0;'>
394
+ <span style='font-size:28px;'>📚</span>
395
+ <h1 style='margin:0; font-size:24px;'>오아시스 문화 프로그램 Q&A 챗봇</h1>
396
+ <div style="margin-left:auto;">
397
+ <input id="keyword-box" type="text" placeholder="키워드 검색" style="width:120px; height:24px; font-size:12px;"/>
398
+ </div>
399
+ </div>
400
+
401
+ <script>
402
+ const input = document.getElementById('keyword-box');
403
+ // Gradio에서 자동으로 생성되는 숨김 textbox를 찾기 위한 함수
404
+ function findKeywordInput() {
405
+ return document.querySelector('textarea[aria-label="keyword_input"]') || document.querySelector('input[type=text][style*="display:none"]');
406
+ }
407
+
408
+ input.addEventListener('input', () => {
409
+ const grKeywordInput = findKeywordInput();
410
+ if (grKeywordInput) {
411
+ grKeywordInput.value = input.value;
412
+ grKeywordInput.dispatchEvent(new Event('input', { bubbles: true }));
413
+ }
414
+ });
415
+ </script>
416
+ """)
417
+
418
+ chatbot = gr.Chatbot(label="💬 챗봇 응답창", height=400, type="messages")
419
+
420
+ with gr.Row():
421
+ with gr.Column(scale=10):
422
+ with gr.Row():
423
+ file_upload = gr.File(label="📎", file_types=[".pdf", ".txt"], interactive=True, show_label=False, scale=1)
424
+ user_input = gr.Textbox(placeholder="질문을 입력하세요...", show_label=False, lines=1, scale=9, container=True)
425
+ with gr.Column(scale=1, min_width=100):
426
+ with gr.Row():
427
+ send_button = gr.Button("📩", size="sm", scale=1)
428
+ clear_button = gr.Button("🗑️", size="sm", scale=1)
429
+
430
+ keyword_input = gr.Textbox(visible=False) # 숨김 키워드 입력
431
+
432
+ send_button.click(fn=respond_with_groq, inputs=[user_input, file_upload, keyword_input], outputs=[user_input, chatbot])
433
+ user_input.submit(fn=respond_with_groq, inputs=[user_input, file_upload, keyword_input], outputs=[user_input, chatbot])
434
+ clear_button.click(fn=clear_chat, outputs=chatbot)
435
+
436
+ demo.launch()
437
+
438
+ # 📦 1단계: 필수 패키지 설치
439
+ !pip install -qU langchain langchain-community langchain-groq langchain-huggingface faiss-cpu PyMuPDF gradio
440
+
441
+ # 🔐 2단계: Groq API 설정
442
+ import os
443
+ from groq import Groq
444
+ os.environ["GROQ_API_KEY"] = "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" # 본인의 Groq API Key로 교체
445
+ client = Groq(api_key=os.environ["GROQ_API_KEY"])
446
+
447
+ # 📄 3단계: PDF 업로드 및 텍스트 추출
448
+ from google.colab import files
449
+ import fitz # PyMuPDF
450
+
451
+ def extract_texts_from_multiple_pdfs():
452
+ uploaded = files.upload()
453
+ pdf_texts = []
454
+
455
+ for filename in uploaded:
456
+ with fitz.open(filename) as doc:
457
+ text = "".join(page.get_text() for page in doc)
458
+ pdf_texts.append({"filename": filename, "text": text})
459
+
460
+ return pdf_texts
461
+
462
+ all_documents = extract_texts_from_multiple_pdfs()
463
+
464
+ # 📚 4단계: LangChain Document 객체 변환
465
+ from langchain_core.documents import Document
466
+
467
+ langchain_docs = [
468
+ Document(page_content=doc["text"], metadata={"source": doc["filename"]})
469
+ for doc in all_documents
470
+ ]
471
+
472
+ # 🧠 5단계: 임베딩 + FAISS 벡터 스토어
473
+ from langchain_community.embeddings import HuggingFaceEmbeddings
474
+ from langchain_community.vectorstores import FAISS
475
+ from langchain_community.vectorstores.utils import DistanceStrategy
476
+
477
+ embedding_model = HuggingFaceEmbeddings(
478
+ model_name="jhgan/ko-sbert-nli",
479
+ model_kwargs={"device": "cpu"},
480
+ encode_kwargs={"normalize_embeddings": True}
481
+ )
482
+
483
+ # ✅ 키워드로 관련 문서 필터링 함수
484
+ def filter_documents_by_keyword(docs, keyword):
485
+ keyword_lower = keyword.lower()
486
+ return [doc for doc in docs if keyword_lower in doc.page_content.lower()]
487
+
488
+ # 🤖 6단계: Groq + QA Chain 생성 함수 (동적으로 생성)
489
+ from langchain_groq import ChatGroq
490
+ from langchain.chains import RetrievalQA
491
+ from langchain.prompts import PromptTemplate
492
+
493
+ def build_qa_chain(filtered_docs):
494
+ if not filtered_docs:
495
+ return None
496
+
497
+ local_vs = FAISS.from_documents(
498
+ documents=filtered_docs,
499
+ embedding=embedding_model,
500
+ distance_strategy=DistanceStrategy.COSINE
501
+ )
502
+
503
+ retriever = local_vs.as_retriever(
504
+ search_type="mmr",
505
+ search_kwargs={"k": 5, "lambda_mult": 0.2}
506
+ )
507
+
508
+ llm = ChatGroq(model_name="llama3-8b-8192", temperature=0.1)
509
+
510
+ prompt = PromptTemplate(
511
+ input_variables=["context", "question"],
512
+ template="""
513
+ 당신은 문화 프로그램에 대해 친절하고 정확하게 설명하는 한국어 도우미입니다.
514
+
515
+ 문서 내용:
516
+ {context}
517
+
518
+ 질문: {question}
519
+
520
+ 지침:
521
+ - 반드시 한국어로 답변해주세요
522
+ - 문서에 없으면 "죄송하지만 해당 정보는 찾을 수 없습니다"라고 답변하세요
523
+ """
524
+ )
525
+
526
+ return RetrievalQA.from_chain_type(
527
+ llm=llm,
528
+ chain_type="stuff",
529
+ retriever=retriever,
530
+ chain_type_kwargs={"prompt": prompt},
531
+ return_source_documents=False
532
+ )
533
+
534
+ # 💬 7단계: Gradio UI
535
+ import gradio as gr
536
+ import time
537
+
538
+ current_keyword = None
539
+ current_qa_chain = None
540
+
541
+ def respond_with_groq(question, history, keyword):
542
+ global current_keyword, current_qa_chain
543
+
544
+ if not keyword.strip():
545
+ return "", history + [("⚠️ 키워드를 입력하세요", "")]
546
+
547
+ if keyword != current_keyword:
548
+ filtered_docs = filter_documents_by_keyword(langchain_docs, keyword)
549
+ current_qa_chain = build_qa_chain(filtered_docs)
550
+ current_keyword = keyword
551
+
552
+ if not current_qa_chain:
553
+ return "", history + [(f"'{keyword}' 관련 문서를 찾을 수 없습니다.", "")]
554
+
555
+ history.append((question, "답변 생성 중..."))
556
+ time.sleep(0.3)
557
+ try:
558
+ result = current_qa_chain({"query": question})
559
+ answer = result["result"]
560
+ except Exception as e:
561
+ answer = f"⚠️ 오류 발생: {str(e)}"
562
+ history[-1] = (question, answer)
563
+ return "", history
564
+
565
+ def clear_history():
566
+ return []
567
+
568
+ with gr.Blocks(title="오아시스 기반 문화프로그램 관련 Q&A 챗봇") as demo:
569
+ # 상단에 왼쪽에 작은 로고 이미지 추가 (img_url은 본인의 오아시스 마크 이미지 URL로 교체)
570
+ img_url = "https://oasis_logo.png" # 예: 구글 드라이브 공개 링크 또는 외부 이미지 링크
571
+
572
+ gr.Markdown(f"""
573
+ <div style="display:flex; align-items:center;">
574
+ <img src="{img_url}" alt="오아시스 마크" style="height:40px; width:auto; margin-right:10px;">
575
+ <h2 style="margin:0;">🤖 오아시스 아카이브 기반 키워드 문화 Q&A 챗봇</h2>
576
+ </div>
577
+ """)
578
+
579
+ gr.Markdown("<small><i>키워드로 관련 문서를 검색한 뒤 질문하세요. PDF는 미리 업로드되어 있어야 합니다.</i></small>")
580
+
581
+ with gr.Row():
582
+ keyword_input = gr.Textbox(label="🔍 키워드 (예: 성인 문화학교)", placeholder="예: 의왕단오축제")
583
+ question_input = gr.Textbox(label="🙋 질문", placeholder="예: 의왕단오축제의 유래는 무엇인가요?", lines=2)
584
+
585
+ chatbot = gr.Chatbot(label="💬 챗봇 대화창", height=400)
586
+
587
+ with gr.Row():
588
+ submit_btn = gr.Button("답변받기 💡", variant="primary")
589
+ clear_btn = gr.Button("기록 초기화 🗑️")
590
+
591
+ submit_btn.click(fn=respond_with_groq, inputs=[question_input, chatbot, keyword_input], outputs=[question_input, chatbot])
592
+ question_input.submit(fn=respond_with_groq, inputs=[question_input, chatbot, keyword_input], outputs=[question_input, chatbot])
593
+ clear_btn.click(fn=clear_history, inputs=None, outputs=chatbot)
594
+
595
+ demo.launch()
596
+
597
+ """# ***진짜 찐찐 최종 UI***"""
598
+
599
+ # STEP 1. 필수 라이브러리 설치
600
+ !pip install -q gradio groq fitz # PyMuPDF는 fitz로 제공됨
601
+
602
+ # STEP 2. Groq API 설정
603
+ import os
604
+ from groq import Groq
605
+
606
+ os.environ["GROQ_API_KEY"] = "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" # ← 본인의 Groq API 키 입력
607
+ client = Groq(api_key=os.environ["GROQ_API_KEY"])
608
+
609
+ # STEP 3. PDF 업로드 및 텍스트 추출
610
+ from google.colab import files
611
+ import fitz # PyMuPDF
612
+
613
+ def extract_texts_from_multiple_pdfs():
614
+ uploaded = files.upload()
615
+ pdf_texts = []
616
+
617
+ for filename in uploaded:
618
+ with fitz.open(filename) as doc:
619
+ text = "".join(page.get_text() for page in doc)
620
+ pdf_texts.append({"filename": filename, "text": text})
621
+
622
+ return pdf_texts
623
+
624
+ all_documents = extract_texts_from_multiple_pdfs()
625
+
626
+ # STEP 4. 키워드 기반 문서 필터링
627
+ def get_documents_by_keyword(documents, keyword):
628
+ matches = []
629
+ for doc in documents:
630
+ if keyword.lower() in doc["text"].lower():
631
+ matches.append(doc["text"])
632
+ return "\n\n".join(matches)
633
+
634
+ # STEP 5. LLM 질의 함수 (Groq llama3)
635
+ def ask_question_with_context(question, context):
636
+ if not context:
637
+ return "❌ 관련 키워드를 포함하는 문서를 찾을 수 없습니다."
638
+
639
+ response = client.chat.completions.create(
640
+ model="llama3-8b-8192",
641
+ messages=[
642
+ {"role": "system", "content": "너는 업로드한 문서를 바탕으로 설명하는 한국어 도우미야."},
643
+ {"role": "user", "content": f"{question}\n\n[문서 내용]\n{context[:3000]}"}
644
+ ]
645
+ )
646
+ return response.choices[0].message.content
647
+
648
+ # STEP 6. Gradio 챗봇 UI
649
+ import gradio as gr
650
+
651
+ chat_history = []
652
+
653
+ def chatbot_fn(message, keyword):
654
+ global all_documents
655
+ context = get_documents_by_keyword(all_documents, keyword)
656
+ response = ask_question_with_context(message, context)
657
+ chat_history.append(("🙋‍♂️ " + message, "🤖 " + response))
658
+ return "", chat_history
659
+
660
+ def clear_history():
661
+ global chat_history
662
+ chat_history = []
663
+ return chat_history
664
+
665
+ with gr.Blocks(title="오아시스 PDF 챗봇") as demo:
666
+ gr.Markdown("### 📚 오아시스 챗봇 뮤지스(Musesis)")
667
+
668
+ chatbot = gr.Chatbot(label="챗봇 응답", height=400)
669
+
670
+ with gr.Row():
671
+ keyword_input = gr.Textbox(label="🔍 키워드", placeholder="예: 단오축제, 문화학교")
672
+ user_input = gr.Textbox(label="✉️ 질문", placeholder="질문을 입력하세요", lines=1)
673
+
674
+ with gr.Row():
675
+ send_btn = gr.Button("질문하기")
676
+ clear_btn = gr.Button("대화 초기화")
677
+
678
+ send_btn.click(chatbot_fn, inputs=[user_input, keyword_input], outputs=[user_input, chatbot])
679
+ user_input.submit(chatbot_fn, inputs=[user_input, keyword_input], outputs=[user_input, chatbot])
680
+ clear_btn.click(clear_history, outputs=chatbot)
681
+
682
+ demo.launch()
683
+
684
+ your_project/
685
+ ├── app.py
686
+ └── requirements.txt