Build with AI - AI Agents for Research

Instructor:

  • Dr Nate Butterworth (Google XWF)

Date:

  • May 21, 2026

Workshop Goals / Learning Objectives:

This workflow is designed as a beginner-friendly introduction to building AI “agents” using the Google Agent Development Kit (ADK) for applications to science and research.

  1. Build a team of Agents: Create a variety of Agents that work together to take on a variety of “Research Assistant” roles
  2. Grant skills to Agents with Custom Tools: Teach an agent a new skill by connecting it to built in and custom tools (functions).

For more info on everything you do today:

What is an AI Agent?

You’ve probably used an LLM like Gemini before, where you give it a prompt and it gives you a text response.

Prompt -> Thought -> Response

An AI Agent takes this further. An agent can think, take actions, and observe the results of those actions to give you a “better” answer.

Prompt -> Thought -> Action -> Observation -> Response

In this notebook, we’ll build agents that can take the actions we define!

Single agents can do a lot. But what happens when the task gets complex? A single “monolithic” agent that tries to do research, writing, editing, and fact-checking all at once becomes a problem. Its instruction prompt gets long and confusing. It’s hard to debug (which part failed?), difficult to maintain, and often produces unreliable results.

Instead of one “do-it-all” agent, we can build a multi-agent system. This is a team of simple, specialised agents that collaborate, just like a real-world team. Each agent has one clear job (e.g., one agent only does research, another only writes). This makes them easier to build, easier to test, and much more powerful and reliable when working together.

Tools/Skills

In an agent’s repetoire is the concept of “tools” and “skills”. These are essentially traditional (Python) functions that take specific input and return fixed output. There are many “pre-built” tools or you can roll your own. See:

Get started

Visit Google Colab and start a “New notebook in Drive”.

# --- 0. Setup and Installation ---
# This field is advancing rapidly. These versions work for now, but may change in future.
# - google-genai: This is the library for the Gemini AI model,
#   which acts as the "brain" of our agent.
# - google-adk: This is the Agent Development Kit! It provides the building
#   blocks (like `Agent`, `Runner`, `Tool`) to structure our application.

# already installed in colab -> # google-genai==1.68.0 google-adk==1.29.0

!pip install -q pypdf # for manipullating pdf files
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/328.3 kB ? eta -:--:--
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 328.3/328.3 kB 17.2 MB/s eta 0:00:00

Configure the remote connection to your models:

Gemini models can be used via AI Studio. Login then get Your API Key at https://aistudio.google.com/api-keys and set it below. Once set in your environment any calls from the ADK will detect your key and securely talk to the models.

import os

# Just for handling secret API keys
from google.colab import userdata

# Set the API key as an environment variable for the ADK to use
api_key = userdata.get('GOOGLE_API_KEY') # Or just replace this with the text string of your API key
# genai.configure(api_key=api_key)
os.environ['GOOGLE_API_KEY'] = api_key

print("✅ API Key configured successfully.")
✅ API Key configured successfully.

To leverage allocated credits and utilise production grade infrastructure and the full suite of Google Cloud Platform you can also connect to Gemini models using a GCP project.

Once set in your environment any calls from the ADK will detect your key and securely talk to the models.

# Set your project ID you want to use from https://console.cloud.google.com/billing/projects
project_id = "gen-lang-client-123456"

# Set the required environment variables
import os
os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "True"
os.environ["GOOGLE_CLOUD_PROJECT"] = project_id
os.environ["GOOGLE_CLOUD_LOCATION"] = "us-central1"

# Login to your Google Cloud project and set the project ID
!gcloud auth application-default login
!gcloud auth application-default set-quota-project {project_id}

You likely will have to enable various permissions in the Agent Studio or elsewhere. Then create a model instance for your Agents to connect to:

# Create a Vertex AI model instance that will use you application auth to connect to your project and consume the project's resources
from google.adk.models import Gemini

vertex_25flash = Gemini(
    model="gemini-2.5-flash", 
    vertexai=True,        
    project=project_id,
    location='global'
)

See more model options at e.g: https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/gemini/2-5-pro

Now, when creating an agent you will have to explicitly set that model, e.g:

example_agent = Agent(
    name="example_agent", 
    model=vertex_25pro, # <- Put your Vertex AI model in the agent definitions below.
    description="An example agent", #usefule for other agents
    instruction="""
    You are just an example. Say hello.
    """
)

Once set up continue importing libraries and building your Agent pipeline.

# System tools
import requests #web requests
import io #opening files

# For processing research paper pdfs
import pypdf

# Agent tools
from google.adk.agents import Agent
from google.adk.tools import google_search
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService, Session
# Additional Agent tools
from google.adk.tools import AgentTool

# LLM processing
from google import genai
from google.genai.types import Content, Part

print("✅ All libraries imported!")
✅ All libraries imported!

PART 1: Our First AGENT

Let’s create an Agent with a specific singular task using an external tool call. See the docs: https://google.github.io/adk-docs/tools/built-in-tools/#google-search

# --- 1a. Define the Agent ---
lit_review_agent = Agent(
    name="lit_review_agent", #required
    model="gemini-2.5-flash-lite", #required
    description="A research assistant that finds recent scientific papers.", #usefule for other agents
    instruction="""
    You are a helpful research assistant.
    Your job is to find scientific articles or
    papers for a user on the topic or subject they ask for.

    When asked for scientific papers on a topic, you MUST:
    1.  Use the `Google Search` tool to search arxiv.org only.
    2.  Find 3-5 relevant relevant results.
    3.  For each result, return the title and the arXiv identifier.
    4.  Present the results in a clear, numbered list.
    """,
    output_key="research_papers", # Output of the agent stored with this key
    tools=[google_search]  # We give the agent the built-in Google Search tool
)

print(f"🤖 Agent: '{lit_review_agent.name}' created!")
🤖 Agent: 'lit_review_agent' created!

Screenshot 2025-12-09 12.59.26 PM.png

To run our agent, we a few more things:

  1. SessionService: This object manages the agent’s “memory.” We’ll use InMemorySessionService, which just keeps the conversation history in your computer’s RAM. Read more: https://google.github.io/adk-docs/sessions/

  2. Runner: This object connects the Agent, the SessionService, and your input to make everything work. In “procedural” programming your code runs top-to-bottom until it finishes - here the “Runner” object is wrapped in a while-loop that is communicating between the user (you), the memory, llm endpoints, the agent, and may need to access them in non-sequential order. See more: https://google.github.io/adk-docs/runtime/#runners-role-orchestrator

  3. async/await: You will see this syntax which enables the Runner’s non-linear behavior. In procedural code, waiting for a slow task usually freezes the entire program. The await can pause the Runner as it needs to. Note: Google Colab Notebooks are already wrapped in async environments, so the structure of this style of code will differ elsewhere. Read more: https://google.github.io/adk-docs/runtime/

# --- 1b. Setup the Runner and Session ---

# This service will manage all our conversation histories.
session_service = InMemorySessionService()

# This runner will execute our agent.
# We tell it which agent to use and how to manage its memory.
agent_runner = Runner(
    agent=lit_review_agent,
    session_service=session_service,
    app_name=lit_review_agent.name
)

# We need to create a "session" for our conversation history.
# Think of this as a unique ID for this specific chat.
# We'll give it a user ID (can be any string) and a session ID.
user_id = "geo_workshop_user"
session_id = "session_001"

session = await session_service.create_session(
    app_name=lit_review_agent.name,
    user_id=user_id,
    session_id=session_id
)
# --- 1c. Run the Agent! ---

# Now, let's ask our agent a question!
user_query = "Find papers about recent earthquakes."
print(f"🗣️  Your Query: '{user_query}'")

# Call `runner.run()` with our session info and our new message.
# By default, `runner.run()` returns a generator for streaming responses.
# This means Python will yield parts of the response as they are generated.
# You can use this to monitor events as they happend.
response_generator = agent_runner.run(
    user_id=user_id,
    session_id=session_id,
    new_message=Content(parts=[Part(text=user_query)], role="user")
)

# We will just capture all the runner generator output.
all_content_objects = []
for content_obj in response_generator:
    all_content_objects.append(content_obj)

print(f"\n✅ Responses gathered!")
🗣️  Your Query: 'Find papers about recent earthquakes.'

✅ Responses gathered!
# Explore the output
for content_obj in all_content_objects:
  print(content_obj.content.parts)
[Part(
  text="""Here are some recent scientific papers about earthquakes found on arXiv:

1.  **Title:** Characterizing recent strong motion records using physics-informed neural networks.
    **arXiv ID:** arXiv:2311.08003
2.  **Title:** A review of recent developments in earthquake early warning systems.
    **arXiv ID:** arXiv:2303.00350
3.  **Title:** Deep learning for earthquake forecasting: Recent advances and future perspectives.
    **arXiv ID:** arXiv:2208.06789
4.  **Title:** Near-real-time estimation of earthquake source parameters using machine learning.
    **arXiv ID:** arXiv:2401.07682
5.  **Title:** Towards a unified model for earthquake cycle processes with machine learning.
    **arXiv ID:** arXiv:2403.01234"""
)]
# --- 1d. Run the Agent again, it remembers your conversation! ---
query_turn_2 = "That's a good start. Can you refine those results to ones that mention subduction?"
print(f"\n🗣️  User (Turn 2): '{query_turn_2}'")

response_turn_2 = agent_runner.run(
    user_id=user_id,
    session_id=session_id,  # <-- Using the *SAME* session_001
    new_message=Content(parts=[Part(text=query_turn_2)], role="user")
)

for content_obj in response_turn_2:
    all_content_objects.append(content_obj)

print(f"\n✅ Responses gathered!")

for content_obj in all_content_objects:
  print(content_obj.content.parts)

🗣️  User (Turn 2): 'That's a good start. Can you refine those results to ones that mention subduction?'

✅ Responses gathered!
[Part(
  text="""Here are some recent scientific papers about earthquakes found on arXiv:

1.  **Title:** Characterizing recent strong motion records using physics-informed neural networks.
    **arXiv ID:** arXiv:2311.08003
2.  **Title:** A review of recent developments in earthquake early warning systems.
    **arXiv ID:** arXiv:2303.00350
3.  **Title:** Deep learning for earthquake forecasting: Recent advances and future perspectives.
    **arXiv ID:** arXiv:2208.06789
4.  **Title:** Near-real-time estimation of earthquake source parameters using machine learning.
    **arXiv ID:** arXiv:2401.07682
5.  **Title:** Towards a unified model for earthquake cycle processes with machine learning.
    **arXiv ID:** arXiv:2403.01234"""
)]
[Part(
  text="""I apologize, but I am unable to refine the previous results to specifically include papers with arXiv identifiers that mention "subduction" at this time. My previous searches on `arxiv.org` did not yield direct arXiv identifiers (e.g., "arXiv:XXXX.XXXXX") within the search snippets, even when explicitly targeting the arXiv domain.

The search results from the `Google Search` tool, when restricted to `site:arxiv.org`, are not consistently providing the arXiv IDs in the snippets, which are necessary to fulfill your request."""
)]

PART 2: Agents with custom tools

The Google Search tool is great, but what if we want our agent to use our own code/tool we write ourselves.

We can do this by creating a custom tool. A custom tool is just a simple Python function!

We will create a tool to fetch real-time earthquake data from the USGS. This API happens to be free, open, and requires no API key, but the same princples would apply for ANY function.

Note: The function docstring definition is pivitol for the Agent using the tool to understand how to use it! The AI model will read this docstring to understand what this tool does, what its arguments are, and when to use it.

# --- 2a. Define the Custom Tool (a Python function) ---
def get_earthquakes(
    latitude: float = 9.0,
    longitude: float = 160.0,
    radius_km: float = 1000.0,
    start_time: str = '2025-01-01',
    end_time: str = '2025-12-31',
    min_magnitude: float | None = None
) -> dict:
    """
    Calls the USGS Earthquake API to find earthquakes in a specific place and time frame.

    Args:
        latitude (float, optional): The latitude of the center point for the search.
        longitude (float, optional): The longitude of the center point for the search.
        radius_km (float, optional): The radius in kilometers from the center point.
        start_time (str, optional): The start date/time in ISO8601 format (e.g., 'YYYY-MM-DD').
        end_time (str, optional): The end date/time in ISO8601 format (e.g., 'YYYY-MM-DD').
        min_magnitude (float, optional): Minimum magnitude for the earthquakes. Defaults to None.

    Returns:
        dict: The parsed GeoJSON response from the USGS API.
    """

    print(f"\n🛠️  TOOL CALLED: get_earthquakes({latitude}, {longitude}, {radius_km}, {start_time}, {end_time}, {min_magnitude})")

    # Base URL for the USGS FDSN Event Web Service query endpoint
    USGS_URL = "https://earthquake.usgs.gov/fdsnws/event/1/query"

    # Define the parameters for the API call
    params = {
        "format": "geojson",  # Request GeoJSON format for easy parsing
        "starttime": start_time,
        "endtime": end_time,
        "latitude": latitude,
        "longitude": longitude,
        "maxradiuskm": radius_km,
    }

    # Add optional parameters if provided
    if min_magnitude is not None:
        params["minmagnitude"] = min_magnitude

    # Make the GET request to the API
    print(f"Querying API: {USGS_URL}?{'&'.join([f'{k}={v}' for k, v in params.items()])}")

    response = requests.get(USGS_URL, params=params)

    print(f"🛠️  TOOL RETURNING:{response.json()}")
    # Return the response
    # simpler outputs are usually easier for the agen to understand
    return response.json()
get_earthquakes()

🛠️  TOOL CALLED: get_earthquakes(9.0, 160.0, 1000.0, 2025-01-01, 2025-12-31, None)
Querying API: https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2025-01-01&endtime=2025-12-31&latitude=9.0&longitude=160.0&maxradiuskm=1000.0
🛠️  TOOL RETURNING:{'type': 'FeatureCollection', 'metadata': {'generated': 1765255098000, 'url': 'https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2025-01-01&endtime=2025-12-31&latitude=9.0&longitude=160.0&maxradiuskm=1000.0', 'title': 'USGS Earthquakes', 'status': 200, 'api': '1.14.1', 'count': 1}, 'features': [{'type': 'Feature', 'properties': {'mag': 4.6, 'place': '155 km E of Kolonia, Micronesia', 'time': 1742736870010, 'updated': 1748540195040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000ppgr', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000ppgr&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 326, 'net': 'us', 'code': '7000ppgr', 'ids': ',us7000ppgr,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 28, 'dmin': 13.266, 'rms': 0.69, 'gap': 136, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.6 - 155 km E of Kolonia, Micronesia'}, 'geometry': {'type': 'Point', 'coordinates': [159.6121, 6.8701, 10]}, 'id': 'us7000ppgr'}]}
{'type': 'FeatureCollection',
 'metadata': {'generated': 1765255098000,
  'url': 'https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2025-01-01&endtime=2025-12-31&latitude=9.0&longitude=160.0&maxradiuskm=1000.0',
  'title': 'USGS Earthquakes',
  'status': 200,
  'api': '1.14.1',
  'count': 1},
 'features': [{'type': 'Feature',
   'properties': {'mag': 4.6,
    'place': '155 km E of Kolonia, Micronesia',
    'time': 1742736870010,
    'updated': 1748540195040,
    'tz': None,
    'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000ppgr',
    'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000ppgr&format=geojson',
    'felt': None,
    'cdi': None,
    'mmi': None,
    'alert': None,
    'status': 'reviewed',
    'tsunami': 0,
    'sig': 326,
    'net': 'us',
    'code': '7000ppgr',
    'ids': ',us7000ppgr,',
    'sources': ',us,',
    'types': ',origin,phase-data,',
    'nst': 28,
    'dmin': 13.266,
    'rms': 0.69,
    'gap': 136,
    'magType': 'mb',
    'type': 'earthquake',
    'title': 'M 4.6 - 155 km E of Kolonia, Micronesia'},
   'geometry': {'type': 'Point', 'coordinates': [159.6121, 6.8701, 10]},
   'id': 'us7000ppgr'}]}
# --- 2b. Define a New Agent that USES this tool ---
earthquake_agent = Agent(
    name="earthquake_agent",
    model="gemini-3.1-flash-lite",
    description="A seismology assistant that reports on earthquakes.",
    instruction="""
    You are a helpful seismology assistant.
    Your job is to report earthquakes using your USGS earthquake tool.

    When a user asks about earthquakes, you MUST:
    1.  Extract user provided details about time, location, and magnitude, if
    provided. Otherwise make assumptions based on the user's query or use the
    function defaults.
    2.  Use the `get_earthquakes` tool to retrieve USGS reported earthquakes
    with the inferred input parameters.
    3.  Report the list of earthquakes you find.
    4.  If no earthquakes are found, report that clearly.
    """,
    output_key="earthquakes",
    tools=[get_earthquakes]
)

print(f"🤖 Agent '{earthquake_agent.name}' is created and can now call the USGS!")
🤖 Agent 'earthquake_agent' is created and can now call the USGS!
# Set a new session and runner for this agent-call
session_service_2 = InMemorySessionService()

agent_runner_2 = Runner(
    agent=earthquake_agent,
    session_service=session_service_2,
    app_name=earthquake_agent.name
)

session = await session_service_2.create_session(
    app_name=earthquake_agent.name,
    user_id=user_id,
    session_id="session_002"
)
# --- 1c. Run the Agent! ---
user_query_2 = "Find large Earthquakes around Tonga in 2025"
print(f"🗣️  Your Query: '{user_query_2}'")

response_generator_2 = agent_runner_2.run(
    user_id=user_id,
    session_id="session_002",
    new_message=Content(parts=[Part(text=user_query_2)], role="user")
)

all_content_objects_2 = []

for content_obj in response_generator_2:
    all_content_objects_2.append(content_obj)

print(f"\n✅ Responses gathered!")
🗣️  Your Query: 'Find large Earthquakes around Tonga in 2025'
WARNING:google_genai.types:Warning: there are non-text parts in the response: ['function_call'], returning concatenated text result from text parts. Check the full candidates.content.parts accessor to get the full model response.

🛠️  TOOL CALLED: get_earthquakes(-21.178976, -175.198242, 500, 2025-01-01, 2025-12-31, 6)
Querying API: https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2025-01-01&endtime=2025-12-31&latitude=-21.178976&longitude=-175.198242&maxradiuskm=500&minmagnitude=6
🛠️  TOOL RETURNING:{'type': 'FeatureCollection', 'metadata': {'generated': 1765255202000, 'url': 'https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2025-01-01&endtime=2025-12-31&latitude=-21.178976&longitude=-175.198242&maxradiuskm=500&minmagnitude=6', 'title': 'USGS Earthquakes', 'status': 200, 'api': '1.14.1', 'count': 4}, 'features': [{'type': 'Feature', 'properties': {'mag': 6, 'place': '187 km SSW of ‘Ohonua, Tonga', 'time': 1748170199192, 'updated': 1755098072040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000q1e7', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000q1e7&format=geojson', 'felt': 5, 'cdi': 2.7, 'mmi': 3.522, 'alert': 'green', 'status': 'reviewed', 'tsunami': 0, 'sig': 555, 'net': 'us', 'code': '7000q1e7', 'ids': ',us7000q1e7,usauto7000q1e7,pt25145000,', 'sources': ',us,usauto,pt,', 'types': ',dyfi,ground-failure,internal-moment-tensor,internal-origin,losspager,moment-tensor,origin,phase-data,shakemap,', 'nst': 216, 'dmin': 6.661, 'rms': 0.75, 'gap': 22, 'magType': 'mww', 'type': 'earthquake', 'title': 'M 6.0 - 187 km SSW of ‘Ohonua, Tonga'}, 'geometry': {'type': 'Point', 'coordinates': [-175.8016, -22.8374, 48]}, 'id': 'us7000q1e7'}, {'type': 'Feature', 'properties': {'mag': 6.4, 'place': '142 km W of Neiafu, Tonga', 'time': 1747196140513, 'updated': 1753463174040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000pz41', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000pz41&format=geojson', 'felt': 8, 'cdi': 2.7, 'mmi': 3.556, 'alert': 'green', 'status': 'reviewed', 'tsunami': 0, 'sig': 632, 'net': 'us', 'code': '7000pz41', 'ids': ',us7000pz41,usauto7000pz41,pt25134000,at00sw8h61,', 'sources': ',us,usauto,pt,at,', 'types': ',dyfi,ground-failure,internal-moment-tensor,internal-origin,losspager,moment-tensor,origin,phase-data,shakemap,', 'nst': 262, 'dmin': 5.518, 'rms': 0.7, 'gap': 30, 'magType': 'mww', 'type': 'earthquake', 'title': 'M 6.4 - 142 km W of Neiafu, Tonga'}, 'geometry': {'type': 'Point', 'coordinates': [-175.3343, -18.7126, 260]}, 'id': 'us7000pz41'}, {'type': 'Feature', 'properties': {'mag': 6.2, 'place': '72 km SSE of Pangai, Tonga', 'time': 1743347094834, 'updated': 1749244348040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000pnvp', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000pnvp&format=geojson', 'felt': None, 'cdi': None, 'mmi': 4.4, 'alert': 'green', 'status': 'reviewed', 'tsunami': 0, 'sig': 591, 'net': 'us', 'code': '7000pnvp', 'ids': ',us7000pnvp,usauto7000pnvp,pt25089001,', 'sources': ',us,usauto,pt,', 'types': ',ground-failure,internal-moment-tensor,internal-origin,losspager,moment-tensor,origin,phase-data,shakemap,', 'nst': 73, 'dmin': 4.06, 'rms': 0.98, 'gap': 53, 'magType': 'mww', 'type': 'earthquake', 'title': 'M 6.2 - 72 km SSE of Pangai, Tonga'}, 'geometry': {'type': 'Point', 'coordinates': [-174.0115, -20.3816, 17]}, 'id': 'us7000pnvp'}, {'type': 'Feature', 'properties': {'mag': 7, 'place': '61 km SSE of Pangai, Tonga', 'time': 1743337130362, 'updated': 1749244348040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000pntq', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000pntq&format=geojson', 'felt': 27, 'cdi': 7.1, 'mmi': 6.108, 'alert': 'green', 'status': 'reviewed', 'tsunami': 1, 'sig': 773, 'net': 'us', 'code': '7000pntq', 'ids': ',us7000pntq,at00stxrjc,pt25089000,usauto7000pntq,', 'sources': ',us,at,pt,usauto,', 'types': ',dyfi,finite-fault,general-text,ground-failure,impact-link,impact-text,internal-moment-tensor,internal-origin,losspager,moment-tensor,origin,phase-data,shakemap,', 'nst': 287, 'dmin': 4.091, 'rms': 0.96, 'gap': 20, 'magType': 'mww', 'type': 'earthquake', 'title': 'M 7.0 - 61 km SSE of Pangai, Tonga'}, 'geometry': {'type': 'Point', 'coordinates': [-174.0718, -20.3036, 29]}, 'id': 'us7000pntq'}], 'bbox': [-175.8016, -22.8374, 17, -174.0115, -18.7126, 260]}

✅ Responses gathered!
print(all_content_objects_2[-1].content.parts[0].text)
I found 4 large earthquakes around Tonga in 2025:

*   **M 6.0** - 187 km SSW of ‘Ohonua, Tonga - May 22, 2025, 09:29:59 UTC
*   **M 6.4** - 142 km W of Neiafu, Tonga - May 10, 2025, 08:15:40 UTC
*   **M 6.2** - 72 km SSE of Pangai, Tonga - March 25, 2025, 18:04:54 UTC
*   **M 7.0** - 61 km SSE of Pangai, Tonga - March 25, 2025, 15:18:50 UTC

PART 3: Agents for unstructured text

A truly powerful use for LLMs is processing unstructured data like messy text that’s extremely difficult to parse with traditional code (like regular expressions).

Imagine you find a paper online. Or Imagine you want to find 1000 papers online! The details you want are buried in a complex pdf with headers, sections, images. A traditional script would need a custom “scraper” for each journal’s specific pdf layout, and it would break every time the layout changed.

An LLM agent, however, can “read” the messy data and find key parts semantically. Let’s build an agent that can do just that.

# --- 3a. Define the Unstructured Data Tool ---
#
# This tool will just grab the *raw, pdf* from a webpage.
# We won't parse it. We'll let the agent's "brain" do the hard work.

def get_text_from_pdf(url: str) -> str:
    """
    Fetches a PDF from an arxiv.org URL, extracts all its text,
    and returns the text as a single string.

    Args:
        url: The full URL of a PDF (e.g., "https://arxiv.org/pdf/2409.18397")

    Returns:
        A string containing all the extracted text from the PDF, or an
        error message if the PDF cannot be fetched or parsed.
    """
    print(f"\n🛠️  TOOL CALLED: get_text_from_arxiv_pdf(url={url})")

    try:
        # We set a 'User-Agent' header to mimic a real browser.
        response = requests.get(url)

        # Load the pdf as raw bytes
        pdf_file = io.BytesIO(response.content)

        # Read the bytes with the pdf reader
        reader = pypdf.PdfReader(pdf_file)
        page = reader.pages[0]
        text = page.extract_text()

        # We return the raw extracted text. The LLM will parse this.
        print(f"🛠️  TOOL RETURNING: Extracted {len(text)} characters from PDF.")
        return text

    except Exception as e:
      # Catch any other unexpected errors
      error_message = f"Error: An error occurred. Details: {e}"
      print(f"🛠️  TOOL ERROR: {error_message}")
      return error_message

# --- 3b. Define the Data-Extraction Agent ---
detail_extractor_agent = Agent(
    name="detail_extractor_agent",
    model="gemini-3-flash-preview",
    description="An assistant that reads research papers.",
    instruction="""
    You are an expert research assistant specializing in parsing
    text from scientific PDFs about geoscience. Your job is to extract the
    key locations mentioned in scientific papers related to Earthquakes.

    When a user gives you a URL, you MUST:
    1.  Call the `get_text_from_pdf` tool to get the pdf's raw text content.
    2.  Scan the entire text you receive from the tool.
    3.  Find the text that corresponds to locations and times of any Earthquakes.
    4.  Return the date and location of any Earthquakes found.
    5.  If you cannot find any mentioned locations, state that clearly and do not make one up.
    6.  If the tool returns an error, report that error to the user.
    """,
    output_key="earthquake_locations",
    tools=[get_text_from_pdf]
)

print(f"🤖 Agent '{detail_extractor_agent.name}' is created and can now read pdfs!")

# --- 3c. Setup and Run the Agent ---
session_service_3 = InMemorySessionService()

agent_runner_3 = Runner(
    agent=detail_extractor_agent,
    session_service=session_service_3,
    app_name=detail_extractor_agent.name
)

session_3 = await session_service_3.create_session(
    app_name=detail_extractor_agent.name,
    user_id=user_id,
    session_id="session_003"
)
🤖 Agent 'detail_extractor_agent' is created and can now read pdfs!
# --- 3d. Run the Agent! ---
#
# Let's give it a real URL for a paper.
paper_url = "https://arxiv.org/pdf/2212.06765"
user_query_3 = f"Can you please find any Earthquakes mentioned in this research paper at this URL: {paper_url}"

print(f"\n🗣️  User Query: '{user_query_3}'")

response_generator_3 = agent_runner_3.run(
    user_id=user_id,
    session_id="session_003",
    new_message=Content(parts=[Part(text=user_query_3)], role="user")
)

all_content_objects_3 = []

for content_obj in response_generator_3:
    all_content_objects_3.append(content_obj)

print(f"\n✅ Responses gathered!")

🗣️  User Query: 'Can you please find any Earthquakes mentioned in this research paper at this URL: https://arxiv.org/pdf/2212.06765'

🛠️  TOOL CALLED: get_text_from_arxiv_pdf(url=https://arxiv.org/pdf/2212.06765)
🛠️  TOOL RETURNING: Extracted 4757 characters from PDF.

✅ Responses gathered!
all_content_objects_3
[Event(model_version='gemini-2.5-flash', content=Content(
   parts=[
     Part(
       function_call=FunctionCall(
         args={
           'url': 'https://arxiv.org/pdf/2212.06765'
         },
         id='adk-65d4beaa-c0da-4c6b-89fc-036044345c40',
         name='get_text_from_pdf'
       ),
       thought_signature=b'\n\xc4\x03\x01r\xc8\xda|\xcd\xca\xb1u\xd2\x990\xb3YKv\xd1\xf7$A\xe8\x89\x82\x18\x1eVvl\x83R\xb7\xd0\xaf"]8\x10\xfd\xec\xf0\xb1e>7\xd9\xdfY\xb0\x80\xea\x1c\xce\xe96\t\xf2\xd9\x86+Mv\x94-\x83\xf6C\npy\xb7P+v\xa6G\xc6u^\xb6\x07W\xf4@/\xcaW\x99\xf07\t.^\x92\x16...'
     ),
   ],
   role='model'
 ), grounding_metadata=None, partial=None, turn_complete=None, finish_reason=<FinishReason.STOP: 'STOP'>, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=GenerateContentResponseUsageMetadata(
   candidates_token_count=36,
   prompt_token_count=378,
   prompt_tokens_details=[
     ModalityTokenCount(
       modality=<MediaModality.TEXT: 'TEXT'>,
       token_count=378
     ),
   ],
   thoughts_token_count=102,
   total_token_count=516
 ), live_session_resumption_update=None, input_transcription=None, output_transcription=None, avg_logprobs=None, logprobs_result=None, cache_metadata=None, citation_metadata=None, invocation_id='e-f7f85f38-7d88-4e5a-a669-999ec6c9269e', author='detail_extractor_agent', actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None), long_running_tool_ids=set(), branch=None, id='2e5c80a8-382d-42f9-ac94-b3c53a0fa565', timestamp=1765255365.941019),
 Event(model_version=None, content=Content(
   parts=[
     Part(
       function_response=FunctionResponse(
         id='adk-65d4beaa-c0da-4c6b-89fc-036044345c40',
         name='get_text_from_pdf',
         response={
           'result': """ 
 Earthquake Impact Analysis Based on Text Mining and Social Media 
 Analytics  
 Zhe Zheng, Hong-Zheng Shi, Yu-Cheng Zhou, Xin-Zheng Lu, and Jia-Rui Lin* 
 Department of Civil Engineering, Tsinghua University, Beijing, China, 100084 
 ABSTRACT: Earthquakes have a deep impact on wide areas, and emergency rescue operations may benefit from 
 social media information about the scope and extent of the disaster. Therefore, this work presents a text mining -
 based approach to collect and analyze social media data for early earthquake impact analysis. First, disaster -
 related microblogs are collected from the Sina microblog based on crawler technology. Then, after data cleaning 
 a series of analyses are conducted including (1) the hot words analysis, (2) the trend of the number of microblogs, 
 (3) the trend of public opinion sentiment, and (4) a keyword and rule -based text classification for earthquake 
 impact analysis. Finally, two recent earthquakes with the same magnitude and focal depth in China are analyzed 
 to compare their impacts. The results show that the public opinion trend analysis and the trend of public opinion 
 sentiment can estimate the earthquake's social impact at an early stage, which will be helpful to decision-making 
 and rescue management. 
 KEYWORDS: Text mining, Social media, Big data, Earthquake loss estimation, Public opinion analysis, Disaster 
 prevention 
 1. Introduction 
 Earthquakes are potential threats to modern cities with large and concentrated populations and complex networked 
 infrastructure systems ( Lu et al., 2020 ). Extreme earthquakes (e.g., the 2008 Wenchuan earthquake, 2010 Haiti 
 earthquake, and 2011 Tōhoku earthquake) will inflict severe damag e and lead to extensive casualties, and 
 significant losses (Cui et al., 2008; DesRoches et al., 2011; Mori et al., 2011 ). So, time-sensitive responses such 
 as emergency rescue operations should be taken to help people locate available resources, deliver assistance, and 
 so on (Yin et al., 2015). The governments’ time-sensitive responses rely on the timely acquisition and analysis of 
 disaster-related information. 
 On the one hand, with the popularity of the mobile Internet and the increasing use of social media platforms, users 
 are generating massive amounts of data and information anytime, anywhere. The contents generated by the user s 
 are a source of big data that could be utilized in decision support systems to help governments and citizens manage 
 disaster risks in terms of vulnerability assessment, early warning, monitoring, and evaluation  (Ford et al., 2016). 
 On the other hand, with the advent of natural language processing (NLP) techniques, the analysis and processing 
 of texts are becoming more and more feasible and convenient. For example, m ore NLP-based applications have 
 emerged, such as the domain -specific language model  (Zheng et al., 2022 ), semantic web ( Wu et al., 2022 ), 
 automated rule checking  (ARC) system (Zhou et al., 2022; Zheng et al., 2022 ), and topic modeling ( Lin et al., 
 2020).  
 Twitter is one of the most popular social media platforms that caught much attention of researchers  (Yao et al., 
 2021). To date, many seismic analyses have been carried out based on the big data retrieval from Twitter.  For 
 example, Sakaki et al.  (2012) proposed a real -time earthquake monitoring method that can effectively detect 
 earthquakes at an early stage merely by monitoring tweets. Van Quan et al. (2017) proposed a convolution neural 
 network (CNN) based method to classify informative tweets and the real-time event detection algorithm to detect 
 the occurrence of a certain  event. Avvenuti et al.  (2018) proposed an emergency management  system called 
 CrisMap to visualize the loss of an earthquake  based on natural language processing and geoparsing tools . Sina 
 microblog is the most popular social platform in China ( Chen et al., 2020), and the big data from Sina have also 
 been utilized in the extraction, detection, and analysis of earthquakes in recent years. Qu et al. (2011) investigated 
 how Chinese netizens used microblogging in response to the 2010 Y ushu Earthquake. Xing et al. (2019) proposed 
 an emergency information spatial distribution detection method  based on microblog information urgency grading 
 and spatial autocorrelation analysis . Yao et al. (2021) proposed an intensity extraction method for Sina texts.  
 Although many studies have been conducted for earthquake disaster analysis based on Twitter or Sina microblogs, 
 more case studies are still needed to further validate the effectiveness of different earthquake hazard analysis 
 methods.  
 To explore and estimate the social impacts and perceptions of earthquakes through  NLP-based approaches, four """
         }
       )
     ),
   ],
   role='user'
 ), grounding_metadata=None, partial=None, turn_complete=None, finish_reason=None, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=None, live_session_resumption_update=None, input_transcription=None, output_transcription=None, avg_logprobs=None, logprobs_result=None, cache_metadata=None, citation_metadata=None, invocation_id='e-f7f85f38-7d88-4e5a-a669-999ec6c9269e', author='detail_extractor_agent', actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None), long_running_tool_ids=None, branch=None, id='af388c69-0b59-4d6e-88a5-1520d557da38', timestamp=1765255367.481801),
 Event(model_version='gemini-2.5-flash', content=Content(
   parts=[
     Part(
       text="""Here are the Earthquakes mentioned in the research paper:
 
 *   **2008 Wenchuan earthquake:** Date: 2008, Location: Wenchuan, China
 *   **2010 Haiti earthquake:** Date: 2010, Location: Haiti
 *   **2011 Tōhoku earthquake:** Date: 2011, Location: Tōhoku, Japan
 *   **2010 Yushu Earthquake:** Date: 2010, Location: Yushu, China""",
       thought_signature=b"\n\x8c\x05\x01r\xc8\xda|+\xfe\xc2z\x92\x8eE\xe6m\xf5\xa6|\r$\x08\xd3\xa7\x96dt\x1a\x80\xda\x9a\x9aH\xa3Q\x7f\xcah\x89)\xec\xbd\xad\x14\x14\x8bD\xf6?d\xa9%%X\x95\xdd\xff\xe8Z\xca\xbb\xf1\xb4R\x94c\xbf'\x91\xc3\xcbt\xa8\xd3y8\x8d{rNs\xa00\xf2l\xda\xe3\x8c\r\xb1M\xef\xe3)\xba\xab...'
     ),
   ],
   role='model'
 ), grounding_metadata=None, partial=None, turn_complete=None, finish_reason=<FinishReason.STOP: 'STOP'>, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=GenerateContentResponseUsageMetadata(
   candidates_token_count=117,
   prompt_token_count=1547,
   prompt_tokens_details=[
     ModalityTokenCount(
       modality=<MediaModality.TEXT: 'TEXT'>,
       token_count=1547
     ),
   ],
   thoughts_token_count=214,
   total_token_count=1878
 ), live_session_resumption_update=None, input_transcription=None, output_transcription=None, avg_logprobs=None, logprobs_result=None, cache_metadata=None, citation_metadata=None, invocation_id='e-f7f85f38-7d88-4e5a-a669-999ec6c9269e', author='detail_extractor_agent', actions=EventActions(skip_summarization=None, state_delta={'earthquake_locations': 'Here are the Earthquakes mentioned in the research paper:\n\n*   **2008 Wenchuan earthquake:** Date: 2008, Location: Wenchuan, China\n*   **2010 Haiti earthquake:** Date: 2010, Location: Haiti\n*   **2011 Tōhoku earthquake:** Date: 2011, Location: Tōhoku, Japan\n*   **2010 Yushu Earthquake:** Date: 2010, Location: Yushu, China'}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None), long_running_tool_ids=None, branch=None, id='b82436f3-6baa-4625-93dc-6916eec66ab2', timestamp=1765255367.487757)]
all_content_objects_3[-1].content.parts[0].text
'Here are the Earthquakes mentioned in the research paper:\n\n*   **2008 Wenchuan earthquake:** Date: 2008, Location: Wenchuan, China\n*   **2010 Haiti earthquake:** Date: 2010, Location: Haiti\n*   **2011 Tōhoku earthquake:** Date: 2011, Location: Tōhoku, Japan\n*   **2010 Yushu Earthquake:** Date: 2010, Location: Yushu, China'

PART 4: A multi-Agent team

Now we have seen a few use-cases of individual agents performing specific tasks, lets see the real power of the team working together.

Screenshot 2025-12-09 12.54.21 PM.png
# We wrap our specialist agents as tools.
# This allows the 'report_agent' to call them, get their output, and use it.
tools_list = [
    AgentTool(agent=lit_review_agent),       # Can find papers
    AgentTool(agent=detail_extractor_agent), # Can read papers found by lit_review
    AgentTool(agent=earthquake_agent)   # Can get real USGS data
]

# Define an Agent that uses all of our sub-agents as tools
report_agent = Agent(
    name="earthquake_report_agent",
    model="gemini-2.5-flash",
    description="Writes comprehensive reports on earthquakes by coordinating research.",
    instruction="""
    You are a Chief Seismological Reporter. Your goal is to write a comprehensive report
    on a specific earthquake topic requested by the user.

    To write this report, you must coordinate your team of agents (available to you as tools):

    1.  **Literature Search**: First, use the `lit_review_agent` to find arxiv.org
        identifiers about recent papers on the topic.
    2.  **Paper Download**: For the most promising paper found, call the
        `detail_extractor_agent` by prefixing the arxiv identifier with
        'https://arxiv.org/pdf/' and retrieve the extracted locations of
        eathquakes in the paper.
    3.  **Real Data Verification**: Use the `earthquake_data_agent` to check
        USGS records for the locations/dates found in the paper (or general
        recent activity if no specific dates or locations are found).
    4.  **Synthesis**: Combine the findings from the literature and the real-time data
        into a single, cohesive report. Cite the papers and the USGS data.
    """,
    tools=tools_list
)

print(f"🤖 Agent: '{report_agent.name}' created!")

# Set up session and runner as before
session_service_master = InMemorySessionService()

runner_master = Runner(
    agent=report_agent,
    session_service=session_service_master,
    app_name=report_agent.name
)

# Create a session
session_master = await session_service_master.create_session(
    app_name=report_agent.name,
    user_id="geo_workshop_user",
    session_id="report_001"
)
🤖 Agent: 'earthquake_report_agent' created!
# A complex query that requires all agents to work together
user_query = "Earthquakes in Tonga"
print(f"\n🗣️  User Query: '{user_query}'")
# Run the agent
response_generator_master = runner_master.run(
    user_id=user_id,
    session_id="report_001",
    new_message=Content(parts=[Part(text=user_query)], role="user")
)

all_content_objects_master = []

for content_obj in response_generator_master:
    all_content_objects_master.append(content_obj)

print(f"\n✅ Responses gathered!")

🗣️  User Query: 'Earthquakes in Tonga'

🛠️  TOOL CALLED: get_earthquakes(-20, -174, 1000, 2023-11-20, 2023-11-27, None)
Querying API: https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2023-11-20&endtime=2023-11-27&latitude=-20&longitude=-174&maxradiuskm=1000
🛠️  TOOL RETURNING:{'type': 'FeatureCollection', 'metadata': {'generated': 1765255821000, 'url': 'https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2023-11-20&endtime=2023-11-27&latitude=-20&longitude=-174&maxradiuskm=1000', 'title': 'USGS Earthquakes', 'status': 200, 'api': '1.14.1', 'count': 27}, 'features': [{'type': 'Feature', 'properties': {'mag': 4, 'place': '264 km WNW of Houma, Tonga', 'time': 1701028276705, 'updated': 1707256435040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000lewg', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000lewg&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 246, 'net': 'us', 'code': '7000lewg', 'ids': ',us7000lewg,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 14, 'dmin': 2.463, 'rms': 0.75, 'gap': 157, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.0 - 264 km WNW of Houma, Tonga'}, 'geometry': {'type': 'Point', 'coordinates': [-177.7354, -20.4892, 456.678]}, 'id': 'us7000lewg'}, {'type': 'Feature', 'properties': {'mag': 4.1, 'place': '278 km WNW of Houma, Tonga', 'time': 1700947946106, 'updated': 1706800427040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000leu2', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000leu2&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 259, 'net': 'us', 'code': '7000leu2', 'ids': ',us7000leu2,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 24, 'dmin': 2.594, 'rms': 0.36, 'gap': 69, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.1 - 278 km WNW of Houma, Tonga'}, 'geometry': {'type': 'Point', 'coordinates': [-177.8831, -20.4984, 460.328]}, 'id': 'us7000leu2'}, {'type': 'Feature', 'properties': {'mag': 4.1, 'place': 'south of the Fiji Islands', 'time': 1700916226115, 'updated': 1706800424040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000lqpb', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us6000lqpb&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 259, 'net': 'us', 'code': '6000lqpb', 'ids': ',us6000lqpb,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 26, 'dmin': 2.99, 'rms': 0.78, 'gap': 116, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.1 - south of the Fiji Islands'}, 'geometry': {'type': 'Point', 'coordinates': [-177.7452, -23.0187, 270.919]}, 'id': 'us6000lqpb'}, {'type': 'Feature', 'properties': {'mag': 4.1, 'place': 'south of the Fiji Islands', 'time': 1700913412562, 'updated': 1706800426040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000let8', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000let8&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 259, 'net': 'us', 'code': '7000let8', 'ids': ',us7000let8,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 18, 'dmin': 4.625, 'rms': 0.5, 'gap': 150, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.1 - south of the Fiji Islands'}, 'geometry': {'type': 'Point', 'coordinates': [-179.6516, -23.2795, 541.921]}, 'id': 'us7000let8'}, {'type': 'Feature', 'properties': {'mag': 4.1, 'place': 'Fiji region', 'time': 1700904473691, 'updated': 1706800424040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000lqnq', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us6000lqnq&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 259, 'net': 'us', 'code': '6000lqnq', 'ids': ',us6000lqnq,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 28, 'dmin': 3.859, 'rms': 0.26, 'gap': 170, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.1 - Fiji region'}, 'geometry': {'type': 'Point', 'coordinates': [-179.3019, -21.739, 591.615]}, 'id': 'us6000lqnq'}, {'type': 'Feature', 'properties': {'mag': 4.1, 'place': '255 km W of Hihifo, Tonga', 'time': 1700847997284, 'updated': 1706800426040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000lela', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000lela&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 259, 'net': 'us', 'code': '7000lela', 'ids': ',us7000lela,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 39, 'dmin': 4.784, 'rms': 0.62, 'gap': 52, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.1 - 255 km W of Hihifo, Tonga'}, 'geometry': {'type': 'Point', 'coordinates': [-176.1812, -16.118, 339.283]}, 'id': 'us7000lela'}, {'type': 'Feature', 'properties': {'mag': 4.3, 'place': 'south of the Fiji Islands', 'time': 1700780966516, 'updated': 1706800425040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000ledv', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000ledv&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 284, 'net': 'us', 'code': '7000ledv', 'ids': ',us7000ledv,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 21, 'dmin': 4.923, 'rms': 0.73, 'gap': 133, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.3 - south of the Fiji Islands'}, 'geometry': {'type': 'Point', 'coordinates': [-179.8581, -23.5646, 531.18]}, 'id': 'us7000ledv'}, {'type': 'Feature', 'properties': {'mag': 4.2, 'place': '260 km W of Houma, Tonga', 'time': 1700777978807, 'updated': 1706800425040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000ledr', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000ledr&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 271, 'net': 'us', 'code': '7000ledr', 'ids': ',us7000ledr,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 28, 'dmin': 2.43, 'rms': 0.58, 'gap': 55, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.2 - 260 km W of Houma, Tonga'}, 'geometry': {'type': 'Point', 'coordinates': [-177.8046, -21.2975, 409.129]}, 'id': 'us7000ledr'}, {'type': 'Feature', 'properties': {'mag': 5, 'place': '104 km W of Hihifo, Tonga', 'time': 1700770054342, 'updated': 1706800423040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000lqcc', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us6000lqcc&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 385, 'net': 'us', 'code': '6000lqcc', 'ids': ',us6000lqcc,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 82, 'dmin': 2.809, 'rms': 0.69, 'gap': 44, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 5.0 - 104 km W of Hihifo, Tonga'}, 'geometry': {'type': 'Point', 'coordinates': [-174.7707, -15.9226, 276.941]}, 'id': 'us6000lqcc'}, {'type': 'Feature', 'properties': {'mag': 4.1, 'place': '269 km WNW of Houma, Tonga', 'time': 1700767680276, 'updated': 1706800425040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000ledp', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000ledp&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 259, 'net': 'us', 'code': '7000ledp', 'ids': ',us7000ledp,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 21, 'dmin': 2.501, 'rms': 0.38, 'gap': 104, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.1 - 269 km WNW of Houma, Tonga'}, 'geometry': {'type': 'Point', 'coordinates': [-177.615, -20.0894, 492.85]}, 'id': 'us7000ledp'}, {'type': 'Feature', 'properties': {'mag': 4.2, 'place': '217 km ENE of Levuka, Fiji', 'time': 1700765667343, 'updated': 1706800425040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000ledm', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000ledm&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 271, 'net': 'us', 'code': '7000ledm', 'ids': ',us7000ledm,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 23, 'dmin': 4.95, 'rms': 0.67, 'gap': 75, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.2 - 217 km ENE of Levuka, Fiji'}, 'geometry': {'type': 'Point', 'coordinates': [-178.7267, -17.4846, 540.929]}, 'id': 'us7000ledm'}, {'type': 'Feature', 'properties': {'mag': 4.3, 'place': 'south of the Fiji Islands', 'time': 1700743933006, 'updated': 1706800425040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000ledg', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000ledg&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 284, 'net': 'us', 'code': '7000ledg', 'ids': ',us7000ledg,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 28, 'dmin': 4.998, 'rms': 0.71, 'gap': 101, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.3 - south of the Fiji Islands'}, 'geometry': {'type': 'Point', 'coordinates': [-179.9116, -23.6306, 529.928]}, 'id': 'us7000ledg'}, {'type': 'Feature', 'properties': {'mag': 4.3, 'place': 'south of the Fiji Islands', 'time': 1700739404662, 'updated': 1706800425040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000ledd', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000ledd&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 284, 'net': 'us', 'code': '7000ledd', 'ids': ',us7000ledd,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 22, 'dmin': 5.408, 'rms': 0.71, 'gap': 127, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.3 - south of the Fiji Islands'}, 'geometry': {'type': 'Point', 'coordinates': [179.321, -23.0404, 557.262]}, 'id': 'us7000ledd'}, {'type': 'Feature', 'properties': {'mag': 4.2, 'place': 'south of the Fiji Islands', 'time': 1700734266846, 'updated': 1706800425040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000ledb', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000ledb&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 271, 'net': 'us', 'code': '7000ledb', 'ids': ',us7000ledb,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 20, 'dmin': 4.016, 'rms': 0.48, 'gap': 106, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.2 - south of the Fiji Islands'}, 'geometry': {'type': 'Point', 'coordinates': [-179.3673, -22.2419, 587.384]}, 'id': 'us7000ledb'}, {'type': 'Feature', 'properties': {'mag': 4.2, 'place': '110 km SSW of Leava, Wallis and Futuna', 'time': 1700717516719, 'updated': 1706800425040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us7000led1', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us7000led1&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 271, 'net': 'us', 'code': '7000led1', 'ids': ',us7000led1,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 27, 'dmin': 4.07, 'rms': 0.47, 'gap': 93, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.2 - 110 km SSW of Leava, Wallis and Futuna'}, 'geometry': {'type': 'Point', 'coordinates': [-178.648, -15.1725, 413.429]}, 'id': 'us7000led1'}, {'type': 'Feature', 'properties': {'mag': 4.3, 'place': '207 km E of Levuka, Fiji', 'time': 1700704534268, 'updated': 1706800422040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000lq75', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us6000lq75&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 284, 'net': 'us', 'code': '6000lq75', 'ids': ',us6000lq75,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 48, 'dmin': 4.802, 'rms': 0.69, 'gap': 62, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.3 - 207 km E of Levuka, Fiji'}, 'geometry': {'type': 'Point', 'coordinates': [-178.7626, -17.7182, 541.81]}, 'id': 'us6000lq75'}, {'type': 'Feature', 'properties': {'mag': 4.5, 'place': '56 km NW of Fangale’ounga, Tonga', 'time': 1700703560845, 'updated': 1706800422040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000lq74', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us6000lq74&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 312, 'net': 'us', 'code': '6000lq74', 'ids': ',us6000lq74,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 28, 'dmin': 1.748, 'rms': 0.74, 'gap': 107, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.5 - 56 km NW of Fangale’ounga, Tonga'}, 'geometry': {'type': 'Point', 'coordinates': [-174.7669, -19.4622, 157.74]}, 'id': 'us6000lq74'}, {'type': 'Feature', 'properties': {'mag': 4.5, 'place': '228 km E of Levuka, Fiji', 'time': 1700692755885, 'updated': 1706800422040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000lq6g', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us6000lq6g&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 312, 'net': 'us', 'code': '6000lq6g', 'ids': ',us6000lq6g,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 100, 'dmin': 3.26, 'rms': 0.64, 'gap': 36, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.5 - 228 km E of Levuka, Fiji'}, 'geometry': {'type': 'Point', 'coordinates': [-178.5314, -17.9467, 583.75]}, 'id': 'us6000lq6g'}, {'type': 'Feature', 'properties': {'mag': 4.5, 'place': 'south of the Fiji Islands', 'time': 1700637490777, 'updated': 1706800425040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000lr2n', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us6000lr2n&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 312, 'net': 'us', 'code': '6000lr2n', 'ids': ',us6000lr2n,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 17, 'dmin': 3.199, 'rms': 1.26, 'gap': 112, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.5 - south of the Fiji Islands'}, 'geometry': {'type': 'Point', 'coordinates': [-178.3702, -22.4312, 10]}, 'id': 'us6000lr2n'}, {'type': 'Feature', 'properties': {'mag': 4.2, 'place': '252 km SSE of Alo, Wallis and Futuna', 'time': 1700598915767, 'updated': 1706800422040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000lpwv', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us6000lpwv&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 271, 'net': 'us', 'code': '6000lpwv', 'ids': ',us6000lpwv,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 57, 'dmin': 5.108, 'rms': 0.88, 'gap': 83, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.2 - 252 km SSE of Alo, Wallis and Futuna'}, 'geometry': {'type': 'Point', 'coordinates': [-177.4233, -16.489, 405.57]}, 'id': 'us6000lpwv'}, {'type': 'Feature', 'properties': {'mag': 5.2, 'place': '89 km NE of Labasa, Fiji', 'time': 1700580734820, 'updated': 1706800422040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000lpu1', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us6000lpu1&format=geojson', 'felt': 1, 'cdi': 2, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 416, 'net': 'us', 'code': '6000lpu1', 'ids': ',us6000lpu1,', 'sources': ',us,', 'types': ',dyfi,earthquake-name,origin,phase-data,', 'nst': 82, 'dmin': 2.606, 'rms': 0.66, 'gap': 114, 'magType': 'mww', 'type': 'earthquake', 'title': 'M 5.2 - 89 km NE of Labasa, Fiji'}, 'geometry': {'type': 'Point', 'coordinates': [179.8499, -15.778, 12.776]}, 'id': 'us6000lpu1'}, {'type': 'Feature', 'properties': {'mag': 4.2, 'place': 'south of the Fiji Islands', 'time': 1700580684229, 'updated': 1706800424040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000lqiz', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us6000lqiz&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 271, 'net': 'us', 'code': '6000lqiz', 'ids': ',us6000lqiz,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 22, 'dmin': 5.196, 'rms': 0.97, 'gap': 112, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.2 - south of the Fiji Islands'}, 'geometry': {'type': 'Point', 'coordinates': [179.971, -23.8458, 511.257]}, 'id': 'us6000lqiz'}, {'type': 'Feature', 'properties': {'mag': 4.2, 'place': '103 km WSW of Houma, Tonga', 'time': 1700579258242, 'updated': 1706800424040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000lqiy', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us6000lqiy&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 271, 'net': 'us', 'code': '6000lqiy', 'ids': ',us6000lqiy,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 19, 'dmin': 1.013, 'rms': 0.46, 'gap': 118, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.2 - 103 km WSW of Houma, Tonga'}, 'geometry': {'type': 'Point', 'coordinates': [-176.1624, -21.6498, 101.412]}, 'id': 'us6000lqiy'}, {'type': 'Feature', 'properties': {'mag': 4.1, 'place': '274 km WNW of Houma, Tonga', 'time': 1700531194634, 'updated': 1706800424040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000lqik', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us6000lqik&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 259, 'net': 'us', 'code': '6000lqik', 'ids': ',us6000lqik,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 21, 'dmin': 2.55, 'rms': 0.43, 'gap': 71, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.1 - 274 km WNW of Houma, Tonga'}, 'geometry': {'type': 'Point', 'coordinates': [-177.7744, -20.3194, 525.731]}, 'id': 'us6000lqik'}, {'type': 'Feature', 'properties': {'mag': 4.2, 'place': 'south of the Fiji Islands', 'time': 1700470017365, 'updated': 1706800424040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000lqh8', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us6000lqh8&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 271, 'net': 'us', 'code': '6000lqh8', 'ids': ',us6000lqh8,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 32, 'dmin': 5.212, 'rms': 0.58, 'gap': 86, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.2 - south of the Fiji Islands'}, 'geometry': {'type': 'Point', 'coordinates': [-179.8411, -24.1542, 500.772]}, 'id': 'us6000lqh8'}, {'type': 'Feature', 'properties': {'mag': 4, 'place': '270 km E of Levuka, Fiji', 'time': 1700465647141, 'updated': 1706800424040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000lqh6', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us6000lqh6&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 246, 'net': 'us', 'code': '6000lqh6', 'ids': ',us6000lqh6,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 16, 'dmin': 3.63, 'rms': 0.27, 'gap': 101, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.0 - 270 km E of Levuka, Fiji'}, 'geometry': {'type': 'Point', 'coordinates': [-178.1392, -17.8732, 567.655]}, 'id': 'us6000lqh6'}, {'type': 'Feature', 'properties': {'mag': 4.2, 'place': '269 km WNW of Houma, Tonga', 'time': 1700442426278, 'updated': 1706800424040, 'tz': None, 'url': 'https://earthquake.usgs.gov/earthquakes/eventpage/us6000lqh0', 'detail': 'https://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us6000lqh0&format=geojson', 'felt': None, 'cdi': None, 'mmi': None, 'alert': None, 'status': 'reviewed', 'tsunami': 0, 'sig': 271, 'net': 'us', 'code': '6000lqh0', 'ids': ',us6000lqh0,', 'sources': ',us,', 'types': ',origin,phase-data,', 'nst': 31, 'dmin': 2.508, 'rms': 0.57, 'gap': 62, 'magType': 'mb', 'type': 'earthquake', 'title': 'M 4.2 - 269 km WNW of Houma, Tonga'}, 'geometry': {'type': 'Point', 'coordinates': [-177.7299, -20.3273, 499.494]}, 'id': 'us6000lqh0'}], 'bbox': [-179.9116, -24.1542, 10, 179.971, -15.1725, 591.615]}

✅ Responses gathered!
all_content_objects_master
[Event(model_version='gemini-2.5-flash', content=Content(
   parts=[
     Part(
       function_call=FunctionCall(
         args={
           'request': 'recent earthquake activity in Tonga'
         },
         id='adk-086a0064-efc0-4c39-985c-16e01f09945b',
         name='earthquake_agent'
       ),
       thought_signature=b'\n\xcc\x05\x01r\xc8\xda|\xdfPt\x8a\xfcE6M?\x83\xcc0\xcb\x86\x83\x15\x12\x17<\x87\x1a\xccH[\x96X\x82\x18Mq"\xb6u\xf8\xea\x90\xa2T\x0e\xef\xc0\xefR2\xaa\x9aQC\xe8\x96\xdb\xf4\xa4\xaa\xa6\xf8\x82~\xd78G\xe4\xf7\x99\xb6\x0f\xfc:\xd5\xac2\xac\xd9\ty\xf2\x123W"\x80s\xe8\xc8\xefK~\x89\xc9...'
     ),
   ],
   role='model'
 ), grounding_metadata=None, partial=None, turn_complete=None, finish_reason=<FinishReason.STOP: 'STOP'>, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=GenerateContentResponseUsageMetadata(
   candidates_token_count=20,
   prompt_token_count=2618,
   prompt_tokens_details=[
     ModalityTokenCount(
       modality=<MediaModality.TEXT: 'TEXT'>,
       token_count=2618
     ),
   ],
   thoughts_token_count=150,
   total_token_count=2788
 ), live_session_resumption_update=None, input_transcription=None, output_transcription=None, avg_logprobs=None, logprobs_result=None, cache_metadata=None, citation_metadata=None, invocation_id='e-0a7f40b3-04c8-4e07-9482-c25badea46a3', author='earthquake_report_agent', actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None), long_running_tool_ids=set(), branch=None, id='20afe540-d16d-4012-86d5-7c97819f4114', timestamp=1765255816.482346),
 Event(model_version=None, content=Content(
   parts=[
     Part(
       function_response=FunctionResponse(
         id='adk-086a0064-efc0-4c39-985c-16e01f09945b',
         name='earthquake_agent',
         response={
           'result': """Here is a summary of recent earthquake activity in the Tonga region from November 20 to November 27, 2023:
 
 *   **M 4.0 - 264 km WNW of Houma, Tonga** on November 27, 2023, 01:11 AM UTC
 *   **M 4.1 - 278 km WNW of Houma, Tonga** on November 26, 2023, 02:52 AM UTC
 *   **M 4.1 - south of the Fiji Islands** on November 25, 2023, 06:03 PM UTC
 *   **M 4.1 - south of the Fiji Islands** on November 25, 2023, 05:16 PM UTC
 *   **M 4.1 - Fiji region** on November 25, 2023, 02:47 PM UTC
 *   **M 4.1 - 255 km W of Hihifo, Tonga** on November 24, 2023, 10:06 PM UTC
 *   **M 4.3 - south of the Fiji Islands** on November 24, 2023, 01:49 PM UTC
 *   **M 4.2 - 260 km W of Houma, Tonga** on November 24, 2023, 01:40 PM UTC
 *   **M 5.0 - 104 km W of Hihifo, Tonga** on November 24, 2023, 11:07 AM UTC
 *   **M 4.1 - 269 km WNW of Houma, Tonga** on November 24, 2023, 10:08 AM UTC
 *   **M 4.2 - 217 km ENE of Levuka, Fiji** on November 24, 2023, 09:34 AM UTC
 *   **M 4.3 - south of the Fiji Islands** on November 24, 2023, 03:32 AM UTC
 *   **M 4.3 - south of the Fiji Islands** on November 24, 2023, 02:16 AM UTC
 *   **M 4.2 - south of the Fiji Islands** on November 24, 2023, 12:51 AM UTC
 *   **M 4.2 - 110 km SSW of Leava, Wallis and Futuna** on November 23, 2023, 08:11 PM UTC
 *   **M 4.3 - 207 km E of Levuka, Fiji** on November 23, 2023, 04:35 PM UTC
 *   **M 4.5 - 56 km NW of Fangale’ounga, Tonga** on November 23, 2023, 04:19 PM UTC
 *   **M 4.5 - 228 km E of Levuka, Fiji** on November 23, 2023, 01:19 PM UTC
 *   **M 4.5 - south of the Fiji Islands** on November 22, 2023, 09:58 PM UTC
 *   **M 4.2 - 252 km SSE of Alo, Wallis and Futuna** on November 22, 2023, 01:55 PM UTC
 *   **M 5.2 - 89 km NE of Labasa, Fiji** on November 22, 2023, 08:52 AM UTC
 *   **M 4.2 - south of the Fiji Islands** on November 22, 2023, 08:51 AM UTC
 *   **M 4.2 - 103 km WSW of Houma, Tonga** on November 22, 2023, 08:27 AM UTC
 *   **M 4.1 - 274 km WNW of Houma, Tonga** on November 21, 2023, 09:46 PM UTC
 *   **M 4.2 - south of the Fiji Islands** on November 21, 2023, 01:26 PM UTC
 *   **M 4.0 - 270 km E of Levuka, Fiji** on November 21, 2023, 12:14 PM UTC
 *   **M 4.2 - 269 km WNW of Houma, Tonga** on November 21, 2023, 06:27 AM UTC"""
         }
       )
     ),
   ],
   role='user'
 ), grounding_metadata=None, partial=None, turn_complete=None, finish_reason=None, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=None, live_session_resumption_update=None, input_transcription=None, output_transcription=None, avg_logprobs=None, logprobs_result=None, cache_metadata=None, citation_metadata=None, invocation_id='e-0a7f40b3-04c8-4e07-9482-c25badea46a3', author='earthquake_report_agent', actions=EventActions(skip_summarization=None, state_delta={'earthquakes': 'Here is a summary of recent earthquake activity in the Tonga region from November 20 to November 27, 2023:\n\n*   **M 4.0 - 264 km WNW of Houma, Tonga** on November 27, 2023, 01:11 AM UTC\n*   **M 4.1 - 278 km WNW of Houma, Tonga** on November 26, 2023, 02:52 AM UTC\n*   **M 4.1 - south of the Fiji Islands** on November 25, 2023, 06:03 PM UTC\n*   **M 4.1 - south of the Fiji Islands** on November 25, 2023, 05:16 PM UTC\n*   **M 4.1 - Fiji region** on November 25, 2023, 02:47 PM UTC\n*   **M 4.1 - 255 km W of Hihifo, Tonga** on November 24, 2023, 10:06 PM UTC\n*   **M 4.3 - south of the Fiji Islands** on November 24, 2023, 01:49 PM UTC\n*   **M 4.2 - 260 km W of Houma, Tonga** on November 24, 2023, 01:40 PM UTC\n*   **M 5.0 - 104 km W of Hihifo, Tonga** on November 24, 2023, 11:07 AM UTC\n*   **M 4.1 - 269 km WNW of Houma, Tonga** on November 24, 2023, 10:08 AM UTC\n*   **M 4.2 - 217 km ENE of Levuka, Fiji** on November 24, 2023, 09:34 AM UTC\n*   **M 4.3 - south of the Fiji Islands** on November 24, 2023, 03:32 AM UTC\n*   **M 4.3 - south of the Fiji Islands** on November 24, 2023, 02:16 AM UTC\n*   **M 4.2 - south of the Fiji Islands** on November 24, 2023, 12:51 AM UTC\n*   **M 4.2 - 110 km SSW of Leava, Wallis and Futuna** on November 23, 2023, 08:11 PM UTC\n*   **M 4.3 - 207 km E of Levuka, Fiji** on November 23, 2023, 04:35 PM UTC\n*   **M 4.5 - 56 km NW of Fangale’ounga, Tonga** on November 23, 2023, 04:19 PM UTC\n*   **M 4.5 - 228 km E of Levuka, Fiji** on November 23, 2023, 01:19 PM UTC\n*   **M 4.5 - south of the Fiji Islands** on November 22, 2023, 09:58 PM UTC\n*   **M 4.2 - 252 km SSE of Alo, Wallis and Futuna** on November 22, 2023, 01:55 PM UTC\n*   **M 5.2 - 89 km NE of Labasa, Fiji** on November 22, 2023, 08:52 AM UTC\n*   **M 4.2 - south of the Fiji Islands** on November 22, 2023, 08:51 AM UTC\n*   **M 4.2 - 103 km WSW of Houma, Tonga** on November 22, 2023, 08:27 AM UTC\n*   **M 4.1 - 274 km WNW of Houma, Tonga** on November 21, 2023, 09:46 PM UTC\n*   **M 4.2 - south of the Fiji Islands** on November 21, 2023, 01:26 PM UTC\n*   **M 4.0 - 270 km E of Levuka, Fiji** on November 21, 2023, 12:14 PM UTC\n*   **M 4.2 - 269 km WNW of Houma, Tonga** on November 21, 2023, 06:27 AM UTC'}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None), long_running_tool_ids=None, branch=None, id='86eea887-cbc8-4b03-8e45-b5c7e8eb6bd8', timestamp=1765255827.836897),
 Event(model_version='gemini-2.5-flash', content=Content(
   parts=[
     Part(
       text="""**Earthquakes in Tonga: A Comprehensive Report**
 
 This report provides a summary of recent seismic activity in Tonga, drawing on available scientific literature and real-time earthquake data.
 
 **1. Literature Search**
 
 A search for recent scientific papers on earthquakes in Tonga yielded one relevant paper with an explicit arXiv identifier:
 
 *   **"Disturbances in the Doppler frequency shift of ionospheric signal and in telluric current caused by the atmospheric waves from an explosive eruption of Hunga Tonga volcano on January 15, 2022"** (arXiv ID: 2303.04362).
 
 This paper, while not directly focused on seismic earthquakes, investigates the atmospheric and ionospheric disturbances caused by the explosive eruption of the Hunga Tonga volcano, an event with significant seismic implications and impact on the region.
 
 **2. Paper Download and Detail Extraction**
 
 The paper with arXiv ID 2303.04362 was accessed and reviewed. However, the document did not contain specific earthquake locations or times. Its primary focus was on the atmospheric and ionospheric effects of the Hunga Tonga volcanic eruption rather than tectonic earthquake occurrences.
 
 **3. Real Data Verification**
 
 To provide a comprehensive understanding of earthquake activity in the Tonga region, a review of recent seismic data from the United States Geological Survey (USGS) was conducted for the period between November 20 and November 27, 2023. The data reveals significant and frequent seismic activity in and around Tonga and the Fiji Islands, indicative of the highly active tectonic environment of the Pacific Ring of Fire.
 
 Notable earthquake events include:
 
 *   **M 5.0 - 104 km W of Hihifo, Tonga** on November 24, 2023, 11:07 AM UTC
 *   **M 4.5 - 56 km NW of Fangale’ounga, Tonga** on November 23, 2023, 04:19 PM UTC
 *   **M 5.2 - 89 km NE of Labasa, Fiji** on November 22, 2023, 08:52 AM UTC
 
 Numerous other earthquakes ranging in magnitudes from 4.0 to 4.3 were recorded within the Tonga region, as well as south of the Fiji Islands and near Wallis and Futuna. These events consistently occurred at varying depths and demonstrate continuous tectonic movement in the area.
 
 **4. Synthesis**
 
 The Tonga region is situated within one of the most seismically active zones globally, the Pacific Ring of Fire, characterized by the subduction of the Pacific Plate beneath the Australian Plate. While the focused literature search yielded a paper primarily concerning the atmospheric impacts of a volcanic eruption rather than specific earthquake locations, the real-time data from the USGS unequivocally illustrates a high level of ongoing seismic activity.
 
 The frequent occurrence of moderate magnitude earthquakes (M 4.0-5.2) in the past week, particularly around Tonga and the Fiji Islands, underscores the dynamic geological processes at play. These earthquakes are a direct consequence of plate tectonic interactions, including subduction, faulting, and volcanic activity, which collectively contribute to the seismic hazard of the region.
 
 Although the detailed literature on specific earthquake locations from arXiv was limited for this report, the robust real-time seismic data from USGS provides critical insights into the pervasive and ongoing seismic hazards in Tonga. Continuous monitoring and further research into the specific mechanisms and recurrence intervals of these earthquakes are essential for understanding and mitigating seismic risks in this highly active oceanic region.
 
 **Citations:**
 
 *   **Liu, M. and Jin, Y.** (2023). Disturbances in the Doppler frequency shift of ionospheric signal and in telluric current caused by the atmospheric waves from an explosive eruption of Hunga Tonga volcano on January 15, 2022. *arXiv preprint arXiv:2303.04362*.
 *   United States Geological Survey (USGS) - Earthquake Hazards Program (Data retrieved November 27, 2023)."""
     ),
   ],
   role='model'
 ), grounding_metadata=None, partial=None, turn_complete=None, finish_reason=<FinishReason.STOP: 'STOP'>, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=GenerateContentResponseUsageMetadata(
   candidates_token_count=857,
   prompt_token_count=3797,
   prompt_tokens_details=[
     ModalityTokenCount(
       modality=<MediaModality.TEXT: 'TEXT'>,
       token_count=3797
     ),
   ],
   total_token_count=4654
 ), live_session_resumption_update=None, input_transcription=None, output_transcription=None, avg_logprobs=None, logprobs_result=None, cache_metadata=None, citation_metadata=None, invocation_id='e-0a7f40b3-04c8-4e07-9482-c25badea46a3', author='earthquake_report_agent', actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None), long_running_tool_ids=None, branch=None, id='b56d3986-c015-4acc-9d36-74a288a306d7', timestamp=1765255827.838917)]

What next?

Plenty we did not cover today:

  1. Model Context Protocol (MCP) servers to standardise models interacting with servers.
  2. Evaluation and Observability for Agent provenance.
  3. AntiGravity: an Agent/LLM-native IDE built on top of VSCode.
  4. Ethics and responsible AI!
  5. Alternatives to Google’s ADK, e.g. https://docs.langchain.com/oss/python/deepagents/overview

Bonus: Literature reviewer Agent flow

Let’s combine a team of Agents that search the web for scientific articles, parse the content from the articles to text, then summarise and draft a paper introduction. They then pass on their draft to be critically reviewed back and forth until the final intro is accepted! We can visualise the Agent setup and introduce the concept of SequentialAgents and LoopAgents.

AgentWorkflow_small.png
# System tools
import os
import requests
import io
import asyncio # Required for async execution

# For processing research paper pdfs
import pypdf

# Agent tools
from google.adk.agents import Agent, LoopAgent, SequentialAgent
from google.adk.tools import google_search
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools import ToolContext # Import ToolContext for loop control

# LLM processing
from google.genai.types import Content, Part
from google.colab import userdata

os.environ['GOOGLE_API_KEY'] = userdata.get('GOOGLE_API_KEY')
# Loop Exit Tool
def exit_loop(tool_context: ToolContext):
    """
    Call this function ONLY when the critique is 'APPROVED'.
    It signals the workflow to stop refining the paper.
    """
    print("✨ CRITIC APPROVED. EXITING LOOP.")
    # This is the specific ADK command to break out of a LoopAgent
    tool_context.actions.escalate = True
    return "Loop exited."

# PDF getter
def get_multiple_text_from_pdf(arxiv_ids: list[str]) -> str:
    """
    Fetches PDFs from arxiv.org IDs, extracts all text,
    and returns the text as a single string.

    Args:
        arxiv_ids: A list of arxiv IDs (e.g. ['2101.04336', '2402.07185'])

    Returns:
        A string containing all the extracted text from the PDF, or an
        error message if the PDF cannot be fetched or parsed.
    """
    print(f"\n🛠️  TOOL CALLED: get_multiple_text_from_pdf -> {arxiv_ids}")
    all_text = ""
    for arxiv_id in arxiv_ids:
      print(arxiv_id)
      try:
          url = "https://arxiv.org/pdf/" + arxiv_id
          response = requests.get(url)

          # Load the pdf as raw bytes
          pdf_file = io.BytesIO(response.content)

          # Read the bytes with the pdf reader
          reader = pypdf.PdfReader(pdf_file)
          page = reader.pages[0]
          text = page.extract_text()

          full_text = "\n".join(text)

      except Exception as e:
        # Catch any other unexpected errors
        error_message = f"Error: An error occurred. Details: {e}"
        print(f"🛠️  TOOL ERROR: {error_message}")

      # We return the raw extracted text. The LLM will parse this.
      print(f"🛠️  TOOL RETURNING: Extracted {len(full_text)} characters from PDFs.")
      return full_text
# 1. Finder: Finds the URL and saves it to state['research_findings']
finder_agent = Agent(
    name="finder_agent",
    model="gemini-2.5-flash",
    description="Finds scientific papers.",
    # We ask it to find *one* good URL to simplify the reader's job for this demo
    instruction="""
    You are a helpful research assistant. Your job is to find scientific articles or
    papers for a user on the topic or subject they ask for. When asked for scientific papers on a topic, you MUST:
    1. Use the `Google Search` tool to search arxiv.org only.
    2. Identify up to five (5) most relevant documents.
    3. For each result, return the arXiv identifier in a formatted list e.g. ['2101.04336', '2402.07185']
    4. If you cannot find the identifier, skip it.
    5. Present the results (up to 10 strings of arxiv identifiers) in a Python list.
    """,
    output_key="research_findings", # Saves output for the next agent
    tools=[google_search]
)


# 2. Reader: Reads the findings, extracts text, saves to state['paper_content']
reader_agent = Agent(
    name="reader_agent",
    model="gemini-3-flash",
    description="Reads the PDF.",
    # We inject the previous agent's output using {research_findings}
    instruction="""
    You are a PDF parser.
    1. Call get_multiple_text_from_pdf with the list of arXiv.org identifiers {research_findings} as the function input argument.
    2. Return the text of the PDFs returned from get_multiple_text_from_pdf.
    """,
    output_key="paper_content", # Saves the full text to state
    tools=[get_multiple_text_from_pdf]
)

# 3. Writer: Creates the first draft from the content
writer_agent = Agent(
    name="writer_agent",
    model="gemini-2.5-flash",
    instruction="""
    You are a scientific writer.
    Using the source text provided below and your knowledge on the subject
    matter, write a comprehensive Introduction section for a paper (approx 300
    words).

    Source Text: {paper_content}
    """,
    output_key="current_draft" # Initial draft saved here
)

# 4. Supervisor (Critic): Checks the draft
supervisor_agent = Agent(
    name="supervisor_agent",
    model="gemini-2.5-flash",
    instruction="""
    Review the following Introduction draft:
    {current_draft}

    Evaluate it for flow, clarity, and academic tone.
    - If it is excellent, respond with EXACTLY: "APPROVED"
    - Otherwise, provide a numbered list of specific critiques.
    """,
    output_key="critique",
)

# 5. Refiner (Associate): Fixes draft OR exits
associate_supervisor_agent = Agent(
    name="associate_supervisor_agent",
    model="gemini-2.5-flash",
    instruction="""
    Critique Status: {critique}
    Current Draft: {current_draft}

    1. IF the Critique is EXACTLY "APPROVED":
       - Call the `exit_loop` tool immediately. Do not write anything else.

    2. OTHERWISE:
       - Rewrite the draft to address the specific critiques.
       - Output the new draft.
    """,
    output_key="current_draft", # Overwrites the draft with the better version
    tools=[exit_loop],
)

# --- WORKFLOW CONSTRUCTION ---

# The Loop: Critique <-> Refine
refinement_loop = LoopAgent(
    name="refinement_loop",
    sub_agents=[supervisor_agent, associate_supervisor_agent],
    max_iterations=2
)

# The Main Pipeline: Find -> Read -> Write -> Refine
root_agent = SequentialAgent(
    name="PaperPipeline",
    sub_agents=[
        finder_agent,
        reader_agent,
        writer_agent,
        refinement_loop
    ],
)

print(f"🤖 Agents built!")
🤖 Agents built!
session_service = InMemorySessionService()

agent_runner = Runner(
    agent=root_agent,
    session_service=session_service,
    app_name=root_agent.name
)

user_id = "worker"
session_id = "session1"

session = await session_service.create_session(
    app_name=root_agent.name,
    user_id=user_id,
    session_id=session_id
)
# A complex query that requires all agents to work together
user_query = "Write an introduction section to a research paper titled: 'Computational design of metallohydrolases'"

print(f"\n🗣️  User Query: '{user_query}'")

response_generator = agent_runner.run(
    user_id=user_id,
    session_id=session_id,
    new_message=Content(parts=[Part(text=user_query)], role="user")
)

all_content_objects = []

for content_obj in response_generator:
    all_content_objects.append(content_obj)

print(f"\n✅ Responses gathered!")

🗣️  User Query: 'Write an introduction section to a research paper titled: 'Computational design of metallohydrolases''
✨ CRITIC APPROVED. EXITING LOOP.

✅ Responses gathered!
all_content_objects
[Event(model_version='gemini-2.5-flash', content=Content(
   parts=[
     Part(
       text="""Enzymes are remarkable biological catalysts essential for nearly all life processes, accelerating biochemical reactions with high specificity and efficiency. Among the diverse classes of enzymes, hydrolases play a pivotal role by catalyzing the cleavage of chemical bonds through the addition of water. This broad class encompasses a vast array of enzymes involved in crucial biological functions, from metabolism and signal transduction to detoxification and immune response. Their versatility also makes them highly attractive for industrial applications, including the production of biofuels, pharmaceuticals, and detergents, as well as in waste treatment and bioremediation.
 
 A significant subset of hydrolases, known as metallohydrolases, relies on one or more metal ions for their catalytic activity. These metal cofactors often participate directly in substrate binding, activate water molecules for nucleophilic attack, or stabilize transition states, thereby facilitating reactions that would otherwise be exceedingly slow. Metallohydrolases are exceptionally diverse in structure and function, catalyzing the hydrolysis of various substrates such as esters, amides, phosphates, and glycosidic bonds. Examples include carbonic anhydrase, which rapidly interconverts CO2 and bicarbonate, and various proteases, nucleases, and phosphatases, all critical for cellular homeostasis. Despite their profound importance, understanding the intricate mechanisms governing metal-dependent catalysis and engineering these enzymes for novel functions remains a formidable challenge.
 
 Traditional approaches to enzyme engineering, relying largely on directed evolution or rational mutagenesis, can be laborious and often yield limited insights into the underlying design principles. The complexity of metal coordination environments, the dynamic interplay between the protein scaffold and the metal ion, and the precise geometric requirements for catalysis make *de novo* design or significant redesign of metallohydrolases particularly difficult. However, advances in computational methodologies have opened new avenues for tackling these challenges. Computational enzyme design leverages powerful algorithms and high-performance computing to predict protein structures, model catalytic mechanisms, and identify optimal amino acid sequences for desired functions.
 
 This paper explores the burgeoning field of computational design applied specifically to metallohydrolases. By integrating principles of protein folding, molecular dynamics, quantum mechanics, and bioinformatics, computational approaches offer unprecedented opportunities to elucidate the fundamental principles governing metal-catalyzed hydrolysis and to engineer novel metallohydrolases with tailored specificities and enhanced activities. We delve into the methodologies and strategies employed in the computational design of these intricate enzymes, discuss recent successes and persistent challenges, and highlight the promising future directions for creating synthetic biocatalysts with broad applications in medicine, biotechnology, and environmental science.
 
 """
     ),
     Part(
       text="""```json
 {"citations": [ {"snippet": "Enzymes are remarkable biological catalysts essential for nearly all life processes, accelerating biochemical reactions with high specificity and efficiency.", "title": "Enzymes as Biological Catalysts: An Overview", "url": "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3672521/", "index": 0}, {"snippet": "Among the diverse classes of enzymes, hydrolases play a pivotal role by catalyzing the cleavage of chemical bonds through the addition of water. This broad class encompasses a vast array of enzymes involved in crucial biological functions, from metabolism and signal transduction to detoxification and immune response.", "title": "Hydrolases: Classification, Mechanism, and Applications", "url": "https://www.sciencedirect.com/topics/agricultural-and-biological-sciences/hydrolases", "index": 1}, {"snippet": "Their versatility also makes them highly attractive for industrial applications, including the production of biofuels, pharmaceuticals, and detergents, as well as in waste treatment and bioremediation.", "title": "Applications of Hydrolases in Biotechnology", "url": "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7147721/", "index": 2}, {"snippet": "Their versatility also makes them highly attractive for industrial applications, including the production of biofuels, pharmaceuticals, and detergents, as well as in waste treatment and bioremediation.", "title": "Hydrolases: Types, Functions, and Biotechnological Applications", "url": "https://www.frontiersin.org/articles/10.3389/fbioe.2021.644123/full", "index": 3}, {"snippet": "A significant subset of hydrolases, known as metallohydrolases, relies on one or more metal ions for their catalytic activity. These metal cofactors often participate directly in substrate binding, activate water molecules for nucleophilic attack, or stabilize transition states, thereby facilitating reactions that would otherwise be exceedingly slow.", "title": "Metallohydrolases: Structure, Function, and Mechanism", "url": "https://www.annualreviews.org/doi/abs/10.1146/annurev-biochem-071715-050410", "index": 4}, {"snippet": "Examples include carbonic anhydrase, which rapidly interconverts CO2 and bicarbonate, and various proteases, nucleases, and phosphatases, all critical for cellular homeostasis.", "title": "Metallohydrolases: A Diverse Family of Enzymes", "url": "https://www.sciencedirect.com/science/article/pii/B9780123744203002620", "index": 5}, {"snippet": "The complexity of metal coordination environments, the dynamic interplay between the protein scaffold and the metal ion, and the precise geometric requirements for catalysis make de novo design or significant redesign of metallohydrolases particularly difficult.", "title": "Challenges in Engineering Metallohydrolases", "url": "https://www.pnas.org/doi/10.1073/pnas.1906233116", "index": 6}, {"snippet": "However, advances in computational methodologies have opened new avenues for tackling these challenges. Computational enzyme design leverages powerful algorithms and high-performance computing to predict protein structures, model catalytic mechanisms, and identify optimal amino acid sequences for desired functions.", "title": "Computational Enzyme Design: Principles and Applications", "url": "https://www.nature.com/articles/nchembio.2045", "index": 7}]}
 ```"""
     ),
   ],
   role='model'
 ), grounding_metadata=GroundingMetadata(
   grounding_supports=[
     GroundingSupport(
       grounding_chunk_indices=[
         0,
       ],
       segment=Segment(
         end_index=156,
         text='Enzymes are remarkable biological catalysts essential for nearly all life processes, accelerating biochemical reactions with high specificity and efficiency'
       )
     ),
     GroundingSupport(
       grounding_chunk_indices=[
         1,
       ],
       segment=Segment(
         end_index=475,
         start_index=303,
         text='This broad class encompasses a vast array of enzymes involved in crucial biological functions, from metabolism and signal transduction to detoxification and immune response'
       )
     ),
     GroundingSupport(
       grounding_chunk_indices=[
         2,
         3,
       ],
       segment=Segment(
         end_index=677,
         start_index=477,
         text='Their versatility also makes them highly attractive for industrial applications, including the production of biofuels, pharmaceuticals, and detergents, as well as in waste treatment and bioremediation'
       )
     ),
     GroundingSupport(
       grounding_chunk_indices=[
         4,
       ],
       segment=Segment(
         end_index=1031,
         start_index=807,
         text='These metal cofactors often participate directly in substrate binding, activate water molecules for nucleophilic attack, or stabilize transition states, thereby facilitating reactions that would otherwise be exceedingly slow'
       )
     ),
     GroundingSupport(
       grounding_chunk_indices=[
         5,
       ],
       segment=Segment(
         end_index=1385,
         start_index=1210,
         text='Examples include carbonic anhydrase, which rapidly interconverts CO2 and bicarbonate, and various proteases, nucleases, and phosphatases, all critical for cellular homeostasis'
       )
     ),
     <... 2 more items ...>,
   ]
 ), partial=None, turn_complete=None, finish_reason=<FinishReason.STOP: 'STOP'>, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=GenerateContentResponseUsageMetadata(
   candidates_token_count=1295,
   prompt_token_count=153,
   prompt_tokens_details=[
     ModalityTokenCount(
       modality=<MediaModality.TEXT: 'TEXT'>,
       token_count=153
     ),
   ],
   thoughts_token_count=278,
   total_token_count=1726
 ), live_session_resumption_update=None, input_transcription=None, output_transcription=None, avg_logprobs=None, logprobs_result=None, cache_metadata=None, citation_metadata=None, invocation_id='e-723f9d22-c1b7-465e-a0d5-09a1d21a436f', author='finder_agent', actions=EventActions(skip_summarization=None, state_delta={'research_findings': 'Enzymes are remarkable biological catalysts essential for nearly all life processes, accelerating biochemical reactions with high specificity and efficiency. Among the diverse classes of enzymes, hydrolases play a pivotal role by catalyzing the cleavage of chemical bonds through the addition of water. This broad class encompasses a vast array of enzymes involved in crucial biological functions, from metabolism and signal transduction to detoxification and immune response. Their versatility also makes them highly attractive for industrial applications, including the production of biofuels, pharmaceuticals, and detergents, as well as in waste treatment and bioremediation.\n\nA significant subset of hydrolases, known as metallohydrolases, relies on one or more metal ions for their catalytic activity. These metal cofactors often participate directly in substrate binding, activate water molecules for nucleophilic attack, or stabilize transition states, thereby facilitating reactions that would otherwise be exceedingly slow. Metallohydrolases are exceptionally diverse in structure and function, catalyzing the hydrolysis of various substrates such as esters, amides, phosphates, and glycosidic bonds. Examples include carbonic anhydrase, which rapidly interconverts CO2 and bicarbonate, and various proteases, nucleases, and phosphatases, all critical for cellular homeostasis. Despite their profound importance, understanding the intricate mechanisms governing metal-dependent catalysis and engineering these enzymes for novel functions remains a formidable challenge.\n\nTraditional approaches to enzyme engineering, relying largely on directed evolution or rational mutagenesis, can be laborious and often yield limited insights into the underlying design principles. The complexity of metal coordination environments, the dynamic interplay between the protein scaffold and the metal ion, and the precise geometric requirements for catalysis make *de novo* design or significant redesign of metallohydrolases particularly difficult. However, advances in computational methodologies have opened new avenues for tackling these challenges. Computational enzyme design leverages powerful algorithms and high-performance computing to predict protein structures, model catalytic mechanisms, and identify optimal amino acid sequences for desired functions.\n\nThis paper explores the burgeoning field of computational design applied specifically to metallohydrolases. By integrating principles of protein folding, molecular dynamics, quantum mechanics, and bioinformatics, computational approaches offer unprecedented opportunities to elucidate the fundamental principles governing metal-catalyzed hydrolysis and to engineer novel metallohydrolases with tailored specificities and enhanced activities. We delve into the methodologies and strategies employed in the computational design of these intricate enzymes, discuss recent successes and persistent challenges, and highlight the promising future directions for creating synthetic biocatalysts with broad applications in medicine, biotechnology, and environmental science.\n\n```json\n{"citations": [ {"snippet": "Enzymes are remarkable biological catalysts essential for nearly all life processes, accelerating biochemical reactions with high specificity and efficiency.", "title": "Enzymes as Biological Catalysts: An Overview", "url": "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3672521/", "index": 0}, {"snippet": "Among the diverse classes of enzymes, hydrolases play a pivotal role by catalyzing the cleavage of chemical bonds through the addition of water. This broad class encompasses a vast array of enzymes involved in crucial biological functions, from metabolism and signal transduction to detoxification and immune response.", "title": "Hydrolases: Classification, Mechanism, and Applications", "url": "https://www.sciencedirect.com/topics/agricultural-and-biological-sciences/hydrolases", "index": 1}, {"snippet": "Their versatility also makes them highly attractive for industrial applications, including the production of biofuels, pharmaceuticals, and detergents, as well as in waste treatment and bioremediation.", "title": "Applications of Hydrolases in Biotechnology", "url": "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7147721/", "index": 2}, {"snippet": "Their versatility also makes them highly attractive for industrial applications, including the production of biofuels, pharmaceuticals, and detergents, as well as in waste treatment and bioremediation.", "title": "Hydrolases: Types, Functions, and Biotechnological Applications", "url": "https://www.frontiersin.org/articles/10.3389/fbioe.2021.644123/full", "index": 3}, {"snippet": "A significant subset of hydrolases, known as metallohydrolases, relies on one or more metal ions for their catalytic activity. These metal cofactors often participate directly in substrate binding, activate water molecules for nucleophilic attack, or stabilize transition states, thereby facilitating reactions that would otherwise be exceedingly slow.", "title": "Metallohydrolases: Structure, Function, and Mechanism", "url": "https://www.annualreviews.org/doi/abs/10.1146/annurev-biochem-071715-050410", "index": 4}, {"snippet": "Examples include carbonic anhydrase, which rapidly interconverts CO2 and bicarbonate, and various proteases, nucleases, and phosphatases, all critical for cellular homeostasis.", "title": "Metallohydrolases: A Diverse Family of Enzymes", "url": "https://www.sciencedirect.com/science/article/pii/B9780123744203002620", "index": 5}, {"snippet": "The complexity of metal coordination environments, the dynamic interplay between the protein scaffold and the metal ion, and the precise geometric requirements for catalysis make de novo design or significant redesign of metallohydrolases particularly difficult.", "title": "Challenges in Engineering Metallohydrolases", "url": "https://www.pnas.org/doi/10.1073/pnas.1906233116", "index": 6}, {"snippet": "However, advances in computational methodologies have opened new avenues for tackling these challenges. Computational enzyme design leverages powerful algorithms and high-performance computing to predict protein structures, model catalytic mechanisms, and identify optimal amino acid sequences for desired functions.", "title": "Computational Enzyme Design: Principles and Applications", "url": "https://www.nature.com/articles/nchembio.2045", "index": 7}]}\n```'}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None), long_running_tool_ids=None, branch=None, id='2e382c61-eb88-49c8-af7e-9d348ed02b42', timestamp=1765177728.065992),
 Event(model_version='gemini-2.0-flash', content=Content(
   parts=[
     Part(
       text="""Enzymes are remarkable biological catalysts essential for nearly all life processes, accelerating biochemical reactions with high specificity and efficiency [1]. Among the diverse classes of enzymes, hydrolases play a pivotal role by catalyzing the cleavage of chemical bonds through the addition of water [2]. Their versatility also makes them highly attractive for industrial applications, including the production of biofuels, pharmaceuticals, detergents, waste treatment, and bioremediation [3, 4]. A significant subset of hydrolases, known as metallohydrolases, relies on one or more metal ions for their catalytic activity [5]. Examples include carbonic anhydrase, which rapidly interconverts CO2 and bicarbonate, and various proteases, nucleases, and phosphatases, all critical for cellular homeostasis [6]. Despite their profound importance, understanding the intricate mechanisms governing metal-dependent catalysis and engineering these enzymes for novel functions remains a formidable challenge [7]. Traditional approaches to enzyme engineering can be laborious and often yield limited insights into the underlying design principles. However, advances in computational methodologies have opened new avenues for tackling these challenges [8]. This paper explores the burgeoning field of computational design applied specifically to metallohydrolases.
 """
     ),
   ],
   role='model'
 ), grounding_metadata=None, partial=None, turn_complete=None, finish_reason=<FinishReason.STOP: 'STOP'>, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=GenerateContentResponseUsageMetadata(
   candidates_token_count=233,
   candidates_tokens_details=[
     ModalityTokenCount(
       modality=<MediaModality.TEXT: 'TEXT'>,
       token_count=233
     ),
   ],
   prompt_token_count=2831,
   prompt_tokens_details=[
     ModalityTokenCount(
       modality=<MediaModality.TEXT: 'TEXT'>,
       token_count=2831
     ),
   ],
   total_token_count=3064
 ), live_session_resumption_update=None, input_transcription=None, output_transcription=None, avg_logprobs=-0.029857273265527555, logprobs_result=None, cache_metadata=None, citation_metadata=None, invocation_id='e-723f9d22-c1b7-465e-a0d5-09a1d21a436f', author='reader_agent', actions=EventActions(skip_summarization=None, state_delta={'paper_content': 'Enzymes are remarkable biological catalysts essential for nearly all life processes, accelerating biochemical reactions with high specificity and efficiency [1]. Among the diverse classes of enzymes, hydrolases play a pivotal role by catalyzing the cleavage of chemical bonds through the addition of water [2]. Their versatility also makes them highly attractive for industrial applications, including the production of biofuels, pharmaceuticals, detergents, waste treatment, and bioremediation [3, 4]. A significant subset of hydrolases, known as metallohydrolases, relies on one or more metal ions for their catalytic activity [5]. Examples include carbonic anhydrase, which rapidly interconverts CO2 and bicarbonate, and various proteases, nucleases, and phosphatases, all critical for cellular homeostasis [6]. Despite their profound importance, understanding the intricate mechanisms governing metal-dependent catalysis and engineering these enzymes for novel functions remains a formidable challenge [7]. Traditional approaches to enzyme engineering can be laborious and often yield limited insights into the underlying design principles. However, advances in computational methodologies have opened new avenues for tackling these challenges [8]. This paper explores the burgeoning field of computational design applied specifically to metallohydrolases.\n'}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None), long_running_tool_ids=None, branch=None, id='fd1aefea-ad1d-4b41-bd04-2836bb3e003b', timestamp=1765177737.006624),
 Event(model_version='gemini-2.5-flash', content=Content(
   parts=[
     Part(
       text="""Enzymes are exceptional biological catalysts, indispensable for virtually all life processes due to their remarkable specificity and efficiency in accelerating biochemical reactions [1]. Within this diverse superfamily, hydrolases stand out by catalyzing the cleavage of chemical bonds through the addition of water [2]. This versatile class of enzymes is crucial for numerous biological functions, from metabolism to cellular signaling, and is equally valuable in various industrial applications, including biofuel production, pharmaceuticals, detergents, waste treatment, and bioremediation [3, 4].
 
 A particularly important group within the hydrolases are the metallohydrolases, which depend on one or more metal ions for their catalytic activity [5]. These metal cofactors are integral to the reaction mechanism, often participating in substrate binding, activating water molecules for nucleophilic attack, or stabilizing transition states. Their functional diversity is vast, encompassing enzymes like carbonic anhydrase, vital for CO2 and bicarbonate interconversion, and various proteases, nucleases, and phosphatases, all essential for maintaining cellular homeostasis [6].
 
 Despite their profound biological importance and industrial potential, deciphering the intricate mechanisms governing metal-dependent catalysis and subsequently engineering these enzymes for novel or enhanced functions presents a significant challenge [7]. Traditional enzyme engineering methods, such as directed evolution or rational mutagenesis, are often laborious and may provide limited insights into the fundamental design principles. However, the emergence of advanced computational methodologies has revolutionized the approach to these challenges [8]. This paper delves into the rapidly expanding field of computational design, specifically as it applies to metallohydrolases, exploring how these powerful *in silico* techniques are enabling the rational engineering of these complex and vital biocatalysts."""
     ),
   ],
   role='model'
 ), grounding_metadata=None, partial=None, turn_complete=None, finish_reason=<FinishReason.STOP: 'STOP'>, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=GenerateContentResponseUsageMetadata(
   candidates_token_count=331,
   prompt_token_count=1874,
   prompt_tokens_details=[
     ModalityTokenCount(
       modality=<MediaModality.TEXT: 'TEXT'>,
       token_count=1874
     ),
   ],
   thoughts_token_count=185,
   total_token_count=2390
 ), live_session_resumption_update=None, input_transcription=None, output_transcription=None, avg_logprobs=None, logprobs_result=None, cache_metadata=None, citation_metadata=None, invocation_id='e-723f9d22-c1b7-465e-a0d5-09a1d21a436f', author='writer_agent', actions=EventActions(skip_summarization=None, state_delta={'current_draft': 'Enzymes are exceptional biological catalysts, indispensable for virtually all life processes due to their remarkable specificity and efficiency in accelerating biochemical reactions [1]. Within this diverse superfamily, hydrolases stand out by catalyzing the cleavage of chemical bonds through the addition of water [2]. This versatile class of enzymes is crucial for numerous biological functions, from metabolism to cellular signaling, and is equally valuable in various industrial applications, including biofuel production, pharmaceuticals, detergents, waste treatment, and bioremediation [3, 4].\n\nA particularly important group within the hydrolases are the metallohydrolases, which depend on one or more metal ions for their catalytic activity [5]. These metal cofactors are integral to the reaction mechanism, often participating in substrate binding, activating water molecules for nucleophilic attack, or stabilizing transition states. Their functional diversity is vast, encompassing enzymes like carbonic anhydrase, vital for CO2 and bicarbonate interconversion, and various proteases, nucleases, and phosphatases, all essential for maintaining cellular homeostasis [6].\n\nDespite their profound biological importance and industrial potential, deciphering the intricate mechanisms governing metal-dependent catalysis and subsequently engineering these enzymes for novel or enhanced functions presents a significant challenge [7]. Traditional enzyme engineering methods, such as directed evolution or rational mutagenesis, are often laborious and may provide limited insights into the fundamental design principles. However, the emergence of advanced computational methodologies has revolutionized the approach to these challenges [8]. This paper delves into the rapidly expanding field of computational design, specifically as it applies to metallohydrolases, exploring how these powerful *in silico* techniques are enabling the rational engineering of these complex and vital biocatalysts.'}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None), long_running_tool_ids=None, branch=None, id='b018fee0-e7ca-402c-ac1e-2b45a00b558a', timestamp=1765177739.034184),
 Event(model_version='gemini-2.5-flash', content=Content(
   parts=[
     Part(
       text='APPROVED'
     ),
   ],
   role='model'
 ), grounding_metadata=None, partial=None, turn_complete=None, finish_reason=<FinishReason.STOP: 'STOP'>, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=GenerateContentResponseUsageMetadata(
   candidates_token_count=1,
   prompt_token_count=2317,
   prompt_tokens_details=[
     ModalityTokenCount(
       modality=<MediaModality.TEXT: 'TEXT'>,
       token_count=2317
     ),
   ],
   thoughts_token_count=1059,
   total_token_count=3377
 ), live_session_resumption_update=None, input_transcription=None, output_transcription=None, avg_logprobs=None, logprobs_result=None, cache_metadata=None, citation_metadata=None, invocation_id='e-723f9d22-c1b7-465e-a0d5-09a1d21a436f', author='supervisor_agent', actions=EventActions(skip_summarization=None, state_delta={'critique': 'APPROVED'}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None), long_running_tool_ids=None, branch=None, id='90e07977-f904-417a-8069-c1d9c10c8cd8', timestamp=1765177742.260851),
 Event(model_version='gemini-2.5-flash', content=Content(
   parts=[
     Part(
       function_call=FunctionCall(
         args={},
         id='adk-af76d222-c482-4770-ab6b-9d34a9964ee6',
         name='exit_loop'
       ),
       thought_signature=b'\n\xa7\x01\x01r\xc8\xda|%\x8c\xac\x82\xe2\xc9\x14\x03\xd68\xe6\x8a<\x0b\xba1\x85\xd2\x8c\xefa\x11\xee5$\x85\xda;\x9f\xb7E\xd2=\xe0\x00B\x9c\x1b^5F\xbe5\x1d\xb4\xb2\xc0\xe90\x85\xe0\x05\xdc\xbdb\r\xf9\xa9\xa7\x84/\xc4\x9e}\x1eP\xb4\x19\xf47\x9d\x8e\xcch\xb4\xf3z\xa0c\xbf\x0ff\x10\x02&\x01&\x17\xe6...'
     ),
   ],
   role='model'
 ), grounding_metadata=None, partial=None, turn_complete=None, finish_reason=<FinishReason.STOP: 'STOP'>, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=GenerateContentResponseUsageMetadata(
   candidates_token_count=10,
   prompt_token_count=2397,
   prompt_tokens_details=[
     ModalityTokenCount(
       modality=<MediaModality.TEXT: 'TEXT'>,
       token_count=2397
     ),
   ],
   thoughts_token_count=31,
   total_token_count=2438
 ), live_session_resumption_update=None, input_transcription=None, output_transcription=None, avg_logprobs=None, logprobs_result=None, cache_metadata=None, citation_metadata=None, invocation_id='e-723f9d22-c1b7-465e-a0d5-09a1d21a436f', author='associate_supervisor_agent', actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None), long_running_tool_ids=set(), branch=None, id='a05d157e-9649-4520-bf9b-10290d1c1320', timestamp=1765177747.974328),
 Event(model_version=None, content=Content(
   parts=[
     Part(
       function_response=FunctionResponse(
         id='adk-af76d222-c482-4770-ab6b-9d34a9964ee6',
         name='exit_loop',
         response={
           'result': 'Loop exited.'
         }
       )
     ),
   ],
   role='user'
 ), grounding_metadata=None, partial=None, turn_complete=None, finish_reason=None, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=None, live_session_resumption_update=None, input_transcription=None, output_transcription=None, avg_logprobs=None, logprobs_result=None, cache_metadata=None, citation_metadata=None, invocation_id='e-723f9d22-c1b7-465e-a0d5-09a1d21a436f', author='associate_supervisor_agent', actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=True, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None), long_running_tool_ids=None, branch=None, id='7ddfdb0d-e60a-4174-92f3-42e9dde7010d', timestamp=1765177748.969192),
 Event(model_version='gemini-2.5-flash', content=Content(
   role='model'
 ), grounding_metadata=None, partial=None, turn_complete=None, finish_reason=<FinishReason.STOP: 'STOP'>, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=GenerateContentResponseUsageMetadata(
   cache_tokens_details=[
     ModalityTokenCount(
       modality=<MediaModality.TEXT: 'TEXT'>,
       token_count=1726
     ),
   ],
   cached_content_token_count=1726,
   prompt_token_count=2424,
   prompt_tokens_details=[
     ModalityTokenCount(
       modality=<MediaModality.TEXT: 'TEXT'>,
       token_count=2424
     ),
   ],
   total_token_count=2424
 ), live_session_resumption_update=None, input_transcription=None, output_transcription=None, avg_logprobs=None, logprobs_result=None, cache_metadata=None, citation_metadata=None, invocation_id='e-723f9d22-c1b7-465e-a0d5-09a1d21a436f', author='associate_supervisor_agent', actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None), long_running_tool_ids=None, branch=None, id='24597353-a3b7-4f5b-ad64-58abc123db3d', timestamp=1765177748.9709)]

A full literature review in one short prompt! This could definitely be refined, prompts could be perfected, some Agent tasks could be done with traditional computation, perhaps with a team of ParallelAgents each working on different part of the problem. But hopefully it gives you an idea for where to start! What part of your research journey do you think could be accelerated with Agents?

Bonus 2: Running out of the Colab environment

As mentioned, colab runs its own “asyncio” environment. You can use this code in a script!

import asyncio
import os
from google.adk.agents import Agent
from google.adk.tools import google_search
from google.adk.sessions import InMemorySessionService
from google.adk.runners import Runner
from google.genai.types import Content, Part

# Ensure your API Key is set in your environment
# os.environ["GOOGLE_API_KEY"] = "YOUR_API_KEY"

async def main():
    # 1. Define the Agent
    lit_review_agent = Agent(
        name="lit_review_agent",
        model="gemini-2.5-flash",
        description="A research assistant that finds recent scientific papers.",
        instruction="""
        You are a helpful research assistant.
        Your job is to find scientific articles or
        papers for a user on the topic or subject they ask for.

        When asked for scientific papers on a topic, you MUST:
        1.  Use the `Google Search` tool to search arxiv.org only.
        2.  Find 3-5 relevant relevant results.
        3.  For each result, return the title and the arXiv identifier.
        4.  Present the results in a clear, numbered list.
        """,
        tools=[google_search]
    )

    # 2. Initialize Session Service and Runner
    session_service = InMemorySessionService()

    agent_runner = Runner(
        agent=lit_review_agent,
        session_service=session_service,
        app_name=lit_review_agent.name
    )

    user_id = "geo_workshop_user"
    session_id = "session_001"

    # 3. Create Session (using await)
    session = await session_service.create_session(
        app_name=lit_review_agent.name,
        user_id=user_id,
        session_id=session_id
    )

    user_query = "Find papers about recent earthquakes."
    print(f"User Query: {user_query}\n")

    # 4. Run the Agent
    # We use run_async() because we are inside an async function.
    # This returns an async generator we can iterate over.
    response_generator = agent_runner.run_async(
        user_id=user_id,
        session_id=session_id,
        new_message=Content(parts=[Part(text=user_query)], role="user")
    )

    all_content_objects = []

    # 5. Process Events
    # Use 'async for' to consume the generator
    async for event in response_generator:
        all_content_objects.append(event)
        
        # Check if this event is the final text response to print it cleanly
        if event.is_final_response() and event.content:
            print("--- Final Response ---")
            for part in event.content.parts:
                print(part.text)


if __name__ == "__main__":
    asyncio.run(main())