Build a Supply Chain Orchestrator with ADK, AlloyDB, and Vertex AI Memory Bank

1. סקירה כללית

ב-Codelab הזה תלמדו איך ליצור סוכן Supply Chain Orchestrator. האפליקציה הזו מאפשרת למשתמשים לנתח את המלאי, לעקוב אחרי לוגיסטיקה ולנהל סיכונים בשרשרת האספקה באמצעות שפה טבעית.

נשתמש ב-Agent Development Kit ‏ (ADK) של Google כדי לבנות ארכיטקטורה של כמה סוכנים ששומרת על ההקשר, זוכרת את העדפות המשתמשים באמצעות Vertex AI Memory Bank ומתקשרת עם מערך נתונים עצום שמאוחסן ב-AlloyDB באמצעות MCP Toolbox.

מה תפַתחו

91e8e53556ac1966.jpeg

אפליקציית Python Flask שכוללת:

סוכן התזמור הגלובלי: סוכן הבסיס שמנהל את זרימת השיחה והאצלת הסמכויות.

סוכנים מומחים: סוכנים כמו InventorySpecialist ו-LogisticsManager למשימות ספציפיות לדומיין.

שילוב זיכרון: זיכרון לטווח קצר של סשן וזיכרון לטווח ארוך באמצעות Vertex AI Memory Bank.

ממשק משתמש נרטיבי: ממשק אינטרנט שמציג באופן חזותי את תהליך החשיבה של הסוכן (הקשר של המעקב).

מה תלמדו

  • איך משתמשים ב-Google ADK כדי ליצור סוכנים ותת-סוכנים ייעודיים.
  • איך משלבים את Vertex AI Memory Bank כדי להגדיר זיכרון ארוך טווח של סוכן.
  • איך משתמשים ב-MCP Toolbox כדי לקשר סוכנים לכלי נתונים של AlloyDB.
  • איך מטמיעים קריאות חוזרות (callback) של ADK כדי לעקוב אחרי הנימוקים של הסוכן ולהציג אותם באופן חזותי.
  • איך פורסים את הפתרון באמצעות Cloud Run או מפעילים אותו באופן מקומי.

הארכיטקטורה

הסטאק הטכנולוגי

  1. AlloyDB ל-PostgreSQL: משמש כמסד נתונים תפעולי עם ביצועים גבוהים שמכיל יותר מ-50,000 רשומות של שרשרת אספקה. הוא מפעיל את החיפוש והאחזור של וקטורים.
  2. ערכת הכלים של MCP למסדי נתונים: משמשת כ "מנהל התזמור", ומציגה נתונים של AlloyDB ככלים שניתנים להרצה והסוכנים יכולים להפעיל.
  3. Agent Development Kit (ADK): המסגרת שמשמשת להגדרת הסוכנים, ההוראות והכלים.
  4. Vertex AI Memory Bank: מספק Memory Bank לטווח ארוך, ומאפשר לסוכן להיזכר בהעדפות המשתמשים ובאינטראקציות קודמות בין הפעלות.
  5. Vertex AI Session Service: מנהל את ההקשר של שיחות לטווח קצר.

הזרימה

  1. שאילתת משתמש: המשתמש שואל שאלה (לדוגמה, "תבדוק אם יש במלאי גלידה פרימיום").
  2. בדיקת הזיכרון: ה-Orchestrator בודק את Memory Bank כדי למצוא מידע רלוונטי מהעבר (לדוגמה, "המשתמש הוא מנהל אזורי באזור EMEA").
  3. העברה: המנהל מעביר את המשימה למומחה המלאי.
  4. הפעלת הכלי: המומחה משתמש בכלים שמופיעים בארגז הכלים של MCP כדי לשלוח שאילתות ל-AlloyDB.
  5. תשובה: הסוכן מעבד את הנתונים ומחזיר טבלה בפורמט Markdown.
  6. אחסון הזיכרון: אינטראקציות משמעותיות נשמרות ב-Memory Bank.

דרישות

  • דפדפן, כמו Chrome או Firefox.
  • פרויקט ב-Google Cloud שהחיוב בו מופעל.
  • היכרות בסיסית עם SQL ו-Python.

‫2. לפני שמתחילים

יצירת פרויקט

  1. ב-מסוף Google Cloud, בדף לבחירת הפרויקט, בוחרים או יוצרים פרויקט ב-Google Cloud.
  2. מוודאים שהחיוב מופעל בפרויקט ב-Cloud. כך בודקים אם החיוב מופעל בפרויקט.
  1. תשתמשו ב-Cloud Shell, סביבת שורת פקודה שפועלת ב-Google Cloud. לוחצים על 'הפעלת Cloud Shell' בחלק העליון של מסוף Google Cloud.

תמונה של לחצן ההפעלה של Cloud Shell

  1. אחרי שמתחברים ל-Cloud Shell, בודקים שכבר בוצע אימות ושהפרויקט מוגדר למזהה הפרויקט באמצעות הפקודה הבאה:
gcloud auth list
  1. מריצים את הפקודה הבאה ב-Cloud Shell כדי לוודא שפקודת gcloud מכירה את הפרויקט.
gcloud config list project
  1. אם הפרויקט לא מוגדר, משתמשים בפקודה הבאה כדי להגדיר אותו:
gcloud config set project <YOUR_PROJECT_ID>
  1. מפעילים את ממשקי ה-API הנדרשים: לוחצים על הקישור ומפעילים את ממשקי ה-API.

אפשר גם להשתמש בפקודת gcloud. אפשר לעיין במאמרי העזרה בנושא פקודות gcloud ושימוש בהן.

נקודות חשובות ופתרון בעיות

תסמונת 'פרויקט הרפאים'

הפעלת את הפקודה gcloud config set project, אבל בפועל את מסתכלת על פרויקט אחר בממשק המשתמש של המסוף. צריך לבדוק את מזהה הפרויקט בתפריט הנפתח שבפינה הימנית העליונה.

מחסום בחיוב

הפעלתם את הפרויקט, אבל שכחתם להוסיף חשבון לחיוב. ‫AlloyDB הוא מנוע עם ביצועים גבוהים, והוא לא יופעל אם 'מיכל הדלק' (החיוב) ריק.

השהיה בהפצת API

לחצתם על 'הפעלת ממשקי API', אבל בשורת הפקודה עדיין מופיעה ההודעה Service Not Enabled. מחכים 60 שניות. יעבור רגע עד שהנוירונים בענן יתעוררו.

מכסה Quags

אם אתם משתמשים בחשבון ניסיון חדש לגמרי, יכול להיות שתגיעו למכסה אזורית של מופעי AlloyDB. אם הפעולה us-central1 נכשלת, מנסים את הפעולה us-east1.

סוכן שירות 'מוסתר'

לפעמים סוכן השירות של AlloyDB לא מקבל אוטומטית את התפקיד aiplatform.user. אם שאילתות ה-SQL לא יכולות לתקשר עם Gemini בהמשך, בדרך כלל זו הסיבה.

3. הגדרת מסד נתונים

בליבת האפליקציה שלנו נמצא AlloyDB ל-PostgreSQL. השתמשנו ביכולות הווקטוריות המתקדמות שלו ובמנוע העמודות המשולב כדי ליצור הטמעות עבור 50,000 רשומות SCM ומעלה. הניתוח הווקטורי מתבצע כמעט בזמן אמת, וכך הסוכנים שלנו יכולים לזהות חריגות במלאי או סיכונים לוגיסטיים במערכי נתונים עצומים בתוך אלפיות השנייה.

בשיעור ה-Lab הזה נשתמש ב-AlloyDB כבסיס הנתונים של נתוני הבדיקה. הוא משתמש באשכולות כדי להכיל את כל המשאבים, כמו מסדי נתונים ויומנים. לכל אשכול יש מופע ראשי שמספק נקודת גישה לנתונים. הטבלאות יכילו את הנתונים בפועל.

ניצור אשכול, מכונה וטבלה ב-AlloyDB שבהם ייטען מערך הנתונים של הבדיקה.

  1. לוחצים על הלחצן או מעתיקים את הקישור שלמטה לדפדפן שבו המשתמש מחובר למסוף Google Cloud.

לחלופין, אפשר לעבור אל Cloud Shell Terminal מהפרויקט שבו מימשתם את החשבון לחיוב, לשכפל את מאגר GitHub ולעבור אל הפרויקט באמצעות הפקודות הבאות:

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

cd easy-alloydb-setup
  1. אחרי שהשלב הזה יסתיים, המאגר ישוכפל לעורך המקומי של Cloud Shell ותוכלו להריץ את הפקודה שלמטה מתוך תיקיית הפרויקט (חשוב לוודא שאתם בספריית הפרויקט):
sh run.sh
  1. עכשיו משתמשים בממשק המשתמש (לוחצים על הקישור במסוף או על הקישור 'תצוגה מקדימה באינטרנט' במסוף).
  2. כדי להתחיל, מזינים את הפרטים של מזהה הפרויקט, האשכול ושמות המופעים.
  3. אפשר ללכת לשתות קפה בזמן שהיומנים מתגללים, וכאן אפשר לקרוא איך זה קורה מאחורי הקלעים.

נקודות חשובות ופתרון בעיות

הבעיה של 'סבלנות'

אשכולות של מסדי נתונים הם תשתית כבדה. אם תרעננו את הדף או תסיימו את הסשן ב-Cloud Shell כי נראה שהוא נתקע, יכול להיות שתקבלו מופע 'רפאים' שהוקצה באופן חלקי ואי אפשר למחוק אותו בלי התערבות ידנית.

חוסר התאמה באזור

אם הפעלתם את ממשקי ה-API ב-us-central1 אבל ניסיתם להקצות את האשכול ב-asia-south1, יכול להיות שתיתקלו בבעיות שקשורות למכסות או בעיכובים בהרשאות של חשבון השירות. חשוב להשתמש באותו אזור לאורך כל שיעור ה-Lab.

Zombie Clusters

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

פסק זמן ב-Cloud Shell

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

4. הקצאת הרשאות לסכימה

אחרי שמפעילים את האשכול ואת המופע של AlloyDB, עוברים אל כלי העריכה של SQL ב-AlloyDB Studio כדי להפעיל את תוספי ה-AI ולספק את הסכימה.

1e3ac974b18a8113.png

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

  • שם משתמש : "postgres"
  • מסד נתונים : "postgres"
  • סיסמה: alloydb (או כל סיסמה אחרת שהגדרתם בזמן היצירה)

אחרי שתעברו בהצלחה את תהליך האימות ב-AlloyDB Studio, תוכלו להזין פקודות SQL בכלי העריכה. אפשר להוסיף כמה חלונות של Editor באמצעות סימן הפלוס שמימין לחלון האחרון.

28cb9a8b6aa0789f.png

מזינים פקודות ל-AlloyDB בחלונות של כלי העריכה, ומשתמשים באפשרויות Run (הפעלה), Format (עיצוב) ו-Clear (ניקוי) לפי הצורך.

הפעלת תוספים

כדי ליצור את האפליקציה הזו, נשתמש בתוספים pgvector ו-google_ml_integration. התוסף pgvector מאפשר לכם לאחסן ולחפש הטמעות של וקטורים. התוסף google_ml_integration מספק פונקציות שמשמשות לגישה לנקודות קצה של חיזוי ב-Vertex AI כדי לקבל חיזויים ב-SQL. מפעילים את התוספים האלה על ידי הפעלת פקודות ה-DDL הבאות:

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

צור טבלה

אתם יכולים ליצור טבלה באמצעות הצהרת ה-DDL שבהמשך ב-AlloyDB Studio:

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)
);

בעמודה embedding אפשר לאחסן את ערכי הווקטור של חלק משדות הטקסט.

הטמעת נתונים

מריצים את קבוצת הצהרות ה-SQL הבאה כדי להוסיף 50,000 רשומות לטבלת המוצרים:

-- 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 $$;

אפשר להוסיף רשומות ספציפיות להדגמה כדי להבטיח תשובות צפויות לשאלות בסגנון מנהלים

-- 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');

הוספת נתוני משלוחים

-- 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;

מתן הרשאה

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

GRANT EXECUTE ON FUNCTION embedding TO postgres;

נותנים לחשבון השירות של AlloyDB את התפקיד Vertex AI User

במסוף IAM של Google Cloud, מעניקים לחשבון השירות של AlloyDB (שנראה כך: service-<<PROJECT_NUMBER>>@gcp-sa-alloydb.iam.gserviceaccount.com) גישה לתפקיד Vertex AI User. ‫PROJECT_NUMBER יכיל את מספר הפרויקט.

אפשר גם להריץ את הפקודה הבאה מ-Cloud Shell Terminal:

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"

יצירת הטמעות

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

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;

בהצהרה שלמעלה הגדרנו את המגבלה כ-5,000, לכן חשוב להריץ אותה שוב ושוב עד שלא תהיה שורה בטבלה עם הטמעת העמודה כ-NULL.

נקודות חשובות ופתרון בעיות

הלולאה של 'שכחתי את הסיסמה'

אם השתמשתם בהגדרה 'קליק אחד' ואתם לא זוכרים את הסיסמה, עוברים לדף 'פרטים בסיסיים של מופע' במסוף ולוחצים על 'עריכה' כדי לאפס את הסיסמה של postgres.

השגיאה 'התוסף לא נמצא'

אם הפעולה CREATE EXTENSION נכשלת, לרוב זה קורה כי המופע עדיין במצב 'תחזוקה' או 'עדכון' מההקצאה הראשונית. בודקים אם שלב יצירת המופע הושלם וממתינים כמה שניות אם צריך.

הפער בהפצת IAM

הפעלתם את פקודת IAM‏ gcloud, אבל פקודת ה-SQL‏ CALL עדיין נכשלת עם שגיאת הרשאה. יכול לעבור זמן מה עד שהשינויים ב-IAM יתעדכנו בכל מערכות Google. כדאי לקחת רגע.

חוסר התאמה במאפיין וקטור

הטבלה items מוגדרת כ-VECTOR(768). אם תנסו להשתמש במודל אחר (למשל מודל עם 1,536 מימדים) בהמשך, התוספים שלכם יתפוצצו. כדאי להשתמש ב-text-embedding-005.

שגיאת הקלדה במזהה הפרויקט

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

5. כלים והגדרת ארגז הכלים

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

אנחנו משתמשים בארגז הכלים Model Context Protocol (MCP) for Databases בתור "מנצח". הוא משמש כתוכנת ביניים סטנדרטית בין הסוכנים שלנו לבין AlloyDB. הגדרת tools.yaml מאפשרת ל-toolbox לחשוף באופן אוטומטי פעולות מורכבות במסד הנתונים ככלים נקיים וניתנים להרצה, כמו search_products_by_context או check_inventory_levels. כך אין צורך בניהול ידני של מאגר חיבורים או ב-SQL סטנדרטי בתוך הלוגיקה של הסוכן.

התקנת שרת Toolbox

במסוף Cloud Shell, יוצרים תיקייה לשמירת קובץ ה-YAML של כלי חדש וקובץ הבינארי של ארגז הכלים:

mkdir scm-agent-toolbox

cd scm-agent-toolbox

מתוך התיקייה החדשה, מריצים את קבוצת הפקודות הבאה:

# 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

לאחר מכן יוצרים את הקובץ tools.yaml בתוך התיקייה החדשה. כדי לעשות זאת, עוברים אל Cloud Shell Editor ומעתיקים את התוכן של קובץ המאגר הזה אל הקובץ 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

עכשיו בודקים את הקובץ tools.yaml בשרת המקומי:

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

אפשר גם לבדוק אותו בממשק המשתמש

./toolbox --ui

מושלם!! אחרי שמוודאים שהכל עובד, אפשר לפרוס את האפליקציה ב-Cloud Run באופן הבא.

פריסה ב-Cloud Run

  1. מגדירים את משתנה הסביבה PROJECT_ID:
export PROJECT_ID="my-project-id"
  1. מאתחלים את ה-CLI של gcloud:
gcloud init
gcloud config set project $PROJECT_ID
  1. צריך להפעיל את ממשקי ה-API הבאים:
gcloud services enable run.googleapis.com \
                       cloudbuild.googleapis.com \
                       artifactregistry.googleapis.com \
                       iam.googleapis.com \
                       secretmanager.googleapis.com
  1. אם עדיין אין לכם חשבון שירות לקצה העורפי, יוצרים חשבון שירות לקצה העורפי:
gcloud iam service-accounts create toolbox-identity
  1. נותנים הרשאות לשימוש במנהל הסודות:
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member serviceAccount:toolbox-identity@$PROJECT_ID.iam.gserviceaccount.com \
    --role roles/secretmanager.secretAccessor
  1. נותנים לחשבון השירות הרשאות נוספות שספציפיות למקור AlloyDB שלנו (roles/alloydb.client ו-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. מעלים את הקובץ tools.yaml כסוד:
gcloud secrets create tools-scm-agent --data-file=tools.yaml
  1. אם כבר יש לכם סוד ואתם רוצים לעדכן את גרסת הסוד, מריצים את הפקודה הבאה:
gcloud secrets versions add tools-scm-agent --data-file=tools.yaml
  1. מגדירים משתנה סביבה לקובץ אימג' של קונטיינר שרוצים להשתמש בו ב-Cloud Run:
export IMAGE=us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:latest
  1. מפעילים את Toolbox ב-Cloud Run באמצעות הפקודה הבאה:

אם הפעלתם גישה ציבורית במופע AlloyDB (לא מומלץ), אתם יכולים להשתמש בפקודה הבאה כדי לבצע פריסה ב-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

אם אתם משתמשים ברשת VPC, משתמשים בפקודה הבאה:

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. הגדרת נציג

באמצעות הערכה לפיתוח סוכנים (ADK), עברנו מהנחיות מונוליטיות לארכיטקטורה מיוחדת של כמה סוכנים:

  • InventorySpecialist: מתמקד במלאי המוצרים ובמדדים של מחסנים.
  • LogisticsManager: מומחה בנתיבי משלוח גלובליים ובניתוח סיכונים.
  • GlobalOrchestrator: ה "מוח" שמשתמש בחשיבה רציונלית כדי להקצות משימות ולזקק ממצאים.

משכפלים את המאגר הזה לפרויקט.

כדי לשכפל את הפרויקט, מריצים את הפקודה הבאה בטרמינל של Cloud Shell (בספריית הבסיס או בכל מקום שבו רוצים ליצור את הפרויקט):

git clone https://github.com/AbiramiSukumaran/scm-memory-agent
  1. הפעולה הזו אמורה ליצור את הפרויקט, ואפשר לוודא זאת בעורך Cloud Shell.

53a398aff6ba7d5b.png

  1. חשוב לעדכן את קובץ ‎ .env בערכים של הפרויקט והמופע.

הסבר מפורט על הקוד

סקירה מהירה של 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,
)

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

  1. בואו נבחן את סוכן המלאי
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
)

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

  1. לאחר מכן יש את סוכן המשנה ללוגיסטיקה:
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
)

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

  1. כל שלושת הסוכנים שדיברנו עליהם עד עכשיו משתמשים בכלים, והכלים מפנים לשרת Toolbox שכבר פרסנו בקטע הקודם. אפשר לעיין בקטע הקוד לדוגמה שבהמשך:
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)

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

7. Agent Engine

יצירת מנוע הסוכן בהרצה הראשונית

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. לריצה הבאה, מעדכנים את Agent Engine עם ההגדרה Memory Bank:
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. הקשר, הפעלה וזיכרון

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

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

זיכרון לטווח ארוך (מאגר זיכרון): מבוסס על מאגר הזיכרון של Vertex AI דרך adk.memorybankservice. השכבה הזו מחלצת מידע 'משמעותי' – כמו העדפה של משתמש לגבי חברות משלוחים ספציפיות או עיכובים חוזרים במחסן – ושומרת אותו בין סשנים.

הפעלת זיכרון הפעלה בשיחה

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

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 לזיכרון לטווח ארוך

זהו החלק בקטע הקוד שיוצר מופע של אובייקט Vertex AI Memory Bank Service עבור מנוע הסוכן.

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

...

מה מוגדר?

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

מה מופעל כחלק מהרצת הסוכן?

   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

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

מה מאוחסן בזיכרון לטווח ארוך?

פרטי הסשן בהיקף האפליקציה והמשתמש מחולצים במשתנה הסשן.

הסשן הזה מתווסף לזיכרון של המשתמש הנוכחי באפליקציה הנוכחית של אובייקט Vertex AI Memory Bank באמצעות השיטה add_session_to_memory.

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}")

שליפת מידע מהזיכרון

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

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

איך הזיכרון שאוחזר נטען כחלק מההקשר?

אנחנו משתמשים במאפיין הבא בהגדרה של סוכן Orchestrator, שמאפשר לסוכן הבסיסי לטעון מראש את ההקשר מ-Memory Bank. בנוסף לכלים שאנחנו מקבלים גישה אליהם משרת ארגז הכלים עבור סוכני המשנה.

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

הקשר של השיחה החוזרת

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

  • אירוע מעקב: GlobalOrchestrator מנתח את דרישות הנתונים...
  • אירוע מעקב: "העברה למומחה מלאי לבדיקת רמות המלאי..."
  • אירוע מעקב: 'שליפת דפוסי עיכוב היסטוריים של ספקים מ-Memory Bank...'

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

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

...

זהו!!! הצלחנו לשכפל את הפרויקט והסברנו את הפרטים של הסוכן, הזיכרון וההקשר.

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

>> pip install -r requirements.txt

>> python app.py

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

9. בואו נפרוס אותו ב-Cloud Run

  1. פורסים אותו ב-Cloud Run על ידי הפעלת הפקודה הבאה מ-Cloud Shell Terminal שבו הפרויקט משוכפל, מוודאים שאתם בתוך תיקיית הבסיס של הפרויקט.

מריצים את הפקודה הבאה בטרמינל Cloud Shell:

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

מחליפים את הערכים של placeholders <<YOUR_PROJECT>>, <<YOUR_APP_ENGINE_URL>>, <<YOUR_TOOLBOX_SERVER>> ו- <<YOUR_AGENT_ENGINE_ID>>

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

  1. מקצים לחשבון השירות של Cloud Run את התפקיד AlloyDB Client.כך האפליקציה שלכם ללא שרת יכולה ליצור מנהרה מאובטחת למסד הנתונים.

מריצים את הפקודה הבאה בטרמינל Cloud Shell:

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

עכשיו משתמשים בכתובת ה-URL של השירות (נקודת הקצה של Cloud Run שהעתקתם קודם) ובודקים את האפליקציה.

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

3e4d36ed99b39325.png

d6b337f79a1f1d82.png

5e781a193a4aa903.png

10. הסרת המשאבים

אחרי שמסיימים את ה-Lab הזה, חשוב למחוק את אשכול AlloyDB ואת המכונה.

הוא צריך לנקות את האשכול יחד עם המכונות שלו.

11. מזל טוב

שילבנו את המהירות של AlloyDB, את יעילות התיאום של MCP Toolbox ואת הזיכרון המוסדי של Vertex AI Memory Bank, כדי לבנות מערכת שרשרת אספקה שמתפתחת. הוא לא רק עונה על שאלות, אלא גם זוכר שהמחסן שלכם בסינגפור תמיד מתמודד עם עיכובים שקשורים למונסון, ומציע באופן יזום לשנות את מסלול המשלוחים עוד לפני שאתם שואלים.