Spaces:
Paused
Paused
Switch to Gradio SDK for ZeroGPU support
Browse files- Dockerfile +0 -22
- README.md +15 -11
- app.py +94 -0
- 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:
|
| 7 |
-
|
|
|
|
| 8 |
pinned: false
|
| 9 |
---
|
| 10 |
|
| 11 |
# RAG-Based Question Answering System
|
| 12 |
|
| 13 |
-
This
|
| 14 |
|
| 15 |
-
##
|
| 16 |
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
- **Background Jobs**: Asynchronous document processing to prevent API blocking.
|
| 21 |
-
- **ZeroGPU Support**: Configured for Hugging Face Spaces.
|
| 22 |
|
| 23 |
-
##
|
| 24 |
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|