Building a Multi-Agent AI Pipeline with Google ADK to Generate Movie Pitches
I recently worked through a tutorial using Google’s Agent Development Kit (ADK), a Python framework for building multi-agent AI workflows. The tutorial used a fun premise: an AI pipeline that researches historical figures and generates full movie pitches, complete with plot outlines, box office projections, and casting suggestions.
What is Google ADK?
The ADK lets you compose multiple AI agents into pipelines using three core building blocks:
SequentialAgent— runs sub-agents one after another, passing state between themLoopAgent— repeatedly runs sub-agents until a termination condition is met (usingexit_loop)ParallelAgent— runs sub-agents concurrently and merges their outputs
Agents communicate through a shared state dictionary, which tools can read and write via a ToolContext object.
The Agent Architecture
The pipeline had six agents arranged in a hierarchy:
greeter
└── film_concept_team (SequentialAgent)
├── writers_room (LoopAgent, max 5 iterations)
│ ├── researcher ← Wikipedia lookups
│ ├── screenwriter ← writes/refines plot outline
│ └── critic ← reviews and exits loop if good enough
├── preproduction_team (ParallelAgent)
│ ├── box_office_researcher
│ └── casting_agent
└── file_writer ← saves the final pitch to disk
The writers_room loop is the most interesting part — the researcher, screenwriter, and critic iterate together until the critic is satisfied, at which point it calls exit_loop to break out.
The Code
import os
import logging
import google.cloud.logging
import sys
sys.path.append("..")
from callback_logging import log_query_to_model, log_model_response
from dotenv import load_dotenv
from google.adk import Agent
from google.adk.agents import SequentialAgent, LoopAgent, ParallelAgent
from google.adk.tools.tool_context import ToolContext
from google.adk.tools.langchain_tool import LangchainTool
from google.adk.models import Gemini
from google.genai import types
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from adk_utils.plugins import Graceful429Plugin
from google.adk.apps.app import App
from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
from google.adk.tools import exit_loop
cloud_logging_client = google.cloud.logging.Client()
cloud_logging_client.setup_logging()
load_dotenv()
model_name = os.getenv("MODEL")
RETRY_OPTIONS = types.HttpRetryOptions(initial_delay=1, attempts=6)
# Tools
def append_to_state(
tool_context: ToolContext, field: str, response: str
) -> dict[str, str]:
"""Append new output to an existing state key."""
existing_state = tool_context.state.get(field, [])
tool_context.state[field] = existing_state + [response]
logging.info(f"[Added to {field}] {response}")
return {"status": "success"}
def write_file(
tool_context: ToolContext,
directory: str,
filename: str,
content: str
) -> dict[str, str]:
target_path = os.path.join(directory, filename)
os.makedirs(os.path.dirname(target_path), exist_ok=True)
with open(target_path, "w") as f:
f.write(content)
return {"status": "success"}
# Agents
file_writer = Agent(
name="file_writer",
model=Gemini(model=model_name, retry_options=RETRY_OPTIONS),
description="Creates marketing details and saves a pitch document.",
instruction="""
INSTRUCTIONS:
- Create a marketable, contemporary movie title suggestion for the movie described in the PLOT_OUTLINE.
- Use your 'write_file' tool to create a new txt file with the following arguments:
- for a filename, use the movie title
- Write to the 'movie_pitches' directory.
- For the 'content' to write, include:
- The PLOT_OUTLINE
- The BOX_OFFICE_REPORT
- The CASTING_REPORT
PLOT_OUTLINE:
{ PLOT_OUTLINE? }
BOX_OFFICE_REPORT:
{ box_office_report? }
CASTING_REPORT:
{ casting_report? }
""",
generate_content_config=types.GenerateContentConfig(temperature=0),
tools=[write_file],
)
screenwriter = Agent(
name="screenwriter",
model=Gemini(model=model_name, retry_options=RETRY_OPTIONS),
description="Writes a logline and plot outline for a biopic about a historical character.",
instruction="""
INSTRUCTIONS:
Your goal is to write a logline and three-act plot outline for an inspiring movie
about the historical character(s) described by the PROMPT: { PROMPT? }
- If there is CRITICAL_FEEDBACK, use those thoughts to improve upon the outline.
- If there is RESEARCH provided, feel free to use details from it.
- If there is a PLOT_OUTLINE, improve upon it.
- Use the 'append_to_state' tool to write your logline and outline to the field 'PLOT_OUTLINE'.
PLOT_OUTLINE: { PLOT_OUTLINE? }
RESEARCH: { research? }
CRITICAL_FEEDBACK: { CRITICAL_FEEDBACK? }
""",
generate_content_config=types.GenerateContentConfig(temperature=0),
tools=[append_to_state],
)
researcher = Agent(
name="researcher",
model=Gemini(model=model_name, retry_options=RETRY_OPTIONS),
description="Answers research questions using Wikipedia.",
instruction="""
PROMPT: { PROMPT? }
PLOT_OUTLINE: { PLOT_OUTLINE? }
CRITICAL_FEEDBACK: { CRITICAL_FEEDBACK? }
INSTRUCTIONS:
- If there is CRITICAL_FEEDBACK, use Wikipedia to research solutions
- If there is a PLOT_OUTLINE, use Wikipedia to add more historical detail
- Otherwise, gather facts about the person in the PROMPT
- Use 'append_to_state' to add your research to the field 'research'.
""",
generate_content_config=types.GenerateContentConfig(temperature=0),
tools=[
LangchainTool(tool=WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())),
append_to_state,
],
)
critic = Agent(
name="critic",
model=Gemini(model=model_name, retry_options=RETRY_OPTIONS),
description="Reviews the outline so that it can be improved.",
instruction="""
INSTRUCTIONS:
Consider these questions about the PLOT_OUTLINE:
- Does it meet a satisfying three-act cinematic structure?
- Do the characters' struggles seem engaging?
- Does it feel grounded in a real time period in history?
- Does it sufficiently incorporate historical details from the RESEARCH?
If the PLOT_OUTLINE does a good job, exit the writing loop with your 'exit_loop' tool.
Otherwise, use 'append_to_state' to add feedback to the field 'CRITICAL_FEEDBACK'.
PLOT_OUTLINE: { PLOT_OUTLINE? }
RESEARCH: { research? }
""",
before_model_callback=log_query_to_model,
after_model_callback=log_model_response,
tools=[append_to_state, exit_loop]
)
box_office_researcher = Agent(
name="box_office_researcher",
model=Gemini(model=model_name, retry_options=RETRY_OPTIONS),
description="Considers the box office potential of this film",
instruction="""
PLOT_OUTLINE: { PLOT_OUTLINE? }
INSTRUCTIONS:
Write a report on the box office potential of a movie like that described in PLOT_OUTLINE
based on the reported box office performance of other recent films.
""",
output_key="box_office_report"
)
casting_agent = Agent(
name="casting_agent",
model=Gemini(model=model_name, retry_options=RETRY_OPTIONS),
description="Generates casting ideas for this film",
instruction="""
PLOT_OUTLINE: { PLOT_OUTLINE? }
INSTRUCTIONS:
Generate ideas for casting for the characters described in PLOT_OUTLINE
by suggesting actors who have received positive feedback from critics and/or
fans when they have played similar roles.
""",
output_key="casting_report"
)
preproduction_team = ParallelAgent(
name="preproduction_team",
sub_agents=[box_office_researcher, casting_agent]
)
writers_room = LoopAgent(
name="writers_room",
description="Iterates through research and writing to improve a movie plot outline.",
sub_agents=[researcher, screenwriter, critic],
max_iterations=5,
)
film_concept_team = SequentialAgent(
name="film_concept_team",
description="Write a film plot outline and save it as a text file.",
sub_agents=[writers_room, preproduction_team, file_writer],
)
root_agent = Agent(
name="greeter",
model=Gemini(model=model_name, retry_options=RETRY_OPTIONS),
description="Guides the user in crafting a movie plot.",
instruction="""
- Let the user know you will help them write a pitch for a hit movie.
Ask them for a historical figure to create a movie about.
- When they respond, use 'append_to_state' to store the user's response
in the 'PROMPT' state key and transfer to the 'film_concept_team' agent.
""",
generate_content_config=types.GenerateContentConfig(temperature=0),
tools=[append_to_state],
sub_agents=[film_concept_team],
)
graceful_plugin = Graceful429Plugin(
name="graceful_429_plugin",
fallback_text={
"default": "**[Simulated Response via 429 Graceful Fallback]**\n\nThe API is out of quota. Please retry."
}
)
graceful_plugin.apply_429_interceptor(root_agent)
app = App(
name="workflow_agents",
root_agent=root_agent,
plugins=[graceful_plugin]
)
The Generated Movie Pitches
I ran the pipeline on five different historical figures. Here’s what it produced:
1. David Unaipon — Australia’s Leonardo
A brilliant Aboriginal inventor battles societal prejudice and limited resources to bring his groundbreaking ideas to life, forever changing the landscape of Australian innovation and inspiring a nation.
The agent produced a three-act arc following David Unaipon — the man on Australia’s $50 note — from his early tinkering with mechanical devices in rural South Australia, through his battles with a dismissive establishment attempting to patent his inventions, to his eventual recognition as a pioneering intellect and advocate for Aboriginal rights. A compelling choice given how little-known his story is internationally.
2. Steve Irwin — Wildlife Warrior
A passionate and unconventional Australian wildlife warrior inspires millions to love and protect the natural world, leaving an indelible legacy that transcends his own untimely end.
This was the most complete pitch — the preproduction_team agents produced both a box office report and a full casting breakdown. Highlights:
Box office projection: $250M–$600M+ worldwide, with comparisons to Bohemian Rhapsody ($911M) and Elvis ($288M).
Casting suggestions:
- Steve Irwin: Chris Hemsworth or an unknown Australian actor
- Terri Irwin: Amy Adams or Jessica Chastain
- Bob Irwin: Bryan Brown or Sam Neill
- Adult Bindi: Angourie Rice or Kaitlyn Dever
3. Elizabeth Blackburn — The Nobel Truth
A brilliant Australian-American scientist defies scientific skepticism and political interference to uncover the secrets of cellular aging, earning a Nobel Prize and championing ethical science in the face of adversity.
The agent picked up on the most dramatic thread in Blackburn’s story: her dismissal from George W. Bush’s President’s Council on Bioethics after clashing with the administration over scientific integrity — a decision that prompted an open letter of protest from 170 scientists. She went on to win the Nobel Prize in Physiology or Medicine in 2009.
4. Norman Borlaug — Seeds of Change
A determined American agronomist battles skepticism, political hurdles, and the forces of nature to revolutionize agriculture, ultimately saving over a billion lives from starvation and earning the Nobel Peace Prize.
A natural cinematic story — Borlaug’s Green Revolution transformed Mexico, Pakistan, and India’s food security through high-yield, disease-resistant wheat varieties. The agent structured this well as a globe-spanning story with clear dramatic stakes: famine averted at massive scale.
5. Zhang Zhongjing — The Cold Damage
In a fractured empire ravaged by disease, a visionary physician defies ancient dogma and personal tragedy to forge a revolutionary path in medicine, becoming the revered “Sage of Medicine” and forever shaping the art of healing.
The most historically distant of the five — Zhang Zhongjing was a physician during the Eastern Han Dynasty whose Treatise on Cold Damage and Miscellaneous Diseases became a foundational text of Traditional Chinese Medicine. The agent grounded this in the political instability of the dynasty’s decline and epidemic disease, creating a compelling backdrop.
Observations
A few things stood out from working through this:
- The loop pattern works well — having the critic force multiple rounds of research and revision visibly improved the outlines compared to a single pass.
- Parallel agents are elegant — running the box office researcher and casting agent simultaneously, then merging results into the final file, felt natural and saved time.
- State management is the key design challenge — deciding what lives in state, how agents read and append to it, and avoiding conflicts requires thought upfront.
- The
Graceful429Pluginwas a useful addition for rate limit handling during development when hitting Gemini API quotas.
Overall the ADK is a clean framework for orchestrating these kinds of pipelines. The composability of Sequential/Loop/Parallel agents maps well onto how you’d naturally decompose a multi-step research and writing workflow.