joelg commited on
Commit
a8115f1
·
1 Parent(s): 9019c28

- more relevant suggested questions
- better reasoning traces handling
- better interface

Files changed (2) hide show
  1. app.py +28 -14
  2. rag_system.py +123 -0
app.py CHANGED
@@ -19,9 +19,12 @@ def process_pdf(pdf_file, embedding_model, chunk_size, chunk_overlap):
19
  else:
20
  status, chunks_display, corpus_text = rag.process_document(pdf_file.name, chunk_size, chunk_overlap)
21
 
22
- return status, chunks_display, corpus_text
 
 
 
23
  except Exception as e:
24
- return f"Error: {str(e)}", "", ""
25
 
26
  @spaces.GPU
27
  def perform_query(
@@ -145,10 +148,13 @@ def create_interface():
145
  with gr.Accordion("📑 Processed Chunks", open=False):
146
  processed_chunks_display = gr.Markdown()
147
 
 
 
 
148
  process_btn.click(
149
  fn=process_pdf,
150
  inputs=[pdf_upload, embedding_model, chunk_size, chunk_overlap],
151
- outputs=[corpus_status, processed_chunks_display, default_corpus_display]
152
  )
153
 
154
  # Tab 2: Retrieval Configuration
@@ -215,17 +221,25 @@ def create_interface():
215
  lines=3
216
  )
217
 
218
- examples = gr.Examples(
219
- examples=[
220
- ["What is Retrieval Augmented Generation?"],
221
- ["How does RAG improve language models?"],
222
- ["What are the main components of a RAG system?"],
223
- ["Explain the role of embeddings in RAG."],
224
- ["What are the advantages of using RAG?"],
225
- ],
226
- inputs=query_input,
227
- label="Example Questions"
228
- )
 
 
 
 
 
 
 
 
229
 
230
  query_btn = gr.Button("🔍 Submit Query", variant="primary", size="lg")
231
 
 
19
  else:
20
  status, chunks_display, corpus_text = rag.process_document(pdf_file.name, chunk_size, chunk_overlap)
21
 
22
+ # Generate example questions based on the corpus
23
+ example_questions = rag.generate_example_questions(num_questions=5)
24
+
25
+ return status, chunks_display, corpus_text, example_questions
26
  except Exception as e:
27
+ return f"Error: {str(e)}", "", "", []
28
 
29
  @spaces.GPU
30
  def perform_query(
 
148
  with gr.Accordion("📑 Processed Chunks", open=False):
149
  processed_chunks_display = gr.Markdown()
150
 
151
+ # State to hold example questions
152
+ example_questions_state = gr.State([])
153
+
154
  process_btn.click(
155
  fn=process_pdf,
156
  inputs=[pdf_upload, embedding_model, chunk_size, chunk_overlap],
157
+ outputs=[corpus_status, processed_chunks_display, default_corpus_display, example_questions_state]
158
  )
159
 
160
  # Tab 2: Retrieval Configuration
 
221
  lines=3
222
  )
223
 
224
+ with gr.Accordion("💡 Example Questions (click to expand)", open=True):
225
+ gr.Markdown("*Questions generated based on your corpus content*")
226
+ examples_markdown = gr.Markdown(visible=False)
227
+
228
+ # Connect processing to update examples
229
+ def format_questions_markdown(questions):
230
+ if not questions or len(questions) == 0:
231
+ return gr.update(value="", visible=False)
232
+
233
+ md = ""
234
+ for i, q in enumerate(questions, 1):
235
+ md += f"{i}. {q}\n\n"
236
+ return gr.update(value=md, visible=True)
237
+
238
+ example_questions_state.change(
239
+ fn=format_questions_markdown,
240
+ inputs=[example_questions_state],
241
+ outputs=[examples_markdown]
242
+ )
243
 
244
  query_btn = gr.Button("🔍 Submit Query", variant="primary", size="lg")
245
 
rag_system.py CHANGED
@@ -2,6 +2,7 @@
2
 
3
  import os
4
  import glob
 
5
  from typing import List, Tuple, Optional
6
  import PyPDF2
7
  import faiss
@@ -267,7 +268,129 @@ Answer:"""
267
  else:
268
  answer = str(response).strip()
269
 
 
 
 
270
  return answer, prompt
271
 
272
  except Exception as e:
273
  return f"Error generating response: {str(e)}", prompt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  import os
4
  import glob
5
+ import re
6
  from typing import List, Tuple, Optional
7
  import PyPDF2
8
  import faiss
 
268
  else:
269
  answer = str(response).strip()
270
 
271
+ # Handle reasoning tokens (for models like Qwen)
272
+ answer = self._process_reasoning_output(answer)
273
+
274
  return answer, prompt
275
 
276
  except Exception as e:
277
  return f"Error generating response: {str(e)}", prompt
278
+
279
+ def _process_reasoning_output(self, text: str) -> str:
280
+ """Process output from reasoning models to separate thinking from answer"""
281
+ # Common patterns for reasoning models
282
+ # Qwen uses <think>...</think> tags
283
+ if '<think>' in text and '</think>' in text:
284
+ # Extract reasoning and answer
285
+ reasoning_match = re.search(r'<think>(.*?)</think>', text, re.DOTALL)
286
+ if reasoning_match:
287
+ reasoning = reasoning_match.group(1).strip()
288
+ answer = re.sub(r'<think>.*?</think>', '', text, flags=re.DOTALL).strip()
289
+
290
+ return f"""**Answer:**
291
+
292
+ {answer}
293
+
294
+ ---
295
+
296
+ <details>
297
+ <summary>🧠 Model Reasoning (click to expand)</summary>
298
+
299
+ ```
300
+ {reasoning}
301
+ ```
302
+
303
+ </details>"""
304
+
305
+ # Alternative pattern: text before "Answer:" or similar markers
306
+ if re.search(r'(Answer:|Final Answer:|Response:)', text, re.IGNORECASE):
307
+ parts = re.split(r'(Answer:|Final Answer:|Response:)', text, re.IGNORECASE)
308
+ if len(parts) >= 3:
309
+ reasoning = parts[0].strip()
310
+ answer = ''.join(parts[2:]).strip()
311
+
312
+ if reasoning and len(reasoning) > 50: # Only if there's substantial reasoning
313
+ return f"""**Answer:**
314
+
315
+ {answer}
316
+
317
+ ---
318
+
319
+ <details>
320
+ <summary>🧠 Model Reasoning (click to expand)</summary>
321
+
322
+ ```
323
+ {reasoning}
324
+ ```
325
+
326
+ </details>"""
327
+
328
+ # No reasoning pattern found, return as is
329
+ return text
330
+
331
+ def generate_example_questions(self, num_questions: int = 5) -> List[str]:
332
+ """Generate example questions based on the corpus content"""
333
+ if not self.is_ready() or not self.chunks:
334
+ return [
335
+ "What is the main topic of this document?",
336
+ "Can you summarize the key points?",
337
+ "What are the main concepts discussed?",
338
+ ]
339
+
340
+ # Sample some chunks to understand the corpus
341
+ sample_size = min(10, len(self.chunks))
342
+ import random
343
+ sample_chunks = random.sample(self.chunks, sample_size)
344
+ sample_text = "\n".join(sample_chunks[:3]) # Use first 3 sampled chunks
345
+
346
+ # Generate questions using the LLM
347
+ try:
348
+ if self.llm_client is None:
349
+ self.set_llm_model("meta-llama/Llama-3.2-1B-Instruct")
350
+
351
+ prompt = f"""Based on the following text excerpts, generate {num_questions} diverse and relevant questions that could be answered using this corpus. Make the questions specific and interesting.
352
+
353
+ Text excerpts:
354
+ {sample_text[:2000]}
355
+
356
+ Generate exactly {num_questions} questions, one per line, without numbering:"""
357
+
358
+ messages = [{"role": "user", "content": prompt}]
359
+
360
+ response = self.llm_client.chat_completion(
361
+ messages=messages,
362
+ max_tokens=300,
363
+ temperature=0.8,
364
+ )
365
+
366
+ # Extract questions
367
+ if hasattr(response, 'choices') and len(response.choices) > 0:
368
+ questions_text = response.choices[0].message.content.strip()
369
+ elif isinstance(response, dict) and 'choices' in response:
370
+ questions_text = response['choices'][0]['message']['content'].strip()
371
+ else:
372
+ questions_text = str(response).strip()
373
+
374
+ # Clean up reasoning if present
375
+ questions_text = re.sub(r'<think>.*?</think>', '', questions_text, flags=re.DOTALL)
376
+
377
+ # Parse questions
378
+ questions = [q.strip() for q in questions_text.split('\n') if q.strip()]
379
+ # Remove numbering if present
380
+ questions = [re.sub(r'^\d+[\.\)]\s*', '', q) for q in questions]
381
+ # Filter out empty or very short questions
382
+ questions = [q for q in questions if len(q) > 10]
383
+
384
+ return questions[:num_questions] if questions else self._default_questions()
385
+
386
+ except Exception as e:
387
+ print(f"Error generating questions: {e}")
388
+ return self._default_questions()
389
+
390
+ def _default_questions(self) -> List[str]:
391
+ """Return default questions if generation fails"""
392
+ return [
393
+ "What is the main topic discussed in this corpus?",
394
+ "Can you summarize the key concepts?",
395
+ "What are the main findings or arguments presented?",
396
+ ]