DocuMind-AI / app.py
fahadqazi's picture
Update app.py
b945bd1 verified
Raw History Blame Contribute Delete
5.75 kB
import os
import time
from huggingface_hub import login
import gradio as gr
import traceback
from pypdf import PdfReader
import uuid
# --- 1. Authentication ---
# This uses the secret named 'token' you created in Space Settings
# HF_TOKEN = os.environ.get("token")
# if HF_TOKEN:
# login(token=HF_TOKEN)
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_groq import ChatGroq
from langchain_classic.chains import ConversationalRetrievalChain
# --- Configuration & Constraints ---
MAX_PDFS = 2
MAX_PAGES = 3
EMBEDDING_MODEL = "BAAI/bge-small-en-v1.5"
GROQ_MODEL = "llama-3.3-70b-versatile"
# Initialize embeddings locally using the token for access
embeddings = HuggingFaceEmbeddings(
model_name=EMBEDDING_MODEL,
model_kwargs={'device': 'cpu'}
)
def is_valid_pdf(file):
try:
# We only read the header/metadata, not the whole file
reader = PdfReader(file.name)
# Accessing the length of pages triggers a basic structure check
if len(reader.pages) > 0:
return True
return False
except Exception:
return False
def process_pdfs(files):
if not files:
return None, "❌ No files provided."
if len(files) > MAX_PDFS:
return None, f"❌ Error: Max {MAX_PDFS} PDFs allowed."
for file in files:
if not is_valid_pdf(file):
return None, "❌ PDF not valid."
all_docs = []
for file in files:
loader = PyPDFLoader(file.name)
pages = loader.load()
if len(pages) > MAX_PAGES:
return None, f"❌ Error: '{os.path.basename(file.name)}' exceeds {MAX_PAGES} pages."
all_docs.extend(pages)
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
splits = text_splitter.split_documents(all_docs)
vectorstore = FAISS.from_documents(documents=splits, embedding=embeddings)
return vectorstore, "✅ PDFs processed successfully!"
def chat_with_pdf(message, history, vectorstore):
# 1. Ensure history is a list (Gradio 6 initialization)
if history is None:
history = []
# 2. Handle the 'None' case for vectorstore (before processing PDFs)
if vectorstore is None:
# Standard dictionary format for Gradio 6
history.append({"role": "user", "content": str(message)})
history.append({"role": "assistant", "content": "Please upload and process PDFs first."})
return history, ""
llm = ChatGroq(
temperature=0,
model_name=GROQ_MODEL,
groq_api_key=os.environ.get("GROQ_API_KEY")
)
# 3. Correctly format history for LangChain (List of Tuples)
formatted_history = []
user_msg = None
for msg in history:
role = msg.get("role")
content = msg.get("content", "")
if role == "user":
user_msg = content
elif role == "assistant" and user_msg is not None:
formatted_history.append((user_msg, content))
user_msg = None
qa_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
)
try:
# 4. Invoke the chain
# result = qa_chain.invoke({"question": str(message), "chat_history": formatted_history})
result = qa_chain.invoke({
"question": str(message),
"chat_history": [
(str(u), str(a)) for u, a in formatted_history
]
})
answer = result['answer']
except Exception as e:
print("❌ FULL EXCEPTION TRACEBACK:")
print(traceback.format_exc())
error_str = str(e).lower()
if "429" in error_str:
answer = "⚠️ [Rate Limit] System is busy. Please wait a moment."
else:
answer = f"⚠️ System Error: {str(e)}"
# 5. Append the new interaction to history
history.append({"role": "user", "content": str(message)})
history.append({"role": "assistant", "content": str(answer)})
return history, ""
# --- UI Setup ---
# Note: theme removed from Blocks() per Gradio 6.0 warning
with gr.Blocks() as demo:
vector_db = gr.State(None)
gr.Markdown(f"# 🚀 PDF RAG Demo\nMax {MAX_PDFS} PDFs | Max {MAX_PAGES} pages each")
with gr.Row():
with gr.Column(scale=1):
file_uploader = gr.File(label="Upload PDFs", file_count="multiple")
process_btn = gr.Button("Build Knowledge Base", variant="primary")
status_msg = gr.Textbox(label="Status", interactive=False)
with gr.Column(scale=2):
# 'type' argument removed as it is now default/standard
chatbot = gr.Chatbot(label="Chat History", height=450)
with gr.Row():
question_input = gr.Textbox(label="Your Question", placeholder="Ask away...", scale=4)
submit_btn = gr.Button("Send", scale=1)
clear_btn = gr.ClearButton([question_input, chatbot])
process_btn.click(
process_pdfs,
inputs=[file_uploader],
outputs=[vector_db, status_msg],
api_name="upload_and_process"
)
submit_btn.click(
chat_with_pdf,
inputs=[question_input, chatbot, vector_db],
outputs=[chatbot, question_input],
api_name="ask_pdf"
)
question_input.submit(
chat_with_pdf,
inputs=[question_input, chatbot, vector_db],
outputs=[chatbot, question_input]
)
if __name__ == "__main__":
demo.launch()