pradeepsengarr commited on
Commit
d7bf74b
Β·
verified Β·
1 Parent(s): 38c113a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +246 -173
app.py CHANGED
@@ -8,6 +8,7 @@ import PyPDF2
8
  import docx
9
  import io
10
  import os
 
11
  from typing import List, Optional
12
 
13
  class DocumentRAG:
@@ -25,6 +26,7 @@ class DocumentRAG:
25
  self.documents = []
26
  self.index = None
27
  self.is_indexed = False
 
28
 
29
  def setup_llm(self):
30
  """Setup quantized Mistral model"""
@@ -91,79 +93,142 @@ class DocumentRAG:
91
  self.tokenizer = None
92
  print("⚠️ Using context-only mode (no text generation)")
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  def simple_context_answer(self, query: str, context: str) -> str:
95
- """Improved context-based answering when model is not available"""
96
  if not context:
97
  return "No relevant information found in the documents."
98
 
99
  query_lower = query.lower()
100
 
 
 
 
101
  # Handle "who is" questions specifically
102
  if "who is" in query_lower:
103
- # Extract name from query
104
- name_part = query_lower.replace("who is", "").strip()
105
 
106
- # Look for professional information in context
107
- lines = context.split('\n')
108
- name_info = []
109
- professional_info = []
110
 
111
- for line in lines:
112
- line = line.strip()
113
- if not line or line.startswith('---'):
114
- continue
115
-
116
- line_lower = line.lower()
117
-
118
- # Look for job titles, companies, roles
119
- if any(keyword in line_lower for keyword in [
120
- 'scientist', 'engineer', 'analyst', 'developer', 'manager',
121
- 'consultant', 'specialist', 'coordinator', 'associate', 'intern',
122
- 'at ', 'working at', 'employed', 'position', 'role'
123
- ]):
124
- professional_info.append(line)
125
-
126
- # Look for name and basic info
127
- elif any(keyword in line_lower for keyword in [
128
- 'name', 'email', 'phone', 'linkedin', 'github', 'experience'
129
- ]):
130
- name_info.append(line)
131
-
132
- # Construct answer
133
- if professional_info:
134
- answer = f"Based on the resume, {name_part} is " + professional_info[0]
135
- if len(professional_info) > 1:
136
- answer += f" and also {professional_info[1]}"
137
- return answer
138
- elif name_info:
139
- return f"The document shows information about {name_part}: " + "; ".join(name_info[:2])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
- # For other questions, use improved keyword matching
142
  query_words = set(query_lower.split())
143
- context_sentences = context.split('.')
 
 
 
 
144
 
145
- # Find sentences that contain query keywords
146
- relevant_sentences = []
147
  for sentence in context_sentences:
148
- sentence = sentence.strip()
149
- if len(sentence) < 10: # Skip very short sentences
150
  continue
151
-
152
  sentence_words = set(sentence.lower().split())
153
- # Check if sentence contains query keywords
154
- common_words = query_words.intersection(sentence_words)
155
- if len(common_words) >= 1: # Lowered threshold
156
- relevant_sentences.append(sentence)
157
-
158
- if relevant_sentences:
159
- # Return the most relevant sentences
160
- return '. '.join(relevant_sentences[:2]) + '.'
161
- else:
162
- # If no exact matches, return first few sentences of context
163
- first_sentences = context_sentences[:2]
164
- if first_sentences:
165
- return '. '.join([s.strip() for s in first_sentences if s.strip()]) + '.'
166
- return "Based on the document content, I found some information but cannot provide a specific answer to your question."
167
 
168
  def extract_text_from_file(self, file_path: str) -> str:
169
  """Extract text from various file formats"""
@@ -217,36 +282,61 @@ class DocumentRAG:
217
  except Exception as e2:
218
  return f"Error reading TXT: {str(e2)}"
219
 
220
- def chunk_text(self, text: str, chunk_size: int = 200, overlap: int = 30) -> List[str]:
221
- """Split text into overlapping chunks with better sentence preservation"""
222
  if not text.strip():
223
  return []
224
 
225
- # Split by sentences first, then group into chunks
226
- sentences = text.replace('\n', ' ').split('. ')
227
  chunks = []
 
 
 
228
  current_chunk = ""
 
229
 
230
- for sentence in sentences:
231
- sentence = sentence.strip()
232
- if not sentence:
233
  continue
234
-
235
- # Add sentence to current chunk
236
- test_chunk = current_chunk + ". " + sentence if current_chunk else sentence
237
 
238
- # If chunk gets too long, save it and start new one
239
- if len(test_chunk.split()) > chunk_size:
240
- if current_chunk:
241
- chunks.append(current_chunk.strip())
242
- current_chunk = sentence
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  else:
244
- current_chunk = test_chunk
 
 
 
 
 
 
 
 
 
 
 
245
 
246
  # Add the last chunk
247
  if current_chunk:
248
  chunks.append(current_chunk.strip())
249
-
250
  return chunks
251
 
252
  def process_documents(self, files) -> str:
@@ -273,8 +363,11 @@ class DocumentRAG:
273
  if not all_text.strip():
274
  return "❌ No text extracted from files!"
275
 
276
- # Chunk the text
277
- self.documents = self.chunk_text(all_text)
 
 
 
278
 
279
  if not self.documents:
280
  return "❌ No valid text chunks created!"
@@ -301,8 +394,8 @@ class DocumentRAG:
301
  except Exception as e:
302
  return f"❌ Error processing documents: {str(e)}"
303
 
304
- def retrieve_context(self, query: str, k: int = 5) -> str:
305
- """Retrieve relevant context for the query with improved retrieval"""
306
  if not self.is_indexed:
307
  return ""
308
 
@@ -312,23 +405,33 @@ class DocumentRAG:
312
  faiss.normalize_L2(query_embedding)
313
 
314
  # Search for similar chunks
315
- scores, indices = self.index.search(query_embedding.astype('float32'), k)
316
 
317
- # Get relevant documents with MUCH LOWER threshold
318
  relevant_docs = []
319
- for i, idx in enumerate(indices[0]):
320
- if idx < len(self.documents) and scores[0][i] > 0.05: # Much lower threshold
321
- relevant_docs.append(self.documents[idx])
322
-
323
- # If no high-similarity matches, take the top results anyway
324
- if not relevant_docs:
325
- for i, idx in enumerate(indices[0]):
326
- if idx < len(self.documents):
327
- relevant_docs.append(self.documents[idx])
328
- if len(relevant_docs) >= 3: # Take at least 3 chunks
329
- break
330
 
331
- return "\n\n".join(relevant_docs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
 
333
  except Exception as e:
334
  print(f"Error in retrieval: {e}")
@@ -345,33 +448,27 @@ class DocumentRAG:
345
  is_mistral = 'mistral' in model_name
346
 
347
  if is_mistral:
348
- # Improved prompt for Mistral with specific instructions
349
- prompt = f"""<s>[INST] You are a helpful assistant that answers questions about people based on their resume/document information.
350
 
351
- Answer the question clearly and concisely. For "who is" questions, provide a brief professional summary.
352
-
353
- Context from document:
354
- {context[:1500]}
355
 
356
  Question: {query}
357
 
358
- Provide a clear, direct answer in 1-2 sentences. [/INST]"""
359
  else:
360
- # Improved prompt for fallback models
361
- prompt = f"""Answer the question about the person based on their resume information:
362
-
363
- Resume Information:
364
- {context[:1000]}
365
 
366
  Question: {query}
 
367
 
368
- Answer (be direct and concise):"""
369
-
370
- # Tokenize with proper handling
371
  inputs = self.tokenizer(
372
  prompt,
373
  return_tensors="pt",
374
- max_length=800,
375
  truncation=True,
376
  padding=True
377
  )
@@ -380,17 +477,16 @@ Answer (be direct and concise):"""
380
  if torch.cuda.is_available() and next(self.model.parameters()).is_cuda:
381
  inputs = {k: v.cuda() for k, v in inputs.items()}
382
 
383
- # Generate with more focused parameters
384
  with torch.no_grad():
385
  outputs = self.model.generate(
386
  **inputs,
387
- max_new_tokens=100, # Shorter for more focused answers
388
- temperature=0.2, # Lower for more deterministic responses
389
  do_sample=True,
390
- top_p=0.8,
391
- num_beams=3,
392
  early_stopping=True,
393
- repetition_penalty=1.2,
394
  pad_token_id=self.tokenizer.pad_token_id,
395
  eos_token_id=self.tokenizer.eos_token_id
396
  )
@@ -398,72 +494,50 @@ Answer (be direct and concise):"""
398
  # Decode response
399
  full_response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
400
 
401
- # Extract answer based on model type
402
  if is_mistral and "[/INST]" in full_response:
403
  answer = full_response.split("[/INST]")[-1].strip()
404
  else:
405
- # For other models, remove the prompt
406
- if "Answer (be direct and concise):" in full_response:
407
- answer = full_response.split("Answer (be direct and concise):")[-1].strip()
408
- elif "Answer:" in full_response:
409
- answer = full_response.split("Answer:")[-1].strip()
410
- else:
411
- answer = full_response[len(prompt):].strip()
412
 
413
- # Clean up the answer
414
  answer = self.clean_answer(answer)
415
 
416
- return answer if answer else self.simple_context_answer(query, context)
 
 
 
 
417
 
418
  except Exception as e:
419
  print(f"Error in generation: {e}")
420
  return self.simple_context_answer(query, context)
421
 
422
  def clean_answer(self, answer: str) -> str:
423
- """Clean up the generated answer with better formatting"""
424
  if not answer or len(answer) < 5:
425
  return ""
426
 
427
- # Remove file markers and cleanup
428
- answer = answer.replace('--- ', '').replace(' ---', '')
429
- answer = answer.replace('.pdf', '').replace('.docx', '').replace('.txt', '')
 
 
430
 
431
- # Split into sentences and clean each
432
- sentences = answer.split('.')
433
- cleaned_sentences = []
434
 
435
- for sentence in sentences:
436
- sentence = sentence.strip()
437
- if not sentence:
438
- continue
439
-
440
- # Skip problematic patterns
441
- if any(pattern in sentence.lower() for pattern in [
442
- 'what are you doing', 'what do you think', 'how are you',
443
- 'i am an ai', 'i cannot', 'i don\'t know', 'linkedin: www',
444
- 'github:', 'email:', 'mobile:', '+91-'
445
- ]):
446
- continue
447
-
448
- # Clean up common formatting issues
449
- sentence = sentence.replace(' ', ' ')
450
- if sentence and len(sentence) > 3:
451
- cleaned_sentences.append(sentence)
452
-
453
- if not cleaned_sentences:
454
- return ""
455
-
456
- # Reconstruct answer
457
- cleaned_answer = '. '.join(cleaned_sentences[:2]) # Limit to 2 sentences
458
-
459
- # Add period if missing
460
- if cleaned_answer and not cleaned_answer.endswith('.'):
461
- cleaned_answer += '.'
462
 
463
- return cleaned_answer.strip()
464
 
465
  def answer_question(self, query: str) -> str:
466
- """Main function to answer questions with improved handling"""
467
  if not query.strip():
468
  return "❓ Please ask a question!"
469
 
@@ -472,19 +546,18 @@ Answer (be direct and concise):"""
472
 
473
  try:
474
  # Retrieve relevant context
475
- context = self.retrieve_context(query, k=7) # Get more chunks
476
 
477
  if not context:
478
- return "πŸ” No relevant information found in the uploaded documents for your question."
479
 
480
  # Generate answer
481
  answer = self.generate_answer(query, context)
482
 
483
- if answer and len(answer) > 10:
484
- return f"πŸ’‘ **Answer:** {answer}\n\nπŸ“„ **Source Context:**\n{context[:300]}..."
485
  else:
486
- # Fallback to simple context display
487
- return f"πŸ“„ **Based on the document content:**\n{context[:500]}..."
488
 
489
  except Exception as e:
490
  return f"❌ Error answering question: {str(e)}"
@@ -532,7 +605,7 @@ def create_interface():
532
  with gr.Column():
533
  question_input = gr.Textbox(
534
  label="Your Question",
535
- placeholder="What would you like to know about your documents?",
536
  lines=3
537
  )
538
  ask_btn = gr.Button("πŸ” Get Answer", variant="primary")
@@ -540,7 +613,7 @@ def create_interface():
540
  with gr.Column():
541
  answer_output = gr.Textbox(
542
  label="Answer",
543
- lines=12,
544
  interactive=False
545
  )
546
 
@@ -553,11 +626,11 @@ def create_interface():
553
  # Example questions
554
  gr.Markdown("""
555
  ### πŸ’‘ Example Questions:
556
- - What is the main topic of the document?
557
- - Can you summarize the key points?
558
- - What are the conclusions mentioned?
559
- - Are there any specific numbers or statistics?
560
- - Who are the main people or organizations mentioned?
561
  """)
562
 
563
  return demo
 
8
  import docx
9
  import io
10
  import os
11
+ import re
12
  from typing import List, Optional
13
 
14
  class DocumentRAG:
 
26
  self.documents = []
27
  self.index = None
28
  self.is_indexed = False
29
+ self.raw_text = "" # Store raw text for fallback
30
 
31
  def setup_llm(self):
32
  """Setup quantized Mistral model"""
 
93
  self.tokenizer = None
94
  print("⚠️ Using context-only mode (no text generation)")
95
 
96
+ def extract_profile_info(self, text: str) -> dict:
97
+ """Extract key profile information from resume text"""
98
+ profile = {
99
+ 'name': '',
100
+ 'role': '',
101
+ 'skills': [],
102
+ 'experience': [],
103
+ 'education': [],
104
+ 'projects': []
105
+ }
106
+
107
+ lines = text.split('\n')
108
+ current_section = None
109
+
110
+ for line in lines:
111
+ line = line.strip()
112
+ if not line:
113
+ continue
114
+
115
+ line_lower = line.lower()
116
+
117
+ # Extract name (usually first meaningful line)
118
+ if not profile['name'] and len(line.split()) <= 4 and not any(char in line for char in ['@', '.com', '+91', 'linkedin']):
119
+ if not any(word in line_lower for word in ['resume', 'cv', 'experience', 'education', 'skills']):
120
+ profile['name'] = line
121
+
122
+ # Look for role/title indicators
123
+ if any(keyword in line_lower for keyword in ['data scientist', 'software engineer', 'developer', 'analyst', 'intern']):
124
+ if 'data scientist' in line_lower:
125
+ profile['role'] = 'Data Scientist'
126
+ elif 'software engineer' in line_lower:
127
+ profile['role'] = 'Software Engineer'
128
+ elif 'developer' in line_lower:
129
+ profile['role'] = 'Developer'
130
+ elif 'analyst' in line_lower:
131
+ profile['role'] = 'Analyst'
132
+
133
+ # Extract skills
134
+ if any(keyword in line_lower for keyword in ['python', 'machine learning', 'react', 'javascript', 'sql']):
135
+ if 'python' in line_lower:
136
+ profile['skills'].append('Python')
137
+ if 'machine learning' in line_lower:
138
+ profile['skills'].append('Machine Learning')
139
+ if 'react' in line_lower:
140
+ profile['skills'].append('React')
141
+ if 'javascript' in line_lower:
142
+ profile['skills'].append('JavaScript')
143
+
144
+ return profile
145
+
146
  def simple_context_answer(self, query: str, context: str) -> str:
147
+ """Improved smart answering based on context analysis"""
148
  if not context:
149
  return "No relevant information found in the documents."
150
 
151
  query_lower = query.lower()
152
 
153
+ # Extract profile information first
154
+ profile = self.extract_profile_info(self.raw_text if self.raw_text else context)
155
+
156
  # Handle "who is" questions specifically
157
  if "who is" in query_lower:
158
+ name_in_query = re.search(r'who is (\w+)', query_lower)
159
+ person_name = name_in_query.group(1) if name_in_query else "this person"
160
 
161
+ # Build answer from profile
162
+ answer_parts = []
 
 
163
 
164
+ if profile['name']:
165
+ if profile['role']:
166
+ answer_parts.append(f"{profile['name']} is a {profile['role']}")
167
+ else:
168
+ # Try to infer role from context
169
+ context_lower = context.lower()
170
+ if 'data scientist' in context_lower or ('python' in context_lower and 'machine learning' in context_lower):
171
+ answer_parts.append(f"{profile['name']} is a Data Scientist")
172
+ elif 'software' in context_lower and 'developer' in context_lower:
173
+ answer_parts.append(f"{profile['name']} is a Software Developer")
174
+ else:
175
+ answer_parts.append(f"{profile['name']} is a professional")
176
+ else:
177
+ # Use name from query
178
+ context_lower = context.lower()
179
+ if 'data scientist' in context_lower or ('python' in context_lower and 'machine learning' in context_lower):
180
+ answer_parts.append(f"{person_name.title()} is a Data Scientist")
181
+ elif 'software' in context_lower and 'developer' in context_lower:
182
+ answer_parts.append(f"{person_name.title()} is a Software Developer")
183
+ else:
184
+ answer_parts.append(f"{person_name.title()} is a professional")
185
+
186
+ # Add key skills if available
187
+ if profile['skills']:
188
+ top_skills = profile['skills'][:3] # Top 3 skills
189
+ answer_parts.append(f"with expertise in {', '.join(top_skills)}")
190
+
191
+ if answer_parts:
192
+ return '. '.join(answer_parts) + '.'
193
+
194
+ # Handle other question types
195
+ elif any(keyword in query_lower for keyword in ['what', 'skills', 'experience', 'work']):
196
+ if 'skills' in query_lower:
197
+ if profile['skills']:
198
+ return f"Key skills include: {', '.join(profile['skills'])}."
199
+ elif 'experience' in query_lower or 'work' in query_lower:
200
+ # Look for experience indicators in context
201
+ exp_lines = []
202
+ for line in context.split('\n'):
203
+ if any(word in line.lower() for word in ['experience', 'worked', 'internship', 'project']):
204
+ exp_lines.append(line.strip())
205
+ if exp_lines:
206
+ return exp_lines[0]
207
 
208
+ # Fallback to keyword matching
209
  query_words = set(query_lower.split())
210
+ context_sentences = [s.strip() for s in context.split('.') if s.strip()]
211
+
212
+ # Find most relevant sentence
213
+ best_sentence = ""
214
+ max_matches = 0
215
 
 
 
216
  for sentence in context_sentences:
217
+ if len(sentence) < 20: # Skip very short sentences
 
218
  continue
219
+
220
  sentence_words = set(sentence.lower().split())
221
+ matches = len(query_words.intersection(sentence_words))
222
+
223
+ if matches > max_matches:
224
+ max_matches = matches
225
+ best_sentence = sentence
226
+
227
+ if best_sentence:
228
+ return best_sentence + '.'
229
+
230
+ # Final fallback
231
+ return "Based on the document, I found relevant information but cannot provide a specific answer."
 
 
 
232
 
233
  def extract_text_from_file(self, file_path: str) -> str:
234
  """Extract text from various file formats"""
 
282
  except Exception as e2:
283
  return f"Error reading TXT: {str(e2)}"
284
 
285
+ def smart_chunk_text(self, text: str) -> List[str]:
286
+ """Smart chunking that preserves important information together"""
287
  if not text.strip():
288
  return []
289
 
 
 
290
  chunks = []
291
+ lines = text.split('\n')
292
+
293
+ # Create chunks based on semantic meaning
294
  current_chunk = ""
295
+ chunk_type = None
296
 
297
+ for line in lines:
298
+ line = line.strip()
299
+ if not line:
300
  continue
 
 
 
301
 
302
+ line_lower = line.lower()
303
+
304
+ # Identify section types
305
+ new_chunk_type = None
306
+ if any(keyword in line_lower for keyword in ['name', 'email', 'phone', 'linkedin', 'github']):
307
+ new_chunk_type = 'contact'
308
+ elif any(keyword in line_lower for keyword in ['experience', 'work', 'internship']):
309
+ new_chunk_type = 'experience'
310
+ elif any(keyword in line_lower for keyword in ['education', 'degree', 'university', 'college']):
311
+ new_chunk_type = 'education'
312
+ elif any(keyword in line_lower for keyword in ['skills', 'technologies', 'programming']):
313
+ new_chunk_type = 'skills'
314
+ elif any(keyword in line_lower for keyword in ['project', 'developed', 'built']):
315
+ new_chunk_type = 'projects'
316
+
317
+ # If section type changes, save current chunk and start new one
318
+ if new_chunk_type != chunk_type and current_chunk:
319
+ chunks.append(current_chunk.strip())
320
+ current_chunk = line
321
+ chunk_type = new_chunk_type
322
  else:
323
+ # Add to current chunk
324
+ if current_chunk:
325
+ current_chunk += "\n" + line
326
+ else:
327
+ current_chunk = line
328
+ chunk_type = new_chunk_type
329
+
330
+ # Limit chunk size
331
+ if len(current_chunk.split()) > 150:
332
+ chunks.append(current_chunk.strip())
333
+ current_chunk = ""
334
+ chunk_type = None
335
 
336
  # Add the last chunk
337
  if current_chunk:
338
  chunks.append(current_chunk.strip())
339
+
340
  return chunks
341
 
342
  def process_documents(self, files) -> str:
 
363
  if not all_text.strip():
364
  return "❌ No text extracted from files!"
365
 
366
+ # Store raw text for smart answering
367
+ self.raw_text = all_text
368
+
369
+ # Smart chunk the text
370
+ self.documents = self.smart_chunk_text(all_text)
371
 
372
  if not self.documents:
373
  return "❌ No valid text chunks created!"
 
394
  except Exception as e:
395
  return f"❌ Error processing documents: {str(e)}"
396
 
397
+ def retrieve_context(self, query: str, k: int = 3) -> str:
398
+ """Retrieve relevant context with improved filtering"""
399
  if not self.is_indexed:
400
  return ""
401
 
 
405
  faiss.normalize_L2(query_embedding)
406
 
407
  # Search for similar chunks
408
+ scores, indices = self.index.search(query_embedding.astype('float32'), min(k, len(self.documents)))
409
 
410
+ # Get relevant documents with reasonable threshold
411
  relevant_docs = []
412
+ query_lower = query.lower()
 
 
 
 
 
 
 
 
 
 
413
 
414
+ for i, idx in enumerate(indices[0]):
415
+ if idx < len(self.documents):
416
+ doc = self.documents[idx]
417
+ score = scores[0][i]
418
+
419
+ # For "who is" questions, prioritize contact/basic info chunks
420
+ if "who is" in query_lower:
421
+ doc_lower = doc.lower()
422
+ if any(keyword in doc_lower for keyword in ['name', 'email', 'linkedin', 'data scientist', 'developer']):
423
+ relevant_docs.insert(0, doc) # Put at beginning
424
+ elif score > 0.15: # Lower threshold for other relevant content
425
+ relevant_docs.append(doc)
426
+ else:
427
+ if score > 0.2: # Standard threshold
428
+ relevant_docs.append(doc)
429
+
430
+ # If no good matches for "who is", get the first few chunks
431
+ if "who is" in query_lower and not relevant_docs:
432
+ relevant_docs = self.documents[:2]
433
+
434
+ return "\n\n".join(relevant_docs[:3]) # Limit to top 3 chunks
435
 
436
  except Exception as e:
437
  print(f"Error in retrieval: {e}")
 
448
  is_mistral = 'mistral' in model_name
449
 
450
  if is_mistral:
451
+ # Focused prompt for Mistral
452
+ prompt = f"""<s>[INST] Answer the question about the person based on their resume. Be concise and direct.
453
 
454
+ Resume Information:
455
+ {context[:800]}
 
 
456
 
457
  Question: {query}
458
 
459
+ Provide a brief, specific answer in 1 sentence. [/INST]"""
460
  else:
461
+ # Focused prompt for fallback models
462
+ prompt = f"""Resume: {context[:600]}
 
 
 
463
 
464
  Question: {query}
465
+ Answer briefly:"""
466
 
467
+ # Tokenize
 
 
468
  inputs = self.tokenizer(
469
  prompt,
470
  return_tensors="pt",
471
+ max_length=600,
472
  truncation=True,
473
  padding=True
474
  )
 
477
  if torch.cuda.is_available() and next(self.model.parameters()).is_cuda:
478
  inputs = {k: v.cuda() for k, v in inputs.items()}
479
 
480
+ # Generate with focused parameters
481
  with torch.no_grad():
482
  outputs = self.model.generate(
483
  **inputs,
484
+ max_new_tokens=50, # Much shorter for focused answers
485
+ temperature=0.1, # Very low for deterministic responses
486
  do_sample=True,
487
+ top_p=0.9,
 
488
  early_stopping=True,
489
+ repetition_penalty=1.1,
490
  pad_token_id=self.tokenizer.pad_token_id,
491
  eos_token_id=self.tokenizer.eos_token_id
492
  )
 
494
  # Decode response
495
  full_response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
496
 
497
+ # Extract answer
498
  if is_mistral and "[/INST]" in full_response:
499
  answer = full_response.split("[/INST]")[-1].strip()
500
  else:
501
+ answer = full_response[len(prompt):].strip()
 
 
 
 
 
 
502
 
503
+ # Clean and validate answer
504
  answer = self.clean_answer(answer)
505
 
506
+ # If answer is too long or poor quality, use fallback
507
+ if not answer or len(answer) > 200:
508
+ return self.simple_context_answer(query, context)
509
+
510
+ return answer
511
 
512
  except Exception as e:
513
  print(f"Error in generation: {e}")
514
  return self.simple_context_answer(query, context)
515
 
516
  def clean_answer(self, answer: str) -> str:
517
+ """Clean up the generated answer"""
518
  if not answer or len(answer) < 5:
519
  return ""
520
 
521
+ # Remove unwanted patterns
522
+ answer = re.sub(r'--- \w+.*? ---', '', answer)
523
+ answer = re.sub(r'\b\w+@\w+\.\w+\b', '', answer) # Remove emails
524
+ answer = re.sub(r'\+91-?\d+', '', answer) # Remove phone numbers
525
+ answer = answer.replace('LinkedIn:', '').replace('Github:', '')
526
 
527
+ # Clean up whitespace
528
+ answer = ' '.join(answer.split())
 
529
 
530
+ # Take only the first sentence if multiple
531
+ sentences = answer.split('.')
532
+ if sentences:
533
+ first_sentence = sentences[0].strip()
534
+ if len(first_sentence) > 10:
535
+ return first_sentence + '.'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536
 
537
+ return answer.strip()
538
 
539
  def answer_question(self, query: str) -> str:
540
+ """Main function to answer questions"""
541
  if not query.strip():
542
  return "❓ Please ask a question!"
543
 
 
546
 
547
  try:
548
  # Retrieve relevant context
549
+ context = self.retrieve_context(query, k=3)
550
 
551
  if not context:
552
+ return "πŸ” No relevant information found in the uploaded documents."
553
 
554
  # Generate answer
555
  answer = self.generate_answer(query, context)
556
 
557
+ if answer and len(answer) > 5:
558
+ return answer
559
  else:
560
+ return "I couldn't generate a specific answer from the document content."
 
561
 
562
  except Exception as e:
563
  return f"❌ Error answering question: {str(e)}"
 
605
  with gr.Column():
606
  question_input = gr.Textbox(
607
  label="Your Question",
608
+ placeholder="Who is Pradeep?",
609
  lines=3
610
  )
611
  ask_btn = gr.Button("πŸ” Get Answer", variant="primary")
 
613
  with gr.Column():
614
  answer_output = gr.Textbox(
615
  label="Answer",
616
+ lines=6,
617
  interactive=False
618
  )
619
 
 
626
  # Example questions
627
  gr.Markdown("""
628
  ### πŸ’‘ Example Questions:
629
+ - Who is [Name]?
630
+ - What are [Name]'s skills?
631
+ - What experience does [Name] have?
632
+ - What projects has [Name] worked on?
633
+ - What is [Name]'s educational background?
634
  """)
635
 
636
  return demo