File size: 8,964 Bytes
2fe9eb8
 
 
 
 
 
 
 
baf9e10
2fe9eb8
 
 
 
 
 
 
 
 
 
 
 
 
baf9e10
 
 
2fe9eb8
 
 
aa67901
2fe9eb8
 
baf9e10
2fe9eb8
 
 
 
4dcb44f
2fe9eb8
 
 
 
aa67901
2fe9eb8
 
aa67901
2fe9eb8
 
 
 
 
 
 
 
 
aa67901
2fe9eb8
baf9e10
 
2fe9eb8
 
baf9e10
2fe9eb8
 
 
 
aa67901
2fe9eb8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
baf9e10
 
 
 
 
 
 
 
 
2fe9eb8
aa67901
 
56f4bfd
aa67901
5ddd7f1
 
6aedb51
5ddd7f1
 
 
 
 
 
 
6aedb51
5ddd7f1
 
2fe9eb8
 
f1e22c1
e30c01e
f1e22c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2fe9eb8
 
79132ac
2fe9eb8
 
 
 
 
 
 
 
 
 
 
e30c01e
2fe9eb8
 
 
baf9e10
56f4bfd
2fe9eb8
 
6aedb51
2fe9eb8
baf9e10
56f4bfd
2fe9eb8
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# 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)