pkgprateek commited on
Commit
74e887d
·
1 Parent(s): 5857a45

refactor: align codebase with seperated agent logics

Browse files

- feat: centralized prompt management in src/utils/prompts.py (no magic strings)
- refactor: update Research, Analyst, and Writer agents to use explicit return types
- test: updated integration tests to match strict data shapes
- fix: enable in-memory checkpointing for robust integration testing

CLAUDE.md ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CLAUDE.md - The Ultrathink Constitution
2
+
3
+ > **"We're not here to write code. We're here to make a dent in the universe."**
4
+
5
+ This file is the single source of truth for the philosophy, architecture, and standards of the **Agentic Market Research Orchestrator**. It is not a suggestion. It is the law.
6
+
7
+ ## I. The Philosophy (Ultrathink)
8
+
9
+ 1. ✅ **Think Different**: Question every assumption. If the code looks "standard," it's probably wrong. Look for the elegant angle capable of 80x improvements.
10
+ 2. ✅ **Craft, Don't Code**: Variable names explain. Functions sing. The code is literature for the next engineer.
11
+ 3. ✅ **Simplify Ruthlessly**: Complexity is the enemy. If a feature doesn't add exponential value, delete it.
12
+ 4. ⭐ **Iterate Relentlessly**: The first draft is for the trash. The second is for the critic. The third is for the user.
13
+
14
+ ## II. The Architecture
15
+
16
+ The system is a **Symbiotic Triad** orchestrated by `LangGraph`.
17
+
18
+ ```mermaid
19
+ graph TD
20
+ User((User)) -->|Topic| Coordinator{LangGraph}
21
+ Coordinator -->|Explore| Researcher[Researcher Agent]
22
+ Coordinator -->|Synthesize| Analyst[Analyst Agent]
23
+ Coordinator -->|Narrate| Writer[Writer Agent]
24
+
25
+ Researcher -->|Raw Data| Analyst
26
+ Analyst -->|Insights| Writer
27
+ Writer -->|Report| User
28
+ ```
29
+
30
+ ### The Agents
31
+ *All agents must inherit from `src.agents.base.BaseAgent`.*
32
+
33
+ 1. **The Researcher (`src/agents/researcher.py`)**
34
+ * **Role**: The Hunter.
35
+ * **Behavior**: Deep, recursive searching. Never satisfied with the first search result. Verifies sources.
36
+ * **Output**: Verified, raw data points with citations.
37
+ 2. **The Analyst (`src/agents/analyst.py`)**
38
+ * **Role**: The Strategist.
39
+ * **Behavior**: Ruthless synthesis. Looks for patterns, gaps, and SWOT elements. Does NOT just summarize; creates *meaning*.
40
+ * **Output**: Structured insights, contradictions, and strategic opportunities.
41
+ 3. **The Writer (`src/agents/writer.py`)**
42
+ * **Role**: The Storyteller.
43
+ * **Behavior**: Clear, professional, executive-level prose. No fluff.
44
+ * **Output**: The final Markdown report that "wows" the user.
45
+
46
+ ## III. Coding Standards
47
+
48
+ ### Python (The Core)
49
+ * ✅ **Version**: 3.12+
50
+ * ✅ **Style**: Strict `ruff` compliance.
51
+ * ✅ **Typing**: Static typing is mandatory. No `Any` unless legally unavoidable. Use `src.workflows.types` for shared models.
52
+ * ✅ **Async**: The world is asynchronous. Use `greenlet` / `asyncio` patterns via FastAPI.
53
+
54
+ ### Patterns
55
+ * ✅ **State Management**: Use `TypedDict` for LangGraph state (see `src/workflows/types.py`).
56
+ * ✅ **Configuration**: All prompts and model configs live in `src/utils/prompts/` or environment variables. No magic strings in code.
57
+ * ⭐ **Error Handling**: Fail gracefully. Agents should report "Intelligence Gaps" rather than crashing.
58
+
59
+ ## IV. Commands & Workflows
60
+
61
+ ### Setup
62
+ ```bash
63
+ python -m venv venv
64
+ source venv/bin/activate
65
+ pip install -r requirements.txt
66
+ ```
67
+
68
+ ### Testing (The Gauntlet)
69
+ Run the full suite. If this fails, you do not push.
70
+ ```bash
71
+ ./scripts/run_all_tests.sh
72
+ ```
73
+
74
+ ### Docker
75
+ ```bash
76
+ docker-compose up --build
77
+ ```
src/agents/analyst.py CHANGED
@@ -1,10 +1,18 @@
1
  """Analysis Agent for competitive intelligence and SWOT analysis."""
2
 
3
- from typing import Any, Dict, 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
 
@@ -43,29 +51,12 @@ class AnalysisAgent(BaseAgent):
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( # type: ignore[override]
66
  self,
67
- research_data: Dict[str, Any],
68
- ) -> Dict[str, Any]:
69
  """
70
  Perform comprehensive analysis on research data.
71
 
@@ -82,10 +73,10 @@ Use bullet points, clear headings, and strategic language."""
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": "",
@@ -120,194 +111,49 @@ Use bullet points, clear headings, and strategic language."""
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
 
1
  """Analysis Agent for competitive intelligence and SWOT analysis."""
2
 
3
+ from typing import 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
+ from src.utils.prompts import (
9
+ ANALYST_COMPETITIVE_MATRIX,
10
+ ANALYST_POSITIONING,
11
+ ANALYST_RECOMMENDATIONS,
12
+ ANALYST_SWOT,
13
+ ANALYST_SYSTEM,
14
+ )
15
+ from src.workflows.types import AnalysisOutput, ResearchOutput
16
 
17
  logger = setup_logger(__name__)
18
 
 
51
 
52
  def get_system_prompt(self) -> str:
53
  """Get system prompt for analysis agent."""
54
+ return ANALYST_SYSTEM
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
  async def run( # type: ignore[override]
57
  self,
58
+ research_data: ResearchOutput,
59
+ ) -> AnalysisOutput:
60
  """
61
  Perform comprehensive analysis on research data.
62
 
 
73
  - positioning: Market positioning analysis
74
  - strategic_recommendations: Action items
75
  """
76
+ company_name = research_data["company_name"]
77
  logger.info(f"Starting analysis for: {company_name}")
78
 
79
+ results: AnalysisOutput = {
80
  "company_name": company_name,
81
  "swot": "",
82
  "competitive_matrix": "",
 
111
 
112
  async def _perform_swot_analysis(
113
  self,
114
+ research_data: ResearchOutput,
115
  ) -> str:
116
+ """Generate SWOT analysis from research data."""
117
+ user_message = ANALYST_SWOT.format(
118
+ company_name=research_data.get("company_name"),
119
+ company_overview=research_data.get("company_overview", ""),
120
+ competitors=research_data.get("competitors", ""),
121
+ market_trends=research_data.get("market_trends", ""),
122
+ )
123
+ return await self._invoke_llm(self._create_messages(user_message))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
  async def _create_competitive_matrix(
126
  self,
127
+ research_data: ResearchOutput,
128
  ) -> str:
129
+ """Create competitive comparison matrix."""
130
+ user_message = ANALYST_COMPETITIVE_MATRIX.format(
131
+ company_name=research_data.get("company_name"),
132
+ competitors_info=research_data.get("competitors", ""),
133
+ )
134
+ return await self._invoke_llm(self._create_messages(user_message))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
  async def _analyze_market_positioning(
137
  self,
138
+ research_data: ResearchOutput,
139
  ) -> str:
140
+ """Analyze market positioning strategy."""
141
+ user_message = ANALYST_POSITIONING.format(
142
+ company_name=research_data.get("company_name"),
143
+ company_overview=research_data.get("company_overview", ""),
144
+ competitors=research_data.get("competitors", ""),
145
+ )
146
+ return await self._invoke_llm(self._create_messages(user_message))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
  async def _generate_recommendations(
149
  self,
150
+ research_data: ResearchOutput,
151
  swot: str,
152
  ) -> str:
153
+ """Generate strategic recommendations."""
154
+ user_message = ANALYST_RECOMMENDATIONS.format(
155
+ company_name=research_data.get("company_name"),
156
+ swot=swot,
157
+ market_trends=research_data.get("market_trends", ""),
158
+ )
159
+ return await self._invoke_llm(self._create_messages(user_message))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/agents/researcher.py CHANGED
@@ -1,11 +1,18 @@
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
 
@@ -46,26 +53,14 @@ class ResearchAgent(BaseAgent):
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( # type: ignore[override]
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
 
@@ -83,7 +78,7 @@ Always cite sources when making specific claims."""
83
  """
84
  logger.info(f"Starting research for: {company_name}")
85
 
86
- results: Dict[str, Any] = {
87
  "company_name": company_name,
88
  "industry": industry,
89
  "company_overview": "",
@@ -99,8 +94,7 @@ Always cite sources when making specific claims."""
99
  max_results=10 if research_depth == "comprehensive" else 5,
100
  )
101
 
102
- if isinstance(results["raw_sources"], list):
103
- results["raw_sources"].extend(company_data.get("results", []))
104
 
105
  # Analyze company data with LLM
106
  company_context = self.search_tool.format_results_for_llm(company_data)
@@ -116,8 +110,7 @@ Always cite sources when making specific claims."""
116
  max_results=10 if research_depth == "comprehensive" else 5,
117
  )
118
 
119
- if isinstance(results["raw_sources"], list):
120
- results["raw_sources"].extend(competitor_data.get("results", []))
121
 
122
  competitor_context = self.search_tool.format_results_for_llm(
123
  competitor_data
@@ -134,8 +127,7 @@ Always cite sources when making specific claims."""
134
  max_results=8 if research_depth == "comprehensive" else 4,
135
  )
136
 
137
- if isinstance(results["raw_sources"], list):
138
- results["raw_sources"].extend(trend_data.get("results", []))
139
 
140
  trend_context = self.search_tool.format_results_for_llm(trend_data)
141
  trend_analysis = await self._analyze_trends(industry, trend_context)
@@ -157,97 +149,30 @@ Always cite sources when making specific claims."""
157
  company_name: str,
158
  search_context: str,
159
  ) -> str:
160
- """
161
- Analyze company information from search results.
162
-
163
- Args:
164
- company_name: Company name
165
- search_context: Formatted search results
166
-
167
- Returns:
168
- Structured company analysis
169
- """
170
- user_message = f"""Analyze the following search results about {company_name}.
171
-
172
- Provide a structured analysis covering:
173
- 1. Company Overview (founded, headquarters, size)
174
- 2. Products & Services (main offerings)
175
- 3. Business Model (how they make money)
176
- 4. Market Position (market share, ranking)
177
- 5. Key Metrics (revenue, employees, growth)
178
-
179
- Search Results:
180
- {search_context}
181
-
182
- Provide your analysis in clear sections with bullet points. Cite sources for specific claims."""
183
-
184
- messages = self._create_messages(user_message)
185
- response = await self._invoke_llm(messages)
186
-
187
- return response
188
 
189
  async def _analyze_competitors(
190
  self,
191
  company_name: str,
192
  search_context: str,
193
  ) -> str:
194
- """
195
- Analyze competitor landscape.
196
-
197
- Args:
198
- company_name: Target company
199
- search_context: Formatted search results
200
-
201
- Returns:
202
- Competitor analysis
203
- """
204
- user_message = f"""Analyze the competitive landscape for {company_name}.
205
-
206
- Based on the search results, identify:
207
- 1. Main Competitors (list 3-5 key competitors)
208
- 2. Competitive Positioning (how each differs)
209
- 3. Market Dynamics (who leads, who follows)
210
- 4. Differentiation Factors (what makes each unique)
211
-
212
- Search Results:
213
- {search_context}
214
-
215
- Format as a structured list with clear comparisons."""
216
-
217
- messages = self._create_messages(user_message)
218
- response = await self._invoke_llm(messages)
219
-
220
- return response
221
 
222
  async def _analyze_trends(
223
  self,
224
  industry: str,
225
  search_context: str,
226
  ) -> str:
227
- """
228
- Analyze market trends.
229
-
230
- Args:
231
- industry: Industry name
232
- search_context: Formatted search results
233
-
234
- Returns:
235
- Trend analysis
236
- """
237
- user_message = f"""Analyze market trends for the {industry} industry.
238
-
239
- Identify:
240
- 1. Key Trends (major shifts in the market)
241
- 2. Growth Drivers (what's fueling growth)
242
- 3. Challenges (obstacles facing the industry)
243
- 4. Future Outlook (predictions for next 1-2 years)
244
-
245
- Search Results:
246
- {search_context}
247
-
248
- Provide analysis with clear trends and supporting evidence."""
249
-
250
- messages = self._create_messages(user_message)
251
- response = await self._invoke_llm(messages)
252
-
253
- return response
 
1
  """Research Agent for gathering market intelligence data."""
2
 
3
+ from typing import 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
+ from src.utils.prompts import (
10
+ RESEARCHER_ANALYZE_COMPANY,
11
+ RESEARCHER_ANALYZE_COMPETITORS,
12
+ RESEARCHER_ANALYZE_TRENDS,
13
+ RESEARCHER_SYSTEM,
14
+ )
15
+ from src.workflows.types import ResearchOutput
16
 
17
  logger = setup_logger(__name__)
18
 
 
53
 
54
  def get_system_prompt(self) -> str:
55
  """Get system prompt for research agent."""
56
+ return RESEARCHER_SYSTEM
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
  async def run( # type: ignore[override]
59
  self,
60
  company_name: str,
61
  industry: Optional[str] = None,
62
  research_depth: str = "comprehensive",
63
+ ) -> ResearchOutput:
64
  """
65
  Gather research data about a company.
66
 
 
78
  """
79
  logger.info(f"Starting research for: {company_name}")
80
 
81
+ results: ResearchOutput = {
82
  "company_name": company_name,
83
  "industry": industry,
84
  "company_overview": "",
 
94
  max_results=10 if research_depth == "comprehensive" else 5,
95
  )
96
 
97
+ results["raw_sources"].extend(company_data.get("results", []))
 
98
 
99
  # Analyze company data with LLM
100
  company_context = self.search_tool.format_results_for_llm(company_data)
 
110
  max_results=10 if research_depth == "comprehensive" else 5,
111
  )
112
 
113
+ results["raw_sources"].extend(competitor_data.get("results", []))
 
114
 
115
  competitor_context = self.search_tool.format_results_for_llm(
116
  competitor_data
 
127
  max_results=8 if research_depth == "comprehensive" else 4,
128
  )
129
 
130
+ results["raw_sources"].extend(trend_data.get("results", []))
 
131
 
132
  trend_context = self.search_tool.format_results_for_llm(trend_data)
133
  trend_analysis = await self._analyze_trends(industry, trend_context)
 
149
  company_name: str,
150
  search_context: str,
151
  ) -> str:
152
+ """Analyze company information from search results."""
153
+ user_message = RESEARCHER_ANALYZE_COMPANY.format(
154
+ company_name=company_name, search_context=search_context
155
+ )
156
+ return await self._invoke_llm(self._create_messages(user_message))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
  async def _analyze_competitors(
159
  self,
160
  company_name: str,
161
  search_context: str,
162
  ) -> str:
163
+ """Analyze competitor landscape."""
164
+ user_message = RESEARCHER_ANALYZE_COMPETITORS.format(
165
+ company_name=company_name, search_context=search_context
166
+ )
167
+ return await self._invoke_llm(self._create_messages(user_message))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
  async def _analyze_trends(
170
  self,
171
  industry: str,
172
  search_context: str,
173
  ) -> str:
174
+ """Analyze market trends."""
175
+ user_message = RESEARCHER_ANALYZE_TRENDS.format(
176
+ industry=industry, search_context=search_context
177
+ )
178
+ return await self._invoke_llm(self._create_messages(user_message))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/agents/writer.py CHANGED
@@ -1,11 +1,17 @@
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
 
@@ -43,33 +49,13 @@ class WriterAgent(BaseAgent):
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( # type: ignore[override]
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
 
@@ -83,7 +69,7 @@ Write for senior executives and decision-makers."""
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:
@@ -119,133 +105,41 @@ Write for senior executives and decision-makers."""
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
 
1
  """Writer Agent for generating professional market intelligence reports."""
2
 
3
  from datetime import datetime
4
+ from typing import 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
+ from src.utils.prompts import (
10
+ WRITER_EXECUTIVE_SUMMARY,
11
+ WRITER_FULL_REPORT,
12
+ WRITER_SYSTEM,
13
+ )
14
+ from src.workflows.types import AnalysisOutput, ReportOutput, ResearchOutput
15
 
16
  logger = setup_logger(__name__)
17
 
 
49
 
50
  def get_system_prompt(self) -> str:
51
  """Get system prompt for writer agent."""
52
+ return WRITER_SYSTEM
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
  async def run( # type: ignore[override]
55
  self,
56
+ research_data: ResearchOutput,
57
+ analysis_data: AnalysisOutput,
58
+ ) -> ReportOutput:
59
  """
60
  Generate comprehensive market intelligence report.
61
 
 
69
  - full_report: Complete markdown report
70
  - metadata: Report metadata (date, sources count, etc.)
71
  """
72
+ company_name = research_data.get("company_name")
73
  logger.info(f"Starting report generation for: {company_name}")
74
 
75
  try:
 
105
 
106
  async def _write_executive_summary(
107
  self,
108
+ research_data: ResearchOutput,
109
+ analysis_data: AnalysisOutput,
110
  ) -> str:
111
+ """Write executive summary (200-300 words)."""
112
+ user_message = WRITER_EXECUTIVE_SUMMARY.format(
113
+ company_name=research_data.get("company_name"),
114
+ company_overview=research_data.get("company_overview", ""),
115
+ swot=analysis_data.get("swot", ""),
116
+ strategic_recommendations=analysis_data.get(
117
+ "strategic_recommendations", ""
118
+ ),
119
+ )
120
+ return await self._invoke_llm(self._create_messages(user_message))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
  async def _write_full_report(
123
  self,
124
+ research_data: ResearchOutput,
125
+ analysis_data: AnalysisOutput,
126
  exec_summary: str,
127
  ) -> str:
128
+ """Write complete markdown report."""
 
 
 
 
 
 
 
 
 
 
129
  company_name = research_data.get("company_name")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
+ user_message = WRITER_FULL_REPORT.format(
132
+ company_name=company_name,
133
+ exec_summary=exec_summary,
134
+ company_overview=research_data.get("company_overview", ""),
135
+ competitors=research_data.get("competitors", ""),
136
+ competitive_matrix=analysis_data.get("competitive_matrix", ""),
137
+ swot=analysis_data.get("swot", ""),
138
+ positioning=analysis_data.get("positioning", ""),
139
+ market_trends=research_data.get("market_trends", ""),
140
+ strategic_recommendations=analysis_data.get(
141
+ "strategic_recommendations", ""
142
+ ),
143
+ date=datetime.now().strftime("%B %d, %Y"),
144
+ )
145
+ return await self._invoke_llm(self._create_messages(user_message))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utils/prompts.py ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Centralized prompt management for the Agentic Market Research system.
3
+
4
+ "Words are the source of misunderstandings." - Antoine de Saint-Exupéry
5
+ But here, they are the source of intelligence.
6
+ """
7
+
8
+ # ==============================================================================
9
+ # RESEARCH AGENT PROMPTS
10
+ # ==============================================================================
11
+
12
+ RESEARCHER_SYSTEM = """You are a professional business research analyst specializing in competitive intelligence.
13
+
14
+ Your role is to gather and synthesize information about companies, markets, and competitors from web sources.
15
+
16
+ When analyzing search results, you should:
17
+ 1. Focus on factual, verifiable information
18
+ 2. Identify key data points: revenue, employees, products, market position
19
+ 3. Note dates and sources for important claims
20
+ 4. Distinguish between facts and opinions
21
+ 5. Highlight competitive advantages and weaknesses
22
+
23
+ Provide your analysis in a structured format with clear sections and bullet points.
24
+ Always cite sources when making specific claims."""
25
+
26
+ RESEARCHER_ANALYZE_COMPANY = """Analyze the following search results about {company_name}.
27
+
28
+ Provide a structured analysis covering:
29
+ 1. Company Overview (founded, headquarters, size)
30
+ 2. Products & Services (main offerings)
31
+ 3. Business Model (how they make money)
32
+ 4. Market Position (market share, ranking)
33
+ 5. Key Metrics (revenue, employees, growth)
34
+
35
+ Search Results:
36
+ {search_context}
37
+
38
+ Provide your analysis in clear sections with bullet points. Cite sources for specific claims."""
39
+
40
+ RESEARCHER_ANALYZE_COMPETITORS = """Analyze the competitive landscape for {company_name}.
41
+
42
+ Based on the search results, identify:
43
+ 1. Main Competitors (list 3-5 key competitors)
44
+ 2. Competitive Positioning (how each differs)
45
+ 3. Market Dynamics (who leads, who follows)
46
+ 4. Differentiation Factors (what makes each unique)
47
+
48
+ Search Results:
49
+ {search_context}
50
+
51
+ Format as a structured list with clear comparisons."""
52
+
53
+ RESEARCHER_ANALYZE_TRENDS = """Analyze market trends for the {industry} industry.
54
+
55
+ Identify:
56
+ 1. Key Trends (major shifts in the market)
57
+ 2. Growth Drivers (what's fueling growth)
58
+ 3. Challenges (obstacles facing the industry)
59
+ 4. Future Outlook (predictions for next 1-2 years)
60
+
61
+ Search Results:
62
+ {search_context}
63
+
64
+ Provide analysis with clear trends and supporting evidence."""
65
+
66
+ # ==============================================================================
67
+ # ANALYST AGENT PROMPTS
68
+ # ==============================================================================
69
+
70
+ ANALYST_SYSTEM = """You are a strategic business analyst specializing in competitive intelligence and market analysis.
71
+
72
+ Your role is to analyze research data and provide actionable strategic insights.
73
+
74
+ When performing analysis, you should:
75
+ 1. Use structured frameworks (SWOT, competitive matrices, positioning maps)
76
+ 2. Identify clear patterns and trends
77
+ 3. Provide specific, actionable recommendations
78
+ 4. Support conclusions with evidence from the research
79
+ 5. Consider multiple perspectives (competitors, customers, market forces)
80
+
81
+ Your analysis should be:
82
+ - Objective and data-driven
83
+ - Structured and easy to scan
84
+ - Focused on business impact
85
+ - Actionable for decision-makers
86
+
87
+ Use bullet points, clear headings, and strategic language."""
88
+
89
+ ANALYST_SWOT = """Based on the research data, perform a comprehensive SWOT analysis for {company_name}.
90
+
91
+ Research Data:
92
+
93
+ COMPANY OVERVIEW:
94
+ {company_overview}
95
+
96
+ COMPETITORS:
97
+ {competitors}
98
+
99
+ MARKET TRENDS:
100
+ {market_trends}
101
+
102
+ Provide a detailed SWOT analysis with:
103
+
104
+ STRENGTHS (internal positive factors):
105
+ - List 4-6 key strengths
106
+ - Focus on competitive advantages, resources, capabilities
107
+
108
+ WEAKNESSES (internal negative factors):
109
+ - List 4-6 key weaknesses
110
+ - Include operational limits, resource constraints, vulnerabilities
111
+
112
+ OPPORTUNITIES (external positive factors):
113
+ - List 4-6 market opportunities
114
+ - Consider market trends, gaps, emerging needs
115
+
116
+ THREATS (external negative factors):
117
+ - List 4-6 threats
118
+ - Include competitive threats, market risks, industry challenges
119
+
120
+ Use bullet points and be specific with evidence."""
121
+
122
+ ANALYST_COMPETITIVE_MATRIX = """Based on the competitor research, create a competitive matrix comparing {company_name} with its main competitors.
123
+
124
+ Competitor Research:
125
+ {competitors_info}
126
+
127
+ Create a comparison matrix with these dimensions:
128
+ 1. Market Share/Size
129
+ 2. Product Range
130
+ 3. Pricing Strategy
131
+ 4. Technology/Innovation
132
+ 5. Customer Segments
133
+ 6. Strengths
134
+ 7. Weaknesses
135
+
136
+ Format as a clear table or structured comparison.
137
+ Include 3-5 main competitors plus {company_name}.
138
+ Use "High/Medium/Low" or specific data points where available."""
139
+
140
+ ANALYST_POSITIONING = """Analyze the market positioning of {company_name}.
141
+
142
+ Company Overview:
143
+ {company_overview}
144
+
145
+ Competitive Landscape:
146
+ {competitors}
147
+
148
+ Provide analysis covering:
149
+
150
+ 1. CURRENT POSITIONING
151
+ - How is {company_name} currently positioned in the market?
152
+ - What is their value proposition?
153
+ - What customer segments do they target?
154
+
155
+ 2. COMPETITIVE DIFFERENTIATION
156
+ - What makes {company_name} different from competitors?
157
+ - What is their unique selling proposition (USP)?
158
+ - Where do they fit in the competitive landscape?
159
+
160
+ 3. POSITIONING GAPS
161
+ - Are there market segments they're missing?
162
+ - Are there positioning opportunities?
163
+ - How could they strengthen their position?
164
+
165
+ Be specific and strategic."""
166
+
167
+ ANALYST_RECOMMENDATIONS = """Based on the SWOT analysis and market trends, provide strategic recommendations for {company_name}.
168
+
169
+ SWOT ANALYSIS:
170
+ {swot}
171
+
172
+ MARKET TRENDS:
173
+ {market_trends}
174
+
175
+ Provide 5-7 actionable strategic recommendations organized by priority:
176
+
177
+ HIGH PRIORITY (immediate action needed):
178
+ - Recommendation 1 (with rationale)
179
+ - Recommendation 2 (with rationale)
180
+
181
+ MEDIUM PRIORITY (next 6-12 months):
182
+ - Recommendation 3 (with rationale)
183
+ - Recommendation 4 (with rationale)
184
+
185
+ LONG-TERM (strategic initiatives):
186
+ - Recommendation 5 (with rationale)
187
+
188
+ Each recommendation should:
189
+ - Be specific and actionable
190
+ - Address a key opportunity or threat
191
+ - Leverage strengths or address weaknesses
192
+ - Include expected impact"""
193
+
194
+ # ==============================================================================
195
+ # WRITER AGENT PROMPTS
196
+ # ==============================================================================
197
+
198
+ WRITER_SYSTEM = """You are a professional business report writer specializing in market intelligence and competitive analysis.
199
+
200
+ Your role is to transform research and analysis into polished, executive-ready reports.
201
+
202
+ When writing reports, you should:
203
+ 1. Use clear, professional business language
204
+ 2. Structure content logically with proper headings
205
+ 3. Include executive summaries for busy stakeholders
206
+ 4. Use bullet points and tables for scannability
207
+ 5. Cite sources properly
208
+ 6. Make insights actionable
209
+
210
+ Report format guidelines:
211
+ - Use markdown formatting
212
+ - Include clear section headers (#, ##, ###)
213
+ - Use tables for competitive comparisons
214
+ - Include bullet points for lists
215
+ - Add citations [source]
216
+ - Keep executive summary to 200-300 words
217
+
218
+ Write for senior executives and decision-makers."""
219
+
220
+ WRITER_EXECUTIVE_SUMMARY = """Write a concise executive summary for a market intelligence report on {company_name}.
221
+
222
+ Use this information:
223
+
224
+ COMPANY OVERVIEW:
225
+ {company_overview}
226
+
227
+ KEY INSIGHTS FROM SWOT:
228
+ {swot}
229
+
230
+ STRATEGIC RECOMMENDATIONS:
231
+ {strategic_recommendations}
232
+
233
+ Requirements:
234
+ - 200-300 words
235
+ - Cover: company overview, market position, key findings, main recommendations
236
+ - Written for senior executives (clear, actionable)
237
+ - Professional business tone
238
+
239
+ Start directly with content (no "Executive Summary" heading)."""
240
+
241
+ WRITER_FULL_REPORT = """Create a comprehensive market intelligence report for {company_name} in markdown format.
242
+
243
+ Use all the provided research and analysis data.
244
+
245
+ Structure the report as follows:
246
+
247
+ # Market Intelligence Report: {company_name}
248
+
249
+ ## Executive Summary
250
+ {exec_summary}
251
+
252
+ ## 1. Company Overview
253
+ {company_overview}
254
+
255
+ ## 2. Competitive Landscape
256
+ {competitors}
257
+ {competitive_matrix}
258
+
259
+ ## 3. SWOT Analysis
260
+ {swot}
261
+
262
+ ## 4. Market Positioning
263
+ {positioning}
264
+
265
+ ## 5. Market Trends & Insights
266
+ {market_trends}
267
+
268
+ ## 6. Strategic Recommendations
269
+ {strategic_recommendations}
270
+
271
+ ## 7. Sources
272
+ [List key sources used]
273
+
274
+ ---
275
+ Report generated: {date}
276
+
277
+ Format requirements:
278
+ - Use proper markdown (headers, bullets, tables)
279
+ - Make it professional and polished
280
+ - Include all relevant details
281
+ - Cite sources where appropriate
282
+ - Make it actionable for executives"""
src/workflows/market_analysis.py CHANGED
@@ -2,6 +2,7 @@
2
 
3
  from langgraph.graph import StateGraph, END
4
  from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
 
5
 
6
  from src.workflows.types import IntelligenceState
7
  from src.agents.researcher import ResearchAgent
@@ -108,8 +109,8 @@ class MarketIntelligenceWorkflow:
108
  return {
109
  "current_agent": "research",
110
  "research_data": research_results,
111
- "competitors": research_results.get("competitors", []),
112
- "market_trends": research_results.get("market_trends", {}),
113
  "raw_sources": research_results.get("raw_sources", []),
114
  "iteration": state.get("iteration", 0) + 1,
115
  }
@@ -137,11 +138,11 @@ class MarketIntelligenceWorkflow:
137
  # Update state
138
  return {
139
  "current_agent": "analysis",
140
- "swot": analysis_results.get("swot", {}),
141
- "competitive_matrix": analysis_results.get("competitive_matrix", {}),
142
- "positioning": analysis_results.get("positioning", {}),
143
  "strategic_recommendations": analysis_results.get(
144
- "strategic_recommendations", {}
145
  ),
146
  }
147
 
@@ -167,11 +168,12 @@ class MarketIntelligenceWorkflow:
167
  report_results = await self.writer_agent.run(
168
  research_data=state["research_data"],
169
  analysis_data={
170
- "swot": state.get("swot", {}),
171
- "competitive_matrix": state.get("competitive_matrix", {}),
172
- "positioning": state.get("positioning", {}),
 
173
  "strategic_recommendations": state.get(
174
- "strategic_recommendations", {}
175
  ),
176
  },
177
  )
@@ -265,14 +267,21 @@ class MarketIntelligenceWorkflow:
265
  "company_name": company_name,
266
  "industry": industry,
267
  "research_depth": research_depth,
268
- "research_data": {},
269
- "competitors": [],
270
- "market_trends": {},
 
 
 
 
 
 
 
271
  "raw_sources": [],
272
- "swot": {},
273
- "competitive_matrix": {},
274
- "positioning": {},
275
- "strategic_recommendations": {},
276
  "executive_summary": "",
277
  "full_report": "",
278
  "report_metadata": {},
@@ -290,11 +299,16 @@ class MarketIntelligenceWorkflow:
290
  config = {"configurable": {"thread_id": thread_id or "default"}}
291
 
292
  try:
293
- async with AsyncSqliteSaver.from_conn_string(
294
- self.checkpoint_path
295
- ) as checkpointer:
296
- workflow = self.graph_builder.compile(checkpointer=checkpointer)
297
  final_state = await workflow.ainvoke(initial_state, config) # type: ignore[arg-type]
 
 
 
 
 
 
298
 
299
  logger.info(f"Workflow complete. Cost: ${final_state['total_cost']:.4f}")
300
  return final_state
 
2
 
3
  from langgraph.graph import StateGraph, END
4
  from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
5
+ from langgraph.checkpoint.memory import MemorySaver
6
 
7
  from src.workflows.types import IntelligenceState
8
  from src.agents.researcher import ResearchAgent
 
109
  return {
110
  "current_agent": "research",
111
  "research_data": research_results,
112
+ "competitors": research_results.get("competitors", ""),
113
+ "market_trends": research_results.get("market_trends", ""),
114
  "raw_sources": research_results.get("raw_sources", []),
115
  "iteration": state.get("iteration", 0) + 1,
116
  }
 
138
  # Update state
139
  return {
140
  "current_agent": "analysis",
141
+ "swot": analysis_results.get("swot", ""),
142
+ "competitive_matrix": analysis_results.get("competitive_matrix", ""),
143
+ "positioning": analysis_results.get("positioning", ""),
144
  "strategic_recommendations": analysis_results.get(
145
+ "strategic_recommendations", ""
146
  ),
147
  }
148
 
 
168
  report_results = await self.writer_agent.run(
169
  research_data=state["research_data"],
170
  analysis_data={
171
+ "company_name": state["company_name"],
172
+ "swot": state.get("swot", ""),
173
+ "competitive_matrix": state.get("competitive_matrix", ""),
174
+ "positioning": state.get("positioning", ""),
175
  "strategic_recommendations": state.get(
176
+ "strategic_recommendations", ""
177
  ),
178
  },
179
  )
 
267
  "company_name": company_name,
268
  "industry": industry,
269
  "research_depth": research_depth,
270
+ "research_data": {
271
+ "company_name": company_name,
272
+ "industry": industry,
273
+ "company_overview": "",
274
+ "competitors": "",
275
+ "market_trends": "",
276
+ "raw_sources": [],
277
+ },
278
+ "competitors": "",
279
+ "market_trends": "",
280
  "raw_sources": [],
281
+ "swot": "",
282
+ "competitive_matrix": "",
283
+ "positioning": "",
284
+ "strategic_recommendations": "",
285
  "executive_summary": "",
286
  "full_report": "",
287
  "report_metadata": {},
 
299
  config = {"configurable": {"thread_id": thread_id or "default"}}
300
 
301
  try:
302
+ if self.checkpoint_path == ":memory:":
303
+ memory_checkpointer = MemorySaver()
304
+ workflow = self.graph_builder.compile(checkpointer=memory_checkpointer)
 
305
  final_state = await workflow.ainvoke(initial_state, config) # type: ignore[arg-type]
306
+ else:
307
+ async with AsyncSqliteSaver.from_conn_string(
308
+ self.checkpoint_path
309
+ ) as checkpointer:
310
+ workflow = self.graph_builder.compile(checkpointer=checkpointer)
311
+ final_state = await workflow.ainvoke(initial_state, config) # type: ignore[arg-type]
312
 
313
  logger.info(f"Workflow complete. Cost: ${final_state['total_cost']:.4f}")
314
  return final_state
src/workflows/types.py CHANGED
@@ -1,9 +1,38 @@
1
  """State definitions for LangGraph workflow."""
2
 
3
- from typing import Annotated, Literal, TypedDict
4
  import operator
5
 
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  class IntelligenceState(TypedDict):
8
  """
9
  State for market intelligence workflow.
@@ -13,34 +42,34 @@ class IntelligenceState(TypedDict):
13
 
14
  # Input
15
  company_name: str
16
- industry: str | None
17
  research_depth: str # "basic" or "comprehensive"
18
 
19
  # Research phase outputs
20
- research_data: dict
21
- competitors: list[dict]
22
- market_trends: dict
23
- raw_sources: list[dict]
24
 
25
  # Analysis phase outputs
26
- swot: dict
27
- competitive_matrix: dict
28
- positioning: dict
29
- strategic_recommendations: dict
30
 
31
  # Writing phase outputs
32
  executive_summary: str
33
  full_report: str
34
- report_metadata: dict
35
 
36
  # Workflow metadata
37
  current_agent: Literal["research", "analysis", "writing", "human_review", "done"]
38
  iteration: int
39
  total_cost: float
40
  total_tokens: int
41
- errors: Annotated[list, operator.add] # Accumulate errors across nodes
42
 
43
  # Human-in-the-loop
44
- human_feedback: str | None
45
  approved: bool
46
  revision_count: int
 
1
  """State definitions for LangGraph workflow."""
2
 
3
+ from typing import Annotated, Any, Dict, List, Literal, TypedDict, Union
4
  import operator
5
 
6
 
7
+ class ResearchOutput(TypedDict):
8
+ """Output structure for Research Agent."""
9
+
10
+ company_name: str
11
+ industry: Union[str, None]
12
+ company_overview: str
13
+ competitors: str
14
+ market_trends: str
15
+ raw_sources: List[Any]
16
+
17
+
18
+ class AnalysisOutput(TypedDict):
19
+ """Output structure for Analysis Agent."""
20
+
21
+ company_name: str
22
+ swot: str
23
+ competitive_matrix: str
24
+ positioning: str
25
+ strategic_recommendations: str
26
+
27
+
28
+ class ReportOutput(TypedDict):
29
+ """Output structure for Writer Agent."""
30
+
31
+ executive_summary: str
32
+ full_report: str
33
+ metadata: Dict[str, Any]
34
+
35
+
36
  class IntelligenceState(TypedDict):
37
  """
38
  State for market intelligence workflow.
 
42
 
43
  # Input
44
  company_name: str
45
+ industry: Union[str, None]
46
  research_depth: str # "basic" or "comprehensive"
47
 
48
  # Research phase outputs
49
+ research_data: ResearchOutput
50
+ competitors: str # Markdown string from analysis
51
+ market_trends: str # Markdown string from analysis
52
+ raw_sources: List[Any]
53
 
54
  # Analysis phase outputs
55
+ swot: str
56
+ competitive_matrix: str
57
+ positioning: str
58
+ strategic_recommendations: str
59
 
60
  # Writing phase outputs
61
  executive_summary: str
62
  full_report: str
63
+ report_metadata: Dict[str, Any]
64
 
65
  # Workflow metadata
66
  current_agent: Literal["research", "analysis", "writing", "human_review", "done"]
67
  iteration: int
68
  total_cost: float
69
  total_tokens: int
70
+ errors: Annotated[List[str], operator.add] # Accumulate errors across nodes
71
 
72
  # Human-in-the-loop
73
+ human_feedback: Union[str, None]
74
  approved: bool
75
  revision_count: int
tests/integration/test_workflow_integration.py CHANGED
@@ -12,7 +12,7 @@ class TestWorkflowErrorRecovery:
12
 
13
  async def test_research_error_ends_workflow(self):
14
  """Test workflow ends gracefully when research fails."""
15
- workflow = MarketIntelligenceWorkflow()
16
 
17
  # Mock research to fail
18
  # Mock research to fail
@@ -29,7 +29,9 @@ class TestWorkflowErrorRecovery:
29
 
30
  async def test_budget_exceeded_stops_workflow(self):
31
  """Test workflow stops when budget is exceeded."""
32
- workflow = MarketIntelligenceWorkflow(max_budget=0.001)
 
 
33
 
34
  # Mock research to succeed with some cost
35
  # Mock research to succeed with some cost
@@ -37,9 +39,11 @@ class TestWorkflowErrorRecovery:
37
  workflow.cost_tracker.track_usage("openai/gpt-5-mini", 10000, 5000)
38
  return {
39
  "company_name": "Mock Company",
40
- "competitors": [],
41
- "market_trends": {},
42
  "raw_sources": [],
 
 
43
  }
44
 
45
  workflow.research_agent.run = AsyncMock(side_effect=mock_run)
@@ -58,25 +62,28 @@ class TestWorkflowIntegration:
58
 
59
  async def test_workflow_with_mocked_agents(self):
60
  """Test complete workflow with mocked agent responses."""
61
- workflow = MarketIntelligenceWorkflow()
62
 
63
  # Mock all agents
64
  # Mock all agents
65
  workflow.research_agent.run = AsyncMock(
66
  return_value={
67
  "company_name": "Test Company",
68
- "competitors": [{"name": "Competitor A"}],
69
- "market_trends": {"trend": "growing"},
70
  "raw_sources": [{"url": "test.com"}],
 
 
71
  }
72
  )
73
 
74
  workflow.analysis_agent.run = AsyncMock(
75
  return_value={
76
- "swot": {"strengths": ["good"]},
77
- "competitive_matrix": {},
78
- "positioning": {},
79
- "strategic_recommendations": {},
 
80
  }
81
  )
82
 
 
12
 
13
  async def test_research_error_ends_workflow(self):
14
  """Test workflow ends gracefully when research fails."""
15
+ workflow = MarketIntelligenceWorkflow(checkpoint_path=":memory:")
16
 
17
  # Mock research to fail
18
  # Mock research to fail
 
29
 
30
  async def test_budget_exceeded_stops_workflow(self):
31
  """Test workflow stops when budget is exceeded."""
32
+ workflow = MarketIntelligenceWorkflow(
33
+ max_budget=0.001, checkpoint_path=":memory:"
34
+ )
35
 
36
  # Mock research to succeed with some cost
37
  # Mock research to succeed with some cost
 
39
  workflow.cost_tracker.track_usage("openai/gpt-5-mini", 10000, 5000)
40
  return {
41
  "company_name": "Mock Company",
42
+ "competitors": "Competitor A, Competitor B",
43
+ "market_trends": "Market is growing",
44
  "raw_sources": [],
45
+ "industry": "Tech",
46
+ "company_overview": "Overview",
47
  }
48
 
49
  workflow.research_agent.run = AsyncMock(side_effect=mock_run)
 
62
 
63
  async def test_workflow_with_mocked_agents(self):
64
  """Test complete workflow with mocked agent responses."""
65
+ workflow = MarketIntelligenceWorkflow(checkpoint_path=":memory:")
66
 
67
  # Mock all agents
68
  # Mock all agents
69
  workflow.research_agent.run = AsyncMock(
70
  return_value={
71
  "company_name": "Test Company",
72
+ "competitors": "Competitor A",
73
+ "market_trends": "Market growing",
74
  "raw_sources": [{"url": "test.com"}],
75
+ "industry": "Tech",
76
+ "company_overview": "Overview",
77
  }
78
  )
79
 
80
  workflow.analysis_agent.run = AsyncMock(
81
  return_value={
82
+ "company_name": "Test Company",
83
+ "swot": "Strengths: Good",
84
+ "competitive_matrix": "Matrix data",
85
+ "positioning": "Leader",
86
+ "strategic_recommendations": "Buy low sell high",
87
  }
88
  )
89