Multi-Agent AI System with Google ADK

Build and deploy a multi-agent customer support system on GCP.

Introduction

30 Second Summary

When you contact customer support, your question gets routed to the right specialist. Someone handles order tracking, someone else handles refunds, and another handles product questions. AI agent systems work the same way.

In this project, you will build a multi-agent customer support system using Google's Agent Development Kit (ADK) and deploy it to Cloud Run with Terraform. You will also create an evaluation pipeline that measures how accurately your agents route queries and select tools.

What You'll Build

A production-style multi-agent system where a coordinator agent intelligently routes customer queries to specialist agents for order tracking, product information, and escalation.

By the end of this project, you'll have:

  • A multi-agent Python application using Google ADK with a coordinator delegating to 3 specialist sub-agents, each equipped with their own tools.
  • Terraform configuration that provisions GCP infrastructure and a deployed Cloud Run service accessible via REST API.
  • An evaluation script that validates agent behavior by comparing predicted tool calls against expected trajectories.
  • Secret Mission: Add an iterative quality-check loop using ADK's LoopAgent that refines agent responses before returning them to the user.

Are there any prerequisites?

This project assumes you have a GCP account with billing enabled, Python 3.12 installed, and the gcloud CLI authenticated. You should also have Terraform installed and a GitHub account ready to go.

Set Up Your Repo and Environment

Before building any agents, you need a clean project structure and authenticated tooling. A well-organized repository makes it easy to separate agent code, infrastructure definitions, and evaluation scripts.

In this step, you will create the GitHub repo, install the Google ADK Python package, and authenticate your GCP project so everything is wired together before you write a single agent.

In this step, get ready to:
  • Create the GitHub repository and project directory structure.
  • Set up a Python virtual environment and install ADK dependencies.
  • Configure GCP authentication and environment variables.
Create the GitHub repository and project structure

The ADK framework expects a specific folder layout where each agent lives in its own directory with an __init__.py and an agent.py file. You will create this structure now along with folders for Terraform configs and evaluation scripts.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder, initialize a git repository, and set up the directory structure by running these commands:
mkdir customer-support-agents
cd customer-support-agents
git init
mkdir support_agent terraform eval

What does this structure do?

  • support_agent/ holds your ADK agent code (the Python module the framework loads).
  • terraform/ holds your infrastructure-as-code configuration for GCP resources.
  • eval/ holds the evaluation dataset and scoring script.
  • Confirm the directories exist by running:
ls

You should see eval, support_agent, and terraform in the output.

  • Create the .gitignore file by running this command:
cat > .gitignore << 'EOF'
# Python
__pycache__/
*.py[cod]
*$py.class
.venv/
venv/
*.egg-info/
dist/
build/

# Environment
.env

# Terraform
terraform/.terraform/
terraform/*.tfstate
terraform/*.tfstate.backup
terraform/.terraform.lock.hcl

# OS
.DS_Store

# IDE
*.swp
*.swo
*~
EOF

What does this file do?

The .gitignore tells Git which files to never track. This keeps secrets (.env), generated caches (__pycache__/), virtual environments (.venv/), and Terraform state files out of your repository.

  • Create the initial README.md by running:
cat > README.md << 'EOF'
# Customer Support Multi-Agent System

A production-style multi-agent customer support system built with Google Agent Development Kit (ADK) and deployed to Cloud Run with Terraform.
EOF
  • Verify both files were created by running:
ls -a

You should see .git, .gitignore, README.md, and the three directories in the output.

Don't see .gitignore in the output?

Files starting with a dot are hidden by default. Make sure you ran ls -a (with the -a flag) to show hidden files.

If the file is missing entirely, re-run the cat > .gitignore << 'EOF' command and make sure you type EOF on its own line at the end.

help me troubleshoot my .gitignore creation

Set up Python virtual environment and install dependencies

A virtual environment isolates this project's packages from your system Python. The google-adk package provides the agent framework, CLI tools, and local dev server you will use throughout this project.

  • Create and activate a virtual environment by running these commands:
python3 -m venv .venv
source .venv/bin/activate

What does activating a venv do?

After activation, your terminal prompt changes to show (.venv) at the beginning. Any pip install commands now install packages into this isolated folder instead of your system Python.

  • Verify your prompt now shows (.venv) at the start.

Don't see (.venv) in your prompt?

If you are on Windows using PowerShell, run .venv\Scripts\activate instead of the source command.

help me activate my Python virtual environment

  • Install the project dependencies by running:
pip install google-adk requests

What are these packages?

google-adk is the Google Agent Development Kit. It provides the agent classes, tool decorators, and the local dev server.

requests is a popular HTTP library you will use in the evaluation script to query the deployed agent's REST API.

  • Create the requirements.txt file so others can reproduce your environment:
cat > requirements.txt << 'EOF'
google-adk
requests
EOF
  • Verify the installation succeeded by running:
pip show google-adk

You should see output showing the package name, version, and location fields confirming the install.

Seeing 'command not found' for pip?

Make sure your virtual environment is active (you should see (.venv) in your prompt). If it is not active, run source .venv/bin/activate again, then retry the install.

help me fix my pip installation

Configure GCP authentication and environment

The ADK framework connects to Vertex AI to run Gemini models. For this to work locally, your terminal needs valid Google Cloud credentials and a project context.

  • Authenticate your Google Cloud CLI by running:
gcloud auth login

A browser window opens asking you to sign in with your Google account. Complete the sign-in flow.

  • Set up Application Default Credentials (required for ADK to access Vertex AI) by running:
gcloud auth application-default login

Another browser tab opens for the same sign-in flow. Complete it.

Why two auth commands?

gcloud auth login authenticates the gcloud CLI itself (for commands like gcloud services enable).

gcloud auth application-default login creates credentials that Python libraries (including ADK) use to call Google APIs on your behalf.

  • Set your active GCP project by running:
gcloud config set project [[GCP_PROJECT_ID="your-gcp-project-id"]]
  • Create the support_agent/.env file that ADK reads at startup:
cat > support_agent/.env << 'EOF'
GOOGLE_GENAI_USE_VERTEXAI=TRUE
GOOGLE_CLOUD_PROJECT=[[GCP_PROJECT_ID="your-gcp-project-id"]]
GOOGLE_CLOUD_LOCATION=us-central1
EOF

What do these environment variables do?

  • GOOGLE_GENAI_USE_VERTEXAI=TRUE tells ADK to route model calls through Vertex AI instead of the public Gemini API.
  • GOOGLE_CLOUD_PROJECT identifies which GCP project to bill and which APIs to use.
  • GOOGLE_CLOUD_LOCATION sets the region where your model requests run.
  • Confirm the file was created by running:
cat support_agent/.env

You should see the three environment variables printed to your terminal.

Browser didn't open for gcloud auth?

If you are in a headless environment (SSH or a remote machine), add the --no-launch-browser flag. The command prints a URL you can paste into any browser to complete sign-in.

help me authenticate gcloud in my environment

✔️ Awesome, I've got everything!

Great. Double check you have saved all files and your terminal still shows (.venv) in the prompt.

ⓧ I'd like to double check the full code

Here are all four files you should have at this point.

# Python
__pycache__/
*.py[cod]
*$py.class
.venv/
venv/
*.egg-info/
dist/
build/

# Environment
.env

# Terraform
terraform/.terraform/
terraform/*.tfstate
terraform/*.tfstate.backup
terraform/.terraform.lock.hcl

# OS
.DS_Store

# IDE
*.swp
*.swo
*~
# Customer Support Multi-Agent System

A production-style multi-agent customer support system built with Google Agent Development Kit (ADK) and deployed to Cloud Run with Terraform.
google-adk
requests
GOOGLE_GENAI_USE_VERTEXAI=TRUE
GOOGLE_CLOUD_PROJECT=your-project-id-here
GOOGLE_CLOUD_LOCATION=us-central1

Your project skeleton is ready and authenticated. Next up, you will build the multi-agent system itself: three specialist agents with tools and a coordinator that routes customer queries to the right one.

Build the Multi-Agent System

Your development environment is ready with google-adk installed and GCP authentication configured. Now it's time to build the core of this project: a multi-agent system where a coordinator routes customer queries to specialist agents.

Each specialist agent has its own tools and instructions, making it an expert at one thing. The coordinator sits above them and decides which specialist should handle each incoming request. This is the same delegation pattern that enterprise customer support systems use.

In this step, get ready to:
  • Create three specialist agents with dedicated tool functions.
  • Build a coordinator agent that routes queries to the right specialist.
  • Test the full multi-agent system locally with the ADK Dev UI.
Create the specialist agents with tools

In Google ADK, agents use tool functions to take actions in the world. A tool is a plain Python function that the agent can call when it needs to look something up or perform an operation.

  • Create a new file called agent.py inside the support_agent/ directory by running:
touch support_agent/agent.py
  • Open support_agent/agent.py in your editor and add the import and three tool functions by pasting this code:
from google.adk.agents import LlmAgent


def lookup_order(order_id: str) -> dict:
    """Look up the status of a customer order by order ID.

    Args:
        order_id: The order identifier (e.g., "ORD-12345").

    Returns:
        A dictionary with order status details.
    """
    orders = {
        "ORD-12345": {
            "status": "shipped",
            "tracking": "1Z999AA10123456784",
            "estimated_delivery": "2026-05-28",
        },
        "ORD-67890": {
            "status": "processing",
            "tracking": None,
            "estimated_delivery": "2026-06-02",
        },
    }
    if order_id in orders:
        return {"order_id": order_id, **orders[order_id]}
    return {"error": f"Order {order_id} not found"}


def get_product_info(product_name: str) -> dict:
    """Retrieve product information including price and availability.

    Args:
        product_name: The name of the product to look up.

    Returns:
        A dictionary with product details.
    """
    products = {
        "wireless headphones": {
            "name": "Wireless Headphones Pro",
            "price": "$79.99",
            "in_stock": True,
            "warranty": "2 years",
        },
        "laptop stand": {
            "name": "Ergonomic Laptop Stand",
            "price": "$45.99",
            "in_stock": True,
            "warranty": "1 year",
        },
    }
    key = product_name.lower()
    for product_key, info in products.items():
        if product_key in key or key in product_key:
            return info
    return {"error": f"Product '{product_name}' not found"}


def escalate_to_human(reason: str) -> dict:
    """Escalate the conversation to a human support agent.

    Args:
        reason: The reason for escalation.

    Returns:
        A dictionary confirming the escalation.
    """
    return {
        "escalated": True,
        "reason": reason,
        "message": "A human agent will be with you shortly. Reference ID: ESC-2026-001",
    }

What does this code do?

Each function is a tool that ADK can call on behalf of an agent. ADK reads the function's docstring and type hints to understand what the tool does and what arguments it accepts.

  • lookup_order() simulates a database lookup for order status and tracking info.
  • get_product_info() returns product details like price and warranty by searching a product catalog.
  • escalate_to_human() hands off the conversation to a human agent when the issue is too complex.

Now add the three specialist agent instances below the tool functions. Each agent gets its own tool, instructions, and a description that tells the coordinator what it handles.

  • Add the following code at the bottom of support_agent/agent.py, below the escalate_to_human function:
order_agent = LlmAgent(
    name="order_agent",
    model="gemini-2.5-flash",
    description="Handles order status inquiries, tracking, and delivery estimates.",
    instruction=(
        "You are an order tracking specialist. Use the lookup_order tool "
        "to find order information when customers ask about their orders. "
        "Always ask for the order ID if not provided. "
        "Present the information clearly and helpfully."
    ),
    tools=[lookup_order],
)

product_agent = LlmAgent(
    name="product_agent",
    model="gemini-2.5-flash",
    description="Answers questions about products, pricing, availability, and warranties.",
    instruction=(
        "You are a product information specialist. Use the get_product_info tool "
        "to look up product details when customers ask about products. "
        "Be helpful and suggest related information like warranty details."
    ),
    tools=[get_product_info],
)

escalation_agent = LlmAgent(
    name="escalation_agent",
    model="gemini-2.5-flash",
    description="Handles complex issues that require human intervention, complaints, or refund requests.",
    instruction=(
        "You are an escalation specialist. When a customer has a complex issue, "
        "complaint, or refund request that cannot be resolved automatically, "
        "use the escalate_to_human tool. Always acknowledge the customer's "
        "frustration and explain that a human agent will help them."
    ),
    tools=[escalate_to_human],
)

What is an LlmAgent?

An LlmAgent is ADK's core agent type. It wraps a language model (here, gemini-2.5-flash) with an instruction prompt, a description, and a list of tools it can call.

The description field is important because the coordinator agent reads it to decide which specialist to delegate to. Think of it as a job title that the coordinator checks before routing a request.

  • Save support_agent/agent.py.

You won't see visible output yet. The coordinator in the next substep brings these specialists together.

Seeing a syntax error?

  • Check that each LlmAgent call ends with a closing parenthesis and comma after tools=[...].
  • Verify the import at the top of the file reads from google.adk.agents import LlmAgent exactly.

Why aren't my specialist agents working?

Create the coordinator agent

The coordinator is the top-level agent that receives every customer message first. Instead of answering itself, it reads the customer's intent and delegates to the right specialist using the sub_agents parameter.

  • Add the root_agent definition at the bottom of support_agent/agent.py, below the escalation_agent:
root_agent = LlmAgent(
    name="customer_support_coordinator",
    model="gemini-2.5-flash",
    description="Main customer support coordinator that routes queries to specialists.",
    instruction=(
        "You are the main customer support coordinator. Your job is to understand "
        "the customer's intent and delegate to the appropriate specialist:\n\n"
        "- For order status, tracking, or delivery questions: delegate to order_agent\n"
        "- For product information, pricing, or availability: delegate to product_agent\n"
        "- For complaints, refunds, or complex issues: delegate to escalation_agent\n\n"
        "Greet the customer warmly and route their request to the right specialist. "
        "Do not try to answer questions yourself - always delegate to a specialist."
    ),
    sub_agents=[order_agent, product_agent, escalation_agent],
)

How does sub_agents delegation work?

The sub_agents parameter establishes a parent-child hierarchy. ADK reads each sub-agent's description and automatically makes them available for the coordinator to delegate to.

When the coordinator decides a query is about orders, it hands control to order_agent, which then uses its lookup_order tool to fetch the data. The coordinator never calls tools directly.

ADK requires a root_agent variable as the entry point for your agent module. You also need an __init__.py file that imports the agent module so ADK can discover it.

  • Create support_agent/__init__.py by running:
echo 'from . import agent' > support_agent/__init__.py
  • Save support_agent/agent.py.
  • Confirm both files exist by running:
ls support_agent/

You should see __init__.py, agent.py, and .env listed in the output.

Missing files in the output?

  • Make sure you ran the echo command from the customer-support-agents root directory, not from inside support_agent/.
  • The .env file might be hidden. Run ls -a support_agent/ to show hidden files.

Why can't ADK find my agent module?

✔️ Awesome, I've got everything!

Great. Double check you've saved both files before moving on.

ⓧ I'd like to double check the full code

Here is the complete support_agent/agent.py at this point:

from google.adk.agents import LlmAgent


def lookup_order(order_id: str) -> dict:
    """Look up the status of a customer order by order ID.

    Args:
        order_id: The order identifier (e.g., "ORD-12345").

    Returns:
        A dictionary with order status details.
    """
    orders = {
        "ORD-12345": {
            "status": "shipped",
            "tracking": "1Z999AA10123456784",
            "estimated_delivery": "2026-05-28",
        },
        "ORD-67890": {
            "status": "processing",
            "tracking": None,
            "estimated_delivery": "2026-06-02",
        },
    }
    if order_id in orders:
        return {"order_id": order_id, **orders[order_id]}
    return {"error": f"Order {order_id} not found"}


def get_product_info(product_name: str) -> dict:
    """Retrieve product information including price and availability.

    Args:
        product_name: The name of the product to look up.

    Returns:
        A dictionary with product details.
    """
    products = {
        "wireless headphones": {
            "name": "Wireless Headphones Pro",
            "price": "$79.99",
            "in_stock": True,
            "warranty": "2 years",
        },
        "laptop stand": {
            "name": "Ergonomic Laptop Stand",
            "price": "$45.99",
            "in_stock": True,
            "warranty": "1 year",
        },
    }
    key = product_name.lower()
    for product_key, info in products.items():
        if product_key in key or key in product_key:
            return info
    return {"error": f"Product '{product_name}' not found"}


def escalate_to_human(reason: str) -> dict:
    """Escalate the conversation to a human support agent.

    Args:
        reason: The reason for escalation.

    Returns:
        A dictionary confirming the escalation.
    """
    return {
        "escalated": True,
        "reason": reason,
        "message": "A human agent will be with you shortly. Reference ID: ESC-2026-001",
    }


order_agent = LlmAgent(
    name="order_agent",
    model="gemini-2.5-flash",
    description="Handles order status inquiries, tracking, and delivery estimates.",
    instruction=(
        "You are an order tracking specialist. Use the lookup_order tool "
        "to find order information when customers ask about their orders. "
        "Always ask for the order ID if not provided. "
        "Present the information clearly and helpfully."
    ),
    tools=[lookup_order],
)

product_agent = LlmAgent(
    name="product_agent",
    model="gemini-2.5-flash",
    description="Answers questions about products, pricing, availability, and warranties.",
    instruction=(
        "You are a product information specialist. Use the get_product_info tool "
        "to look up product details when customers ask about products. "
        "Be helpful and suggest related information like warranty details."
    ),
    tools=[get_product_info],
)

escalation_agent = LlmAgent(
    name="escalation_agent",
    model="gemini-2.5-flash",
    description="Handles complex issues that require human intervention, complaints, or refund requests.",
    instruction=(
        "You are an escalation specialist. When a customer has a complex issue, "
        "complaint, or refund request that cannot be resolved automatically, "
        "use the escalate_to_human tool. Always acknowledge the customer's "
        "frustration and explain that a human agent will help them."
    ),
    tools=[escalate_to_human],
)

root_agent = LlmAgent(
    name="customer_support_coordinator",
    model="gemini-2.5-flash",
    description="Main customer support coordinator that routes queries to specialists.",
    instruction=(
        "You are the main customer support coordinator. Your job is to understand "
        "the customer's intent and delegate to the appropriate specialist:\n\n"
        "- For order status, tracking, or delivery questions: delegate to order_agent\n"
        "- For product information, pricing, or availability: delegate to product_agent\n"
        "- For complaints, refunds, or complex issues: delegate to escalation_agent\n\n"
        "Greet the customer warmly and route their request to the right specialist. "
        "Do not try to answer questions yourself - always delegate to a specialist."
    ),
    sub_agents=[order_agent, product_agent, escalation_agent],
)

And here is support_agent/__init__.py:

from . import agent
Test locally with ADK Dev UI

ADK ships with a browser-based development UI that lets you chat with your agents, inspect tool calls, and trace the delegation path. This is how you verify routing works before deploying.

  • Make sure you are in the customer-support-agents root directory (the parent folder containing support_agent/).
  • Launch the ADK Dev UI by running:
adk web .

What does this command do?

The adk web command starts a local development server at http://localhost:8000. The . tells ADK to scan the current directory for agent modules. ADK finds support_agent/ because it contains a valid __init__.py that imports the agent module with a root_agent variable.

  • Open http://localhost:8000 in your browser.
  • In the top-left dropdown, select support_agent.
  • Type Where is my order ORD-12345? in the chat box and press Enter.

You should see the coordinator delegate to order_agent, which calls the lookup_order tool and returns shipping details.

  • Test product routing by typing: How much do the wireless headphones cost?.
  • Test escalation by typing: I want a refund, this is unacceptable..
  • Click the Events tab on the left panel to inspect the tool calls for each message.

You should see each query routed to the correct specialist, with the corresponding tool call visible in the Events trace.

Don't see support_agent in the dropdown?

  • Make sure you ran adk web . from the customer-support-agents root directory, not from inside support_agent/.
  • Check that support_agent/__init__.py contains exactly from . import agent.
  • Verify your virtual environment is active (you should see (.venv) in your terminal prompt).

My agent doesn't appear in the ADK Dev UI dropdown.

Windows users

If you see a _make_subprocess_transport NotImplementedError, use adk web --no-reload instead.

Your multi-agent system is working locally. Next up, you'll write Terraform configuration to provision the GCP infrastructure needed for a production deployment.

Write Terraform Infrastructure

Your multi-agent system works locally, but it needs a proper home on Google Cloud Platform. Before you can deploy the agent to Cloud Run, you need supporting infrastructure: enabled APIs, a service account with the right permissions, and a Docker image registry.

Terraform lets you define all of this infrastructure as code. Instead of clicking through the GCP console, you'll write configuration files that describe your desired state and let Terraform create everything reproducibly.

In this step, get ready to:
  • Define Terraform variables and provider configuration.
  • Provision a service account, IAM roles, and an Artifact Registry repository.
  • Initialize Terraform and apply infrastructure to GCP.
Define Terraform variables and provider configuration

Terraform uses variables to keep configuration flexible across environments. You'll start by creating a variables file that defines your project's configurable values.

  • Open the terraform/variables.tf file in Vim by running:
vim terraform/variables.tf
  • Add the following variable definitions:
variable "project_id" {
  description = "The GCP project ID"
  type        = string
}

variable "region" {
  description = "The GCP region for deployment"
  type        = string
  default     = "us-central1"
}

variable "service_name" {
  description = "Name of the Cloud Run service"
  type        = string
  default     = "customer-support-agent"
}

What does this code do?

These variables parameterize your Terraform configuration so nothing is hardcoded.

  • project_id has no default because every learner has a different GCP project.
  • region defaults to us-central1 which is where Vertex AI and Cloud Run are available.
  • service_name defaults to customer-support-agent so it matches your deployment later.
  • Save and exit Vim by pressing Esc, then typing :wq and pressing Enter.

Now you'll create the main Terraform configuration file with the provider setup and API enablement.

  • Open terraform/main.tf in Vim by running:
vim terraform/main.tf
  • Add the Terraform block, provider configuration, and API enablement resources:
terraform {
  required_version = ">= 1.3"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 6.0"
    }
  }
}

provider "google" {
  project = var.project_id
  region  = var.region
}

resource "google_project_service" "apis" {
  for_each = toset([
    "run.googleapis.com",
    "aiplatform.googleapis.com",
    "cloudbuild.googleapis.com",
    "artifactregistry.googleapis.com",
    "cloudresourcemanager.googleapis.com",
  ])

  project = var.project_id
  service = each.value

  disable_on_destroy = false
}

What does this code do?

The terraform block pins the Terraform version to 1.3+ and requires the Google provider version ~> 6.0.

The google_project_service resource uses for_each to enable five GCP APIs in a single block. The disable_on_destroy = false setting means running terraform destroy won't disable these APIs, preventing accidental breakage of other services in your project.

  • Keep this file open in Vim. You'll add more resources in the next substep.

Seeing a syntax error in Vim?

Make sure the closing brackets and braces are balanced. Each { needs a matching } and each [ needs a matching ].

help me fix syntax errors in my Terraform main.tf file

Provision service account and Artifact Registry

Your agent needs a dedicated service account with specific permissions to access Vertex AI and Cloud Run. You'll also create an Artifact Registry repository to store the Docker image of your agent.

  • Below the google_project_service resource in terraform/main.tf, add the service account, IAM bindings, and Artifact Registry resources:
resource "google_service_account" "agent_sa" {
  account_id   = "customer-support-agent-sa"
  display_name = "Customer Support Agent Service Account"
  project      = var.project_id
}

resource "google_project_iam_member" "vertex_ai_user" {
  project = var.project_id
  role    = "roles/aiplatform.user"
  member  = "serviceAccount:${google_service_account.agent_sa.email}"
}

resource "google_project_iam_member" "run_invoker" {
  project = var.project_id
  role    = "roles/run.invoker"
  member  = "serviceAccount:${google_service_account.agent_sa.email}"
}

resource "google_artifact_registry_repository" "agent_repo" {
  location      = var.region
  repository_id = "agent-images"
  format        = "DOCKER"
  description   = "Docker repository for customer support agent images"
  project       = var.project_id

  depends_on = [google_project_service.apis]
}

What does this code do?

  • google_service_account creates a dedicated identity for your agent with the ID customer-support-agent-sa.
  • The two google_project_iam_member resources follow least-privilege. roles/aiplatform.user lets the agent call Gemini via Vertex AI. roles/run.invoker lets it invoke Cloud Run services.
  • google_artifact_registry_repository creates a Docker registry named agent-images where your containerized agent image will be stored.
  • depends_on ensures the Artifact Registry API is enabled before creating the repository.
  • Save and exit Vim by pressing Esc, then typing :wq and pressing Enter.

Why least-privilege permissions?

In production, every service account should have only the permissions it needs to function. If your agent's service account were compromised, an attacker could only access Vertex AI and invoke Cloud Run services. They couldn't delete resources, read storage buckets, or modify IAM policies.

✔️ Awesome, I've got everything!

Great. Double check you've saved terraform/main.tf before continuing.

ⓧ I'd like to double check the full code

terraform {
  required_version = ">= 1.3"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 6.0"
    }
  }
}

provider "google" {
  project = var.project_id
  region  = var.region
}

resource "google_project_service" "apis" {
  for_each = toset([
    "run.googleapis.com",
    "aiplatform.googleapis.com",
    "cloudbuild.googleapis.com",
    "artifactregistry.googleapis.com",
    "cloudresourcemanager.googleapis.com",
  ])

  project = var.project_id
  service = each.value

  disable_on_destroy = false
}

resource "google_service_account" "agent_sa" {
  account_id   = "customer-support-agent-sa"
  display_name = "Customer Support Agent Service Account"
  project      = var.project_id
}

resource "google_project_iam_member" "vertex_ai_user" {
  project = var.project_id
  role    = "roles/aiplatform.user"
  member  = "serviceAccount:${google_service_account.agent_sa.email}"
}

resource "google_project_iam_member" "run_invoker" {
  project = var.project_id
  role    = "roles/run.invoker"
  member  = "serviceAccount:${google_service_account.agent_sa.email}"
}

resource "google_artifact_registry_repository" "agent_repo" {
  location      = var.region
  repository_id = "agent-images"
  format        = "DOCKER"
  description   = "Docker repository for customer support agent images"
  project       = var.project_id

  depends_on = [google_project_service.apis]
}
Create outputs and initialize Terraform

Terraform outputs let you retrieve important values after provisioning completes. You'll output the service account email and Artifact Registry URL so you can reference them during deployment.

  • Create terraform/outputs.tf in Vim by running:
vim terraform/outputs.tf
  • Add the following output definitions:
output "service_account_email" {
  description = "Email of the agent service account"
  value       = google_service_account.agent_sa.email
}

output "artifact_registry_url" {
  description = "URL of the Artifact Registry repository"
  value       = "${var.region}-docker.pkg.dev/${var.project_id}/${google_artifact_registry_repository.agent_repo.repository_id}"
}

output "project_id" {
  description = "GCP project ID"
  value       = var.project_id
}

output "region" {
  description = "GCP region"
  value       = var.region
}

What does this code do?

These outputs print useful values to your terminal after terraform apply finishes.

  • service_account_email shows the full email of the service account Terraform created.
  • artifact_registry_url constructs the Docker registry URL you'll push images to.
  • Save and exit Vim by pressing Esc, then typing :wq and pressing Enter.

Now you'll initialize Terraform to download the Google provider plugin and validate your configuration.

  • Move into the terraform/ directory and initialize Terraform by running:
cd terraform
terraform init
  • You should see a message saying "Terraform has been successfully initialized!" in your terminal output.

Seeing a provider error?

Make sure you have Terraform installed and that your internet connection is active. The terraform init command downloads the hashicorp/google provider from the Terraform Registry.

help me troubleshoot terraform init errors with the Google provider

  • Preview the infrastructure changes by running:
terraform plan -var="project_id=[[PROJECT_ID="your GCP project ID"]]"
  • You should see a plan summary showing resources to add (APIs, service account, IAM bindings, and Artifact Registry repository). Look for a line like "Plan: 8 to add, 0 to change, 0 to destroy." at the bottom.

Why pass project_id as a flag?

The project_id variable has no default value, so Terraform requires you to provide it. Using -var on the command line is the quickest approach for a single variable.

  • Apply the infrastructure by running:
terraform apply -var="project_id=[[PROJECT_ID="your GCP project ID"]]"
  • Terraform will show you the planned changes and prompt you to confirm. Type yes and press Enter to proceed.
  • Wait for the apply to complete. You should see the outputs printed at the end, including your service account email and Artifact Registry URL.

Permission denied or API not enabled?

Make sure the GCP project ID you entered matches the project where gcloud is authenticated. Run gcloud config get-value project to double check.

If you see a Cloud Resource Manager API error, you may need to enable it manually first by running gcloud services enable cloudresourcemanager.googleapis.com in a separate terminal.

help me fix terraform apply permission errors on GCP

Your GCP infrastructure is ready. Next up, you'll deploy your multi-agent system to Cloud Run so it's accessible as a live API.

Deploy to Cloud Run

Your infrastructure is provisioned and your agent system works locally. Now it is time to deploy the multi-agent system to Cloud Run so it can serve requests from anywhere on the internet.

The ADK CLI handles containerization and deployment in a single command. It builds a Docker image from your agent code, pushes it to Artifact Registry, and creates a Cloud Run service automatically.

In this step, get ready to:
  • Deploy the agent to Cloud Run using the ADK CLI.
  • Verify the deployment by sending test queries via curl.
Enable APIs and deploy using the ADK CLI

The adk deploy cloud_run command relies on Cloud Run, Vertex AI, and Cloud Build APIs. Your Terraform already enabled these, but running the gcloud confirmation ensures they are active before deployment begins.

  • Confirm the required APIs are enabled by running this command:
gcloud services enable run.googleapis.com aiplatform.googleapis.com cloudbuild.googleapis.com

What does this command do?

This enables three GCP APIs that the deployment pipeline needs. Since Terraform already enabled them, this command completes instantly as a no-op confirmation.

Now you are ready to deploy the agent. The adk deploy cloud_run command packages your agent code into a container and deploys it as a Cloud Run service.

  • Make sure you are in the customer-support-agents project root directory (the parent of support_agent/).
  • Deploy the agent to Cloud Run by running this command:
adk deploy cloud_run \
  --project=$GOOGLE_CLOUD_PROJECT \
  --region=$GOOGLE_CLOUD_LOCATION \
  --service_name=customer-support-agent \
  support_agent/

What does this command do?

This single command does several things behind the scenes. It creates a Dockerfile, builds a container image using Cloud Build, pushes the image to Artifact Registry, and deploys the container as a Cloud Run service.

  • --project specifies your GCP project ID (read from your environment variable).
  • --region sets the deployment region (us-central1 from your .env).
  • --service_name names the Cloud Run service customer-support-agent.
  • support_agent/ is the path to your agent source code directory.

The deployment takes a few minutes

Cloud Build needs to build and push your container image. This typically takes 2-5 minutes. You will see build logs streaming in your terminal while it runs.

You may be prompted about public access

During deployment, the CLI may ask whether to allow unauthenticated (public) access to your service.

  • Enter y and press Enter to allow public access to your agent's API endpoint.

This makes it easier to test with curl. In a production environment, you would deny public access and use authentication tokens instead.

  • When the deployment completes, look for the service URL in the output. It will look something like https://customer-support-agent-abc123xyz.us-central1.run.app.
  • Copy this URL. You will need it for the next substep.

Seeing a permission error during build?

The Cloud Build service account may need additional permissions when deploying for the first time. Try granting it the Service Account User role:

gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
  --member="serviceAccount:$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format='value(projectNumber)')@cloudbuild.gserviceaccount.com" \
  --role="roles/iam.serviceAccountUser"

Then re-run the adk deploy cloud_run command. Help me fix a permission error during adk deploy cloud_run

Verify the deployment

Your Cloud Run service is live. Now you will confirm it works by creating a session and sending a test query using curl.

  • Store your service URL in a variable by running this command (replace the URL with your own from the deployment output):
export SERVICE_URL=[[SERVICE_URL="your Cloud Run service URL, e.g. https://customer-support-agent-abc123.us-central1.run.app"]]
  • Create a session on the deployed agent by running this command:
curl -X POST $SERVICE_URL/apps/support_agent/users/test_user/sessions/test_session_1 \
  -H "Content-Type: application/json" \
  -d '{}'

What does this command do?

ADK agents are stateful. Before you can send messages, you need to create a session that tracks the conversation. The URL path specifies the app name (support_agent), user ID, and session ID.

  • You should see a JSON response confirming the session was created.
  • Send a test query to the deployed agent by running this command:
curl -X POST $SERVICE_URL/run \
  -H "Content-Type: application/json" \
  -d '{"appName":"support_agent","userId":"test_user","sessionId":"test_session_1","newMessage":{"role":"user","parts":[{"text":"Where is my order ORD-12345?"}]}}'

What does this command do?

This sends a customer query to the deployed coordinator agent. The coordinator should route it to the order_agent, which calls the lookup_order tool with ORD-12345.

The JSON payload follows the ADK REST API format. You specify the app name, user ID, session ID, and the new message with its role and text parts.

  • Check the response. You should see JSON output that includes a functionCall for lookup_order and the order details (status shipped, tracking number, estimated delivery).

Getting a 403 Forbidden error?

If you declined public access at the prompt during deployment, your service requires authentication. You can add an identity token to your curl commands:

curl -X POST $SERVICE_URL/run \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
  -d '{"appName":"support_agent","userId":"test_user","sessionId":"test_session_1","newMessage":{"role":"user","parts":[{"text":"Where is my order ORD-12345?"}]}}'

Help me fix the 403 error when calling my Cloud Run agent

Your multi-agent system is live on Cloud Run. Next up, you will build an evaluation pipeline that programmatically tests whether your agent routes queries correctly.

Build the Evaluation Pipeline

Your multi-agent system is deployed and handling requests on Cloud Run. But how do you know the coordinator is routing queries to the right specialist every time?

In production, teams evaluate agents by comparing the actual tool calls (the predicted trajectory) against the expected tool calls (the reference trajectory). You will build a lightweight evaluation pipeline that sends test prompts to your deployed agent and measures whether it picks the correct tool for each query.

In this step, get ready to:
  • Create an evaluation dataset with reference trajectories for each specialist agent.
  • Write an evaluation script that queries your deployed agent and computes trajectory metrics.
  • Run the evaluation, update your README, and push everything to GitHub.
Define the evaluation dataset

A trajectory evaluation needs two things for each test case: a prompt to send the agent, and the expected tool call the agent should make. This pairing is called a reference trajectory.

  • Create the evaluation dataset file by running:
touch eval/eval_dataset.json
  • Open eval/eval_dataset.json in Vim and paste the following test cases:
[
  {
    "prompt": "Where is my order ORD-12345?",
    "expected_agent": "order_agent",
    "expected_tool": "lookup_order",
    "expected_args": {"order_id": "ORD-12345"}
  },
  {
    "prompt": "How much do the wireless headphones cost?",
    "expected_agent": "product_agent",
    "expected_tool": "get_product_info",
    "expected_args": {"product_name": "wireless headphones"}
  },
  {
    "prompt": "I want a refund, this product is terrible and I am very frustrated.",
    "expected_agent": "escalation_agent",
    "expected_tool": "escalate_to_human",
    "expected_args": {}
  },
  {
    "prompt": "What is the status of order ORD-67890?",
    "expected_agent": "order_agent",
    "expected_tool": "lookup_order",
    "expected_args": {"order_id": "ORD-67890"}
  },
  {
    "prompt": "Is the laptop stand in stock?",
    "expected_agent": "product_agent",
    "expected_tool": "get_product_info",
    "expected_args": {"product_name": "laptop stand"}
  }
]

What does this dataset do?

Each test case pairs a customer query with the tool the agent should call. The evaluation script will compare what the agent actually calls (predicted trajectory) against these expected values (reference trajectory).

The dataset covers all three specialists: two order queries for lookup_order, two product queries for get_product_info, and one escalation for escalate_to_human.

  • Save eval/eval_dataset.json.
  • Confirm the file exists by running:
ls eval/

You should see eval_dataset.json in the output.

Write the evaluation script

The evaluation script sends each test prompt to your deployed Cloud Run agent, extracts the tool calls from the response, and compares them against the reference trajectories. It computes two metrics: tool precision (did the agent call the right tool?) and routing accuracy (did the agent call any tool at all?).

  • Create the evaluation script by running:
touch eval/evaluate.py
  • Open eval/evaluate.py in Vim and paste the imports and API helper functions:
"""Evaluation script for the customer support multi-agent system."""

import json
import sys
import requests


def load_eval_dataset(path):
    # Load test cases from JSON file
    with open(path, "r") as f:
        return json.load(f)

def create_session(service_url, app_name, user_id, session_id):
    # Create a new session on the deployed agent
    url = f"{service_url}/apps/{app_name}/users/{user_id}/sessions/{session_id}"
    response = requests.post(url, json={}, headers={"Content-Type": "application/json"})
    return response.json()

def query_agent(service_url, app_name, user_id, session_id, message):
    # Send a query to the deployed agent and return the response
    url = f"{service_url}/run"
    payload = {"appName": app_name, "userId": user_id, "sessionId": session_id, "newMessage": {"role": "user", "parts": [{"text": message}]}}
    response = requests.post(url, json=payload, headers={"Content-Type": "application/json"})
    return response.json()

What do these functions do?

  • load_eval_dataset() reads the JSON test cases you just created.
  • create_session() hits the /apps/{app}/users/{user}/sessions/{session} endpoint to start a fresh conversation.
  • query_agent() sends a message to the /run endpoint. This is the same API you tested with curl in the previous step.
  • Below the query_agent function, add the analysis and orchestration functions:
def extract_tool_calls(response):
    # Parse the response to find which tools were called
    tool_calls = []
    events = response.get("events", response.get("result", []))
    if isinstance(events, list):
        for event in events:
            parts = event.get("content", {}).get("parts", [])
            for part in parts:
                if "functionCall" in part:
                    tool_calls.append({"tool": part["functionCall"].get("name", ""), "args": part["functionCall"].get("args", {})})
    return tool_calls

def compute_metrics(results):
    # Calculate precision and routing accuracy from results
    total = len(results)
    tool_matches = sum(1 for r in results if r["tool_match"])
    agent_routes = sum(1 for r in results if r["agent_routed"])
    return {"total_cases": total, "tool_precision": tool_matches / total if total > 0 else 0, "routing_accuracy": agent_routes / total if total > 0 else 0}

def run_evaluation(service_url):
    # Orchestrate the full evaluation pipeline
    app_name = "support_agent"
    user_id = "eval_user"
    dataset = load_eval_dataset("eval/eval_dataset.json")
    print(f"Running evaluation against: {service_url}")
    results = []
    for i, case in enumerate(dataset):
        session_id = f"eval_session_{i}"
        create_session(service_url, app_name, user_id, session_id)
        response = query_agent(service_url, app_name, user_id, session_id, case["prompt"])
        tool_calls = extract_tool_calls(response)
        tool_match = any(tc["tool"] == case["expected_tool"] for tc in tool_calls)
        agent_routed = len(tool_calls) > 0
        results.append({"prompt": case["prompt"], "expected_tool": case["expected_tool"], "actual_tools": [tc["tool"] for tc in tool_calls], "tool_match": tool_match, "agent_routed": agent_routed})
        status = "PASS" if tool_match else "FAIL"
        print(f"  [{status}] {case['prompt'][:50]}...")
    metrics = compute_metrics(results)
    print(f"Tool Precision: {metrics['tool_precision']:.0%}")
    print(f"Routing Accuracy: {metrics['routing_accuracy']:.0%}")
    return metrics

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python evaluate.py <SERVICE_URL>")
        sys.exit(1)
    service_url = sys.argv[1].rstrip("/")
    run_evaluation(service_url)

What does this code do?

  • extract_tool_calls() digs into the agent's response to find functionCall entries. These are the predicted trajectory.
  • compute_metrics() calculates tool precision (proportion of queries where the agent called the correct tool) and routing accuracy (proportion where the agent called any tool at all).
  • run_evaluation() ties everything together. It creates a fresh session per test case, sends the prompt, extracts tool calls, and prints PASS/FAIL for each.

Why build a custom evaluation instead of using Vertex AI's evaluation service?

Vertex AI offers a full evaluation service with metrics like trajectory_precision and trajectory_recall. Your script implements the same concept. It compares predicted tool calls against reference tool calls and computes precision.

Building it yourself demonstrates the underlying concept clearly. It also avoids extra dependencies and keeps the project under 60 minutes.

  • Save eval/evaluate.py.

Seeing a syntax error?

Check that the f-strings use matching quotes. The payload dictionary in query_agent must use consistent quote styles throughout.

help me fix a syntax error in my evaluate.py file

✔️ Awesome, I've got everything!

Great. Double check you have saved eval/evaluate.py.

ⓧ I'd like to double check the full code

"""Evaluation script for the customer support multi-agent system."""

import json
import sys
import requests


def load_eval_dataset(path):
    with open(path, "r") as f:
        return json.load(f)

def create_session(service_url, app_name, user_id, session_id):
    url = f"{service_url}/apps/{app_name}/users/{user_id}/sessions/{session_id}"
    response = requests.post(url, json={}, headers={"Content-Type": "application/json"})
    return response.json()

def query_agent(service_url, app_name, user_id, session_id, message):
    url = f"{service_url}/run"
    payload = {"appName": app_name, "userId": user_id, "sessionId": session_id, "newMessage": {"role": "user", "parts": [{"text": message}]}}
    response = requests.post(url, json=payload, headers={"Content-Type": "application/json"})
    return response.json()

def extract_tool_calls(response):
    tool_calls = []
    events = response.get("events", response.get("result", []))
    if isinstance(events, list):
        for event in events:
            parts = event.get("content", {}).get("parts", [])
            for part in parts:
                if "functionCall" in part:
                    tool_calls.append({"tool": part["functionCall"].get("name", ""), "args": part["functionCall"].get("args", {})})
    return tool_calls

def compute_metrics(results):
    total = len(results)
    tool_matches = sum(1 for r in results if r["tool_match"])
    agent_routes = sum(1 for r in results if r["agent_routed"])
    return {"total_cases": total, "tool_precision": tool_matches / total if total > 0 else 0, "routing_accuracy": agent_routes / total if total > 0 else 0}

def run_evaluation(service_url):
    app_name = "support_agent"
    user_id = "eval_user"
    dataset = load_eval_dataset("eval/eval_dataset.json")
    print(f"Running evaluation against: {service_url}")
    results = []
    for i, case in enumerate(dataset):
        session_id = f"eval_session_{i}"
        create_session(service_url, app_name, user_id, session_id)
        response = query_agent(service_url, app_name, user_id, session_id, case["prompt"])
        tool_calls = extract_tool_calls(response)
        tool_match = any(tc["tool"] == case["expected_tool"] for tc in tool_calls)
        agent_routed = len(tool_calls) > 0
        results.append({"prompt": case["prompt"], "expected_tool": case["expected_tool"], "actual_tools": [tc["tool"] for tc in tool_calls], "tool_match": tool_match, "agent_routed": agent_routed})
        status = "PASS" if tool_match else "FAIL"
        print(f"  [{status}] {case['prompt'][:50]}...")
    metrics = compute_metrics(results)
    print(f"Tool Precision: {metrics['tool_precision']:.0%}")
    print(f"Routing Accuracy: {metrics['routing_accuracy']:.0%}")
    return metrics

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python evaluate.py <SERVICE_URL>")
        sys.exit(1)
    service_url = sys.argv[1].rstrip("/")
    run_evaluation(service_url)
Run the evaluation and commit results

Time to put your agent to the test. The evaluation script takes your Cloud Run service URL as an argument and runs all 5 test cases against the live deployment.

  • Run the evaluation script against your deployed agent by running:
python eval/evaluate.py [[SERVICE_URL="your Cloud Run service URL (e.g. https://customer-support-agent-abc123.us-central1.run.app)"]]

What should I see?

The script prints PASS or FAIL for each of the 5 test cases, followed by summary metrics. A well-configured agent should achieve high tool precision and routing accuracy. If some cases show FAIL, the coordinator may occasionally pick a different routing path.

LLM-based agents are non-deterministic. Scores of 80-100% are expected for this system.

Seeing connection errors?

Make sure your service URL does not have a trailing slash. The script strips it automatically, but verify the URL is correct by checking the output from your deployment in Step 4.

If you see a 403 error, your Cloud Run service may require authentication. Check that the service allows unauthenticated access, or pass an identity token.

help me troubleshoot connection errors when running my evaluation script against Cloud Run

  • Create a README.md file in your project root with your evaluation results by running:
touch README.md
  • Open README.md in Vim and paste the following, replacing the placeholder values with your actual metrics:
# Customer Support Multi-Agent System

A production-style multi-agent customer support system built with Google Agent Development Kit (ADK) and deployed to Cloud Run with Terraform.

## Architecture

Coordinator Agent routes to: order_agent (lookup_order), product_agent (get_product_info), escalation_agent (escalate_to_human)

## Tech Stack
- Agent Framework: Google ADK 2.x (Python)
- Model: Gemini 2.5 Flash (via Vertex AI)
- Infrastructure: Terraform
- Deployment: Cloud Run
- Evaluation: Custom trajectory evaluation pipeline

## Evaluation Results
Tool Precision: [results here]
Routing Accuracy: [results here]
Total Cases: 5

Use your actual numbers

Replace [results here] with the percentages your evaluation script printed. For example, Tool Precision: 100%.

  • Save README.md.
  • Commit all your code and push to GitHub by running:
git add .
git commit -m "Add evaluation pipeline and README"
git push origin main
  • Verify the push succeeded. You should see your commit reflected on your GitHub repository page.

Push rejected?

If git push fails with a rejection, make sure you have committed all changes first. Run git status to check for uncommitted files.

help me fix a git push rejection when pushing my evaluation code to GitHub

Your multi-agent system is now deployed, evaluated, and version-controlled. The evaluation pipeline gives you a repeatable way to measure agent quality whenever you make changes to the routing logic or specialist instructions.

Secret mission

Add Iterative Quality Refinement with LoopAgent

Your multi-agent system routes queries correctly, but what about response quality? In production, Google Cloud FDEs add reasoning loops that review and refine agent outputs before they reach the customer. Add a LoopAgent-powered quality check that iteratively improves responses.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Cloud Run scales to zero when idle, so this project incurs no ongoing cost while you're not sending requests.

Resources you used:

  • Cloud Run service (customer-support-agent in us-central1).
  • Artifact Registry repository (agent-images).
  • GCP Service Account (customer-support-agent-sa).
  • GitHub repository (customer-support-agents).
  • Local project folder (customer-support-agents on your Desktop).

Keep everything running

No action needed. Choose this if you're still actively building or experimenting with your agent system.

  • Cloud Run scales to zero automatically when not receiving requests. You will not be charged while the service is idle.
  • Artifact Registry stores your container image. The first 500 MB is free.
  • Your local project folder and GitHub repo remain as-is for future development.

Pause - I'll come back to this later

Cloud Run auto-scales to zero when not in use, so there is nothing to pause manually. Your service stops consuming resources the moment requests stop.

  • No action required for Cloud Run or the service account.
  • Artifact Registry keeps your image stored. You can delete old image tags later if you need to reclaim space.
  • Your local virtual environment can be deactivated by running:
deactivate

When you're ready to resume, reactivate with source .venv/bin/activate.

Delete - I don't want to use this again

Remove all project resources and start fresh. Follow each step below to fully clean up.

  • Delete the Artifact Registry repository and service account by running Terraform destroy from the terraform/ directory:
cd terraform
terraform destroy
  • Confirm the destroy when prompted by typing yes.
  • Delete the Cloud Run service by running:
gcloud run services delete customer-support-agent --region=us-central1
  • Confirm the deletion when prompted.
  • Delete the local project folder. On macOS or Linux:
cd ~/Desktop
rm -rf customer-support-agents
  • On Windows:
cd ~\Desktop
rmdir /s /q customer-support-agents
  • Delete the GitHub repository by navigating to your customer-support-agents repo on GitHub.
  • Click Settings.
  • Scroll down to the Danger Zone section and click Delete this repository.
  • Follow the confirmation prompts to complete the deletion.

Nice Work!

Nice Work!

You just built exactly what Google Cloud Forward Deployed Engineers build for enterprise customers. Your multi-agent customer support system is live on Cloud Run, routing queries intelligently across specialist agents and validating behavior with a real evaluation pipeline.

What you learned:

  • Built a multi-agent system using Google ADK with a coordinator agent delegating to 3 specialist sub_agents, each equipped with their own tools.
  • Provisioned GCP infrastructure with Terraform (APIs, service account, Artifact Registry) and deployed the agent to Cloud Run using the ADK CLI.
  • Created an evaluation pipeline that measures agent trajectory accuracy by comparing predicted tool calls against expected reference behavior.
  • Added iterative quality refinement using ADK's LoopAgent pattern to review and improve agent responses before returning them to the user.

Ready to quiz yourself?