imkrish commited on
Commit
f7aa6a6
·
1 Parent(s): 7798863

Switch to Gradio SDK for ZeroGPU support

Browse files
Files changed (4) hide show
  1. Dockerfile +0 -22
  2. README.md +15 -11
  3. app.py +94 -0
  4. app/main.py +0 -97
Dockerfile DELETED
@@ -1,22 +0,0 @@
1
- FROM python:3.9-slim
2
-
3
- WORKDIR /app
4
-
5
- # Install system dependencies for FAISS and PyMuPDF
6
- RUN apt-get update && apt-get install -y \
7
- build-essential \
8
- libgl1 \
9
- && rm -rf /var/lib/apt/lists/*
10
-
11
- COPY requirements.txt .
12
- RUN pip install --no-cache-dir -r requirements.txt
13
-
14
- COPY . .
15
-
16
- # Create necessary directories and set permissions
17
- RUN mkdir -p data/uploads data/vector_store && chmod -R 777 data
18
-
19
- # Set PYTHONPATH to ensure 'app' module is found
20
- ENV PYTHONPATH=/app
21
-
22
- CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -3,23 +3,27 @@ title: RAG QA System
3
  emoji: 📚
4
  colorFrom: blue
5
  colorTo: indigo
6
- sdk: docker
7
- app_port: 7860
 
8
  pinned: false
9
  ---
10
 
11
  # RAG-Based Question Answering System
12
 
13
- This is a Retrieval-Augmented Generation (RAG) system built with FastAPI, FAISS, and Google's Gemini LLM. It allows uploading PDF/TXT documents and asking questions based on their content.
14
 
15
- ## Features
16
 
17
- - **FastAPI**: Modern, fast web framework for APIs.
18
- - **FAISS**: Efficient similarity search for document chunks.
19
- - **Gemini 1.5 Flash**: High-performance LLM for generating accurate answers.
20
- - **Background Jobs**: Asynchronous document processing to prevent API blocking.
21
- - **ZeroGPU Support**: Configured for Hugging Face Spaces.
22
 
23
- ## Setup & Configuration
24
 
25
- Add `GOOGLE_API_KEY` to your Hugging Face Space Secrets.
 
 
 
 
 
 
3
  emoji: 📚
4
  colorFrom: blue
5
  colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: 4.12.0
8
+ app_file: app.py
9
  pinned: false
10
  ---
11
 
12
  # RAG-Based Question Answering System
13
 
14
+ This system uses **FastAPI** for its API and **Gradio** for its user interface, running on **ZeroGPU**.
15
 
16
+ ## 🚀 How it works
17
 
18
+ 1. **Upload**: Send PDF or TXT files.
19
+ 2. **Retrieve**: FAISS finds the most relevant chunks.
20
+ 3. **Generate**: Gemini 1.5 Flash synthesizes an answer using **ZeroGPU**.
 
 
21
 
22
+ ## 🛠️ API Access
23
 
24
+ The FastAPI endpoints are available at:
25
+
26
+ - `POST /upload`: Upload documents.
27
+ - `POST /ask`: Query the documents.
28
+
29
+ Make sure to set `GOOGLE_API_KEY` in your Space Secrets.
app.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from fastapi import FastAPI, UploadFile, File, BackgroundTasks, HTTPException, Request
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ import os
5
+ import shutil
6
+ import uuid
7
+ import spaces
8
+
9
+ # Import our existing services
10
+ from app.core.config import settings
11
+ from app.services.document_processor import DocumentProcessor
12
+ from app.services.vector_store import vector_store
13
+ from app.services.llm_service import llm_service
14
+
15
+ # --- FASTAPI SETUP ---
16
+ fastapi_app = FastAPI(title="RAG API")
17
+ fastapi_app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
18
+
19
+ @fastapi_app.post("/upload")
20
+ async def api_upload(background_tasks: BackgroundTasks, file: UploadFile = File(...)):
21
+ if not file.filename.endswith(('.pdf', '.txt')):
22
+ raise HTTPException(status_code=400, detail="Only PDF and TXT supported.")
23
+ file_id = str(uuid.uuid4())
24
+ temp_path = os.path.join(settings.UPLOADS_DIR, f"{file_id}_{file.filename}")
25
+ os.makedirs(settings.UPLOADS_DIR, exist_ok=True)
26
+ with open(temp_path, "wb") as buffer:
27
+ shutil.copyfileobj(file.file, buffer)
28
+
29
+ # Internal function for background processing
30
+ def process_task(path, name):
31
+ text = DocumentProcessor.extract_text(path)
32
+ chunks = DocumentProcessor.chunk_text(text)
33
+ vector_store.add_documents(chunks, name)
34
+ if os.path.exists(path): os.remove(path)
35
+
36
+ background_tasks.add_task(process_task, temp_path, file.filename)
37
+ return {"message": "Processing in background", "filename": file.filename}
38
+
39
+ @fastapi_app.post("/ask")
40
+ async def api_ask(request: Request):
41
+ data = await request.json()
42
+ question = data.get("question")
43
+ relevant_chunks = vector_store.search(question)
44
+ if not relevant_chunks:
45
+ return {"answer": "No relevant info found.", "sources": []}
46
+
47
+ answer = generate_answer_with_gpu(question, relevant_chunks)
48
+ sources = list(set([c['source'] for c in relevant_chunks]))
49
+ return {"answer": answer, "sources": sources}
50
+
51
+ # --- ZERO-GPU DECORATED FUNCTION ---
52
+ @spaces.GPU
53
+ def generate_answer_with_gpu(question, chunks):
54
+ return llm_service.generate_answer(question, chunks)
55
+
56
+ # --- GRADIO UI SETUP ---
57
+ def chatbot_response(message, history):
58
+ relevant_chunks = vector_store.search(message)
59
+ if not relevant_chunks:
60
+ return "I couldn't find any relevant information in the uploaded documents."
61
+
62
+ answer = generate_answer_with_gpu(message, relevant_chunks)
63
+ sources = list(set([c['source'] for c in relevant_chunks]))
64
+ return f"{answer}\n\n**Sources:** {', '.join(sources)}"
65
+
66
+ def upload_file(file):
67
+ if file is None:
68
+ return "No file selected."
69
+
70
+ # Process immediately for the UI
71
+ text = DocumentProcessor.extract_text(file.name)
72
+ chunks = DocumentProcessor.chunk_text(text)
73
+ vector_store.add_documents(chunks, os.path.basename(file.name))
74
+ return f"Successfully processed: {os.path.basename(file.name)}"
75
+
76
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as demo:
77
+ gr.Markdown("# 📚 RAG-Based Question Answering System")
78
+ gr.Markdown("Upload documents (PDF/TXT) and ask questions. Powered by **Gemini 1.5 Flash** and **ZeroGPU**.")
79
+
80
+ with gr.Row():
81
+ with gr.Column(scale=1):
82
+ file_output = gr.Textbox(label="Upload Status")
83
+ upload_button = gr.UploadButton("Click to Upload PDF/TXT", file_types=[".pdf", ".txt"])
84
+ upload_button.upload(upload_file, upload_button, file_output)
85
+
86
+ with gr.Column(scale=2):
87
+ chat = gr.ChatInterface(chatbot_response)
88
+
89
+ # --- MOUNTING AND LAUNCHING ---
90
+ app = gr.mount_gradio_app(fastapi_app, demo, path="/")
91
+
92
+ if __name__ == "__main__":
93
+ import uvicorn
94
+ uvicorn.run(app, host="0.0.0.0", port=7860)
app/main.py DELETED
@@ -1,97 +0,0 @@
1
- from fastapi import FastAPI, UploadFile, File, BackgroundTasks, HTTPException, Request
2
- from fastapi.middleware.cors import CORSMiddleware
3
- from slowapi import Limiter, _rate_limit_exceeded_handler
4
- from slowapi.util import get_remote_address
5
- from slowapi.errors import RateLimitExceeded
6
- from pydantic import BaseModel
7
- import shutil
8
- import os
9
- import uuid
10
-
11
- from app.core.config import settings
12
- from app.services.document_processor import DocumentProcessor
13
- from app.services.vector_store import vector_store
14
- from app.services.llm_service import llm_service
15
-
16
- # Initialize Limiter
17
- limiter = Limiter(key_func=get_remote_address)
18
- app = FastAPI(title="RAG-Based QA System")
19
- app.state.limiter = limiter
20
- app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
21
-
22
- app.add_middleware(
23
- CORSMiddleware,
24
- allow_origins=["*"],
25
- allow_methods=["*"],
26
- allow_headers=["*"],
27
- )
28
-
29
- class QueryRequest(BaseModel):
30
- question: str
31
-
32
- class QueryResponse(BaseModel):
33
- answer: str
34
- sources: list[str]
35
-
36
- def process_document_task(file_path: str, original_filename: str):
37
- try:
38
- text = DocumentProcessor.extract_text(file_path)
39
- chunks = DocumentProcessor.chunk_text(text)
40
- vector_store.add_documents(chunks, original_filename)
41
- except Exception as e:
42
- print(f"Error processing document: {e}")
43
- finally:
44
- if os.path.exists(file_path):
45
- os.remove(file_path)
46
-
47
- @app.post("/upload")
48
- @limiter.limit(settings.RATE_LIMIT)
49
- async def upload_document(request: Request, background_tasks: BackgroundTasks, file: UploadFile = File(...)):
50
- if not file.filename.endswith(('.pdf', '.txt')):
51
- raise HTTPException(status_code=400, detail="Only PDF and TXT files are supported.")
52
-
53
- file_id = str(uuid.uuid4())
54
- temp_path = os.path.join(settings.UPLOADS_DIR, f"{file_id}_{file.filename}")
55
-
56
- # Ensure directory exists
57
- os.makedirs(settings.UPLOADS_DIR, exist_ok=True)
58
-
59
- with open(temp_path, "wb") as buffer:
60
- shutil.copyfileobj(file.file, buffer)
61
-
62
- background_tasks.add_task(process_document_task, temp_path, file.filename)
63
-
64
- return {"message": "File uploaded successfully. Processing in background.", "filename": file.filename}
65
-
66
- try:
67
- import spaces
68
- @spaces.GPU
69
- def generate_with_gpu(query, context_chunks):
70
- return llm_service.generate_answer(query, context_chunks)
71
- except ImportError:
72
- def generate_with_gpu(query, context_chunks):
73
- return llm_service.generate_answer(query, context_chunks)
74
-
75
- @app.post("/ask", response_model=QueryResponse)
76
- @limiter.limit(settings.RATE_LIMIT)
77
- async def ask_question(request: Request, query_req: QueryRequest):
78
- relevant_chunks = vector_store.search(query_req.question)
79
-
80
- if not relevant_chunks:
81
- raise HTTPException(status_code=404, detail="No relevant context found in documents.")
82
-
83
- # Use the GPU decorated function
84
- answer = generate_with_gpu(query_req.question, relevant_chunks)
85
- sources = list(set([chunk['source'] for chunk in relevant_chunks]))
86
-
87
- return QueryResponse(answer=answer, sources=sources)
88
-
89
- @app.get("/")
90
- async def root():
91
- return {"message": "Welcome to the RAG-Based QA System API"}
92
-
93
- if __name__ == "__main__":
94
- import uvicorn
95
- # Use port assigned by HF or default to 7860
96
- port = int(os.environ.get("PORT", 7860))
97
- uvicorn.run(app, host="0.0.0.0", port=port)