Lab 17 Solution: Building a Blog Post Generator Pipeline
Goal
This file contains the complete code for the agent.py script in the Blog Post Generator Pipeline lab.
blog_pipeline/agent.py
from __future__ import annotations
from pydantic import BaseModel, Field
from google.adk.agents import Agent, SequentialAgent
# ===== Define Structured Schemas =====
class ResearchFindings(BaseModel):
topic: str = Field(description="The topic being researched")
facts: list[str] = Field(description="List of 5-7 key facts or insights")
class BlogDraft(BaseModel):
title: str = Field(description="Engaging title for the blog post")
paragraphs: list[str] = Field(description="3-4 paragraphs of the draft")
class EditorialFeedback(BaseModel):
improvements: list[str] = Field(description="List of specific improvements. Empty if none.")
is_ready: bool = Field(description="True if no revisions are needed, False otherwise")
# ===== Agent 1: Research Agent =====
# Gathers key facts about the topic
research_agent = Agent(
name="researcher",
model="gemini-2.5-flash",
description="Researches a topic and gathers key information",
instruction=(
"You are a research assistant. Your task is to gather key facts and information "
"about the topic requested by the user."
),
output_schema=ResearchFindings,
output_key="research_findings" # Saves JSON structured data to state!
)
# ===== Agent 2: Writer Agent =====
# Writes blog post draft from structured research
writer_agent = Agent(
name="writer",
model="gemini-2.5-flash",
description="Writes a blog post draft based on research findings",
instruction=(
"You are a creative blog writer. Write an engaging blog post based on "
"the structured research findings below.\n"
"\n"
"**Research Findings (JSON):**\n"
"{research_findings}\n" # Reads structured JSON from state!
"\n"
"Write a draft that:\n"
"- Has an engaging introduction\n"
"- Incorporates the key facts naturally\n"
"- Has a conclusion that wraps up the topic\n"
"- Uses a friendly, conversational tone"
),
output_schema=BlogDraft,
output_key="draft_post" # Saves structured JSON to state!
)
# ===== Agent 3: Editor Agent =====
# Reviews the structured draft and suggests improvements
editor_agent = Agent(
name="editor",
model="gemini-2.5-flash",
description="Reviews blog post draft and provides editorial feedback",
instruction=(
"You are an experienced editor. Review the blog post draft below and provide "
"constructive feedback.\n"
"\n"
"**Draft Blog Post (JSON):**\n"
"{draft_post}\n" # Reads structured JSON from state!
"\n"
"Analyze the post for clarity, flow, grammar, and engagement.\n"
"Provide your feedback as a list of improvements. If the post is excellent, "
"set 'is_ready' to true and leave the improvements list empty."
),
output_schema=EditorialFeedback,
output_key="editorial_feedback" # Saves structured JSON to state!
)
# ===== Agent 4: Formatter Agent =====
# Applies edits and formats as markdown
formatter_agent = Agent(
name="formatter",
model="gemini-2.5-flash",
description="Applies editorial feedback and formats the final blog post",
instruction=(
"You are a formatter. Create the final version of the blog post by applying "
"the editorial feedback to improve the draft.\n"
"\n"
"**Original Draft (JSON):**\n"
"{draft_post}\n" # Reads structured JSON from state!
"\n"
"**Editorial Feedback (JSON):**\n"
"{editorial_feedback}\n" # Reads structured JSON from state!
"\n"
"Create the final blog post by applying the suggested improvements and formatting "
"it as proper markdown with headings and paragraph breaks. "
"Output ONLY the final formatted blog post in markdown (no JSON)."
),
output_key="final_post" # Saves final string to state!
)
# ===== Create the Sequential Pipeline =====
blog_creation_pipeline = SequentialAgent(
name="BlogCreationPipeline",
sub_agents=[
research_agent,
writer_agent,
editor_agent,
formatter_agent
], # Executes in this EXACT order!
description="Complete blog post creation pipeline from research to publication"
)
# MUST be named root_agent for ADK
root_agent = blog_creation_pipeline
Self-Reflection Answers
-
The
SequentialAgentis deterministic. What does this mean, and why is it a desirable property for a workflow like content creation?- Answer: "Deterministic" means the outcome is predictable and repeatable. Given the same starting conditions, a
SequentialAgentwill always execute the same agents in the exact same order. This is desirable for content pipelines because it ensures quality control (e.g., every post must be edited) and reliability (e.g., the formatter will always have an approved draft to work on). It eliminates the "unpredictability" of an LLM deciding which step to take next.
- Answer: "Deterministic" means the outcome is predictable and repeatable. Given the same starting conditions, a
-
What do you think would happen if you forgot to add the
output_keyto theresearch_agent? How would thewriter_agentbehave?- Answer: If
research_agenthas nooutput_key, its output (the structured JSON) would not be saved to the shared state. When thewriter_agentruns, it would try to interpolate{research_findings}into its prompt. Since that key is missing from the state, the ADK (or the underlying Python f-string logic) would likely raise an error or insert a blank/placeholder value. This would cause the writer to hallucinate facts or fail to write a relevant post because it lacks the necessary input context.
- Answer: If
-
How could you modify this pipeline to include a human-in-the-loop? For example, what if you wanted a human to approve the
draft_postbefore theeditor_agentruns?- Answer: You could insert a custom
FunctionToolor a specialized "HumanAgent" between the writer and the editor. This component would pause the execution (if the runtime supports it) or present the draft to a UI and wait for a signal (like a button press or a specific input) before proceeding. In a simpler implementation, you could split the pipeline into twoSequentialAgents: one that generates the draft and stops, and a second one that takes the approved draft and runs the edit/format steps, with the human step happening manually in between.
- Answer: You could insert a custom