Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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(
|
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 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
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 |
-
|
99 |
-
|
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 |
-
|
121 |
-
|
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 |
-
|
|
|
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 |
-
|
|
|
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 |
-
|
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 |
-
|
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 |
-
|
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 |
-
|
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 |
-
|
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 |
-
|
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 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
643 |
-
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
850 |
-
|
851 |
-
|
852 |
-
|
853 |
-
|
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 |
-
|
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 |
-
|
918 |
-
|
|
|
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 |
-
|
|
|
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 |
-
|
1000 |
-
|
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
|
1016 |
-
|
1017 |
-
|
1018 |
-
|
1019 |
-
|
1020 |
-
|
1021 |
-
|
1022 |
-
|
1023 |
-
|
1024 |
-
|
1025 |
-
|
1026 |
-
|
1027 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
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 |
-
|
1256 |
-
|
1257 |
-
|
1258 |
-
|
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 |
-
|
1279 |
-
|
1280 |
-
display: grid;
|
1281 |
-
grid-template-columns: 2fr 1fr;
|
1282 |
-
gap: 20px;
|
1283 |
-
padding: 20px;
|
1284 |
-
background: #1a1a1a;
|
1285 |
}
|
1286 |
-
|
1287 |
-
|
1288 |
-
|
1289 |
-
|
1290 |
-
|
1291 |
}
|
1292 |
-
|
1293 |
-
|
1294 |
-
|
1295 |
-
border-radius: 10px;
|
1296 |
-
padding: 20px;
|
1297 |
}
|
1298 |
-
|
1299 |
-
|
1300 |
-
|
1301 |
-
|
1302 |
-
|
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 |
-
|
1384 |
-
|
1385 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1386 |
],
|
1387 |
-
|
1388 |
-
|
1389 |
-
|
1390 |
-
|
1391 |
-
|
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)
|