diff --git a/agent_workflows/crew_setup.py b/agent_workflows/crew_setup.py index dd50f9c..80053d1 100644 --- a/agent_workflows/crew_setup.py +++ b/agent_workflows/crew_setup.py @@ -41,13 +41,20 @@ def create_crew(config): # 1. DEFINE CUSTOM TOOLS & OUTPUT SCHEMAS # ========================================== + class CompetitorDetail(BaseModel): + company_name: str = PydanticField(..., description="The name of the competitor company.") + pricing_range: str = PydanticField(..., description="Pricing tiers or estimated cost ranges.") + core_features: List[str] = PydanticField(..., description="Key modules, tools, or features offered.") + target_audience: str = PydanticField(..., description="The primary customer profile or ideal user persona.") + class ResearchEvidence(BaseModel): source_url: str = PydanticField(..., description="The source URL of the evidence.") claim: str = PydanticField(..., description="The factual finding or data point.") context: str = PydanticField(..., description="The context or time frame of this data point.") - + class ResearchOutputSchema(BaseModel): - competitors: List[Dict[str, Any]] = PydanticField(..., description="List of competitors with products pricing range, features, and target audience.") + # CHANGED: Replaced List[Dict[str, Any]] with List[CompetitorDetail] + competitors: List[CompetitorDetail] = PydanticField(..., description="List of competitors with products pricing range, features, and target audience.") market_trends: List[str] = PydanticField(..., description="Market Analysis & Growth Trends") evidence_citations: List[ResearchEvidence] = PydanticField(..., description="Valid source citations for facts collected.") @@ -154,7 +161,7 @@ def create_crew(config): return f"Error executing search: {str(e)}" @tool("FastMCP Batch Web Content extraction Tool") - def fastmcp_batch_web_content_extraction_tool(urls: list[str]) -> str: + def fastmcp_batch_web_content_extraction_tool(urls: list[str], query: str) -> str: """ Connects directly to the FastMCP HTTP server to scrape and extract text from a list of multiple URLs simultaneously in parallel. @@ -164,8 +171,8 @@ def create_crew(config): transport = StreamableHttpTransport("http://localhost:8000/mcp/") async with Client(transport) as client: - # call_tool abstracts away deep JSON-RPC structures perfectly - result = await client.call_tool("batch_web_content_extraction", {"urls": urls}) + # call_tool abstracts away deep JSON-RPC structures + result = await client.call_tool("batch_web_content_extraction", {"urls": urls, "query": query}) return result try: @@ -191,10 +198,10 @@ def create_crew(config): formatted_output.append(f"CONTENT:\n{item['content']}\n") formatted_output.append("=" * 40 + "\n") - logger.info(f"""FastMCP Batch Web Content extraction tool used.\nURLs:\n{"\n".join(urls)}\nResult:\n{"\n".join(formatted_output)}""") + logger.info(f"""FastMCP Batch Web Content extraction tool used.\nURLs:\n{"\n".join(urls)}\nQuery: {query}\nResult:\n{"\n".join(formatted_output)}""") return "\n".join(formatted_output) except json.JSONDecodeError: - logger.info(f"""FastMCP Batch Web Content extraction tool used. Error in JSON formatting so raw data returned:\nURLs:\n{"\n".join(urls)}\nResult:\n{raw_content}""") + logger.info(f"""FastMCP Batch Web Content extraction tool used. Error in JSON formatting so raw data returned:\nURLs:\n{"\n".join(urls)}\nQuery: {query}\nResult:\n{raw_content}""") return raw_content except Exception as e: @@ -209,7 +216,8 @@ def create_crew(config): llm = LLM( model=config.model, api_key=os.getenv("OPENAI_API_KEY"), - temperature=config.temperature, + temperature=config.temperature if config.model != "gpt-5-mini" else 1, + #reasoning_effort="low", max_completion_tokens=config.max_tokens, ) else: @@ -250,7 +258,7 @@ def create_crew(config): verbose=verbose, tools=[web_search_tool, fastmcp_batch_web_content_extraction_tool], use_system_prompt=False, - llm=llm + llm=llm, ) # 3. analyst @@ -262,6 +270,7 @@ def create_crew(config): #goal='Synthesize raw market data into structured frameworks like SWOT, 4P, 7P, and target user personas.', #backstory='You are a data-driven Strategic Analyst. You look past surface-level facts to find underlying market gaps, evaluate competitive positioning, and model risks.', verbose=verbose, + use_system_prompt=False, llm=llm ) @@ -274,6 +283,7 @@ def create_crew(config): #goal='Formulate the actionable Go-To-Market blueprint, positioning, pricing strategies, and launch timelines.', #backstory='You are a veteran GTM Growth Strategist. You specialize in crafting commercial launch playbooks, positioning products uniquely against rivals, and designing user-acquisition loops.', verbose=verbose, + use_system_prompt=False, llm=llm ) # --------------------------------------------------------- @@ -292,17 +302,20 @@ def create_crew(config): # "Do not fabricate any data and do not make assumptions. Only include verifiable facts using working URLs." # "If you cannot find a specific metric, leave it blank and do not fabricate data." description=(""" - 1. Using your Web Search Tool, search websites for the compagnies related the target market: '{query}'. When sending the search query, do not add any information about the year. It should be the most recent data. - 2. Review the result snippets and keep URLs that match the target market. Make sure URLs are not borken or do not redirect to "Not Found" website - 3. Collect those URLs into a list and feed them into the 'FastMCP Batch Web Content Extraction Tool' in a single call. - 4. Review the returned parallel scrape data and extract following information: - a. List of compagnies with products pricing range, keys features, and target audience. Do not list more than one time a specific compagny. - b. Market Analysis & Growth Trends - c. Evidence citations which include URL, the factual finding or data point and the context or time frame of this data point. - 5. Every single metric or claim MUST have a URL source tracked inside the JSON structure. + Research for competitor compagnies in the target market: {query} + Use your Web Search tool to find relevant and recent company, product, industry, and market sources. Do not add a year to the search query. Remove broken, duplicate, or irrelevant URLs. + Create a focused MCP query that describes the information needed, including products, pricing, key features, target customers, market trends, growth, and competitive information. + Send the selected URLs and the focused query to the FastMCP Batch Web Content Extraction Tool in a single call. + Analyze the returned content and produce: + Companies/products: company, products, price range, key features, and target audience. Do not duplicate companies. + Market analysis: market characteristics, growth, trends, drivers, and competitive developments. + Evidence: every factual claim or metric must have a source URL, factual finding, and context/timeframe. + Prefer primary and authoritative sources. Do not invent information. If information is unavailable, state that it was not found. + Critical: Every factual claim, metric, price, feature, market statistic, or growth figure must be traceable to a source URL. + Return valid JSON: """ ), - expected_output="A structured JSON file matching the schema and strict citation tracking.", + expected_output="A structured JSON payload containing validated competitor profiles, trend metrics, and verifiable source citations.", output_json=ResearchOutputSchema, agent=researcher_agent ) @@ -310,9 +323,10 @@ def create_crew(config): # Task 2: Analyst Agent creates comparative structures analysis_task = Task( description=( - "Review the structured JSON output provided by the Research Agent. " - "Construct a detailed markdown competitive landscape table and a pricing matrix. Include source URL in the table." - "Perform a comprehensive SWOT, 4P, and 7P analysis based strictly on the evidence compiled." + "1. Extract and read the text/JSON-formatted market research data passed to you from the Research Agent in the context below.\n" + "2. Using that data, construct a detailed markdown competitive landscape table and a pricing matrix. Ensure every row includes its respective source URL.\n" + "3. Perform a comprehensive SWOT, 4P, and 7P analysis based strictly on the facts and evidence compiled by the Research Agent.\n" + "4. Do not speculate or invent market details not present in the research context data." ), expected_output="A deep-dive analytical report featuring clean markdown tables, a SWOT quadrant, and an exhaustive 4P/7P matrix analysis.", context=[research_task], @@ -325,7 +339,7 @@ def create_crew(config): "Take the analytical matrices and SWOT outputs to build a comprehensive GTM document. " "Define 2 distinct Ideal Customer Profiles (ICPs). Outline the core value proposition. " "Create a messaging framework (Hook, Problem, Solution) per ICP. " - "Identify high-ROI acquisition channels and detail a 30-60-90 day milestone launch plan." + "Identify high-ROI acquisition channels and detail a 30-60-90 day milestone launch plan." \ ), expected_output="An actionable, comprehensive market entry strategy blueprint divided into logical execution phases.", context=[analysis_task], @@ -337,7 +351,8 @@ def create_crew(config): description=( """Synthesize the outputs from the Research, Analyst, and Strategy agents into a seamless, high-caliber Master GTM Report. Include all research data tables, citations, SWOT/7P strategic matrices, and execution timelines. - Include a title and small summary that cleary identifies the target market and highlights the core value proposition. Include the input query '{query}' for reference at the end of the summary. + Include a title + Inlude small summary that cleary identifies the target market including the input query '{query}' for reference. Include a table of contents. Include a conclusion and Evidence Citations section at the end. Ensure the document is formatted for executive-level presentation. diff --git a/app.py b/app.py index 14dce52..75571bf 100644 --- a/app.py +++ b/app.py @@ -36,8 +36,8 @@ if "chat_history" not in st.session_state: #llm_model = "gpt-4o" #Output~ 10 US$ / 1M tokens #llm_model = "gpt-5.5" #Output~ 30 US$ / 1M tokens MODEL_REGISTRY={ - "OpenAI":["gpt-4o-mini","gpt-5.4-nano","gpt-5-mini","gpt-4.1","gpt-4o","gpt-3.5-turbo"], - "Ollama":["gemma4:e4b","llama3.1","gpt-oss:20b","qwen2.5-coder:14b","mistral-small3.2"], + "OpenAI":["gpt-4o-mini","gpt-5-mini","gpt-5.4-nano","gpt-4.1","gpt-4o","gpt-3.5-turbo"], + "Ollama":["llama3.1","gemma4:e4b","gpt-oss:20b","qwen2.5-coder:14b","mistral-small3.2"], } #7- Creating Sidebar @@ -67,7 +67,7 @@ with st.sidebar: config.max_tokens=st.slider( "Max Tokens", min_value=100, - max_value=30000, + max_value=10000, value=config.max_tokens, step=100 )