Spaces:
Sleeping
Sleeping
| """ | |
| Gradio UI — RAG Document Q&A | |
| Deployed on HuggingFace Spaces. | |
| Supports: | |
| - Pre-loaded demo documents (Attention Is All You Need, RAG paper) | |
| - User PDF uploads — upload any PDF and query it instantly | |
| """ | |
| import os | |
| import tempfile | |
| from pathlib import Path | |
| import gradio as gr | |
| from src.generation.rag_chain import RAGChain | |
| from src.retrieval.vector_store import VectorStore | |
| from src.ingestion.pdf_loader import load_pdf | |
| from src.ingestion.chunker import chunk_pages | |
| from src.retrieval.embedder import Embedder | |
| from src.utils.logger import logger | |
| # ------------------------------------------------------------ | |
| # Startup — ingest demo documents if store is empty | |
| # ------------------------------------------------------------ | |
| def ensure_demo_ingested(): | |
| store = VectorStore() | |
| if store.collection.count() > 0: | |
| logger.info(f"Vector store has {store.collection.count()} chunks — skipping demo ingestion") | |
| return store.collection.count() | |
| logger.info("Ingesting demo documents...") | |
| from src.ingestion.pdf_loader import load_pdfs_from_dir | |
| from src.ingestion.chunker import chunk_pages | |
| pages = load_pdfs_from_dir("data/raw") | |
| if not pages: | |
| logger.warning("No demo PDFs found in data/raw/") | |
| return 0 | |
| chunks = chunk_pages(pages) | |
| embedder = Embedder() | |
| embeddings = embedder.embed_texts([c["text"] for c in chunks]) | |
| store.add_chunks(chunks, embeddings) | |
| logger.info(f"Demo ingestion complete — {len(chunks)} chunks") | |
| return len(chunks) | |
| demo_chunks = ensure_demo_ingested() | |
| chain = RAGChain() | |
| embedder = Embedder() | |
| # ------------------------------------------------------------ | |
| # PDF upload handler | |
| # ------------------------------------------------------------ | |
| def ingest_pdf(file) -> str: | |
| """ | |
| Ingest a user-uploaded PDF into the vector store. | |
| Adds to existing chunks — doesn't reset the store. | |
| """ | |
| if file is None: | |
| return "No file uploaded." | |
| try: | |
| path = Path(file.name) | |
| logger.info(f"User uploaded: {path.name}") | |
| pages = load_pdf(path) | |
| if not pages: | |
| return f"Could not extract text from {path.name}. Is it a scanned PDF?" | |
| chunks = chunk_pages(pages) | |
| embeddings = embedder.embed_texts([c["text"] for c in chunks]) | |
| store = VectorStore() | |
| store.add_chunks(chunks, embeddings) | |
| total = store.collection.count() | |
| return ( | |
| f"✅ **{path.name}** ingested successfully!\n\n" | |
| f"- Pages extracted: {len(pages)}\n" | |
| f"- Chunks created: {len(chunks)}\n" | |
| f"- Total chunks in store: {total}\n\n" | |
| f"You can now ask questions about this document." | |
| ) | |
| except Exception as e: | |
| logger.error(f"Ingestion error: {e}") | |
| return f"❌ Error ingesting file: {str(e)}" | |
| # ------------------------------------------------------------ | |
| # Query handler | |
| # ------------------------------------------------------------ | |
| def answer_question(question: str) -> tuple[str, str]: | |
| if not question.strip(): | |
| return "Please enter a question.", "" | |
| result = chain.query(question) | |
| answer = result["answer"] | |
| sources_lines = ["**Sources retrieved:**\n"] | |
| for i, s in enumerate(result["sources"], start=1): | |
| sources_lines.append( | |
| f"{i}. `{s['source']}` — Page {s['page']} (similarity: {s['score']})" | |
| ) | |
| sources_md = "\n".join(sources_lines) | |
| return answer, sources_md | |
| # ------------------------------------------------------------ | |
| # Gradio UI | |
| # ------------------------------------------------------------ | |
| with gr.Blocks(title="RAG Document Q&A", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown(""" | |
| # RAG Document Q&A | |
| Ask questions about documents using Retrieval-Augmented Generation. | |
| **Pre-loaded:** Attention Is All You Need + RAG paper (Lewis et al., 2020) | |
| **Or upload your own PDF** and query it instantly. | |
| """) | |
| with gr.Tab("Ask a question"): | |
| with gr.Row(): | |
| question_box = gr.Textbox( | |
| label="Your question", | |
| placeholder="e.g. What is the attention mechanism? How does RAG work?", | |
| lines=2, | |
| ) | |
| submit_btn = gr.Button("Ask", variant="primary", size="lg") | |
| with gr.Row(): | |
| answer_box = gr.Textbox( | |
| label="Answer", | |
| lines=8, | |
| interactive=False, | |
| ) | |
| sources_box = gr.Markdown(label="Sources") | |
| gr.Examples( | |
| examples=[ | |
| "What is the attention mechanism in transformers?", | |
| "What is multi-head attention?", | |
| "How does RAG combine retrieval and generation?", | |
| "What datasets were used to evaluate RAG?", | |
| "What is the encoder-decoder architecture?", | |
| ], | |
| inputs=question_box, | |
| ) | |
| submit_btn.click( | |
| fn=answer_question, | |
| inputs=[question_box], | |
| outputs=[answer_box, sources_box], | |
| ) | |
| with gr.Tab("Upload your PDF"): | |
| gr.Markdown(""" | |
| ### Upload a PDF to query | |
| Upload any PDF document and it will be ingested into the vector store. | |
| You can then ask questions about it in the **Ask a question** tab. | |
| **Note:** Uploaded documents are added to the existing store alongside the demo papers. | |
| Scanned PDFs (image-only) are not supported — the PDF must have extractable text. | |
| """) | |
| file_upload = gr.File( | |
| label="Upload PDF", | |
| file_types=[".pdf"], | |
| ) | |
| upload_btn = gr.Button("Ingest PDF", variant="primary") | |
| upload_status = gr.Markdown(label="Status") | |
| upload_btn.click( | |
| fn=ingest_pdf, | |
| inputs=[file_upload], | |
| outputs=[upload_status], | |
| ) | |
| gr.Markdown( | |
| "_Built with sentence-transformers, ChromaDB, and Groq (Llama 3.1 8B). " | |
| "[View source on GitHub](https://github.com/OmUniyal/rag-document-qa)_" | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |