Shreyas094 commited on
Commit
a2c11b9
1 Parent(s): 05d00f6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +191 -49
app.py CHANGED
@@ -71,12 +71,12 @@ def fetch_custom_models():
71
  if not CUSTOM_LLM:
72
  return []
73
  try:
74
- response = requests.get(f"{CUSTOM_LLM}/v1/models")
75
  response.raise_for_status()
76
- models = response.json().get("data", [])
77
- return [model["id"] for model in models]
78
  except Exception as e:
79
- logger.error(f"Error fetching custom models: {e}")
80
  return []
81
 
82
  # Fetch custom models and determine the default model
@@ -84,7 +84,7 @@ custom_models = fetch_custom_models()
84
  all_models = ["huggingface", "groq", "mistral"] + custom_models
85
 
86
  # Determine the default model
87
- default_model = CUSTOM_LLM_DEFAULT_MODEL if CUSTOM_LLM_DEFAULT_MODEL in all_models else "huggingface"
88
 
89
  logger.info(f"Default model selected: {default_model}")
90
 
@@ -157,23 +157,44 @@ class MistralModel(AIModel):
157
  class CustomModel(AIModel):
158
  def __init__(self, model_name):
159
  self.model_name = model_name
 
160
 
161
  def generate_response(self, messages: List[Dict[str, str]], max_tokens: int, temperature: float) -> str:
162
  try:
 
 
 
 
 
 
163
  response = requests.post(
164
- f"{CUSTOM_LLM}/v1/chat/completions",
165
  json={
166
  "model": self.model_name,
167
- "messages": messages,
168
- "max_tokens": max_tokens,
169
- "temperature": temperature
 
 
170
  }
171
  )
172
  response.raise_for_status()
173
- return response.json()["choices"][0]["message"]["content"].strip()
 
 
 
 
 
 
 
 
 
 
 
 
174
  except Exception as e:
175
- logger.error(f"Error generating response from custom model: {e}")
176
- return "Error: Unable to generate response from custom model."
177
 
178
  class AIModelFactory:
179
  @staticmethod
@@ -357,7 +378,7 @@ def scrape_with_newspaper(url):
357
  logger.error(f"Error scraping {url} with Newspaper3k: {e}")
358
  return ""
359
 
360
- def rephrase_query(chat_history, query, temperature=0.2):
361
  system_prompt = """You are a highly intelligent and context-aware conversational assistant. Your tasks are as follows:
362
 
363
  1. Determine if the new query is a continuation of the previous conversation or an entirely new topic.
@@ -425,14 +446,49 @@ Rephrased query:"""
425
  ]
426
 
427
  try:
428
- logger.info(f"Sending rephrasing request to LLM with temperature {temperature}")
429
- response = client.chat_completion(
430
- messages=messages,
431
- max_tokens=150,
432
- temperature=temperature
433
- )
434
- logger.info("Received rephrased query from LLM")
435
- rephrased_question = response.choices[0].message.content.strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
 
437
  # Remove surrounding quotes if present
438
  if (rephrased_question.startswith('"') and rephrased_question.endswith('"')) or \
@@ -441,8 +497,9 @@ Rephrased query:"""
441
 
442
  logger.info(f"Rephrased Query (cleaned): {rephrased_question}")
443
  return rephrased_question
 
444
  except Exception as e:
445
- logger.error(f"Error rephrasing query with LLM: {e}")
446
  return query # Fallback to original query if rephrasing fails
447
 
448
  class BM25:
@@ -528,13 +585,33 @@ def prepare_documents_for_bm25(documents: List[Dict]) -> Tuple[List[str], List[D
528
  Tuple of (document texts, original documents)
529
  """
530
  doc_texts = []
 
 
531
  for doc in documents:
532
- # Combine title and content for better matching
533
- doc_text = f"{doc['title']} {doc['content']}"
534
- doc_texts.append(doc_text)
535
- return doc_texts, documents
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536
 
537
- # Now modify the rerank_documents_with_priority function to include BM25 ranking
538
  def rerank_documents(query: str, documents: List[Dict],
539
  similarity_threshold: float = 0.95, max_results: int = 5) -> List[Dict]:
540
  try:
@@ -542,9 +619,24 @@ def rerank_documents(query: str, documents: List[Dict],
542
  logger.warning("No documents to rerank.")
543
  return documents
544
 
 
 
 
 
545
  # Step 1: Prepare documents for BM25
546
- doc_texts, original_docs = prepare_documents_for_bm25(documents)
547
 
 
 
 
 
 
 
 
 
 
 
 
548
  # Step 2: Initialize and fit BM25
549
  bm25 = BM25()
550
  bm25.fit(doc_texts)
@@ -554,19 +646,26 @@ def rerank_documents(query: str, documents: List[Dict],
554
 
555
  # Step 4: Get semantic similarity scores
556
  query_embedding = similarity_model.encode(query, convert_to_tensor=True)
557
- doc_summaries = [doc['summary'] for doc in documents]
558
  doc_embeddings = similarity_model.encode(doc_summaries, convert_to_tensor=True)
559
  semantic_scores = util.cos_sim(query_embedding, doc_embeddings)[0]
560
 
561
  # Step 5: Combine scores (normalize first)
562
- bm25_scores_norm = (bm25_scores - np.min(bm25_scores)) / (np.max(bm25_scores) - np.min(bm25_scores))
563
- semantic_scores_norm = (semantic_scores - torch.min(semantic_scores)) / (torch.max(semantic_scores) - torch.min(semantic_scores))
 
 
 
 
 
 
 
564
 
565
  # Combine scores with weights (0.4 for BM25, 0.6 for semantic similarity)
566
  combined_scores = 0.4 * bm25_scores_norm + 0.6 * semantic_scores_norm.numpy()
567
 
568
  # Create scored documents with combined scores
569
- scored_documents = list(zip(documents, combined_scores))
570
 
571
  # Sort by combined score (descending)
572
  scored_documents.sort(key=lambda x: x[1], reverse=True)
@@ -621,7 +720,7 @@ def is_content_unique(new_content, existing_contents, similarity_threshold=0.8):
621
  return False
622
  return True
623
 
624
- def assess_relevance_and_summarize(llm_client, query, document, temperature=0.2):
625
  system_prompt = """You are a world-class AI assistant specializing in news analysis and document summarization. Your task is to provide a comprehensive and detailed summary of the given document that captures its key points and relevance to the user's query."""
626
 
627
  user_prompt = f"""
@@ -658,23 +757,57 @@ Remember to:
658
  ]
659
 
660
  try:
661
- response = llm_client.chat_completion(
662
- messages=messages,
663
- max_tokens=300,
664
- temperature=temperature,
665
- top_p=0.9,
666
- frequency_penalty=1.4
667
- )
668
- summary = response.choices[0].message.content.strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
669
 
670
- # If the summary starts with "Summary: ", remove it
671
  if summary.startswith("Summary: "):
672
  summary = summary[9:].strip()
673
 
674
- # Always return format as if document was relevant
675
  return f"Relevant: Yes\nSummary: {summary}"
 
676
  except Exception as e:
677
- logger.error(f"Error summarizing with LLM: {e}")
 
678
  return f"Relevant: Yes\nSummary: Error occurred while summarizing the document: {str(e)}"
679
 
680
  def scrape_full_content(url, max_chars=3000, timeout=5, use_pydf2=True):
@@ -747,6 +880,15 @@ Instructions:
747
  stream=False
748
  )
749
  return response.choices[0].message.content.strip()
 
 
 
 
 
 
 
 
 
750
  else: # huggingface
751
  response = client.chat_completion(
752
  messages=messages,
@@ -779,7 +921,7 @@ def search_and_scrape(
779
  ):
780
  try:
781
  # Step 1: Rephrase the Query
782
- rephrased_query = rephrase_query(chat_history, query, temperature=llm_temperature)
783
  logger.info(f"Rephrased Query: {rephrased_query}")
784
 
785
  if not rephrased_query or rephrased_query.lower() == "not_needed":
@@ -896,7 +1038,7 @@ def search_and_scrape(
896
  relevant_documents = []
897
  unique_summaries = []
898
  for doc in scraped_content:
899
- assessment = assess_relevance_and_summarize(client, rephrased_query, doc, temperature=llm_temperature)
900
  relevance, summary = assessment.split('\n', 1)
901
 
902
  if relevance.strip().lower() == "relevant: yes":
@@ -1011,8 +1153,8 @@ iface = gr.ChatInterface(
1011
  gr.Checkbox(label="Only do web search", value=True), # Add this line
1012
  gr.Slider(5, 20, value=3, step=1, label="Number of initial results"),
1013
  gr.Slider(500, 10000, value=1500, step=100, label="Max characters to retrieve"),
1014
- gr.Dropdown(["", "day", "week", "month", "year"], value="", label="Time Range"),
1015
- gr.Dropdown(["", "all", "en", "fr", "de", "es", "it", "nl", "pt", "pl", "ru", "zh"], value="", label="Language"),
1016
  gr.Dropdown(["", "general", "news", "images", "videos", "music", "files", "it", "science", "social media"], value="general", label="Category"),
1017
  gr.Dropdown(
1018
  ["google", "bing", "duckduckgo", "baidu", "yahoo", "qwant", "startpage"],
@@ -1040,4 +1182,4 @@ iface = gr.ChatInterface(
1040
 
1041
  if __name__ == "__main__":
1042
  logger.info("Starting the SearXNG Scraper for News using ChatInterface with Advanced Parameters")
1043
- iface.launch(share=True)
 
71
  if not CUSTOM_LLM:
72
  return []
73
  try:
74
+ response = requests.get(f"{CUSTOM_LLM}/api/tags") # Ollama endpoint for listing models
75
  response.raise_for_status()
76
+ models = response.json().get("models", [])
77
+ return [model["name"] for model in models] # Ollama returns model names directly
78
  except Exception as e:
79
+ logger.error(f"Error fetching Ollama models: {e}")
80
  return []
81
 
82
  # Fetch custom models and determine the default model
 
84
  all_models = ["huggingface", "groq", "mistral"] + custom_models
85
 
86
  # Determine the default model
87
+ default_model = CUSTOM_LLM_DEFAULT_MODEL if CUSTOM_LLM_DEFAULT_MODEL in all_models else "groq"
88
 
89
  logger.info(f"Default model selected: {default_model}")
90
 
 
157
  class CustomModel(AIModel):
158
  def __init__(self, model_name):
159
  self.model_name = model_name
160
+ self.base_url = os.getenv("CUSTOM_LLM", "http://localhost:11434")
161
 
162
  def generate_response(self, messages: List[Dict[str, str]], max_tokens: int, temperature: float) -> str:
163
  try:
164
+ # Convert messages to Ollama format
165
+ prompt = "\n".join([
166
+ f"{msg['role'].capitalize()}: {msg['content']}"
167
+ for msg in messages
168
+ ])
169
+
170
  response = requests.post(
171
+ f"{self.base_url}/api/generate", # Ollama endpoint
172
  json={
173
  "model": self.model_name,
174
+ "prompt": prompt,
175
+ "options": {
176
+ "num_predict": max_tokens,
177
+ "temperature": temperature
178
+ }
179
  }
180
  )
181
  response.raise_for_status()
182
+
183
+ # Handle Ollama's streaming response
184
+ full_response = ""
185
+ for line in response.iter_lines():
186
+ if line:
187
+ chunk = json.loads(line)
188
+ if 'response' in chunk:
189
+ full_response += chunk['response']
190
+ if chunk.get('done', False):
191
+ break
192
+
193
+ return full_response.strip()
194
+
195
  except Exception as e:
196
+ logger.error(f"Error generating response from Ollama model: {e}")
197
+ return f"Error: Unable to generate response from Ollama model. {str(e)}"
198
 
199
  class AIModelFactory:
200
  @staticmethod
 
378
  logger.error(f"Error scraping {url} with Newspaper3k: {e}")
379
  return ""
380
 
381
+ def rephrase_query(chat_history, query, model, temperature=0.2) -> str:
382
  system_prompt = """You are a highly intelligent and context-aware conversational assistant. Your tasks are as follows:
383
 
384
  1. Determine if the new query is a continuation of the previous conversation or an entirely new topic.
 
446
  ]
447
 
448
  try:
449
+ logger.info(f"Sending rephrasing request to {model} with temperature {temperature}")
450
+
451
+ if model == "groq":
452
+ response = groq_client.chat.completions.create(
453
+ messages=messages,
454
+ model="llama-3.1-70b-versatile",
455
+ max_tokens=150,
456
+ temperature=temperature,
457
+ top_p=0.9,
458
+ presence_penalty=1.2,
459
+ stream=False
460
+ )
461
+ rephrased_question = response.choices[0].message.content.strip()
462
+
463
+ elif model == "mistral":
464
+ response = mistral_client.chat.complete(
465
+ model="open-mistral-nemo",
466
+ messages=messages,
467
+ max_tokens=150,
468
+ temperature=temperature,
469
+ top_p=0.9,
470
+ stream=False
471
+ )
472
+ rephrased_question = response.choices[0].message.content.strip()
473
+
474
+ elif CUSTOM_LLM and model in fetch_custom_models():
475
+ # Create CustomModel instance for Ollama
476
+ custom_model = CustomModel(model)
477
+ rephrased_question = custom_model.generate_response(
478
+ messages=messages,
479
+ max_tokens=150,
480
+ temperature=temperature
481
+ )
482
+
483
+ else: # huggingface
484
+ response = client.chat_completion(
485
+ messages=messages,
486
+ max_tokens=150,
487
+ temperature=temperature,
488
+ frequency_penalty=1.4,
489
+ top_p=0.9
490
+ )
491
+ rephrased_question = response.choices[0].message.content.strip()
492
 
493
  # Remove surrounding quotes if present
494
  if (rephrased_question.startswith('"') and rephrased_question.endswith('"')) or \
 
497
 
498
  logger.info(f"Rephrased Query (cleaned): {rephrased_question}")
499
  return rephrased_question
500
+
501
  except Exception as e:
502
+ logger.error(f"Error rephrasing query with {model} LLM: {e}")
503
  return query # Fallback to original query if rephrasing fails
504
 
505
  class BM25:
 
585
  Tuple of (document texts, original documents)
586
  """
587
  doc_texts = []
588
+ valid_documents = []
589
+
590
  for doc in documents:
591
+ try:
592
+ # Get title and content with default empty strings if missing
593
+ title = doc.get('title', '')
594
+ content = doc.get('content', '')
595
+
596
+ # Skip documents with no content and title
597
+ if not (title.strip() or content.strip()):
598
+ logger.warning(f"Skipping document with no title or content: {doc}")
599
+ continue
600
+
601
+ # Combine title and content for better matching
602
+ doc_text = f"{title} {content}".strip()
603
+ doc_texts.append(doc_text)
604
+ valid_documents.append(doc)
605
+
606
+ except Exception as e:
607
+ logger.warning(f"Error processing document {doc}: {e}")
608
+ continue
609
+
610
+ if not valid_documents:
611
+ raise ValueError("No valid documents found with required fields")
612
+
613
+ return doc_texts, valid_documents
614
 
 
615
  def rerank_documents(query: str, documents: List[Dict],
616
  similarity_threshold: float = 0.95, max_results: int = 5) -> List[Dict]:
617
  try:
 
619
  logger.warning("No documents to rerank.")
620
  return documents
621
 
622
+ # Validate input documents
623
+ if not all(isinstance(doc, dict) for doc in documents):
624
+ raise ValueError("All documents must be dictionaries")
625
+
626
  # Step 1: Prepare documents for BM25
627
+ doc_texts, valid_docs = prepare_documents_for_bm25(documents)
628
 
629
+ if not valid_docs:
630
+ logger.warning("No valid documents after preparation.")
631
+ return documents[:max_results]
632
+
633
+ # Verify all documents have summaries for semantic scoring
634
+ valid_docs = [doc for doc in valid_docs if 'summary' in doc and doc['summary'].strip()]
635
+
636
+ if not valid_docs:
637
+ logger.warning("No documents with valid summaries found.")
638
+ return documents[:max_results]
639
+
640
  # Step 2: Initialize and fit BM25
641
  bm25 = BM25()
642
  bm25.fit(doc_texts)
 
646
 
647
  # Step 4: Get semantic similarity scores
648
  query_embedding = similarity_model.encode(query, convert_to_tensor=True)
649
+ doc_summaries = [doc['summary'] for doc in valid_docs]
650
  doc_embeddings = similarity_model.encode(doc_summaries, convert_to_tensor=True)
651
  semantic_scores = util.cos_sim(query_embedding, doc_embeddings)[0]
652
 
653
  # Step 5: Combine scores (normalize first)
654
+ if len(bm25_scores) > 1:
655
+ bm25_scores_norm = (bm25_scores - np.min(bm25_scores)) / (np.max(bm25_scores) - np.min(bm25_scores))
656
+ else:
657
+ bm25_scores_norm = bm25_scores
658
+
659
+ if len(semantic_scores) > 1:
660
+ semantic_scores_norm = (semantic_scores - torch.min(semantic_scores)) / (torch.max(semantic_scores) - torch.min(semantic_scores))
661
+ else:
662
+ semantic_scores_norm = semantic_scores
663
 
664
  # Combine scores with weights (0.4 for BM25, 0.6 for semantic similarity)
665
  combined_scores = 0.4 * bm25_scores_norm + 0.6 * semantic_scores_norm.numpy()
666
 
667
  # Create scored documents with combined scores
668
+ scored_documents = list(zip(valid_docs, combined_scores))
669
 
670
  # Sort by combined score (descending)
671
  scored_documents.sort(key=lambda x: x[1], reverse=True)
 
720
  return False
721
  return True
722
 
723
+ def assess_relevance_and_summarize(llm_client, query, document, model, temperature=0.2) -> str:
724
  system_prompt = """You are a world-class AI assistant specializing in news analysis and document summarization. Your task is to provide a comprehensive and detailed summary of the given document that captures its key points and relevance to the user's query."""
725
 
726
  user_prompt = f"""
 
757
  ]
758
 
759
  try:
760
+ if model == "groq":
761
+ response = groq_client.chat.completions.create(
762
+ messages=messages,
763
+ model="llama-3.1-70b-versatile",
764
+ max_tokens=500,
765
+ temperature=temperature,
766
+ top_p=0.9,
767
+ presence_penalty=1.2,
768
+ stream=False
769
+ )
770
+ summary = response.choices[0].message.content.strip()
771
+
772
+ elif model == "mistral":
773
+ response = mistral_client.chat.complete(
774
+ model="open-mistral-nemo",
775
+ messages=messages,
776
+ max_tokens=500,
777
+ temperature=temperature,
778
+ top_p=0.9,
779
+ stream=False
780
+ )
781
+ summary = response.choices[0].message.content.strip()
782
+
783
+ elif CUSTOM_LLM and model in fetch_custom_models():
784
+ # Create CustomModel instance for Ollama
785
+ custom_model = CustomModel(model)
786
+ summary = custom_model.generate_response(
787
+ messages=messages,
788
+ max_tokens=500,
789
+ temperature=temperature
790
+ )
791
+
792
+ else: # huggingface
793
+ response = client.chat_completion(
794
+ messages=messages,
795
+ max_tokens=500,
796
+ temperature=temperature,
797
+ frequency_penalty=1.4,
798
+ top_p=0.9
799
+ )
800
+ summary = response.choices[0].message.content.strip()
801
 
802
+ # Clean up the summary if needed
803
  if summary.startswith("Summary: "):
804
  summary = summary[9:].strip()
805
 
 
806
  return f"Relevant: Yes\nSummary: {summary}"
807
+
808
  except Exception as e:
809
+ error_msg = f"Error summarizing with {model} LLM: {str(e)}"
810
+ logger.error(error_msg)
811
  return f"Relevant: Yes\nSummary: Error occurred while summarizing the document: {str(e)}"
812
 
813
  def scrape_full_content(url, max_chars=3000, timeout=5, use_pydf2=True):
 
880
  stream=False
881
  )
882
  return response.choices[0].message.content.strip()
883
+ elif CUSTOM_LLM and model in fetch_custom_models():
884
+ # Create CustomModel instance for Ollama
885
+ custom_model = CustomModel(model)
886
+ response = custom_model.generate_response(
887
+ messages=messages,
888
+ max_tokens=5000,
889
+ temperature=temperature
890
+ )
891
+ return response
892
  else: # huggingface
893
  response = client.chat_completion(
894
  messages=messages,
 
921
  ):
922
  try:
923
  # Step 1: Rephrase the Query
924
+ rephrased_query = rephrase_query(chat_history, query, model, temperature=llm_temperature)
925
  logger.info(f"Rephrased Query: {rephrased_query}")
926
 
927
  if not rephrased_query or rephrased_query.lower() == "not_needed":
 
1038
  relevant_documents = []
1039
  unique_summaries = []
1040
  for doc in scraped_content:
1041
+ assessment = assess_relevance_and_summarize(client, rephrased_query, doc, model, temperature=llm_temperature)
1042
  relevance, summary = assessment.split('\n', 1)
1043
 
1044
  if relevance.strip().lower() == "relevant: yes":
 
1153
  gr.Checkbox(label="Only do web search", value=True), # Add this line
1154
  gr.Slider(5, 20, value=3, step=1, label="Number of initial results"),
1155
  gr.Slider(500, 10000, value=1500, step=100, label="Max characters to retrieve"),
1156
+ gr.Dropdown(["", "day", "week", "month", "year"], value="week", label="Time Range"),
1157
+ gr.Dropdown(["", "all", "en", "fr", "de", "es", "it", "nl", "pt", "pl", "ru", "zh"], value="en", label="Language"),
1158
  gr.Dropdown(["", "general", "news", "images", "videos", "music", "files", "it", "science", "social media"], value="general", label="Category"),
1159
  gr.Dropdown(
1160
  ["google", "bing", "duckduckgo", "baidu", "yahoo", "qwant", "startpage"],
 
1182
 
1183
  if __name__ == "__main__":
1184
  logger.info("Starting the SearXNG Scraper for News using ChatInterface with Advanced Parameters")
1185
+ iface.launch(server_name="0.0.0.0", server_port=7862, share=False)