Supply Chain Orchestrator mit ADK, AlloyDB und Vertex AI Memory Bank erstellen

1. Übersicht

In diesem Codelab erstellen Sie einen Supply Chain Orchestrator-Agenten. Mit dieser Anwendung können Nutzer Inventar analysieren, Logistik verfolgen und Lieferkettenrisiken in natürlicher Sprache verwalten.

Wir nutzen das Agent Development Kit (ADK) von Google, um eine Multi-Agent-Architektur zu erstellen, die den Kontext beibehält, sich Nutzerpräferenzen über Vertex AI Memory Bank merkt und über die MCP Toolbox mit einem in AlloyDB gespeicherten umfangreichen Datensatz interagiert.

Aufgaben

91e8e53556ac1966.jpeg

Eine Python Flask-Anwendung, die Folgendes umfasst:

Global Orchestrator Agent:Der Stamm-Agent, der den Unterhaltungsverlauf und die Delegierung verwaltet.

Spezialisten-Kundenservicemitarbeiter: „InventorySpecialist“ und „LogisticsManager“ für domainspezifische Aufgaben.

Speicherintegration:Kurzzeitspeicher für Sitzungen und Langzeitspeicher mit Vertex AI Memory Bank.

Narrative UI:Eine Weboberfläche, auf der der Denkprozess des Agents (Trace Context) visualisiert wird.

Lerninhalte

  • So erstellen Sie mit dem Google ADK spezialisierte Agents und Sub-Agents.
  • So integrieren Sie Vertex AI Memory Bank für das Langzeitgedächtnis von Agents.
  • So verwenden Sie die MCP Toolbox, um Agenten mit AlloyDB-Datentools zu verbinden.
  • ADK-Callbacks implementieren, um die Argumentation des Agenten nachzuvollziehen und zu visualisieren
  • So stellen Sie die Lösung mit Cloud Run bereit oder führen sie lokal aus.

Die Architektur

Der Tech-Stack

  1. AlloyDB for PostgreSQL:Dient als leistungsstarke operative Datenbank mit über 50.000 Datensätzen zur Lieferkette. Sie ist die Grundlage für die Vektorsuche und das Abrufen von Informationen.
  2. MCP Toolbox for Databases:Fungiert als „Orchestration Maestro“ und stellt AlloyDB-Daten als ausführbare Tools bereit, die von den Agenten aufgerufen werden können.
  3. Agent Development Kit (ADK): Das Framework, mit dem die Agenten, Anweisungen und Tools definiert werden.
  4. Vertex AI Memory Bank:Bietet langfristig gemerkte Informationen, sodass sich der KI-Agent sitzungsübergreifend an Nutzerpräferenzen und frühere Interaktionen erinnern kann.
  5. Vertex AI Session Service:Verwaltet den kurzfristigen Konversationskontext.

Der Flow

  1. Nutzeranfrage:Der Nutzer stellt eine Frage (z.B. „Check stock for Premium Ice Cream“).
  2. Speicherprüfung:Der Orchestrator prüft den Speicher auf relevante vergangene Informationen (z.B. „Der Nutzer ist Regional Manager für EMEA“).
  3. Delegierung:Der Orchestrator delegiert die Aufgabe an den InventorySpecialist.
  4. Tool-Ausführung:Der Spezialist verwendet Tools, die von der MCP Toolbox bereitgestellt werden, um AlloyDB abzufragen.
  5. Antwort:Der KI-Agent verarbeitet die Daten und gibt eine Markdown-formatierte Tabelle zurück.
  6. Speicher:Wichtige Interaktionen werden im Memory Bank gespeichert.

Voraussetzungen

  • Ein Browser, z. B. Chrome oder Firefox.
  • Google Cloud-Projekt mit aktivierter Abrechnungsfunktion.
  • Grundkenntnisse in SQL und Python.

2. Hinweis

Projekt erstellen

  1. Wählen Sie in der Google Cloud Console auf der Seite zur Projektauswahl ein Google Cloud-Projekt aus oder erstellen Sie eines.
  2. Die Abrechnung für das Cloud-Projekt muss aktiviert sein. So prüfen Sie, ob die Abrechnung für ein Projekt aktiviert ist.
  1. Sie verwenden Cloud Shell, eine Befehlszeilenumgebung, die in Google Cloud ausgeführt wird. Klicken Sie oben in der Google Cloud Console auf „Cloud Shell aktivieren“.

Bild der Schaltfläche „Cloud Shell aktivieren“

  1. Sobald die Verbindung mit der Cloud Shell hergestellt ist, prüfen Sie mit dem folgenden Befehl, ob Sie bereits authentifiziert sind und für das Projekt schon Ihre Projekt-ID eingestellt ist:
gcloud auth list
  1. Führen Sie den folgenden Befehl in Cloud Shell aus, um zu bestätigen, dass der gcloud-Befehl Ihr Projekt kennt.
gcloud config list project
  1. Wenn Ihr Projekt nicht festgelegt ist, verwenden Sie den folgenden Befehl, um es festzulegen:
gcloud config set project <YOUR_PROJECT_ID>
  1. Aktivieren Sie die erforderlichen APIs: Folgen Sie dem Link und aktivieren Sie die APIs.

Alternativ können Sie dazu den gcloud-Befehl verwenden. Informationen zu gcloud-Befehlen und deren Verwendung finden Sie in der Dokumentation.

Wichtige Hinweise und Fehlerbehebung

Das „Geisterprojekt“-Syndrom

Sie haben gcloud config set project ausgeführt, sehen sich aber in der Console-UI ein anderes Projekt an. Prüfen Sie die Projekt-ID im Drop-down-Menü oben links.

Die Abrechnungsbarrikade

Sie haben das Projekt aktiviert, aber das Rechnungskonto vergessen. AlloyDB ist eine leistungsstarke Engine, die nicht startet, wenn der „Benzintank“ (Abrechnung) leer ist.

Verzögerung bei der API-Weitergabe

Sie haben auf „APIs aktivieren“ geklickt, aber in der Befehlszeile wird weiterhin Service Not Enabled angezeigt. Warte 60 Sekunden. Die Cloud braucht einen Moment, um ihre Neuronen zu aktivieren.

Kontingent  – Häufig gestellte Fragen

Wenn Sie ein brandneues Testkonto verwenden, erreichen Sie möglicherweise ein regionales Kontingent für AlloyDB-Instanzen. Wenn us-central1 fehlschlägt, versuchen Sie es mit us-east1.

Verborgener Kundenservicemitarbeiter

Manchmal wird dem AlloyDB-Dienst-Agenten die Rolle aiplatform.user nicht automatisch zugewiesen. Wenn Ihre SQL-Abfragen später nicht mit Gemini kommunizieren können, liegt das in der Regel daran.

3. Datenbank einrichten

Das Herzstück unserer Anwendung ist AlloyDB for PostgreSQL. Wir haben die leistungsstarken Vektorfähigkeiten und die integrierte spaltenbasierte Engine genutzt,um Einbettungen für über 50.000 SCM-Datensätze zu generieren. Dies ermöglicht eine Vektoranalyse in nahezu Echtzeit, sodass unsere Kundenservicemitarbeiter Inventaranomalien oder Logistikrisiken in riesigen Datasets in Millisekunden erkennen können.

In diesem Lab verwenden wir AlloyDB als Datenbank für die Testdaten. Darin werden Cluster verwendet, um alle Ressourcen wie Datenbanken und Logs zu speichern. Jeder Cluster hat eine primäre Instanz, die einen Zugriffspunkt auf die Daten bietet. Tabellen enthalten die tatsächlichen Daten.

Erstellen wir einen AlloyDB-Cluster, eine Instanz und eine Tabelle, in die das Test-Dataset geladen wird.

  1. Klicken Sie auf die Schaltfläche oder kopieren Sie den Link unten in den Browser, in dem der Google Cloud Console-Nutzer angemeldet ist.

Alternativ können Sie in Ihrem Projekt, in dem Sie das Abrechnungskonto eingelöst haben, zum Cloud Shell-Terminal wechseln, das GitHub-Repository klonen und mit den folgenden Befehlen zum Projekt navigieren:

git clone https://github.com/AbiramiSukumaran/easy-alloydb-setup

cd easy-alloydb-setup
  1. Sobald dieser Schritt abgeschlossen ist, wird das Repository in Ihren lokalen Cloud Shell-Editor geklont und Sie können den folgenden Befehl über den Projektordner ausführen. Achten Sie darauf, dass Sie sich im Projektverzeichnis befinden:
sh run.sh
  1. Verwenden Sie jetzt die Benutzeroberfläche (klicken Sie auf den Link im Terminal oder auf den Link „Vorschau im Web“ im Terminal).
  2. Geben Sie die Details für Projekt-ID, Cluster- und Instanznamen ein, um zu beginnen.
  3. Holen Sie sich einen Kaffee, während die Logs durchlaufen. Hier können Sie nachlesen, wie das im Hintergrund funktioniert.

Wichtige Hinweise und Fehlerbehebung

Das Problem mit der Geduld

Datenbankcluster sind eine schwere Infrastruktur. Wenn Sie die Seite aktualisieren oder die Cloud Shell-Sitzung beenden, weil sie „hängt“, kann es passieren, dass eine „Geisterinstanz“ entsteht, die teilweise bereitgestellt wurde und ohne manuellen Eingriff nicht gelöscht werden kann.

Region stimmt nicht überein

Wenn Sie Ihre APIs in us-central1 aktiviert haben, aber versuchen, den Cluster in asia-south1 bereitzustellen, kann es zu Kontingentproblemen oder Verzögerungen bei den Berechtigungen für Dienstkonten kommen. Bleiben Sie für das gesamte Lab bei einer Region.

Zombie-Cluster

Wenn Sie zuvor denselben Namen für einen Cluster verwendet und ihn nicht gelöscht haben, wird im Skript möglicherweise angezeigt, dass der Clustername bereits vorhanden ist. Cluster-Namen müssen innerhalb eines Projekts eindeutig sein.

Cloud Shell-Zeitüberschreitung

Wenn Ihre Kaffeepause 30 Minuten dauert, wechselt Cloud Shell möglicherweise in den Ruhemodus und trennt die Verbindung zum sh run.sh-Prozess. Lassen Sie den Tab aktiv!

4. Schemabereitstellung

Sobald Ihr AlloyDB-Cluster und Ihre Instanz ausgeführt werden, können Sie im SQL-Editor von AlloyDB Studio die KI-Erweiterungen aktivieren und das Schema bereitstellen.

1e3ac974b18a8113.png

Möglicherweise müssen Sie warten, bis die Instanz erstellt wurde. Melden Sie sich dann mit den Anmeldedaten in AlloyDB an, die Sie beim Erstellen des Clusters erstellt haben. Verwenden Sie die folgenden Daten für die Authentifizierung bei PostgreSQL:

  • Nutzername: „postgres
  • Datenbank: „postgres
  • Passwort: „alloydb“ (oder das Passwort, das Sie bei der Erstellung festgelegt haben)

Nachdem Sie sich erfolgreich in AlloyDB Studio authentifiziert haben, werden SQL-Befehle im Editor eingegeben. Sie können mehrere Editorfenster hinzufügen, indem Sie auf das Pluszeichen rechts neben dem letzten Fenster klicken.

28cb9a8b6aa0789f.png

Sie geben Befehle für AlloyDB in Editorfenstern ein und verwenden bei Bedarf die Optionen „Ausführen“, „Formatieren“ und „Löschen“.

Erweiterungen aktivieren

Für die Entwicklung dieser App verwenden wir die Erweiterungen pgvector und google_ml_integration. Mit der pgvector-Erweiterung können Sie Vektoreinbettungen speichern und durchsuchen. Die Erweiterung google_ml_integration bietet Funktionen, mit denen Sie auf Vertex AI-Vorhersageendpunkte zugreifen können, um Vorhersagen in SQL zu erhalten. Aktivieren Sie diese Erweiterungen, indem Sie die folgenden DDLs ausführen:

CREATE EXTENSION IF NOT EXISTS google_ml_integration CASCADE;
CREATE EXTENSION IF NOT EXISTS vector;

Tabelle erstellen

Sie können eine Tabelle mit der folgenden DDL-Anweisung in AlloyDB Studio erstellen:

DROP TABLE IF EXISTS shipments;
DROP TABLE IF EXISTS products;

-- 1. Product Inventory Table

CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
category VARCHAR(100),
stock_level INTEGER,
distribution_center VARCHAR(100),
region VARCHAR(50),
embedding vector(768),
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 2. Logistics & Shipments
CREATE TABLE shipments (
shipment_id SERIAL PRIMARY KEY,
product_id INTEGER REFERENCES products(id),
status VARCHAR(50), -- 'In Transit', 'Delayed', 'Delivered', 'Pending'
estimated_arrival TIMESTAMP,
route_efficiency_score DECIMAL(3, 2)
);

In der Spalte embedding können die Vektorwerte einiger Textfelder gespeichert werden.

Datenaufnahme

Führen Sie die folgenden SQL-Anweisungen aus, um 50.000 Datensätze in die Tabelle „products“ einzufügen:

-- We use a CROSS JOIN pattern with realistic naming segments to create meaningful variety
DO $$
DECLARE
brand_names TEXT[] := ARRAY['Artisan', 'Nature', 'Elite', 'Pure', 'Global', 'Eco', 'Velocity', 'Heritage', 'Aura', 'Summit'];
product_types TEXT[] := ARRAY['Ice Cream', 'Body Wash', 'Laundry Detergent', 'Shampoo', 'Mayonnaise', 'Deodorant', 'Tea', 'Soup', 'Face Cream', 'Soap'];
variants TEXT[] := ARRAY['Classic', 'Gold', 'Premium', 'Eco-Friendly', 'Organic', 'Night-Repair', 'Extra-Fresh', 'Zero-Sugar', 'Sensitive', 'Maximum-Strength'];
regions TEXT[] := ARRAY['EMEA', 'APAC', 'LATAM', 'NAMER'];
dcs TEXT[] := ARRAY['London-Hub', 'Mumbai-Central', 'Sao-Paulo-Logistics', 'Singapore-Port', 'Rotterdam-Gate', 'New-York-DC'];
BEGIN
INSERT INTO products (name, category, stock_level, distribution_center, region)
SELECT
b || ' ' || v || ' ' || t as name,
CASE
WHEN t IN ('Ice Cream', 'Mayonnaise', 'Tea', 'Soup') THEN 'Food & Refreshment'
WHEN t IN ('Body Wash', 'Shampoo', 'Deodorant', 'Face Cream', 'Soap') THEN 'Personal Care'
ELSE 'Home Care'
END as category,
floor(random() * 20000 + 100)::int as stock_level,
dcs[floor(random() * 6 + 1)] as distribution_center,
regions[floor(random() * 4 + 1)] as region
FROM
unnest(brand_names) b,
unnest(variants) v,
unnest(product_types) t,
generate_series(1, 50); -- 10 * 10 * 10 * 50 = 50,000 records
END $$;

Wir fügen demospezifische Datensätze ein, um vorhersehbare Antworten auf Fragen im Executive-Stil zu erhalten.

-- These ensure you have predictable answers for specific "Executive" questions
INSERT INTO products (name, category, stock_level, distribution_center, region) VALUES
('Magnum Ultra Gold Limited Edition', 'Food & Refreshment', 45, 'Rotterdam-Gate', 'EMEA'),
('Dove Pro-Health Deep Moisture', 'Personal Care', 12000, 'Mumbai-Central', 'APAC'),
('Hellmanns Real Organic Mayonnaise', 'Food & Refreshment', 8000, 'London-Hub', 'EMEA');

Versanddaten einfügen

-- Shipments Generation (More shipments than products)
INSERT INTO shipments (product_id, status, estimated_arrival, route_efficiency_score)
SELECT
id,
CASE
WHEN random() > 0.8 THEN 'Delayed'
WHEN random() > 0.4 THEN 'In Transit'
ELSE 'Delivered'
END,
NOW() + (random() * 10 || ' days')::interval,
(random() * 0.5 + 0.5)::decimal(3,2)
FROM products
WHERE random() > 0.3; -- Create shipments for ~70% of products


-- Add duplicate shipments for some products to show complex logistics
INSERT INTO shipments (product_id, status, estimated_arrival, route_efficiency_score)
SELECT id, 'In Transit', NOW() + INTERVAL '12 days', 0.88
FROM products
LIMIT 5000;

Berechtigung gewähren

Führen Sie die folgende Anweisung aus, um die Ausführung der Funktion „embedding“ zu gewähren:

GRANT EXECUTE ON FUNCTION embedding TO postgres;

AlloyDB-Dienstkonto die Rolle „Vertex AI User“ gewähren

Gewähren Sie in der Google Cloud IAM-Konsole dem AlloyDB-Dienstkonto (das so aussieht: service-<<PROJECT_NUMBER>>@gcp-sa-alloydb.iam.gserviceaccount.com) Zugriff auf die Rolle „Vertex AI-Nutzer“. PROJECT_NUMBER enthält Ihre Projektnummer.

Alternativ können Sie den folgenden Befehl im Cloud Shell-Terminal ausführen:

PROJECT_ID=$(gcloud config get-value project)


gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:service-$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")@gcp-sa-alloydb.iam.gserviceaccount.com" \
--role="roles/aiplatform.user"

Einbettungen generieren

Als Nächstes generieren wir Vektoreinbettungen für bestimmte aussagekräftige Textfelder:

WITH
 rows_to_update AS (
 SELECT
   id
 FROM
   products
 WHERE
   embedding IS NULL
 LIMIT
   5000 )
UPDATE
 products
SET
 embedding = ai.embedding('text-embedding-005', name || ' ' || category || ' ' || distribution_center || ' ' || region)::vector
FROM
 rows_to_update
WHERE
 products.id = rows_to_update.id
 AND embedding IS null;

In der obigen Anweisung haben wir das Limit auf 5.000 festgelegt. Führen Sie die Anweisung also wiederholt aus, bis in der Tabelle keine Zeile mehr vorhanden ist, in der die Spalte „embedding“ den Wert NULL hat.

Wichtige Hinweise und Fehlerbehebung

Die „Passwort vergessen“-Schleife

Wenn Sie die Einrichtung mit nur einem Klick verwendet haben und sich nicht mehr an Ihr Passwort erinnern, rufen Sie in der Konsole die Seite „Instance basic information“ (Allgemeine Informationen zur Instanz) auf und klicken Sie auf „Edit“ (Bearbeiten), um das postgres-Passwort zurückzusetzen.

Fehler „Erweiterung nicht gefunden“

Wenn CREATE EXTENSION fehlschlägt, liegt das oft daran, dass sich die Instanz nach der ersten Bereitstellung noch im Status „Wartung“ oder „Wird aktualisiert“ befindet. Prüfen Sie, ob der Schritt zur Instanzerstellung abgeschlossen ist, und warten Sie gegebenenfalls einige Sekunden.

IAM-Weitergabeverzögerung

Sie haben den IAM-Befehl gcloud ausgeführt, aber der SQL-Befehl CALL schlägt weiterhin mit einem Berechtigungsfehler fehl. Es kann einige Zeit dauern, bis IAM-Änderungen über das Google-Backbone übertragen werden. Atme tief durch.

Falsche Vektordimension

Die Tabelle items ist auf VECTOR(768) festgelegt. Wenn Sie später versuchen, ein anderes Modell zu verwenden, z. B. ein Modell mit 1.536 Dimensionen, werden Ihre Einfügungen explodieren. Halte dich an text-embedding-005.

Tippfehler in der Projekt-ID

Wenn Sie im create_model-Aufruf die Klammern « » weglassen oder Ihre Projekt-ID falsch eingeben, wird die Modellregistrierung zwar als erfolgreich angezeigt, schlägt aber bei der ersten tatsächlichen Anfrage fehl. Überprüfen Sie den String noch einmal.

5. Tools und Toolbox einrichten

Die MCP Toolbox für Datenbanken ist ein Open-Source-MCP-Server für Datenbanken. Damit können Sie Tools einfacher, schneller und sicherer entwickeln, da Komplexitäten wie Connection Pooling, Authentifizierung und mehr abgedeckt werden. Mit der Toolbox können Sie GenAI-Tools erstellen, mit denen Ihre Agenten auf Daten in Ihrer Datenbank zugreifen können.

Wir verwenden die MCP-Toolbox (Model Context Protocol) für Datenbanken als „Dirigent“. Sie fungiert als standardisierte Middleware zwischen unseren Agents und AlloyDB. Durch die Definition einer tools.yaml-Konfiguration werden komplexe Datenbankvorgänge automatisch als übersichtliche, ausführbare Tools wie search_products_by_context oder check_inventory_levels in der Toolbox verfügbar gemacht. Dadurch ist kein manuelles Connection Pooling oder Boilerplate-SQL in der Agentenlogik mehr erforderlich.

Toolbox-Server installieren

Erstellen Sie im Cloud Shell-Terminal einen Ordner zum Speichern der neuen YAML-Datei für die Tools und der Toolbox-Binärdatei:

mkdir scm-agent-toolbox

cd scm-agent-toolbox

Führen Sie in diesem neuen Ordner die folgenden Befehle aus:

# see releases page for other versions
export VERSION=0.27.0
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox

Erstellen Sie als Nächstes die Datei tools.yaml in diesem neuen Ordner. Rufen Sie dazu den Cloud Shell-Editor auf und kopieren Sie den Inhalt dieser Repository-Datei in die Datei „tools.yaml“.

sources:
    supply_chain_db:
        kind: "alloydb-postgres"
        project: "YOUR_PROJECT_ID"
        region: "us-central1"
        cluster: "YOUR_CLUSTER"
        instance: "YOUR_INSTANCE"
        database: "postgres"
        user: "postgres"
        password: "YOUR_PASSWORD"

tools:
  search_products_by_context:
    kind: postgres-sql
    source: supply_chain_db
    description: Find products in the inventory using natural language search and vector embeddings.
    parameters:
      - name: search_text
        type: string
        description: Description of the product or category the user is looking for.
    statement: |
     SELECT name, category, stock_level, distribution_center, region
      FROM products
      ORDER BY embedding <=> ai.embedding('text-embedding-005', $1)::vector
      LIMIT 5;

  check_inventory_levels:
    kind: postgres-sql
    source: supply_chain_db
    description: Get precise stock levels for a specific product name.
    parameters:
      - name: product_name
        type: string
        description: The exact or partial name of the product.
    statement: |
     SELECT name, stock_level, distribution_center, last_updated
      FROM products
      WHERE name ILIKE '%' || $1 || '%'
      ORDER BY stock_level DESC;

  track_shipment_status:
    kind: postgres-sql
    source: supply_chain_db
    description: Retrieve real-time logistics and shipping status for a specific region or product.
    parameters:
      - name: region
        type: string
        description: The geographical region to filter shipments (e.g., EMEA, APAC).
    statement: |
     SELECT p.name, s.status, s.estimated_arrival, s.route_efficiency_score
      FROM shipments s
      JOIN products p ON s.product_id = p.id
      WHERE p.region = $1
      ORDER BY s.estimated_arrival ASC;

  analyze_supply_chain_risk:
    kind: postgres-sql
    source: supply_chain_db
    description: Rerank and filter shipments based on risk profiles and efficiency scores using Google ML reranker.
    parameters:
      - name: risk_context
        type: string
        description: The business context for risk analysis (e.g., 'heatwave impact' or 'port strike').
    statement: |
     WITH initial_ranking AS (
      SELECT s.shipment_id, p.name, s.status, p.distribution_center,
      ROW_NUMBER() OVER () AS ref_number
      FROM shipments s
      JOIN products p ON s.product_id = p.id
      WHERE s.status != 'Delivered'
      LIMIT 10
      ),
      reranked_results AS (
      SELECT index, score FROM
      ai.rank(
      model_id => 'semantic-ranker-default-003',
      search_string => $1,
      documents => (SELECT ARRAY_AGG(name || ' at ' || distribution_center ORDER BY ref_number) FROM initial_ranking)
      )
      )
      SELECT i.name, i.status, i.distribution_center, r.score
      FROM initial_ranking i, reranked_results r
      WHERE i.ref_number = r.index
      ORDER BY r.score DESC;

toolsets:
   supply_chain_toolset:
     - search_products_by_context
     - check_inventory_levels
     - track_shipment_status
     - analyze_supply_chain_risk

Testen Sie nun die Datei „tools.yaml“ auf dem lokalen Server:

./toolbox --tools-file "tools.yaml"

Alternativ können Sie es in der Benutzeroberfläche testen.

./toolbox --ui

Perfekt! Wenn Sie sicher sind, dass alles funktioniert, können Sie die Anwendung wie unten beschrieben in Cloud Run bereitstellen.

Cloud Run-Bereitstellung

  1. Legen Sie die Umgebungsvariable PROJECT_ID fest:
export PROJECT_ID="my-project-id"
  1. Initialisieren Sie die gcloud CLI:
gcloud init
gcloud config set project $PROJECT_ID
  1. Folgende APIs müssen aktiviert sein:
gcloud services enable run.googleapis.com \
                       cloudbuild.googleapis.com \
                       artifactregistry.googleapis.com \
                       iam.googleapis.com \
                       secretmanager.googleapis.com
  1. Erstellen Sie ein Backend-Dienstkonto, falls Sie noch keines haben:
gcloud iam service-accounts create toolbox-identity
  1. Berechtigungen zur Verwendung von Secret Manager erteilen:
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member serviceAccount:toolbox-identity@$PROJECT_ID.iam.gserviceaccount.com \
    --role roles/secretmanager.secretAccessor
  1. Gewähren Sie dem Dienstkonto zusätzliche Berechtigungen, die für unsere AlloyDB-Quelle spezifisch sind (roles/alloydb.client und roles/serviceusage.serviceUsageConsumer).
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member serviceAccount:toolbox-identity@$PROJECT_ID.iam.gserviceaccount.com \
    --role roles/alloydb.client


gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member serviceAccount:toolbox-identity@$PROJECT_ID.iam.gserviceaccount.com \
    --role serviceusage.serviceUsageConsumer
  1. Laden Sie „tools.yaml“ als Secret hoch:
gcloud secrets create tools-scm-agent --data-file=tools.yaml
  1. Wenn Sie bereits ein Secret haben und die Secret-Version aktualisieren möchten, führen Sie Folgendes aus:
gcloud secrets versions add tools-scm-agent --data-file=tools.yaml
  1. Legen Sie eine Umgebungsvariable für das Container-Image fest, das Sie für Cloud Run verwenden möchten:
export IMAGE=us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:latest
  1. Stellen Sie die Toolbox mit dem folgenden Befehl in Cloud Run bereit:

Wenn Sie öffentlichen Zugriff in Ihrer AlloyDB-Instanz aktiviert haben (nicht empfohlen), folgen Sie dem Befehl unten für das Deployment in Cloud Run:

gcloud run deploy toolbox-scm-agent \
    --image $IMAGE \
    --service-account toolbox-identity \
    --region us-central1 \
    --set-secrets "/app/tools.yaml=tools-scm-agent:latest" \
    --args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080" \
    --allow-unauthenticated

Wenn Sie ein VPC-Netzwerk verwenden, verwenden Sie den folgenden Befehl:

gcloud run deploy toolbox-scm-agent \
    --image $IMAGE \
    --service-account toolbox-identity \
    --region us-central1 \
    --set-secrets "/app/tools.yaml=tools-scm-agent:latest" \
    --args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080" \
    # TODO(dev): update the following to match your VPC details
    --network <<YOUR_NETWORK_NAME>> \
    --subnet <<YOUR_SUBNET_NAME>> \
    --allow-unauthenticated

6. Agent-Einrichtung

Mit dem Agent Development Kit (ADK) haben wir uns von monolithischen Prompts hin zu einer spezialisierten Architektur mit mehreren Agenten verabschiedet:

  • InventorySpecialist: Schwerpunkt auf Produktbestand und Lagermesswerten.
  • LogisticsManager: Experte für globale Versandrouten und Risikoanalysen.
  • GlobalOrchestrator: Das „Gehirn“, das Aufgaben delegiert und Ergebnisse zusammenfasst.

Klonen Sie dieses Repository in Ihr Projekt und sehen wir uns die einzelnen Schritte an.

Führen Sie zum Klonen dieses Projekts im Cloud Shell-Terminal (im Stammverzeichnis oder an einem beliebigen anderen Ort, an dem Sie das Projekt erstellen möchten) den folgenden Befehl aus:

git clone https://github.com/AbiramiSukumaran/scm-memory-agent
  1. Dadurch sollte das Projekt erstellt werden. Sie können dies im Cloud Shell Editor überprüfen.

53a398aff6ba7d5b.png

  1. Achten Sie darauf, dass Sie die .env-Datei mit den Werten für Ihr Projekt und Ihre Instanz aktualisieren.

Schritt-für-Schritt-Anleitung zum Code

Kurzer Überblick über den Orchestrator-Agent

    Go to app.py and you should be able to see the following snippet:
orchestrator = adk.Agent(
    name="GlobalOrchestrator",
    model="gemini-2.5-flash",
    description="Global Supply Chain Orchestrator root agent.",
    instruction="""
    You are the Global Supply Chain Brain. You are responsible for products, inventory and logistics.
    You also have access to the memory tool, remember to include all the information that the tool can provide you with about the user before you respond.
    1. Understand intent and delegate to specialists. As the Global Orchestrator, you have access to the full conversation history with the user.
    When you transfer a query to a specialist agent, sub agent or tool, share the important facts and information from your memory to them so they can operate with the full context. 
    2. Ensure the final response is professional and uses Markdown tables for data.
    3. If a specialist provides a long list, ensure only the top 10 items are shown initially.
    4. Conclude with a brief, high-level executive summary of what the data implies.
    """,
    tools=[adk.tools.preload_memory_tool.PreloadMemoryTool()],
    sub_agents=[inventory_agent, logistics_agent],
    
    #after_agent_callback=auto_save_session_to_memory_callback,
)

Dieses Snippet ist die Definition für den Stamm, also den Orchestrator-Agent, der die Unterhaltung oder Anfrage vom Nutzer empfängt und je nach Aufgabe an den entsprechenden untergeordneten Agent oder Nutzer weiterleitet.

  1. Sehen wir uns den Inventar-Agenten an.
inventory_agent = adk.Agent(
    name="InventorySpecialist",
    model="gemini-2.5-flash",
    description="Specialist in product stock and warehouse data.",
    instruction="""
    Analyze inventory levels.
    1. Use 'search_products_by_context' or 'check_inventory_levels'.
    2. ALWAYS format results as a clean Markdown table.
    3. If there are many results, display only the TOP 10 most relevant ones.
    4. At the end, state: 'There are additional records available. Would you like to see more?'
    """,
    tools=tools
)

Dieser spezielle untergeordnete Agent ist auf Inventaraktivitäten wie die kontextbezogene Suche nach Produkten und die Überprüfung von Inventarbeständen spezialisiert.

  1. Dann gibt es noch den Logistik-Untervertreter:
logistics_agent = adk.Agent(
    name="LogisticsManager",
    model="gemini-2.5-flash",
    description="Expert in global shipping routes and logistics tracking.",
    instruction="""
    Check shipment statuses.
    1. Use 'track_shipment_status' or 'analyze_supply_chain_risk'.
    2. ALWAYS format results as a clean Markdown table.
    3. Limit initial output to the top 10 shipments.
    4. Ask if the user needs the full manifest if more results exist.
    """,
    tools=tools
)

Dieser spezielle Untervertreter ist auf Logistikaktivitäten wie die Sendungsverfolgung und die Analyse von Risiken in der Lieferkette spezialisiert.

  1. Alle drei Agents, die wir bisher besprochen haben, verwenden Tools. Auf Tools wird über unseren Toolbox-Server verwiesen, den wir bereits im vorherigen Abschnitt bereitgestellt haben. Hier ein Beispiel:
from toolbox_core import ToolboxSyncClient

TOOLBOX_SERVER = os.environ["TOOLBOX_SERVER"]
TOOLBOX_TOOLSET = os.environ["TOOLBOX_TOOLSET"]

# --- ADK TOOLBOX CONFIGURATION ---
toolbox = ToolboxSyncClient(TOOLBOX_SERVER)
tools = toolbox.load_toolset(TOOLBOX_TOOLSET)

Dieser spezielle Untervertreter ist auf Logistikaktivitäten wie die Sendungsverfolgung und die Analyse von Risiken in der Lieferkette spezialisiert.

7. Agent Engine

Agent Engine bei der ersten Ausführung erstellen

import vertexai

GOOGLE_CLOUD_PROJECT = os.environ["GOOGLE_CLOUD_PROJECT"]
GOOGLE_CLOUD_LOCATION = os.environ["GOOGLE_CLOUD_LOCATION"]

client = vertexai.Client(
  project=GOOGLE_CLOUD_PROJECT,
  location=GOOGLE_CLOUD_LOCATION
)

agent_engine = client.agent_engines.create()
  1. Aktualisieren Sie für den nächsten Lauf die Agent Engine mit der Memory Bank-Konfiguration:
agent_engine = client.agent_engines.update(
    name=APP_NAME,
    config={
        "context_spec": {
            "memory_bank_config": {
                "generation_config": {
                    "model": f"projects/{PROJECT_ID}/locations/{GOOGLE_CLOUD_LOCATION}/publishers/google/models/gemini-2.5-flash"
                }
            }
        }
    })

8. Kontext, Ausführung und Arbeitsspeicher

Die Kontextverwaltung ist in zwei separate Ebenen unterteilt, damit sich der Agent wie ein kontinuierlicher Partner und nicht wie ein zustandsloser Bot anfühlt:

Kurzzeitgedächtnis (Sitzungen): Wird über VertexAiSessionService verwaltet und erfasst den unmittelbaren Ereignisverlauf (Nutzernachrichten, Tool-Antworten) innerhalb einer einzelnen Interaktion.

Langzeitspeicher (Memory Bank): Basierend auf der Vertex AI Memory Bank über adk.memorybankservice. In dieser Ebene werden „aussagekräftige“ Informationen wie die bevorzugten Versandunternehmen eines Nutzers oder wiederkehrende Lagerverzögerungen extrahiert und sitzungsübergreifend gespeichert.

Sitzung für Sitzungsspeicher im Rahmen der Unterhaltung initialisieren

Dieser Teil des Snippets erstellt die Sitzung für die aktuelle App für den aktuellen Nutzer.

from google.adk.sessions import VertexAiSessionService

...

session_service = VertexAiSessionService(
    project=PROJECT_ID,
    location=GOOGLE_CLOUD_LOCATION,
)

...

# Initialize the session *outside* of the route handler to avoid repeated creation
session = None
session_lock = threading.Lock()

async def initialize_session():
    global session
    try:
        session = await session_service.create_session(app_name=APP_NAME, user_id=USER_ID)
        print(f"Session {session.id} created successfully.")  # Add a log
    except Exception as e:
        print(f"Error creating session: {e}")
        session = None  # Ensure session is None in case of error

# Create the session on app startup
asyncio.run(initialize_session())

Vertex AI Memory Bank für langfristigen Speicher initialisieren

In diesem Teil des Snippets wird das Vertex AI Memory Bank Service-Objekt für die Agent Engine instanziiert.

from google.adk.memory import InMemoryMemoryService
from google.adk.memory import VertexAiMemoryBankService

...

try:
    memory_bank_service = adk.memory.VertexAiMemoryBankService(
        agent_engine_id=AGENT_ENGINE_ID,
        project=PROJECT_ID,
        location=GOOGLE_CLOUD_LOCATION,
    )
    #in_memory_service = InMemoryMemoryService()
    print("Memory Bank Service initialized successfully.")
except Exception as e:
    print(f"Error initializing Memory Bank Service: {e}")
    memory_bank_service = None

runner = adk.Runner(
    agent=orchestrator,
    app_name=APP_NAME,
    session_service=session_service,
    memory_service=memory_bank_service,
)

...

Was wird konfiguriert?

In diesem Teil des Snippets konfigurieren wir den Vertex AI Memory Bank Service für das Langzeitgedächtnis. Die Sitzung für die jeweilige App für den jeweiligen Nutzer wird kontextbezogen als Erinnerung in der Vertex AI-Speicherbank gespeichert.

Was wird im Rahmen der Agent-Ausführung ausgeführt?

   async def run_and_collect():
        final_text = ""
        try:
            async for event in runner.run_async(
                new_message=content,
                user_id=user_id,
                session_id=session_id
            ):
                if hasattr(event, 'author') and event.author:
                    if not any(log['agent'] == event.author for log in execution_logs):
                        execution_logs.append({
                            "agent": event.author,
                            "action": "Analyzing data requirements...",
                            "type": "orchestration_event"
                        })
                if hasattr(event, 'text') and event.text:
                    final_text = event.text
                elif hasattr(event, 'content') and hasattr(event.content, 'parts'):
                    for part in event.content.parts:
                        if hasattr(part, 'text') and part.text:
                            final_text = part.text
        except Exception as e:
            print(f"Error during runner.run_async: {e}")
            raise  # Re-raise the exception to signal failure
        finally:
            gc.collect()
            return final_text

Die Eingabeinhalte des Nutzers werden in das Objekt „new_message“ mit der Nutzer-ID und der Sitzungs-ID im Bereich verarbeitet. Anschließend übernimmt der Agent und die Agent-Antwort wird verarbeitet und zurückgegeben.

Was wird im Langzeitgedächtnis gespeichert?

Die Sitzungsdetails im Bereich der App und des Nutzers werden in der Sitzungsvariable extrahiert.

Diese Sitzung wird dann als Speicher für den aktuellen Nutzer für die aktuelle App des Vertex AI Memory Bank-Objekts mit der Methode „add_session_to_memory“ hinzugefügt.

session = asyncio.run(session_service.get_session(app_name=APP_NAME, user_id=USER_ID, session_id=session.id))

if memory_bank_service and session:  # Check memory service AND session
                try:
                    #asyncio.run(in_memory_service.add_session_to_memory(session))
                    asyncio.run(memory_bank_service.add_session_to_memory(session))
                    '''
                    client.agent_engines.memories.generate(
                        scope={"app_name": APP_NAME, "user_id": USER_ID},
                        name=APP_NAME,
                        direct_contents_source={
                            "events": [
                                {"content": content}
                            ]
                        },
                        config={"wait_for_completion": True},
                    )   
                    '''

                    print("Successfully added session to memory.******")
                    print(session.id)

                except Exception as e:
                    print(f"Error adding session to memory: {e}")

Abruf von Informationen aus dem Gedächtnis

Wir müssen den gespeicherten Langzeitspeicher mit dem App-Namen und dem Nutzernamen als Bereich abrufen, da dies der Bereich ist, für den wir die Erinnerungen gespeichert haben. So können wir ihn als Teil des Kontexts an den Orchestrator und gegebenenfalls andere Agents übergeben.

    results = client.agent_engines.memories.retrieve(
    name=APP_NAME,
    scope={"app_name": APP_NAME, "user_id": USER_ID}
    )
    # RetrieveMemories returns a pager. You can use `list` to retrieve all pages' memories.
    list(results)
    print(list(results))

Wie wird die abgerufene Erinnerung als Teil des Kontexts geladen?

Wir verwenden das folgende Attribut in der Definition des Orchestrator-Agents, damit der Stamm-Agent den Kontext aus dem Memory Bank vorab laden kann. Dies gilt zusätzlich zu den Tools, auf die wir über den Toolbox-Server für die untergeordneten Kundenservicemitarbeiter zugreifen.

tools=[adk.tools.preload_memory_tool.PreloadMemoryTool()],

Callback-Kontext

In einer Unternehmenslieferkette darf es keine „Blackbox“ geben. Wir verwenden den CallbackContext des ADK, um eine Narrative Engine zu erstellen. Durch das Einbinden in die Ausführung des Agents erfassen wir jeden Denkprozess und jeden Tool-Aufruf und streamen sie an eine Seitenleiste in der Benutzeroberfläche.

  • Trace-Ereignis: „GlobalOrchestrator is analyzing data requirements...“
  • Trace-Ereignis: „Delegating to InventorySpecialist for stock levels...“ (Delegiere an InventorySpecialist für Lagerbestände…)
  • Trace-Ereignis: „Retrieving historical supplier delay patterns from Memory Bank...“ (Abrufen von Mustern für Lieferantenverzögerungen aus Memory Bank...)

Dieser Prüfpfad ist für das Debugging von unschätzbarem Wert und sorgt dafür, dass menschliche Bediener den autonomen Entscheidungen des Agents vertrauen können.

from google.adk.agents.callback_context import CallbackContext

...

# --- ADK CALLBACKS (Narrative Engine) ---
execution_logs = []

async def trace_callback(context: CallbackContext):
    """
    Captures agent and tool invocation flow for the UI narrative.
    """
    agent_name = context.agent.name
    event = {
        "agent": agent_name,
        "action": "Processing request steps...",
        "type": "orchestration_event"
    }
    execution_logs.append(event)
    return None

...

Das war es!!! Wir haben das Projekt erfolgreich geklont und die Details des Agents, des Speichers und des Kontexts durchgegangen.

Sie können dies testen, indem Sie zum Projektordner des geklonten Repositorys wechseln und die folgenden Befehle ausführen:

>> pip install -r requirements.txt

>> python app.py

Dadurch sollte Ihr Agent lokal gestartet werden und Sie können ihn testen.

9. In Cloud Run bereitstellen

  1. Stellen Sie die App in Cloud Run bereit, indem Sie den folgenden Befehl im Cloud Shell-Terminal ausführen, in dem das Projekt geklont wurde. Achten Sie darauf, dass Sie sich im Stammordner des Projekts befinden.

Führen Sie diesen Befehl in Ihrem Cloud Shell-Terminal aus:

gcloud run deploy supply-chain-agent --source . --platform managed   --region us-central1 --allow-unauthenticated --set-env-vars GOOGLE_CLOUD_PROJECT=<<YOUR_PROJECT>>,GOOGLE_CLOUD_LOCATION=us-central1,GOOGLE_GENAI_USE_VERTEXAI=TRUE,REASONING_ENGINE_APP_NAME=<<YOUR_APP_ENGINE_URL>>,TOOLBOX_SERVER=<<YOUR_TOOLBOX_SERVER>>,TOOLBOX_TOOLSET=supply_chain_toolset,AGENT_ENGINE_ID=<<YOUR_AGENT_ENGINE_ID>>

Ersetzen Sie die Werte für die Platzhalter <<YOUR_PROJECT>>, <<YOUR_APP_ENGINE_URL>>, <<YOUR_TOOLBOX_SERVER>> und <<YOUR_AGENT_ENGINE_ID>>.

Nach Abschluss des Befehls wird eine Dienst-URL ausgegeben. Kopieren.

  1. Weisen Sie dem Cloud Run-Dienstkonto die Rolle AlloyDB-Client zu.So kann Ihre serverlose Anwendung einen sicheren Tunnel zur Datenbank herstellen.

Führen Sie diesen Befehl in Ihrem Cloud Shell-Terminal aus:

# 1. Get your Project ID and Project Number
PROJECT_ID=$(gcloud config get-value project)
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")

# 2. Grant the AlloyDB Client role
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
--role="roles/alloydb.client"

Verwenden Sie nun die Dienst-URL (den zuvor kopierten Cloud Run-Endpunkt), um die App zu testen.

Hinweis:Wenn ein Dienstproblem auftritt und der Arbeitsspeicher als Grund angegeben wird, versuchen Sie, das zugewiesene Speicherlimit auf 1 GiB zu erhöhen, um das Problem zu beheben.

3e4d36ed99b39325.png

d6b337f79a1f1d82.png

5e781a193a4aa903.png

10. Bereinigen

Vergessen Sie nicht, den AlloyDB-Cluster und die AlloyDB-Instanz zu löschen, wenn Sie dieses Lab abgeschlossen haben.

Dadurch sollte der Cluster zusammen mit seinen Instanzen bereinigt werden.

11. Glückwunsch

Durch die Kombination der Geschwindigkeit von AlloyDB, der Orchestrierungseffizienz der MCP-Toolbox und des „institutionellen Gedächtnisses“ von Vertex AI Memory Bank haben wir ein sich ständig weiterentwickelndes Supply-Chain-System entwickelt. Gemini beantwortet nicht nur Fragen, sondern erinnert sich auch daran, dass Ihr Lager in Singapur immer mit monsunbedingten Verzögerungen zu kämpfen hat, und schlägt proaktiv vor, Sendungen umzuleiten, bevor Sie überhaupt fragen.