diff --git a/.env b/.env new file mode 100644 index 0000000..e24b670 --- /dev/null +++ b/.env @@ -0,0 +1,7 @@ +#LANGCHAIN_TRACING_V2=true +#LANGCHAIN_PROJECT=capstone_msagi_VR +#LANGCHAIN_ENDPOINT=https://apac.api.smith.langchain.com + +LANGSMITH_TRACING_V2=true +LANGSMITH_ENDPOINT=https://apac.api.smith.langchain.com +LANGSMITH_PROJECT="capstone_msagi_VR" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fe6041 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Ignore all __pycache__ directories +**/__pycache__/ + +# Ignore compiled Python files +*.py[cod] + +venv/ +log/ +output/ + + +bin/__pycache__/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cc081ee --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python-envs.defaultEnvManager": "ms-python.python:venv" +} \ No newline at end of file diff --git a/1762856365_capstoneprojectproblemstatement.pdf b/1762856365_capstoneprojectproblemstatement.pdf new file mode 100644 index 0000000..61d5133 Binary files /dev/null and b/1762856365_capstoneprojectproblemstatement.pdf differ diff --git a/README.md b/README.md index 9d33e4d..8427727 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,35 @@ +# Go through the following steps to run the application: + +1) Create Python Virtual Env from your root directory +python -m venv venv + +2) Activate Virtual Env +a) Windows: venv\Scripts\activate.bat +b) Unix: source venv/bin/activate + +3) Installing Dependencies +pip install -r requirements.txt + +4) +Install GTK-for-Windows (needed to generate the PDF) +1.Download the installer and install: Go to the GTK-for-Windows runtime project on GitHub (https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases) +2.Ensure you check the box during installation that says "Add to the PATH environment variable". + +5) +Disable Smart App Control (SAC) on Windows +1.Open the Start menu, type Windows Security, and press Enter. +2.Click on App & browser control from the left navigation panel or home menu. +3.Click the Smart App Control settings link. +4.Select Off to disable the feature + +6) Setup OPENAI_API_KEY from terminal +set LANGCHAIN_API_KEY= +set OPENAI_API_KEY= + +7) Running the Streamlit Application +python -m streamlit run app.py + + # capstone_msagi Course-end Project: Product Strategy Simulation diff --git a/agent_workflows/crew_setup.py b/agent_workflows/crew_setup.py new file mode 100644 index 0000000..27c9895 --- /dev/null +++ b/agent_workflows/crew_setup.py @@ -0,0 +1,201 @@ +import os +from pydantic import BaseModel, Field as PydanticField # Alias it to avoid conflicts +from crewai.tools import tool +from typing import List, Dict, Any +from shared_config import verbose, logger +from crewai import Agent, Task, Crew, Process, LLM, TaskOutput, TaskOutput +from crewai.tools import tool +from langchain_community.tools import DuckDuckGoSearchRun +from datetime import datetime +import os + +########################################################################################### +#Define a callback function to log CrewAI step task +########################################################################################### +def log_task_to_file(output:TaskOutput): + """Callback function to append task details into the log file.""" + + formatted_str = f""" +{'\n'}CrewAI Task Info +{"=" * 50} +Timestamp: {datetime.now()} +Task Description: {output.description} +Agent Assigned: {output.agent} +{"-" * 30} +Raw Output:{'\n'} +{output.raw}{'\n'} +{"=" * 50} +""" + logger.info(formatted_str) + +def create_crew(config): + + # ========================================== + # 1. DEFINE CUSTOM TOOLS & OUTPUT SCHEMAS + # ========================================== + + # Simple search tool for the Research Agent + @tool("web_search") + def web_search_tool(query: str) -> str: + """Search the web for current events, news, or factual information.""" + ddg = DuckDuckGoSearchRun() + return ddg.invoke(query) + + + + 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 pricing, features, and target audience.") + market_trends: List[str] = PydanticField(..., description="Key macro trends observed.") + evidence_citations: List[ResearchEvidence] = PydanticField(..., description="Valid source citations for facts collected.") + + + # --------------------------------------------------------- + #Create llm instance + # --------------------------------------------------------- + if config.provider == "OpenAI": + llm = LLM( + model=config.model, + api_key=os.getenv("OPENAI_API_KEY"), + temperature=config.temperature, + max_completion_tokens=config.max_tokens, + ) + else: + llm = LLM( + model=f"ollama/{config.model}", + api_key="ollama", + base_url="http://localhost:11434", + temperature=config.temperature + ) + + logger.info(f"""LLM Model: {config.model}, Provider: {config.provider}, Temperature: {config.temperature}""") + + # --------------------------------------------------------- + # 1. Agent Definitions + # --------------------------------------------------------- + + # 1. Orchestrator & Documenter (Manager) + head_planner = Agent( + role='Head Planner and Orchestrator', + goal="Synthesize intermediary outputs and assemble the final master GTM document.", + backstory="A precise compiler and documentarian...", + #goal='Orchestrate the GTM workflow, delegate tasks to specialized agents, and synthesize all insights into a flawless final GTM document.', + #backstory='You are a Principal Operations Strategy Director. You excel at managing cross-functional teams, ensuring strict alignment, and formatting messy raw data into executive-ready corporate documentation.', + allow_delegation=False, + verbose=verbose, + llm=llm + ) + + # 2. researcher + researcher_agent = Agent( + role="Market Research Specialist", + goal="Gather raw competitive intelligence, target audience signals, and pricing tiers with structural citations.", + backstory="An elite OSINT researcher who tracks digital signals. You ignore marketing fluff and extract verifiable numbers, product capabilities, and structural URLs.", + #role='Research Agent', + #goal='Gather comprehensive market data, identify industry trends, and map out the competitive landscape.', + #backstory='You are an elite Market Research Analyst. You are an expert at mining deep web data, extracting customer pain points, and finding precise statistical facts from modern industry reports.', + verbose=verbose, + tools=[web_search_tool], + use_system_prompt=False, + llm=llm + ) + + # 3. analyst + analyst_agent = Agent( + role="Strategic Business Analyst", + goal="Transform unstructured research data into rigorous market models, SWOT matrixes, and 4P/7P frameworks.", + backstory="A former MBB consultant who specializes in corporate strategy. You find patterns in chaotic data, flag market gaps, and synthesize pricing elasticities.", + #role='Analyst Agent', + #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, + llm=llm + ) + + # 4. strategist + strategist_agent = Agent( + role="Go-To-Market (GTM) Strategist", + goal="Draft the final actionable GTM blueprint including ICP maps, messaging layers, and launch milestone phases.", + backstory="A legendary growth marketing executive. You turn raw analytical data into high-converting messaging matrixes, channel frameworks, and scalable product launch schedules.", + #role='Strategy Agent', + #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, + llm=llm + ) + # --------------------------------------------------------- + # 2. Task Definitions + # --------------------------------------------------------- + + #Fetch User query from config + query = config.user_query + + # Task 1: Research Agent gathers raw intelligence + research_task = Task( + description=( + "Search the web for the top 3-5 players in the target market: '{query}'. " + "Extract pricing tiers, core feature modules, and target customer profiles. " + "Every single metric or claim MUST have a URL source tracked inside the JSON structure. " + "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." + ), + expected_output="A structured JSON file matching the schema with validated competitive rows and strict citation tracking.", + output_json=ResearchOutputSchema, + agent=researcher_agent + ) + + # 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. " + "Perform a comprehensive SWOT, 4P, and 7P analysis based strictly on the evidence compiled." + ), + expected_output="A deep-dive analytical report featuring clean markdown tables, a SWOT quadrant, and an exhaustive 4P/7P matrix analysis.", + agent=analyst_agent + ) + + # Task 3: Strategy Agent plans market entry + strategy_task = Task( + description=( + "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." + ), + expected_output="An actionable, comprehensive market entry strategy blueprint divided into logical execution phases.", + agent=strategist_agent + ) + + # Task 4: Head Planner synthesizes, formats, and exports + compilation_task = Task( + 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 that cleary identifies the target market and a subtitle that highlights the core value proposition. " + ), + expected_output="Final master GTM document.", + context=[research_task, analysis_task, strategy_task], + agent=head_planner + ) + + # --------------------------------------------------------- + # 3. Crew Setup + # --------------------------------------------------------- + + crew= Crew( + agents=[researcher_agent, analyst_agent, strategist_agent, head_planner], + tasks=[research_task, analysis_task, strategy_task, compilation_task], + process=Process.sequential, # Tasks execute in exact sequence + #manager_agent=head_planner + task_callback=log_task_to_file, + verbose=verbose + ) + + logger.info(f"Crew setup complete with {len(crew.agents)} agents and {len(crew.tasks)} tasks.") + return crew + diff --git a/agent_workflows/orchestration.py b/agent_workflows/orchestration.py new file mode 100644 index 0000000..6bf2762 --- /dev/null +++ b/agent_workflows/orchestration.py @@ -0,0 +1,38 @@ + +from langsmith import traceable +from agent_workflows.crew_setup import create_crew +from shared_config import AgenticAIConfig, logger +from langsmith.integrations.otel import OtelSpanProcessor +from opentelemetry import trace +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.instrumentation.crewai import CrewAIInstrumentor +from opentelemetry.instrumentation.openai import OpenAIInstrumentor + +# Initialize the trace provider +tracer_provider = TracerProvider() +#trace.set_tracer_provider(tracer_provider) + +# Add the LangSmith OTel processor +tracer_provider.add_span_processor(OtelSpanProcessor()) + +# Instrument both CrewAI and OpenAI layers +CrewAIInstrumentor().instrument(tracer_provider=tracer_provider) +OpenAIInstrumentor().instrument(tracer_provider=tracer_provider) + +@traceable(name="Capstone_GTM Strategy Simulation", run_type="chain") +def runAgenticWorkflow(config: AgenticAIConfig): + """ + Main Entry Point of Agentic AI Workflow + """ + logger.info("Agentic Workflow Started...") + + crew = create_crew(config) + + result = crew.kickoff( + inputs={ + "query": config.user_query + } + ) + + config.response = str(result) + return config \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..ee41141 --- /dev/null +++ b/app.py @@ -0,0 +1,161 @@ + +from pathlib import Path +from shared_config import AgenticAIConfig, create_logger, logger +import streamlit as st +from dotenv import load_dotenv +from agent_workflows.orchestration import runAgenticWorkflow +from utilities.pdf_tools import export_to_pdf +from datetime import datetime + + +#2- Loading Env Variables +load_dotenv() # It will load all the Env Variables + +#3- Set Page Config +st.set_page_config( + page_title="Simplilearn Capstone Project", #Show it in the Tab + page_icon="🤖", + layout="wide" +) + +#4- Initialize Config +if "config" not in st.session_state: + st.session_state.config = AgenticAIConfig() +config = st.session_state.config + +#5- Initialize Streamlit- Conversation History +if "chat_history" not in st.session_state: + st.session_state.chat_history=[] + +#6- Setting dict for Available Models +#llm_model = "gpt-5-nano" #Output~ 0.4 US$ / 1M tokens +#llm_model = "gpt-4o-mini" #Output~ 0.6 US$ / 1M tokens +#llm_model = "gpt-5.4-nano" #Output~ 1.25 US$ / 1M tokens +#llm_model = "gpt-5-mini" #Output~ 2 US$ / 1M tokens +#llm_model = "gpt-5.4-mini" #Output~ 4.50 US$ / 1M tokens +#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":["llama3.1","gpt-oss:20b","gemma4:e4b","qwen2.5-coder:14b","mistral-small3.2"], + } + +#7- Creating Sidebar +with st.sidebar: + st.title("LLM Configuration") + config.provider = st.selectbox( + "LLM Provider", + list(MODEL_REGISTRY.keys()) + ) + + #Dynamic Model Selection Logic + available_models = MODEL_REGISTRY[config.provider] + + config.model = st.selectbox( + "Model", + available_models + ) + + config.temperature= st.slider( + "Temperature", + min_value=0.0, + max_value=2.0, + value=config.temperature, + step=0.1 + ) + + config.max_tokens=st.slider( + "Max Tokens", + min_value=100, + max_value=4000, + value=config.max_tokens, + step=100 + ) + + st.divider() + + # Display Current Configurations Separately + st.subheader("Current Configuration:") + + st.write(f"**Provider:** {config.provider}") + st.write(f"**Model:** {config.model}") + st.write(f"**Temperature:** {config.temperature}") + st.write(f"**Max Token:** {config.max_tokens}") + + +#8- Setting Page Title +st.title("🤖 Simplilearn Agentic AI Capstone: Research and GTM Planning") + +#9- Creating User Input Section +config.user_query = st.text_area( + "Enter the product or service you want to research and plan a GTM strategy: ", + value = config.user_query, + height=200, + placeholder = "Example: Electrical bicycle, Burger shop in Tokyo, New AI-powered project management tool targeting small businesses, ..." +) +# Create Response Button +if st.button("Generate Response", type="primary"): + if not config.user_query.strip(): + st.warning("Please enter a prompt before clicking the button") + else: + #Create logger + if logger is not None: + if logger.hasHandlers(): + logger.removeHandler(logger.handlers[0]) # Remove existing handlers + create_logger() + #Store the User Query for displaying in conversation History on Streamlit + st.session_state.chat_history.append( + { + "role": "user", + "message": config.user_query + } + ) + + logger.info("Product or service to research and plan a GTM strategy: "+config.user_query) + + # Create a Spinner until response is generated + with st.spinner("AI Agent is Thinking..."): + # Call CrewAI FLow + runAgenticWorkflow(config) + + #Export the final GTM document to PDF + logger.info("Creating PDF...") + pdf_fiename_prefix = "Agentic_AI_Generated_GTM_Document" + script_dir = Path(__file__).parent + target_dir = script_dir / "output" + target_dir.mkdir(parents=True, exist_ok=True) + now = datetime.now() + formatted_DateTime = now.strftime("%Y-%m-%d_%H%M%S") + pdfpath = f"""output/{pdf_fiename_prefix}_{formatted_DateTime}.pdf""" + export_to_pdf( + inputText=config.response, + filename=pdfpath) + + logger.info("PDF Exported Successfully at: "+pdfpath) + + + #Store the response for display in conversation history + st.session_state.chat_history.append( + { + "role": "assistant", + "message": config.response + } + ) + st.session_state.config= config + + +#10- Creating Response Section +st.subheader("Conversation History") + +for chat in st.session_state.chat_history: + if chat["role"]=="user": + with st.chat_message("user"): + st.write(chat["message"]) + else: + with st.chat_message("assistant"): + st.write(chat["message"]) + +#11- Creating the button for Users to clear all the chats from the UI +if st.sidebar.button("Clear Chat"): + st.session_state.chat_history=[] + st.rerun() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bd209e6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,24 @@ +# Useful Python Librarires +python-dotenv + +# Agentic AI Libraries +langchain +langchain-openai +langchain-ollama +langchain-classic +langchain-community +langsmith +crewai +opentelemetry-instrumentation-crewai +opentelemetry-instrumentation-openai +#fastmcp + +#web search +ddgs + +# UI +streamlit + +#PDF Generation +markdown +weasyprint diff --git a/shared_config.py b/shared_config.py new file mode 100644 index 0000000..a1dac70 --- /dev/null +++ b/shared_config.py @@ -0,0 +1,53 @@ + +from datetime import datetime +import logging +from pathlib import Path +from dataclasses import dataclass, field +from typing import List +from utilities.logger import simple_logger + + +########################################################################################### +verbose = True #Set to True to see CrewAI process information in the console +########################################################################################### + +########################################################################################### +printLogToConsole = False #Set to True to save and print log information to console, else set to False to only save log information to file +########################################################################################### + +########################################################################################### +#Define logger parameter +########################################################################################### +logger = logging.getLogger() +def create_logger(): + log_fiename_prefix = "log" + script_dir = Path(__file__).parent + target_dir = script_dir / "log" + target_dir.mkdir(parents=True, exist_ok=True) + now = datetime.now() + formatted_DateTime = now.strftime("%Y-%m-%d_%H%M%S") + log_path = f"""log/{log_fiename_prefix}_{formatted_DateTime}.log""" + global logger + logger = simple_logger(log_path, printLogToConsole) + +########################################################################################### +#Create a 'dataclass' for all configs that can be used anywhere in this Project +########################################################################################### +@dataclass +class AgenticAIConfig: + # UI Selections from users + provider:str = "OpenAI" + model: str = "gpt-4o" + temperature: float = 0.4 + max_tokens:int = 3000 + + # User Input + user_query:str="" + + # Agent Settings (CrewAI Specifics) + #agent_name:str = "default" + #tools: List[str] = field(default_factory=list) + + # Runtime + response:str="" + chat_history:List[dict] = field(default_factory=list) diff --git a/utilities/logger.py b/utilities/logger.py new file mode 100644 index 0000000..69989a4 --- /dev/null +++ b/utilities/logger.py @@ -0,0 +1,27 @@ +import logging +from xmlrpc.client import boolean + + +############################################################################################################################## +#Function to create a simple logger that logs messages to a file and optionally prints them to the console. +#If logger_name = "", then the root logger will be used. If you want to create a custom logger, provide a name for it. +############################################################################################################################## +def simple_logger(log_relative_path = str, printToConsole = boolean, logger_name = ""): + logger = logging.getLogger(logger_name) + logger.setLevel(logging.INFO) + + formatter = logging.Formatter(fmt="%(asctime)s [%(levelname)s] %(message)s", + datefmt="%Y-%m-%d %H:%M:%S") + file_handler = logging.FileHandler(log_relative_path) + file_handler.setFormatter(formatter) + file_handler.setLevel(logging.INFO) + logger.addHandler(file_handler) + + if printToConsole: + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + console_handler.setLevel(logging.INFO) + logger.addHandler(console_handler) + + return logger + diff --git a/utilities/pdf_tools.py b/utilities/pdf_tools.py new file mode 100644 index 0000000..2667275 --- /dev/null +++ b/utilities/pdf_tools.py @@ -0,0 +1,60 @@ +html_template = """ + + + + + + + + {content} + + +""" + +import markdown +from weasyprint import HTML + +def export_to_pdf(inputText, filename): + + # Step 1: Convert Markdown components to HTML chunks. + # The 'tables' and 'fenced_code' extensions keep standard LLM formatting neat. + html_content = markdown.markdown(inputText, extensions=['tables', 'fenced_code']) + + # Step 2: Inject the content into our CSS-styled HTML boilerplate template + final_html = html_template.format(content=html_content) + + # Step 3: Render directly to a high-fidelity PDF file + HTML(string=final_html).write_pdf(filename) + #print(f"Successfully generated styled PDF at: {output_pdf_path}")