imkrish commited on
Commit
f0b3792
·
1 Parent(s): 73b2faf

feat: Update .gitignore and README for improved documentation and clarity; enhance error handling in answer generation with retry logic

Browse files
Files changed (4) hide show
  1. .gitignore +2 -1
  2. README.md +70 -34
  3. app/rag.py +19 -12
  4. streamlit_app.py +1 -1
.gitignore CHANGED
@@ -1,4 +1,5 @@
1
  venv
2
 
3
  .env
4
- __pycache__/
 
 
1
  venv
2
 
3
  .env
4
+ __pycache__/
5
+ data/
README.md CHANGED
@@ -1,53 +1,89 @@
1
- ---
2
- title: RAG QA System
3
- emoji: "🧠"
4
- colorFrom: indigo
5
- colorTo: blue
6
- sdk: streamlit
7
- sdk_version: "1.37.1"
8
- python_version: "3.10"
9
- app_file: streamlit_app.py
10
- pinned: false
11
- ---
12
-
13
  # RAG-Based Question Answering System
14
 
15
  ## Overview
16
- This project provides a minimal RAG pipeline using FastAPI + FAISS for retrieval and a Streamlit UI for interaction. Documents are ingested in the background, chunked, embedded, and stored locally. Queries retrieve relevant chunks and optionally call the Hugging Face Inference API to generate answers.
17
 
18
  ## Features
19
- - PDF + TXT ingestion
20
- - Chunking + embeddings (SentenceTransformers)
21
- - FAISS vector store
22
- - Background ingestion job
23
- - FastAPI endpoints with Pydantic validation
24
- - Basic rate limiting
25
- - Streamlit UI
26
- - Metrics logging (latency)
27
 
28
  ## Architecture Diagram
29
- See [docs/architecture.drawio](docs/architecture.drawio).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  ## Setup
32
- 1. Create a virtual environment and install deps:
 
 
 
 
 
33
  - `pip install -r requirements.txt`
34
- 2. Configure environment variables (copy `.env.example` to `.env`):
35
- - `HUGGINGFACEHUB_API_TOKEN` (optional)
36
- - `HF_MODEL` (optional)
37
- 3. Run the API:
38
  - `uvicorn app.main:app --reload`
39
- 4. Run Streamlit:
40
  - `streamlit run streamlit_app.py`
41
 
42
  ## API Endpoints
43
- - `POST /documents/upload` (multipart file) → returns `job_id`
44
- - `GET /documents/status/{job_id}` → job status
45
- - `POST /qa` → answer + contexts
46
- - `GET /health`
47
 
48
  ## Usage
49
- 1. Upload a document using the Streamlit UI or `POST /documents/upload`.
50
- 2. Wait for job completion using `/documents/status/{job_id}`.
 
 
 
 
 
 
 
 
 
 
 
 
51
  3. Ask questions via the UI or `POST /qa`.
52
 
53
  ## Mandatory Explanations
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # RAG-Based Question Answering System
2
 
3
  ## Overview
4
+ This project implements a Retrieval-Augmented Generation (RAG) system using FastAPI for the backend API, FAISS for vector storage, Sentence Transformers for embeddings, and Google Gemini for answer generation. It supports PDF and TXT document ingestion with OCR fallback for scanned PDFs, background job processing, and a Streamlit UI for user interaction.
5
 
6
  ## Features
7
+ - Document ingestion (PDF, TXT) with OCR support for scanned PDFs
8
+ - Text chunking and embedding using Sentence Transformers
9
+ - FAISS vector store for similarity search
10
+ - Background job processing for ingestion
11
+ - FastAPI with Pydantic validation and basic rate limiting
12
+ - Streamlit UI for document upload and question answering
13
+ - Metrics logging (latency, chunks, etc.)
 
14
 
15
  ## Architecture Diagram
16
+ ```
17
+ User (Browser) <-> Streamlit UI <-> FastAPI Backend
18
+ |
19
+ v
20
+ Document Ingestion (Background)
21
+ |
22
+ v
23
+ Text Extraction (PyPDF2 + OCR)
24
+ |
25
+ v
26
+ Chunking (size: 800, overlap: 120)
27
+ |
28
+ v
29
+ Embedding (SentenceTransformers)
30
+ |
31
+ v
32
+ FAISS Index + Metadata Storage
33
+ |
34
+ v
35
+ Query Processing -> Retrieval -> Generation (Gemini)
36
+ ```
37
+
38
+ ## Mandatory Explanations
39
+
40
+ ### Why Chunk Size of 800 with 120 Overlap?
41
+ - **Chunk Size (800)**: Balances providing sufficient context for coherent answers while keeping chunks small enough for precise retrieval. Larger chunks (e.g., 2000) might include irrelevant information, reducing retrieval accuracy; smaller chunks (e.g., 500) may lose context across sentences.
42
+ - **Overlap (120)**: Ensures continuity between chunks, preventing loss of information at boundaries. This helps maintain semantic flow, especially for questions spanning multiple sentences.
43
+
44
+ ### One Retrieval Failure Case Observed
45
+ - **Case**: When querying "who is krish kumar" on a resume PDF, the top retrieved chunks had low similarity scores (e.g., 0.087), leading to potential irrelevant or incomplete answers. This occurred because the query embedding didn't closely match the document chunks due to variations in phrasing or focus on specific sections.
46
+ - **Mitigation**: Increasing top_k from 1 to 4 improved coverage, and the system falls back to showing excerpts if LLM fails.
47
+
48
+ ### One Metric Tracked
49
+ - **Latency**: Tracked for both ingestion (time to process and embed document) and QA (time for retrieval + generation). Logged in `metrics.jsonl` for monitoring performance. For example, ingestion latency averaged 5-10 seconds for PDFs, QA around 2-10 seconds depending on API response.
50
 
51
  ## Setup
52
+ 1. Clone the repository:
53
+ - `git clone <repo-url>`
54
+ 2. Create a virtual environment:
55
+ - `python -m venv venv`
56
+ - `source venv/bin/activate` (Linux/Mac) or `venv\Scripts\activate` (Windows)
57
+ 3. Install dependencies:
58
  - `pip install -r requirements.txt`
59
+ 4. Set up environment variables (create `.env` file):
60
+ - `GOOGLE_API_KEY=<your-gemini-api-key>`
61
+ 5. Run the FastAPI backend:
 
62
  - `uvicorn app.main:app --reload`
63
+ 6. Run the Streamlit UI:
64
  - `streamlit run streamlit_app.py`
65
 
66
  ## API Endpoints
67
+ - `POST /documents/upload` (multipart file upload) → Returns `job_id` for background ingestion
68
+ - `GET /documents/status/{job_id}` → Job status and details
69
+ - `POST /qa` → Answer and retrieved contexts (JSON body: `{"question": "str", "top_k": 4}`)
70
+ - `GET /health` → Health check
71
 
72
  ## Usage
73
+ 1. Start the backend and UI as above.
74
+ 2. In the Streamlit UI, upload a PDF or TXT file.
75
+ 3. Wait for ingestion (check job status if needed).
76
+ 4. Ask questions; the system retrieves relevant chunks and generates answers using Gemini.
77
+
78
+ ## Evaluation Criteria Alignment
79
+ - **Chunking Strategy**: 800/120 overlap for optimal context vs. precision.
80
+ - **Retrieval Quality**: FAISS similarity search with normalized embeddings; fallback to excerpts.
81
+ - **API Design**: RESTful with Pydantic, background jobs, rate limiting.
82
+ - **Metrics Awareness**: Latency logging for performance monitoring.
83
+ - **System Explanation**: Detailed in this README and code comments.
84
+
85
+ ## GitHub Repository
86
+ [Link to GitHub Repo](https://github.com/your-username/rag-qa-system) # Replace with actual link
87
  3. Ask questions via the UI or `POST /qa`.
88
 
89
  ## Mandatory Explanations
app/rag.py CHANGED
@@ -192,18 +192,25 @@ def generate_answer(question: str, contexts: List[Dict]) -> str:
192
  f"Context:\n{context_text}\n\nQuestion: {question}\nAnswer:"
193
  )
194
 
195
- try:
196
- client = genai.Client(api_key=GOOGLE_API_KEY)
197
- response = client.models.generate_content(
198
- model="gemini-2.0-flash",
199
- contents=prompt,
200
- )
201
- return response.text.strip() or context_text
202
- except Exception as e:
203
- return (
204
- f"LLM generation failed: {str(e)}. Here are the most relevant excerpts:\n\n"
205
- + context_text
206
- )
 
 
 
 
 
 
 
207
 
208
 
209
  def ingest_document(path: Path, doc_id: str, source: str) -> Dict:
 
192
  f"Context:\n{context_text}\n\nQuestion: {question}\nAnswer:"
193
  )
194
 
195
+ max_retries = 3
196
+ for attempt in range(max_retries):
197
+ try:
198
+ client = genai.Client(api_key=GOOGLE_API_KEY)
199
+ response = client.models.generate_content(
200
+ model="gemini-2.0-flash",
201
+ contents=prompt,
202
+ )
203
+ return response.text.strip() or context_text
204
+ except Exception as e:
205
+ error_str = str(e)
206
+ if "429" in error_str and attempt < max_retries - 1:
207
+ wait_time = 2 ** attempt # Exponential backoff
208
+ time.sleep(wait_time)
209
+ continue
210
+ return (
211
+ f"LLM generation failed: {error_str}. Here are the most relevant excerpts:\n\n"
212
+ + context_text
213
+ )
214
 
215
 
216
  def ingest_document(path: Path, doc_id: str, source: str) -> Dict:
streamlit_app.py CHANGED
@@ -12,7 +12,7 @@ from app.rag import ingest_document, search, generate_answer
12
 
13
  st.set_page_config(page_title="RAG QA", page_icon="🧠", layout="wide")
14
 
15
- API_BASE = os.getenv("API_BASE", "http://localhost:8000")
16
 
17
  st.title("RAG-Based Question Answering")
18
 
 
12
 
13
  st.set_page_config(page_title="RAG QA", page_icon="🧠", layout="wide")
14
 
15
+ API_BASE = os.getenv("API_BASE", "http://localhost:8001")
16
 
17
  st.title("RAG-Based Question Answering")
18