Reality123b commited on
Commit
53a2696
·
verified ·
1 Parent(s): d2fd4d5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +202 -314
app.py CHANGED
@@ -26,7 +26,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
26
  import tiktoken
27
 
28
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
29
- logger = logging.getLogger(name)
30
 
31
  HF_API_KEY = os.environ.get("HF_API_KEY")
32
  if not HF_API_KEY:
@@ -37,25 +37,20 @@ client = InferenceClient(provider="hf-inference", api_key=HF_API_KEY)
37
  MAIN_LLM_MODEL = "mistralai/Mistral-Nemo-Instruct-2407"
38
  REASONING_LLM_MODEL = "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B"
39
  CRITIC_LLM_MODEL = "Qwen/QwQ-32B-Preview"
40
- SPECIALIST_MODELS = {
41
- "medical": "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B",
42
- "scientific": "mistralai/Mistral-7B-Instruct-v0.2",
43
- "financial": "mistralai/Mistral-7B-Instruct-v0.3",
44
- "legal": "Qwen/Qwen2.5-Coder-32B-Instruct"
45
- }
46
- ENSEMBLE_MODELS = [MAIN_LLM_MODEL, REASONING_LLM_MODEL, CRITIC_LLM_MODEL] + list(SPECIALIST_MODELS.values())
47
-
48
- MAX_ITERATIONS = 100
49
- TIMEOUT = 300
50
- RETRY_DELAY = 15
51
- NUM_RESULTS = 50
52
- SIMILARITY_THRESHOLD = 0.12
53
- MAX_CONTEXT_ITEMS = 100
54
- MAX_HISTORY_ITEMS = 25
55
- MAX_FULL_TEXT_LENGTH = 50000
56
  FAISS_INDEX_PATH = "research_index.faiss"
57
  RESEARCH_DATA_PATH = "research_data.pkl"
58
- PAPER_SUMMARIES_PATH = "paper_summaries.pkl"
 
59
 
60
  try:
61
  main_similarity_model = SentenceTransformer('sentence-transformers/all-mpnet-base-v2')
@@ -67,9 +62,8 @@ try:
67
  index = faiss.read_index(FAISS_INDEX_PATH)
68
  logger.info(f"Loaded FAISS index from {FAISS_INDEX_PATH}")
69
  else:
70
- index = faiss.IndexFlatIP(embedding_dim)
71
  logger.info("Created a new FAISS index.")
72
-
73
  except Exception as e:
74
  logger.error(f"Failed to load models or initialize FAISS: {e}")
75
  raise
@@ -95,8 +89,8 @@ def load_research_data():
95
  try:
96
  with open(RESEARCH_DATA_PATH, "rb") as f:
97
  data = pickle.load(f)
98
- logger.info(f"Loaded research data from {RESEARCH_DATA_PATH}")
99
- return data
100
  except Exception as e:
101
  logger.error(f"Error loading research data: {e}")
102
  return {}
@@ -117,8 +111,8 @@ def load_paper_summaries() -> Dict[str, str]:
117
  try:
118
  with open(PAPER_SUMMARIES_PATH, "rb") as f:
119
  data = pickle.load(f)
120
- logger.info(f"Loaded paper summaries from {PAPER_SUMMARIES_PATH}")
121
- return data
122
  except Exception as e:
123
  logger.error(f"Error loading paper summaries: {e}")
124
  return {}
@@ -126,7 +120,8 @@ def load_paper_summaries() -> Dict[str, str]:
126
  logger.info("No existing paper summaries found.")
127
  return {}
128
 
129
- def hf_inference(model_name, prompt, max_tokens=2000, retries=5, stream=False):
 
130
  for attempt in range(retries):
131
  try:
132
  messages = [{"role": "user", "content": prompt}]
@@ -134,24 +129,25 @@ def hf_inference(model_name, prompt, max_tokens=2000, retries=5, stream=False):
134
  model=model_name,
135
  messages=messages,
136
  max_tokens=max_tokens,
137
- stream=stream
138
  )
139
  if stream:
140
- return response_generator
141
  else:
142
- response = next(response_generator)
 
143
  return {"generated_text": response.choices[0].message.content}
144
  except Exception as e:
145
  if attempt == retries - 1:
146
  logger.error(f"Request failed after {retries} retries: {e}")
147
  return {"error": f"Request failed after {retries} retries: {e}"}
148
  time.sleep(RETRY_DELAY * (1 + attempt))
149
- return {"error": "Request failed after multiple retries."}
150
 
151
- def ensemble_inference(prompt, models=ENSEMBLE_MODELS, max_tokens=1500, stream=False):
152
  results = []
153
 
154
- if stream:
155
  def generate_responses():
156
  with ThreadPoolExecutor(max_workers=len(models)) as executor:
157
  futures = {executor.submit(hf_inference, model, prompt, max_tokens, stream=True): model for model in models}
@@ -159,14 +155,14 @@ def ensemble_inference(prompt, models=ENSEMBLE_MODELS, max_tokens=1500, stream=F
159
  for future in as_completed(futures):
160
  model = future_to_model[future]
161
  try:
162
- for chunk in future.result():
163
- yield {"model": model, "text": chunk.choices[0].delta.content}
164
  except Exception as e:
165
  logger.error(f"Error with model {model}: {e}")
166
  yield {"model": model, "text": f"Error: {e}"}
167
- return generate_responses()
168
 
169
- else:
170
  with ThreadPoolExecutor(max_workers=len(models)) as executor:
171
  future_to_model = {executor.submit(hf_inference, model, prompt, max_tokens, stream=False): model for model in models}
172
  for future in as_completed(future_to_model):
@@ -188,14 +184,14 @@ def ensemble_inference(prompt, models=ENSEMBLE_MODELS, max_tokens=1500, stream=F
188
  for result in results:
189
  synthesis_prompt += f"Expert {results.index(result) + 1} ({result['model'].split('/')[-1]}):\n{result['text']}\n\n"
190
 
191
- synthesis = hf_inference(MAIN_LLM_MODEL, synthesis_prompt)
192
  if "generated_text" in synthesis:
193
  return synthesis
194
  else:
195
- return {"generated_text": max(results, key=lambda x: len(x["text"]))["text"]}
196
 
197
  def tool_search_web(query: str, num_results: int = NUM_RESULTS, safesearch: str = "moderate",
198
- time_filter: Optional[str] = None, region: str = "wt-wt", language: str = "en-us") -> list:
199
  try:
200
  with DDGS() as ddgs:
201
  kwargs = {
@@ -293,7 +289,7 @@ def tool_search_wikipedia(query: str, max_results: int = 3) -> list:
293
  except Exception as e:
294
  logger.error(f"Wikipedia search error: {e}")
295
  return []
296
-
297
  def tool_search_scholar(query: str, max_results: int = 5) -> list:
298
  try:
299
  search_query = scholarly.search_pubs(query)
@@ -330,7 +326,7 @@ def extract_article_content(url: str) -> str:
330
  return ""
331
 
332
  def tool_reason(prompt: str, search_results: list, reasoning_context: list = [],
333
- critique: str = "", focus_areas: list = []) -> str:
334
  if not search_results:
335
  return "No search results to reason about."
336
 
@@ -342,7 +338,7 @@ def tool_reason(prompt: str, search_results: list, reasoning_context: list = [],
342
 
343
  results_by_source = {}
344
  for i, result in enumerate(search_results):
345
- source = result.get('source', 'Web Search')
346
  if source not in results_by_source:
347
  results_by_source[source] = []
348
  results_by_source[source].append((i, result))
@@ -358,7 +354,7 @@ def tool_reason(prompt: str, search_results: list, reasoning_context: list = [],
358
  reasoning_input += "\n"
359
 
360
  if reasoning_context:
361
- recent_context = reasoning_context[-MAX_HISTORY_ITEMS:]
362
  reasoning_input += "\nPrevious Reasoning Context:\n" + "\n".join(recent_context)
363
 
364
  if critique:
@@ -366,7 +362,7 @@ def tool_reason(prompt: str, search_results: list, reasoning_context: list = [],
366
 
367
  reasoning_input += "\nProvide a thorough, nuanced analysis that builds upon previous reasoning if applicable. Consider multiple perspectives, potential contradictions in the search results, and the reliability of different sources. Address any specific critiques."
368
 
369
- reasoning_output = ensemble_inference(reasoning_input)
370
 
371
  if isinstance(reasoning_output, dict) and "generated_text" in reasoning_output:
372
  return reasoning_output["generated_text"].strip()
@@ -380,7 +376,7 @@ def tool_summarize(insights: list, prompt: str, contradictions: list = []) -> st
380
 
381
  summarization_input = f"Synthesize the following insights into a cohesive and comprehensive summary regarding: '{prompt}'\n\n"
382
 
383
- max_tokens = 12000
384
  selected_insights = []
385
  token_count = get_token_count(summarization_input) + get_token_count("\n\n".join(contradictions))
386
 
@@ -408,7 +404,7 @@ def tool_summarize(insights: list, prompt: str, contradictions: list = []) -> st
408
  return "Could not generate a summary due to an error."
409
 
410
  def tool_generate_search_query(prompt: str, previous_queries: list = [],
411
- failed_queries: list = [], focus_areas: list = []) -> str:
412
  query_gen_input = f"Generate an effective search query for the following prompt: {prompt}\n"
413
 
414
  if previous_queries:
@@ -433,7 +429,7 @@ def tool_generate_search_query(prompt: str, previous_queries: list = [],
433
  return ""
434
 
435
  def tool_critique_reasoning(reasoning_output: str, prompt: str,
436
- previous_critiques: list = []) -> str:
437
  critique_input = f"Critically evaluate the following reasoning output in relation to the prompt:\n\nPrompt: {prompt}\n\nReasoning: {reasoning_output}\n\n"
438
 
439
  if previous_critiques:
@@ -441,7 +437,7 @@ def tool_critique_reasoning(reasoning_output: str, prompt: str,
441
 
442
  critique_input += "Identify any flaws, biases, logical fallacies, unsupported claims, or areas for improvement. Be specific and constructive. Suggest concrete ways to enhance the reasoning. Also evaluate the strength of evidence and whether conclusions are proportionate to the available information."
443
 
444
- critique_output = hf_inference(CRITIC_LLM_MODEL, critique_input)
445
 
446
  if isinstance(critique_output, dict) and "generated_text" in critique_output:
447
  return critique_output["generated_text"].strip()
@@ -453,7 +449,7 @@ def tool_identify_contradictions(insights: list) -> list:
453
  if len(insights) < 2:
454
  return []
455
 
456
- max_tokens = 12000
457
  selected_insights = []
458
  token_count = 0
459
 
@@ -468,12 +464,13 @@ def tool_identify_contradictions(insights: list) -> list:
468
  contradiction_input = "Identify specific contradictions in these insights:\n\n" + "\n\n".join(selected_insights)
469
  contradiction_input += "\n\nList each contradiction as a separate numbered point. For each contradiction, cite the specific claims that are in tension and evaluate which claim is better supported. If no contradictions exist, respond with 'No contradictions found.'"
470
 
471
- contradiction_output = hf_inference(CRITIC_LLM_MODEL, contradiction_input)
472
 
473
  if isinstance(contradiction_output, dict) and "generated_text" in contradiction_output:
474
  result = contradiction_output["generated_text"].strip()
475
  if result == "No contradictions found.":
476
  return []
 
477
  contradictions = re.findall(r'\d+\.\s+(.*?)(?=\d+\.|$)', result, re.DOTALL)
478
  return [c.strip() for c in contradictions if c.strip()]
479
 
@@ -481,7 +478,7 @@ def tool_identify_contradictions(insights: list) -> list:
481
  return []
482
 
483
  def tool_identify_focus_areas(prompt: str, insights: list = [],
484
- failed_areas: list = []) -> list:
485
  focus_input = f"Based on this research prompt: '{prompt}'\n\n"
486
 
487
  if insights:
@@ -493,10 +490,11 @@ def tool_identify_focus_areas(prompt: str, insights: list = [],
493
 
494
  focus_input += "Identify 3-5 specific aspects that should be investigated further to get a complete understanding. Be precise and prioritize underexplored areas. For each suggested area, briefly explain why it's important to investigate."
495
 
496
- focus_output = hf_inference(MAIN_LLM_MODEL, focus_input)
497
 
498
  if isinstance(focus_output, dict) and "generated_text" in focus_output:
499
  result = focus_output["generated_text"].strip()
 
500
  areas = re.findall(r'(?:^|\n)(?:\d+\.|\*|\-)\s*(.*?)(?=(?:\n(?:\d+\.|\*|\-|$))|$)', result)
501
  return [area.strip() for area in areas if area.strip()][:5]
502
 
@@ -509,7 +507,7 @@ def add_to_faiss_index(text: str):
509
  if embedding_np.shape[1] != embedding_dim:
510
  logger.error(f"Embedding dimension mismatch: expected {embedding_dim}, got {embedding_np.shape[1]}")
511
  return
512
- faiss.normalize_L2(embedding_np)
513
  index.add(embedding_np)
514
 
515
  def search_faiss_index(query: str, top_k: int = 5) -> List[str]:
@@ -534,7 +532,7 @@ def filter_results(search_results, prompt, previous_snippets=None):
534
  for result in search_results:
535
  combined_text = result['title'] + " " + result['snippet']
536
 
537
- if result['snippet'] in seen_snippets:
538
  continue
539
 
540
  result_embedding = main_similarity_model.encode(combined_text, convert_to_tensor=True)
@@ -543,16 +541,16 @@ def filter_results(search_results, prompt, previous_snippets=None):
543
  if cosine_score >= SIMILARITY_THRESHOLD:
544
  result['relevance_score'] = cosine_score
545
  filtered_results.append(result)
546
- seen_snippets.add(result['snippet'])
547
  add_to_faiss_index(result['snippet'])
548
 
549
 
550
- filtered_results.sort(key=lambda x: x.get('relevance_score', 0), reverse=True)
551
  return filtered_results
552
 
553
  except Exception as e:
554
  logger.error(f"Error during filtering: {e}")
555
- return search_results
556
 
557
  def tool_extract_key_entities(prompt: str) -> list:
558
  entity_input = f"Extract the key entities (people, organizations, concepts, technologies, events, time periods, locations, etc.) from this research prompt that should be investigated individually:\n\n{prompt}\n\nList the 5-7 most important entities, one per line, with a brief explanation (2-3 sentences) of why each is central to the research question."
@@ -562,7 +560,7 @@ def tool_extract_key_entities(prompt: str) -> list:
562
  if isinstance(entity_output, dict) and "generated_text" in entity_output:
563
  result = entity_output["generated_text"].strip()
564
  entities = [e.strip() for e in result.split('\n') if e.strip()]
565
- return entities[:7]
566
 
567
  logger.error(f"Failed to extract key entities: {entity_output}")
568
  return []
@@ -575,11 +573,11 @@ def tool_meta_analyze(entity_insights: Dict[str, list], prompt: str) -> str:
575
 
576
  for entity, insights in entity_insights.items():
577
  if insights:
578
- meta_input += f"\n--- {entity} ---\n" + insights[-1] + "\n"
579
 
580
  meta_input += "\nProvide a high-level synthesis that identifies:\n1. Common themes across entities\n2. Important differences and contradictions\n3. How these entities interact or influence each other\n4. The broader implications for the original research question\n5. A systems-level understanding of how these elements fit together"
581
 
582
- meta_output = ensemble_inference(meta_input)
583
 
584
  if isinstance(meta_output, dict) and "generated_text" in meta_output:
585
  return meta_output["generated_text"].strip()
@@ -604,7 +602,7 @@ def tool_draft_research_plan(prompt: str, entities: list, focus_areas: list = []
604
  plan_input += "5. Potential challenges and how to address them\n"
605
  plan_input += "6. Criteria for evaluating the quality of findings"
606
 
607
- plan_output = hf_inference(REASONING_LLM_MODEL, plan_input)
608
 
609
  if isinstance(plan_output, dict) and "generated_text" in plan_output:
610
  return plan_output["generated_text"].strip()
@@ -616,40 +614,27 @@ def tool_extract_article(url: str) -> str:
616
  extracted_text = extract_article_content(url)
617
  return extracted_text if extracted_text else f"Could not extract content from {url}"
618
 
 
619
  def tool_summarize_paper(paper_text: str) -> str:
620
- summarization_prompt = f"""Summarize this academic paper, focusing on the following:
621
-
622
- Main Research Question(s): What questions does the paper address?
623
-
624
- Methodology: Briefly describe the methods used (e.g., experiments, surveys, simulations, theoretical analysis).
625
 
626
- Key Findings: What are the most important results or conclusions?
 
 
 
 
627
 
628
- Limitations: What are the acknowledged limitations of the study?
629
-
630
- Implications: What are the broader implications of the findings, according to the authors?
631
  Paper Text:
632
  {paper_text[:MAX_FULL_TEXT_LENGTH]}
633
- """
634
- summary = hf_inference(REASONING_LLM_MODEL, summarization_prompt, max_tokens=500)
635
-
636
- if isinstance(summary, dict) and "generated_text" in summary:
637
- return summary["generated_text"].strip()
638
- else:
639
- logger.error(f"Failed to generate summary: {summary}")
640
- return "Could not generate a summary due to an error."
641
 
642
- def tool_search_patents(query: str, max_results: int = 10) -> list:
643
- pass
 
 
 
644
 
645
- def tool_search_clinical_trials(query: str, max_results: int = 10) -> list:
646
- pass
647
-
648
- def tool_search_datasets(query: str, max_results: int = 10) -> list:
649
- pass
650
-
651
- def tool_search_conferences(query: str, max_results: int = 10) -> list:
652
- pass
653
 
654
  tools = {
655
  "search_web": {
@@ -687,7 +672,7 @@ tools = {
687
  "max_results": {"type": "integer", "description": "Maximum number of articles to return."}
688
  },
689
  },
690
- "search_scholar": {
691
  "function": tool_search_scholar,
692
  "description": "Searches Google Scholar for academic publications.",
693
  "parameters": {
@@ -702,7 +687,7 @@ tools = {
702
  "url": {"type": "string", "description": "The URL of the article to extract"}
703
  },
704
  },
705
- "summarize_paper": {
706
  "function": tool_summarize_paper,
707
  "description": "Summarizes the content of an academic paper.",
708
  "parameters": {
@@ -753,7 +738,7 @@ tools = {
753
  "description": "Identifies contradictions across multiple insights.",
754
  "parameters": {
755
  "insights": {"type": "array", "description": "Collection of insights to analyze for contradictions."},
756
- },
757
  },
758
  "identify_focus_areas": {
759
  "function": tool_identify_focus_areas,
@@ -787,38 +772,6 @@ tools = {
787
  "entities": {"type": "array", "description": "Key entities to investigate."},
788
  "focus_areas": {"type": "array", "description": "Additional areas to focus on."}
789
  }
790
- },
791
- "search_patents": {
792
- "function": tool_search_patents,
793
- "description": "Searches patent databases globally",
794
- "parameters": {
795
- "query": {"type": "string", "description": "Patent search query"},
796
- "max_results": {"type": "integer", "description": "Maximum number of patents to return"}
797
- }
798
- },
799
- "search_clinical_trials": {
800
- "function": tool_search_clinical_trials,
801
- "description": "Search ClinicalTrials.gov and WHO ICTRP",
802
- "parameters": {
803
- "query": {"type": "string", "description": "Search query for ClinicalTrials.gov and WHO ICTRP"},
804
- "max_results": {"type": "integer", "description": "Maximum number of results to return"}
805
- }
806
- },
807
- "search_datasets": {
808
- "function": tool_search_datasets,
809
- "description": "Search academic datasets from repositories like Kaggle, UCI, etc.",
810
- "parameters": {
811
- "query": {"type": "string", "description": "Search query for academic datasets"},
812
- "max_results": {"type": "integer", "description": "Maximum number of results to return"}
813
- }
814
- },
815
- "search_conferences": {
816
- "function": tool_search_conferences,
817
- "description": "Search major conference proceedings",
818
- "parameters": {
819
- "query": {"type": "string", "description": "Search query for conference proceedings"},
820
- "max_results": {"type": "integer", "description": "Maximum number of results to return"}
821
- }
822
  }
823
  }
824
 
@@ -845,18 +798,12 @@ Instructions:
845
  Select the BEST tool and parameters for the current research stage. Output valid JSON. If no tool is appropriate, respond with {}.
846
  Only use provided tools. Be strategic about which tool to use next based on the research progress so far.
847
  You MUST be methodical. Think step-by-step:
848
-
849
- Plan: If it's the very beginning, extract key entities, identify focus areas, and then draft a research plan.
850
-
851
- Search: Use a variety of search tools. Start with broad searches, then narrow down. Use specific search tools (arXiv, PubMed, Scholar) for relevant topics.
852
-
853
- Analyze: Reason deeply about search results, and critique your reasoning. Identify contradictions. Filter and use FAISS index for relevant information.
854
-
855
- Refine: If results are poor, generate better search queries. Adjust focus areas.
856
-
857
- Iterate: Repeat steps 2-4, focusing on different entities and aspects.
858
-
859
- Synthesize: Finally, summarize the findings, addressing contradictions.
860
  Example:
861
  {"tool": "search_web", "parameters": {"query": "Eiffel Tower location"}}
862
  Output:
@@ -866,7 +813,7 @@ Output:
866
  def deep_research(prompt):
867
  task_description = "You are an advanced research assistant, designed to be as comprehensive as possible. Use available tools iteratively, focus on different aspects, explore promising leads thoroughly, critically evaluate your findings, and build up a comprehensive understanding of the research topic. Utilize the FAISS index to avoid redundant searches and to build a persistent knowledge base."
868
  research_data = load_research_data()
869
- paper_summaries = load_paper_summaries()
870
 
871
  context = research_data.get('context', [])
872
  all_insights = research_data.get('all_insights', [])
@@ -881,59 +828,62 @@ def deep_research(prompt):
881
  seen_snippets = set(research_data.get('seen_snippets', []))
882
  contradictions = research_data.get('contradictions', [])
883
  research_session_id = research_data.get('research_session_id', str(uuid4()))
884
-
885
  global index
886
  if research_data:
887
- logger.info("Restoring FAISS Index from loaded data.")
888
  else:
889
  index.reset()
890
  logger.info("Initialized a fresh FAISS Index")
891
 
892
  key_entities_with_descriptions = tool_extract_key_entities(prompt=prompt)
893
- key_entities = [e.split(":")[0].strip() for e in key_entities_with_descriptions]
894
  if key_entities:
895
  context.append(f"Identified key entities: {key_entities}")
896
  intermediate_output += f"Identified key entities for focused research: {key_entities_with_descriptions}\n"
897
  yield "Identifying key entities... (Completed)"
898
 
 
899
  entity_progress = {entity: {'queries': [], 'insights': []} for entity in key_entities}
900
- entity_progress['general'] = {'queries': [], 'insights': []}
901
  for entity in key_entities + ['general']:
902
- if entity in research_data:
903
  entity_progress[entity]['queries'] = research_data[entity]['queries']
904
  entity_progress[entity]['insights'] = research_data[entity]['insights']
905
 
906
- if not focus_areas:
907
  initial_focus_areas = tool_identify_focus_areas(prompt=prompt)
908
  yield "Identifying initial focus areas...(Completed)"
909
  research_plan = tool_draft_research_plan(prompt=prompt, entities=key_entities, focus_areas=initial_focus_areas)
910
  yield "Drafting initial research plan...(Completed)"
911
- context.append(f"Initial Research Plan: {research_plan[:200]}...")
912
  intermediate_output += f"Initial Research Plan:\n{research_plan}\n\n"
913
  focus_areas = initial_focus_areas
914
 
915
 
916
  for i in range(MAX_ITERATIONS):
917
- if key_entities and i > 0:
918
- entities_to_process = key_entities + ['general']
 
919
  current_entity = entities_to_process[i % len(entities_to_process)]
920
  else:
921
- current_entity = 'general'
922
 
923
  context.append(f"Current focus: {current_entity}")
924
 
925
- if i > 0:
 
926
  faiss_results_indices = search_faiss_index(prompt if current_entity == 'general' else f"{prompt} {current_entity}")
927
  faiss_context = []
928
  for idx in faiss_results_indices:
929
- if idx < len(all_insights):
930
  faiss_context.append(f"Previously found insight: {all_insights[idx]}")
931
  if faiss_context:
932
- context.extend(faiss_context)
933
  intermediate_output += f"Iteration {i+1} - Retrieved {len(faiss_context)} relevant items from FAISS index.\n"
934
-
935
 
936
- if i == 0:
937
  initial_query = tool_generate_search_query(prompt=prompt)
938
  yield f"Generating initial search query... (Iteration {i+1})"
939
  if initial_query:
@@ -996,68 +946,69 @@ def deep_research(prompt):
996
  for future in as_completed(futures):
997
  search_results.extend(future.result())
998
 
999
- yield f"Searching for information on entity: {current_entity}... (Iteration {i+1})"
1000
- filtered_search_results = filter_results(search_results,
1001
  f"{prompt} {current_entity}",
1002
- previous_snippets=seen_snippets)
1003
-
1004
- if filtered_search_results:
1005
- context.append(f"Entity Search for {current_entity}: {len(filtered_search_results)} results")
1006
-
1007
- entity_reasoning = tool_reason(
1008
- prompt=f"{prompt} focusing on {current_entity}",
1009
- search_results=filtered_search_results,
1010
- reasoning_context=entity_progress[current_entity]['insights'],
1011
- focus_areas=focus_areas
1012
- )
1013
- yield f"Reasoning about entity: {current_entity}... (Iteration {i+1})"
1014
 
1015
- if entity_reasoning:
1016
- all_insights.append(entity_reasoning)
1017
- entity_progress[current_entity]['insights'].append(entity_reasoning)
1018
-
1019
- if current_entity not in entity_specific_insights:
1020
- entity_specific_insights[current_entity] = []
1021
- entity_specific_insights[current_entity].append(entity_reasoning)
1022
-
1023
- context.append(f"Reasoning about {current_entity}: {entity_reasoning[:200]}...")
1024
- add_to_faiss_index(entity_reasoning)
1025
- else:
1026
- failed_queries.append(entity_query)
1027
- context.append(f"Entity query for {current_entity} yielded no relevant results")
 
 
 
 
 
 
 
 
 
 
 
1028
 
1029
  llm_prompt = create_prompt(task_description, prompt, tools, context)
1030
- llm_response = hf_inference(MAIN_LLM_MODEL, llm_prompt, stream=True)
1031
 
1032
  if isinstance(llm_response, dict) and "error" in llm_response:
1033
  intermediate_output += f"LLM Error: {llm_response['error']}\n"
1034
- yield f"LLM Error (Iteration {i+1}): {llm_response['error']}"
1035
  continue
1036
 
 
1037
  response_text = ""
1038
  try:
1039
  for chunk in llm_response:
1040
  if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content:
1041
  response_text += chunk.choices[0].delta.content
1042
- yield f"Iteration {i+1} - Thinking... {response_text}"
1043
 
1044
  except Exception as e:
1045
  intermediate_output += f"Streaming Error: {str(e)}\n"
1046
- yield f"Streaming Error (Iteration {i+1}): {str(e)}"
1047
  continue
1048
 
1049
  try:
1050
- response_json = json.loads(response_text)
1051
  intermediate_output += f"Iteration {i+1} - Focus: {current_entity} - Action: {response_text}\n"
1052
  except json.JSONDecodeError:
1053
  intermediate_output += f"Iteration {i+1} - LLM Response (Invalid JSON): {response_text[:100]}...\n"
1054
- context.append(f"Invalid JSON: {response_text[:100]}...")
1055
  continue
1056
 
1057
  tool_name = response_json.get("tool")
1058
  parameters = response_json.get("parameters", {})
1059
 
1060
- if not tool_name:
1061
  if all_insights:
1062
  if i > MAX_ITERATIONS // 2:
1063
  break
@@ -1080,7 +1031,7 @@ def deep_research(prompt):
1080
  yield f"Iteration {i+1} - Generated search query: {result}"
1081
 
1082
  if current_entity != 'general':
1083
- entity_progress[current_entity]['queries'].append(result)
1084
 
1085
  previous_queries.append(result)
1086
 
@@ -1092,12 +1043,13 @@ def deep_research(prompt):
1092
 
1093
  filtered_result = filter_results(result, search_prompt, previous_snippets=seen_snippets)
1094
 
1095
- result = filtered_result
1096
 
1097
- if not result and 'query' in parameters:
1098
  failed_queries.append(parameters['query'])
1099
 
1100
  elif tool_name == "reason":
 
1101
  if current_entity != 'general' and 'reasoning_context' not in parameters:
1102
  parameters['reasoning_context'] = entity_progress[current_entity]['insights']
1103
  elif 'reasoning_context' not in parameters:
@@ -1110,9 +1062,9 @@ def deep_research(prompt):
1110
  parameters['prompt'] = prompt
1111
 
1112
  if 'search_results' not in parameters:
1113
- parameters['search_results'] = []
1114
 
1115
- if 'focus_areas' not in parameters and focus_areas:
1116
  parameters['focus_areas'] = focus_areas
1117
 
1118
  result = tool["function"](**parameters)
@@ -1121,20 +1073,20 @@ def deep_research(prompt):
1121
  if current_entity != 'general':
1122
  entity_progress[current_entity]['insights'].append(result)
1123
  if current_entity not in entity_specific_insights:
1124
- entity_specific_insights[current_entity] = []
1125
  entity_specific_insights[current_entity].append(result)
1126
  else:
1127
- reasoning_context.append(result)
1128
  add_to_faiss_index(result)
1129
  all_insights.append(result)
1130
 
1131
  elif tool_name == "critique_reasoning":
1132
- if 'previous_critiques' not in parameters:
1133
  parameters['previous_critiques'] = previous_critiques
1134
 
1135
  if all_insights:
1136
  if 'reasoning_output' not in parameters:
1137
- parameters['reasoning_output'] = all_insights[-1]
1138
  if 'prompt' not in parameters:
1139
  parameters['prompt'] = prompt
1140
 
@@ -1149,7 +1101,7 @@ def deep_research(prompt):
1149
  result = tool["function"](**parameters)
1150
  yield f"Iteration {i+1} - Identifying contradictions..."
1151
  if result:
1152
- contradictions = result
1153
  context.append(f"Identified contradictions: {result}")
1154
 
1155
  elif tool_name == "identify_focus_areas":
@@ -1159,8 +1111,8 @@ def deep_research(prompt):
1159
  yield f"Iteration {i+1} - Identifying focus areas..."
1160
  if result:
1161
  old_focus = set(focus_areas)
1162
- focus_areas = result
1163
- failed_areas.extend([area for area in old_focus if area not in result])
1164
  context.append(f"New focus areas: {result}")
1165
 
1166
  elif tool_name == "extract_article":
@@ -1168,20 +1120,21 @@ def deep_research(prompt):
1168
  yield f"Iteration {i+1} - Extracting article content..."
1169
  if result:
1170
  context.append(f"Extracted article content from {parameters['url']}: {result[:200]}...")
 
1171
  reasoning_about_article = tool_reason(prompt=prompt, search_results=[{"title": "Extracted Article", "snippet": result, "url": parameters['url']}])
1172
  if reasoning_about_article:
1173
  all_insights.append(reasoning_about_article)
1174
  add_to_faiss_index(reasoning_about_article)
1175
-
1176
  elif tool_name == "summarize_paper":
1177
  result = tool["function"](**parameters)
1178
  yield f"Iteration {i+1} - Summarizing paper..."
1179
  if result:
1180
- paper_summaries[parameters['paper_text'][:100]] = result
1181
  save_paper_summaries(paper_summaries)
1182
  context.append(f"Summarized paper: {result[:200]}...")
1183
- add_to_faiss_index(result)
1184
- all_insights.append(result)
1185
 
1186
  elif tool_name == "meta_analyze":
1187
  if 'entity_insights' not in parameters:
@@ -1191,13 +1144,13 @@ def deep_research(prompt):
1191
  result = tool["function"](**parameters)
1192
  yield f"Iteration {i+1} - Performing meta-analysis..."
1193
  if result:
1194
- all_insights.append(result)
1195
  context.append(f"Meta-analysis across entities: {result[:200]}...")
1196
  add_to_faiss_index(result)
1197
 
1198
 
1199
  elif tool_name == "draft_research_plan":
1200
- result = "Research plan already generated."
1201
 
1202
  else:
1203
  result = tool["function"](**parameters)
@@ -1208,6 +1161,7 @@ def deep_research(prompt):
1208
 
1209
  intermediate_output += f"Iteration {i+1} - Result: {result_str}\n"
1210
 
 
1211
  result_context = result_str
1212
  if len(result_str) > 300:
1213
  result_context = result_str[:300] + "..."
@@ -1219,6 +1173,7 @@ def deep_research(prompt):
1219
  intermediate_output += f"Iteration {i+1} - Error: {str(e)}\n"
1220
  continue
1221
 
 
1222
  research_data = {
1223
  'context': context,
1224
  'all_insights': all_insights,
@@ -1234,28 +1189,31 @@ def deep_research(prompt):
1234
  'research_session_id': research_session_id
1235
  }
1236
  for entity in entity_progress:
1237
- research_data[entity] = entity_progress[entity]
1238
  save_research_data(research_data, index)
1239
 
 
 
1240
  if len(entity_specific_insights) > 1 and len(all_insights) > 2:
1241
  meta_analysis = tool_meta_analyze(entity_insights=entity_specific_insights, prompt=prompt)
1242
  if meta_analysis:
1243
  all_insights.append(meta_analysis)
1244
  intermediate_output += f"Final Meta-Analysis: {meta_analysis[:500]}...\n"
1245
- add_to_faiss_index(meta_analysis)
1246
 
1247
  if all_insights:
1248
- final_result = tool_summarize(all_insights, prompt, contradictions)
1249
  else:
1250
  final_result = "Could not find meaningful information despite multiple attempts."
1251
 
 
1252
  full_output = f"**Research Prompt:** {prompt}\n\n"
1253
 
1254
  if key_entities_with_descriptions:
1255
- full_output += f"**Key Entities Identified:**\n"
1256
- for entity in key_entities_with_descriptions:
1257
- full_output += f"- {entity}\n"
1258
- full_output += "\n"
1259
 
1260
  full_output += "**Research Process:**\n" + intermediate_output + "\n"
1261
 
@@ -1272,124 +1230,54 @@ def deep_research(prompt):
1272
  full_output += f"Total iterations: {i+1}\n"
1273
  full_output += f"Total insights generated: {len(all_insights)}\n"
1274
 
1275
- yield full_output
 
1276
 
1277
  custom_css = """
1278
- /* Modern Research Interface */
1279
- .research-container {
1280
- display: grid;
1281
- grid-template-columns: 2fr 1fr;
1282
- gap: 20px;
1283
- padding: 20px;
1284
- background: #1a1a1a;
1285
  }
1286
-
1287
- .main-content {
1288
- background: #2d2d2d;
1289
- border-radius: 10px;
1290
- padding: 20px;
1291
  }
1292
-
1293
- .sidebar {
1294
- background: #2d2d2d;
1295
- border-radius: 10px;
1296
- padding: 20px;
1297
  }
1298
-
1299
- /* Progress Tracking */
1300
- .progress-container {
1301
- background: #333;
1302
- border-radius: 8px;
1303
- padding: 15px;
1304
- margin-bottom: 15px;
1305
- }
1306
-
1307
- .tool-indicator {
1308
- display: flex;
1309
- align-items: center;
1310
- gap: 10px;
1311
- padding: 8px;
1312
- background: #444;
1313
- border-radius: 5px;
1314
- margin-bottom: 8px;
1315
- }
1316
-
1317
- .tool-icon {
1318
- width: 24px;
1319
- height: 24px;
1320
- }
1321
-
1322
- .tool-name {
1323
- color: #4CAF50;
1324
- font-weight: bold;
1325
- }
1326
-
1327
- /* Enhanced Output Formatting */
1328
- .research-output {
1329
- font-family: 'Inter', sans-serif;
1330
- line-height: 1.6;
1331
- }
1332
-
1333
- .research-output h2 {
1334
- color: #4CAF50;
1335
- border-bottom: 2px solid #4CAF50;
1336
- padding-bottom: 5px;
1337
- }
1338
-
1339
- .research-output code {
1340
- background: #333;
1341
- padding: 2px 6px;
1342
- border-radius: 4px;
1343
- }
1344
-
1345
- /* Statistics Panel */
1346
- .stats-panel {
1347
- background: #333;
1348
- border-radius: 8px;
1349
- padding: 15px;
1350
- margin-top: 15px;
1351
- }
1352
-
1353
- .stat-item {
1354
- display: flex;
1355
- justify-content: space-between;
1356
- padding: 8px 0;
1357
- border-bottom: 1px solid #444;
1358
  }
1359
  """
1360
 
1361
  iface = gr.Interface(
1362
  fn=deep_research,
1363
  inputs=[
1364
- gr.Textbox(lines=5, placeholder="Enter your research question...", label="Research Question"),
1365
- gr.Dropdown(
1366
- choices=["Quick", "Standard", "Comprehensive", "Exhaustive"],
1367
- label="Research Depth",
1368
- value="Standard"
1369
- ),
1370
- gr.CheckboxGroup(
1371
- choices=["Academic Papers", "Patents", "News", "Clinical Trials", "Datasets"],
1372
- label="Source Types",
1373
- value=["Academic Papers", "News"]
1374
- ),
1375
- gr.Slider(
1376
- minimum=1,
1377
- maximum=24,
1378
- value=2,
1379
- label="Time Limit (hours)"
1380
- )
1381
  ],
1382
- outputs=[
1383
- gr.Markdown(label="Research Results", elem_classes=["research-output"]),
1384
- gr.JSON(label="Progress Statistics", elem_classes=["stats-panel"]),
1385
- gr.HTML(label="Active Tools", elem_classes=["tool-indicator"])
 
 
 
 
 
 
 
1386
  ],
1387
- title="Advanced Research Institution Assistant",
1388
- description="""Enterprise-grade research system with real-time progress tracking,
1389
- comprehensive source coverage, and advanced analysis capabilities.""",
1390
- theme=gr.themes.Base(primary_hue="green"),
1391
- css=custom_css
1392
  )
1393
 
1394
  if __name__ == "__main__":
1395
- iface.launch(share=False)
 
26
  import tiktoken
27
 
28
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
29
+ logger = logging.getLogger(__name__)
30
 
31
  HF_API_KEY = os.environ.get("HF_API_KEY")
32
  if not HF_API_KEY:
 
37
  MAIN_LLM_MODEL = "mistralai/Mistral-Nemo-Instruct-2407"
38
  REASONING_LLM_MODEL = "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B"
39
  CRITIC_LLM_MODEL = "Qwen/QwQ-32B-Preview"
40
+ ENSEMBLE_MODELS = [MAIN_LLM_MODEL, REASONING_LLM_MODEL, CRITIC_LLM_MODEL] # Keep, but expand upon.
41
+
42
+ MAX_ITERATIONS = 40 # Increased for deeper research.
43
+ TIMEOUT = 180 # Longer timeout for larger models / complex tasks.
44
+ RETRY_DELAY = 10 # longer delay
45
+ NUM_RESULTS = 20
46
+ SIMILARITY_THRESHOLD = 0.15
47
+ MAX_CONTEXT_ITEMS = 50 # Increased context window.
48
+ MAX_HISTORY_ITEMS = 12
49
+ MAX_FULL_TEXT_LENGTH = 20000 # larger document size
 
 
 
 
 
 
50
  FAISS_INDEX_PATH = "research_index.faiss"
51
  RESEARCH_DATA_PATH = "research_data.pkl"
52
+ PAPER_SUMMARIES_PATH = "paper_summaries.pkl" #New path for storing paper summary
53
+
54
 
55
  try:
56
  main_similarity_model = SentenceTransformer('sentence-transformers/all-mpnet-base-v2')
 
62
  index = faiss.read_index(FAISS_INDEX_PATH)
63
  logger.info(f"Loaded FAISS index from {FAISS_INDEX_PATH}")
64
  else:
65
+ index = faiss.IndexFlatIP(embedding_dim) # Use IndexFlatIP for inner product (cosine similarity).
66
  logger.info("Created a new FAISS index.")
 
67
  except Exception as e:
68
  logger.error(f"Failed to load models or initialize FAISS: {e}")
69
  raise
 
89
  try:
90
  with open(RESEARCH_DATA_PATH, "rb") as f:
91
  data = pickle.load(f)
92
+ logger.info(f"Loaded research data from {RESEARCH_DATA_PATH}")
93
+ return data
94
  except Exception as e:
95
  logger.error(f"Error loading research data: {e}")
96
  return {}
 
111
  try:
112
  with open(PAPER_SUMMARIES_PATH, "rb") as f:
113
  data = pickle.load(f)
114
+ logger.info(f"Loaded paper summaries from {PAPER_SUMMARIES_PATH}")
115
+ return data
116
  except Exception as e:
117
  logger.error(f"Error loading paper summaries: {e}")
118
  return {}
 
120
  logger.info("No existing paper summaries found.")
121
  return {}
122
 
123
+
124
+ def hf_inference(model_name, prompt, max_tokens=2000, retries=5, stream=False): # Added stream parameter
125
  for attempt in range(retries):
126
  try:
127
  messages = [{"role": "user", "content": prompt}]
 
129
  model=model_name,
130
  messages=messages,
131
  max_tokens=max_tokens,
132
+ stream=stream # Pass the stream parameter
133
  )
134
  if stream:
135
+ return response_generator # Return the generator directly
136
  else:
137
+ # If not streaming, get the full response
138
+ response = next(response_generator) # Consume the first chunk to get complete object
139
  return {"generated_text": response.choices[0].message.content}
140
  except Exception as e:
141
  if attempt == retries - 1:
142
  logger.error(f"Request failed after {retries} retries: {e}")
143
  return {"error": f"Request failed after {retries} retries: {e}"}
144
  time.sleep(RETRY_DELAY * (1 + attempt))
145
+ return {"error": "Request failed after multiple retries."}
146
 
147
+ def ensemble_inference(prompt, models=ENSEMBLE_MODELS, max_tokens=1500, stream=False): #Added stream
148
  results = []
149
 
150
+ if stream: # If streaming, return a generator that yields from each model
151
  def generate_responses():
152
  with ThreadPoolExecutor(max_workers=len(models)) as executor:
153
  futures = {executor.submit(hf_inference, model, prompt, max_tokens, stream=True): model for model in models}
 
155
  for future in as_completed(futures):
156
  model = future_to_model[future]
157
  try:
158
+ for chunk in future.result(): # Iterate through chunks
159
+ yield {"model": model, "text": chunk.choices[0].delta.content} #yield the content of the chunk
160
  except Exception as e:
161
  logger.error(f"Error with model {model}: {e}")
162
  yield {"model": model, "text": f"Error: {e}"}
163
+ return generate_responses() # return the generator
164
 
165
+ else: #Non-streaming behavior
166
  with ThreadPoolExecutor(max_workers=len(models)) as executor:
167
  future_to_model = {executor.submit(hf_inference, model, prompt, max_tokens, stream=False): model for model in models}
168
  for future in as_completed(future_to_model):
 
184
  for result in results:
185
  synthesis_prompt += f"Expert {results.index(result) + 1} ({result['model'].split('/')[-1]}):\n{result['text']}\n\n"
186
 
187
+ synthesis = hf_inference(MAIN_LLM_MODEL, synthesis_prompt) # Use a consistent model for final synthesis
188
  if "generated_text" in synthesis:
189
  return synthesis
190
  else:
191
+ return {"generated_text": max(results, key=lambda x: len(x["text"]))["text"]} # Fallback
192
 
193
  def tool_search_web(query: str, num_results: int = NUM_RESULTS, safesearch: str = "moderate",
194
+ time_filter: Optional[str] = None, region: str = "wt-wt", language: str = "en-us") -> list:
195
  try:
196
  with DDGS() as ddgs:
197
  kwargs = {
 
289
  except Exception as e:
290
  logger.error(f"Wikipedia search error: {e}")
291
  return []
292
+
293
  def tool_search_scholar(query: str, max_results: int = 5) -> list:
294
  try:
295
  search_query = scholarly.search_pubs(query)
 
326
  return ""
327
 
328
  def tool_reason(prompt: str, search_results: list, reasoning_context: list = [],
329
+ critique: str = "", focus_areas: list = []) -> str:
330
  if not search_results:
331
  return "No search results to reason about."
332
 
 
338
 
339
  results_by_source = {}
340
  for i, result in enumerate(search_results):
341
+ source = result.get('source', 'Web Search') # Default to 'Web Search'
342
  if source not in results_by_source:
343
  results_by_source[source] = []
344
  results_by_source[source].append((i, result))
 
354
  reasoning_input += "\n"
355
 
356
  if reasoning_context:
357
+ recent_context = reasoning_context[-MAX_HISTORY_ITEMS:] # Limit history
358
  reasoning_input += "\nPrevious Reasoning Context:\n" + "\n".join(recent_context)
359
 
360
  if critique:
 
362
 
363
  reasoning_input += "\nProvide a thorough, nuanced analysis that builds upon previous reasoning if applicable. Consider multiple perspectives, potential contradictions in the search results, and the reliability of different sources. Address any specific critiques."
364
 
365
+ reasoning_output = ensemble_inference(reasoning_input) # Use ensemble for high-quality reasoning.
366
 
367
  if isinstance(reasoning_output, dict) and "generated_text" in reasoning_output:
368
  return reasoning_output["generated_text"].strip()
 
376
 
377
  summarization_input = f"Synthesize the following insights into a cohesive and comprehensive summary regarding: '{prompt}'\n\n"
378
 
379
+ max_tokens = 12000 # Increased token limit
380
  selected_insights = []
381
  token_count = get_token_count(summarization_input) + get_token_count("\n\n".join(contradictions))
382
 
 
404
  return "Could not generate a summary due to an error."
405
 
406
  def tool_generate_search_query(prompt: str, previous_queries: list = [],
407
+ failed_queries: list = [], focus_areas: list = []) -> str:
408
  query_gen_input = f"Generate an effective search query for the following prompt: {prompt}\n"
409
 
410
  if previous_queries:
 
429
  return ""
430
 
431
  def tool_critique_reasoning(reasoning_output: str, prompt: str,
432
+ previous_critiques: list = []) -> str:
433
  critique_input = f"Critically evaluate the following reasoning output in relation to the prompt:\n\nPrompt: {prompt}\n\nReasoning: {reasoning_output}\n\n"
434
 
435
  if previous_critiques:
 
437
 
438
  critique_input += "Identify any flaws, biases, logical fallacies, unsupported claims, or areas for improvement. Be specific and constructive. Suggest concrete ways to enhance the reasoning. Also evaluate the strength of evidence and whether conclusions are proportionate to the available information."
439
 
440
+ critique_output = hf_inference(CRITIC_LLM_MODEL, critique_input) # Use specialized critique model.
441
 
442
  if isinstance(critique_output, dict) and "generated_text" in critique_output:
443
  return critique_output["generated_text"].strip()
 
449
  if len(insights) < 2:
450
  return []
451
 
452
+ max_tokens = 12000 # Increased token limit for potentially more contradictions
453
  selected_insights = []
454
  token_count = 0
455
 
 
464
  contradiction_input = "Identify specific contradictions in these insights:\n\n" + "\n\n".join(selected_insights)
465
  contradiction_input += "\n\nList each contradiction as a separate numbered point. For each contradiction, cite the specific claims that are in tension and evaluate which claim is better supported. If no contradictions exist, respond with 'No contradictions found.'"
466
 
467
+ contradiction_output = hf_inference(CRITIC_LLM_MODEL, contradiction_input) # Use critique model
468
 
469
  if isinstance(contradiction_output, dict) and "generated_text" in contradiction_output:
470
  result = contradiction_output["generated_text"].strip()
471
  if result == "No contradictions found.":
472
  return []
473
+ # More robust contradiction extraction, handles multi-sentence contradictions
474
  contradictions = re.findall(r'\d+\.\s+(.*?)(?=\d+\.|$)', result, re.DOTALL)
475
  return [c.strip() for c in contradictions if c.strip()]
476
 
 
478
  return []
479
 
480
  def tool_identify_focus_areas(prompt: str, insights: list = [],
481
+ failed_areas: list = []) -> list:
482
  focus_input = f"Based on this research prompt: '{prompt}'\n\n"
483
 
484
  if insights:
 
490
 
491
  focus_input += "Identify 3-5 specific aspects that should be investigated further to get a complete understanding. Be precise and prioritize underexplored areas. For each suggested area, briefly explain why it's important to investigate."
492
 
493
+ focus_output = hf_inference(MAIN_LLM_MODEL, focus_input) # Consistent model
494
 
495
  if isinstance(focus_output, dict) and "generated_text" in focus_output:
496
  result = focus_output["generated_text"].strip()
497
+ # More robust extraction, handles different list formats
498
  areas = re.findall(r'(?:^|\n)(?:\d+\.|\*|\-)\s*(.*?)(?=(?:\n(?:\d+\.|\*|\-|$))|$)', result)
499
  return [area.strip() for area in areas if area.strip()][:5]
500
 
 
507
  if embedding_np.shape[1] != embedding_dim:
508
  logger.error(f"Embedding dimension mismatch: expected {embedding_dim}, got {embedding_np.shape[1]}")
509
  return
510
+ faiss.normalize_L2(embedding_np) # Normalize for cosine similarity.
511
  index.add(embedding_np)
512
 
513
  def search_faiss_index(query: str, top_k: int = 5) -> List[str]:
 
532
  for result in search_results:
533
  combined_text = result['title'] + " " + result['snippet']
534
 
535
+ if result['snippet'] in seen_snippets: # Prevent exact duplicates
536
  continue
537
 
538
  result_embedding = main_similarity_model.encode(combined_text, convert_to_tensor=True)
 
541
  if cosine_score >= SIMILARITY_THRESHOLD:
542
  result['relevance_score'] = cosine_score
543
  filtered_results.append(result)
544
+ seen_snippets.add(result['snippet']) # Add snippets after filtering
545
  add_to_faiss_index(result['snippet'])
546
 
547
 
548
+ filtered_results.sort(key=lambda x: x.get('relevance_score', 0), reverse=True) # Sort by relevance.
549
  return filtered_results
550
 
551
  except Exception as e:
552
  logger.error(f"Error during filtering: {e}")
553
+ return search_results # Return original results on error.
554
 
555
  def tool_extract_key_entities(prompt: str) -> list:
556
  entity_input = f"Extract the key entities (people, organizations, concepts, technologies, events, time periods, locations, etc.) from this research prompt that should be investigated individually:\n\n{prompt}\n\nList the 5-7 most important entities, one per line, with a brief explanation (2-3 sentences) of why each is central to the research question."
 
560
  if isinstance(entity_output, dict) and "generated_text" in entity_output:
561
  result = entity_output["generated_text"].strip()
562
  entities = [e.strip() for e in result.split('\n') if e.strip()]
563
+ return entities[:7] # Limit to top 7 entities
564
 
565
  logger.error(f"Failed to extract key entities: {entity_output}")
566
  return []
 
573
 
574
  for entity, insights in entity_insights.items():
575
  if insights:
576
+ meta_input += f"\n--- {entity} ---\n" + insights[-1] + "\n" # Most recent insight for each entity
577
 
578
  meta_input += "\nProvide a high-level synthesis that identifies:\n1. Common themes across entities\n2. Important differences and contradictions\n3. How these entities interact or influence each other\n4. The broader implications for the original research question\n5. A systems-level understanding of how these elements fit together"
579
 
580
+ meta_output = ensemble_inference(meta_input) # Ensemble for meta-analysis
581
 
582
  if isinstance(meta_output, dict) and "generated_text" in meta_output:
583
  return meta_output["generated_text"].strip()
 
602
  plan_input += "5. Potential challenges and how to address them\n"
603
  plan_input += "6. Criteria for evaluating the quality of findings"
604
 
605
+ plan_output = hf_inference(REASONING_LLM_MODEL, plan_input) # Use reasoning model
606
 
607
  if isinstance(plan_output, dict) and "generated_text" in plan_output:
608
  return plan_output["generated_text"].strip()
 
614
  extracted_text = extract_article_content(url)
615
  return extracted_text if extracted_text else f"Could not extract content from {url}"
616
 
617
+ # New tool for summarizing a single paper
618
  def tool_summarize_paper(paper_text: str) -> str:
619
+ summarization_prompt = f"""Summarize this academic paper, focusing on the following:
 
 
 
 
620
 
621
+ 1. **Main Research Question(s):** What questions does the paper address?
622
+ 2. **Methodology:** Briefly describe the methods used (e.g., experiments, surveys, simulations, theoretical analysis).
623
+ 3. **Key Findings:** What are the most important results or conclusions?
624
+ 4. **Limitations:** What are the acknowledged limitations of the study?
625
+ 5. **Implications:** What are the broader implications of the findings, according to the authors?
626
 
 
 
 
627
  Paper Text:
628
  {paper_text[:MAX_FULL_TEXT_LENGTH]}
629
+ """ # Truncate if necessary
630
+ summary = hf_inference(REASONING_LLM_MODEL, summarization_prompt, max_tokens=500)
 
 
 
 
 
 
631
 
632
+ if isinstance(summary, dict) and "generated_text" in summary:
633
+ return summary["generated_text"].strip()
634
+ else:
635
+ logger.error(f"Failed to generate summary: {summary}")
636
+ return "Could not generate a summary due to an error."
637
 
 
 
 
 
 
 
 
 
638
 
639
  tools = {
640
  "search_web": {
 
672
  "max_results": {"type": "integer", "description": "Maximum number of articles to return."}
673
  },
674
  },
675
+ " "search_scholar": {
676
  "function": tool_search_scholar,
677
  "description": "Searches Google Scholar for academic publications.",
678
  "parameters": {
 
687
  "url": {"type": "string", "description": "The URL of the article to extract"}
688
  },
689
  },
690
+ "summarize_paper": {
691
  "function": tool_summarize_paper,
692
  "description": "Summarizes the content of an academic paper.",
693
  "parameters": {
 
738
  "description": "Identifies contradictions across multiple insights.",
739
  "parameters": {
740
  "insights": {"type": "array", "description": "Collection of insights to analyze for contradictions."},
741
+ },
742
  },
743
  "identify_focus_areas": {
744
  "function": tool_identify_focus_areas,
 
772
  "entities": {"type": "array", "description": "Key entities to investigate."},
773
  "focus_areas": {"type": "array", "description": "Additional areas to focus on."}
774
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
775
  }
776
  }
777
 
 
798
  Select the BEST tool and parameters for the current research stage. Output valid JSON. If no tool is appropriate, respond with {}.
799
  Only use provided tools. Be strategic about which tool to use next based on the research progress so far.
800
  You MUST be methodical. Think step-by-step:
801
+ 1. **Plan:** If it's the very beginning, extract key entities, identify focus areas, and then draft a research plan.
802
+ 2. **Search:** Use a variety of search tools. Start with broad searches, then narrow down. Use specific search tools (arXiv, PubMed, Scholar) for relevant topics.
803
+ 3. **Analyze:** Reason deeply about search results, and critique your reasoning. Identify contradictions. Filter and use FAISS index for relevant information.
804
+ 4. **Refine:** If results are poor, generate *better* search queries. Adjust focus areas.
805
+ 5. **Iterate:** Repeat steps 2-4, focusing on different entities and aspects.
806
+ 6. **Synthesize:** Finally, summarize the findings, addressing contradictions.
 
 
 
 
 
 
807
  Example:
808
  {"tool": "search_web", "parameters": {"query": "Eiffel Tower location"}}
809
  Output:
 
813
  def deep_research(prompt):
814
  task_description = "You are an advanced research assistant, designed to be as comprehensive as possible. Use available tools iteratively, focus on different aspects, explore promising leads thoroughly, critically evaluate your findings, and build up a comprehensive understanding of the research topic. Utilize the FAISS index to avoid redundant searches and to build a persistent knowledge base."
815
  research_data = load_research_data()
816
+ paper_summaries = load_paper_summaries() # Load paper summaries
817
 
818
  context = research_data.get('context', [])
819
  all_insights = research_data.get('all_insights', [])
 
828
  seen_snippets = set(research_data.get('seen_snippets', []))
829
  contradictions = research_data.get('contradictions', [])
830
  research_session_id = research_data.get('research_session_id', str(uuid4()))
831
+
832
  global index
833
  if research_data:
834
+ logger.info("Restoring FAISS Index from loaded data.")
835
  else:
836
  index.reset()
837
  logger.info("Initialized a fresh FAISS Index")
838
 
839
  key_entities_with_descriptions = tool_extract_key_entities(prompt=prompt)
840
+ key_entities = [e.split(":")[0].strip() for e in key_entities_with_descriptions] # Extract just entity names
841
  if key_entities:
842
  context.append(f"Identified key entities: {key_entities}")
843
  intermediate_output += f"Identified key entities for focused research: {key_entities_with_descriptions}\n"
844
  yield "Identifying key entities... (Completed)"
845
 
846
+ # Initialize progress tracking for each entity.
847
  entity_progress = {entity: {'queries': [], 'insights': []} for entity in key_entities}
848
+ entity_progress['general'] = {'queries': [], 'insights': []} # For general, non-entity-specific searches
849
  for entity in key_entities + ['general']:
850
+ if entity in research_data: # Load existing progress
851
  entity_progress[entity]['queries'] = research_data[entity]['queries']
852
  entity_progress[entity]['insights'] = research_data[entity]['insights']
853
 
854
+ if not focus_areas: # Corrected placement: outside the loop
855
  initial_focus_areas = tool_identify_focus_areas(prompt=prompt)
856
  yield "Identifying initial focus areas...(Completed)"
857
  research_plan = tool_draft_research_plan(prompt=prompt, entities=key_entities, focus_areas=initial_focus_areas)
858
  yield "Drafting initial research plan...(Completed)"
859
+ context.append(f"Initial Research Plan: {research_plan[:200]}...") # Add plan to context
860
  intermediate_output += f"Initial Research Plan:\n{research_plan}\n\n"
861
  focus_areas = initial_focus_areas
862
 
863
 
864
  for i in range(MAX_ITERATIONS):
865
+ # Entity-focused iteration strategy
866
+ if key_entities and i > 0: # Cycle through entities *after* initial setup
867
+ entities_to_process = key_entities + ['general'] # Include 'general' for broad searches
868
  current_entity = entities_to_process[i % len(entities_to_process)]
869
  else:
870
+ current_entity = 'general' # Start with general research.
871
 
872
  context.append(f"Current focus: {current_entity}")
873
 
874
+ # FAISS Retrieval
875
+ if i > 0: # Use FAISS *after* the first iteration (once we have data)
876
  faiss_results_indices = search_faiss_index(prompt if current_entity == 'general' else f"{prompt} {current_entity}")
877
  faiss_context = []
878
  for idx in faiss_results_indices:
879
+ if idx < len(all_insights): # Check index bounds
880
  faiss_context.append(f"Previously found insight: {all_insights[idx]}")
881
  if faiss_context:
882
+ context.extend(faiss_context) # Add FAISS context
883
  intermediate_output += f"Iteration {i+1} - Retrieved {len(faiss_context)} relevant items from FAISS index.\n"
884
+
885
 
886
+ if i == 0: #Initial broad search
887
  initial_query = tool_generate_search_query(prompt=prompt)
888
  yield f"Generating initial search query... (Iteration {i+1})"
889
  if initial_query:
 
946
  for future in as_completed(futures):
947
  search_results.extend(future.result())
948
 
949
+ yield f"Searching for information on entity: {current_entity}... (Iteration {i+1})"
950
+ filtered_search_results = filter_results(search_results,
951
  f"{prompt} {current_entity}",
952
+ previous_snippets=seen_snippets) # Pass existing snippets
 
 
 
 
 
 
 
 
 
 
 
953
 
954
+ if filtered_search_results:
955
+ context.append(f"Entity Search for {current_entity}: {len(filtered_search_results)} results")
956
+
957
+ entity_reasoning = tool_reason(
958
+ prompt=f"{prompt} focusing on {current_entity}",
959
+ search_results=filtered_search_results,
960
+ reasoning_context=entity_progress[current_entity]['insights'], # Use entity-specific context
961
+ focus_areas=focus_areas
962
+ )
963
+ yield f"Reasoning about entity: {current_entity}... (Iteration {i+1})"
964
+
965
+ if entity_reasoning:
966
+ all_insights.append(entity_reasoning)
967
+ entity_progress[current_entity]['insights'].append(entity_reasoning)
968
+
969
+ if current_entity not in entity_specific_insights:
970
+ entity_specific_insights[current_entity] = []
971
+ entity_specific_insights[current_entity].append(entity_reasoning)
972
+
973
+ context.append(f"Reasoning about {current_entity}: {entity_reasoning[:200]}...")
974
+ add_to_faiss_index(entity_reasoning)
975
+ else:
976
+ failed_queries.append(entity_query)
977
+ context.append(f"Entity query for {current_entity} yielded no relevant results")
978
 
979
  llm_prompt = create_prompt(task_description, prompt, tools, context)
980
+ llm_response = hf_inference(MAIN_LLM_MODEL, llm_prompt, stream=True) # Use streaming
981
 
982
  if isinstance(llm_response, dict) and "error" in llm_response:
983
  intermediate_output += f"LLM Error: {llm_response['error']}\n"
984
+ yield f"LLM Error (Iteration {i+1}): {llm_response['error']}" # Display error in output
985
  continue
986
 
987
+ # Process streaming response
988
  response_text = ""
989
  try:
990
  for chunk in llm_response:
991
  if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content:
992
  response_text += chunk.choices[0].delta.content
993
+ yield f"Iteration {i+1} - Thinking... {response_text}" # Real time output
994
 
995
  except Exception as e:
996
  intermediate_output += f"Streaming Error: {str(e)}\n"
997
+ yield f"Streaming Error (Iteration {i+1}): {str(e)}" #Error
998
  continue
999
 
1000
  try:
1001
+ response_json = json.loads(response_text) # Parse the JSON response.
1002
  intermediate_output += f"Iteration {i+1} - Focus: {current_entity} - Action: {response_text}\n"
1003
  except json.JSONDecodeError:
1004
  intermediate_output += f"Iteration {i+1} - LLM Response (Invalid JSON): {response_text[:100]}...\n"
1005
+ context.append(f"Invalid JSON: {response_text[:100]}...") # Add invalid JSON to context
1006
  continue
1007
 
1008
  tool_name = response_json.get("tool")
1009
  parameters = response_json.get("parameters", {})
1010
 
1011
+ if not tool_name: #LLM didn't return a tool. End the process if we are past halfway.
1012
  if all_insights:
1013
  if i > MAX_ITERATIONS // 2:
1014
  break
 
1031
  yield f"Iteration {i+1} - Generated search query: {result}"
1032
 
1033
  if current_entity != 'general':
1034
+ entity_progress[current_entity]['queries'].append(result) # Add entity-specific
1035
 
1036
  previous_queries.append(result)
1037
 
 
1043
 
1044
  filtered_result = filter_results(result, search_prompt, previous_snippets=seen_snippets)
1045
 
1046
+ result = filtered_result # Work with filtered results
1047
 
1048
+ if not result and 'query' in parameters: # Add query to failures if nothing returned.
1049
  failed_queries.append(parameters['query'])
1050
 
1051
  elif tool_name == "reason":
1052
+ # Ensure correct reasoning context is passed.
1053
  if current_entity != 'general' and 'reasoning_context' not in parameters:
1054
  parameters['reasoning_context'] = entity_progress[current_entity]['insights']
1055
  elif 'reasoning_context' not in parameters:
 
1062
  parameters['prompt'] = prompt
1063
 
1064
  if 'search_results' not in parameters:
1065
+ parameters['search_results'] = [] #Avoid errors if no search results.
1066
 
1067
+ if 'focus_areas' not in parameters and focus_areas: # Avoid overwriting focus_areas if already set
1068
  parameters['focus_areas'] = focus_areas
1069
 
1070
  result = tool["function"](**parameters)
 
1073
  if current_entity != 'general':
1074
  entity_progress[current_entity]['insights'].append(result)
1075
  if current_entity not in entity_specific_insights:
1076
+ entity_specific_insights[current_entity] = []
1077
  entity_specific_insights[current_entity].append(result)
1078
  else:
1079
+ reasoning_context.append(result) #Add to general context.
1080
  add_to_faiss_index(result)
1081
  all_insights.append(result)
1082
 
1083
  elif tool_name == "critique_reasoning":
1084
+ if 'previous_critiques' not in parameters: #Pass in the previous critiques.
1085
  parameters['previous_critiques'] = previous_critiques
1086
 
1087
  if all_insights:
1088
  if 'reasoning_output' not in parameters:
1089
+ parameters['reasoning_output'] = all_insights[-1] #Critique the most recent insight.
1090
  if 'prompt' not in parameters:
1091
  parameters['prompt'] = prompt
1092
 
 
1101
  result = tool["function"](**parameters)
1102
  yield f"Iteration {i+1} - Identifying contradictions..."
1103
  if result:
1104
+ contradictions = result # Keep track of contradictions.
1105
  context.append(f"Identified contradictions: {result}")
1106
 
1107
  elif tool_name == "identify_focus_areas":
 
1111
  yield f"Iteration {i+1} - Identifying focus areas..."
1112
  if result:
1113
  old_focus = set(focus_areas)
1114
+ focus_areas = result # Update focus areas
1115
+ failed_areas.extend([area for area in old_focus if area not in result]) #Track failed areas
1116
  context.append(f"New focus areas: {result}")
1117
 
1118
  elif tool_name == "extract_article":
 
1120
  yield f"Iteration {i+1} - Extracting article content..."
1121
  if result:
1122
  context.append(f"Extracted article content from {parameters['url']}: {result[:200]}...")
1123
+ # Reason specifically about the extracted article.
1124
  reasoning_about_article = tool_reason(prompt=prompt, search_results=[{"title": "Extracted Article", "snippet": result, "url": parameters['url']}])
1125
  if reasoning_about_article:
1126
  all_insights.append(reasoning_about_article)
1127
  add_to_faiss_index(reasoning_about_article)
1128
+
1129
  elif tool_name == "summarize_paper":
1130
  result = tool["function"](**parameters)
1131
  yield f"Iteration {i+1} - Summarizing paper..."
1132
  if result:
1133
+ paper_summaries[parameters['paper_text'][:100]] = result # Store by a snippet of the text
1134
  save_paper_summaries(paper_summaries)
1135
  context.append(f"Summarized paper: {result[:200]}...")
1136
+ add_to_faiss_index(result) # Add the summary itself to FAISS.
1137
+ all_insights.append(result) #Add summary to insights for later summarization.
1138
 
1139
  elif tool_name == "meta_analyze":
1140
  if 'entity_insights' not in parameters:
 
1144
  result = tool["function"](**parameters)
1145
  yield f"Iteration {i+1} - Performing meta-analysis..."
1146
  if result:
1147
+ all_insights.append(result) # Add meta-analysis to overall insights.
1148
  context.append(f"Meta-analysis across entities: {result[:200]}...")
1149
  add_to_faiss_index(result)
1150
 
1151
 
1152
  elif tool_name == "draft_research_plan":
1153
+ result = "Research plan already generated." # Avoid re-generating.
1154
 
1155
  else:
1156
  result = tool["function"](**parameters)
 
1161
 
1162
  intermediate_output += f"Iteration {i+1} - Result: {result_str}\n"
1163
 
1164
+ # Add tool use to context, limit context length
1165
  result_context = result_str
1166
  if len(result_str) > 300:
1167
  result_context = result_str[:300] + "..."
 
1173
  intermediate_output += f"Iteration {i+1} - Error: {str(e)}\n"
1174
  continue
1175
 
1176
+ #Save data
1177
  research_data = {
1178
  'context': context,
1179
  'all_insights': all_insights,
 
1189
  'research_session_id': research_session_id
1190
  }
1191
  for entity in entity_progress:
1192
+ research_data[entity] = entity_progress[entity] #save the individual entity
1193
  save_research_data(research_data, index)
1194
 
1195
+
1196
+ # Perform meta-analysis *before* final summarization, if we have enough entity-specific insights.
1197
  if len(entity_specific_insights) > 1 and len(all_insights) > 2:
1198
  meta_analysis = tool_meta_analyze(entity_insights=entity_specific_insights, prompt=prompt)
1199
  if meta_analysis:
1200
  all_insights.append(meta_analysis)
1201
  intermediate_output += f"Final Meta-Analysis: {meta_analysis[:500]}...\n"
1202
+ add_to_faiss_index(meta_analysis) # Add to FAISS
1203
 
1204
  if all_insights:
1205
+ final_result = tool_summarize(all_insights, prompt, contradictions) # Summarize all insights.
1206
  else:
1207
  final_result = "Could not find meaningful information despite multiple attempts."
1208
 
1209
+
1210
  full_output = f"**Research Prompt:** {prompt}\n\n"
1211
 
1212
  if key_entities_with_descriptions:
1213
+ full_output += f"**Key Entities Identified:**\n"
1214
+ for entity in key_entities_with_descriptions:
1215
+ full_output += f"- {entity}\n"
1216
+ full_output += "\n"
1217
 
1218
  full_output += "**Research Process:**\n" + intermediate_output + "\n"
1219
 
 
1230
  full_output += f"Total iterations: {i+1}\n"
1231
  full_output += f"Total insights generated: {len(all_insights)}\n"
1232
 
1233
+ yield full_output # Final output
1234
+
1235
 
1236
  custom_css = """
1237
+ .gradio-container {
1238
+ background-color: #f7f9fc;
 
 
 
 
 
1239
  }
1240
+ .output-box {
1241
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
1242
+ line-height: 1.5;
1243
+ font-size: 14px;
1244
+ white-space: pre-wrap; /* Preserve newlines and spacing */
1245
  }
1246
+ h3 {
1247
+ color: #2c3e50;
1248
+ font-weight: 600;
 
 
1249
  }
1250
+ .footer {
1251
+ text-align: center;
1252
+ margin-top: 20px;
1253
+ color: #7f8c8d;
1254
+ font-size: 0.9em;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1255
  }
1256
  """
1257
 
1258
  iface = gr.Interface(
1259
  fn=deep_research,
1260
  inputs=[
1261
+ gr.Textbox(lines=5, placeholder="Enter your research question...", label="Research Question")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1262
  ],
1263
+ outputs=gr.Markdown(label="Research Results", elem_classes=["output-box"]), #Changed to markdown
1264
+ title="Advanced Multi-Stage Research Assistant",
1265
+ description="""This tool performs deep, multi-faceted research, leveraging multiple search engines,
1266
+ specialized academic databases, and advanced AI models. It incorporates a persistent knowledge
1267
+ base using FAISS indexing to avoid redundant searches and build upon previous findings. Progress is shown in real-time.""",
1268
+ examples=[
1269
+ ["What are the key factors affecting urban tree survival and how do they vary between developing and developed countries?"],
1270
+ ["Compare and contrast the economic policies of China and the United States over the past two decades, analyzing their impacts on global trade."],
1271
+ ["What are the most promising approaches to quantum computing and what are their respective advantages and limitations?"],
1272
+ ["Analyze the environmental and social impacts of lithium mining for electric vehicle batteries."],
1273
+ ["How has artificial intelligence influenced medical diagnostics in the past five years, and what are the ethical considerations?"]
1274
  ],
1275
+ theme="default",
1276
+ cache_examples=False,
1277
+ css=custom_css,
1278
+ allow_flagging="never",
1279
+ live=True #for real time streaming
1280
  )
1281
 
1282
  if __name__ == "__main__":
1283
+ iface.launch(share=False)