Database come strumento: RAG agentica con ADK, MCP Toolbox e Cloud SQL

1. Introduzione

L'utilità degli agenti AI dipende dalla qualità dei dati a cui possono accedere. La maggior parte dei dati reali si trova nei database e il collegamento degli agenti ai database in genere comporta la scrittura della gestione delle connessioni, della logica delle query e delle pipeline di incorporamento all'interno del codice dell'agente. Ogni agente che ha bisogno dell'accesso al database ripete questo lavoro e ogni modifica della query richiede il redeploy dell'agente.

Questo codelab mostra un approccio diverso. Dichiari gli strumenti di database in un file YAML, incluse query SQL standard, ricerca di somiglianza vettoriale e persino generazione automatica di embedding, e MCP Toolbox for Databases gestisce tutte le operazioni del database come server MCP. Il codice dell'agente rimane minimale: carica gli strumenti e lascia che Gemini decida quale chiamare.

Cosa creerai

Un assistente per bacheche di lavoro intelligenti per "TechJobs", un agente ADK basato su Gemini che aiuta gli sviluppatori a sfogliare gli annunci di lavoro nel settore tecnologico utilizzando filtri standard (ruolo, stack tecnologico) e a scoprire lavori tramite descrizioni in linguaggio naturale come "Voglio un lavoro da remoto che mi permetta di lavorare su chatbot AI". L'agente legge e scrive in un database Cloud SQL PostgreSQL interamente tramite MCP Toolbox for Databases, che gestisce tutto l'accesso al database, inclusa la generazione automatica di embedding per la ricerca vettoriale. Alla fine, sia Toolbox che l'agente vengono eseguiti su Cloud Run.

eb6de681c40990c1.jpeg

Cosa imparerai a fare

  • In che modo lo standard MCP (Model Context Protocol) standardizza l'accesso agli strumenti per gli agenti AI e in che modo MCP Toolbox for Databases lo applica alle operazioni del database
  • Configura MCP Toolbox for Databases come middleware tra un agente ADK e Cloud SQL PostgreSQL
  • Definisci gli strumenti di database in modo dichiarativo in tools.yaml: nessun codice di database nell'agente
  • Crea un agente ADK che carica gli strumenti da un server Toolbox in esecuzione utilizzando ToolboxToolset
  • Genera vector embedding utilizzando la funzione embedding() integrata di Cloud SQL e abilita la ricerca semantica con pgvector
  • Utilizza la funzionalità valueFromParam per l'importazione automatica di vettori nelle operazioni di scrittura
  • Esegui il deployment del server Toolbox e dell'agente ADK su Cloud Run

Prerequisiti

  • Un account Google Cloud con un account di fatturazione di prova
  • Conoscenza di base di Python e SQL
  • L'esperienza pregressa con Cloud Database e ADK sarà utile

2. Configura l'ambiente

Questo passaggio prepara l'ambiente Cloud Shell, configura il progetto Google Cloud e clona il repository di riferimento.

Apri Cloud Shell

Apri Cloud Shell nel browser. Cloud Shell fornisce un ambiente preconfigurato con tutti gli strumenti necessari per questo codelab. Quando richiesto, fai clic su Autorizza.

Poi fai clic su "Visualizza" -> "Terminale" per aprire il terminale.L'interfaccia dovrebbe avere un aspetto simile a questo

86307fac5da2f077.png

Questa sarà la nostra interfaccia principale, con l'IDE in alto e il terminale in basso.

Configurare la directory di lavoro

Crea la directory di lavoro. Tutto il codice che scrivi in questo codelab si trova qui:

mkdir -p ~/build-agent-adk-toolbox-cloudsql
cloudshell workspace ~/build-agent-adk-toolbox-cloudsql && cd ~/build-agent-adk-toolbox-cloudsql

Dopodiché, prepariamo diverse directory per gestire elementi come script di seeding e log.

mkdir -p ~/build-agent-adk-toolbox-cloudsql/scripts
mkdir -p ~/build-agent-adk-toolbox-cloudsql/logs

Configura il progetto Google Cloud

Crea il file .env con le variabili di località:

# For Vertex AI / Gemini API calls
echo "GOOGLE_CLOUD_LOCATION=global" > .env
# For Cloud SQL, Cloud Run, Artifact Registry
echo "REGION=us-central1" >> .env

Per semplificare la configurazione del progetto nel terminale, scarica questo script di configurazione del progetto nella tua directory di lavoro:

curl -sL https://raw.githubusercontent.com/alphinside/cloud-trial-project-setup/main/setup_verify_trial_project.sh -o setup_verify_trial_project.sh

Esegui lo script. Verifica il tuo account di fatturazione di prova, crea un nuovo progetto (o ne convalida uno esistente), salva l'ID progetto in un file .env nella directory corrente e imposta il progetto attivo in gcloud.

bash setup_verify_trial_project.sh && source .env

Lo script:

  1. Verificare di avere un account di fatturazione di prova attivo
  2. Controlla se esiste un progetto in .env (se presente)
  3. Crea un nuovo progetto o riutilizza quello esistente
  4. Collega l'account di fatturazione di prova al tuo progetto
  5. Salva l'ID progetto in .env
  6. Imposta il progetto come progetto gcloud attivo

Verifica che il progetto sia impostato correttamente controllando il testo giallo accanto alla directory di lavoro nel prompt del terminale Cloud Shell. Dovrebbe essere visualizzato l'ID progetto.

dcba35ce1389f313.png

Attiva l'API richiesta

Successivamente, dobbiamo abilitare diverse API per il prodotto con cui interagiremo:

gcloud services enable \
  aiplatform.googleapis.com \
  sqladmin.googleapis.com \
  compute.googleapis.com \
  run.googleapis.com \
  cloudbuild.googleapis.com \
  artifactregistry.googleapis.com
  • API Vertex AI (aiplatform.googleapis.com): l'agente utilizza i modelli Gemini e Toolbox utilizza l'API Embedding per la ricerca vettoriale.
  • API Cloud SQL Admin (sqladmin.googleapis.com): esegui il provisioning e la gestione di un'istanza PostgreSQL.
  • API Compute Engine (compute.googleapis.com): necessaria per creare istanze Cloud SQL.
  • Cloud Run, Cloud Build, Artifact Registry: utilizzati nel passaggio di deployment più avanti in questo codelab

3. Preparazione degli script per l'inizializzazione del database

Questo passaggio avvia la creazione dell'istanza Cloud SQL ed esegue uno script di configurazione automatizzato che attende che l'istanza sia pronta, quindi crea il database, lo inizializza con le offerte di lavoro e genera gli incorporamenti, tutto in un'unica operazione.

Innanzitutto, aggiungiamo la password del database al file .env e ricarichiamolo:

echo "DB_PASSWORD=techjobs-pwd" >> .env
echo "DB_INSTANCE=jobs-instance" >> .env
echo "DB_NAME=jobs_db" >> .env
source .env

Creazione dello script Bash per la creazione dell'istanza e del database

Quindi, crea lo script scripts/setup_database.sh con il seguente comando

mkdir -p ~/build-agent-adk-toolbox-cloudsql/scripts
cloudshell edit scripts/setup_database.sh

Quindi, copia il seguente codice nel file scripts/setup_database.sh

#!/bin/bash
set -e
source .env

echo "================================================"
echo "Database Setup"
echo "================================================"
echo ""

# Step 1: Create Cloud SQL instance
echo "[1/5] Creating Cloud SQL instance..."

# Check if instance already exists
if gcloud sql instances describe "$DB_INSTANCE" --quiet >/dev/null 2>&1; then
    echo "      Instance already exists"
else
    echo "      Creating instance (takes 5-10 minutes)..."
    gcloud sql instances create "$DB_INSTANCE" \
        --database-version=POSTGRES_17 \
        --tier=db-custom-1-3840 \
        --edition=ENTERPRISE \
        --region="$REGION" \
        --root-password="$DB_PASSWORD" \
        --enable-google-ml-integration \
        --database-flags cloudsql.enable_google_ml_integration=on \
        --quiet
fi
echo "      ✓ Instance ready"
echo ""

# Step 2: Verify instance is ready
echo "[2/5] Verifying instance state..."

STATE=$(gcloud sql instances describe "$DB_INSTANCE" --format='value(state)')

if [ "$STATE" != "RUNNABLE" ]; then
    echo "ERROR: Instance not ready (state: $STATE)"
    exit 1
fi
echo "      ✓ Instance is RUNNABLE"
echo ""

# Step 3: Grant IAM permissions
echo "[3/5] Granting Vertex AI permissions..."

SERVICE_ACCOUNT=$(gcloud sql instances describe "$DB_INSTANCE" \
    --format='value(serviceAccountEmailAddress)')

if [ -z "$SERVICE_ACCOUNT" ]; then
    echo "ERROR: Could not retrieve service account"
    exit 1
fi

gcloud projects add-iam-policy-binding "$GOOGLE_CLOUD_PROJECT" \
    --member="serviceAccount:$SERVICE_ACCOUNT" \
    --role="roles/aiplatform.user" \
    --quiet

echo "      ✓ Permissions granted"
echo ""

# Step 4: Create database
echo "[4/5] Creating database..."

# Check if database already exists
if gcloud sql databases describe "$DB_NAME" \
    --instance="$DB_INSTANCE" --quiet >/dev/null 2>&1; then
    echo "      Database already exists"
else
    gcloud sql databases create "$DB_NAME" \
        --instance="$DB_INSTANCE" \
        --quiet
fi

echo "      ✓ Database '$DB_NAME' ready"
echo ""

# Step 5: Seed database and generate embeddings
echo "[5/5] Seeding database and generating embeddings..."

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SETUP_SCRIPT="${SCRIPT_DIR}/setup_jobs_db.py"

if [ ! -f "$SETUP_SCRIPT" ]; then
    echo "ERROR: Setup script not found: $SETUP_SCRIPT"
    exit 1
fi

uv run "$SETUP_SCRIPT"

echo ""
echo "================================================"
echo "Setup complete!"
echo "================================================"
echo ""

Creazione dello script Python per il seeding dei dati

Dopodiché, crea il file Python dello script di seeding scripts/setup_jobs_db.py utilizzando il comando riportato di seguito.

cloudshell edit scripts/setup_jobs_db.py

Quindi, copia il seguente codice nel file scripts/setup_jobs_db.py

import os
import sys
from pathlib import Path
from dotenv import load_dotenv
from google.cloud.sql.connector import Connector
import pg8000
import time

# Load environment variables from .env file
env_path = Path(__file__).parent.parent / '.env'
load_dotenv(env_path)
EMBEDDING_MODEL='gemini-embedding-001'

# Verify required environment variables
required_vars = ['GOOGLE_CLOUD_PROJECT', 'REGION', 'DB_PASSWORD']
missing_vars = [var for var in required_vars if not os.environ.get(var)]

if missing_vars:
    print(f"ERROR: Missing required environment variables: {', '.join(missing_vars)}", file=sys.stderr)
    print(f"", file=sys.stderr)
    print(f"Expected .env file location: {env_path}", file=sys.stderr)
    if not env_path.exists():
        print(f"✗ File not found at that location", file=sys.stderr)
    else:
        print(f"✓ File exists but is missing the variables above", file=sys.stderr)
    print(f"", file=sys.stderr)
    print(f"Make sure your .env file contains:", file=sys.stderr)
    for var in missing_vars:
        print(f"  {var}=<value>", file=sys.stderr)
    sys.exit(1)

# Job listings data (fictional, for tutorial purposes only)
JOBS = [
    ("Senior Backend Engineer", "Stripe", "Backend", "Go, PostgreSQL, gRPC, Kubernetes", "$180-250K/year", "San Francisco, Hybrid", 3,
     "Design and build high-throughput microservices powering payment infrastructure for millions of businesses. Optimize Go services for sub-100ms latency at scale, work with PostgreSQL and Redis for data persistence, and deploy on Kubernetes clusters handling billions of API calls."),
    ("Machine Learning Engineer", "Spotify", "Data/AI", "Python, TensorFlow, BigQuery, Vertex AI", "$170-230K/year", "Stockholm, Remote", 2,
     "Build and deploy ML models for music recommendation and personalization systems serving hundreds of millions of listeners. Design feature pipelines in BigQuery, train models using distributed computing, and serve predictions through real-time APIs processing thousands of requests per second."),
    ("Frontend Engineer", "Vercel", "Frontend", "React, TypeScript, Next.js", "$140-190K/year", "Remote", 4,
     "Build developer-facing dashboard interfaces and deployment tools used by millions of developers worldwide. Create responsive, accessible React components for project management, analytics, and real-time deployment monitoring with a focus on developer experience."),
    ("DevOps Engineer", "Datadog", "DevOps", "Terraform, GCP, Docker, Kubernetes, ArgoCD", "$160-220K/year", "New York, Hybrid", 2,
     "Manage cloud infrastructure powering an observability platform used by thousands of engineering teams. Automate deployment pipelines with ArgoCD, manage multi-cloud Kubernetes clusters, and implement infrastructure-as-code with Terraform across production environments."),
    ("Mobile Engineer (Android)", "Grab", "Mobile", "Kotlin, Jetpack Compose, GraphQL", "$120-170K/year", "Singapore, Hybrid", 3,
     "Develop features for a super-app serving millions of users across Southeast Asia. Build modern Android UIs with Jetpack Compose, integrate GraphQL APIs, and optimize app performance for diverse device capabilities and network conditions."),
    ("Data Engineer", "Airbnb", "Data", "Python, Apache Spark, Airflow, BigQuery", "$160-210K/year", "San Francisco, Hybrid", 2,
     "Build data pipelines that process booking, search, and pricing data for a global travel marketplace. Design ETL workflows with Apache Spark and Airflow, maintain data warehouses in BigQuery, and ensure data quality for analytics and machine learning teams."),
    ("Full Stack Engineer", "Revolut", "Full Stack", "TypeScript, Node.js, React, PostgreSQL", "$130-180K/year", "London, Remote", 5,
     "Build the next generation of financial products making banking accessible to millions of users across 35 countries. Develop real-time trading interfaces with React and WebSockets, build Node.js APIs handling market data streams, and design PostgreSQL schemas for financial transactions."),
    ("Site Reliability Engineer", "Cloudflare", "SRE", "Go, Prometheus, Grafana, GCP, Terraform", "$170-230K/year", "Austin, Hybrid", 2,
     "Ensure 99.99% uptime for a global network handling millions of requests per second. Define SLOs, build monitoring dashboards with Prometheus and Grafana, manage incident response, and automate infrastructure scaling across 300+ data centers worldwide."),
    ("Cloud Architect", "Google Cloud", "Cloud", "GCP, Terraform, Kubernetes, Python", "$200-280K/year", "Seattle, Hybrid", 1,
     "Help enterprises modernize their infrastructure on Google Cloud. Design multi-region architectures, lead migration projects from on-premises to GKE, and build reference implementations using Terraform and Cloud Foundation Toolkit."),
    ("Backend Engineer (Payments)", "Square", "Backend", "Java, Spring Boot, PostgreSQL, Kafka", "$160-220K/year", "San Francisco, Hybrid", 3,
     "Build payment processing systems handling millions of transactions for businesses of all sizes. Design event-driven architectures using Kafka, implement idempotent payment flows with Spring Boot, and ensure PCI-DSS compliance across all services."),
    ("AI Engineer", "Hugging Face", "Data/AI", "Python, LangChain, Vertex AI, FastAPI, PostgreSQL", "$150-210K/year", "Paris, Remote", 2,
     "Build AI-powered tools for the largest open-source ML community. Develop RAG pipelines that index and search model documentation, create conversational agents using LangChain, and deploy AI services with FastAPI on cloud infrastructure."),
    ("Platform Engineer", "Coinbase", "Platform", "Rust, Kubernetes, AWS, Terraform", "$180-250K/year", "Remote", 0,
     "Build the infrastructure platform for a leading cryptocurrency exchange. Develop high-performance matching engines in Rust, manage Kubernetes clusters for microservices, and design CI/CD pipelines that enable rapid feature deployment with zero downtime."),
    ("QA Automation Engineer", "Shopify", "QA", "Python, Selenium, Cypress, Jenkins", "$110-160K/year", "Toronto, Hybrid", 3,
     "Design and maintain automated test suites for a commerce platform powering millions of merchants. Build end-to-end test frameworks with Cypress and Selenium, integrate tests into Jenkins CI pipelines, and establish quality gates that prevent regressions in checkout and payment flows."),
    ("Security Engineer", "CrowdStrike", "Security", "Python, SIEM, Kubernetes, Penetration Testing", "$170-240K/year", "Austin, On-site", 1,
     "Protect enterprise customers from cyber threats on a leading endpoint security platform. Conduct penetration testing, design security monitoring with SIEM tools, implement zero-trust networking in Kubernetes environments, and lead incident response for security events."),
    ("Product Engineer", "GitLab", "Full Stack", "Go, React, PostgreSQL, Redis, GCP", "$140-200K/year", "Remote", 4,
     "Own features end-to-end for an all-in-one DevSecOps platform used by millions of developers. Build Go microservices for CI/CD pipelines, create React frontends for code review and project management, and collaborate with product managers to iterate on user-facing features using data-driven development."),
]


def get_connection():
    """Create a connection to Cloud SQL using the connector."""
    project = os.environ['GOOGLE_CLOUD_PROJECT']
    region = os.environ['REGION']
    password = os.environ['DB_PASSWORD']
    instance = os.environ['DB_INSTANCE']
    database = os.environ['DB_NAME']

    connector = Connector()
    conn = connector.connect(
        f"{project}:{region}:{instance}",
        "pg8000",
        user="postgres",
        password=password,
        db=database
    )
    return conn, connector


def create_schema(cursor):
    """Create extensions and jobs table."""
    cursor.execute("CREATE EXTENSION IF NOT EXISTS google_ml_integration")
    cursor.execute("CREATE EXTENSION IF NOT EXISTS vector")
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS jobs (
            id SERIAL PRIMARY KEY,
            title VARCHAR NOT NULL,
            company VARCHAR NOT NULL,
            role VARCHAR NOT NULL,
            tech_stack VARCHAR NOT NULL,
            salary_range VARCHAR NOT NULL,
            location VARCHAR NOT NULL,
            openings INTEGER NOT NULL,
            description TEXT NOT NULL,
            description_embedding vector(3072)
        )
    """)


def seed_jobs(cursor, conn):
    """Insert job listings."""
    cursor.execute("SELECT COUNT(*) FROM jobs")
    existing_count = cursor.fetchone()[0]

    if existing_count > 0:
        print(f"      {existing_count} jobs already exist, skipping seed")
        return 0

    cursor.executemany("""
        INSERT INTO jobs (title, company, role, tech_stack, salary_range, location, openings, description)
        VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
    """, JOBS)
    conn.commit()
    return len(JOBS)


def generate_embeddings(cursor, conn):
    """Generate embeddings using Cloud SQL's embedding() function."""
    cursor.execute("SELECT COUNT(*) FROM jobs WHERE description_embedding IS NULL")
    null_count = cursor.fetchone()[0]

    if null_count == 0:
        print("      All jobs already have embeddings")
        return 0

    cursor.execute(f"""
        UPDATE jobs
        SET description_embedding = embedding('{EMBEDDING_MODEL}', description)::vector
        WHERE description_embedding IS NULL
    """)
    rows_updated = cursor.rowcount
    conn.commit()
    return rows_updated


def main():
    conn, connector = get_connection()
    cursor = conn.cursor()

    try:
        create_schema(cursor)
        conn.commit()

        seeded = seed_jobs(cursor, conn)
        if seeded > 0:
            print(f"      ✓ Inserted {seeded} jobs")

        # Waiting for vertex role propagation
        time.sleep(60)
        embedded = generate_embeddings(cursor, conn)
        if embedded > 0:
            print(f"      ✓ Generated {embedded} embeddings")

    except Exception as e:
        print(f"ERROR: {e}", file=sys.stderr)
        sys.exit(1)
    finally:
        cursor.close()
        conn.close()
        connector.close()


if __name__ == "__main__":
    main()

Ora passiamo al passaggio successivo.

4. Crea e inizializza il database

Ora gli script sono pronti per essere eseguiti. Avremo bisogno di Python per eseguire lo script preparato, quindi prepariamolo prima.

Configurare il progetto Python

uv è un gestore di progetti e pacchetti Python veloce scritto in Rust ( documentazione di uv). Questo codelab lo utilizza per la velocità e la semplicità di manutenzione del progetto Python

Inizializza un progetto Python e aggiungi le dipendenze richieste:

uv init
uv add cloud-sql-python-connector --extra pg8000
uv add python-dotenv

Tieni presente che qui utilizziamo l'SDK Python cloud-sql-python-connector per inizializzare una connessione sicura con la nostra istanza di database, autenticata utilizzando le credenziali predefinite dell'applicazione.

Esegui lo script di configurazione

Ora possiamo eseguire lo script di configurazione in background e controllare l'output della console che verrà scritto nel file logs/atabase_setup.log utilizzando il comando seguente. Puoi continuare con la sezione successiva mentre aspetti che l'operazione venga completata.

mkdir -p ~/build-agent-adk-toolbox-cloudsql/logs
bash scripts/setup_database.sh > logs/database_setup.log 2>&1 &

Scarica il binario di Toolbox

In questo tutorial utilizzeremo MCP Toolbox, che fortunatamente include un binario precompilato pronto per essere utilizzato nell'ambiente Linux. Ora scarichiamolo in background, perché ci vuole un po'. Esegui questo comando per scaricare il file binario ed esaminare il log di output su logs/toolbox_dl.log . Puoi continuare con la sezione successiva mentre aspetti che l'operazione venga completata.

cd ~/build-agent-adk-toolbox-cloudsql
curl -O https://storage.googleapis.com/mcp-toolbox-for-databases/v1.0.0/linux/amd64/toolbox > logs/toolbox_dl.log 2>&1 &

Informazioni sullo script di configurazione scripts/setup_database.sh

Ora proviamo a capire lo script di configurazione che abbiamo configurato in precedenza. Esegue la seguente procedura

  1. Il primo comando che eseguiamo è gcloud sql instances create con il seguente flag
  • db-custom-1-3840 è il livello Cloud SQL con core dedicati più piccolo (1 vCPU, 3,75 GB di RAM) nella versione ENTERPRISE. Puoi leggere maggiori dettagli qui. Per l'integrazione di Vertex AI ML è necessario un core dedicato. I livelli con core condiviso (db-f1-micro, db-g1-small) non lo supportano.
  • --root-password imposta la password per l'utente postgres predefinito.
  • --enable-google-ml-integration abilita l'integrazione integrata di Cloud SQL con Vertex AI, che consente di chiamare i modelli di embedding direttamente da SQL utilizzando la funzione embedding().
  1. Verifica se l'istanza è già nello stato RUNNABLE
  2. Concedi al service account dell'istanza Cloud SQL l'autorizzazione per chiamare Vertex AI utilizzando il comando gcloud projects add-iam-policy-binding. Questo è necessario per la funzione embedding() integrata che utilizzeremo per inizializzare il database
  3. Creazione del database
  4. Esecuzione dello script di seeding setup_jobs_db.py

Informazioni sullo script seed scripts/setup_jobs_db.py

Ora, passando allo script di seeding, questo script esegue le seguenti operazioni:

  1. Inizializza la connessione all'istanza di database.
  2. Installa due estensioni PostgreSQL:
  • google_ml_integration: fornisce la funzione SQL embedding(), che chiama i modelli di incorporamento di Vertex AI direttamente da SQL. Si tratta di un'estensione a livello di database che rende disponibili le funzioni di ML all'interno di jobs_db. Il flag a livello di istanza (--enable-google-ml-integration) che imposti durante la creazione dell'istanza consente alla VM Cloud SQL di raggiungere Vertex AI. L'estensione rende disponibili le funzioni SQL all'interno di questo database specifico.
  • vector (pgvector): aggiunge il tipo di dati vector e gli operatori di distanza per archiviare ed eseguire query sugli incorporamenti.
  1. Crea la tabella, nota che la colonna description_embedding è vector(3072), ovvero una colonna pgvector che memorizza vettori a 3072 dimensioni.
  2. Inserisci i dati iniziali dei job
  3. Genera i dati di embedding dal campo description e compila description_embedding utilizzando l'integrazione Vertex integrata tramite la funzione embedding()
  • embedding('gemini-embedding-001', description): chiama il modello di embedding Gemini di Vertex AI direttamente da SQL, passando il testo description di ogni annuncio di lavoro. Questa è l'estensione google_ml_integration che hai installato nello script seed.
  • ::vector: esegue il cast dell'array float restituito al tipo vector di pgvector in modo che possa essere archiviato ed eseguito query con operatori di distanza.
  • UPDATE viene eseguito su tutte le 15 righe, generando un embedding a 3072 dimensioni per ogni descrizione del lavoro.

In questo modo verranno preparati i dati iniziali a cui accederà il nostro agente

5. Configura MCP Toolbox for Databases

Questo passaggio introduce MCP Toolbox for Databases, lo configura per la connessione all'istanza Cloud SQL e definisce due strumenti di query SQL standard.

Che cos'è MCP e perché utilizzare Toolbox?

e7b9be2e1c98b4db.png

MCP (Model Context Protocol) è un protocollo aperto che standardizza il modo in cui gli agenti AI scoprono e interagiscono con strumenti esterni. Definisce un modello client-server: l'agente ospita un client MCP e gli strumenti vengono esposti dai server MCP. Qualsiasi client compatibile con MCP può utilizzare qualsiasi server compatibile con MCP. L'agente non ha bisogno di codice di integrazione personalizzato per ogni strumento.

5bf26eeecad2277d.png

MCP Toolbox for Databases è un server MCP open source creato appositamente per l'accesso al database. Senza, dovresti scrivere funzioni Python che aprono connessioni al database, gestiscono i pool di connessioni, creano query con parametri per impedire l'SQL injection, gestiscono gli errori e incorporano tutto questo codice all'interno dell'agente. Ogni agente che ha bisogno dell'accesso al database ripete questa operazione. La modifica di una query comporta il nuovo deployment dell'agente.

Con Toolbox, scrivi un file YAML. Ogni strumento viene mappato a un'istruzione SQL con parametri. Toolbox gestisce il pool di connessioni, le query parametrizzate, l'autenticazione e l'osservabilità. Gli strumenti sono separati dall'agente: aggiorna una query modificando tools.yaml e riavviando Toolbox, senza toccare il codice dell'agente. Gli stessi strumenti funzionano con ADK, LangGraph, LlamaIndex o qualsiasi framework compatibile con MCP.

Scrivere la configurazione degli strumenti

Ora dobbiamo creare un file denominato tools.yaml nell'editor di Cloud Shell per configurare gli strumenti.

cloudshell edit tools.yaml

Il file utilizza YAML multi-documento: ogni blocco separato da --- è una risorsa autonoma. Ogni risorsa ha un kind che dichiara di cosa si tratta (sources per le connessioni al database, tools per le azioni chiamabili dall'agente) e un type che specifica il backend (cloud-sql-postgres per l'origine, postgres-sql per gli strumenti basati su SQL). Uno strumento fa riferimento alla sua origine tramite name, in modo che Toolbox sappia quale pool di connessioni eseguire. Le variabili di ambiente utilizzano la sintassi ${VAR_NAME} e vengono risolte all'avvio.

Ora, copia prima i seguenti script nel file tools.yaml

# tools.yaml

# --- Data Source ---
kind: source
name: jobs-db
type: cloud-sql-postgres
project: ${GOOGLE_CLOUD_PROJECT}
region: ${REGION}
instance: ${DB_INSTANCE}
database: ${DB_NAME}
user: postgres
password: ${DB_PASSWORD}

---

Questo script definisce la seguente risorsa:

  • Origine (jobs-db): indica a Toolbox come connettersi all'istanza Cloud SQL PostgreSQL. Il tipo cloud-sql-postgres utilizza internamente il connettore Cloud SQL, gestendo automaticamente l'autenticazione e le connessioni sicure. I segnaposto ${GOOGLE_CLOUD_PROJECT} , ${REGION} e ${DB_PASSWORD} vengono risolti dalle variabili di ambiente all'avvio.

Successivamente, aggiungi il seguente script sotto il simbolo --- nel file tools.yaml.

# --- Tool 1: Search jobs by role and/or tech stack ---
kind: tool
name: search-jobs
type: postgres-sql
source: jobs-db
description: >-
  Search for job listings by role category and/or tech stack.
  Use this tool when the developer wants to browse listings
  by role (e.g., Backend, Frontend, Data) or find jobs
  using a specific technology. Both parameters accept an
  empty string to match all values.
statement: |
  SELECT title, company, role, tech_stack, salary_range, location, openings
  FROM jobs
  WHERE ($1 = '' OR LOWER(role) = LOWER($1))
  AND ($2 = '' OR LOWER(tech_stack) LIKE '%' || LOWER($2) || '%')
  ORDER BY title
  LIMIT 10
parameters:
  - name: role
    type: string
    description: "The role category to filter by (e.g., 'Backend', 'Frontend', 'Data/AI', 'DevOps'). Use empty string for all roles."
  - name: tech_stack
    type: string
    description: "A technology to search for in the tech stack (partial match, e.g., 'Python', 'Kubernetes'). Use empty string for all tech stacks."

---

# --- Tool 2: Get full details for a specific job ---
kind: tool
name: get-job-details
type: postgres-sql
source: jobs-db
description: >-
  Get full details for a specific job listing including its description,
  salary range, location, and number of openings. Use this tool when the
  developer asks about a particular job by title or company.
statement: |
  SELECT title, company, role, tech_stack, salary_range, location, openings, description
  FROM jobs
  WHERE LOWER(title) LIKE '%' || LOWER($1) || '%'
  OR LOWER(company) LIKE '%' || LOWER($1) || '%'
parameters:
  - name: search_term
    type: string
    description: "The job title or company name to look up (partial match supported)."

---

Questo script definisce la seguente risorsa:

  • Strumenti 1 e 2 (search-jobs, get-job-details): strumenti di query SQL standard. Ognuna mappa un nome di strumento (ciò che vede l'agente) a un'istruzione SQL parametrizzata (ciò che esegue il database). I parametri utilizzano i segnaposto posizionali $1 e $2. Toolbox li esegue come istruzioni preparate, il che impedisce l'SQL injection.

Continuiamo aggiungendo il seguente script sotto il simbolo --- nel file tools.yaml

# --- Embedding Model ---
kind: embeddingModel
name: gemini-embedding
type: gemini
model: gemini-embedding-001
project: ${GOOGLE_CLOUD_PROJECT}
location: ${GOOGLE_CLOUD_LOCATION}
dimension: 3072

---

Questo script definisce la seguente risorsa:

  • Modello di embedding (gemini-embedding): configura Toolbox per chiamare il modello gemini-embedding-001 di Gemini per generare embedding di testo a 3072 dimensioni. Toolbox utilizza le Credenziali predefinite dell'applicazione (ADC) per l'autenticazione. Non è necessaria alcuna chiave API in Cloud Shell o Cloud Run. Tieni presente che questo dimension configurato qui deve essere lo stesso di quello configurato in precedenza per inizializzare il database

Continuiamo aggiungendo il seguente script sotto il simbolo --- nel file tools.yaml

# --- Tool 3: Semantic search by description ---
kind: tool
name: search-jobs-by-description
type: postgres-sql
source: jobs-db
description: >-
  Find jobs that match a natural language description of what the developer
  is looking for. Use this tool when the developer describes their ideal job
  using interests, work style, career goals, or project type rather than a
  specific role or tech stack. Examples: "I want to work on AI chatbots,"
  "a remote job at a fintech startup," "something involving infrastructure
  and reliability."
statement: |
  SELECT title, company, role, tech_stack, salary_range, location, description
  FROM jobs
  WHERE description_embedding IS NOT NULL
  ORDER BY description_embedding <=> $1
  LIMIT 5
parameters:
  - name: search_query
    type: string
    description: "A natural language description of the kind of job the developer is looking for."
    embeddedBy: gemini-embedding

---

Questo script definisce la seguente risorsa:

  • Strumento 3 (search-jobs-by-description): uno strumento di ricerca vettoriale. Il parametro search_query ha embeddedBy: gemini-embedding, che indica a Strumenti amministrativi di intercettare il testo non elaborato, inviarlo al modello di embedding e utilizzare il vettore risultante nell'istruzione SQL. L'operatore <=> è la distanza del coseno di pgvector: valori più piccoli indicano descrizioni più simili.

Infine, aggiungi l'ultimo strumento sotto il simbolo --- nel file tools.yaml.

# --- Tool 4: Add a new job listing with automatic embedding ---
kind: tool
name: add-job
type: postgres-sql
source: jobs-db
description: >-
  Add a new job listing to the platform. Use this tool when a user asks
  to post a job that is not currently listed.
statement: |
  INSERT INTO jobs (title, company, role, tech_stack, salary_range, location, openings, description, description_embedding)
  VALUES ($1, $2, $3, $4, $5, $6, CAST($7 AS INTEGER), $8, $9)
  RETURNING title, company
parameters:
  - name: title
    type: string
    description: "The job title (e.g., 'Senior Backend Engineer')."
  - name: company
    type: string
    description: "The company name (e.g., 'Stripe', 'Spotify')."
  - name: role
    type: string
    description: "The role category (e.g., 'Backend', 'Frontend', 'Data/AI', 'DevOps')."
  - name: tech_stack
    type: string
    description: "Comma-separated list of technologies (e.g., 'Python, FastAPI, GCP')."
  - name: salary_range
    type: string
    description: "The salary range (e.g., '$150-200K/year')."
  - name: location
    type: string
    description: "Work location and arrangement (e.g., 'Remote')."
  - name: openings
    type: string
    description: "The number of open positions."
  - name: description
    type: string
    description: "A short description of the job (2-3 sentences)."
  - name: description_vector
    type: string
    description: "Auto-generated embedding vector for the job description."
    valueFromParam: description
    embeddedBy: gemini-embedding

Questo script definisce la seguente risorsa:

  • Tool 4 (add-job): mostra l'importazione di vettori. Il parametro description_vector ha due campi speciali:
  • valueFromParam: description: Toolbox copia il valore dal parametro description in questo. Il LLM non vede mai questo parametro.
  • embeddedBy: gemini-embedding: Toolbox incorpora il testo copiato in un vettore prima di passarlo a SQL.

Il risultato è che una chiamata allo strumento memorizza sia il testo della descrizione non elaborato sia il suo incorporamento vettoriale, senza che l'agente sappia nulla degli incorporamenti.

Il formato YAML multi-documento separa ogni risorsa con ---. Ogni documento ha campi kind, name e type che ne definiscono il contenuto. In sintesi, abbiamo già configurato tutti gli elementi seguenti:

  • Definisci il database di origine
  • Definisci gli strumenti ( strumento 1 e 2) per eseguire query sul database con il filtro standard
  • Definisci il modello di embedding
  • Definisci lo strumento per eseguire la ricerca vettoriale ( strumento 3) nel database
  • Definisci lo strumento per l'importazione dei dati vettoriali ( strumento 4) nel database

6. Esecuzione del server MCP Toolbox

Nel passaggio precedente, abbiamo già impostato la configurazione necessaria per la nostra casella degli strumenti MCP. Ora siamo pronti per eseguire il server.

Verificare i dati iniziali

Prima di avviare Toolbox, verifichiamo che la configurazione del database sia stata completata. Crea uno script Python scripts/verify_database.py utilizzando il seguente comando

cloudshell edit scripts/verify_seed.py

Quindi, copia il seguente codice nel file scripts/verify_seed.py

#!/usr/bin/env python3
"""Verify the database has 15 jobs with embeddings."""

import os
import sys
from pathlib import Path
from dotenv import load_dotenv
from google.cloud.sql.connector import Connector
import pg8000

# Load environment variables
env_path = Path(__file__).parent.parent / '.env'
load_dotenv(env_path)

# Verify required environment variables
required_vars = ['GOOGLE_CLOUD_PROJECT', 'REGION', 'DB_PASSWORD', 'DB_INSTANCE', 'DB_NAME']
missing_vars = [var for var in required_vars if not os.environ.get(var)]

if missing_vars:
    print(f"ERROR: Missing environment variables: {', '.join(missing_vars)}", file=sys.stderr)
    sys.exit(1)


def verify_database():
    """Check that 15 jobs exist with embeddings."""
    connector = Connector()

    try:
        project = os.environ['GOOGLE_CLOUD_PROJECT']
        region = os.environ['REGION']
        password = os.environ['DB_PASSWORD']
        instance = os.environ['DB_INSTANCE']
        database = os.environ['DB_NAME']

        conn = connector.connect(
            f"{project}:{region}:{instance}",
            "pg8000",
            user="postgres",
            password=password,
            db=database
        )
        cursor = conn.cursor()

        # Count jobs and embeddings
        cursor.execute("SELECT COUNT(*) FROM jobs")
        job_count = cursor.fetchone()[0]

        cursor.execute("SELECT COUNT(*) FROM jobs WHERE description_embedding IS NOT NULL")
        embedding_count = cursor.fetchone()[0]

        print(f"Jobs: {job_count}/15")
        print(f"Embeddings: {embedding_count}/15")

        cursor.close()
        conn.close()

        if job_count == 15 and embedding_count == 15:
            print("\n✓ Database ready!")
            return True
        else:
            print("\n✗ Database not ready")
            return False

    except Exception as e:
        print(f"\nERROR: {e}", file=sys.stderr)
        return False
    finally:
        connector.close()


if __name__ == "__main__":
    success = verify_database()
    sys.exit(0 if success else 1)

Questo script controllerà il numero di dati dei post di lavoro e la loro incorporazione. Esegui lo script utilizzando questo comando

uv run scripts/verify_seed.py

Se vedi il seguente output del terminale, significa che i dati sono pronti

Jobs: 15/15
Embeddings: 15/15

✓ Database ready!

Avviare il server di Strumenti

Nel passaggio di configurazione precedente, abbiamo già scaricato l'eseguibile toolbox. Assicurati che questo file binario esista e sia stato scaricato correttamente. In caso contrario, scaricalo e attendi il completamento.

cd ~/build-agent-adk-toolbox-cloudsql
if [ ! -f toolbox ]; then
  curl -O https://storage.googleapis.com/mcp-toolbox-for-databases/v1.0.0/linux/amd64/toolbox
fi
chmod +x toolbox

Dovremo esporre le nostre variabili .env al processo secondario eseguito dalla casella degli strumenti MCP. Esegui il comando seguente per avviare il server della toolbox e registrare l'output della console nel file logs/mcp_toolbox.log

set -a; source .env; set +a
./toolbox --config tools.yaml --enable-api > logs/mcp_toolbox.log 2>&1 &

Dovresti vedere un output nel file logs/mcp_toolbox.log che conferma che il server è pronto, come mostrato di seguito:

... INFO "Initialized 1 sources: jobs-db"
... INFO "Initialized 0 authServices: "
... INFO "Using Vertex AI backend for Gemini embedding" 
... INFO "Initialized 1 embeddingModels: gemini-embedding" 
... INFO "Initialized 4 tools: add-job, search-jobs, get-job-details, search-jobs-by-description" 
...
... INFO "Server ready to serve!"

Verificare gli strumenti

Esegui una query sull'API Toolbox per elencare tutti gli strumenti registrati:

curl -s http://localhost:5000/api/toolset | uv run -m json.tool

Dovresti visualizzare gli strumenti con le relative descrizioni e i parametri. Come mostrato di seguito

...
       "search-jobs-by-description": {
            "description": "Find jobs that match a natural language description of what the developer is looking for. Use this tool when the developer describes their ideal job using interests, work style, career goals, or project type rather than a specific role or tech stack. Examples: \"I want to work on AI chatbots,\" \"a remote job at a fintech startup,\" \"something involving infrastructure and reliability.\"",
            "parameters": [
                {
                    "name": "search_query",
                    "type": "string",
                    "required": true,
                    "description": "A natural language description of the kind of job the developer is looking for.",
                    "authSources": []
                }
            ],
            "authRequired": []
        }
...

Testa direttamente lo strumento search-jobs:

curl -s -X POST http://localhost:5000/api/tool/search-jobs/invoke \
  -H "Content-Type: application/json" \
  -d '{"role": "Backend", "tech_stack": ""}' | jq '.result | fromjson'

La risposta deve contenere i due lavori di ingegneria backend dei dati iniziali.

[
  {
    "title": "Backend Engineer (Payments)",
    "company": "Square",
    "role": "Backend",
    "tech_stack": "Java, Spring Boot, PostgreSQL, Kafka",
    "salary_range": "$160-220K/year",
    "location": "San Francisco, Hybrid",
    "openings": 3
  },
  {
    "title": "Senior Backend Engineer",
    "company": "Stripe",
    "role": "Backend",
    "tech_stack": "Go, PostgreSQL, gRPC, Kubernetes",
    "salary_range": "$180-250K/year",
    "location": "San Francisco, Hybrid",
    "openings": 3
  }
]

7. Crea l'agente ADK

Ora utilizzeremo ADK in Python per questo progetto. Aggiungiamo le dipendenze richieste:

uv add google-adk==1.29.0 toolbox-adk==1.0.0
  • google-adk: Agent Development Kit di Google, incluso l'SDK Gemini
  • toolbox-adk: integrazione di ADK per MCP Toolbox for Databases.

Creare la struttura di directory dell'agente

L'ADK prevede un layout di cartelle specifico: una directory denominata come l'agente contenente __init__.py, agent.py e .env. Per facilitare questa operazione, è stato integrato un comando per stabilire rapidamente la struttura:

uv run adk create jobs_agent \
    --model gemini-2.5-flash \
    --project ${GOOGLE_CLOUD_PROJECT} \
    --region ${GOOGLE_CLOUD_LOCATION}

La directory dovrebbe ora avere il seguente aspetto:

build-agent-adk-toolbox-cloudsql/
├── jobs_agent/
│   ├── __init__.py
│   ├── agent.py
│   └── .env
├── logs
├── scripts
└── ...

Successivamente, dovremo integrare l'agente ADK nel server Toolbox in esecuzione e testare tutti e quattro gli strumenti: query standard, ricerca semantica e importazione vettoriale. Il codice dell'agente è minimo: tutta la logica del database si trova in tools.yaml.

Configurare l'ambiente dell'agente

ADK legge GOOGLE_GENAI_USE_VERTEXAI, GOOGLE_CLOUD_PROJECT e GOOGLE_CLOUD_LOCATION dall'ambiente shell, che hai già impostato nel passaggio precedente. L'unica variabile specifica dell'agente è TOOLBOX_URL. Aggiungila al file .env dell'agente:

echo -e "\nTOOLBOX_URL=http://127.0.0.1:5000" >> jobs_agent/.env

Aggiornare il modulo dell'agente

Apri jobs_agent/agent.py nell'editor di Cloud Shell

cloudshell edit jobs_agent/agent.py

e sovrascrivi i contenuti con il seguente codice:

# jobs_agent/agent.py
import os

from google.adk.agents import LlmAgent
from toolbox_adk import ToolboxToolset

TOOLBOX_URL = os.environ.get("TOOLBOX_URL", "http://127.0.0.1:5000")

toolbox = ToolboxToolset(TOOLBOX_URL)

root_agent = LlmAgent(
    name="jobs_agent",
    model="gemini-2.5-flash",
    instruction="""You are a helpful assistant at "TechJobs," a tech job listing platform.

Your job:
- Help developers browse job listings by role or tech stack.
- Provide full details about specific positions, including salary range and number of openings.
- Recommend jobs based on natural language descriptions of what the developer is looking for.
- Add new job listings to the platform when asked.

When a developer asks about a specific job by title or company, use the get-job-details tool.
When a developer asks for a specific role category or tech stack, use the search-jobs tool.
When a developer describes what kind of job they want — by interest area, work style,
career goals, or project type — use the search-jobs-by-description tool for semantic search.
When in doubt between search-jobs and search-jobs-by-description, prefer
search-jobs-by-description — it searches job descriptions and finds more relevant matches.

If a position has no openings (openings is 0), let the developer know
and suggest similar alternatives from the search results.

Be conversational, knowledgeable, and concise.""",
    tools=[toolbox],
)

Tieni presente che qui non è presente alcun codice di database: ToolboxToolset si connette al server Toolbox all'avvio e carica tutti gli strumenti disponibili. L'agente chiama gli strumenti per nome; Toolbox traduce queste chiamate in query SQL su Cloud SQL.

La variabile di ambiente TOOLBOX_URL è impostata su http://127.0.0.1:5000 per lo sviluppo locale. Quando esegui il deployment su Cloud Run in un secondo momento, esegui l'override con l'URL Cloud Run del servizio Toolbox, senza bisogno di modifiche al codice.

L'istruzione attualmente fa riferimento solo ai due strumenti standard (search-jobs e get-job-details). La espanderai nel passaggio successivo quando aggiungerai gli strumenti di ricerca semantica e importazione.

Testare l'agente

Avvia l'interfaccia utente di sviluppo ADK:

cd ~/build-agent-adk-toolbox-cloudsql
uv run adk web --allow_origins "regex:https://.*\.cloudshell\.dev"

Apri l'URL mostrato nel terminale (in genere http://localhost:8000) utilizzando la funzionalità Anteprima web di Cloud Shell o Ctrl + clic sull'URL mostrato nel terminale. Seleziona jobs_agent dal menu a discesa degli agenti nell'angolo in alto a sinistra.

Testare le query standard

Prova questi prompt per verificare gli strumenti SQL standard:

What backend engineering jobs do you have?
Any jobs using Kubernetes?
Tell me about the Cloud Architect position

93ac33e7f73aa0b9.png 240c53376042a916.png

Prova descrizioni in linguaggio naturale che non corrispondono a un ruolo o a uno stack tecnologico specifico:

I want a remote job where I can work on AI and machine learning
Find me something in fintech with good work-life balance
I'm interested in infrastructure and reliability engineering

L'agente cercherà di scegliere lo strumento giusto in base al tipo di query: i filtri strutturati vengono elaborati tramite search-jobs, mentre le descrizioni in linguaggio naturale tramite search-jobs-by-description.

b0ea629f5c9b4c26.png

Testare l'importazione di vettori

Chiedi all'agente di aggiungere un nuovo job:

Add a new job: 'Robotics Software Engineer' at Boston Dynamics, role Robotics, tech stack: Python, C++, ROS, Computer Vision, salary $160-230K/year, location Waltham MA, Hybrid, 2 openings. Description: Design and implement autonomous navigation and manipulation algorithms for next-generation robots. Work on perception pipelines using computer vision and lidar, develop motion planning software in C++ and Python, and test systems on real hardware in warehouse and logistics environments.

c601a7a9bc0a705b.png

Ora prova a cercarlo:

Find me jobs involving autonomous systems and working with physical hardware

L'incorporamento è stato generato automaticamente durante l'inserimento, senza bisogno di un passaggio separato.

5a3d8e6f523dc18b.png

Ora hai già un'applicazione RAG agentica completamente funzionante che utilizza ADK, MCP Toolbox e CloudSQL. Complimenti! Facciamo un ulteriore passo avanti per eseguire il deployment di queste app su Cloud Run.

Ora, interrompiamo l'interfaccia utente per sviluppatori terminando il processo premendo Ctrl+C due volte prima di procedere.

8. Esegui il deployment in Cloud Run

L'agente e Toolbox funzionano localmente. Questo passaggio esegue il deployment di entrambi come servizi Cloud Run, in modo che siano accessibili su internet. Il servizio Toolbox viene eseguito come server MCP su Cloud Run e il servizio agente si connette a questo server.

Preparare la cassetta degli attrezzi per il deployment

Crea una directory di deployment per il servizio Toolbox:

cd ~/build-agent-adk-toolbox-cloudsql
mkdir -p deploy-toolbox
cp toolbox tools.yaml deploy-toolbox/

Crea il Dockerfile per la toolbox. Apri deploy-toolbox/Dockerfile nell'editor di Cloud Shell:

cloudshell edit deploy-toolbox/Dockerfile

e copia il seguente script

# deploy-toolbox/Dockerfile
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY toolbox tools.yaml ./
RUN chmod +x toolbox
EXPOSE 8080
CMD ["./toolbox", "--config", "tools.yaml", "--enable-api", "--address", "0.0.0.0", "--port", "8080"]

Il binario Toolbox e tools.yaml sono inclusi in un'immagine Debian minima. Cloud Run instrada il traffico alla porta 8080.

Esegui il deployment del servizio Strumenti

cd ~/build-agent-adk-toolbox-cloudsql
gcloud run deploy toolbox-service \
  --source deploy-toolbox/ \
  --region $REGION \
  --set-env-vars "DB_PASSWORD=$DB_PASSWORD,DB_INSTANCE=$DB_INSTANCE,DB_NAME=$DB_NAME,GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT,REGION=$REGION,GOOGLE_CLOUD_LOCATION=$GOOGLE_CLOUD_LOCATION" \
  --allow-unauthenticated \
  --quiet > logs/deploy_toolbox.log 2>&1 &

Questo comando invia l'origine a Cloud Build, crea un'immagine container, la invia ad Artifact Registry ed esegue il deployment su Cloud Run. Ci vorranno alcuni minuti. Possiamo esaminare il log del processo di deployment nel file logs/deploy_toolbox.log.

Preparare l'agente per il deployment

Mentre viene creata la casella degli strumenti, configura i file di deployment dell'agente.

Crea un Dockerfile nella radice del progetto. Apri Dockerfile nell'editor di Cloud Shell:

cloudshell edit Dockerfile

Quindi, copia i seguenti contenuti

# Dockerfile
FROM ghcr.io/astral-sh/uv:python3.12-trixie-slim
WORKDIR /app
COPY pyproject.toml ./
COPY uv.lock ./
RUN uv sync --no-dev
COPY jobs_agent/ jobs_agent/
EXPOSE 8080
CMD ["uv", "run", "adk", "web", "--host", "0.0.0.0", "--port", "8080"]

Questo Dockerfile utilizza ghcr.io/astral-sh/uv come immagine di base, che include sia Python sia uv preinstallati. Non è necessario installare uv separatamente tramite pip.

Crea un file .dockerignore per escludere i file non necessari dall'immagine container:

cloudshell edit .dockerignore

Poi copia il seguente script

# .dockerignore
.venv/
__pycache__/
*.pyc
.env
jobs_agent/.env
toolbox
tools.yaml
seed.sql
deploy-toolbox/

Esegui il deployment del servizio agente

Attendi il completamento del deployment di Toolbox. Controlla di nuovo la procedura di deployment su logs/deploy_toolbox.log per verificarla. Quindi, recupera il relativo URL Cloud Run utilizzando il comando seguente

TOOLBOX_URL=$(gcloud run services describe toolbox-service \
  --region=$REGION \
  --format='value(status.url)')
echo "Toolbox URL: $TOOLBOX_URL"

Vedrai un output simile a questo

Toolbox URL: https://toolbox-service-xxxxxx-xx.a.run.app

Quindi, verifichiamo che Toolbox di cui è stato eseguito il deployment funzioni:

curl -s "$TOOLBOX_URL/api/toolset" | python3 -m json.tool | head -5

Se l'output viene visualizzato come in questo esempio, il deployment è già riuscito

{
    "serverVersion": "1.0.0+binary.linux.amd64.c5524d3",
    "tools": {
        "add-job": {
            "description": "Add a new job listing to the platform. Use this tool when a user asks to post a job that is not currently listed.",

Ora eseguiamo il deployment dell'agente, passando l'URL di Toolbox come variabile di ambiente:

cd ~/build-agent-adk-toolbox-cloudsql
gcloud run deploy jobs-agent \
  --source . \
  --region $REGION \
  --set-env-vars "TOOLBOX_URL=$TOOLBOX_URL,GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT,GOOGLE_CLOUD_LOCATION=$GOOGLE_CLOUD_LOCATION,GOOGLE_GENAI_USE_VERTEXAI=TRUE" \
  --allow-unauthenticated \
  --quiet

Il codice dell'agente legge TOOLBOX_URL dall'ambiente (che hai configurato in precedenza). A livello locale punta a http://127.0.0.1:5000; su Cloud Run punta all'URL del servizio Toolbox. Non sono necessarie modifiche al codice.

Testare l'agente di cui è stato eseguito il deployment

Recupera l'URL Cloud Run dell'agente:

AGENT_URL=$(gcloud run services describe jobs-agent \
  --region=$REGION \
  --format='value(status.url)')
echo "Agent URL: $AGENT_URL"

Apri l'URL nel browser. Viene caricata l'interfaccia utente di sviluppo dell'ADK, la stessa che hai utilizzato localmente, ora in esecuzione su Cloud Run.

Seleziona jobs_agent dal menu a discesa e testa:

What backend engineering jobs do you have?
I want a remote job working on AI and machine learning

Entrambe le query funzionano tramite i servizi di cui è stato eseguito il deployment: l'agente su Cloud Run chiama Toolbox su Cloud Run, che esegue query su Cloud SQL.

9. Congratulazioni / Pulizia

Hai creato e implementato un assistente per bacheche intelligenti di annunci di lavoro che utilizza MCP Toolbox for Databases per collegare un agente ADK e Cloud SQL PostgreSQL, sia con query SQL standard sia con la ricerca semantica di vettori.

Che cosa hai imparato

  • In che modo MCP standardizza l'accesso agli strumenti per gli agenti AI e in che modo MCP Toolbox for Databases lo applica in modo specifico alle operazioni di database, sostituendo il codice di database personalizzato con la configurazione YAML dichiarativa
  • Come configurare Cloud SQL PostgreSQL come origine dati di Toolbox utilizzando il tipo di origine cloud-sql-postgres
  • Come definire strumenti di query SQL standard con istruzioni parametrizzate che impediscono SQL injection
  • Come attivare la ricerca vettoriale utilizzando pgvector e gemini-embedding-001, con il parametro embeddedBy per l'embedding automatico delle query
  • In che modo valueFromParam consente l'inserimento automatico di vettori: il modello LLM fornisce una descrizione testuale e Toolbox copia, incorpora e archivia il vettore insieme al testo in modo invisibile
  • In che modo ToolboxToolset di ADK carica gli strumenti da un server Toolbox in esecuzione, mantenendo il codice dell'agente minimo e la logica del database completamente disaccoppiata
  • Come eseguire il deployment del server Toolbox MCP e dell'agente ADK su Cloud Run come servizi separati

Pulizia

Per evitare che al tuo account Google Cloud vengano addebitati costi relativi alle risorse create in questo codelab, puoi eliminare le singole risorse o l'intero progetto.

Il modo più semplice per liberare spazio è eliminare il progetto. Vengono rimosse tutte le risorse associate al progetto.

gcloud projects delete $GOOGLE_CLOUD_PROJECT

Opzione 2: elimina singole risorse

Se vuoi conservare il progetto, ma rimuovere solo le risorse create in questo codelab:

gcloud run services delete jobs-agent --region=$REGION --quiet
gcloud run services delete toolbox-service --region=$REGION --quiet
gcloud sql instances delete jobs-instance --quiet
gcloud artifacts repositories delete cloud-run-source-deploy --location=$REGION --quiet 2>/dev/null