מסד נתונים ככלי: RAG מבוסס-סוכן עם ADK, ערכת הכלים MCP ו-Cloud SQL

1. מבוא

התועלת של סוכני AI תלויה בנתונים שהם יכולים לגשת אליהם. רוב הנתונים בעולם האמיתי נמצאים במסדי נתונים – ובדרך כלל, כדי לקשר סוכנים למסדי נתונים צריך לכתוב ניהול חיבורים, לוגיקה של שאילתות וצינורות הטמעה בתוך קוד הסוכן. כל נציג שזקוק לגישה למסד הנתונים צריך לחזור על הפעולה הזו, וכל שינוי בשאילתה מחייב פריסה מחדש של הנציג.

ב-Codelab הזה נציג גישה שונה. אתם מגדירים את כלי מסד הנתונים בקובץ YAML – שאילתות SQL סטנדרטיות, חיפוש דמיון וקטורי ואפילו יצירה אוטומטית של הטמעה – ו-MCP Toolbox for Databases מטפל בכל פעולות מסד הנתונים כשרת MCP. קוד הסוכן נשאר מינימלי: טוענים את הכלים ומאפשרים ל-Gemini להחליט לאיזה כלי להתקשר.

מה תפַתחו

מלצר וירטואלי למסעדה בשם "Foodie Finds" – סוכן ADK שמבוסס על Gemini ועוזר לסועדים לעיין בתפריט של המסעדה באמצעות מסננים רגילים (קטגוריה, סוג מטבח) ולגלות מנות באמצעות תיאורים בשפה טבעית כמו "אני רוצה משהו חריף וצמחוני". הסוכן קורא מתוך מסד נתונים של Cloud SQL PostgreSQL וכותב לתוכו אך ורק דרך MCP Toolbox for Databases, שמטפל בכל הגישה למסד הנתונים – כולל יצירה אוטומטית של הטמעה לחיפוש וקטורי. בסוף התהליך, גם ה-Toolbox וגם הסוכן יפעלו ב-Cloud Run.

53a7afb736a8fc8c.jpeg

מה תלמדו

  • איך תקן MCP‏ (Model Context Protocol) מאפשר גישה סטנדרטית לכלים לסוכני AI, ואיך MCP Toolbox for Databases מיישם את זה בפעולות שקשורות למסדי נתונים
  • הגדרת MCP Toolbox for Databases כתוכנת ביניים בין סוכן ADK לבין Cloud SQL PostgreSQL
  • הגדרת כלי מסד נתונים באופן הצהרתי ב-tools.yaml – אין קוד מסד נתונים בסוכן
  • יצירת סוכן ADK שמעמיס כלים משרת Toolbox פעיל באמצעות ToolboxToolset
  • יצירת הטמעות וקטורים באמצעות הפונקציה המובנית embedding() של Cloud SQL והפעלת חיפוש סמנטי באמצעות pgvector
  • שימוש בתכונה valueFromParam להוספה אוטומטית של וקטורים בפעולות כתיבה
  • פריסה של השרת Toolbox וסוכן ה-ADK ב-Cloud Run

דרישות מוקדמות

  • חשבון Google Cloud עם חשבון לחיוב בתקופת ניסיון
  • היכרות בסיסית עם Python ו-SQL
  • ניסיון קודם עם Cloud Database ו-ADK יעזור לכם

2. הגדרת הסביבה

בשלב הזה מכינים את סביבת Cloud Shell, מגדירים את הפרויקט ב-Google Cloud ומשכפלים את מאגר ההפניות.

פתיחת Cloud Shell

פותחים את Cloud Shell בדפדפן. ‫Cloud Shell מספקת סביבה שהוגדרה מראש עם כל הכלים שדרושים ל-Codelab הזה. כשמופיעה בקשה, לוחצים על Authorize.

אחר כך לוחצים על View (תצוגה) -> Terminal (טרמינל) כדי לפתוח את הטרמינל.הממשק אמור להיראות בערך כך:

86307fac5da2f077.png

זה יהיה הממשק הראשי שלנו, סביבת הפיתוח המשולבת (IDE) בחלק העליון והטרמינל בחלק התחתון

הגדרת ספריית העבודה

יוצרים את ספריית העבודה. כל הקוד שכותבים ב-Codelab הזה נמצא כאן:

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

אחרי זה, נכין כמה ספריות לניהול דברים כמו סקריפטים של הפעלה ויומנים

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

הגדרת הפרויקט ב-Google Cloud

יוצרים את קובץ .env עם משתני המיקום:

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

כדי לפשט את הגדרת הפרויקט במסוף, מורידים את סקריפט הגדרת הפרויקט הזה לספריית העבודה:

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

מריצים את הסקריפט. הוא מאמת את החשבון לחיוב של תקופת הניסיון, יוצר פרויקט חדש (או מאמת פרויקט קיים), שומר את מזהה הפרויקט בקובץ .env בספרייה הנוכחית ומגדיר את הפרויקט הפעיל ב-gcloud.

bash setup_verify_trial_project.sh && source .env

הסקריפט:

  1. אימות שיש לכם חשבון לחיוב עם תקופת ניסיון פעילה
  2. בודקים אם יש פרויקט קיים ב-.env (אם יש)
  3. יוצרים פרויקט חדש או משתמשים בפרויקט קיים
  4. קישור החשבון לחיוב בתקופת הניסיון לפרויקט
  5. שומרים את מזהה הפרויקט ב-.env
  6. הגדרת הפרויקט כפרויקט פעיל ב-gcloud

כדי לוודא שהפרויקט מוגדר בצורה נכונה, בודקים את הטקסט הצהוב שליד ספריית העבודה בהנחיית הטרמינל של Cloud Shell. מזהה הפרויקט צריך להופיע בו.

dcba35ce1389f313.png

הפעלת API נדרש

לאחר מכן, צריך להפעיל כמה ממשקי API למוצר שאיתו תהיה אינטראקציה:

gcloud services enable \
  aiplatform.googleapis.com \
  sqladmin.googleapis.com \
  compute.googleapis.com \
  run.googleapis.com \
  cloudbuild.googleapis.com \
  artifactregistry.googleapis.com
  • Vertex AI API (aiplatform.googleapis.com) – הסוכן משתמש במודלים של Gemini, וכלי העזר משתמש ב-Embedding API לחיפוש וקטורי.
  • Cloud SQL Admin API ‏ (sqladmin.googleapis.com) – אתם מקצים ומנהלים מופע PostgreSQL.
  • Compute Engine API ‏ (compute.googleapis.com) – נדרש ליצירת מופעים של Cloud SQL.
  • Cloud Run, ‏ Cloud Build, ‏ Artifact Registry – השירותים האלה ישמשו בשלב הפריסה בהמשך שיעור ה-Codelab הזה

3. הכנת סקריפטים לאתחול מסד נתונים

בשלב הזה מתחילה יצירה של מופע Cloud SQL ומופעל סקריפט הגדרה אוטומטי שממתין עד שהמופע יהיה מוכן, ואז יוצר את מסד הנתונים, מאכלס אותו בפרטי משרות ויוצר הטבעות – והכול בפעולה אחת.

קודם צריך להוסיף את הסיסמה של מסד הנתונים לקובץ .env ולטעון אותו מחדש:

echo "DB_PASSWORD=restaurant-pwd" >> .env
echo "DB_INSTANCE=restaurant-instance" >> .env
echo "DB_NAME=restaurant_db" >> .env
source .env

יצירת סקריפט Bash ליצירת מכונה ומסד נתונים

לאחר מכן, יוצרים את הסקריפט scripts/setup_database.sh באמצעות הפקודה הבאה

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

לאחר מכן, מעתיקים את הקוד הבא לקובץ 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_restaurant_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 ""

יצירת סקריפט Python לנתוני התחלה

לאחר מכן, יוצרים את קובץ ה-Python של סקריפט ההרצה scripts/setup_restaurant_db.py באמצעות הפקודה הבאה

cloudshell edit scripts/setup_restaurant_db.py

לאחר מכן, מעתיקים את הקוד הבא לקובץ scripts/setup_restaurant_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)

# Menu items data
MENU_ITEMS = [
    ("Truffle Mushroom Risotto", "Italian", "Main Course",
     "Arborio rice, truffle oil, porcini mushrooms, parmesan, white wine",
     "$28", "Vegetarian, Gluten-Free", True,
     "A creamy, luxurious risotto made with arborio rice slow-cooked in white wine and mushroom broth, finished with shaved black truffle and aged parmesan. The porcini mushrooms add a deep, earthy flavor that pairs beautifully with the delicate truffle oil drizzled on top."),
    ("Spicy Tuna Tartare", "Japanese", "Appetizer",
     "Ahi tuna, sriracha, sesame oil, avocado, crispy wonton",
     "$22", "Gluten-Free, Dairy-Free", True,
     "Fresh ahi tuna diced and tossed with sriracha aioli, toasted sesame oil, and lime juice, served atop creamy avocado slices with crispy wonton chips. A perfect balance of heat, richness, and crunch inspired by modern Japanese fusion cuisine."),
    ("Lamb Kofta Kebab", "Middle Eastern", "Main Course",
     "Ground lamb, cumin, coriander, yogurt sauce, flatbread",
     "$24", "Halal", True,
     "Hand-formed spiced lamb kebabs grilled over charcoal, seasoned with cumin, coriander, and sumac. Served with warm flatbread, tangy yogurt-cucumber sauce, and a fresh herb salad. A classic Middle Eastern street food elevated with premium ingredients."),
    ("Pad Thai", "Thai", "Main Course",
     "Rice noodles, shrimp, tamarind, peanuts, bean sprouts, lime",
     "$19", "Gluten-Free, Dairy-Free", True,
     "Stir-fried rice noodles with tiger shrimp, scrambled egg, and a sweet-sour tamarind sauce, topped with crushed peanuts, fresh bean sprouts, and a squeeze of lime. This classic Thai street food dish balances sweet, sour, salty, and umami in every bite."),
    ("Margherita Pizza", "Italian", "Main Course",
     "San Marzano tomatoes, fresh mozzarella, basil, olive oil",
     "$18", "Vegetarian", True,
     "A Neapolitan-style pizza with a thin, charred crust topped with crushed San Marzano tomatoes, creamy buffalo mozzarella, fresh basil leaves, and a drizzle of extra virgin olive oil. Simple, classic, and made with imported Italian ingredients."),
    ("Miso Glazed Black Cod", "Japanese", "Main Course",
     "Black cod, white miso, mirin, sake, pickled ginger",
     "$36", "Gluten-Free, Dairy-Free", True,
     "Buttery black cod marinated for 72 hours in a sweet white miso glaze with mirin and sake, then broiled until caramelized. Served with pickled ginger and steamed bok choy. A signature dish inspired by Nobu's iconic preparation."),
    ("Caesar Salad", "American", "Appetizer",
     "Romaine lettuce, parmesan, croutons, anchovy dressing",
     "$14", "Contains Gluten", True,
     "Crisp romaine hearts tossed with a house-made anchovy-garlic dressing, shaved parmesan, and golden sourdough croutons. A timeless salad that serves as the perfect light starter or side dish with grilled proteins."),
    ("Chicken Tikka Masala", "Indian", "Main Course",
     "Chicken thigh, tomato cream sauce, garam masala, basmati rice",
     "$21", "Gluten-Free", True,
     "Tender chunks of tandoori-marinated chicken simmered in a rich, creamy tomato sauce spiced with garam masala, cumin, and fenugreek. Served over fragrant basmati rice with warm garlic naan on the side."),
    ("Chocolate Lava Cake", "French", "Dessert",
     "Dark chocolate, butter, eggs, vanilla, powdered sugar",
     "$15", "Vegetarian", True,
     "A warm, individual-sized chocolate cake with a molten dark chocolate center that flows when you break through the delicate outer shell. Made with 70% Belgian dark chocolate and served with a scoop of vanilla bean ice cream."),
    ("Pho Bo", "Vietnamese", "Main Course",
     "Rice noodles, beef brisket, star anise, cinnamon, bean sprouts, Thai basil",
     "$17", "Gluten-Free, Dairy-Free", True,
     "A deeply aromatic beef broth simmered for 12 hours with star anise, cinnamon, and charred ginger, ladled over rice noodles and thinly sliced beef brisket. Served with fresh Thai basil, bean sprouts, jalapeño, and lime for the table to customize."),
    ("Lobster Bisque", "French", "Appetizer",
     "Lobster, heavy cream, cognac, tarragon, cayenne",
     "$19", "Gluten-Free", True,
     "A velvety smooth soup made from roasted lobster shells, finished with heavy cream, a splash of cognac, and fresh tarragon. Each bowl is garnished with tender lobster meat and a pinch of cayenne for subtle warmth."),
    ("Falafel Plate", "Middle Eastern", "Main Course",
     "Chickpeas, herbs, tahini, pickled vegetables, hummus",
     "$16", "Vegan, Gluten-Free", True,
     "Crispy-on-the-outside, fluffy-on-the-inside chickpea fritters seasoned with fresh parsley, cilantro, and cumin. Served with creamy tahini sauce, house-made hummus, pickled turnips, and warm pita bread."),
    ("Crème Brûlée", "French", "Dessert",
     "Heavy cream, vanilla bean, egg yolks, caramelized sugar",
     "$13", "Vegetarian, Gluten-Free", True,
     "A classic French custard made with Madagascar vanilla bean and farm-fresh egg yolks, topped with a perfectly torched layer of caramelized sugar that cracks with a satisfying snap. Rich, creamy, and elegantly simple."),
    ("Korean BBQ Short Ribs", "Korean", "Main Course",
     "Beef short ribs, soy sauce, sesame, garlic, pear marinade",
     "$32", "Dairy-Free", False,
     "Premium beef short ribs marinated overnight in a sweet and savory blend of soy sauce, Asian pear, garlic, and toasted sesame. Grilled tableside over charcoal and served with lettuce wraps, pickled daikon, and gochujang dipping sauce."),
    ("Tiramisu", "Italian", "Dessert",
     "Mascarpone, espresso, ladyfingers, cocoa, Marsala wine",
     "$14", "Vegetarian, Contains Gluten", True,
     "Layers of espresso-soaked ladyfingers and whipped mascarpone cream flavored with Marsala wine, dusted with premium Dutch cocoa powder. Made fresh daily and chilled for 24 hours to develop rich, complex flavors."),
]


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 menu_items 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 menu_items (
            id SERIAL PRIMARY KEY,
            name VARCHAR NOT NULL,
            cuisine_type VARCHAR NOT NULL,
            category VARCHAR NOT NULL,
            ingredients VARCHAR NOT NULL,
            price VARCHAR NOT NULL,
            dietary_tags VARCHAR NOT NULL,
            available BOOLEAN NOT NULL DEFAULT TRUE,
            description TEXT NOT NULL,
            description_embedding vector(3072)
        )
    """)


def seed_menu_items(cursor, conn):
    """Insert menu items."""
    cursor.execute("SELECT COUNT(*) FROM menu_items")
    existing_count = cursor.fetchone()[0]

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

    cursor.executemany("""
        INSERT INTO menu_items (name, cuisine_type, category, ingredients, price, dietary_tags, available, description)
        VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
    """, MENU_ITEMS)
    conn.commit()
    return len(MENU_ITEMS)


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

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

    cursor.execute(f"""
        UPDATE menu_items
        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_menu_items(cursor, conn)
        if seeded > 0:
            print(f"      ✓ Inserted {seeded} menu items")

        # 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()

עכשיו אפשר לעבור לשלב הבא

4. יצירה ואתחול של מסד הנתונים

עכשיו הסקריפטים שלנו מוכנים להרצה. נצטרך Python כדי להריץ את הסקריפט שהכנו, אז נכין אותו קודם.

הגדרת פרויקט Python

uv הוא מנהל פרויקטים וחבילות Python מהיר שנכתב ב-Rust ( מסמכי uv ). אנחנו משתמשים בו בשיעור Codelab הזה כדי לשמור על מהירות ופשטות בתחזוקת פרויקט Python

מאתחלים פרויקט Python ומוסיפים את התלות הנדרשת:

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

שימו לב: אנחנו משתמשים כאן ב-cloud-sql-python-connector Python SDK כדי להפעיל חיבור מאובטח למופע מסד הנתונים שלנו, שמאומת באמצעות Application Default Credentials.

הפעלת סקריפט ההגדרה

עכשיו אפשר להריץ את סקריפט ההגדרה ברקע ולבדוק את הפלט של המסוף שייכתב לקובץ logs/atabase_setup.log באמצעות הפקודה הבאה. אפשר להמשיך לקטע הבא בזמן ההמתנה לסיום התהליך.

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

הורדת קובץ הבינארי של Toolbox

במדריך הזה נשתמש ב-MCP Toolbox. למרבה המזל, הוא מגיע עם קובץ בינארי מוכן מראש שאפשר להשתמש בו בסביבת Linux. עכשיו נוריד אותו ברקע כי זה לוקח די הרבה זמן. מריצים את הפקודה הבאה כדי להוריד את הקובץ הבינארי ולבדוק את יומן הפלט ב-logs/toolbox_dl.log . אפשר להמשיך לקטע הבא בזמן ההמתנה לסיום התהליך.

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

הסבר על סקריפט ההגדרה scripts/setup_database.sh

עכשיו ננסה להבין את סקריפט ההגדרה שהגדרנו קודם. התהליך הבא מתבצע

  1. הפקודה הראשונה שמריצים היא gcloud sql instances create עם הדגל הבא
  • db-custom-1-3840 היא הרמה הקטנה ביותר של Cloud SQL עם ליבה ייעודית (‎1 vCPU, ‏ ‎3.75 GB RAM) במהדורת ENTERPRISE. פרטים נוספים זמינים כאן. נדרשת ליבה ייעודית לשילוב של Vertex AI ML – שכבות של ליבת מעבד משותפת (db-f1-micro, db-g1-small) לא תומכות בה.
  • --root-password מגדיר את הסיסמה למשתמש ברירת המחדל postgres.
  • --enable-google-ml-integration מאפשרת את השילוב המובנה של Cloud SQL עם Vertex AI, שמאפשר לכם להפעיל מודלים של הטמעות ישירות מ-SQL באמצעות הפונקציה embedding().
  1. בודקים אם המופע כבר בסטטוס RUNNABLE
  2. נותנים לחשבון השירות של מכונת Cloud SQL הרשאה לקרוא ל-Vertex AI באמצעות הפקודה gcloud projects add-iam-policy-binding. הפעולה הזו נדרשת עבור הפונקציה המובנית embedding() שבה נשתמש כשנזרע את מסד הנתונים
  3. יצירת מסד הנתונים
  4. הרצת סקריפט ההרצה הראשונית setup_restaurant_db.py

הסבר על סקריפט הזרע scripts/setup_restaurant_db.py

עכשיו נעבור לסקריפט ההגדרה הראשונית. הסקריפט הזה מבצע את הפעולות הבאות:

  1. הפעלת החיבור למופע של מסד הנתונים
  2. הכלי מתקין שני תוספים ל-PostgreSQL:
  • google_ml_integration — מספק את פונקציית ה-SQL‏ embedding(), שקוראת למודלים של הטמעה ב-Vertex AI ישירות מ-SQL. זהו תוסף ברמת מסד הנתונים שמאפשר להשתמש בפונקציות של למידת מכונה בתוך restaurant_db. הדגל ברמת המופע (--enable-google-ml-integration) שאתם מגדירים במהלך יצירת המופע מאפשר למכונה הווירטואלית של Cloud SQL להגיע אל Vertex AI – התוסף מאפשר להשתמש בפונקציות ה-SQL במסד הנתונים הספציפי הזה.
  • vector (pgvector) – מוסיף את סוג הנתונים vector ואת אופרטורי המרחק לאחסון ולשאילתת הטמעה.
  1. יוצרים את הטבלה, ומציינים שהעמודה description_embedding היא vector(3072) – עמודה מסוג pgvector שמאחסנת וקטורים תלת-ממדיים של 3,072.
  2. הזנת נתונים ראשוניים של פריטים בתפריט
  3. יצירת נתוני ההטמעה מהשדה description ומילוי השדה description_embedding באמצעות השילוב המובנה של Vertex דרך הפונקציה embedding()
  • embedding('gemini-embedding-001', description) — קורא למודל ההטמעה של Gemini ב-Vertex AI ישירות מ-SQL, ומעביר את description הטקסט של כל משרה. זהו התוסף google_ml_integration שהתקנתם בסקריפט ה-seed.
  • ::vector — מבצע המרה של מערך המספרים הממשיים שמוחזר לסוג vector של pgvector, כדי שאפשר יהיה לאחסן אותו ולשאול עליו שאילתות באמצעות אופרטורים של מרחק.
  • המודל UPDATE פועל על כל 15 השורות, ויוצר הטמעה אחת בת 3,072 ממדים לכל תיאור משרה.

הפעולה הזו תכין נתונים ראשוניים שהסוכן שלנו יוכל לגשת אליהם

5. הגדרת MCP Toolbox for Databases

בשלב הזה נציג את MCP Toolbox for Databases, נגדיר אותו לחיבור למופע Cloud SQL ונגדיר שני כלים לשאילתות SQL סטנדרטי.

מה זה MCP ולמה כדאי להשתמש ב-Toolbox?

e7b9be2e1c98b4db.png

MCP (Model Context Protocol) הוא פרוטוקול פתוח שקובע תקן לאופן שבו סוכני AI מגלים כלים חיצוניים ומקיימים איתם אינטראקציות. הוא מגדיר מודל לקוח-שרת: הסוכן מארח לקוח MCP, והכלים נחשפים על ידי שרתי MCP. כל לקוח שתואם ל-MCP יכול להשתמש בכל שרת שתואם ל-MCP – לא צריך קוד שילוב מותאם אישית לסוכן עבור כל כלי.

5bf26eeecad2277d.png

MCP Toolbox for Databases הוא שרת MCP בקוד פתוח שנבנה במיוחד לגישה למסדי נתונים. בלי זה, תצטרכו לכתוב פונקציות Python שפותחות חיבורים למסד נתונים, מנהלות מאגרי חיבורים, בונות שאילתות עם פרמטרים כדי למנוע הזרקת SQL, מטפלות בשגיאות ומשבצות את כל הקוד הזה בתוך הסוכן. כל נציג שזקוק לגישה למסד הנתונים חוזר על הפעולה הזו. שינוי שאילתה מחייב פריסה מחדש של הסוכן.

כדי להשתמש ב-Toolbox, צריך לכתוב קובץ YAML. כל כלי ממופה להצהרת SQL עם פרמטרים. Toolbox מטפל בניהול חיבורים, בשאילתות עם פרמטרים, באימות ובניטור. הכלים מופרדים מהסוכן – כדי לעדכן שאילתה, צריך לערוך את tools.yaml ולהפעיל מחדש את Toolbox, בלי לגעת בקוד של הסוכן. אותם כלים פועלים ב-ADK, ב-LangGraph, ב-LlamaIndex או בכל מסגרת תואמת ל-MCP.

כתיבת ההגדרה של כלי

עכשיו צריך ליצור קובץ בשם tools.yaml ב-Cloud Shell Editor כדי להגדיר את כלי ההגדרה

cloudshell edit tools.yaml

הקובץ משתמש ב-YAML עם כמה מסמכים – כל בלוק שמופרד על ידי --- הוא משאב עצמאי. לכל משאב יש מאפיין kind שמצהיר מהו (sources לחיבורי מסד נתונים, tools לפעולות שאפשר להפעיל באמצעות סוכן) ומאפיין type שמציין את ה-Backend (cloud-sql-postgres למקור, postgres-sql לכלים מבוססי SQL). כלי מפנה למקור שלו באמצעות name, וכך ארגז הכלים יודע מול איזה מאגר חיבורים לבצע את הפעולה. משתני הסביבה משתמשים בתחביר ${VAR_NAME} ונפתרים בעת ההפעלה.

עכשיו מעתיקים את הסקריפטים הבאים לקובץ tools.yaml

# tools.yaml

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

---

הסקריפט הזה מגדיר את המשאב הבא:

  • מקור (restaurant-db) – מציין לארגז הכלים איך להתחבר למופע Cloud SQL PostgreSQL. הסוג cloud-sql-postgres משתמש במחבר Cloud SQL באופן פנימי, ומטפל באימות ובחיבורים מאובטחים באופן אוטומטי. ה-placeholders‏ ${GOOGLE_CLOUD_PROJECT} , ‏ ${REGION} ו-${DB_PASSWORD} נפתרים ממשתני סביבה בזמן ההפעלה.

בשלב הבא, מוסיפים את הסקריפט הבא מתחת לסמל --- בקובץ tools.yaml

 
# --- Tool 1: Search menu items by category and/or cuisine type ---
kind: tool
name: search-menu
type: postgres-sql
source: restaurant-db
description: >-
  Search for menu items by category and/or cuisine type.
  Use this tool when the user wants to browse menu items
  by category (e.g., Main Course, Appetizer, Dessert) or find dishes
  from a specific cuisine. Both parameters accept an
  empty string to match all values.
statement: |
  SELECT name, cuisine_type, category, ingredients, price, dietary_tags, available
  FROM menu_items
  WHERE ($1 = '' OR LOWER(category) = LOWER($1))
  AND ($2 = '' OR LOWER(cuisine_type) LIKE '%' || LOWER($2) || '%')
  ORDER BY name
  LIMIT 10
parameters:
  - name: category
    type: string
    description: "The menu category to filter by (e.g., 'Main Course', 'Appetizer', 'Dessert'). Use empty string for all categories."
  - name: cuisine_type
    type: string
    description: "A cuisine type to search for (partial match, e.g., 'Italian', 'Japanese'). Use empty string for all cuisines."

---

# --- Tool 2: Get full details for a specific menu item ---
kind: tool
name: get-item-details
type: postgres-sql
source: restaurant-db
description: >-
  Get full details for a specific menu item including its description,
  price, dietary tags, and availability. Use this tool when the
  user asks about a particular dish by name or cuisine.
statement: |
  SELECT name, cuisine_type, category, ingredients, price, dietary_tags, available, description
  FROM menu_items
  WHERE LOWER(name) LIKE '%' || LOWER($1) || '%'
  OR LOWER(cuisine_type) LIKE '%' || LOWER($1) || '%'
parameters:
  - name: search_term
    type: string
    description: "The dish name or cuisine type to look up (partial match supported)."

---

הסקריפט הזה מגדיר את המשאב הבא:

  • כלי 1 ו-2 (search-menu, get-item-details) – כלי שאילתות SQL סטנדרטיים. כל אחד ממפה שם של כלי (מה שהסוכן רואה) להצהרת SQL עם פרמטרים (מה שמסד הנתונים מבצע). הפרמטרים משתמשים ב-$1, $2 placeholders מיקומיים. ארגז הכלים מבצע את הפעולות האלה כהצהרות מוכנות, מה שמונע הזרקת SQL.

ממשיכים ומוסיפים את הסקריפט הבא מתחת לסמל --- בקובץ 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

---

הסקריפט הזה מגדיר את המשאב הבא:

  • מודל להטמעה (gemini-embedding) – מגדיר את Toolbox לקריאה למודל gemini-embedding-001 של Gemini כדי ליצור הטמעות טקסט ב-3,072 ממדים. Toolbox משתמש ב-Application Default Credentials (ADC) כדי לבצע אימות – אין צורך במפתח API ב-Cloud Shell או ב-Cloud Run. הערה: המודל dimension שהוגדר כאן חייב להיות זהה למודל שהוגדר קודם כדי לאכלס את מסד הנתונים.

ממשיכים ומוסיפים את הסקריפט הבא מתחת לסמל --- בקובץ tools.yaml

 
# --- Tool 3: Semantic search by description ---
kind: tool
name: search-menu-by-description
type: postgres-sql
source: restaurant-db
description: >-
  Find menu items that match a natural language description of what the user
  is looking for. Use this tool when the user describes their ideal dish
  using flavors, textures, dietary preferences, or cravings rather than a
  specific category or cuisine. Examples: "I want something spicy and creamy,"
  "a light vegetarian appetizer," "something rich and chocolatey for dessert."
statement: |
  SELECT name, cuisine_type, category, ingredients, price, dietary_tags, description
  FROM menu_items
  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 dish the user is looking for."
    embeddedBy: gemini-embedding

---

הסקריפט הזה מגדיר את המשאב הבא:

  • כלי 3 (search-menu-by-description) – כלי לחיפוש וקטורים. לפרמטר search_query יש את הערך embeddedBy: gemini-embedding, שמורה ל-Toolbox ליירט את הטקסט הגולמי, לשלוח אותו למודל ההטמעה ולהשתמש בווקטור שמתקבל בהצהרת ה-SQL. האופרטור <=> הוא המרחק הקוסינוסי של pgvector – ערכים קטנים יותר מציינים תיאורים דומים יותר.

לבסוף, מוסיפים את הכלי האחרון מתחת לסמל --- בקובץ tools.yaml

 
# --- Tool 4: Add a new menu item with automatic embedding ---
kind: tool
name: add-menu-item
type: postgres-sql
source: restaurant-db
description: >-
  Add a new menu item to the restaurant. Use this tool when a user asks
  to add a dish that is not currently on the menu.
statement: |
  INSERT INTO menu_items (name, cuisine_type, category, ingredients, price, dietary_tags, available, description, description_embedding)
  VALUES ($1, $2, $3, $4, $5, $6, CAST($7 AS BOOLEAN), $8, $9)
  RETURNING name, cuisine_type
parameters:
  - name: name
    type: string
    description: "The dish name (e.g., 'Truffle Mushroom Risotto')."
  - name: cuisine_type
    type: string
    description: "The cuisine type (e.g., 'Italian', 'Japanese', 'Thai')."
  - name: category
    type: string
    description: "The menu category (e.g., 'Main Course', 'Appetizer', 'Dessert')."
  - name: ingredients
    type: string
    description: "Comma-separated list of key ingredients (e.g., 'salmon, miso, ginger')."
  - name: price
    type: string
    description: "The price (e.g., '$24')."
  - name: dietary_tags
    type: string
    description: "Dietary information (e.g., 'Vegetarian, Gluten-Free')."
  - name: available
    type: string
    description: "Whether the dish is currently available (true or false)."
  - name: description
    type: string
    description: "A short description of the dish (2-3 sentences)."
  - name: description_vector
    type: string
    description: "Auto-generated embedding vector for the dish description."
    valueFromParam: description
    embeddedBy: gemini-embedding

הסקריפט הזה מגדיר את המשאב הבא:

  • כלי 4 (add-menu-item) – הדגמה של הטמעה של וקטורים. לפרמטר description_vector יש שני שדות מיוחדים:
  • valueFromParam: description — הכלי מעתיק את הערך מהפרמטר description אל הפרמטר הזה. ה-LLM אף פעם לא רואה את הפרמטר הזה.
  • embeddedBy: gemini-embedding — Toolbox מטמיע את הטקסט שהועתק בווקטור לפני שהוא מעביר אותו ל-SQL.

התוצאה: קריאה אחת לכלי מאחסנת גם את הטקסט הגולמי של התיאור וגם את הטמעת הווקטור שלו, בלי שהסוכן יודע משהו על הטמעות.

פורמט ה-YAML של כמה מסמכים מפריד בין כל משאב באמצעות ---. לכל מסמך יש שדות kind, name ו-type שמגדירים מה הוא. לסיכום, כבר הגדרנו את כל הדברים הבאים:

  • הגדרת מסד הנתונים של המקור
  • הגדרת כלים ( tool 1 ו-2 ) לשליחת שאילתות למסד נתונים עם מסנן רגיל
  • הגדרת מודל להטמעה
  • הגדרת כלי לביצוע חיפוש וקטורי ( כלי 3 ) במסד הנתונים
  • הגדרת כלי להטמעת נתוני וקטור ( כלי 4 ) במסד נתונים

6. הפעלת שרת MCP Toolbox

בשלב הקודם כבר הגדרנו את התצורה הנדרשת ל-MCP Toolbox. עכשיו אנחנו מוכנים להריץ את השרת

אימות הנתונים הראשוניים

לפני שמתחילים להשתמש ב-Toolbox, צריך לוודא שהגדרת מסד הנתונים הושלמה. יוצרים סקריפט Python‏ scripts/verify_database.py באמצעות הפקודה הבאה

cloudshell edit scripts/verify_seed.py

לאחר מכן, מעתיקים את הקוד הבא לקובץ scripts/verify_seed.py

#!/usr/bin/env python3
"""Verify the database has 15 menu items 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 menu items 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 menu items and embeddings
        cursor.execute("SELECT COUNT(*) FROM menu_items")
        item_count = cursor.fetchone()[0]

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

        print(f"Menu Items: {item_count}/15")
        print(f"Embeddings: {embedding_count}/15")

        cursor.close()
        conn.close()

        if item_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)

הסקריפט הזה יבדוק את מספר נתוני פריטי התפריט וההטמעה שלהם. מריצים את הסקריפט באמצעות הפקודה הבאה

uv run scripts/verify_seed.py

אם מופיע פלט הטרמינל הבא, המשמעות היא שהנתונים מוכנים

Menu Items: 15/15
Embeddings: 15/15

✓ Database ready!

הפעלת השרת של ארגז הכלים

בשלב ההגדרה הקודם, כבר הורדנו את קובץ ההפעלה toolbox. מוודאים שהקובץ הבינארי הזה קיים והורד בהצלחה. אם לא, מורידים אותו וממתינים עד לסיום ההורדה.

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

נצטרך לחשוף את המשתנים .env לתהליך הצאצא שמופעל על ידי ערכת הכלים של MCP. מריצים את הפקודה הבאה כדי להפעיל את שרת ערכת הכלים ולרשום את הפלט של המסוף בקובץ logs/mcp_toolbox.log

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

אמור להופיע פלט בקובץ logs/mcp_toolbox.log שמאשר שהשרת מוכן, כמו שמוצג בהמשך:

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

אימות הכלים

מריצים שאילתה ב-Toolbox API כדי לראות רשימה של כל הכלים הרשומים:

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

יוצגו כלים עם התיאורים והפרמטרים שלהם. כמו שמוצג בהמשך

...
       "search-menu-by-description": {
            "description": "Find menu items that match a natural language description of what the user is looking for. Use this tool when the user describes their ideal dish using flavors, textures, dietary preferences, or cravings rather than a specific category or cuisine. Examples: \"I want something spicy and creamy,\" \"a light vegetarian appetizer,\" \"something rich and chocolatey for dessert.\"",
            "parameters": [
                {
                    "name": "search_query",
                    "type": "string",
                    "required": true,
                    "description": "A natural language description of the kind of dish the user is looking for.",
                    "authServices": []
                }
            ],
            "authRequired": []
        }
...

בודקים את הכלי search-menu ישירות:

curl -s -X POST http://localhost:5000/api/tool/search-menu/invoke \\   -H "Content-Type: application/json" \\   -d '{"category": "Main Course", "cuisine_type": "Italian"}' | jq '.result | fromjson'

התשובה צריכה לכלול את המנות העיקריות האיטלקיות מנתוני הבסיס שלך.

[
  {
    "name": "Margherita Pizza",
    "cuisine_type": "Italian",
    "category": "Main Course",
    "ingredients": "San Marzano tomatoes, fresh mozzarella, basil, olive oil",
    "price": "$18",
    "dietary_tags": "Vegetarian",
    "available": true
  },
  {
    "name": "Truffle Mushroom Risotto",
    "cuisine_type": "Italian",
    "category": "Main Course",
    "ingredients": "Arborio rice, truffle oil, porcini mushrooms, parmesan, white wine",
    "price": "$28",
    "dietary_tags": "Vegetarian, Gluten-Free",
    "available": true
  }
]

7. יצירת סוכן ADK

עכשיו נשתמש ב-ADK ב-Python לפרויקט הזה. נוסיף את התלויות הנדרשות:

uv add google-adk==1.29.0 toolbox-adk==1.0.0
  • google-adk — ערכת הכלים לפיתוח סוכנים של Google, כולל Gemini SDK
  • toolbox-adk — שילוב ADK ב-MCP Toolbox for Databases.

יצירת מבנה ספריות של סוכנים

ערכת ה-ADK מצפה לפריסת תיקיות ספציפית: ספרייה שנקראת על שם הסוכן שלכם ומכילה את הקבצים __init__.py, agent.py ו-.env. כדי לעזור לכם, יש פקודה מובנית ליצירת המבנה במהירות:

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

עכשיו הספריה אמורה להיראות כך:

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

לאחר מכן, נצטרך לשלב את סוכן ה-ADK עם שרת Toolbox הפועל ולבדוק את כל ארבעת הכלים – שאילתות רגילות, חיפוש סמנטי והטמעה של וקטורים. קוד הסוכן מינימלי: כל הלוגיקה של מסד הנתונים נמצאת ב-tools.yaml.

הגדרת הסביבה של הסוכן

‫ADK קורא את GOOGLE_GENAI_USE_VERTEXAI,‏ GOOGLE_CLOUD_PROJECT ו-GOOGLE_CLOUD_LOCATION מסביבת מעטפת, שכבר הגדרתם בשלב הקודם. המשתנה היחיד שספציפי לסוכן הוא TOOLBOX_URL – צריך לצרף אותו לקובץ .env של הסוכן:

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

עדכון מודול הסוכן

פתיחה של restaurant_agent/agent.py ב-Cloud Shell Editor

cloudshell edit restaurant_agent/agent.py

ומחליפים את התוכן בקוד הבא:

# restaurant_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="restaurant_agent",
    model="gemini-3.5-flash",
    instruction="""You are a friendly and knowledgeable concierge at "Foodie Finds," a restaurant. Your job:
- Help diners browse the menu by category or cuisine type.
- Provide full details about specific dishes, including ingredients, price, and dietary information.
- Recommend dishes based on natural language descriptions of what the diner is craving.
- Add new menu items when asked.

When a diner asks about a specific dish by name or cuisine, use the get-item-details tool.
When a diner asks for a specific category or cuisine type, use the search-menu tool.
When a diner describes what kind of food they want — by flavor, texture, dietary needs, or cravings — use the search-menu-by-description tool for semantic search.

When in doubt between search-menu and search-menu-by-description, prefer search-menu-by-description — it searches dish descriptions and finds more relevant matches.
If a dish is not available (available is false), let the diner know and suggest similar alternatives from the search results.
Be conversational, knowledgeable, and concise.""",
    tools=[toolbox],
)

שימו לב שאין כאן קוד של מסד נתונים – ToolboxToolset מתחבר לשרת של Toolbox בהפעלה וטוען את כל הכלים הזמינים. הסוכן קורא לכלים לפי שם, ו-Toolbox מתרגם את הקריאות האלה לשאילתות SQL מול Cloud SQL.

משתנה הסביבה TOOLBOX_URL מוגדר כברירת מחדל ל-http://127.0.0.1:5000 לפיתוח מקומי. כשפורסים ל-Cloud Run בהמשך, מחליפים את כתובת ה-URL הזו בכתובת ה-URL של שירות Toolbox ב-Cloud Run – אין צורך לשנות את הקוד.

בדיקת הסוכן

מפעילים את ממשק המשתמש של ADK למפתחים:

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

פותחים את כתובת ה-URL שמוצגת במסוף (בדרך כלל http://localhost:8000) באמצעות התכונה Web Preview של Cloud Shell או באמצעות ctrl + click על כתובת ה-URL שמוצגת במסוף. בוחרים באפשרות restaurant_agent בתפריט הנפתח של הסוכן בפינה הימנית העליונה.

בדיקת שאילתות רגילות

כדי לאמת את הכלים של SQL סטנדרטי, אפשר לנסות את ההנחיות הבאות:

What Italian dishes do you have?
Tell me about the Miso Glazed Black Cod

99807eceeb0212f6.png

834580f0aa4c5106.png

נסו להשתמש בתיאורים בשפה טבעית שלא מתאימים לתפקיד ספציפי או לסט טכנולוגיות ספציפי:

I want something spicy and creamy
Something rich and chocolatey for dessert
I'm in the mood for something light and healthy

הנציג ינסה לבחור את הכלי הנכון על סמך סוג השאילתה: מסננים מובנים עוברים דרך search-menu, ותיאורים בשפה טבעית עוברים דרך search-menu-by-description.

2b9ed5d90c5990c6.png

בדיקה של הטמעת וקטורים

מבקשים מהנציג להוסיף משרה חדשה:

Add a new dish: 'Seared Duck Breast' cuisine type French, category Main Course, ingredients: duck breast, cherry reduction, roasted root vegetables, thyme, price $34, dietary tags: Gluten-Free Dairy-Free, available true. Description: A perfectly seared duck breast with crispy skin, served with a tart cherry reduction sauce and a medley of roasted root vegetables. The duck is cooked sous vide for tender, pink meat,
then
finished in a cast iron skillet for maximum crispiness.

f7a96ca8c37e7c56.png

עכשיו נסו לחפש אותו:

Find me something with rich, gamey flavors and fruit sauce

ההטמעה נוצרה אוטומטית במהלך הפעולה INSERT – לא נדרש שלב נפרד.

be78e692c056596e.png

עכשיו יש לכם אפליקציית Agentic RAG שעובדת באופן מלא ומבוססת על ADK,‏ MCP Toolbox ו-CloudSQL. מזל טוב! בשלב הבא נסביר איך פורסים את האפליקציות האלה ב-Cloud Run.

עכשיו, כדי להמשיך, צריך לעצור את ממשק המשתמש למפתחים. לשם כך, מקישים על Ctrl+C פעמיים כדי להפסיק את התהליך.

8. פריסה ב-Cloud Run

הסוכן וה-Toolbox פועלים באופן מקומי. בשלב הזה פורסים את שניהם כשירותי Cloud Run כדי שאפשר יהיה לגשת אליהם דרך האינטרנט. שירות ה-Toolbox פועל כשרת MCP ב-Cloud Run, ושירות הסוכן מתחבר אליו.

הכנת ערכת הכלים לפריסה

יוצרים ספריית פריסה לשירות Toolbox:

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

יוצרים את קובץ ה-Dockerfile עבור ערכת הכלים. פותחים את deploy-toolbox/Dockerfile ב-Cloud Shell Editor:

cloudshell edit deploy-toolbox/Dockerfile

מעתיקים אליו את הסקריפט הבא

# 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"]

קובץ ההפעלה של Toolbox ו-tools.yaml נארזים בתמונת Debian מינימלית. ‫Cloud Run מעביר את התנועה ליציאה 8080.

פריסת שירות ארגז הכלים

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 &

הפקודה הזו שולחת את המקור ל-Cloud Build, יוצרת קובץ אימג' של קונטיינר, מעבירה אותו בדחיפה ל-Artifact Registry ופורסת אותו ב-Cloud Run. התהליך יימשך כמה דקות. אפשר לבדוק את יומן הפריסה בקובץ logs/deploy_toolbox.log.

הכנת הסוכן לפריסה

בזמן שהכלי לבניית אפליקציות יוצר את הקבצים, מגדירים את קובצי הפריסה של הסוכן.

יוצרים Dockerfile בספריית הבסיס של הפרויקט. פותחים את Dockerfile ב-Cloud Shell Editor:

cloudshell edit Dockerfile

לאחר מכן, מעתיקים את התוכן הבא

# 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 restaurant_agent/ restaurant_agent/
EXPOSE 8080
CMD ["uv", "run", "adk", "web", "--host", "0.0.0.0", "--port", "8080"]

קובץ Dockerfile הזה משתמש ב-ghcr.io/astral-sh/uv כקובץ האימג' הבסיסי, שכולל את Python ואת uv שהותקנו מראש – אין צורך להתקין את uv בנפרד דרך pip.

יוצרים קובץ .dockerignore כדי להחריג קבצים מיותרים מקובץ אימג' של קונטיינר:

cloudshell edit .dockerignore

לאחר מכן מעתיקים לתוכו את הסקריפט הבא

# .dockerignore
.venv/
__pycache__/
*.pyc
.env
restaurant_agent/.env
toolbox
tools.yaml
deploy-toolbox/

פריסת שירות הסוכן

ממתינים עד שהפריסה של Toolbox תסתיים. כדי לאמת את התהליך, בודקים שוב את תהליך הפריסה ב-logs/deploy_toolbox.log. לאחר מכן, מאחזרים את כתובת ה-URL של Cloud Run באמצעות הפקודה הבאה

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

הפלט שיוצג יהיה דומה לזה:

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

עכשיו נבדוק שה-Toolbox שהטמענו פועל:

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

אם הפלט נראה כמו בדוגמה הזו, הפריסה כבר הצליחה

{
    "serverVersion": "1.1.0+binary.linux.amd64.da6f5f8",
    "tools": {
        "add-menu-item": {
            "description": "Add a new menu item to the restaurant. Use this tool when a user asks to add a dish that is not currently on the menu.",

בשלב הבא, פורסים את הסוכן ומעבירים את כתובת ה-URL של ארגז הכלים כמשתנה סביבה:

cd ~/build-agent-adk-toolbox-cloudsql
gcloud run deploy restaurant-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

קוד הסוכן קורא את TOOLBOX_URL מהסביבה (הגדרתם את זה קודם). באופן מקומי, הוא מצביע על http://127.0.0.1:5000. ב-Cloud Run הוא מצביע על כתובת ה-URL של שירות Toolbox. אין צורך לשנות את הקוד.

בדיקת הסוכן הפעיל

מאחזרים את כתובת ה-URL של הסוכן ב-Cloud Run:

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

פותחים את כתובת ה-URL בדפדפן. ממשק המשתמש של ADK Dev נטען – אותו ממשק שבו השתמשתם באופן מקומי, ועכשיו הוא פועל ב-Cloud Run.

בוחרים באפשרות restaurant_agent מהתפריט הנפתח ומבצעים בדיקה:

What Italian dishes do you have?
I want something spicy and creamy

שתי השאילתות פועלות דרך השירותים שנפרסו: הסוכן ב-Cloud Run קורא ל-Toolbox ב-Cloud Run, ששולח שאילתה ל-Cloud SQL.

9. Congratulations / Clean Up

יצרתם ופרסתם עוזר חכם לתפריט מסעדה שמשתמש ב-MCP Toolbox for Databases כדי לגשר בין סוכן ADK לבין Cloud SQL PostgreSQL – עם שאילתות SQL סטנדרטיות וחיפוש סמנטי של וקטורים.

מה למדתם

  • איך MCP מתקנן את הגישה של סוכני AI לכלים, ואיך MCP Toolbox for Databases מיישם את זה באופן ספציפי על פעולות במסד נתונים – החלפת קוד מותאם אישית של מסד נתונים בהגדרת YAML הצהרתית
  • איך מגדירים את Cloud SQL PostgreSQL כמקור נתונים ב-Toolbox באמצעות cloud-sql-postgres סוג המקור
  • איך מגדירים כלי שאילתות SQL סטנדרטי עם הצהרות פרמטריות שמונעות הזרקת SQL
  • איך מפעילים חיפוש וקטורים באמצעות pgvector ו-gemini-embedding-001, עם הפרמטר embeddedBy להטמעה אוטומטית של שאילתות
  • איך valueFromParam מאפשרת הטמעה אוטומטית של וקטורים – מודל ה-LLM מספק תיאור טקסט, והכלי מעתיק, מטמיע ומאחסן את הווקטור לצד הטקסט באופן שקט
  • איך ToolboxToolset ב-ADK טוען כלים משרת Toolbox פעיל, כדי לשמור על קוד הסוכן מינימלי ולנתק לחלוטין את הלוגיקה של מסד הנתונים
  • איך פורסים את השרת MCP Toolbox ואת סוכן ה-ADK ב-Cloud Run כשירותים נפרדים

פינוי נפח

כדי להימנע מחיובים בחשבון Google Cloud על המשאבים שנוצרו ב-Codelab הזה, אתם יכולים למחוק את המשאבים בנפרד או למחוק את הפרויקט כולו.

הדרך הקלה ביותר לנקות היא למחוק את הפרויקט. כך יוסרו כל המשאבים שמשויכים לפרויקט.

gcloud projects delete $GOOGLE_CLOUD_PROJECT

אפשרות 2: מחיקת משאבים ספציפיים

אם אתם רוצים לשמור את הפרויקט אבל להסיר רק את המשאבים שנוצרו ב-Codelab הזה:

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