KalooAI commited on
Commit
8ea8fe8
·
verified ·
1 Parent(s): 38a2b8e

Update app.py

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