Muhammad Umer commited on
Commit
ab23676
Β·
1 Parent(s): 061b2a7

better documentations

Browse files
Files changed (4) hide show
  1. README.md +7 -201
  2. README_HF.md +181 -0
  3. README_RAG.md +239 -0
  4. requirements.txt +11 -10
README.md CHANGED
@@ -21,12 +21,10 @@ A complete **Retrieval-Augmented Generation (RAG)** system deployed as a Hugging
21
 
22
  1. [Overview](#overview)
23
  2. [Architecture](#architecture)
24
- 3. [Step-by-Step Explanation](#step-by-step-explanation)
25
- 4. [API Endpoints](#api-endpoints)
26
- 5. [Setup & Deployment](#setup--deployment)
27
- 6. [Adding Binary Files to the HF Space](#adding-binary-files-to-the-hf-space)
28
- 7. [Configuration](#configuration)
29
- 8. [How It Works (Detailed)](#how-it-works-detailed)
30
 
31
  ---
32
 
@@ -67,201 +65,6 @@ This application demonstrates how to build a production-ready RAG system within
67
 
68
  ---
69
 
70
- ## Step-by-Step Explanation
71
-
72
- ### Step 1: Document Ingestion & Chunking
73
-
74
- Before we can answer questions, we need to prepare our knowledge base.
75
-
76
- 1. **Load documents** β€” Read text files from `sample_documents/` directory
77
- 2. **Chunk text** β€” Split documents into smaller overlapping chunks (512 tokens, 50 token overlap) using `RecursiveCharacterTextSplitter`. This ensures each chunk fits within the embedding model's context window while maintaining semantic coherence.
78
-
79
- ```python
80
- splitter = RecursiveCharacterTextSplitter(
81
- chunk_size=512,
82
- chunk_overlap=50,
83
- separators=["\n\n", "\n", ". ", " ", ""],
84
- )
85
- chunks = splitter.split_text(document_text)
86
- ```
87
-
88
- ### Step 2: Generate Embeddings
89
-
90
- Convert text chunks into dense vector representations that capture semantic meaning.
91
-
92
- 1. **Call Azure OpenAI** β€” We use the `text-embedding-3-small` model via the Azure OpenAI embeddings endpoint
93
- 2. **Encode text** β€” Each chunk is transformed into a fixed-size vector where semantically similar texts are closer together in vector space
94
-
95
- ```python
96
- import requests as http_requests
97
-
98
- headers = {"api-key": AZURE_API_KEY, "Content-Type": "application/json"}
99
- payload = {"input": ["chunk 1 text", "chunk 2 text"], "model": "text-embedding-3-small"}
100
- resp = http_requests.post(EMBEDDING_ENDPOINT_URL, headers=headers, json=payload)
101
- embeddings = [item["embedding"] for item in resp.json()["data"]]
102
- ```
103
-
104
- ### Step 3: Store in Vector Database (ChromaDB)
105
-
106
- Persist embeddings in a vector store optimized for similarity search.
107
-
108
- 1. **Initialize ChromaDB** β€” Create a persistent client that stores data on disk (survives Space restarts)
109
- 2. **Create collection** β€” A named collection with cosine similarity metric
110
- 3. **Add documents** β€” Store embeddings alongside the original text and metadata
111
-
112
- ```python
113
- import chromadb
114
-
115
- client = chromadb.PersistentClient(path="./data/chroma_db")
116
- collection = client.get_or_create_collection(
117
- name="rag_documents",
118
- metadata={"hnsw:space": "cosine"},
119
- )
120
- collection.add(
121
- ids=["doc_0", "doc_1"],
122
- embeddings=embeddings.tolist(),
123
- documents=["chunk 1 text", "chunk 2 text"],
124
- metadatas=[{"source": "file.txt"}, {"source": "file.txt"}],
125
- )
126
- ```
127
-
128
- ### Step 4: Query & Retrieval
129
-
130
- When a user asks a question, find the most relevant context.
131
-
132
- 1. **Embed the query** β€” Use the same Azure OpenAI embedding model to convert the question to a vector
133
- 2. **Similarity search** β€” Find the top-K nearest vectors in ChromaDB (cosine similarity)
134
- 3. **Return context** β€” Extract the original text chunks for the closest matches
135
-
136
- ```python
137
- query_embedding = generate_embeddings(["What is the Eiffel Tower?"])[0]
138
- results = collection.query(
139
- query_embeddings=[query_embedding],
140
- n_results=3,
141
- )
142
- ```
143
-
144
- ### Step 5: LLM Generation (Augmented Response)
145
-
146
- Combine retrieved context with the user's question and generate an answer.
147
-
148
- 1. **Build prompt** β€” Load the template from [`prompts/rag_prompt.txt`](prompts/rag_prompt.txt), inject retrieved context and the user's question
149
- 2. **Call Azure OpenAI** β€” Send the prompt to the Azure OpenAI chat/completions endpoint (`gpt-5`)
150
- 3. **Return response** β€” The LLM generates an answer grounded in the provided context
151
-
152
- The prompt template (`prompts/rag_prompt.txt`):
153
-
154
- ```
155
- You are a helpful assistant. Answer the user's question based ONLY on the provided context.
156
- If the context does not contain enough information to answer, say "I don't have enough information to answer this question."
157
- Always be concise and factual.
158
-
159
- Context:
160
- {context}
161
-
162
- Question: {question}
163
- ```
164
-
165
- The template is loaded once at startup and sent as the user message to the chat endpoint:
166
-
167
- ```python
168
- RAG_PROMPT_TEMPLATE = Path("prompts/rag_prompt.txt").read_text(encoding="utf-8")
169
-
170
- # At query time:
171
- prompt = RAG_PROMPT_TEMPLATE.format(context=context_text, question=user_query)
172
- headers = {"api-key": AZURE_API_KEY, "Content-Type": "application/json"}
173
- payload = {
174
- "model": "gpt-5",
175
- "messages": [{"role": "user", "content": prompt}],
176
- "max_completion_tokens": 512,
177
- "temperature": 0.7,
178
- "top_p": 0.95,
179
- }
180
- resp = requests.post(LLM_ENDPOINT_URL, headers=headers, json=payload)
181
- answer = resp.json()["choices"][0]["message"]["content"]
182
- ```
183
-
184
- > **Tip:** Edit `prompts/rag_prompt.txt` to tune the model's behaviour (tone, language, output format) without touching application code.
185
-
186
- ### Step 6: API Endpoint (`/query`)
187
-
188
- The FastAPI endpoint ties everything together for the evaluation system.
189
-
190
- ```python
191
- @app.post("/query")
192
- async def query_endpoint(request: QueryRequest):
193
- # 1. Retrieve relevant context
194
- # 2. Build augmented prompt
195
- # 3. Generate LLM response
196
- # 4. Return answer + sources
197
- result = rag_query(request.query, top_k=request.top_k)
198
- return JSONResponse(content=result)
199
- ```
200
-
201
- ---
202
-
203
- ## API Endpoints
204
-
205
- ### `POST /query`
206
-
207
- The primary endpoint for the RAG evaluation system.
208
-
209
- **Request:**
210
- ```json
211
- {
212
- "query": "What materials is the Eiffel Tower made of?",
213
- "top_k": 3
214
- }
215
- ```
216
-
217
- **Response:**
218
- ```json
219
- {
220
- "answer": "The Eiffel Tower is made of wrought iron (puddled iron)...",
221
- "sources": [
222
- {"source": "eiffel_tower.txt", "score": 0.87},
223
- {"source": "paris_landmarks.txt", "score": 0.72}
224
- ],
225
- "query": "What materials is the Eiffel Tower made of?"
226
- }
227
- ```
228
-
229
- ### `POST /ingest`
230
-
231
- Add new documents to the knowledge base.
232
-
233
- **Request:**
234
- ```json
235
- {
236
- "text": "The Eiffel Tower was built in 1889...",
237
- "source": "my_document.txt"
238
- }
239
- ```
240
-
241
- **Response:**
242
- ```json
243
- {
244
- "status": "success",
245
- "chunks_added": 5,
246
- "total_chunks": 42
247
- }
248
- ```
249
-
250
- ### `GET /health`
251
-
252
- System health check.
253
-
254
- **Response:**
255
- ```json
256
- {
257
- "status": "healthy",
258
- "documents_in_store": 42,
259
- "embedding_model": "text-embedding-3-small",
260
- "llm_model": "gpt-5"
261
- }
262
- ```
263
-
264
- ---
265
 
266
  ## Setup & Deployment
267
 
@@ -293,6 +96,7 @@ source ~/.venv/hackathon-eiffel/bin/activate
293
  ~\.venv\hackathon-eiffel\Scripts\Activate.ps1
294
  # Windows (Command Prompt)
295
  ~\.venv\hackathon-eiffel\Scripts\activate.bat
 
296
  ```
297
 
298
  Once your virtual environment is active, install dependencies and run the app:
@@ -310,6 +114,8 @@ $env:AZURE_API_KEY = "your_azure_api_key_here"
310
  # Run the application
311
  python app.py
312
  # Server starts at http://localhost:7860
 
 
313
  ```
314
 
315
  > **Tip:** To deactivate the virtual environment when you are done, run `deactivate` (venv) or `conda deactivate` (conda).
 
21
 
22
  1. [Overview](#overview)
23
  2. [Architecture](#architecture)
24
+ 3. [Setup & Deployment](#setup--deployment)
25
+ 4. [Adding Binary Files to the HF Space](#adding-binary-files-to-the-hf-space)
26
+ 5. [Configuration](#configuration)
27
+ 6. [How It Works (Detailed)](#how-it-works-detailed)
 
 
28
 
29
  ---
30
 
 
65
 
66
  ---
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
  ## Setup & Deployment
70
 
 
96
  ~\.venv\hackathon-eiffel\Scripts\Activate.ps1
97
  # Windows (Command Prompt)
98
  ~\.venv\hackathon-eiffel\Scripts\activate.bat
99
+
100
  ```
101
 
102
  Once your virtual environment is active, install dependencies and run the app:
 
114
  # Run the application
115
  python app.py
116
  # Server starts at http://localhost:7860
117
+
118
+ # To make it work, you first need to create embeddings , you can learn about it from README_RAG.md
119
  ```
120
 
121
  > **Tip:** To deactivate the virtual environment when you are done, run `deactivate` (venv) or `conda deactivate` (conda).
README_HF.md ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # How to Push Code to a New HuggingFace Space
2
+
3
+ ## Prerequisites
4
+
5
+ - [Git](https://git-scm.com/) installed
6
+ - A [HuggingFace account](https://huggingface.co/join)
7
+ - A HuggingFace Access Token (create one at [Settings > Tokens](https://huggingface.co/settings/tokens) with **Write** permission)
8
+
9
+ ---
10
+
11
+ ## Steps
12
+
13
+ ### 1. Create a New Space on HuggingFace
14
+
15
+ 1. Go to [huggingface.co/new-space](https://huggingface.co/new-space)
16
+ 2. Choose a **Space name** (e.g., `My_App`)
17
+ 3. Select the **SDK** (Gradio, Streamlit, Docker, or Static)
18
+ 4. Choose visibility (Public or Private)
19
+ 5. Click **Create Space**
20
+
21
+ Your Space URL will be: `https://huggingface.co/spaces/<YOUR_USERNAME>/<SPACE_NAME>`
22
+
23
+ ---
24
+
25
+ ### 2. Clone the Empty Space Locally
26
+
27
+ ```bash
28
+ git clone https://huggingface.co/spaces/<YOUR_USERNAME>/<SPACE_NAME>
29
+ cd <SPACE_NAME>
30
+ ```
31
+
32
+ When prompted for credentials:
33
+ - **Username:** Your HuggingFace username
34
+ - **Password:** Your HuggingFace Access Token (NOT your account password)
35
+
36
+ ---
37
+
38
+ ### 3. Add Your Code
39
+
40
+ You have **two options** to get code into your new Space:
41
+
42
+ #### Option A: Pull from an Existing Repo into the Space
43
+
44
+ If the code you want already lives in another git repo (e.g., a teammate's HF Space or a GitHub repo), you can pull it in:
45
+
46
+ ```bash
47
+ # Inside your cloned Space folder:
48
+ cd <SPACE_NAME>
49
+
50
+ # Add the source repo as a second remote
51
+ git remote add source https://huggingface.co/spaces/<SOURCE_OWNER>/<SOURCE_REPO>
52
+ # or from GitHub:
53
+ # git remote add source https://github.com/<OWNER>/<REPO>.git
54
+
55
+ # Fetch all branches from the source
56
+ git fetch source
57
+
58
+ # Merge the source's main branch into your Space
59
+ git merge source/main --allow-unrelated-histories -m "Pull code from source repo"
60
+ ```
61
+
62
+ > If there are merge conflicts, resolve them, then `git add -A` and `git commit`.
63
+
64
+ #### Option B: Copy Files Manually
65
+
66
+ Simply copy/paste your project files into the cloned Space folder.
67
+
68
+ ---
69
+
70
+ **Either way**, make sure you have a `.gitignore` to exclude unnecessary files:
71
+
72
+ ```
73
+ .venv
74
+ __pycache__/
75
+ **/__pycache__/
76
+ *.sqlite3
77
+ chroma_db/
78
+ .env
79
+ ```
80
+
81
+ ---
82
+
83
+ ### 4. Commit and Push
84
+
85
+ ```bash
86
+ git add -A
87
+ git commit -m "Initial commit"
88
+ git push origin main
89
+ git remote remove source
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Alternative (OPTIONAL): Push an Existing Local Project (with Full History)
95
+
96
+ If you already have a local project with commits and want to push everything (all history) to a new HF Space:
97
+
98
+ ### 1. Add the Space as a Remote (OPTIONAL)
99
+
100
+ ```bash
101
+ cd /path/to/your/project
102
+ git remote add hfspace https://<YOUR_USERNAME>:<HF_TOKEN>@huggingface.co/spaces/<YOUR_USERNAME>/<SPACE_NAME>
103
+ ```
104
+
105
+ > **Tip:** Embedding the token in the URL avoids repeated password prompts.
106
+
107
+ ### 2. Make Sure Binary Files Are NOT Tracked
108
+
109
+ HuggingFace rejects any push containing binary files (`.sqlite3`, `.pkl`, `.bin`, etc.).
110
+ Before pushing, ensure they are in `.gitignore` **and** removed from the entire git history.
111
+
112
+ ```bash
113
+ # Add binary paths to .gitignore first, then:
114
+ git rm -r --cached path/to/binary/files
115
+ git add -A
116
+ git commit -m "Remove binary files from tracking"
117
+ ```
118
+
119
+ If binaries exist in **older commits**, you must rewrite history (see Troubleshooting below).
120
+
121
+ ### 3. Push the Current Branch with All Commits (OPTIONAL)
122
+
123
+ ```bash
124
+ git push hfspace HEAD:main --force
125
+ ```
126
+
127
+ - `HEAD` = your current branch (whatever it's called)
128
+ - `HEAD:main` = push it to the `main` branch on the Space
129
+ - `--force` = overwrite the Space's existing initial commit
130
+
131
+ This preserves your full commit history on the Space.
132
+
133
+ ---
134
+
135
+ ## Troubleshooting
136
+
137
+ ### Binary File Rejection
138
+
139
+ HuggingFace rejects pushes containing binary files (e.g., `.sqlite3`, `.pkl`, `.bin`).
140
+
141
+ **Fix:**
142
+
143
+ 1. Add the binary files to `.gitignore`
144
+ 2. Remove them from git tracking:
145
+ ```bash
146
+ git rm -r --cached path/to/binary/file
147
+ ```
148
+ 3. Squash history to purge them completely:
149
+ ```bash
150
+ git add -A
151
+ git reset --soft $(git rev-list --max-parents=0 HEAD)
152
+ git commit -m "Clean initial commit"
153
+ git push hfspace main --force
154
+ ```
155
+
156
+ ### Authentication Failed
157
+
158
+ - HuggingFace does **not** accept account passwords for git. Use an **Access Token**.
159
+ - Make sure the token has **Write** permission.
160
+ - You can embed the token in the remote URL:
161
+ ```bash
162
+ git remote set-url hfspace https://<USERNAME>:<TOKEN>@huggingface.co/spaces/<USERNAME>/<SPACE_NAME>
163
+ ```
164
+
165
+ ### Wrong Branch Name
166
+
167
+ Some repos use `master` instead of `main`. Check with:
168
+
169
+ ```bash
170
+ git branch
171
+ ```
172
+
173
+ Push to whichever branch your Space expects (usually `main`).
174
+
175
+ ---
176
+
177
+ ## Security Reminder
178
+
179
+ - **Never** commit your HF token or API keys to the repo.
180
+ - If a token is accidentally exposed, revoke it immediately at [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) and generate a new one.
181
+ - Use environment variables or HuggingFace Space **Secrets** (Settings > Variables and secrets) for sensitive values.
README_RAG.md ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # πŸ—Ό RAG Chat API β€” Gustave Eiffel Hackathon 2026
2
+
3
+ A complete **Retrieval-Augmented Generation (RAG)** system deployed as a Hugging Face Space, with a `/query` API endpoint designed for the RAG evaluation system.
4
+
5
+ ---
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ This application demonstrates how to build a production-ready RAG system within the Hugging Face ecosystem. It covers:
11
+
12
+ | Requirement | Solution |
13
+ |---|---|
14
+ | LLM API calls | Azure OpenAI (`gpt-5` via REST) |
15
+ | Text β†’ Embeddings | Azure OpenAI (`text-embedding-3-small` via REST) |
16
+ | Vector Store | ChromaDB (persistent, runs in-process) |
17
+ | API Endpoint | FastAPI with `POST /query` |
18
+ | UI | Gradio Blocks (chat + document ingestion) |
19
+
20
+ ---
21
+
22
+ ## Architecture
23
+
24
+ ```
25
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
26
+ β”‚ Hugging Face Space β”‚
27
+ β”‚ β”‚
28
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
29
+ β”‚ β”‚ Gradio β”‚ β”‚ FastAPI β”‚ β”‚ ChromaDB β”‚ β”‚
30
+ β”‚ β”‚ UI │────▢│ /query │────▢│ Vector Store β”‚ β”‚
31
+ β”‚ β”‚ β”‚ β”‚ /ingest β”‚ β”‚ (persistent) β”‚ β”‚
32
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
33
+ β”‚ β”‚ β–² β”‚
34
+ β”‚ β–Ό β”‚ β”‚
35
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
36
+ β”‚ β”‚ Azure OpenAI β”‚ β”‚ Azure OpenAI β”‚ β”‚
37
+ β”‚ β”‚ GPT-5 (LLM) β”‚ β”‚ text-embedding β”‚ β”‚
38
+ β”‚ β”‚ β”‚ β”‚ -3-small β”‚ β”‚
39
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
40
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
41
+ ```
42
+
43
+ ---
44
+
45
+ ## Step-by-Step Explanation
46
+
47
+ ### Step 1: Document Ingestion & Chunking
48
+
49
+ Before we can answer questions, we need to prepare our knowledge base.
50
+
51
+ 1. **Load documents** β€” Read text files from `sample_documents/` directory
52
+ 2. **Chunk text** β€” Split documents into smaller overlapping chunks (512 tokens, 50 token overlap) using `RecursiveCharacterTextSplitter`. This ensures each chunk fits within the embedding model's context window while maintaining semantic coherence.
53
+
54
+ ```python
55
+ splitter = RecursiveCharacterTextSplitter(
56
+ chunk_size=512,
57
+ chunk_overlap=50,
58
+ separators=["\n\n", "\n", ". ", " ", ""],
59
+ )
60
+ chunks = splitter.split_text(document_text)
61
+ ```
62
+
63
+ ### Step 2: Generate Embeddings
64
+
65
+ Convert text chunks into dense vector representations that capture semantic meaning.
66
+
67
+ 1. **Call Azure OpenAI** β€” We use the `text-embedding-3-small` model via the Azure OpenAI embeddings endpoint
68
+ 2. **Encode text** β€” Each chunk is transformed into a fixed-size vector where semantically similar texts are closer together in vector space
69
+
70
+ ```python
71
+ import requests as http_requests
72
+
73
+ headers = {"api-key": AZURE_API_KEY, "Content-Type": "application/json"}
74
+ payload = {"input": ["chunk 1 text", "chunk 2 text"], "model": "text-embedding-3-small"}
75
+ resp = http_requests.post(EMBEDDING_ENDPOINT_URL, headers=headers, json=payload)
76
+ embeddings = [item["embedding"] for item in resp.json()["data"]]
77
+ ```
78
+
79
+ ### Step 3: Store in Vector Database (ChromaDB)
80
+
81
+ Persist embeddings in a vector store optimized for similarity search.
82
+
83
+ 1. **Initialize ChromaDB** β€” Create a persistent client that stores data on disk (survives Space restarts)
84
+ 2. **Create collection** β€” A named collection with cosine similarity metric
85
+ 3. **Add documents** β€” Store embeddings alongside the original text and metadata
86
+
87
+ ```python
88
+ import chromadb
89
+
90
+ client = chromadb.PersistentClient(path="./data/chroma_db")
91
+ collection = client.get_or_create_collection(
92
+ name="rag_documents",
93
+ metadata={"hnsw:space": "cosine"},
94
+ )
95
+ collection.add(
96
+ ids=["doc_0", "doc_1"],
97
+ embeddings=embeddings.tolist(),
98
+ documents=["chunk 1 text", "chunk 2 text"],
99
+ metadatas=[{"source": "file.txt"}, {"source": "file.txt"}],
100
+ )
101
+ ```
102
+
103
+ ### Step 4: Query & Retrieval
104
+
105
+ When a user asks a question, find the most relevant context.
106
+
107
+ 1. **Embed the query** β€” Use the same Azure OpenAI embedding model to convert the question to a vector
108
+ 2. **Similarity search** β€” Find the top-K nearest vectors in ChromaDB (cosine similarity)
109
+ 3. **Return context** β€” Extract the original text chunks for the closest matches
110
+
111
+ ```python
112
+ query_embedding = generate_embeddings(["What is the Eiffel Tower?"])[0]
113
+ results = collection.query(
114
+ query_embeddings=[query_embedding],
115
+ n_results=3,
116
+ )
117
+ ```
118
+
119
+ ### Step 5: LLM Generation (Augmented Response)
120
+
121
+ Combine retrieved context with the user's question and generate an answer.
122
+
123
+ 1. **Build prompt** β€” Load the template from [`prompts/rag_prompt.txt`](prompts/rag_prompt.txt), inject retrieved context and the user's question
124
+ 2. **Call Azure OpenAI** β€” Send the prompt to the Azure OpenAI chat/completions endpoint (`gpt-5`)
125
+ 3. **Return response** β€” The LLM generates an answer grounded in the provided context
126
+
127
+ The prompt template (`prompts/rag_prompt.txt`):
128
+
129
+ ```
130
+ You are a helpful assistant. Answer the user's question based ONLY on the provided context.
131
+ If the context does not contain enough information to answer, say "I don't have enough information to answer this question."
132
+ Always be concise and factual.
133
+
134
+ Context:
135
+ {context}
136
+
137
+ Question: {question}
138
+ ```
139
+
140
+ The template is loaded once at startup and sent as the user message to the chat endpoint:
141
+
142
+ ```python
143
+ RAG_PROMPT_TEMPLATE = Path("prompts/rag_prompt.txt").read_text(encoding="utf-8")
144
+
145
+ # At query time:
146
+ prompt = RAG_PROMPT_TEMPLATE.format(context=context_text, question=user_query)
147
+ headers = {"api-key": AZURE_API_KEY, "Content-Type": "application/json"}
148
+ payload = {
149
+ "model": "gpt-5",
150
+ "messages": [{"role": "user", "content": prompt}],
151
+ "max_completion_tokens": 512,
152
+ "temperature": 0.7,
153
+ "top_p": 0.95,
154
+ }
155
+ resp = requests.post(LLM_ENDPOINT_URL, headers=headers, json=payload)
156
+ answer = resp.json()["choices"][0]["message"]["content"]
157
+ ```
158
+
159
+ > **Tip:** Edit `prompts/rag_prompt.txt` to tune the model's behaviour (tone, language, output format) without touching application code.
160
+
161
+ ### Step 6: API Endpoint (`/query`)
162
+
163
+ The FastAPI endpoint ties everything together for the evaluation system.
164
+
165
+ ```python
166
+ @app.post("/query")
167
+ async def query_endpoint(request: QueryRequest):
168
+ # 1. Retrieve relevant context
169
+ # 2. Build augmented prompt
170
+ # 3. Generate LLM response
171
+ # 4. Return answer + sources
172
+ result = rag_query(request.query, top_k=request.top_k)
173
+ return JSONResponse(content=result)
174
+ ```
175
+
176
+ ---
177
+
178
+ ## API Endpoints
179
+
180
+ ### `POST /query`
181
+
182
+ The primary endpoint for the RAG evaluation system.
183
+
184
+ **Request:**
185
+ ```json
186
+ {
187
+ "query": "What materials is the Eiffel Tower made of?",
188
+ "top_k": 3
189
+ }
190
+ ```
191
+
192
+ **Response:**
193
+ ```json
194
+ {
195
+ "answer": "The Eiffel Tower is made of wrought iron (puddled iron)...",
196
+ "sources": [
197
+ {"source": "eiffel_tower.txt", "score": 0.87},
198
+ {"source": "paris_landmarks.txt", "score": 0.72}
199
+ ],
200
+ "query": "What materials is the Eiffel Tower made of?"
201
+ }
202
+ ```
203
+
204
+ ### `POST /ingest`
205
+
206
+ Add new documents to the knowledge base.
207
+
208
+ **Request:**
209
+ ```json
210
+ {
211
+ "text": "The Eiffel Tower was built in 1889...",
212
+ "source": "my_document.txt"
213
+ }
214
+ ```
215
+
216
+ **Response:**
217
+ ```json
218
+ {
219
+ "status": "success",
220
+ "chunks_added": 5,
221
+ "total_chunks": 42
222
+ }
223
+ ```
224
+
225
+ ### `GET /health`
226
+
227
+ System health check.
228
+
229
+ **Response:**
230
+ ```json
231
+ {
232
+ "status": "healthy",
233
+ "documents_in_store": 42,
234
+ "embedding_model": "text-embedding-3-small",
235
+ "llm_model": "gpt-5"
236
+ }
237
+ ```
238
+
239
+ ---
requirements.txt CHANGED
@@ -1,14 +1,15 @@
1
  fastapi==0.115.0
2
  uvicorn==0.30.0
3
  gradio==4.44.1
4
- chromadb==0.5.0
5
- huggingface-hub==0.25.0
6
- langchain==0.3.0
7
- langchain-community==0.3.0
8
- langchain-huggingface==0.1.0
9
- langchain-chroma==0.2.0
10
- langchain-text-splitters==0.3.0
11
- pypdf==4.3.0
12
- python-multipart==0.0.9
13
- pydantic==2.9.0
 
14
  requests>=2.31.0
 
1
  fastapi==0.115.0
2
  uvicorn==0.30.0
3
  gradio==4.44.1
4
+ chromadb>=1.0.0
5
+ sentence-transformers>=3.0.0
6
+ huggingface-hub==0.36.2
7
+ langchain>=0.3.0
8
+ langchain-community>=0.3.0
9
+ langchain-huggingface>=0.1.0
10
+ langchain-chroma>=0.2.2
11
+ langchain-text-splitters>=0.3.0
12
+ pypdf>=4.3.0
13
+ python-multipart>=0.0.9
14
+ pydantic>=2.9.0
15
  requests>=2.31.0