# 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)