gpt-oss-RAG / app.py
openfree's picture
Update app.py
ee0cb34 verified
raw
history blame
20.5 kB
import gradio as gr
import os
from typing import List, Dict, Any, Optional
import hashlib
import json
from datetime import datetime
# PDF ์ฒ˜๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
import pymupdf # PyMuPDF
import chromadb
from chromadb.utils import embedding_functions
from langchain.text_splitter import RecursiveCharacterTextSplitter
from sentence_transformers import SentenceTransformer
import numpy as np
# Custom CSS (๊ธฐ์กด CSS + ์ถ”๊ฐ€ ์Šคํƒ€์ผ)
custom_css = """
.gradio-container {
background: linear-gradient(135deg, #667eea 0%, #764ba2 25%, #f093fb 50%, #4facfe 75%, #00f2fe 100%);
background-size: 400% 400%;
animation: gradient-animation 15s ease infinite;
min-height: 100vh;
}
@keyframes gradient-animation {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.dark .gradio-container {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 25%, #0f3460 50%, #533483 75%, #e94560 100%);
background-size: 400% 400%;
animation: gradient-animation 15s ease infinite;
}
.main-container {
background-color: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 20px;
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
border: 1px solid rgba(255, 255, 255, 0.18);
margin: 10px;
}
.dark .main-container {
background-color: rgba(30, 30, 30, 0.95);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.pdf-status {
padding: 10px;
border-radius: 10px;
margin: 10px 0;
font-size: 0.9em;
}
.pdf-success {
background-color: rgba(52, 211, 153, 0.2);
border: 1px solid rgba(52, 211, 153, 0.5);
color: #10b981;
}
.pdf-error {
background-color: rgba(248, 113, 113, 0.2);
border: 1px solid rgba(248, 113, 113, 0.5);
color: #ef4444;
}
.pdf-processing {
background-color: rgba(251, 191, 36, 0.2);
border: 1px solid rgba(251, 191, 36, 0.5);
color: #f59e0b;
}
.document-card {
padding: 12px;
margin: 8px 0;
border-radius: 8px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
cursor: pointer;
transition: all 0.3s ease;
}
.document-card:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateX(5px);
}
"""
class PDFRAGSystem:
"""PDF ๊ธฐ๋ฐ˜ RAG ์‹œ์Šคํ…œ ํด๋ž˜์Šค"""
def __init__(self):
self.documents = {}
self.embedder = None
self.vector_store = None
self.text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
length_function=len,
separators=["\n\n", "\n", ".", "!", "?", ",", " ", ""]
)
self.initialize_vector_store()
def initialize_vector_store(self):
"""๋ฒกํ„ฐ ์ €์žฅ์†Œ ์ดˆ๊ธฐํ™”"""
try:
# Sentence Transformer ๋ชจ๋ธ ๋กœ๋“œ
self.embedder = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
# ChromaDB ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
self.chroma_client = chromadb.Client()
self.collection = self.chroma_client.create_collection(
name="pdf_documents",
metadata={"hnsw:space": "cosine"}
)
except Exception as e:
print(f"Vector store initialization error: {e}")
def extract_text_from_pdf(self, pdf_path: str) -> Dict[str, Any]:
"""PDF์—์„œ ํ…์ŠคํŠธ ์ถ”์ถœ"""
try:
doc = pymupdf.open(pdf_path)
text_content = []
metadata = {
"title": doc.metadata.get("title", "Untitled"),
"author": doc.metadata.get("author", "Unknown"),
"pages": len(doc),
"creation_date": doc.metadata.get("creationDate", ""),
"file_name": os.path.basename(pdf_path)
}
for page_num, page in enumerate(doc):
text = page.get_text()
if text.strip():
text_content.append({
"page": page_num + 1,
"content": text
})
doc.close()
return {
"metadata": metadata,
"pages": text_content,
"full_text": "\n\n".join([p["content"] for p in text_content])
}
except Exception as e:
raise Exception(f"PDF ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {str(e)}")
def process_and_index_pdf(self, pdf_path: str, doc_id: str) -> Dict[str, Any]:
"""PDF ์ฒ˜๋ฆฌ ๋ฐ ๋ฒกํ„ฐ ์ธ๋ฑ์‹ฑ"""
try:
# PDF ํ…์ŠคํŠธ ์ถ”์ถœ
pdf_data = self.extract_text_from_pdf(pdf_path)
# ํ…์ŠคํŠธ๋ฅผ ์ฒญํฌ๋กœ ๋ถ„ํ• 
chunks = self.text_splitter.split_text(pdf_data["full_text"])
# ๊ฐ ์ฒญํฌ์— ๋Œ€ํ•œ ์ž„๋ฒ ๋”ฉ ์ƒ์„ฑ
embeddings = self.embedder.encode(chunks)
# ChromaDB์— ์ €์žฅ
ids = [f"{doc_id}_{i}" for i in range(len(chunks))]
metadatas = [
{
"doc_id": doc_id,
"chunk_index": i,
"source": pdf_data["metadata"]["file_name"],
"page_count": pdf_data["metadata"]["pages"]
}
for i in range(len(chunks))
]
self.collection.add(
ids=ids,
embeddings=embeddings.tolist(),
documents=chunks,
metadatas=metadatas
)
# ๋ฌธ์„œ ์ •๋ณด ์ €์žฅ
self.documents[doc_id] = {
"metadata": pdf_data["metadata"],
"chunk_count": len(chunks),
"upload_time": datetime.now().isoformat()
}
return {
"success": True,
"doc_id": doc_id,
"chunks": len(chunks),
"pages": pdf_data["metadata"]["pages"],
"title": pdf_data["metadata"]["title"]
}
except Exception as e:
return {
"success": False,
"error": str(e)
}
def search_relevant_chunks(self, query: str, top_k: int = 5) -> List[Dict]:
"""์ฟผ๋ฆฌ์™€ ๊ด€๋ จ๋œ ์ฒญํฌ ๊ฒ€์ƒ‰"""
try:
# ์ฟผ๋ฆฌ ์ž„๋ฒ ๋”ฉ ์ƒ์„ฑ
query_embedding = self.embedder.encode([query])
# ์œ ์‚ฌํ•œ ๋ฌธ์„œ ๊ฒ€์ƒ‰
results = self.collection.query(
query_embeddings=query_embedding.tolist(),
n_results=top_k
)
if results and results['documents']:
chunks = []
for i in range(len(results['documents'][0])):
chunks.append({
"content": results['documents'][0][i],
"metadata": results['metadatas'][0][i],
"distance": results['distances'][0][i] if 'distances' in results else None
})
return chunks
return []
except Exception as e:
print(f"Search error: {e}")
return []
def generate_rag_prompt(self, query: str, context_chunks: List[Dict]) -> str:
"""RAG ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ"""
context = "\n\n---\n\n".join([
f"[์ถœ์ฒ˜: {chunk['metadata']['source']}, ์ฒญํฌ {chunk['metadata']['chunk_index']+1}]\n{chunk['content']}"
for chunk in context_chunks
])
prompt = f"""๋‹ค์Œ ๋ฌธ์„œ ๋‚ด์šฉ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์งˆ๋ฌธ์— ๋‹ต๋ณ€ํ•ด์ฃผ์„ธ์š”.
๋‹ต๋ณ€์€ ์ œ๊ณต๋œ ๋ฌธ์„œ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ž‘์„ฑํ•˜๋˜, ํ•„์š”์‹œ ์ถ”๊ฐ€ ์„ค๋ช…์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋ฌธ์„œ์—์„œ ๊ด€๋ จ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ, ๊ทธ ์‚ฌ์‹ค์„ ๋ช…์‹œํ•ด์ฃผ์„ธ์š”.
๐Ÿ“š ์ฐธ๊ณ  ๋ฌธ์„œ:
{context}
โ“ ์งˆ๋ฌธ: {query}
๐Ÿ’ก ๋‹ต๋ณ€:"""
return prompt
# RAG ์‹œ์Šคํ…œ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
rag_system = PDFRAGSystem()
# State variables
current_model = gr.State("openai/gpt-oss-120b")
uploaded_documents = gr.State({})
rag_enabled = gr.State(False)
def upload_pdf(file):
"""PDF ํŒŒ์ผ ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ"""
if file is None:
return gr.update(value="ํŒŒ์ผ์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”"), gr.update(choices=[]), gr.update(value=False)
try:
# ํŒŒ์ผ ํ•ด์‹œ๋ฅผ ID๋กœ ์‚ฌ์šฉ
with open(file.name, 'rb') as f:
file_hash = hashlib.md5(f.read()).hexdigest()[:8]
doc_id = f"doc_{file_hash}"
# PDF ์ฒ˜๋ฆฌ ๋ฐ ์ธ๋ฑ์‹ฑ
result = rag_system.process_and_index_pdf(file.name, doc_id)
if result["success"]:
status_html = f"""
<div class="pdf-status pdf-success">
โœ… PDF ์—…๋กœ๋“œ ์„ฑ๊ณต!<br>
๐Ÿ“„ ์ œ๋ชฉ: {result.get('title', 'Unknown')}<br>
๐Ÿ“‘ ํŽ˜์ด์ง€: {result['pages']}ํŽ˜์ด์ง€<br>
๐Ÿ” ์ƒ์„ฑ๋œ ์ฒญํฌ: {result['chunks']}๊ฐœ<br>
๐Ÿ†” ๋ฌธ์„œ ID: {doc_id}
</div>
"""
# ๋ฌธ์„œ ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ
doc_list = list(rag_system.documents.keys())
doc_choices = [f"{doc_id}: {rag_system.documents[doc_id]['metadata']['file_name']}"
for doc_id in doc_list]
return status_html, gr.update(choices=doc_choices, value=doc_choices), gr.update(value=True)
else:
status_html = f"""
<div class="pdf-status pdf-error">
โŒ PDF ์—…๋กœ๋“œ ์‹คํŒจ<br>
์˜ค๋ฅ˜: {result['error']}
</div>
"""
return status_html, gr.update(choices=[]), gr.update(value=False)
except Exception as e:
status_html = f"""
<div class="pdf-status pdf-error">
โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}
</div>
"""
return status_html, gr.update(choices=[]), gr.update(value=False)
def clear_documents():
"""์—…๋กœ๋“œ๋œ ๋ฌธ์„œ ์ดˆ๊ธฐํ™”"""
try:
# ChromaDB ์ปฌ๋ ‰์…˜ ์žฌ์ƒ์„ฑ
rag_system.chroma_client.delete_collection("pdf_documents")
rag_system.collection = rag_system.chroma_client.create_collection(
name="pdf_documents",
metadata={"hnsw:space": "cosine"}
)
rag_system.documents = {}
return gr.update(value="<div class='pdf-status pdf-success'>โœ… ๋ชจ๋“  ๋ฌธ์„œ๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค</div>"), gr.update(choices=[], value=[]), gr.update(value=False)
except Exception as e:
return gr.update(value=f"<div class='pdf-status pdf-error'>โŒ ์‚ญ์ œ ์‹คํŒจ: {str(e)}</div>"), gr.update(), gr.update()
def process_with_rag(message: str, enable_rag: bool, selected_docs: List[str], top_k: int = 5):
"""RAG๋ฅผ ํ™œ์šฉํ•œ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ"""
if not enable_rag or not selected_docs:
return message # RAG ๋น„ํ™œ์„ฑํ™”์‹œ ์›๋ณธ ๋ฉ”์‹œ์ง€ ๋ฐ˜ํ™˜
try:
# ๊ด€๋ จ ์ฒญํฌ ๊ฒ€์ƒ‰
relevant_chunks = rag_system.search_relevant_chunks(message, top_k=top_k)
if relevant_chunks:
# ์„ ํƒ๋œ ๋ฌธ์„œ์˜ ์ฒญํฌ๋งŒ ํ•„ํ„ฐ๋ง
selected_doc_ids = [doc.split(":")[0] for doc in selected_docs]
filtered_chunks = [
chunk for chunk in relevant_chunks
if chunk['metadata']['doc_id'] in selected_doc_ids
]
if filtered_chunks:
# RAG ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ
rag_prompt = rag_system.generate_rag_prompt(message, filtered_chunks[:top_k])
return rag_prompt
return message
except Exception as e:
print(f"RAG processing error: {e}")
return message
def switch_model(model_choice):
"""๋ชจ๋ธ ์ „ํ™˜ ํ•จ์ˆ˜"""
return gr.update(visible=False), gr.update(visible=True), model_choice
# Gradio ์ธํ„ฐํŽ˜์ด์Šค
with gr.Blocks(fill_height=True, theme="Nymbo/Nymbo_Theme", css=custom_css) as demo:
with gr.Row():
# ์‚ฌ์ด๋“œ๋ฐ”
with gr.Column(scale=1):
with gr.Group(elem_classes="main-container"):
gr.Markdown("# ๐Ÿš€ AI Chat with RAG")
gr.Markdown(
"PDF ๋ฌธ์„œ๋ฅผ ์—…๋กœ๋“œํ•˜์—ฌ AI๊ฐ€ ๋ฌธ์„œ ๋‚ด์šฉ์„ ์ฐธ๊ณ ํ•ด ๋‹ต๋ณ€ํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
)
# ๋ชจ๋ธ ์„ ํƒ
model_dropdown = gr.Dropdown(
choices=["openai/gpt-oss-120b", "openai/gpt-oss-20b"],
value="openai/gpt-oss-120b",
label="๐Ÿ“Š ๋ชจ๋ธ ์„ ํƒ"
)
login_button = gr.LoginButton("Sign in with Hugging Face", size="lg")
reload_btn = gr.Button("๐Ÿ”„ ๋ชจ๋ธ ๋ณ€๊ฒฝ ์ ์šฉ", variant="primary", size="lg")
# RAG ์„ค์ •
with gr.Accordion("๐Ÿ“š PDF RAG ์„ค์ •", open=True):
pdf_upload = gr.File(
label="PDF ์—…๋กœ๋“œ",
file_types=[".pdf"],
type="filepath"
)
upload_status = gr.HTML(
value="<div class='pdf-status'>PDF๋ฅผ ์—…๋กœ๋“œํ•˜์—ฌ RAG๋ฅผ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”</div>"
)
document_list = gr.CheckboxGroup(
choices=[],
label="๐Ÿ“„ ์—…๋กœ๋“œ๋œ ๋ฌธ์„œ",
info="์งˆ๋ฌธ์— ์ฐธ๊ณ ํ•  ๋ฌธ์„œ๋ฅผ ์„ ํƒํ•˜์„ธ์š”"
)
with gr.Row():
clear_btn = gr.Button("๐Ÿ—‘๏ธ ๋ชจ๋“  ๋ฌธ์„œ ์‚ญ์ œ", size="sm")
refresh_btn = gr.Button("๐Ÿ”„ ๋ชฉ๋ก ์ƒˆ๋กœ๊ณ ์นจ", size="sm")
enable_rag = gr.Checkbox(
label="RAG ํ™œ์„ฑํ™”",
value=False,
info="๋ฌธ์„œ ๊ธฐ๋ฐ˜ ๋‹ต๋ณ€ ์ƒ์„ฑ ํ™œ์„ฑํ™”"
)
with gr.Accordion("โš™๏ธ RAG ๊ณ ๊ธ‰ ์„ค์ •", open=False):
top_k_chunks = gr.Slider(
minimum=1,
maximum=10,
value=5,
step=1,
label="์ฐธ์กฐํ•  ์ฒญํฌ ์ˆ˜",
info="๋‹ต๋ณ€ ์ƒ์„ฑ์‹œ ์ฐธ๊ณ ํ•  ๋ฌธ์„œ ์ฒญํฌ์˜ ๊ฐœ์ˆ˜"
)
chunk_size = gr.Slider(
minimum=500,
maximum=2000,
value=1000,
step=100,
label="์ฒญํฌ ํฌ๊ธฐ",
info="๋ฌธ์„œ๋ฅผ ๋ถ„ํ• ํ•˜๋Š” ์ฒญํฌ์˜ ํฌ๊ธฐ (๋ฌธ์ž ์ˆ˜)"
)
# ๊ณ ๊ธ‰ ์˜ต์…˜
with gr.Accordion("โš™๏ธ ๋ชจ๋ธ ์„ค์ •", open=False):
temperature = gr.Slider(
minimum=0,
maximum=2,
value=0.7,
step=0.1,
label="Temperature"
)
max_tokens = gr.Slider(
minimum=1,
maximum=4096,
value=512,
step=1,
label="Max Tokens"
)
# ๋ฉ”์ธ ์ฑ„ํŒ… ์˜์—ญ
with gr.Column(scale=3):
with gr.Group(elem_classes="main-container"):
gr.Markdown("## ๐Ÿ’ฌ Chat Interface")
# RAG ์ƒํƒœ ํ‘œ์‹œ
with gr.Row():
rag_status = gr.HTML(
value="<div style='padding: 10px; background: rgba(59, 130, 246, 0.1); border-radius: 8px; margin-bottom: 10px;'>๐Ÿ” RAG: <strong>๋น„ํ™œ์„ฑํ™”</strong></div>"
)
# ๋ชจ๋ธ ์ธํ„ฐํŽ˜์ด์Šค ์ปจํ…Œ์ด๋„ˆ
with gr.Column(visible=True) as model_120b_container:
gr.Markdown("### Model: openai/gpt-oss-120b")
# ์‹ค์ œ ๋ชจ๋ธ ๋กœ๋“œ๋Š” gr.load()๋กœ ์ฒ˜๋ฆฌ
chatbot_120b = gr.Chatbot(height=400)
msg_box_120b = gr.Textbox(
label="๋ฉ”์‹œ์ง€ ์ž…๋ ฅ",
placeholder="PDF ๋‚ด์šฉ์— ๋Œ€ํ•ด ์งˆ๋ฌธํ•ด๋ณด์„ธ์š”...",
lines=2
)
with gr.Row():
send_btn_120b = gr.Button("๐Ÿ“ค ์ „์†ก", variant="primary")
clear_btn_120b = gr.Button("๐Ÿ—‘๏ธ ๋Œ€ํ™” ์ดˆ๊ธฐํ™”")
with gr.Column(visible=False) as model_20b_container:
gr.Markdown("### Model: openai/gpt-oss-20b")
chatbot_20b = gr.Chatbot(height=400)
msg_box_20b = gr.Textbox(
label="๋ฉ”์‹œ์ง€ ์ž…๋ ฅ",
placeholder="PDF ๋‚ด์šฉ์— ๋Œ€ํ•ด ์งˆ๋ฌธํ•ด๋ณด์„ธ์š”...",
lines=2
)
with gr.Row():
send_btn_20b = gr.Button("๐Ÿ“ค ์ „์†ก", variant="primary")
clear_btn_20b = gr.Button("๐Ÿ—‘๏ธ ๋Œ€ํ™” ์ดˆ๊ธฐํ™”")
# ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
# PDF ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ
pdf_upload.upload(
fn=upload_pdf,
inputs=[pdf_upload],
outputs=[upload_status, document_list, enable_rag]
)
# ๋ฌธ์„œ ์ดˆ๊ธฐํ™”
clear_btn.click(
fn=clear_documents,
outputs=[upload_status, document_list, enable_rag]
)
# RAG ์ƒํƒœ ์—…๋ฐ์ดํŠธ
enable_rag.change(
fn=lambda x: gr.update(
value=f"<div style='padding: 10px; background: rgba(59, 130, 246, 0.1); border-radius: 8px; margin-bottom: 10px;'>๐Ÿ” RAG: <strong>{'ํ™œ์„ฑํ™”' if x else '๋น„ํ™œ์„ฑํ™”'}</strong></div>"
),
inputs=[enable_rag],
outputs=[rag_status]
)
# ๋ชจ๋ธ ์ „ํ™˜
reload_btn.click(
fn=switch_model,
inputs=[model_dropdown],
outputs=[model_120b_container, model_20b_container, current_model]
).then(
fn=lambda: gr.Info("๋ชจ๋ธ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ „ํ™˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค!"),
inputs=[],
outputs=[]
)
# ์ฑ„ํŒ… ๊ธฐ๋Šฅ (RAG ํ†ตํ•ฉ)
def chat_with_rag(message, history, enable_rag, selected_docs, top_k):
"""RAG๋ฅผ ํ™œ์šฉํ•œ ์ฑ„ํŒ…"""
# RAG ์ฒ˜๋ฆฌ
processed_message = process_with_rag(message, enable_rag, selected_docs, top_k)
# ์—ฌ๊ธฐ์— ์‹ค์ œ ๋ชจ๋ธ API ํ˜ธ์ถœ ์ฝ”๋“œ๊ฐ€ ๋“ค์–ด๊ฐ€์•ผ ํ•จ
# ํ˜„์žฌ๋Š” ์˜ˆ์‹œ ์‘๋‹ต
if enable_rag and selected_docs:
response = f"[RAG ํ™œ์„ฑํ™”] ์„ ํƒ๋œ {len(selected_docs)}๊ฐœ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ๋‹ต๋ณ€ํ•ฉ๋‹ˆ๋‹ค:\n\n{processed_message[:200]}..."
else:
response = f"[์ผ๋ฐ˜ ๋ชจ๋“œ] {message}์— ๋Œ€ํ•œ ๋‹ต๋ณ€์ž…๋‹ˆ๋‹ค."
history.append((message, response))
return "", history
# 120b ๋ชจ๋ธ ์ฑ„ํŒ…
msg_box_120b.submit(
fn=chat_with_rag,
inputs=[msg_box_120b, chatbot_120b, enable_rag, document_list, top_k_chunks],
outputs=[msg_box_120b, chatbot_120b]
)
send_btn_120b.click(
fn=chat_with_rag,
inputs=[msg_box_120b, chatbot_120b, enable_rag, document_list, top_k_chunks],
outputs=[msg_box_120b, chatbot_120b]
)
clear_btn_120b.click(
lambda: ([], ""),
outputs=[chatbot_120b, msg_box_120b]
)
# 20b ๋ชจ๋ธ ์ฑ„ํŒ…
msg_box_20b.submit(
fn=chat_with_rag,
inputs=[msg_box_20b, chatbot_20b, enable_rag, document_list, top_k_chunks],
outputs=[msg_box_20b, chatbot_20b]
)
send_btn_20b.click(
fn=chat_with_rag,
inputs=[msg_box_20b, chatbot_20b, enable_rag, document_list, top_k_chunks],
outputs=[msg_box_20b, chatbot_20b]
)
clear_btn_20b.click(
lambda: ([], ""),
outputs=[chatbot_20b, msg_box_20b]
)
if __name__ == "__main__":
demo.launch()