pkgprateek commited on
Commit
0b2427a
·
unverified ·
1 Parent(s): 790b5af

Phase 2: Multi-agent implementation complete (#1)

Browse files

Implemented complete multi-agent system with three specialized agents:

Research Agent:
- Tavily search integration for web research
- Specialized methods for company info, competitors, market trends
- Gathers and formats data from 20+ sources

Analysis Agent:
- SWOT analysis generation
- Competitive matrix creation
- Market positioning analysis
- Strategic recommendations with priority levels

Writer Agent:
- Executive summary generation (200-300 words)
- Full markdown report creation
- Professional formatting with citations
- Metadata tracking

Core Infrastructure:
- BaseAgent class with OpenRouter LLM integration
- Cost tracking for all agent operations
- Tavily search tool wrapper
- Structured prompts for each agent role

Testing:
- 18/18 unit tests passing
- Complete pipeline test successful on real query
- Generated professional report with 28 sources
- Free tier model working (Grok) - /bin/zsh cost

Files Added:
- src/agents/base.py - Base agent class
- src/agents/researcher.py - Research agent
- src/agents/analyst.py - Analysis agent
- src/agents/writer.py - Writer agent
- src/tools/search.py - Tavily search wrapper
- tests/unit/test_base_agent.py - Agent tests
- test_agents.py - Complete pipeline test

Ready for Phase 3: LangGraph workflow orchestration

src/agents/analyst.py ADDED
@@ -0,0 +1,313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Analysis Agent for competitive intelligence and SWOT analysis."""
2
+
3
+ from typing import Any, Dict, List, Optional
4
+
5
+ from src.agents.base import BaseAgent
6
+ from src.utils.cost_tracker import CostTracker
7
+ from src.utils.logging import setup_logger
8
+
9
+ logger = setup_logger(__name__)
10
+
11
+
12
+ class AnalysisAgent(BaseAgent):
13
+ """
14
+ Analysis Agent responsible for strategic business analysis.
15
+
16
+ Takes research data and produces:
17
+ - SWOT analysis
18
+ - Competitive matrix
19
+ - Market positioning analysis
20
+ - Strategic recommendations
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ model: Optional[str] = None,
26
+ temperature: float = 0.4, # Balanced for analytical reasoning
27
+ cost_tracker: Optional[CostTracker] = None,
28
+ ):
29
+ """
30
+ Initialize Analysis Agent.
31
+
32
+ Args:
33
+ model: LLM model to use
34
+ temperature: Sampling temperature
35
+ cost_tracker: Cost tracker instance
36
+ """
37
+ super().__init__(
38
+ name="AnalysisAgent",
39
+ model=model,
40
+ temperature=temperature,
41
+ cost_tracker=cost_tracker,
42
+ )
43
+
44
+ def get_system_prompt(self) -> str:
45
+ """Get system prompt for analysis agent."""
46
+ return """You are a strategic business analyst specializing in competitive intelligence and market analysis.
47
+
48
+ Your role is to analyze research data and provide actionable strategic insights.
49
+
50
+ When performing analysis, you should:
51
+ 1. Use structured frameworks (SWOT, competitive matrices, positioning maps)
52
+ 2. Identify clear patterns and trends
53
+ 3. Provide specific, actionable recommendations
54
+ 4. Support conclusions with evidence from the research
55
+ 5. Consider multiple perspectives (competitors, customers, market forces)
56
+
57
+ Your analysis should be:
58
+ - Objective and data-driven
59
+ - Structured and easy to scan
60
+ - Focused on business impact
61
+ - Actionable for decision-makers
62
+
63
+ Use bullet points, clear headings, and strategic language."""
64
+
65
+ async def run(
66
+ self,
67
+ research_data: Dict[str, Any],
68
+ ) -> Dict[str, Any]:
69
+ """
70
+ Perform comprehensive analysis on research data.
71
+
72
+ Args:
73
+ research_data: Output from ResearchAgent containing:
74
+ - company_overview
75
+ - competitors
76
+ - market_trends
77
+
78
+ Returns:
79
+ Dictionary with analysis results:
80
+ - swot: SWOT analysis
81
+ - competitive_matrix: Competitor comparison
82
+ - positioning: Market positioning analysis
83
+ - strategic_recommendations: Action items
84
+ """
85
+ company_name = research_data.get("company_name", "Unknown Company")
86
+ logger.info(f"Starting analysis for: {company_name}")
87
+
88
+ results = {
89
+ "company_name": company_name,
90
+ "swot": "",
91
+ "competitive_matrix": "",
92
+ "positioning": "",
93
+ "strategic_recommendations": "",
94
+ }
95
+
96
+ try:
97
+ # 1. SWOT Analysis
98
+ swot = await self._perform_swot_analysis(research_data)
99
+ results["swot"] = swot
100
+
101
+ # 2. Competitive Matrix
102
+ matrix = await self._create_competitive_matrix(research_data)
103
+ results["competitive_matrix"] = matrix
104
+
105
+ # 3. Market Positioning
106
+ positioning = await self._analyze_market_positioning(research_data)
107
+ results["positioning"] = positioning
108
+
109
+ # 4. Strategic Recommendations
110
+ recommendations = await self._generate_recommendations(research_data, swot)
111
+ results["strategic_recommendations"] = recommendations
112
+
113
+ logger.info(f"Analysis complete for {company_name}")
114
+
115
+ return results
116
+
117
+ except Exception as e:
118
+ logger.error(f"Analysis failed for {company_name}: {e}")
119
+ raise
120
+
121
+ async def _perform_swot_analysis(
122
+ self,
123
+ research_data: Dict[str, Any],
124
+ ) -> str:
125
+ """
126
+ Generate SWOT analysis from research data.
127
+
128
+ Args:
129
+ research_data: Research results
130
+
131
+ Returns:
132
+ SWOT analysis text
133
+ """
134
+ company_name = research_data.get("company_name")
135
+ company_overview = research_data.get("company_overview", "")
136
+ competitors = research_data.get("competitors", "")
137
+ market_trends = research_data.get("market_trends", "")
138
+
139
+ user_message = f"""Based on the research data, perform a comprehensive SWOT analysis for {company_name}.
140
+
141
+ Research Data:
142
+
143
+ COMPANY OVERVIEW:
144
+ {company_overview}
145
+
146
+ COMPETITORS:
147
+ {competitors}
148
+
149
+ MARKET TRENDS:
150
+ {market_trends}
151
+
152
+ Provide a detailed SWOT analysis with:
153
+
154
+ STRENGTHS (internal positive factors):
155
+ - List 4-6 key strengths
156
+ - Focus on competitive advantages, resources, capabilities
157
+
158
+ WEAKNESSES (internal negative factors):
159
+ - List 4-6 key weaknesses
160
+ - Include operational limits, resource constraints, vulnerabilities
161
+
162
+ OPPORTUNITIES (external positive factors):
163
+ - List 4-6 market opportunities
164
+ - Consider market trends, gaps, emerging needs
165
+
166
+ THREATS (external negative factors):
167
+ - List 4-6 threats
168
+ - Include competitive threats, market risks, industry challenges
169
+
170
+ Use bullet points and be specific with evidence."""
171
+
172
+ messages = self._create_messages(user_message)
173
+ response = await self._invoke_llm(messages)
174
+
175
+ return response
176
+
177
+ async def _create_competitive_matrix(
178
+ self,
179
+ research_data: Dict[str, Any],
180
+ ) -> str:
181
+ """
182
+ Create competitive comparison matrix.
183
+
184
+ Args:
185
+ research_data: Research results
186
+
187
+ Returns:
188
+ Competitive matrix as formatted text
189
+ """
190
+ company_name = research_data.get("company_name")
191
+ competitors_info = research_data.get("competitors", "")
192
+
193
+ user_message = f"""Based on the competitor research, create a competitive matrix comparing {company_name} with its main competitors.
194
+
195
+ Competitor Research:
196
+ {competitors_info}
197
+
198
+ Create a comparison matrix with these dimensions:
199
+ 1. Market Share/Size
200
+ 2. Product Range
201
+ 3. Pricing Strategy
202
+ 4. Technology/Innovation
203
+ 5. Customer Segments
204
+ 6. Strengths
205
+ 7. Weaknesses
206
+
207
+ Format as a clear table or structured comparison.
208
+ Include 3-5 main competitors plus {company_name}.
209
+ Use "High/Medium/Low" or specific data points where available."""
210
+
211
+ messages = self._create_messages(user_message)
212
+ response = await self._invoke_llm(messages)
213
+
214
+ return response
215
+
216
+ async def _analyze_market_positioning(
217
+ self,
218
+ research_data: Dict[str, Any],
219
+ ) -> str:
220
+ """
221
+ Analyze market positioning strategy.
222
+
223
+ Args:
224
+ research_data: Research results
225
+
226
+ Returns:
227
+ Positioning analysis
228
+ """
229
+ company_name = research_data.get("company_name")
230
+ company_overview = research_data.get("company_overview", "")
231
+ competitors = research_data.get("competitors", "")
232
+
233
+ user_message = f"""Analyze the market positioning of {company_name}.
234
+
235
+ Company Overview:
236
+ {company_overview}
237
+
238
+ Competitive Landscape:
239
+ {competitors}
240
+
241
+ Provide analysis covering:
242
+
243
+ 1. CURRENT POSITIONING
244
+ - How is {company_name} currently positioned in the market?
245
+ - What is their value proposition?
246
+ - What customer segments do they target?
247
+
248
+ 2. COMPETITIVE DIFFERENTIATION
249
+ - What makes {company_name} different from competitors?
250
+ - What is their unique selling proposition (USP)?
251
+ - Where do they fit in the competitive landscape?
252
+
253
+ 3. POSITIONING GAPS
254
+ - Are there market segments they're missing?
255
+ - Are there positioning opportunities?
256
+ - How could they strengthen their position?
257
+
258
+ Be specific and strategic."""
259
+
260
+ messages = self._create_messages(user_message)
261
+ response = await self._invoke_llm(messages)
262
+
263
+ return response
264
+
265
+ async def _generate_recommendations(
266
+ self,
267
+ research_data: Dict[str, Any],
268
+ swot: str,
269
+ ) -> str:
270
+ """
271
+ Generate strategic recommendations.
272
+
273
+ Args:
274
+ research_data: Research results
275
+ swot: SWOT analysis
276
+
277
+ Returns:
278
+ Strategic recommendations
279
+ """
280
+ company_name = research_data.get("company_name")
281
+ market_trends = research_data.get("market_trends", "")
282
+
283
+ user_message = f"""Based on the SWOT analysis and market trends, provide strategic recommendations for {company_name}.
284
+
285
+ SWOT ANALYSIS:
286
+ {swot}
287
+
288
+ MARKET TRENDS:
289
+ {market_trends}
290
+
291
+ Provide 5-7 actionable strategic recommendations organized by priority:
292
+
293
+ HIGH PRIORITY (immediate action needed):
294
+ - Recommendation 1 (with rationale)
295
+ - Recommendation 2 (with rationale)
296
+
297
+ MEDIUM PRIORITY (next 6-12 months):
298
+ - Recommendation 3 (with rationale)
299
+ - Recommendation 4 (with rationale)
300
+
301
+ LONG-TERM (strategic initiatives):
302
+ - Recommendation 5 (with rationale)
303
+
304
+ Each recommendation should:
305
+ - Be specific and actionable
306
+ - Address a key opportunity or threat
307
+ - Leverage strengths or address weaknesses
308
+ - Include expected impact"""
309
+
310
+ messages = self._create_messages(user_message)
311
+ response = await self._invoke_llm(messages)
312
+
313
+ return response
src/agents/base.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Base agent class for all agents in the system."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, Dict, Optional
5
+
6
+ from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
7
+ from langchain_openai import ChatOpenAI
8
+
9
+ from src.utils.config import get_settings
10
+ from src.utils.cost_tracker import CostTracker
11
+ from src.utils.logging import setup_logger
12
+
13
+ logger = setup_logger(__name__)
14
+
15
+
16
+ class BaseAgent(ABC):
17
+ """
18
+ Base class for all agents in the multi-agent system.
19
+
20
+ Provides common functionality for LLM interaction, cost tracking,
21
+ and error handling.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ name: str,
27
+ model: Optional[str] = None,
28
+ temperature: float = 0.7,
29
+ cost_tracker: Optional[CostTracker] = None,
30
+ ):
31
+ """
32
+ Initialize base agent.
33
+
34
+ Args:
35
+ name: Agent name for logging
36
+ model: LLM model to use (defaults to config default)
37
+ temperature: LLM sampling temperature (0-1)
38
+ cost_tracker: Optional cost tracker instance
39
+ """
40
+ self.name = name
41
+ self.cost_tracker = cost_tracker or CostTracker()
42
+
43
+ settings = get_settings()
44
+ self.model_name = model or settings.default_model
45
+
46
+ # Initialize LLM via OpenRouter
47
+ self.llm = ChatOpenAI(
48
+ model=self.model_name,
49
+ temperature=temperature,
50
+ openai_api_key=settings.openrouter_api_key,
51
+ openai_api_base=settings.openrouter_base_url,
52
+ )
53
+
54
+ logger.info(f"Initialized {name} with model {self.model_name}")
55
+
56
+ @abstractmethod
57
+ def get_system_prompt(self) -> str:
58
+ """
59
+ Get the system prompt for this agent.
60
+
61
+ Returns:
62
+ System prompt string
63
+ """
64
+ pass
65
+
66
+ @abstractmethod
67
+ async def run(self, **kwargs) -> Dict[str, Any]:
68
+ """
69
+ Execute the agent's main task.
70
+
71
+ Args:
72
+ **kwargs: Agent-specific parameters
73
+
74
+ Returns:
75
+ Dictionary with results
76
+ """
77
+ pass
78
+
79
+ async def _invoke_llm(
80
+ self,
81
+ messages: list[BaseMessage],
82
+ **llm_kwargs,
83
+ ) -> str:
84
+ """
85
+ Invoke LLM and track costs.
86
+
87
+ Args:
88
+ messages: List of messages to send
89
+ **llm_kwargs: Additional LLM parameters
90
+
91
+ Returns:
92
+ LLM response text
93
+ """
94
+ try:
95
+ response = await self.llm.ainvoke(messages, **llm_kwargs)
96
+
97
+ # Track usage if available
98
+ if hasattr(response, "response_metadata"):
99
+ usage = response.response_metadata.get("usage", {})
100
+ if usage:
101
+ self.cost_tracker.track_usage(
102
+ model=self.model_name,
103
+ input_tokens=usage.get("prompt_tokens", 0),
104
+ output_tokens=usage.get("completion_tokens", 0),
105
+ )
106
+
107
+ logger.info(
108
+ f"{self.name} LLM call complete",
109
+ extra={
110
+ "extra_fields": {
111
+ "model": self.model_name,
112
+ "total_cost": self.cost_tracker.total_cost,
113
+ }
114
+ },
115
+ )
116
+
117
+ return response.content
118
+
119
+ except Exception as e:
120
+ logger.error(f"{self.name} LLM call failed: {e}")
121
+ raise
122
+
123
+ def _create_messages(
124
+ self,
125
+ user_message: str,
126
+ system_prompt: Optional[str] = None,
127
+ ) -> list[BaseMessage]:
128
+ """
129
+ Create message list for LLM.
130
+
131
+ Args:
132
+ user_message: User message content
133
+ system_prompt: Optional system prompt (uses default if None)
134
+
135
+ Returns:
136
+ List of messages
137
+ """
138
+ messages = []
139
+
140
+ # Add system message
141
+ prompt = system_prompt or self.get_system_prompt()
142
+ messages.append(SystemMessage(content=prompt))
143
+
144
+ # Add user message
145
+ messages.append(HumanMessage(content=user_message))
146
+
147
+ return messages
148
+
149
+ def get_cost_summary(self) -> Dict[str, Any]:
150
+ """
151
+ Get cost summary for this agent's operations.
152
+
153
+ Returns:
154
+ Cost summary dictionary
155
+ """
156
+ return self.cost_tracker.get_summary()
src/agents/researcher.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Research Agent for gathering market intelligence data."""
2
+
3
+ from typing import Any, Dict, Optional
4
+
5
+ from src.agents.base import BaseAgent
6
+ from src.tools.search import TavilySearchTool
7
+ from src.utils.cost_tracker import CostTracker
8
+ from src.utils.logging import setup_logger
9
+
10
+ logger = setup_logger(__name__)
11
+
12
+
13
+ class ResearchAgent(BaseAgent):
14
+ """
15
+ Research Agent responsible for gathering data from web sources.
16
+
17
+ Uses Tavily API for web search and can gather:
18
+ - Company information
19
+ - Competitor analysis data
20
+ - Market trends and insights
21
+ - Industry news
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ model: Optional[str] = None,
27
+ temperature: float = 0.3, # Lower for more factual responses
28
+ cost_tracker: Optional[CostTracker] = None,
29
+ ):
30
+ """
31
+ Initialize Research Agent.
32
+
33
+ Args:
34
+ model: LLM model to use
35
+ temperature: Sampling temperature (lower for research)
36
+ cost_tracker: Cost tracker instance
37
+ """
38
+ super().__init__(
39
+ name="ResearchAgent",
40
+ model=model,
41
+ temperature=temperature,
42
+ cost_tracker=cost_tracker,
43
+ )
44
+
45
+ self.search_tool = TavilySearchTool()
46
+
47
+ def get_system_prompt(self) -> str:
48
+ """Get system prompt for research agent."""
49
+ return """You are a professional business research analyst specializing in competitive intelligence.
50
+
51
+ Your role is to gather and synthesize information about companies, markets, and competitors from web sources.
52
+
53
+ When analyzing search results, you should:
54
+ 1. Focus on factual, verifiable information
55
+ 2. Identify key data points: revenue, employees, products, market position
56
+ 3. Note dates and sources for important claims
57
+ 4. Distinguish between facts and opinions
58
+ 5. Highlight competitive advantages and weaknesses
59
+
60
+ Provide your analysis in a structured format with clear sections and bullet points.
61
+ Always cite sources when making specific claims."""
62
+
63
+ async def run(
64
+ self,
65
+ company_name: str,
66
+ industry: Optional[str] = None,
67
+ research_depth: str = "comprehensive",
68
+ ) -> Dict[str, Any]:
69
+ """
70
+ Gather research data about a company.
71
+
72
+ Args:
73
+ company_name: Target company name
74
+ industry: Optional industry context
75
+ research_depth: "basic" or "comprehensive"
76
+
77
+ Returns:
78
+ Dictionary with research results:
79
+ - company_overview: Company information
80
+ - competitors: Competitor analysis
81
+ - market_trends: Industry trends
82
+ - raw_sources: List of sources used
83
+ """
84
+ logger.info(f"Starting research for: {company_name}")
85
+
86
+ results = {
87
+ "company_name": company_name,
88
+ "industry": industry,
89
+ "company_overview": "",
90
+ "competitors": "",
91
+ "market_trends": "",
92
+ "raw_sources": [],
93
+ }
94
+
95
+ try:
96
+ # 1. Company Overview
97
+ company_data = await self.search_tool.get_company_info(
98
+ company_name=company_name,
99
+ max_results=10 if research_depth == "comprehensive" else 5,
100
+ )
101
+
102
+ results["raw_sources"].extend(company_data.get("results", []))
103
+
104
+ # Analyze company data with LLM
105
+ company_context = self.search_tool.format_results_for_llm(company_data)
106
+ company_analysis = await self._analyze_company(
107
+ company_name, company_context
108
+ )
109
+ results["company_overview"] = company_analysis
110
+
111
+ # 2. Competitor Analysis
112
+ competitor_data = await self.search_tool.get_competitor_info(
113
+ company_name=company_name,
114
+ industry=industry,
115
+ max_results=10 if research_depth == "comprehensive" else 5,
116
+ )
117
+
118
+ results["raw_sources"].extend(competitor_data.get("results", []))
119
+
120
+ competitor_context = self.search_tool.format_results_for_llm(
121
+ competitor_data
122
+ )
123
+ competitor_analysis = await self._analyze_competitors(
124
+ company_name, competitor_context
125
+ )
126
+ results["competitors"] = competitor_analysis
127
+
128
+ # 3. Market Trends (if industry provided)
129
+ if industry:
130
+ trend_data = await self.search_tool.get_market_trends(
131
+ industry=industry,
132
+ max_results=8 if research_depth == "comprehensive" else 4,
133
+ )
134
+
135
+ results["raw_sources"].extend(trend_data.get("results", []))
136
+
137
+ trend_context = self.search_tool.format_results_for_llm(trend_data)
138
+ trend_analysis = await self._analyze_trends(industry, trend_context)
139
+ results["market_trends"] = trend_analysis
140
+
141
+ logger.info(
142
+ f"Research complete for {company_name}. "
143
+ f"Processed {len(results['raw_sources'])} sources"
144
+ )
145
+
146
+ return results
147
+
148
+ except Exception as e:
149
+ logger.error(f"Research failed for {company_name}: {e}")
150
+ raise
151
+
152
+ async def _analyze_company(
153
+ self,
154
+ company_name: str,
155
+ search_context: str,
156
+ ) -> str:
157
+ """
158
+ Analyze company information from search results.
159
+
160
+ Args:
161
+ company_name: Company name
162
+ search_context: Formatted search results
163
+
164
+ Returns:
165
+ Structured company analysis
166
+ """
167
+ user_message = f"""Analyze the following search results about {company_name}.
168
+
169
+ Provide a structured analysis covering:
170
+ 1. Company Overview (founded, headquarters, size)
171
+ 2. Products & Services (main offerings)
172
+ 3. Business Model (how they make money)
173
+ 4. Market Position (market share, ranking)
174
+ 5. Key Metrics (revenue, employees, growth)
175
+
176
+ Search Results:
177
+ {search_context}
178
+
179
+ Provide your analysis in clear sections with bullet points. Cite sources for specific claims."""
180
+
181
+ messages = self._create_messages(user_message)
182
+ response = await self._invoke_llm(messages)
183
+
184
+ return response
185
+
186
+ async def _analyze_competitors(
187
+ self,
188
+ company_name: str,
189
+ search_context: str,
190
+ ) -> str:
191
+ """
192
+ Analyze competitor landscape.
193
+
194
+ Args:
195
+ company_name: Target company
196
+ search_context: Formatted search results
197
+
198
+ Returns:
199
+ Competitor analysis
200
+ """
201
+ user_message = f"""Analyze the competitive landscape for {company_name}.
202
+
203
+ Based on the search results, identify:
204
+ 1. Main Competitors (list 3-5 key competitors)
205
+ 2. Competitive Positioning (how each differs)
206
+ 3. Market Dynamics (who leads, who follows)
207
+ 4. Differentiation Factors (what makes each unique)
208
+
209
+ Search Results:
210
+ {search_context}
211
+
212
+ Format as a structured list with clear comparisons."""
213
+
214
+ messages = self._create_messages(user_message)
215
+ response = await self._invoke_llm(messages)
216
+
217
+ return response
218
+
219
+ async def _analyze_trends(
220
+ self,
221
+ industry: str,
222
+ search_context: str,
223
+ ) -> str:
224
+ """
225
+ Analyze market trends.
226
+
227
+ Args:
228
+ industry: Industry name
229
+ search_context: Formatted search results
230
+
231
+ Returns:
232
+ Trend analysis
233
+ """
234
+ user_message = f"""Analyze market trends for the {industry} industry.
235
+
236
+ Identify:
237
+ 1. Key Trends (major shifts in the market)
238
+ 2. Growth Drivers (what's fueling growth)
239
+ 3. Challenges (obstacles facing the industry)
240
+ 4. Future Outlook (predictions for next 1-2 years)
241
+
242
+ Search Results:
243
+ {search_context}
244
+
245
+ Provide analysis with clear trends and supporting evidence."""
246
+
247
+ messages = self._create_messages(user_message)
248
+ response = await self._invoke_llm(messages)
249
+
250
+ return response
src/agents/writer.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Writer Agent for generating professional market intelligence reports."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any, Dict, Optional
5
+
6
+ from src.agents.base import BaseAgent
7
+ from src.utils.cost_tracker import CostTracker
8
+ from src.utils.logging import setup_logger
9
+
10
+ logger = setup_logger(__name__)
11
+
12
+
13
+ class WriterAgent(BaseAgent):
14
+ """
15
+ Writer Agent responsible for generating final reports.
16
+
17
+ Takes research and analysis data and creates:
18
+ - Executive summary
19
+ - Comprehensive market intelligence report
20
+ - Properly formatted markdown with citations
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ model: Optional[str] = None,
26
+ temperature: float = 0.6, # Higher for better writing quality
27
+ cost_tracker: Optional[CostTracker] = None,
28
+ ):
29
+ """
30
+ Initialize Writer Agent.
31
+
32
+ Args:
33
+ model: LLM model to use
34
+ temperature: Sampling temperature
35
+ cost_tracker: Cost tracker instance
36
+ """
37
+ super().__init__(
38
+ name="WriterAgent",
39
+ model=model,
40
+ temperature=temperature,
41
+ cost_tracker=cost_tracker,
42
+ )
43
+
44
+ def get_system_prompt(self) -> str:
45
+ """Get system prompt for writer agent."""
46
+ return """You are a professional business report writer specializing in market intelligence and competitive analysis.
47
+
48
+ Your role is to transform research and analysis into polished, executive-ready reports.
49
+
50
+ When writing reports, you should:
51
+ 1. Use clear, professional business language
52
+ 2. Structure content logically with proper headings
53
+ 3. Include executive summaries for busy stakeholders
54
+ 4. Use bullet points and tables for scannability
55
+ 5. Cite sources properly
56
+ 6. Make insights actionable
57
+
58
+ Report format guidelines:
59
+ - Use markdown formatting
60
+ - Include clear section headers (#, ##, ###)
61
+ - Use tables for competitive comparisons
62
+ - Include bullet points for lists
63
+ - Add citations [source]
64
+ - Keep executive summary to 200-300 words
65
+
66
+ Write for senior executives and decision-makers."""
67
+
68
+ async def run(
69
+ self,
70
+ research_data: Dict[str, Any],
71
+ analysis_data: Dict[str, Any],
72
+ ) -> Dict[str, Any]:
73
+ """
74
+ Generate comprehensive market intelligence report.
75
+
76
+ Args:
77
+ research_data: Output from ResearchAgent
78
+ analysis_data: Output from AnalysisAgent
79
+
80
+ Returns:
81
+ Dictionary with report components:
82
+ - executive_summary: Brief overview
83
+ - full_report: Complete markdown report
84
+ - metadata: Report metadata (date, sources count, etc.)
85
+ """
86
+ company_name = research_data.get("company_name", "Unknown Company")
87
+ logger.info(f"Starting report generation for: {company_name}")
88
+
89
+ try:
90
+ # Generate report sections
91
+ exec_summary = await self._write_executive_summary(
92
+ research_data, analysis_data
93
+ )
94
+
95
+ full_report = await self._write_full_report(
96
+ research_data, analysis_data, exec_summary
97
+ )
98
+
99
+ # Gather metadata
100
+ metadata = {
101
+ "company_name": company_name,
102
+ "industry": research_data.get("industry"),
103
+ "generated_date": datetime.now().isoformat(),
104
+ "sources_count": len(research_data.get("raw_sources", [])),
105
+ "model_used": self.model_name,
106
+ }
107
+
108
+ logger.info(f"Report generation complete for {company_name}")
109
+
110
+ return {
111
+ "executive_summary": exec_summary,
112
+ "full_report": full_report,
113
+ "metadata": metadata,
114
+ }
115
+
116
+ except Exception as e:
117
+ logger.error(f"Report generation failed for {company_name}: {e}")
118
+ raise
119
+
120
+ async def _write_executive_summary(
121
+ self,
122
+ research_data: Dict[str, Any],
123
+ analysis_data: Dict[str, Any],
124
+ ) -> str:
125
+ """
126
+ Write executive summary (200-300 words).
127
+
128
+ Args:
129
+ research_data: Research results
130
+ analysis_data: Analysis results
131
+
132
+ Returns:
133
+ Executive summary text
134
+ """
135
+ company_name = research_data.get("company_name")
136
+
137
+ user_message = f"""Write a concise executive summary for a market intelligence report on {company_name}.
138
+
139
+ Use this information:
140
+
141
+ COMPANY OVERVIEW:
142
+ {research_data.get("company_overview", "")}
143
+
144
+ KEY INSIGHTS FROM SWOT:
145
+ {analysis_data.get("swot", "")}
146
+
147
+ STRATEGIC RECOMMENDATIONS:
148
+ {analysis_data.get("strategic_recommendations", "")}
149
+
150
+ Requirements:
151
+ - 200-300 words
152
+ - Cover: company overview, market position, key findings, main recommendations
153
+ - Written for senior executives (clear, actionable)
154
+ - Professional business tone
155
+
156
+ Start directly with content (no "Executive Summary" heading)."""
157
+
158
+ messages = self._create_messages(user_message)
159
+ response = await self._invoke_llm(messages)
160
+
161
+ return response
162
+
163
+ async def _write_full_report(
164
+ self,
165
+ research_data: Dict[str, Any],
166
+ analysis_data: Dict[str, Any],
167
+ exec_summary: str,
168
+ ) -> str:
169
+ """
170
+ Write complete markdown report.
171
+
172
+ Args:
173
+ research_data: Research results
174
+ analysis_data: Analysis results
175
+ exec_summary: Executive summary
176
+
177
+ Returns:
178
+ Full report in markdown format
179
+ """
180
+ company_name = research_data.get("company_name")
181
+ industry = research_data.get("industry") or "Market"
182
+
183
+ # Build comprehensive context
184
+ context = f"""
185
+ COMPANY: {company_name}
186
+ INDUSTRY: {industry}
187
+
188
+ RESEARCH DATA:
189
+ Company Overview: {research_data.get("company_overview", "")}
190
+ Competitors: {research_data.get("competitors", "")}
191
+ Market Trends: {research_data.get("market_trends", "")}
192
+
193
+ ANALYSIS DATA:
194
+ SWOT: {analysis_data.get("swot", "")}
195
+ Competitive Matrix: {analysis_data.get("competitive_matrix", "")}
196
+ Market Positioning: {analysis_data.get("positioning", "")}
197
+ Strategic Recommendations: {analysis_data.get("strategic_recommendations", "")}
198
+
199
+ EXECUTIVE SUMMARY:
200
+ {exec_summary}
201
+ """
202
+
203
+ user_message = f"""Create a comprehensive market intelligence report for {company_name} in markdown format.
204
+
205
+ Use all the provided research and analysis data.
206
+
207
+ Structure the report as follows:
208
+
209
+ # Market Intelligence Report: {company_name}
210
+
211
+ ## Executive Summary
212
+ [Insert the provided executive summary]
213
+
214
+ ## 1. Company Overview
215
+ [Detailed company information from research]
216
+
217
+ ## 2. Competitive Landscape
218
+ [Competitor analysis and competitive matrix]
219
+
220
+ ## 3. SWOT Analysis
221
+ [Detailed SWOT with clear sections: Strengths, Weaknesses, Opportunities, Threats]
222
+
223
+ ## 4. Market Positioning
224
+ [Positioning analysis and differentiation]
225
+
226
+ ## 5. Market Trends & Insights
227
+ [Industry trends and market dynamics]
228
+
229
+ ## 6. Strategic Recommendations
230
+ [Prioritized recommendations with rationale]
231
+
232
+ ## 7. Sources
233
+ [List key sources used]
234
+
235
+ ---
236
+ Report generated: {datetime.now().strftime("%B %d, %Y")}
237
+
238
+ Data to use:
239
+ {context}
240
+
241
+ Format requirements:
242
+ - Use proper markdown (headers, bullets, tables)
243
+ - Make it professional and polished
244
+ - Include all relevant details
245
+ - Cite sources where appropriate
246
+ - Make it actionable for executives"""
247
+
248
+ messages = self._create_messages(user_message)
249
+ response = await self._invoke_llm(messages)
250
+
251
+ return response
src/tools/search.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Search tools for web research using Tavily API."""
2
+
3
+ from typing import Dict, List, Optional
4
+
5
+ from tavily import TavilyClient
6
+
7
+ from src.utils.config import get_settings
8
+ from src.utils.logging import setup_logger
9
+
10
+ logger = setup_logger(__name__)
11
+
12
+
13
+ class TavilySearchTool:
14
+ """
15
+ Wrapper for Tavily search API optimized for research agents.
16
+
17
+ Tavily is designed for AI agents and provides clean, structured
18
+ results ideal for LLM consumption.
19
+ """
20
+
21
+ def __init__(self, api_key: Optional[str] = None):
22
+ """
23
+ Initialize Tavily search tool.
24
+
25
+ Args:
26
+ api_key: Optional Tavily API key (uses config if None)
27
+ """
28
+ settings = get_settings()
29
+ self.api_key = api_key or settings.tavily_api_key
30
+ self.client = TavilyClient(api_key=self.api_key)
31
+
32
+ logger.info("Tavily search tool initialized")
33
+
34
+ async def search(
35
+ self,
36
+ query: str,
37
+ max_results: int = 5,
38
+ search_depth: str = "advanced",
39
+ include_domains: Optional[List[str]] = None,
40
+ exclude_domains: Optional[List[str]] = None,
41
+ ) -> Dict:
42
+ """
43
+ Perform web search using Tavily.
44
+
45
+ Args:
46
+ query: Search query
47
+ max_results: Maximum number of results to return
48
+ search_depth: "basic" or "advanced" (advanced is more comprehensive)
49
+ include_domains: Optional list of domains to include
50
+ exclude_domains: Optional list of domains to exclude
51
+
52
+ Returns:
53
+ Dictionary with search results:
54
+ - results: List of search results
55
+ - query: Original query
56
+ - answer: Tavily's AI-generated answer (if available)
57
+ """
58
+ try:
59
+ logger.info(f"Tavily search: {query}")
60
+
61
+ response = self.client.search(
62
+ query=query,
63
+ max_results=max_results,
64
+ search_depth=search_depth,
65
+ include_domains=include_domains,
66
+ exclude_domains=exclude_domains,
67
+ )
68
+
69
+ logger.info(f"Tavily returned {len(response.get('results', []))} results")
70
+
71
+ return response
72
+
73
+ except Exception as e:
74
+ logger.error(f"Tavily search failed: {e}")
75
+ raise
76
+
77
+ async def get_company_info(
78
+ self,
79
+ company_name: str,
80
+ max_results: int = 10,
81
+ ) -> Dict:
82
+ """
83
+ Get comprehensive company information.
84
+
85
+ Args:
86
+ company_name: Company name to research
87
+ max_results: Maximum results to retrieve
88
+
89
+ Returns:
90
+ Search results focused on company information
91
+ """
92
+ query = f"{company_name} company overview products services business model"
93
+ return await self.search(
94
+ query=query,
95
+ max_results=max_results,
96
+ search_depth="advanced",
97
+ )
98
+
99
+ async def get_competitor_info(
100
+ self,
101
+ company_name: str,
102
+ industry: Optional[str] = None,
103
+ max_results: int = 10,
104
+ ) -> Dict:
105
+ """
106
+ Find competitors for a given company.
107
+
108
+ Args:
109
+ company_name: Company name
110
+ industry: Optional industry context
111
+ max_results: Maximum results
112
+
113
+ Returns:
114
+ Search results about competitors
115
+ """
116
+ industry_context = f"in {industry}" if industry else ""
117
+ query = f"{company_name} competitors alternatives {industry_context}"
118
+
119
+ return await self.search(
120
+ query=query,
121
+ max_results=max_results,
122
+ search_depth="advanced",
123
+ )
124
+
125
+ async def get_market_trends(
126
+ self,
127
+ industry: str,
128
+ year: Optional[str] = "2025",
129
+ max_results: int = 8,
130
+ ) -> Dict:
131
+ """
132
+ Get market trends for an industry.
133
+
134
+ Args:
135
+ industry: Industry name
136
+ year: Year for trends (default: 2025)
137
+ max_results: Maximum results
138
+
139
+ Returns:
140
+ Search results about market trends
141
+ """
142
+ query = f"{industry} market trends {year} growth forecast opportunities"
143
+
144
+ return await self.search(
145
+ query=query,
146
+ max_results=max_results,
147
+ search_depth="advanced",
148
+ )
149
+
150
+ def format_results_for_llm(self, search_response: Dict) -> str:
151
+ """
152
+ Format search results for LLM consumption.
153
+
154
+ Args:
155
+ search_response: Tavily search response
156
+
157
+ Returns:
158
+ Formatted string with search results
159
+ """
160
+ results = search_response.get("results", [])
161
+
162
+ if not results:
163
+ return "No search results found."
164
+
165
+ formatted = []
166
+ for i, result in enumerate(results, 1):
167
+ title = result.get("title", "No title")
168
+ url = result.get("url", "")
169
+ content = result.get("content", "No content")
170
+ score = result.get("score", 0)
171
+
172
+ formatted.append(
173
+ f"[{i}] {title}\n"
174
+ f"URL: {url}\n"
175
+ f"Relevance: {score:.2f}\n"
176
+ f"Content: {content}\n"
177
+ )
178
+
179
+ # Add AI answer if available
180
+ if answer := search_response.get("answer"):
181
+ formatted.insert(0, f"AI Summary: {answer}\n\n")
182
+
183
+ return "\n".join(formatted)
184
+
185
+
186
+ class WikipediaSearchTool:
187
+ """
188
+ Wikipedia search for factual company/product information.
189
+
190
+ Note: This is a simple wrapper. For production, consider using
191
+ the wikipedia-api library for more robust access.
192
+ """
193
+
194
+ def __init__(self):
195
+ """Initialize Wikipedia search tool."""
196
+ logger.info("Wikipedia search tool initialized")
197
+
198
+ async def search(self, query: str, max_results: int = 3) -> Dict:
199
+ """
200
+ Search Wikipedia (placeholder for now).
201
+
202
+ Args:
203
+ query: Search query
204
+ max_results: Maximum results
205
+
206
+ Returns:
207
+ Search results dictionary
208
+ """
209
+ # TODO: Implement actual Wikipedia API integration
210
+ # For now, we'll use Tavily which can search Wikipedia
211
+ logger.info(f"Wikipedia search: {query}")
212
+
213
+ return {
214
+ "query": query,
215
+ "results": [],
216
+ "note": "Wikipedia integration pending - using Tavily for now",
217
+ }
test_agents.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test script to run agents manually on a real query.
3
+
4
+ This demonstrates the agent pipeline without LangGraph orchestration.
5
+ """
6
+
7
+ import asyncio
8
+ import sys
9
+
10
+ from src.agents.researcher import ResearchAgent
11
+ from src.agents.analyst import AnalysisAgent
12
+ from src.agents.writer import WriterAgent
13
+ from src.utils.cost_tracker import CostTracker
14
+
15
+
16
+ async def test_complete_pipeline():
17
+ """Test the complete agent pipeline."""
18
+
19
+ # Query from user
20
+ query = "going viral on Instagram using AI without showing face"
21
+ industry = "Social Media Marketing"
22
+
23
+ print("=" * 80)
24
+ print(f"TESTING AGENT PIPELINE")
25
+ print(f"Query: {query}")
26
+ print(f"Industry: {industry}")
27
+ print("=" * 80)
28
+ print()
29
+
30
+ # Shared cost tracker
31
+ cost_tracker = CostTracker()
32
+
33
+ try:
34
+ # Step 1: Research Agent
35
+ print("\n[STEP 1] Running Research Agent...")
36
+ print("-" * 80)
37
+ researcher = ResearchAgent(cost_tracker=cost_tracker)
38
+
39
+ research_results = await researcher.run(
40
+ company_name=query, industry=industry, research_depth="comprehensive"
41
+ )
42
+
43
+ print(f"\nResearch completed!")
44
+ print(f"Sources gathered: {len(research_results.get('raw_sources', []))}")
45
+ print(f"\nCompany Overview (first 500 chars):")
46
+ print(research_results["company_overview"][:500] + "...")
47
+
48
+ # Step 2: Analysis Agent
49
+ print("\n\n[STEP 2] Running Analysis Agent...")
50
+ print("-" * 80)
51
+ analyst = AnalysisAgent(cost_tracker=cost_tracker)
52
+
53
+ analysis_results = await analyst.run(research_data=research_results)
54
+
55
+ print(f"\nAnalysis completed!")
56
+ print(f"\nSWOT Analysis (first 500 chars):")
57
+ print(analysis_results["swot"][:500] + "...")
58
+
59
+ # Step 3: Writer Agent
60
+ print("\n\n[STEP 3] Running Writer Agent...")
61
+ print("-" * 80)
62
+ writer = WriterAgent(cost_tracker=cost_tracker)
63
+
64
+ report_results = await writer.run(
65
+ research_data=research_results, analysis_data=analysis_results
66
+ )
67
+
68
+ print(f"\nReport generation completed!")
69
+
70
+ # Display results
71
+ print("\n\n" + "=" * 80)
72
+ print("EXECUTIVE SUMMARY")
73
+ print("=" * 80)
74
+ print(report_results["executive_summary"])
75
+
76
+ print("\n\n" + "=" * 80)
77
+ print("FULL REPORT")
78
+ print("=" * 80)
79
+ print(report_results["full_report"])
80
+
81
+ # Cost summary
82
+ print("\n\n" + "=" * 80)
83
+ print("COST SUMMARY")
84
+ print("=" * 80)
85
+ summary = cost_tracker.get_summary()
86
+ print(f"Total Cost: ${summary['total_cost']:.4f}")
87
+ print(f"Total Tokens: {summary['total_tokens']:,}")
88
+ print(f"API Calls: {summary['calls']}")
89
+ print(f"\nBreakdown by model:")
90
+ for model, data in summary["by_model"].items():
91
+ print(f" {model}:")
92
+ print(f" Input: {data['input_tokens']:,} tokens")
93
+ print(f" Output: {data['output_tokens']:,} tokens")
94
+ print(f" Cost: ${data['cost']:.4f}")
95
+
96
+ # Save report
97
+ output_file = "test_report.md"
98
+ with open(output_file, "w") as f:
99
+ f.write(f"# Market Intelligence Report\n\n")
100
+ f.write(f"**Query:** {query}\n\n")
101
+ f.write(f"**Industry:** {industry}\n\n")
102
+ f.write(f"---\n\n")
103
+ f.write(f"## Executive Summary\n\n")
104
+ f.write(report_results["executive_summary"])
105
+ f.write(f"\n\n---\n\n")
106
+ f.write(report_results["full_report"])
107
+ f.write(f"\n\n---\n\n")
108
+ f.write(f"**Total Cost:** ${summary['total_cost']:.4f}\n")
109
+
110
+ print(f"\n\nReport saved to: {output_file}")
111
+
112
+ return True
113
+
114
+ except Exception as e:
115
+ print(f"\n\nERROR: {e}")
116
+ import traceback
117
+
118
+ traceback.print_exc()
119
+ return False
120
+
121
+
122
+ if __name__ == "__main__":
123
+ print("\nStarting agent test...")
124
+ print("Make sure you have .env file with API keys set up!\n")
125
+
126
+ result = asyncio.run(test_complete_pipeline())
127
+
128
+ if result:
129
+ print("\n\nTest PASSED!")
130
+ sys.exit(0)
131
+ else:
132
+ print("\n\nTest FAILED!")
133
+ sys.exit(1)
test_report.md ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Market Intelligence Report
2
+
3
+ **Query:** going viral on Instagram using AI without showing face
4
+
5
+ **Industry:** Social Media Marketing
6
+
7
+ ---
8
+
9
+ ## Executive Summary
10
+
11
+ This executive summary outlines the market intelligence on leveraging AI tools for faceless Instagram content to achieve virality, targeting rapid growth in short-form video amid a $32.55B influencer market (57.1% Instagram share) [4].
12
+
13
+ **Key Players and Landscape**: Leading SaaS tools include Invideo AI (text-to-Reel generation in ~20 minutes), Virlo (faceless workflows with Leonardo.ai/Eleven Labs integrations), and Zebracat (viral niches like pet videos/AI art). Supporting platforms: TailorTalk (DM automation), Madgicx (trend forecasting), Juicer (content/engagement AI). AI influencers like Lil Miquela (3M+ followers) exemplify success via brand deals [1][2][3][9].
14
+
15
+ **Market Position**: Faceless AI content commands premium engagement—5% for AI art (vs. 2% Instagram average), 3% uplift over humans—driven by scalability, 24/7 operation, and low risks. High-growth niche amid 93% marketer video spend increase ($140B in 2025), but faces authenticity gaps and saturation [3][9].
16
+
17
+ **Key Findings (SWOT Highlights)**: **Strengths**: Rapid production, superior metrics, multi-platform repurposing. **Weaknesses**: AI fatigue, tool dependency. **Opportunities**: Social commerce ($2.9T by 2026), niche virality. **Threats**: Consumer distrust (50% pullback), algorithm volatility [1-10].
18
+
19
+ **Strategic Recommendations** (Prioritized for Execution):
20
+ - **Immediate (0-3 Months)**: Deploy workflows in 2-3 niches (e.g., pet motivation) for 5-10 Reels/week; automate outbound engagement for 3-5x reach, targeting 10K followers [1][3][6].
21
+ - **Medium (6-12 Months)**: Blend UGC (30%) for authenticity; launch Instagram Shops/affiliates for $10K+ quarterly revenue [2][4].
22
+ - **Long-Term**: Cross-post to TikTok/YouTube; build proprietary prompts for 1M+ followers, 7% engagement [5][6].
23
+
24
+ Actionable path: Start with Invideo/Z zebracat pilots to capture first-mover edges, mitigating risks for scalable, passive income. (248 words)
25
+
26
+ ---
27
+
28
+ # Market Intelligence Report: Going Viral on Instagram Using AI Without Showing Face
29
+
30
+ ## Executive Summary
31
+ This executive summary outlines the market intelligence on leveraging AI tools for faceless Instagram content to achieve virality, targeting rapid growth in short-form video amid a $32.55B influencer market (57.1% Instagram share) [4].
32
+
33
+ **Key Players and Landscape**: Leading SaaS tools include Invideo AI (text-to-Reel generation in ~20 minutes), Virlo (faceless workflows with Leonardo.ai/Eleven Labs integrations), and Zebracat (viral niches like pet videos/AI art). Supporting platforms: TailorTalk (DM automation), Madgicx (trend forecasting), Juicer (content/engagement AI). AI influencers like Lil Miquela (3M+ followers) exemplify success via brand deals [1][2][3][9].
34
+
35
+ **Market Position**: Faceless AI content commands premium engagement—5% for AI art (vs. 2% Instagram average), 3% uplift over humans—driven by scalability, 24/7 operation, and low risks. High-growth niche amid 93% marketer video spend increase ($140B in 2025), but faces authenticity gaps and saturation [3][9].
36
+
37
+ **Key Findings (SWOT Highlights)**: **Strengths**: Rapid production, superior metrics, multi-platform repurposing. **Weaknesses**: AI fatigue, tool dependency. **Opportunities**: Social commerce ($2.9T by 2026), niche virality. **Threats**: Consumer distrust (50% pullback), algorithm volatility [1-10].
38
+
39
+ **Strategic Recommendations** (Prioritized for Execution):
40
+ - **Immediate (0-3 Months)**: Deploy workflows in 2-3 niches (e.g., pet motivation) for 5-10 Reels/week; automate outbound engagement for 3-5x reach, targeting 10K followers [1][3][6].
41
+ - **Medium (6-12 Months)**: Blend UGC (30%) for authenticity; launch Instagram Shops/affiliates for $10K+ quarterly revenue [2][4].
42
+ - **Long-Term**: Cross-post to TikTok/YouTube; build proprietary prompts for 1M+ followers, 7% engagement [5][6].
43
+
44
+ Actionable path: Start with Invideo/Zebracat pilots to capture first-mover edges, mitigating risks for scalable, passive income. (248 words)
45
+
46
+ ## 1. Company Overview
47
+ "Going Viral on Instagram Using AI Without Showing Face" represents an emerging strategy and ecosystem in the social media marketing industry, leveraging AI-powered SaaS tools to create scalable, anonymous content (e.g., Reels, AI art, videos) for virality and monetization. No formal company structure (e.g., founding date, headquarters, employee count) is established, as it is a niche approach powered by specialized platforms rather than a single entity [1][2][3].
48
+
49
+ ### Key Offerings (Products & Services)
50
+ - **Invideo AI**: Generates faceless Instagram Reels from text prompts in ~20 minutes; supports monetization via digital products, affiliates, and brand partnerships [1].
51
+ - **Virlo**: AI workflows for faceless accounts, integrating Leonardo.ai (imagery), Eleven Labs (voice-overs), and Flarecut (video transformation); enables Instagram Shop setup for merchandise [2].
52
+ - **Zebracat**: AI video generator for viral faceless niches (e.g., pets, AI art, study-with-me, PowerPoint tutorials); focuses on psychological virality triggers [3].
53
+ - Supporting tools: TailorTalk (Instagram automation for DMs/comments), Madgicx (audience growth/trend forecasting), Juicer (content creation/engagement), Copy.ai (copywriting), Descript (editing), Mentionlytics (social listening) [5][6][7][8].
54
+
55
+ ### Business Model
56
+ - Primarily SaaS/subscription-based for tools (e.g., Invideo AI, Zebracat sign-ups) [1][3].
57
+ - Monetization strategies: Digital products (spreadsheets, courses), affiliates, brand deals (e.g., AI influencers like Aitana Lopez with Fenty Beauty), Instagram Shops, AdSense [1][2][4][9].
58
+ - Enables 24/7 scalable engagement for passive income without human limitations [4][9].
59
+
60
+ ### Market Position and Key Metrics
61
+ - Niche high-growth positioning: Faceless AI art achieves ~5% engagement (vs. Instagram 2% average); AI influencers yield 3% higher engagement than humans [3][9].
62
+ - Examples: Lil Miquela (3M+ followers); Zebracat cases (509K followers, millions of views) [3][9].
63
+ - Growth drivers: Efficiency, multi-platform repurposing, no reputational risks [1][2][3].
64
+
65
+ ## 2. Competitive Landscape
66
+ The competitive landscape for AI-driven faceless Instagram virality is emerging and fragmented, dominated by niche SaaS tools focused on content generation, automation, and optimization. No dominant player holds quantifiable market share, but Zebracat leads in proven virality [1][3].
67
+
68
+ ### Main Competitors
69
+ - **Zebracat**: AI video for niches like pets/AI art; high engagement (5%) [1].
70
+ - **BigMotion AI**: Full automation for Reels (generation to auto-posting) [3].
71
+ - **Faceless.video**: Faceless video ads with auto-posting/social integration [4].
72
+ - **Crayo AI**: Viral short-form Reels ideation/production by faceless experts [6].
73
+
74
+ ### Competitive Positioning and Dynamics
75
+ - **Leaders**: Zebracat (creative niches, proven metrics); BigMotion AI (automation for passive income).
76
+ - **Followers**: Faceless.video (ads), Crayo (ideation).
77
+ - Differentiation: Zebracat (niche templates), BigMotion (free tier/auto-post), Faceless.video (analytics), Crayo (expert-backed).
78
+ - Market: Fast-evolving; general tools (Canva, Hootsuite) lag in faceless virality [1][3][4][6].
79
+
80
+ ### Competitive Matrix
81
+ | Dimension | FacelessViral IG (Ours) | Zebracat [1] | BigMotion AI [3] | Faceless.video [4] | Crayo AI [6] |
82
+ |------------------------|------------------------------------------|---------------------------------------|---------------------------------------|---------------------------------------|---------------------------------------|
83
+ | **Market Share/Size** | Low (Emerging; IG-specific focus) | **High** (509K followers, 5% engagement) | Medium-High (Free multi-platform) | Low (No IG cases) | Low (Emerging) |
84
+ | **Product Range** | Medium (Reels virality) | Medium (Niche templates) | **High** (Gen-to-post automation) | Medium (Ads/auto-post) | Medium (Ideation/production) |
85
+ | **Pricing Strategy** | Unknown (Competitive SaaS) | Medium (Beginner-friendly) | **High** (Free tier) | Medium (Tiered SaaS) | Medium (No free tier) |
86
+ | **Technology/Innovation** | Medium (Reels optimization) | **High** (Niche workflows) | **High** (Algo adaptation) | Medium (Analytics integration) | Medium-High (Viral ideation) |
87
+ | **Customer Segments** | IG viral creators (passive focus) | Beginners (pets/AI art) | Passive income (multi-platform) | Ad marketers | Short-form creators |
88
+ | **Strengths** | IG-exclusive virality, anonymity | Proven metrics, templates | Automation, free tier | Ad optimization | Expert-backed virals |
89
+ | **Weaknesses** | Narrow scope, no metrics | Niche-limited | Less creative depth | Ad-centric | Less automation |
90
+
91
+ **Insights**: Differentiate via IG-exclusive focus; pursue hybrid automation/creatives [1][3].
92
+
93
+ ## 3. SWOT Analysis
94
+
95
+ #### Strengths (Internal Positive Factors)
96
+ - **Rapid, Scalable Content Production**: AI tools generate Reels in ~20 minutes; workflows enable high-volume output [1][2][3].
97
+ - **Superior Engagement Metrics**: 5% for AI art, 3% uplift for AI influencers [3][9].
98
+ - **Low Reputational and Operational Risks**: No face exposure; 24/7 scalability [2][3][9].
99
+ - **Cost-Effective Monetization Pathways**: Affiliates, Shops, digital products [1][2][4].
100
+ - **Trend-First-Mover Capabilities**: Real-time forecasting [6][7].
101
+ - **Multi-Platform Repurposing**: Easy adaptation [1][3].
102
+
103
+ #### Weaknesses (Internal Negative Factors)
104
+ - **Perceived Lack of Authenticity**: Risks AI fatigue [3][9][10].
105
+ - **Dependency on Third-Party Tools**: Pricing/outage vulnerabilities [1][2][3].
106
+ - **Steep Learning Curve**: Prompt mastery needed [2][3].
107
+ - **Limited Metrics for New Entrants**: Unproven scaling [1][3].
108
+ - **Content Saturation Risk**: Oversupply in niches [3].
109
+ - **Narrow Human Oversight**: Lacks nuance [5].
110
+
111
+ #### Opportunities (External Positive Factors)
112
+ - **Short-Form Video and Influencer Boom**: $140B video ads, $32.55B influencers [4].
113
+ - **Social Commerce Expansion**: $2.9T by 2026 [2][3].
114
+ - **Niche Virality and AI Art Demand**: Psychological hooks [1][3].
115
+ - **AI Efficiency Amid Burnout**: 50% leaders adopting [2][4][6].
116
+ - **Platform Discovery Prioritization**: Organic reach [1][2].
117
+ - **UGC and Private Communities**: Retention boost [1][4].
118
+
119
+ #### Threats (External Negative Factors)
120
+ - **Consumer Distrust and Pullback**: 50% limiting interactions [2][4].
121
+ - **Intense Niche Competition**: Zebracat/BigMotion dominance [1][3][4][6].
122
+ - **Algorithm Volatility**: Interaction shifts [1][3][4].
123
+ - **Market Saturation and Fatigue**: AI erosion [1][4][7][10].
124
+ - **Regulatory/Economic Pressures**: Ad spend benefits incumbents [2][4][9].
125
+ - **Resource Gaps**: Investment needs [2][5][8].
126
+
127
+ ## 4. Market Positioning
128
+ Faceless AI Virality positions as an **accessible, low-risk Instagram monetization strategy** via anonymous Reels, yielding 5% engagement [3].
129
+
130
+ ### 1. Current Positioning
131
+ - **Value Proposition**: 20-min virals, passive income, 2.5x engagement [1][2][3][9].
132
+ - **Target Segments**:
133
+ | Segment | Description | Examples |
134
+ |----------------------|--------------------------------------|---------------------------|
135
+ | Beginner Creators | Quick virality/passive income | Pet/AI art [1][3] |
136
+ | Passive Income Seekers | Avoid personal branding | Affiliates [1][2] |
137
+ | Niche Producers | Specialized themes | Tutorials [3] |
138
+ | AI Influencer Teams | Virtual personas | Aitana Lopez [2][4] |
139
+
140
+ ### 2. Competitive Differentiation
141
+ - **Positioning Map** (Automation vs. Virality):
142
+ | Tool/Player | Automation | Virality Focus | Key Diff |
143
+ |-------------------|------------|----------------|---------------------------|
144
+ | Zebracat | Medium | High | Proven metrics [1] |
145
+ | BigMotion AI | High | Medium | Free auto-post [3] |
146
+ | Faceless.video | High | Low-Medium | Ads [4] |
147
+ | Crayo AI | Medium | High | Ideation [6] |
148
+ | **Ours** | **High** | **Highest** | Anonymity + hooks [3][9] |
149
+
150
+ - **USP**: Anonymity + scaling outperforms humans [9].
151
+
152
+ ### 3. Positioning Gaps and Opportunities
153
+ - Gaps: Enterprise, global, e-commerce.
154
+ - Recommendations: Hybrid SaaS, B2B pivot, authenticity boosters [1][5].
155
+
156
+ ## 5. Market Trends & Insights
157
+ ### 1. Key Trends
158
+ - Short-form video dominance (78% preference) [4].
159
+ - Authenticity/UGC/niches rise [1][3][4].
160
+ - AI integration, social commerce, discovery engines [2][3][7].
161
+
162
+ ### 2. Growth Drivers
163
+ - Ad/video spend ($276.7B/$140B in 2025) [2][4].
164
+ - Influencers ($32.55B), commerce ($2.9T) [3][4].
165
+ - AI innovations, niche storytelling [2][5][6].
166
+
167
+ ### 3. Challenges
168
+ - Consumer pullback (50%), algorithm uncertainty, saturation [1][2][4].
169
+ - Burnout, authenticity fatigue [2][5].
170
+
171
+ ### 4. Future Outlook
172
+ - Growth through 2026; AI enabler with authenticity focus; fragmentation risks [2][4][5].
173
+
174
+ ## 6. Strategic Recommendations
175
+
176
+ #### HIGH PRIORITY (0-3 Months)
177
+ - **1**: Niche workflows (5-10 Reels/week); 3-5x reach [1][3][6].
178
+ - **2**: Auto-posting/outbound; 2-4% lift [3][9].
179
+
180
+ #### MEDIUM PRIORITY (6-12 Months)
181
+ - **3**: 30% UGC blend; 25% churn reduction [1][4].
182
+ - **4**: Shops/affiliates; $10K+ revenue [2][4].
183
+
184
+ #### LONG-TERM (12+ Months)
185
+ - **5**: Multi-platform; 2x audience [1][4].
186
+ - **6**: Proprietary prompts; 7% engagement [2][5][6].
187
+
188
+ ## 7. Sources
189
+ - [1] Zebracat case studies and examples.
190
+ - [2] Virlo workflows and AI influencer context.
191
+ - [3] Invideo AI, BigMotion AI features; faceless engagement metrics.
192
+ - [4] Faceless.video; market trends (e.g., Gartner, HypeAuditor).
193
+ - [5] TailorTalk automation.
194
+ - [6] Madgicx, Crayo AI.
195
+ - [7] Juicer social listening.
196
+ - [8] Copy.ai, Descript, Mentionlytics.
197
+ - [9] AI influencers (Lil Miquela, Aitana Lopez).
198
+ - [10] General tools (Canva, Hootsuite); authenticity research.
199
+
200
+ ---
201
+ Report generated: November 28, 2025
202
+
203
+ ---
204
+
205
+ **Total Cost:** $0.0000
tests/unit/test_base_agent.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unit tests for base agent class."""
2
+
3
+ import pytest
4
+ from unittest.mock import AsyncMock, MagicMock, patch
5
+
6
+ from src.agents.base import BaseAgent
7
+ from src.utils.cost_tracker import CostTracker
8
+
9
+
10
+ class TestAgent(BaseAgent):
11
+ """Concrete test agent for testing base class."""
12
+
13
+ def get_system_prompt(self) -> str:
14
+ return "Test system prompt"
15
+
16
+ async def run(self, **kwargs):
17
+ return {"result": "test"}
18
+
19
+
20
+ @pytest.mark.asyncio
21
+ async def test_base_agent_initialization():
22
+ """Test base agent initializes correctly."""
23
+ tracker = CostTracker()
24
+
25
+ with patch("src.agents.base.get_settings") as mock_settings:
26
+ mock_settings.return_value = MagicMock(
27
+ default_model="x-ai/grok-4.1-fast:free",
28
+ openrouter_api_key="test-key",
29
+ openrouter_base_url="https://openrouter.ai/api/v1",
30
+ )
31
+
32
+ agent = TestAgent(
33
+ name="TestAgent",
34
+ model="openai/gpt-5-mini",
35
+ temperature=0.5,
36
+ cost_tracker=tracker,
37
+ )
38
+
39
+ assert agent.name == "TestAgent"
40
+ assert agent.model_name == "openai/gpt-5-mini"
41
+ assert agent.cost_tracker == tracker
42
+
43
+
44
+ @pytest.mark.asyncio
45
+ async def test_base_agent_uses_default_model():
46
+ """Test agent uses default model from config."""
47
+ with patch("src.agents.base.get_settings") as mock_settings:
48
+ mock_settings.return_value = MagicMock(
49
+ default_model="x-ai/grok-4.1-fast:free",
50
+ openrouter_api_key="test-key",
51
+ openrouter_base_url="https://openrouter.ai/api/v1",
52
+ )
53
+
54
+ agent = TestAgent(name="TestAgent")
55
+
56
+ assert agent.model_name == "x-ai/grok-4.1-fast:free"
57
+
58
+
59
+ @pytest.mark.asyncio
60
+ async def test_create_messages():
61
+ """Test message creation."""
62
+ with patch("src.agents.base.get_settings") as mock_settings:
63
+ mock_settings.return_value = MagicMock(
64
+ default_model="test-model",
65
+ openrouter_api_key="test-key",
66
+ openrouter_base_url="https://test.com",
67
+ )
68
+
69
+ agent = TestAgent(name="TestAgent")
70
+
71
+ messages = agent._create_messages("test user message")
72
+
73
+ assert len(messages) == 2
74
+ assert messages[0].content == "Test system prompt"
75
+ assert messages[1].content == "test user message"
76
+
77
+
78
+ @pytest.mark.asyncio
79
+ async def test_get_cost_summary():
80
+ """Test cost summary retrieval."""
81
+ tracker = CostTracker()
82
+
83
+ with patch("src.agents.base.get_settings") as mock_settings:
84
+ mock_settings.return_value = MagicMock(
85
+ default_model="test-model",
86
+ openrouter_api_key="test-key",
87
+ openrouter_base_url="https://test.com",
88
+ )
89
+
90
+ agent = TestAgent(name="TestAgent", cost_tracker=tracker)
91
+
92
+ # Track some usage
93
+ tracker.track_usage("openai/gpt-5-mini", 1000, 500)
94
+
95
+ summary = agent.get_cost_summary()
96
+
97
+ assert summary["total_input_tokens"] == 1000
98
+ assert summary["total_output_tokens"] == 500
99
+ assert summary["calls"] == 1