قاعدة البيانات كأداة: التوليد المعزّز بالاسترجاع المستند إلى الوكيل باستخدام "حزمة تطوير التطبيقات" و"مجموعة أدوات MCP" وCloud SQL

1. مقدمة

تعتمد فائدة وكلاء الذكاء الاصطناعي على البيانات التي يمكنهم الوصول إليها. تتوفّر معظم البيانات الواقعية في قواعد البيانات، وعادةً ما يعني ربط الوكلاء بقواعد البيانات كتابة إدارة الاتصال ومنطق طلب البحث وتضمين خطوط الأنابيب داخل رمز الوكيل. ويكرّر كل وكيل يحتاج إلى الوصول إلى قاعدة البيانات هذه العملية، ويتطلّب كل تغيير في طلب البحث إعادة نشر الوكيل.

يوضّح هذا الدرس التطبيقي العملي أسلوبًا مختلفًا. يمكنك تعريف أدوات قاعدة البيانات في ملف YAML، مثل طلبات البحث بلغة الاستعلامات البنيوية (SQL) العادية، والبحث عن التشابه بين المتّجهات، وحتى إنشاء عمليات تضمين تلقائية، ويتولّى MCP Toolbox for Databases جميع عمليات قاعدة البيانات كخادم MCP. يبقى رمز الوكيل بسيطًا: حمِّل الأدوات، ودَع Gemini يقرّر الأداة التي سيستخدمها.

ما ستنشئه

مساعد لوحة وظائف ذكي لـ "TechJobs": هو وكيل ADK يستند إلى Gemini ويساعد المطوّرين في تصفّح قوائم الوظائف في مجال التكنولوجيا باستخدام فلاتر عادية (الدور، مجموعة التكنولوجيا) واكتشاف الوظائف من خلال أوصاف مكتوبة بلغة طبيعية، مثل "أريد وظيفة عن بُعد في تطوير برامج الدردشة المستندة إلى الذكاء الاصطناعي". يقرأ الوكيل من قاعدة بيانات Cloud SQL PostgreSQL ويكتب فيها بالكامل من خلال MCP Toolbox for Databases، الذي يتعامل مع جميع عمليات الوصول إلى قاعدة البيانات، بما في ذلك إنشاء عمليات تضمين تلقائية للبحث عن المتّجهات. وبنهاية هذه العملية، سيتم تشغيل كلّ من Toolbox والوكيل على Cloud Run.

eb6de681c40990c1.jpeg

ما ستتعلمه

  • كيف يوحّد بروتوكول سياق النموذج (MCP) إمكانية الوصول إلى الأدوات لوكلاء الذكاء الاصطناعي، وكيف تطبّق أداة 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 وتضبط مشروعك على السحابة الإلكترونية وتستنسخ مستودع الرموز المرجعي.

فتح Cloud Shell

افتح Cloud Shell في المتصفّح. توفّر Cloud Shell بيئة تم ضبطها مسبقًا تتضمّن جميع الأدوات التي تحتاج إليها لإكمال هذا الدرس التطبيقي حول الترميز. انقر على تفويض عندما يُطلب منك ذلك

بعد ذلك، انقر على "عرض" -> "وحدة طرفية" لفتح الوحدة الطرفية.يجب أن تبدو واجهتك مشابهة لما يلي

86307fac5da2f077.png

ستكون هذه واجهتنا الرئيسية، مع وضع بيئة التطوير المتكاملة في الأعلى والوحدة الطرفية في الأسفل.

إعداد دليل العمل

أنشئ دليل العمل. يتم تخزين جميع الرموز التي تكتبها في هذا الدرس التطبيقي حول الترميز هنا:

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

تفعيل واجهة برمجة التطبيقات المطلوبة

بعد ذلك، علينا تفعيل عدة واجهات برمجة تطبيقات للمنتج الذي سنتفاعل معه:

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، وتستخدم "مجموعة الأدوات" واجهة برمجة التطبيقات الخاصة بالتضمين للبحث المتّجه.
  • واجهة برمجة التطبيقات Cloud SQL Admin (sqladmin.googleapis.com): يمكنك توفير مثيل PostgreSQL وإدارته.
  • Compute Engine API (compute.googleapis.com): مطلوب لإنشاء مثيلات Cloud SQL.
  • ‫Cloud Run وCloud Build وArtifact Registry: يتم استخدامها في خطوة النشر لاحقًا في هذا الدرس التطبيقي حول الترميز

3- تجهيز النصوص البرمجية لإعداد قاعدة البيانات

تبدأ هذه الخطوة بإنشاء مثيل Cloud SQL وتشغيل نص برمجي مُبرمَج للإعداد ينتظر إلى أن يصبح المثيل جاهزًا، ثم ينشئ قاعدة البيانات ويملأها ببيانات الوظائف الشاغرة وينشئ عمليات التضمين، وكل ذلك في عملية واحدة.

أولاً، لنضِف كلمة مرور قاعدة البيانات إلى ملف .env ثم نعيد تحميله:

echo "DB_PASSWORD=techjobs-pwd" >> .env
echo "DB_INSTANCE=jobs-instance" >> .env
echo "DB_NAME=jobs_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_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 ""

إنشاء نص برمجي بلغة Python لإنشاء بيانات أولية

بعد ذلك، أنشئ ملف Python الخاص بنص التعبئة scripts/setup_jobs_db.py باستخدام الأمر أدناه

cloudshell edit scripts/setup_jobs_db.py

بعد ذلك، انسخ الرمز التالي والصِقه في ملف 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()

لننتقل الآن إلى الخطوة التالية

4. إنشاء قاعدة البيانات وتهيئتها

أصبحت النصوص البرمجية جاهزة الآن للتنفيذ. سنحتاج إلى Python لتنفيذ النص البرمجي الذي أعددناه، لذا لنبدأ بإعداد Python أولاً

إعداد مشروع Python

uv هي حزمة Python سريعة ومدير مشاريع مكتوب بلغة Rust ( مستندات uv). تستخدمها ورشة العمل هذه لتحقيق السرعة والبساطة في الحفاظ على مشروع Python.

ابدأ مشروع Python وأضِف التبعيات المطلوبة:

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

يُرجى العِلم أنّنا نستخدم حزمة تطوير البرامج (SDK) cloud-sql-python-connector Python هنا لإنشاء اتصال آمن بنسخة قاعدة البيانات التي يتمّ إثبات ملكيتها باستخدام بيانات الاعتماد التلقائية للتطبيق.

تنفيذ نص الإعداد البرمجي

يمكننا الآن تشغيل نص الإعداد في الخلفية وفحص ناتج وحدة التحكّم الذي سيتمّ كتابته في الملف 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.0.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 (وحدة معالجة مركزية افتراضية واحدة، وذاكرة وصول عشوائي سعتها 3.75 غيغابايت) في إصدار 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_jobs_db.py الخاص بإنشاء البيانات الأولية

فهم النص البرمجي الأوّلي scripts/setup_jobs_db.py

بالانتقال الآن إلى نص التعبئة، ينفّذ هذا النص الإجراءات التالية:

  1. تهيئة عملية الربط بمثيل قاعدة البيانات
  2. تثبِّت هذه الحزمة إضافتَين إلى PostgreSQL:
  • google_ml_integration: توفّر دالة embedding() SQL التي تستدعي نماذج التضمين في Vertex AI مباشرةً من SQL. هذه إضافة على مستوى قاعدة البيانات تتيح استخدام وظائف تعلُّم الآلة داخل jobs_db. تتيح العلامة على مستوى المثيل (--enable-google-ml-integration) التي تضبطها أثناء إنشاء المثيل لجهاز Cloud SQL الظاهري الوصول إلى Vertex AI، ويجعل هذا الامتداد دوال SQL متاحةً في قاعدة البيانات المحدّدة هذه.
  • vector (pgvector): يضيف نوع البيانات vector وعوامل تشغيل المسافة لتخزين عمليات التضمين والاستعلام عنها.
  1. أنشئ الجدول، مع العلم أنّ العمود description_embedding هو vector(3072)، أي عمود pgvector يخزّن متجهات ثلاثية الأبعاد.
  2. تعبئة بيانات الوظائف الأولية
  3. إنشاء بيانات التضمين من الحقل description وملء description_embedding باستخدام عملية التكامل المضمّنة في Vertex من خلال الدالة embedding()
  • embedding('gemini-embedding-001', description): تستدعي نموذج التضمين Gemini في Vertex AI مباشرةً من SQL، مع تمرير نص description لكل وظيفة. هذه هي الإضافة google_ml_integration التي ثبَّتها في النص البرمجي الأوّلي.
  • ::vector: تحويل مصفوفة الأرقام العشرية التي تم إرجاعها إلى نوع vector في pgvector حتى يمكن تخزينها والاستعلام عنها باستخدام عوامل تشغيل المسافة
  • يتم تشغيل UPDATE على جميع الصفوف الـ 15، ما يؤدي إلى إنشاء تضمين واحد ذي 3072 بُعدًا لكل وصف وظيفي.

سيؤدي ذلك إلى إعداد البيانات الأولية التي سيصل إليها الموظف

5- ضبط إعدادات MCP Toolbox for Databases

تقدّم هذه الخطوة MCP Toolbox for Databases، وتضبطها للاتصال بمثيل Cloud SQL، وتحدّد أداتَي استعلام SQL عاديتَين.

ما هي "برنامج شركاء المحتوى" ولماذا يجب استخدام "أدوات الناشرين"؟

e7b9be2e1c98b4db.png

بروتوكول سياق النموذج (MCP) هو بروتوكول مفتوح يوحّد طريقة عثور وكلاء الذكاء الاصطناعي على الأدوات الخارجية والتفاعل معها. يحدّد هذا النموذج نموذجًا للعميل والخادم: يستضيف الوكيل عميل MCP، وتوفّر خوادم MCP الأدوات. يمكن لأي عميل متوافق مع MCP استخدام أي خادم متوافق مع MCP، ولا يحتاج الوكيل إلى رمز دمج مخصّص لكل أداة.

5bf26eeecad2277d.png

MCP Toolbox for Databases هو خادم MCP مفتوح المصدر تم إنشاؤه خصيصًا للوصول إلى قواعد البيانات. وبدونها، عليك كتابة دوال Python تفتح اتصالات بقاعدة البيانات، وتدير مجموعات الاتصالات، وتنشئ استعلامات ذات مَعلمات لمنع اختراق SQL، وتتعامل مع الأخطاء، وتضمّن كل هذا الرمز البرمجي داخل برنامجك. ويكرّر كل وكيل يحتاج إلى الوصول إلى قاعدة البيانات هذه العملية. يعني تغيير الاستعلام إعادة نشر الوكيل.

باستخدام Toolbox، يمكنك كتابة ملف YAML. ترتبط كل أداة بعبارة SQL ذات معلَمات. تتعامل مجموعة الأدوات مع تجميع الاتصالات، والاستعلامات التي تتضمّن مَعلمات، والمصادقة، وإمكانية المراقبة. يتم فصل الأدوات عن الوكيل، ويمكنك تعديل طلب بحث من خلال تعديل tools.yaml وإعادة تشغيل Toolbox بدون تعديل رمز الوكيل. تعمل الأدوات نفسها على جميع إطارات العمل المتوافقة مع MCP، مثل ADK أو LangGraph أو LlamaIndex.

كتابة إعدادات الأدوات

الآن، علينا إنشاء ملف باسم tools.yaml في "محرّر Cloud Shell" لإعداد ضبط الأدوات.

cloudshell edit tools.yaml

يستخدم الملف YAML متعدد المستندات، وكلّ كتلة مفصولة بـ --- هي مورد مستقل. يحتوي كل مصدر على kind يوضّح نوعه (sources لعمليات ربط قواعد البيانات، وtools للإجراءات التي يمكن أن يستدعيها الوكيل) وtype يحدّد الخلفية (cloud-sql-postgres للمصدر، وpostgres-sql للأدوات المستندة إلى SQL). تشير الأداة إلى مصدرها باستخدام name، وهي الطريقة التي تعرف بها "مجموعة الأدوات" مجموعة الاتصال التي سيتم تنفيذها. تستخدم متغيرات البيئة بنية ${VAR_NAME} ويتم تحديدها عند بدء التشغيل.

لننسخ الآن البرامج النصية التالية أولاً في ملف 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}

---

يحدّد هذا النص البرمجي المورد التالي:

  • المصدر (jobs-db): يوضّح هذا الحقل لـ "مجموعة الأدوات" كيفية الاتصال بمثيل Cloud SQL PostgreSQL. يستخدم النوع cloud-sql-postgres موصّل Cloud SQL داخليًا، ويتعامل مع المصادقة والاتصالات الآمنة تلقائيًا. يتم تحديد قيم العناصر النائبة ${GOOGLE_CLOUD_PROJECT} و${REGION} و${DB_PASSWORD} من متغيرات البيئة عند بدء التشغيل.

بعد ذلك، أضِف النص البرمجي التالي تحت الرمز --- في ملف 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)."

---

يحدّد هذا النص البرمجي المورد التالي:

  • الأداتان 1 و2 (search-jobs وget-job-details): أدوات طلبات بحث SQL العادية يربط كلّ منها اسم أداة (ما يراه الوكيل) بعبارة SQL ذات معلَمات (ما تنفّذه قاعدة البيانات). تستخدِم المَعلمات العناصر النائبة الموضعية $1 و$2. تنفِّذ "أداة صندوق الأدوات" هذه العمليات كعبارات مُعدّة، ما يمنع إدخال رموز 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): يضبط هذا النموذج "أداة صندوق الأدوات" لاستخدام نموذج gemini-embedding-001 من Gemini لإنشاء عمليات تضمين نصية ذات 3072 بُعدًا. تستخدم Toolbox بيانات الاعتماد التلقائية للتطبيق (ADC) للمصادقة، ولا يلزم توفير مفتاح API في Cloud Shell أو Cloud Run. يجب أن يكون dimension الذي تم ضبطه هنا هو نفسه الذي تم ضبطه سابقًا لإنشاء قاعدة البيانات.

لنتابع، أضِف النص البرمجي التالي تحت الرمز --- في ملف 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

---

يحدّد هذا النص البرمجي المورد التالي:

  • الأداة 3 (search-jobs-by-description): هي أداة بحث متّجه. تحتوي المَعلمة search_query على embeddedBy: gemini-embedding، ما يطلب من "أداة صندوق الأدوات" اعتراض النص الأولي وإرساله إلى نموذج التضمين واستخدام المتّجه الناتج في عبارة SQL. عامل التشغيل <=> هو مسافة جيب التمام في pgvector، وتعني القيم الأصغر أوصافًا أكثر تشابهًا.

أخيرًا، أضِف الأداة الأخيرة تحت الرمز --- في ملف 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

يحدّد هذا النص البرمجي المورد التالي:

  • الأداة 4 (add-job): توضّح عملية استيعاب المتجهات. تحتوي المَعلمة description_vector على حقلَين خاصَّين:
  • valueFromParam: description: تنسخ "أداة المطوّرين" القيمة من المَعلمة description إلى هذه المَعلمة. لا يرى النموذج اللغوي الكبير هذه المَعلمة أبدًا.
  • embeddedBy: gemini-embedding: تدمج "أدوات المطوّرين" النص المنسوخ في متّجه قبل تمريره إلى لغة SQL.

والنتيجة هي أنّ استدعاء أداة واحد يخزّن كلاً من نص الوصف الأولي والتضمين المتجهي، بدون أن يعرف الوكيل أي شيء عن عمليات التضمين.

يفصل تنسيق YAML المتعدد المستندات كل مورد باستخدام ---. يحتوي كل مستند على الحقول kind وname وtype التي تحدّد نوعه. لقد أعددنا مسبقًا كل ما يلي:

  • تحديد قاعدة البيانات المصدر
  • تحديد الأدوات ( الأداة 1 والأداة 2 ) لطلب البحث من قاعدة البيانات باستخدام فلتر عادي
  • تحديد نموذج التضمين
  • تحديد أداة لإجراء البحث عن المتّجهات ( الأداة 3 ) في قاعدة البيانات
  • تحديد أداة لنقل بيانات المتجهات ( الأداة 4 ) إلى قاعدة البيانات

6. تشغيل خادم MCP Toolbox

في الخطوة السابقة، ضبطنا الإعدادات اللازمة لـ "مجموعة أدوات MCP". نحن الآن جاهزون لتشغيل الخادم

التحقّق من البيانات الأولية

قبل بدء استخدام 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 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)

سيفحص هذا النص البرمجي عدد بيانات إعلانات الوظائف وتضمينها. شغِّل النص البرمجي باستخدام الأمر التالي

uv run scripts/verify_seed.py

إذا ظهر لك الناتج التالي في الوحدة الطرفية، يعني ذلك أنّ البيانات جاهزة

Jobs: 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.0.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: 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!"

التحقّق من الأدوات

استخدِم طلب بحث في Toolbox API لإدراج جميع الأدوات المسجّلة:

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

من المفترض أن تظهر الأدوات مع أوصافها ومَعلماتها. كما هو موضّح أدناه

...
       "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": []
        }
...

اختبِر أداة 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'

يجب أن يتضمّن الرد وظيفتَي هندسة الخلفية من البيانات الأولية.

[
  {
    "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. إنشاء وكيل ADK

الآن، سنستخدم ADK في Python لهذا المشروع، لذا لنضِف التبعيات المطلوبة:

uv add google-adk==1.29.0 toolbox-adk==1.0.0
  • google-adk: حزمة تطوير الوكلاء من Google، بما في ذلك حزمة تطوير البرامج (SDK) الخاصة بـ Gemini
  • toolbox-adk: دمج حزمة تطوير التطبيقات (ADK) مع MCP Toolbox لقواعد البيانات

إنشاء بنية دليل الوكيل

يتوقّع ADK تخطيط مجلد محدّد: دليل مسمّى باسم وكيلك يحتوي على __init__.py وagent.py و.env. للمساعدة في ذلك، يتضمّن الأمر المدمج التالي لإنشاء البنية بسرعة:

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

يجب أن يبدو دليلك الآن على النحو التالي:

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

بعد ذلك، سنحتاج إلى دمج وكيل ADK مع خادم Toolbox قيد التشغيل واختبار جميع الأدوات الأربع، أي طلبات البحث العادية والبحث الدلالي واستيعاب المتجهات. رمز الوكيل بسيط: يتم تخزين جميع منطق قاعدة البيانات في tools.yaml.

ضبط بيئة الوكيل

يقرأ ADK GOOGLE_GENAI_USE_VERTEXAI وGOOGLE_CLOUD_PROJECT وGOOGLE_CLOUD_LOCATION من بيئة shell التي سبق لك ضبطها في الخطوة السابقة. المتغيّر الوحيد الخاص بالوكيل هو TOOLBOX_URL، لذا أضِفه إلى ملف .env الخاص بالوكيل:

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

تعديل وحدة وكيل المستخدم

افتح jobs_agent/agent.py في "محرِّر Cloud Shell"

cloudshell edit jobs_agent/agent.py

واستبدِل المحتوى بالرمز التالي:

# 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],
)

لاحظ أنّه لا يوجد رمز قاعدة بيانات هنا، إذ يتصل ToolboxToolset بخادم Toolbox عند بدء التشغيل ويحمّل جميع الأدوات المتاحة. يستدعي الوكيل الأدوات بالاسم، وتترجم Toolbox هذه الاستدعاءات إلى طلبات بحث SQL مقابل Cloud SQL.

يتم ضبط قيمة متغيّر البيئة TOOLBOX_URL تلقائيًا على http://127.0.0.1:5000 عند التطوير على الجهاز. عند التوزيع على Cloud Run لاحقًا، يمكنك تجاهل هذا الإعداد واستخدام عنوان URL لخدمة Toolbox على Cloud Run، بدون الحاجة إلى إجراء أي تغييرات في الرمز البرمجي.

تشير التعليمات حاليًا إلى الأداتَين العاديتَين فقط (search-jobs وget-job-details). ستوسّعها في الخطوة التالية عند إضافة أدوات البحث الدلالي والاستيعاب.

اختبار الوكيل

ابدأ واجهة مستخدم مطوّر ADK:

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

افتح عنوان URL المعروض في النافذة الطرفية (عادةً http://localhost:8000) باستخدام ميزة المعاينة على الويب في Cloud Shell أو اضغط على ctrl + النقر على عنوان URL المعروض في النافذة الطرفية. اختَر jobs_agent من القائمة المنسدلة الخاصة بالوكيل في أعلى يمين الصفحة.

اختبار طلبات البحث العادية

جرِّب الطلبات التالية للتحقّق من أدوات SQL العادية:

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

93ac33e7f73aa0b9.png 240c53376042a916.png

جرِّب أوصافًا باللغة الطبيعية لا تتوافق مع دور أو حزمة التكنولوجيا محدّدة:

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

سيحاول الوكيل اختيار الأداة المناسبة استنادًا إلى نوع طلب البحث: تمر الفلاتر المنظَّمة عبر search-jobs، بينما تمر الأوصاف المكتوبة بلغة طبيعية عبر search-jobs-by-description.

b0ea629f5c9b4c26.png

اختبار عرض المتّجهات

اطلب من الموظف إضافة وظيفة جديدة:

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

الآن، جرِّب البحث عنه:

Find me jobs involving autonomous systems and working with physical hardware

تم إنشاء التضمين تلقائيًا أثناء عملية INSERT، بدون الحاجة إلى اتّخاذ خطوة منفصلة.

5a3d8e6f523dc18b.png

أصبح لديك الآن تطبيق توليد معزّز بالاسترجاع (RAG) مستنِد إلى الذكاء الاصطناعي الوكيل يعمل بكامل طاقته ويستخدم ADK وMCP Toolbox وCloudSQL. تهانينا! لننتقل إلى خطوة أخرى لنشر هذه التطبيقات على Cloud Run.

الآن، لنوقف واجهة مستخدم أدوات المطوّرين عن طريق إنهاء العملية من خلال الضغط على Ctrl+C مرّتين قبل المتابعة.

8. النشر على Cloud Run

يعمل كلّ من "الوكيل" و"مجموعة الأدوات" على الجهاز. تؤدي هذه الخطوة إلى نشر كليهما كخدمات Cloud Run حتى يمكن الوصول إليهما عبر الإنترنت. تعمل خدمة Toolbox كخادم MCP على Cloud Run، وتتصل بها خدمة الوكيل.

تحضير "صندوق الأدوات" للنشر

أنشئ دليل نشر لخدمة "أدوات المطوّرين":

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

أنشئ ملف Dockerfile لـ Toolbox. افتح deploy-toolbox/Dockerfile في "محرِّر Cloud Shell":

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":

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 jobs_agent/ jobs_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
jobs_agent/.env
toolbox
tools.yaml
seed.sql
deploy-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

بعد ذلك، يجب التحقّق من أنّ "أدوات المطوّرين" المنشورة تعمل بشكل صحيح:

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

إذا كانت المخرجات معروضة مثل هذا المثال، يعني ذلك أنّ عملية النشر قد نجحت.

{
    "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.",

بعد ذلك، لننشئ الوكيل ونمرّر عنوان URL الخاص بـ Toolbox كمتغيّر بيئة:

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

يقرأ رمز الوكيل TOOLBOX_URL من البيئة (يجب إعداد ذلك مسبقًا). يشير هذا المتغير محليًا إلى http://127.0.0.1:5000، بينما يشير على Cloud Run إلى عنوان URL الخاص بخدمة Toolbox. ولا يلزم إجراء أي تغييرات على الرموز البرمجية.

اختبار الوكيل الذي تم نشره

استرداد عنوان URL الخاص بالوكيل على Cloud Run:

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

افتح عنوان URL في المتصفّح. يتم تحميل واجهة مستخدم مطوّر ADK، وهي الواجهة نفسها التي كنت تستخدمها على جهازك، ولكنها تعمل الآن على Cloud Run.

اختَر jobs_agent من القائمة المنسدلة واختبِرها:

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

يعمل كلا الطلبين من خلال الخدمات التي تم نشرها: يطلب الوكيل على Cloud Run من Toolbox على Cloud Run، الذي يطلب بدوره من Cloud SQL.

9- تهانينا / تنظيف

لقد أنشأت ونشرت مساعدًا ذكيًا للوحات الوظائف يستخدم MCP Toolbox for Databases لربط وكيل ADK بخدمة Cloud SQL PostgreSQL، وذلك باستخدام طلبات البحث بلغة SQL العادية والبحث الدلالي عن المتّجهات.

ما تعلّمته

  • كيفية توحيد MCP لطريقة الوصول إلى الأدوات لوكلاء الذكاء الاصطناعي، وكيفية تطبيق MCP Toolbox for Databases ذلك على عمليات قواعد البيانات تحديدًا، ما يؤدي إلى استبدال رمز قاعدة البيانات المخصّص بإعداد YAML تعريفي
  • كيفية ضبط Cloud SQL PostgreSQL كمصدر بيانات في "أدوات Google" باستخدام نوع المصدر cloud-sql-postgres
  • كيفية تحديد أدوات طلب بحث SQL العادية باستخدام عبارات ذات مَعلمات تمنع حقن SQL
  • كيفية تفعيل البحث عن المتّجهات باستخدام pgvector وgemini-embedding-001، مع المَعلمة embeddedBy لتضمين طلب البحث تلقائيًا
  • الطريقة التي تتيح بها valueFromParam إدخال المتجهات تلقائيًا: يقدّم النموذج اللغوي الكبير وصفًا نصيًا، وتعمل "أداة إنشاء التطبيقات" على نسخ المتجه وتضمينه وتخزينه بصمت إلى جانب النص
  • كيفية تحميل ToolboxToolset في ADK للأدوات من خادم Toolbox قيد التشغيل، مع الحفاظ على الحد الأدنى من رمز الوكيل وفصل منطق قاعدة البيانات بالكامل
  • كيفية نشر كلّ من خادم Toolbox MCP ووكيل ADK على Cloud Run كخدمات منفصلة

إخلاء مساحة

لتجنُّب تحمّل رسوم في حسابك على Google Cloud مقابل الموارد التي تم إنشاؤها في هذا الدرس التطبيقي حول الترميز، يمكنك حذف الموارد الفردية أو حذف المشروع بأكمله.

أسهل طريقة لتنظيف حسابك هي حذف المشروع. يؤدي هذا الإجراء إلى إزالة جميع الموارد المرتبطة بالمشروع.

gcloud projects delete $GOOGLE_CLOUD_PROJECT

الخيار 2: حذف موارد فردية

إذا كنت تريد الاحتفاظ بالمشروع ولكن إزالة الموارد التي تم إنشاؤها في هذا الدرس العملي فقط، اتّبِع الخطوات التالية:

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