First working implementation of the program
This commit is contained in:
@@ -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
@@ -0,0 +1,12 @@
|
||||
# Ignore all __pycache__ directories
|
||||
**/__pycache__/
|
||||
|
||||
# Ignore compiled Python files
|
||||
*.py[cod]
|
||||
|
||||
venv/
|
||||
log/
|
||||
output/
|
||||
|
||||
|
||||
bin/__pycache__/
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"python-envs.defaultEnvManager": "ms-python.python:venv"
|
||||
}
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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}")
|
||||
Reference in New Issue
Block a user