KalooAI commited on
Commit
451bc89
·
verified ·
1 Parent(s): 165947a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +100 -28
app.py CHANGED
@@ -1,10 +1,12 @@
 
1
  import os
2
  import time
3
  import gradio as gr
4
  import numpy as np
5
- import requests
6
  from PyPDF2 import PdfReader
7
  from sentence_transformers import SentenceTransformer
 
 
8
 
9
  # ============================================================
10
  # CONFIG
@@ -12,34 +14,44 @@ from sentence_transformers import SentenceTransformer
12
 
13
  HF_TOKEN = os.getenv("HF_TOKEN", None)
14
 
15
- # FREE MODEL (no credits required)
16
- FREE_MODEL = "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B"
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
19
  embedding_model = SentenceTransformer(EMBEDDING_MODEL)
20
 
 
 
 
21
 
22
  # ============================================================
23
- # FREE HF INFERENCE API (NO CREDITS REQUIRED)
24
  # ============================================================
25
 
26
- import requests
27
-
28
- def hf_free_inference(model, prompt):
29
- response = requests.post(
30
- f"https://router.huggingface.co/hf-inference/models/{model}",
31
- headers={"Authorization": f"Bearer {HF_TOKEN}"},
32
- json={"inputs": prompt}
33
- )
34
- data = response.json()
35
-
36
- # Some models return a list, others return a dict
37
- if isinstance(data, list) and "generated_text" in data[0]:
38
- return data[0]["generated_text"]
39
- if isinstance(data, dict) and "generated_text" in data:
40
- return data["generated_text"]
41
-
42
- return str(data)
43
 
44
 
45
  # ============================================================
@@ -75,6 +87,10 @@ def chunk_text(text, chunk_size=800, overlap=200):
75
  # ============================================================
76
 
77
  def retrieve_context_per_file(question, state, k=10):
 
 
 
 
78
  if not state["files"]:
79
  return ""
80
 
@@ -88,9 +104,13 @@ def retrieve_context_per_file(question, state, k=10):
88
  if len(chunks) == 0:
89
  continue
90
 
 
91
  distances = np.linalg.norm(embeddings - q_emb, axis=1)
 
 
92
  top_k_idx = distances.argsort()[: min(k, len(chunks))]
93
 
 
94
  selected = [f"[{filename}]\n{chunks[i]}" for i in top_k_idx]
95
  all_selected.extend(selected)
96
 
@@ -118,8 +138,18 @@ def generate_answer(question, context):
118
  f"Question: {question}\nAnswer:"
119
  )
120
 
121
- answer = hf_free_inference(FREE_MODEL, prompt)
122
- return answer.strip()
 
 
 
 
 
 
 
 
 
 
123
 
124
 
125
  # ============================================================
@@ -127,6 +157,25 @@ def generate_answer(question, context):
127
  # ============================================================
128
 
129
  def load_pdf(files, state):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  if not files:
131
  state["files"] = {}
132
  state["pdf_count"] = 0
@@ -139,10 +188,12 @@ def load_pdf(files, state):
139
  )
140
 
141
  try:
 
142
  state["files"] = {}
143
  state["pdf_count"] = 0
144
  state["total_chunks"] = 0
145
 
 
146
  unique_files = list(dict.fromkeys(files))
147
 
148
  for file in unique_files:
@@ -191,8 +242,10 @@ def remove_pdf(filename, state):
191
  gr.update(choices=list(state["files"].keys()), value=None),
192
  )
193
 
 
194
  state["files"].pop(filename, None)
195
 
 
196
  pdf_count = len(state["files"])
197
  total_chunks = sum(len(data["chunks"]) for data in state["files"].values())
198
 
@@ -212,6 +265,14 @@ def remove_pdf(filename, state):
212
 
213
 
214
  def answer_question(question, state):
 
 
 
 
 
 
 
 
215
  question = question.strip()
216
  if not question:
217
  return "Please enter a question."
@@ -219,7 +280,7 @@ def answer_question(question, state):
219
  if not state["files"]:
220
  return "Please upload at least one PDF first."
221
 
222
- # Persistent memory: "Remember that ..."
223
  lower_q = question.lower()
224
  if lower_q.startswith("remember that"):
225
  fact = question[len("remember that"):].strip(" .")
@@ -229,8 +290,10 @@ def answer_question(question, state):
229
  else:
230
  return "What should I remember?"
231
 
 
232
  rag_context = retrieve_context_per_file(question, state, k=10)
233
 
 
234
  memory_context = ""
235
  if state["history"]:
236
  memory_context = "\n\n".join(
@@ -240,10 +303,12 @@ def answer_question(question, state):
240
  ]
241
  )
242
 
 
243
  facts_context = ""
244
  if state["facts"]:
245
  facts_context = "User Facts:\n" + "\n".join(state["facts"]) + "\n\n"
246
 
 
247
  full_context_parts = []
248
  if facts_context:
249
  full_context_parts.append(facts_context)
@@ -254,8 +319,10 @@ def answer_question(question, state):
254
 
255
  full_context = "\n\n".join(full_context_parts)
256
 
 
257
  answer = generate_answer(question, full_context)
258
 
 
259
  state["history"].append({"user": question, "assistant": answer})
260
 
261
  return answer
@@ -270,20 +337,21 @@ with gr.Blocks(title="PDF Q&A Chatbot") as demo:
270
 
271
  state = gr.State(
272
  {
273
- "files": {},
274
  "pdf_count": 0,
275
  "total_chunks": 0,
276
- "history": [],
277
- "facts": [],
278
  }
279
  )
280
 
281
  with gr.Row():
 
282
  with gr.Column(scale=1):
283
  pdf_input = gr.File(
284
  label="Upload PDF(s)",
285
  file_types=[".pdf"],
286
- file_count="multiple",
287
  )
288
  pdf_status = gr.Textbox(label="Status", interactive=False)
289
 
@@ -294,6 +362,7 @@ with gr.Blocks(title="PDF Q&A Chatbot") as demo:
294
  )
295
  remove_button = gr.Button("Remove Selected PDF")
296
 
 
297
  with gr.Column(scale=2):
298
  with gr.Row():
299
  question = gr.Textbox(
@@ -311,6 +380,7 @@ with gr.Blocks(title="PDF Q&A Chatbot") as demo:
311
  answer = gr.Textbox(label="Answer", lines=8)
312
  ask_button = gr.Button("Ask")
313
 
 
314
  pdf_input.change(
315
  load_pdf,
316
  inputs=[pdf_input, state],
@@ -329,6 +399,8 @@ with gr.Blocks(title="PDF Q&A Chatbot") as demo:
329
  outputs=answer,
330
  )
331
 
 
 
332
  clear_button.click(lambda: "", outputs=question)
333
 
334
 
 
1
+
2
  import os
3
  import time
4
  import gradio as gr
5
  import numpy as np
 
6
  from PyPDF2 import PdfReader
7
  from sentence_transformers import SentenceTransformer
8
+ from huggingface_hub import InferenceClient
9
+ from huggingface_hub.errors import HfHubHTTPError
10
 
11
  # ============================================================
12
  # CONFIG
 
14
 
15
  HF_TOKEN = os.getenv("HF_TOKEN", None)
16
 
17
+ # Free model compatible with new HF API
18
+ #LLM_MODEL = "meta-llama/Llama-3.2-1B-Instruct"
19
+ #LLM_MODEL = "meta-llama/Llama-3.1-8B-Instruct"
20
+ # LLM_MODEL = "mistralai/Mistral-7B-Instruct-v0.3" Does not work
21
+ # LLM_MODEL ="HuggingFaceH4/zephyr-7b-beta"
22
+ # LLM_MODEL = "NousResearch/Hermes-2-Pro-Mistral-7B" Does not work
23
+
24
+ # Were Great LLM
25
+ # LLM_MODEL = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"
26
+ # LLM_MODEL = "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B"
27
+
28
+ # LLM model
29
+ # LLM_MODEL = "deepseek-ai/DeepSeek-V3"
30
+ LLM_MODEL = 'Qwen/Qwen2.5-14B-Instruct'
31
 
32
  EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
33
  embedding_model = SentenceTransformer(EMBEDDING_MODEL)
34
 
35
+ # IMPORTANT: do NOT pass model=... here
36
+ llm = InferenceClient(token=HF_TOKEN)
37
+
38
 
39
  # ============================================================
40
+ # SAFE LLM CALL (prevents 429 errors)
41
  # ============================================================
42
 
43
+ def safe_chat_completion(llm, **kwargs):
44
+ for attempt in range(5):
45
+ try:
46
+ return llm.chat.completions.create(**kwargs)
47
+ except HfHubHTTPError as e:
48
+ if "429" in str(e):
49
+ sleep_time = 2 ** attempt
50
+ print(f"[Rate limit] Retrying in {sleep_time}s...")
51
+ time.sleep(sleep_time)
52
+ else:
53
+ raise
54
+ raise RuntimeError("Too many retries after rate limits.")
 
 
 
 
 
55
 
56
 
57
  # ============================================================
 
87
  # ============================================================
88
 
89
  def retrieve_context_per_file(question, state, k=10):
90
+ """
91
+ For each file, retrieve top-k chunks separately,
92
+ then merge all selected chunks into a single context.
93
+ """
94
  if not state["files"]:
95
  return ""
96
 
 
104
  if len(chunks) == 0:
105
  continue
106
 
107
+ # Compute L2 distances to the query embedding
108
  distances = np.linalg.norm(embeddings - q_emb, axis=1)
109
+
110
+ # Get top-k indices for this file
111
  top_k_idx = distances.argsort()[: min(k, len(chunks))]
112
 
113
+ # Tag chunks with filename for transparency
114
  selected = [f"[{filename}]\n{chunks[i]}" for i in top_k_idx]
115
  all_selected.extend(selected)
116
 
 
138
  f"Question: {question}\nAnswer:"
139
  )
140
 
141
+ response = safe_chat_completion(
142
+ llm,
143
+ model=LLM_MODEL,
144
+ messages=[{"role": "user", "content": prompt}],
145
+ max_tokens=1024,
146
+ temperature=0.25,
147
+ top_p=0.9,
148
+ frequency_penalty=0.1,
149
+ presence_penalty=0.0,
150
+ )
151
+
152
+ return response.choices[0].message["content"].strip()
153
 
154
 
155
  # ============================================================
 
157
  # ============================================================
158
 
159
  def load_pdf(files, state):
160
+ """
161
+ files: current list from the UI (after add/remove).
162
+ We rebuild state from scratch to stay in sync.
163
+
164
+ state = {
165
+ "files": {
166
+ "filename.pdf": {
167
+ "chunks": [...],
168
+ "embeddings": np.array([...])
169
+ },
170
+ ...
171
+ },
172
+ "pdf_count": int,
173
+ "total_chunks": int,
174
+ "history": [...],
175
+ "facts": [...]
176
+ }
177
+ """
178
+ # If user cleared all files
179
  if not files:
180
  state["files"] = {}
181
  state["pdf_count"] = 0
 
188
  )
189
 
190
  try:
191
+ # Reset and rebuild from current files only
192
  state["files"] = {}
193
  state["pdf_count"] = 0
194
  state["total_chunks"] = 0
195
 
196
+ # Deduplicate while preserving order
197
  unique_files = list(dict.fromkeys(files))
198
 
199
  for file in unique_files:
 
242
  gr.update(choices=list(state["files"].keys()), value=None),
243
  )
244
 
245
+ # Remove the file entry
246
  state["files"].pop(filename, None)
247
 
248
+ # Recompute counts
249
  pdf_count = len(state["files"])
250
  total_chunks = sum(len(data["chunks"]) for data in state["files"].values())
251
 
 
265
 
266
 
267
  def answer_question(question, state):
268
+ print("HF_TOKEN loaded:", HF_TOKEN is not None)
269
+
270
+ if HF_TOKEN:
271
+ print("HF_TOKEN length:", len(HF_TOKEN))
272
+ print("HF_TOKEN preview:", HF_TOKEN[:6] + "..." + HF_TOKEN[-4:])
273
+ else:
274
+ print("HF_TOKEN is None or empty")
275
+
276
  question = question.strip()
277
  if not question:
278
  return "Please enter a question."
 
280
  if not state["files"]:
281
  return "Please upload at least one PDF first."
282
 
283
+ # Simple persistent memory hook: "Remember that ..."
284
  lower_q = question.lower()
285
  if lower_q.startswith("remember that"):
286
  fact = question[len("remember that"):].strip(" .")
 
290
  else:
291
  return "What should I remember?"
292
 
293
+ # RAG context (per-file balanced retrieval)
294
  rag_context = retrieve_context_per_file(question, state, k=10)
295
 
296
+ # Conversation memory (last 5 turns)
297
  memory_context = ""
298
  if state["history"]:
299
  memory_context = "\n\n".join(
 
303
  ]
304
  )
305
 
306
+ # Persistent facts
307
  facts_context = ""
308
  if state["facts"]:
309
  facts_context = "User Facts:\n" + "\n".join(state["facts"]) + "\n\n"
310
 
311
+ # Merge all contexts
312
  full_context_parts = []
313
  if facts_context:
314
  full_context_parts.append(facts_context)
 
319
 
320
  full_context = "\n\n".join(full_context_parts)
321
 
322
+ # Generate answer
323
  answer = generate_answer(question, full_context)
324
 
325
+ # Save conversational memory
326
  state["history"].append({"user": question, "assistant": answer})
327
 
328
  return answer
 
337
 
338
  state = gr.State(
339
  {
340
+ "files": {}, # filename -> {chunks, embeddings}
341
  "pdf_count": 0,
342
  "total_chunks": 0,
343
+ "history": [], # conversational memory
344
+ "facts": [], # persistent user facts
345
  }
346
  )
347
 
348
  with gr.Row():
349
+ # LEFT COLUMN — PDF upload + removal
350
  with gr.Column(scale=1):
351
  pdf_input = gr.File(
352
  label="Upload PDF(s)",
353
  file_types=[".pdf"],
354
+ file_count="multiple", # proper multi-upload in Gradio 6
355
  )
356
  pdf_status = gr.Textbox(label="Status", interactive=False)
357
 
 
362
  )
363
  remove_button = gr.Button("Remove Selected PDF")
364
 
365
+ # RIGHT COLUMN — question + clear + answer
366
  with gr.Column(scale=2):
367
  with gr.Row():
368
  question = gr.Textbox(
 
380
  answer = gr.Textbox(label="Answer", lines=8)
381
  ask_button = gr.Button("Ask")
382
 
383
+ # Bind events AFTER components exist
384
  pdf_input.change(
385
  load_pdf,
386
  inputs=[pdf_input, state],
 
399
  outputs=answer,
400
  )
401
 
402
+
403
+
404
  clear_button.click(lambda: "", outputs=question)
405
 
406