First working implementation of the program

This commit is contained in:
2026-07-27 17:42:48 +09:00
parent 9f9c72e824
commit e5bb49a596
12 changed files with 618 additions and 0 deletions
+7
View File
@@ -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"
+12
View File
@@ -0,0 +1,12 @@
# Ignore all __pycache__ directories
**/__pycache__/
# Ignore compiled Python files
*.py[cod]
venv/
log/
output/
bin/__pycache__/
+3
View File
@@ -0,0 +1,3 @@
{
"python-envs.defaultEnvManager": "ms-python.python:venv"
}
Binary file not shown.
+32
View File
@@ -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=<your-langsmith-key>
set OPENAI_API_KEY=<your-api-key>
7) Running the Streamlit Application
python -m streamlit run app.py
# capstone_msagi
Course-end Project: Product Strategy Simulation
+201
View File
@@ -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
+38
View File
@@ -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
+161
View File
@@ -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()
+24
View File
@@ -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
+53
View File
@@ -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)
+27
View File
@@ -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
+60
View File
@@ -0,0 +1,60 @@
html_template = """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
/* Existing styles... */
/* Styles for Links */
a {{
color: #0000ff; /* Sets the link color to blue */
text-decoration: underline; /* Ensures the underline is visible */
}}
a:visited {{
color: #0000ff; /* Keeps the link blue even after it is clicked */
}}
/* Styles for clean Table Lines */
table {{
width: 100%;
border-collapse: collapse; /* Merges adjacent cell borders into a single line */
margin: 20px 0;
font-size: 11pt;
}}
th, td {{
border: 1px solid #bdc3c7; /* Draws the actual grid lines */
padding: 10px;
text-align: left;
}}
th {{
background-color: #f2f4f4; /* Optional: adds a neat background header color */
font-weight: bold;
color: #2c3e50;
}}
tr:nth-child(even) {{
background-color: #f9f9f9; /* Optional: adds zebra striping to rows */
}}
</style>
</head>
<body>
{content}
</body>
</html>
"""
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}")