AI_Blog_Writer / app.py
cicboy's picture
update gradio UI
5ddd7f1
# import relevant libraries and modules
import warnings
from crewai import LLM, Task, Agent, Crew
import os
from dotenv import load_dotenv
from pathlib import Path
import gradio as gr
from tools.hybrid_retriever_tool import HybridRetrieverTool
# control warnings
warnings.filterwarnings("ignore")
load_dotenv()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
#Define LLMs
llm_planner = LLM(model="gpt-4o-mini", temperature=0.5)
llm_writer = LLM(model="gpt-5-mini", temperature=1.0)
llm_editor = LLM(model="gpt-4-turbo", temperature=0.3)
llm_fact=LLM(model="gpt-4o-mini", temperature=0.3)
#Define tools
hybrid_tool = HybridRetrieverTool(alpha=0.6) #including RAG in search
#Creating Agents
planner = Agent(
role="Research Lead",
goal="Plan engaging and factually accurate content on {topic}",
backstory="You are working on planning a blog article about the topic {topic}."
"Use the retriever tool to gather accurate, recent information before outlining." #RAG search
"You collect relevant information that helps the audience learn something and make informed decisions. "
"Your work is the basis for the Content Writer to write an article on this topic.",
allow_delegation=False,
verbose=True,
tools = [hybrid_tool], #Rag search
llm=llm_planner
)
writer = Agent(
role="Technical Writer",
goal="Write an insightful and factually accurate opinion piece about the topic: {topic}",
backstory="You are working on writing a new opnion piece about the topic: {topic}. "
"You base your writing on the work of the research lead, who provides an outline and relevant context about the topic. "
"You follow the main objectives and direction of the outline, as provided by the Content Planner. "
"You also provide objective and impartial insights, supported by relevant information that is provided by the Content Planner. "
"You acknowledge in your opnion piece when your statements are opinions as opposed to objective staements.",
allow_delegation=False,
verbose=True,
llm=llm_writer
)
fact_checker = Agent(
role="Data Verifier",
goal="Verify factual accuracy, detect unsupported claims and identify missing references or sources.",
backstory="You are a meticulous research analyst who checks every claim against known facts and relaible sources"
"Use the retriever tool to cross-check the Content Writer's statements against reliable, recent information.",
allow_delegation=False,
verbose=True,
tools = [hybrid_tool], #Rag search
llm=llm_fact
)
editor = Agent(
role="Editor-in-Chief",
goal="Edit a given blog post to align with the context and narrative style of the {topic} (e.g. academic, informal, satirical).",
backstory="You are an editor who receives a blog post from the Content Writer. "
"Your goal is to review the blog post to ensure that it follows journalistic best practices, "
"provides balanced viewpoints, when providing opinions or assertions, "
"and also avoids major controversial topics or opinions when possible. "
"The blog post should align with the context and narrative style of the topic being discussed.",
allow_delegation=False,
verbose=True,
llm=llm_editor
)
#Creating Tasks
plan = Task(
name="content_planner_task",
description=(
"1. Prioritize the latest trends, key players, and groundbreaking news on {topic}.\n"
"2. Identify the target audience, considering their interests and pain points.\n"
"3. Develop a detailed content outline including and introduction, key points, and a call to action.\n"
"4. Include SEO keywords and relevant data or sources"
),
expected_output="A comprehensive content plan document with an outline, audience analysis, SEO keywords, and resources.",
agent = planner
)
write = Task(
name="blog_writer_task",
description=(
"1. Use the provided content plan to craft a compelling blog post on {topic}.\n"
"2. Incorporate SEO keywords naturally.\n"
"3. Sections/Subtitles are properly named in an engaging manner.\n"
"4. Ensure the post structured with an engaging introduction, insightful body, and a summarizing conclusion.\n"
"5. The narrative style and language used should be determined by the target audience and {tone}.\n"
"6. Proofread for grammatical errors and alignment with the target audience.\n"
),
expected_output="A well-written blog post in markdown format, ready for publication. "
"Each section should have 2 or 3 paragraphs and the whole blog should be a maximum of 1000 words.",
agent=writer,
context=[plan]
)
fact_check = Task(
name="fact_checker_task",
description=(
"Carefully review the drafted blog post provided by the Content Writer. "
"Check all factual statements for accuracy and ensure sources or references are mentioned. "
"Identify any unsupported claims, logical inconsistencies, or outdated information. "
"Provide corrections or highlight areas that require further verification."
),
expected_output=(
"A revised version of the blog post in markdown format, with annotations or corrections "
"for any inaccurate or unsupported statements."
),
agent=fact_checker,
context=[write]
)
edit = Task(
name="editor_task",
description=("Edit and refine the fact-checked blog post to ensure clarity, flow and consistent {tone}.\n"
"Polish grammar, readability, and overall style."
),
expected_output="A well-written blog post in markdown format, ready for publication. "
"Each section should have 2 or 3 paragraphs and the whole blog should be a maximum of 1000 words",
agent=editor,
context=[fact_check]
)
# Creating the Crew
crew = Crew(
agents=[planner, writer, fact_checker, editor],
tasks=[plan, write, fact_check, edit],
process="sequential",
verbose=True
)
# fetch context for RAG search
def fetch_context(topic):
passages = hybrid_tool._run(topic)
if isinstance(passages, str):
summary = passages
else:
summary = hybrid_tool.summarize_passages(topic, passages)
return summary
# Define Gradio Handler
def generate_blog(topic, tone, progress=gr.Progress(track_tqdm=True)):
progress(0, desc="Starting collaborative report generation...")
yield "### 🧭 Starting Collaborative Report\n"
# Run the entire multi-agent workflow once (non-streaming)
result = crew.kickoff(inputs={"topic": topic, "tone": tone})
# Try to extract clean text output
if hasattr(result, "output"):
final_output = result.output
elif hasattr(result, "final_output"):
final_output = result.final_output
else:
final_output = str(result)
progress(1.0, desc="Completed.")
yield f"βœ… **Collaborative Report Generated Successfully!**\n\n{final_output}"
# Build Gradio Interface
css = """
#output-box {
background-color: #f8f9fa;
border-radius: 12px;
padding: 1.5rem;
font-family: 'Inter', sans-serif;
font-size: 1rem;
line-height: 1.6;
white-space: pre-wrap;
overflow-y: auto;
max-height: 70vh;
}
#context-box h1, #context-box h2, #context-box h3 {
font-size: 1rem !important;
font-weight: 600 !important;
}
#context-box {
font-family: 'Inter', sans-serif;
font-size: 1rem;
line-height: 1.6;
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 10px;
padding: 1rem;
margin-top: 0.5rem;
max-height: 70vh;
overflow-y: auto;
}
"""
with gr.Blocks(css=css) as demo:
gr.Markdown(
"""
## ✍️ AI Blog Writer Multi-Agent (Multi-Agent Crew: Sequential research β†’ writing β†’ verification β†’ editing)
Create a professional, fact-checked, and polished article using CrewAI agents.
"""
)
with gr.Row():
topic = gr.Textbox(
label="Blog Topic",
placeholder="e.g. Sustainable Investing, Vegan cuisine, AI in Healthcare",
value="Sustainable Investing"
)
tone=gr.Dropdown(
["professional", "playful", "academic", "casual", "satirical", "humorous"],
label="Select Writing Tone",
value="academic"
)
fetch_btn = gr.Button("🌐 Fetch & Summarize Context", variant="secondary") # Rag Search
context_output = gr.Markdown(label="πŸ“š Retrieved Context Summary") # Rag Search
run_button = gr.Button("πŸš€ Generate Blog", variant="primary")
final_output = gr.Textbox(label="πŸ“° Generated Blog Post", elem_id="output-box", lines=30, interactive=False, show_label=True)
fetch_btn.click(fetch_context, inputs=[topic], outputs=[context_output]) # Rag Search
run_button.click(generate_blog, inputs=[topic, tone], outputs=[final_output])
#Launch app
if __name__=="__main__":
demo.launch(server_name="0.0.0.0", server_port=7860, share=True)