You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
"""Planning Agent for generating Exa-optimized research subqueries."""importjsonimporthttpxfromtypingimportDict, Any, Listfromsrc.utils.configimportConfigclassPlanningAgent:
""" Agent that decomposes research queries into Exa-optimized subqueries. Uses OpenRouter with Gemini 2.5 Flash. """def__init__(self):
"""Initialize Planning Agent."""self.api_key=Config.OPENROUTER_API_KEYself.base_url=Config.OPENROUTER_BASE_URLself.model=Config.OPENROUTER_MODELself.system_prompt="""Generate 8-12 comprehensive Exa-optimized subqueries for deep research on the topic.For thorough coverage, create subqueries across multiple dimensions:- Core concepts and definitions- Latest developments and breakthroughs (recent news)- Historical context and evolution- Technical implementations and applications- Expert opinions and analysis- Academic research and papers- Industry trends and market analysis- Future implications and predictions- Challenges and limitations- Related technologies and comparisonsConsider the following when creating subqueries:- Neural search formulation: Use natural language questions and descriptive phrases- Domain filters: Suggest specific domains when relevant (e.g., arxiv.org for papers, news sites)- Time periods: Specify time relevance (recent, past_week, past_month, past_year, any)- Content types: Specify type when relevant (news, research paper, pdf, blog, etc.)- Priority: Assign priority 1-5 (1=highest priority)Each subquery should focus on a different aspect of the research topic to ensure comprehensive, multi-dimensional coverage.Output valid JSON in this exact format:{ "subqueries": [ { "query": "descriptive natural language query", "type": "auto|news|research paper|pdf|etc", "time_period": "recent|past_week|past_month|past_year|any", "include_domains": ["example.com"], "exclude_domains": ["example.org"], "priority": 1 } ]}Notes:- include_domains and exclude_domains are optional (can be null or omitted)- type should be "auto" unless you have specific content type needs- Ensure queries are diverse and cover different angles of the topic"""defplan(self, research_query: str) ->Dict[str, Any]:
""" Generate Exa-optimized subqueries for a research topic. Args: research_query: The main research question or topic Returns: Dictionary containing subqueries with optimization parameters """headers= {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
}
payload= {
"model": self.model,
"messages": [
{"role": "system", "content": self.system_prompt},
{
"role": "user",
"content": f"Research topic: {research_query}\n\nGenerate Exa-optimized subqueries for comprehensive research on this topic.",
},
],
"temperature": 0.7,
"max_tokens": 3000,
}
try:
withhttpx.Client(timeout=60.0) asclient:
response=client.post(
f"{self.base_url}/chat/completions",
json=payload,
headers=headers,
)
response.raise_for_status()
result=response.json()
# Extract the content from the responsecontent=result["choices"][0]["message"]["content"]
# Parse the JSON response# Handle potential markdown code blocksif"```json"incontent:
content=content.split("```json")[1].split("```")[0].strip()
elif"```"incontent:
content=content.split("```")[1].split("```")[0].strip()
subqueries_data=json.loads(content)
return {
"status": "success",
"subqueries": subqueries_data.get("subqueries", []),
"research_query": research_query,
}
excepthttpx.HTTPErrorase:
return {
"status": "error",
"error": f"HTTP error: {str(e)}",
"subqueries": [],
}
exceptjson.JSONDecodeErrorase:
return {
"status": "error",
"error": f"JSON parsing error: {str(e)}",
"subqueries": [],
}
exceptExceptionase:
return {
"status": "error",
"error": f"Unexpected error: {str(e)}",
"subqueries": [],
}
defexecute(self, query: str) ->str:
""" Execute planning and return formatted results as a string. This method is called by the supervisor via tool execution. Args: query: Research query to plan for Returns: JSON string containing the planning results """result=self.plan(query)
returnjson.dumps(result, indent=2)
File: src/agents/supervisor.py
"""Supervisor Agent using Minimax M2 with interleaved thinking."""importanthropicfromtypingimportList, Dict, Anyfromrich.consoleimportConsolefromsrc.utils.configimportConfigfromsrc.agents.planning_agentimportPlanningAgentfromsrc.agents.web_search_retrieverimportWebSearchRetriever# Initialize rich consoleconsole=Console()
classSupervisorAgent:
""" Main supervisor agent that coordinates research workflow using Minimax M2. Implements interleaved thinking by preserving all content blocks in conversation history. """def__init__(self):
"""Initialize Supervisor Agent with Minimax M2."""self.client=anthropic.Anthropic(
api_key=Config.MINIMAX_API_KEY,
base_url=Config.MINIMAX_BASE_URL,
)
self.model=Config.MINIMAX_MODEL# Initialize sub-agentsself.planning_agent=PlanningAgent()
self.web_search_retriever=WebSearchRetriever()
# Conversation history with interleaved thinkingself.messages: List[Dict[str, Any]] = []
self.system_prompt="""You are a deep research coordinator specializing in comprehensive, academic-quality research reports. Your goal is to produce thorough, well-structured, in-depth analysis.You have access to the following tools:1. planning_agent - Breaks down research queries into 8-12 Exa-optimized subqueries - Input: research_query (string) - Returns: JSON with optimized subqueries covering multiple dimensions2. web_search_retriever - Searches the web using Exa and synthesizes findings - Input: research_query (string), subqueries_json (string) - Returns: Comprehensive organized findings with sourcesResearch Workflow:1. Call planning_agent with the user's research query to generate comprehensive subqueries2. Call web_search_retriever with the research query and subqueries to gather extensive information3. Synthesize a COMPREHENSIVE research report (15-30 pages equivalent) with the following structure:## Required Report Structure:### Executive Summary (3-5 paragraphs) - Overview of research scope - Key findings summary - Main conclusions and implications### Introduction (2-3 paragraphs) - Context and background - Research objectives - Methodology overview### Key Findings (Multiple detailed sections organized by theme) - Each major theme gets its own section with subsections - Include data, statistics, expert opinions - Cite sources inline with URLs - Provide examples and case studies### Detailed Analysis (Deep dive into each area) - Technical details and mechanisms - Historical context and evolution - Current state of the art - Comparisons and contrasts - Strengths and limitations### Industry/Application Analysis (if relevant) - Real-world applications - Market trends and adoption - Key players and institutions - Success stories and challenges### Future Implications and Trends - Emerging developments - Predictions and projections - Challenges ahead - Opportunities and potential### Critical Analysis - Debates and controversies - Limitations and challenges - Alternative perspectives - Unanswered questions### Conclusion - Summary of main points - Broader implications - Recommendations (if applicable)### Sources and Citations - Comprehensive list of all sources with URLs - Organized by category or theme## Quality Guidelines:- Be EXTREMELY thorough and detailed - aim for 5-10x more content than a typical report- Use specific data, statistics, and concrete examples throughout- Quote experts and authoritative sources- Explain technical concepts clearly- Make connections across different aspects of the topic- Maintain academic rigor and objectivity- Use clear section headers and subsections- Provide context and background for all major points- Include both breadth (covering many aspects) and depth (detailed analysis)## CRITICAL: Inline Citations Format- **ALWAYS include inline citations** immediately after claims, data, or quotes- Use markdown link format: `[descriptive text](URL)` for all citations- Place citations right where information is used, not just at the end- Examples: * "The market is projected to reach $47 billion by 2030 [according to Grand View Research](https://www.example.com/report)" * "As noted by [Nick Bostrom's research on AI safety](https://example.com/paper), superintelligence poses..." * "Studies show a 44% growth rate [Statista Market Analysis](https://example.com/stats)"- When citing statistics: include the source inline: "Growth rates of 44% [Source](URL)"- When quoting experts: cite immediately: "According to [Expert Name](URL), '...'"- Every factual claim, statistic, or data point MUST have an inline citation- The final Sources section should be a comprehensive list, but inline citations are PRIMARY"""# Tool definitions for Anthropic formatself.tools= [
{
"name": "planning_agent",
"description": "Generates Exa-optimized subqueries for a research topic. Takes a research query and returns JSON with 3-5 subqueries optimized for neural search.",
"input_schema": {
"type": "object",
"properties": {
"research_query": {
"type": "string",
"description": "The main research question or topic to plan for",
}
},
"required": ["research_query"],
},
},
{
"name": "web_search_retriever",
"description": "Executes web searches using Exa API for provided subqueries and synthesizes findings. Returns organized research findings with sources.",
"input_schema": {
"type": "object",
"properties": {
"research_query": {
"type": "string",
"description": "The original research query for context",
},
"subqueries_json": {
"type": "string",
"description": "JSON string containing subqueries from planning_agent",
},
},
"required": ["research_query", "subqueries_json"],
},
},
]
defexecute_tool(self, tool_name: str, tool_input: Dict[str, Any]) ->str:
""" Execute a tool and return its result. Args: tool_name: Name of the tool to execute tool_input: Input parameters for the tool Returns: Tool execution result as string """iftool_name=="planning_agent":
research_query=tool_input.get("research_query", "")
returnself.planning_agent.execute(research_query)
eliftool_name=="web_search_retriever":
research_query=tool_input.get("research_query", "")
subqueries_json=tool_input.get("subqueries_json", "")
returnself.web_search_retriever.retrieve(research_query, subqueries_json)
else:
returnf"Error: Unknown tool '{tool_name}'"defresearch(self, query: str, max_iterations: int=10) ->str:
""" Conduct research on a given query using Minimax M2 with interleaved thinking. Args: query: Research question or topic max_iterations: Maximum number of agent iterations Returns: Comprehensive research report """# Initialize conversation with user queryself.messages= [
{
"role": "user",
"content": query,
}
]
iteration=0whileiteration<max_iterations:
iteration+=1try:
# Call Minimax M2 with streaming for long requestsconsole.print(f"[bold magenta][Iteration {iteration}][/bold magenta] [cyan]Calling Minimax M2...[/cyan]")
withself.client.messages.stream(
model=self.model,
max_tokens=32000,
system=self.system_prompt,
messages=self.messages,
tools=self.tools,
) asstream:
foreventinstream:
ifhasattr(event, 'type') andevent.type=='content_block_start':
console.print("[green].[/green]", end="")
response=stream.get_final_message()
console.print()
# CRITICAL: Append the COMPLETE response to message history# This preserves the interleaved thinking across turnsassistant_message= {
"role": "assistant",
"content": response.content, # Includes thinking, text, and tool_use blocks
}
self.messages.append(assistant_message)
# Check stop reasonifresponse.stop_reason=="end_turn":
# Model has finished - extract final responsefinal_text=self._extract_text_from_content(response.content)
returnfinal_textelifresponse.stop_reason=="tool_use":
# Model wants to use tools - execute themnum_tools=len([bforbinresponse.contentifhasattr(b, 'type') andb.type=='tool_use'])
console.print(f"[bold blue][Tool execution][/bold blue] M2 requested [yellow]{num_tools}[/yellow] tool(s)")
tool_results= []
forcontent_blockinresponse.content:
ifcontent_block.type=="tool_use":
tool_name=content_block.nametool_input=content_block.inputtool_use_id=content_block.idconsole.print(f"[dim] → Executing:[/dim] [cyan]{tool_name}[/cyan]")
# Execute the toolresult=self.execute_tool(tool_name, tool_input)
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": result,
})
# Add tool results to conversationself.messages.append({
"role": "user",
"content": tool_results,
})
else:
# Unexpected stop reasonconsole.print(f"[bold red]⚠ Unexpected stop reason:[/bold red] {response.stop_reason}")
returnf"Research stopped unexpectedly: {response.stop_reason}"exceptExceptionase:
console.print(f"[bold red]✗ Error during research:[/bold red] {str(e)}")
returnf"Error during research: {str(e)}"console.print(f"[bold yellow]⚠ Research reached maximum iterations ({max_iterations}) without completion.[/bold yellow]")
return"Research reached maximum iterations without completion."def_extract_text_from_content(self, content: List[Any]) ->str:
""" Extract text content from response content blocks. Args: content: List of content blocks from API response Returns: Combined text content """text_parts= []
forblockincontent:
ifhasattr(block, "type") andblock.type=="text":
text_parts.append(block.text)
return"\n\n".join(text_parts) iftext_partselse"No text content in response."defget_conversation_history(self) ->List[Dict[str, Any]]:
""" Get the complete conversation history including thinking blocks. Returns: List of message dictionaries """returnself.messages
File: src/agents/web_search_retriever.py
"""Web Search Retriever Agent using Exa API."""importjsonimporthttpxfromtypingimportDict, Any, Listfromsrc.tools.exa_toolimportExaToolfromsrc.utils.configimportConfigclassWebSearchRetriever:
""" Agent that executes Exa searches for provided subqueries and synthesizes findings. Uses OpenRouter with Gemini 2.5 Flash for synthesis. """def__init__(self):
"""Initialize Web Search Retriever."""self.exa=ExaTool()
self.api_key=Config.OPENROUTER_API_KEYself.base_url=Config.OPENROUTER_BASE_URLself.model=Config.OPENROUTER_MODELself.system_prompt="""You are a web search retrieval specialist.Your job is to:1. Execute Exa searches for each provided subquery2. Use find_similar() on the best results to discover related content3. Organize findings by relevance and topic4. Extract key insights from the search resultsReturn structured results with:- URLs and titles- Summaries of key findings- Relevant quotes/highlights- How each source contributes to answering the research queryBe comprehensive but focused. Prioritize high-quality, authoritative sources."""defsearch_with_subqueries(self, subqueries: List[Dict[str, Any]]) ->List[Dict[str, Any]]:
""" Execute Exa searches for all subqueries. Args: subqueries: List of subquery dictionaries from planning agent Returns: List of search results for each subquery """all_results= []
forsubqueryinsubqueries:
query_text=subquery.get("query", "")
content_type=subquery.get("type", "auto")
time_period=subquery.get("time_period", "any")
include_domains=subquery.get("include_domains")
exclude_domains=subquery.get("exclude_domains")
priority=subquery.get("priority", 3)
# Map time period to date filters (approximate)start_date=Noneiftime_period=="recent"ortime_period=="past_week":
# Exa uses ISO format datesstart_date="2025-11-17T00:00:00.000Z"# Approximate for recenteliftime_period=="past_month":
start_date="2025-10-24T00:00:00.000Z"eliftime_period=="past_year":
start_date="2024-11-24T00:00:00.000Z"# Execute search with more results for deeper researchsearch_results=self.exa.search(
query=query_text,
num_results=20ifpriority<=2else15, # Increased for deeper researchstart_published_date=start_date,
include_domains=include_domains,
exclude_domains=exclude_domains,
type=content_type,
)
# Format resultsformatted_results=self.exa.format_results(search_results)
# Find similar content for more queries (priority <= 3 instead of <= 2)similar_results= []
ifformatted_resultsandpriority<=3:
top_url=formatted_results[0].get("url")
iftop_url:
similar_response=self.exa.find_similar(
url=top_url,
num_results=5, # Increased from 3 to 5
)
similar_results=self.exa.format_results(similar_response)
all_results.append({
"subquery": query_text,
"priority": priority,
"results": formatted_results,
"similar_results": similar_results,
})
returnall_resultsdefsynthesize_findings(
self,
research_query: str,
search_results: List[Dict[str, Any]]
) ->str:
""" Use LLM to synthesize search results into organized findings. Args: research_query: Original research query search_results: Results from Exa searches Returns: Synthesized findings as a string """# Prepare context from search resultscontext_parts= []
forresult_setinsearch_results:
subquery=result_set.get("subquery", "")
results=result_set.get("results", [])
context_parts.append(f"\n## Subquery: {subquery}")
fori, resultinenumerate(results[:10], 1): # Top 10 per subquery for deeper researchtitle=result.get("title", "No title")
url=result.get("url", "")
highlights=result.get("highlights", [])
text_excerpt=result.get("text", "")[:1000] # Increased to 1000 chars for more contextcontext_parts.append(f"\n### Result {i}: {title}")
context_parts.append(f"URL: {url}")
ifhighlights:
context_parts.append(f"Highlights: {', '.join(highlights[:5])}") # More highlightsiftext_excerpt:
context_parts.append(f"Excerpt: {text_excerpt}...")
context="\n".join(context_parts)
# Create synthesis requestheaders= {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
}
payload= {
"model": self.model,
"messages": [
{"role": "system", "content": self.system_prompt},
{
"role": "user",
"content": f"""Research Query: {research_query}Search Results:{context}Organize these findings into a comprehensive, detailed summary. Include:1. Key findings organized by topic/theme with extensive details2. Important sources with URLs and brief descriptions3. Relevant quotes and highlights with context4. How the sources address the research query5. Connections and patterns across different sources6. Notable experts, institutions, or authoritative voices7. Data, statistics, or concrete examples when availableBe thorough and detailed - this will feed into a comprehensive research report.""",
},
],
"temperature": 0.5,
"max_tokens": 6000, # Increased for more comprehensive synthesis
}
try:
withhttpx.Client(timeout=60.0) asclient:
response=client.post(
f"{self.base_url}/chat/completions",
json=payload,
headers=headers,
)
response.raise_for_status()
result=response.json()
returnresult["choices"][0]["message"]["content"]
exceptExceptionase:
returnf"Error synthesizing findings: {str(e)}"defretrieve(self, research_query: str, subqueries_json: str) ->str:
""" Execute web search retrieval for given subqueries. This method is called by the supervisor via tool execution. Args: research_query: Original research query subqueries_json: JSON string containing subqueries from planning agent Returns: Synthesized findings as a string """try:
# Parse subqueriessubqueries_data=json.loads(subqueries_json)
subqueries=subqueries_data.get("subqueries", [])
ifnotsubqueries:
return"Error: No subqueries provided"# Execute searchessearch_results=self.search_with_subqueries(subqueries)
# Synthesize findingsfindings=self.synthesize_findings(research_query, search_results)
returnfindingsexceptjson.JSONDecodeErrorase:
returnf"Error parsing subqueries: {str(e)}"exceptExceptionase:
returnf"Error in retrieval: {str(e)}"
File: src/tools/exa_tool.py
"""Exa API wrapper for neural web search."""importhttpxfromtypingimportList, Dict, Any, Optionalfromsrc.utils.configimportConfigclassExaTool:
"""Wrapper for Exa API endpoints."""def__init__(self):
"""Initialize Exa API client."""self.api_key=Config.EXA_API_KEYself.base_url=Config.EXA_BASE_URLself.headers= {
"x-api-key": self.api_key,
"Content-Type": "application/json",
}
defsearch(
self,
query: str,
num_results: int=10,
start_published_date: Optional[str] =None,
end_published_date: Optional[str] =None,
include_domains: Optional[List[str]] =None,
exclude_domains: Optional[List[str]] =None,
type: str="auto",
use_autoprompt: bool=True,
text: bool=True,
highlights: bool=True,
) ->Dict[str, Any]:
""" Perform neural search using Exa API. Args: query: Search query num_results: Number of results to return start_published_date: Filter results published after this date (ISO format) end_published_date: Filter results published before this date (ISO format) include_domains: List of domains to include exclude_domains: List of domains to exclude type: Content type filter (auto, news, research paper, pdf, etc.) use_autoprompt: Let Exa optimize the query text: Include full text content highlights: Include relevant highlights Returns: Dictionary containing search results """url=f"{self.base_url}/search"payload= {
"query": query,
"numResults": num_results,
"useAutoprompt": use_autoprompt,
"type": type,
"contents": {
"text": text,
"highlights": highlights,
},
}
ifstart_published_date:
payload["startPublishedDate"] =start_published_dateifend_published_date:
payload["endPublishedDate"] =end_published_dateifinclude_domains:
payload["includeDomains"] =include_domainsifexclude_domains:
payload["excludeDomains"] =exclude_domainstry:
withhttpx.Client(timeout=30.0) asclient:
response=client.post(url, json=payload, headers=self.headers)
response.raise_for_status()
returnresponse.json()
excepthttpx.HTTPErrorase:
return {
"error": str(e),
"status": "failed",
"results": [],
}
deffind_similar(
self,
url: str,
num_results: int=5,
exclude_source_domain: bool=True,
text: bool=True,
highlights: bool=True,
) ->Dict[str, Any]:
""" Find similar content to a given URL using Exa's neural similarity search. Args: url: URL to find similar content for num_results: Number of similar results to return exclude_source_domain: Exclude results from the same domain text: Include full text content highlights: Include relevant highlights Returns: Dictionary containing similar search results """api_url=f"{self.base_url}/findSimilar"payload= {
"url": url,
"numResults": num_results,
"excludeSourceDomain": exclude_source_domain,
"contents": {
"text": text,
"highlights": highlights,
},
}
try:
withhttpx.Client(timeout=30.0) asclient:
response=client.post(api_url, json=payload, headers=self.headers)
response.raise_for_status()
returnresponse.json()
excepthttpx.HTTPErrorase:
return {
"error": str(e),
"status": "failed",
"results": [],
}
defget_contents(
self,
ids: List[str],
text: bool=True,
highlights: bool=True,
) ->Dict[str, Any]:
""" Get full content for specific result IDs. Args: ids: List of Exa result IDs text: Include full text content highlights: Include relevant highlights Returns: Dictionary containing content for the specified IDs """url=f"{self.base_url}/contents"payload= {
"ids": ids,
"contents": {
"text": text,
"highlights": highlights,
},
}
try:
withhttpx.Client(timeout=30.0) asclient:
response=client.post(url, json=payload, headers=self.headers)
response.raise_for_status()
returnresponse.json()
excepthttpx.HTTPErrorase:
return {
"error": str(e),
"status": "failed",
"contents": [],
}
defformat_results(self, results: Dict[str, Any]) ->List[Dict[str, Any]]:
""" Format Exa search results into a standardized structure. Args: results: Raw results from Exa API Returns: List of formatted result dictionaries """if"error"inresultsor"results"notinresults:
return []
formatted= []
forresultinresults.get("results", []):
formatted_result= {
"title": result.get("title", "No title"),
"url": result.get("url", ""),
"author": result.get("author"),
"published_date": result.get("publishedDate"),
"score": result.get("score", 0),
"text": result.get("text", ""),
"highlights": result.get("highlights", []),
"summary": result.get("summary", ""),
}
formatted.append(formatted_result)
returnformatted
File: src/utils/config.py
"""Configuration management for Deep Research Agent."""importosfromdotenvimportload_dotenv# Load environment variables from .env fileload_dotenv()
classConfig:
"""Configuration class for managing API keys and settings."""# Minimax API ConfigurationMINIMAX_API_KEY=os.getenv("MINIMAX_API_KEY")
MINIMAX_BASE_URL="https://api.minimax.io/anthropic"MINIMAX_MODEL="MiniMax-M2"# OpenRouter API ConfigurationOPENROUTER_API_KEY=os.getenv("OPENROUTER_API_KEY")
OPENROUTER_BASE_URL="https://openrouter.ai/api/v1"OPENROUTER_MODEL="google/gemini-2.5-flash"# Exa API ConfigurationEXA_API_KEY=os.getenv("EXA_API_KEY")
EXA_BASE_URL="https://api.exa.ai"@classmethoddefvalidate(cls):
"""Validate that all required API keys are set."""missing= []
ifnotcls.MINIMAX_API_KEY:
missing.append("MINIMAX_API_KEY")
ifnotcls.OPENROUTER_API_KEY:
missing.append("OPENROUTER_API_KEY")
ifnotcls.EXA_API_KEY:
missing.append("EXA_API_KEY")
ifmissing:
raiseValueError(
f"Missing required API keys: {', '.join(missing)}. "f"Please set them in your .env file."
)
returnTrue# Validate configuration on importtry:
Config.validate()
exceptValueErrorase:
print(f"Configuration Error: {e}")
print("Please copy .env.example to .env and fill in your API keys.")
File: .env.example
# Minimax API Configuration (for Supervisor Agent with M2 model)
MINIMAX_API_KEY=your_minimax_api_key_here
# OpenRouter API Configuration (for Planning Agent and Web Search Retriever)
OPENROUTER_API_KEY=your_openrouter_api_key_here
# Exa API Configuration (for web search)
EXA_API_KEY=your_exa_api_key_here
# MiniMax-M2 Deep Research Agent
A sophisticated research tool powered by **Minimax M2** with interleaved thinking, **Exa** neural search, and multi-agent orchestration.
## Features-**Minimax M2 Supervisor**: Uses interleaved thinking to maintain reasoning state across multi-step research
-**Intelligent Planning**: Automatically decomposes research queries into optimized subqueries
-**Neural Web Search**: Leverages Exa API for high-quality, AI-powered web search
-**Comprehensive Reports**: Generates detailed research reports with citations and analysis
-**CLI Interface**: Simple command-line interface with interactive and single-query modes
## Architecture```+-----------------------------------------------+| Supervisor Agent || (Minimax M2 + Interleaved Thinking) |+-----------------------------------------------+ | +--------------+--------------+ | | | v v v+------------+ +-------------+ +-----------+| Planning | | Web Search | | Synthesis || Agent | | Retriever | | (M2) || (Gemini) | | | | |+------------+ +-------------+ +-----------+ | v +------------+ | Exa API | +------------+```### Agent Descriptions1.**Supervisor Agent**:
- Uses Minimax M2 with Anthropic SDK
- Implements interleaved thinking (preserves reasoning across turns)
- Coordinates planning, search, and synthesis
- Generates final comprehensive research report
2.**Planning Agent**:
- Uses Gemini 2.5 Flash via OpenRouter
- Generates 3-5 Exa-optimized subqueries
- Considers time periods, domains, content types, and priorities
3.**Web Search Retriever**:
- Uses Gemini 2.5 Flash for synthesis
- Executes Exa searches with neural search capabilities
- Finds similar content using Exa's similarity search
- Organizes findings with sources and highlights
## Installation### Prerequisites- Python 3.9+
-[uv](https://github.com/astral-sh/uv) package manager
- API keys for:
- Minimax (M2 model)
- OpenRouter (for Gemini)
- Exa (web search)
### Setup1.**Install dependencies**:
```bashcd deep-research-agent
uv sync
```2.**Configure environment variables**:
```bash# Copy the example file
cp .env.example .env
# Edit .env and add your API keys
MINIMAX_API_KEY=your_minimax_api_key_here
OPENROUTER_API_KEY=your_openrouter_api_key_here
EXA_API_KEY=your_exa_api_key_here
```3.**Activate virtual environment** (if needed):
```bashsource .venv/bin/activate
```## Usage### Interactive Mode
Run without arguments to enter interactive mode:
```bash
python main.py
```
Then enter your research queries at the prompt:
```Research Query: What are the latest developments in quantum computing?```### Single Query Mode
Run a single research query:
```bash
python main.py -q "What are the latest developments in quantum computing?"```### Save Report to File
Save the research report automatically:
```bash
python main.py -q "AI trends in 2025" --save
```### Verbose Mode
Show detailed progress and thinking blocks:
```bash
python main.py -q "Climate change solutions" --verbose
```### Interactive Mode Commands
While in interactive mode, you can use these commands:
-`/save <query>` - Save the report to a file
-`/verbose <query>` - Show detailed progress
-`/help` - Show help message
-`exit`, `quit`, or `q` - Exit the program
## How It Works### 1. Query Planning
When you submit a research query, the **Planning Agent** decomposes it into 3-5 optimized subqueries:
```json
{
"subqueries": [
{
"query": "quantum computing breakthroughs 2025",
"type": "news",
"time_period": "recent",
"priority": 1
},
{
"query": "quantum computing applications cryptography",
"type": "auto",
"time_period": "any",
"priority": 2
}
]
}
```### 2. Web Search
The **Web Search Retriever** executes each subquery using Exa:
- Performs neural search for each subquery
- Finds similar content for high-priority results
- Extracts highlights and key information
- Organizes findings by relevance
### 3. Synthesis
The **Supervisor Agent** (using Minimax M2 with interleaved thinking):
- Receives all search findings
- Maintains reasoning state across the research process
- Synthesizes a comprehensive report with:
- Executive summary
- Key findings organized by theme
- Detailed analysis
- Cited sources with URLs
### 4. Interleaved Thinking
The key innovation is **interleaved thinking**:
- The supervisor preserves ALL content blocks (thinking + text + tool_use) in conversation history
- This maintains the reasoning chain across multiple turns
- Results in more coherent, contextualized research reports
- Prevents "state drift" in multi-step workflows
## Project Structure```deep-research-agent/├── README.md # This file├── .env.example # Environment variables template├── .env # Your API keys (create this)├── pyproject.toml # Project dependencies├── main.py # CLI entry point└── src/ ├── agents/ │ ├── supervisor.py # Minimax M2 supervisor │ ├── planning_agent.py # Query planning │ └── web_search_retriever.py # Exa search integration ├── tools/ │ └── exa_tool.py # Exa API wrapper └── utils/ └── config.py # Configuration management```## API Keys### Getting API Keys1.**Minimax M2**: Sign up at [platform.minimax.io](https://platform.minimax.io)2.**OpenRouter**: Get key at [openrouter.ai](https://openrouter.ai)3.**Exa**: Register at [exa.ai](https://exa.ai)### Why These Services?-**Minimax M2**: Advanced reasoning model with native interleaved thinking support
-**OpenRouter**: Unified API for accessing Gemini and other LLMs
-**Exa**: Neural search engine optimized for research and discovery
## Examples### Example 1: Technology Research```bash
python main.py -q "What are the latest breakthroughs in artificial general intelligence?"```**Output**: Comprehensive report covering recent AGI developments, key research papers, major announcements, and expert opinions with citations.
### Example 2: Business Intelligence```bash
python main.py -q "What are the emerging trends in electric vehicle adoption?" --save
```**Output**: Market analysis with statistics, industry trends, regional adoption rates, and future projections. Report saved to file.
### Example 3: Scientific Research```bash
python main.py -q "What are the most promising approaches to carbon capture technology?" --verbose
```**Output**: Technical analysis of carbon capture methods with detailed thinking process visible.
## Advanced Usage### Customizing Prompts
Edit the system prompts in:
-`src/agents/supervisor.py` - Main coordinator logic
-`src/agents/planning_agent.py` - Query decomposition strategy
-`src/agents/web_search_retriever.py` - Search and synthesis approach
### Adjusting Search Parameters
Modify Exa search parameters in `src/agents/web_search_retriever.py`:
-`num_results`: Number of results per query (default: 5-10)
-`time_period`: Date filtering (recent, past_week, past_month, past_year, any)
-`content_type`: Filter by type (news, research paper, pdf, blog, etc.)
## Troubleshooting### Configuration Errors
If you see "Missing required API keys":
1. Ensure `.env` file exists (copy from `.env.example`)
2. Verify all API keys are set correctly
3. Check that API keys don't have extra spaces or quotes
### API Errors
If you encounter API errors:
1. Verify your API keys are valid and active
2. Check your API rate limits and quotas
3. Ensure you have sufficient credits/balance
### Import Errors
If you see module import errors:
1. Activate the virtual environment: `source .venv/bin/activate`2. Install dependencies: `uv sync`3. Ensure you're running from the project root directory
## Performance- Average research query: 30-60 seconds
- Depends on:
- Number of subqueries (3-5)
- Complexity of search results
- LLM response times
## Future Enhancements
Potential improvements:
-[ ] Support for additional search engines (Tavily, Perplexity)
-[ ] PDF and document upload for context
-[ ] Multi-turn conversations with follow-up questions
-[ ] Export to different formats (PDF, Markdown, JSON)
-[ ] Web UI interface
-[ ] Caching and result persistence
-[ ] Custom research templates
## License
MIT License - feel free to use and modify for your own projects.
## Acknowledgments
Built with:
-[Minimax M2](https://www.minimax.io/) - Advanced reasoning model
-[Exa](https://exa.ai/) - Neural web search
-[Anthropic SDK](https://github.com/anthropics/anthropic-sdk-python) - API client
-[OpenRouter](https://openrouter.ai/) - LLM routing