Building Persistent AI Agents with ADK and CloudSQL

1. מבוא

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

זו ארכיטקטורת המערכת שנבנה

a98bbd65ddedd29c.jpeg

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

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

מה תלמדו

  • יצירת סוכן AI באמצעות Agent Development Kit ‏ (ADK) של Google עם כלים בהתאמה אישית
  • מגדירים כלים שקוראים וכותבים את מצב הסשן באמצעות ToolContext
  • הבחנה בין מצב ברמת הסשן לבין מצב ברמת המשתמש (הקידומת user:)
  • הקצאת מכונה של Cloud SQL PostgreSQL וחיבור אליה מ-Cloud Shell
  • מעבר מאחסון מקומי (שהוא ברירת המחדל כשמשתמשים בפקודה adk web) אל DatabaseSessionService לאחסון קבוע עם מסד נתונים ייעודי
  • איך מוודאים שהזיכרון של הסוכן נשמר גם אחרי הפעלה מחדש של האפליקציה וגם בין סשנים נפרדים של שיחות

מה תצטרכו

  • מחשב תקין וחיבור אמין לאינטרנט.
  • דפדפן, כמו Chrome, כדי לגשת אל Google Cloud Console
  • ראש פתוח וסקרנות, ורצון ללמוד.

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

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

פתיחת Cloud Shell

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

הממשק אמור להיראות כך

86307fac5da2f077.png

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

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

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

# Create your working directory
mkdir -p ~/build-agent-adk-cloudsql

# Change cloudshell workspace and working directory into previously created dir
cloudshell workspace ~/build-agent-adk-cloudsql && cd ~/build-agent-adk-cloudsql

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

ccc3214812750f1c.png

הגדרת פרויקט Google Cloud ומשתני סביבה ראשוניים

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

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

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

bash setup_verify_trial_project.sh && source .env

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

54b615cd15f2a535.png

אחרי שמחכים זמן מה, אם הפלט הזה מופיע במסוף, אפשר לעבור לשלב הבא e576b4c13d595156.png

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

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

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

9e11ee21cd23405f.png

הפעלת ממשקי ה-API הנדרשים

מפעילים את ממשקי Google Cloud API שנדרשים ל-codelab הזה:

gcloud services enable \
  aiplatform.googleapis.com \
  sqladmin.googleapis.com \
  compute.googleapis.com
  • Vertex AI API ‏ (aiplatform.googleapis.com) – הסוכן משתמש במודלים של Gemini דרך Vertex AI.
  • Cloud SQL Admin API ‏ (sqladmin.googleapis.com) – אתם מקצים ומנהלים מופע PostgreSQL לאחסון קבוע.
  • Compute Engine API ‏ (compute.googleapis.com) – נדרש ליצירת מופעים של Cloud SQL.

הגדרת האזור של Gemini ושל מוצרי Cloud

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

# This is for our Gemini endpoint
echo "GOOGLE_CLOUD_LOCATION=global" >> .env

# This is for our other Cloud products
echo "REGION=us-central1" >> .env

source .env

אפשר להמשיך לשלב הבא

3. הגדרה של Cloud SQL

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

התחלת יצירת המופע

מוסיפים את הסיסמה של מסד הנתונים לקובץ ‎.env ומטעינים אותו מחדש. נשתמש ב-cafe-agent-pwd-2025 כסיסמה.

echo "DB_PASSWORD=cafe-agent-pwd-2025" >> .env
source .env

מריצים את הפקודה הזו כדי ליצור מכונת Cloud SQL PostgreSQL. התהליך נמשך כמה דקות – צריך להשאיר אותו פועל ולהמשיך לקטע הבא.

gcloud sql instances create cafe-concierge-db \
  --database-version=POSTGRES_17 \
  --edition=ENTERPRISE \
  --region=${REGION} \
  --availability-type=ZONAL \
  --project=${GOOGLE_CLOUD_PROJECT} \
  --tier=db-f1-micro \
  --root-password=${DB_PASSWORD} \
  --quiet &

כמה הערות לגבי הפקודה שלמעלה:

  • db-f1-micro הוא הרמה הקטנה (והזולה) ביותר ב-Cloud SQL – והיא מספיקה ל-Codelab הזה.
  • --root-password מגדיר את הסיסמה למשתמש ברירת המחדל של postgres.
  • הסיומת & בפקודה מריצה את הפקודה ברקע כדי שתוכלו להמשיך לעבוד.

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

b01e3fbd89f17332.png

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

cd ~/build-agent-adk-cloudsql
bash setup_verify_trial_project.sh && source .env

אחר כך, נמשיך לקטע הבא.

4. יצירת סוכן קונסיירז' בבית קפה

בשלב הזה נוצר מבנה הפרויקט של סוכן ADK ומוגדרת תוכנת Cafe Concierge בסיסית עם כלי לתפריט.

אתחול פרויקט Python

ב-codelab הזה נעשה שימוש ב-uv, מנהל חבילות Python מהיר שמטפל בסביבות וירטואליות וביחסי תלות בכלי אחד. הוא מותקן מראש ב-Cloud Shell.

מאתחלים פרויקט Python ומוסיפים את ADK כתלות:

uv init
uv add google-adk==1.25.0 asyncpg

uv init יוצר pyproject.toml וסביבה וירטואלית. ‫uv add מתקין את התלות ומתעד אותה ב-pyproject.toml.

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

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

ל-ADK יש פקודה מובנית שתעזור לכם להגדיר את זה במהירות. מריצים את הפקודה הבאה

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

הפקודה הזו תיצור מבנה של נציג עם gemini-2.5-flash בתור המוח. עכשיו הספריה אמורה להיראות כך:

build-agent-adk-cloudsql/
├── cafe_concierge/
│   ├── __init__.py
│   ├── agent.py
│   └── .env
├── pyproject.toml
├── .env      
├── .venv/
└── ...

כתיבה לסוכן

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

cloudshell edit cafe_concierge/agent.py

ומחליפים את הקובץ בקוד הבא

# cafe_concierge/agent.py
from google.adk.agents import LlmAgent
from google.adk.tools import ToolContext

CAFE_MENU = {
    "espresso": {
        "price": 3.50,
        "description": "Rich and bold single shot",
        "tags": ["vegan", "dairy-free", "gluten-free"],
    },
    "latte": {
        "price": 5.00,
        "description": "Espresso with steamed milk",
        "tags": ["gluten-free"],
    },
    "oat milk latte": {
        "price": 5.50,
        "description": "Espresso with steamed oat milk",
        "tags": ["vegan", "dairy-free", "gluten-free"],
    },
    "cappuccino": {
        "price": 4.50,
        "description": "Espresso with equal parts steamed milk and foam",
        "tags": ["gluten-free"],
    },
    "cold brew": {
        "price": 4.00,
        "description": "Slow-steeped for 12 hours, served over ice",
        "tags": ["vegan", "dairy-free", "gluten-free"],
    },
    "matcha latte": {
        "price": 5.50,
        "description": "Ceremonial grade matcha with steamed milk",
        "tags": ["gluten-free"],
    },
    "croissant": {
        "price": 3.00,
        "description": "Buttery, flaky French pastry",
        "tags": [],
    },
    "banana bread": {
        "price": 3.50,
        "description": "Homemade with walnuts",
        "tags": ["vegan"],
    },
}


def get_menu() -> dict:
    """Returns the full cafe menu with prices, descriptions, and dietary tags.

    Use this tool when the customer asks what's available, wants to see
    the menu, or asks about specific items.
    """
    return CAFE_MENU


root_agent = LlmAgent(
    name="cafe_concierge",
    model="gemini-2.5-flash",
    instruction="""You are a friendly and knowledgeable barista at "The Cloud Cafe".

Your job:
- Help customers browse the menu and answer questions about items.
- Take coffee and food orders.
- Remember and respect dietary preferences.

Be conversational, warm, and concise. If a customer mentions a dietary
restriction, acknowledge it and suggest suitable options from the menu.
""",
    tools=[get_menu],
)

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

אימות הפעלת הסוכן

מפעילים את ממשק המשתמש של ADK dev מספריית העבודה:

cd ~/build-agent-adk-cloudsql
uv run adk web

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

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

What's on the menu?

376ee6b189657e7a.png

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

5. הוספת ניהול הזמנות עם שמירת מצב

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

הסבר על אירועים ומצבים של סשנים

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

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

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

כך הם קשורים זה לזה:

cd9871699451867d.png

הכלים קוראים את הסטטוס וכותבים אותו באמצעות ToolContext – אובייקט ש-ADK מוסיף באופן אוטומטי לכל פונקציית כלי שמצהירה עליו כפרמטר. אתם לא יוצרים אותו בעצמכם. באמצעות tool_context.state, כלי יכול לקרוא ולכתוב את טיוטת המצב של הסשן. ה-ADK בודק את חתימת הפונקציה: פרמטרים עם סוג ToolContext מוזרקים, וכל שאר הפרמטרים מאוכלסים על ידי מודל שפה גדול (LLM) על סמך השיחה.

כשכלי כותב ל-tool_context.state, ה-ADK מתעד את השינוי הזה כ-state_delta בתוך האירוע. לאחר מכן, המערכת של SessionService מחילה את הדלתא על המצב הנוכחי של הסשן. כלומר, תמיד אפשר לעקוב אחרי שינויים במצב עד לאירוע שגרם להם. ההגדרה הזו חלה גם על סוגים אחרים של הקשר, כמו callback_context

הסבר על קידומות של מדינות

מפתחות מצב משתמשים בקידומות כדי לשלוט בהיקף שלהם:

קידומת

היקף

האם הוא שורד הפעלה מחדש? ‫(עם DB)

(none)

הסשן הנוכחי בלבד

כן

user:

כל הסשנים של המשתמש הזה

כן

app:

כל הסשנים, כל המשתמשים

כן

temp:

הפעלה נוכחית בלבד

לא

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

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

פותחים את הקובץ cafe_concierge/agent.py ב-Cloud Shell Editor.

cloudshell edit cafe_concierge/agent.py

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

# cafe_concierge/agent.py (add below get_menu, above root_agent)

def place_order(tool_context: ToolContext, items: list[str]) -> dict:
    """Places an order for the specified menu items.

    Use this tool when the customer confirms they want to order something.

    Args:
        tool_context: Provided automatically by ADK.
        items: A list of menu item names the customer wants to order.
    """
    valid_items = []
    invalid_items = []
    total = 0.0

    for item in items:
        item_lower = item.lower()
        if item_lower in CAFE_MENU:
            valid_items.append(item_lower)
            total += CAFE_MENU[item_lower]["price"]
        else:
            invalid_items.append(item)

    if not valid_items:
        return {"error": f"None of these items are on our menu: {invalid_items}"}

    order = {"items": valid_items, "total": round(total, 2)}
    tool_context.state["current_order"] = order

    result = {"order": order}
    if invalid_items:
        result["warning"] = f"These items are not on our menu: {invalid_items}"
    return result


def get_order_summary(tool_context: ToolContext) -> dict:
    """Returns the current order summary for this session.

    Use this tool when the customer asks about their current order,
    wants to review what they ordered, or asks for the total.

    Args:
        tool_context: Provided automatically by ADK.
    """
    order = tool_context.state.get("current_order")
    if order:
        return {"order": order}
    return {"message": "No order has been placed yet in this session."}


def set_dietary_preference(tool_context: ToolContext, preference: str) -> dict:
    """Saves a dietary preference that persists across all conversations.

    Use this tool when the customer mentions a dietary restriction or
    preference (e.g., "I'm vegan", "I'm lactose intolerant",
    "I have a nut allergy").

    Args:
        tool_context: Provided automatically by ADK.
        preference: The dietary preference to save (e.g., "vegan",
            "lactose intolerant", "nut allergy").
    """
    existing = tool_context.state.get("user:dietary_preferences", [])
    if not isinstance(existing, list):
        existing = []

    preference_lower = preference.lower().strip()
    if preference_lower not in existing:
        existing.append(preference_lower)

    tool_context.state["user:dietary_preferences"] = existing
    return {
        "saved": preference_lower,
        "all_preferences": existing,
    }


def get_dietary_preferences(tool_context: ToolContext) -> dict:
    """Retrieves the customer's saved dietary preferences.

    Use this tool when you need to check the customer's dietary
    restrictions before making recommendations.

    Args:
        tool_context: Provided automatically by ADK.
    """
    preferences = tool_context.state.get("user:dietary_preferences", [])
    if preferences:
        return {"preferences": preferences}
    return {"message": "No dietary preferences saved yet."}

שני דברים שכדאי לשים לב אליהם:

  1. place_order ו-get_order_summary משתמשים במקשים ללא קידומת (current_order). המצב הזה קשור להפעלה הנוכחית – שיחה חדשה מתחילה עם הזמנה ריקה.
  2. set_dietary_preference ו-get_dietary_preferences משתמשים בקידומת user: (user:dietary_preferences). המצב הזה משותף לכל הסשנים של אותו משתמש.

עדכון הסוכן באמצעות כלים והוראות חדשים

מחליפים את ההגדרה הקיימת של root_agent בחלק התחתון של הקובץ בהגדרה הבאה:

# cafe_concierge/agent.py (replace the existing root_agent)

root_agent = LlmAgent(
    name="cafe_concierge",
    model="gemini-2.5-flash",
    instruction="""You are a friendly and knowledgeable barista at "The Cloud Cafe".

Your job:
- Help customers browse the menu and answer questions about items.
- Take coffee and food orders.
- Remember and respect dietary preferences.

The customer's saved dietary preferences are: {user:dietary_preferences?}

IMPORTANT RULES:
- When a customer mentions a dietary restriction, ALWAYS save it using the
  set_dietary_preference tool before doing anything else.
- Before recommending items, check the customer's dietary preferences. If they
  have preferences saved, only recommend items compatible with those
  restrictions. Check the menu item tags to determine compatibility.
- When placing an order, confirm the items and total with the customer.

Be conversational, warm, and concise.
""",
    tools=[
        get_menu,
        place_order,
        get_order_summary,
        set_dietary_preference,
        get_dietary_preferences,
    ],
)

בהוראה נעשה שימוש בתבנית להחדרת מצב {user:dietary_preferences?} כדי להחדיר את ההעדפות השמורות של הלקוח ישירות להנחיה.

אימות הקובץ המלא

הפרמטר cafe_concierge/agent.py צריך לכלול עכשיו:

  • המילון לCAFE_MENU
  • חמש פונקציות של כלי: get_menu, place_order, get_order_summary, set_dietary_preference, get_dietary_preferences
  • ההגדרה של root_agent עם כל חמשת הכלים

6. בדיקת הסוכן באמצעות ממשק המשתמש של ADK Dev

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

הפעלת ממשק המשתמש למפתחים

cd ~/build-agent-adk-cloudsql
uv run adk web

פותחים את התצוגה המקדימה באינטרנט ביציאה 8000 ובוחרים באפשרות cafe_concierge מהתפריט הנפתח.

שיחה 1: ביצוע הזמנה והגדרת העדפות

נסו את ההנחיות הבאות לפי הסדר:

What's on the menu?
I'm lactose intolerant
What would you recommend?
I'll have an oat milk latte and a banana bread
What's my order?

בדיקת אירועים בסשן

כל האירוע יתועד ויוצג בממשק המשתמש באינטרנט. בתיבת הצ'אט תוכלו לראות שלא מופיעים רק ההנחיה והתשובה שלכם, אלא גם tool_call ו-tool_response

9051b46978c8017b.png

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

מחבר

סוג

מה מייצג הנתון

user

message

הודעה שהקלדתם בצ'אט

cafe_concierge

message

תשובה בהודעת טקסט מהנציג

cafe_concierge

tool_call

הסוכן החליט להפעיל כלי (מוצגים שם הפונקציה והארגומנטים)

cafe_concierge

tool_response

הערך המוחזר מקריאה לכלי

לוחצים על אחד מהאירועים tool_call – לדוגמה, על השיחה set_dietary_preference. הפרטים שמוצגים הם:

  • שם הפונקציה: set_dietary_preference
  • ארגומנטים: {"preference": "lactose intolerant"}

עכשיו לוחצים על האירוע המתאים tool_response שמתחתיו. אמור להופיע ערך ההחזרה:

  • תגובה: {"saved": "lactose intolerant", "all_preferences": ["lactose intolerant"]}

b528f4efd6a9f337.png

מחפשים את השדה state_delta בתוך האירוע tool_response. התשובה הזו מראה בדיוק איזה מצב השתנה כתוצאה מהפעלת הכלי:

state_delta: {"user:dietary_preferences": ["lactose intolerant"]}

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

בדיקת מצב הסשן

לוחצים על הכרטיסייה State (מצב). בניגוד ליומן האירועים (שמציג את ההיסטוריה המלאה), בכרטיסייה 'מצב' מוצג מצב המידע שהסוכן יודע כרגע – הערך הנוכחי של כל מפתח מצב.

5e06fb54f3f0d8d6.png

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

  • current_order{"items": ["oat milk latte", "banana bread"], "total": 9.0}
  • user:dietary_preferences["lactose intolerant"]

שימו לב להבדל בשמות המפתחות:

  • למאפיין current_order אין תוספת לשם – הוא ברמת הסשן. הוא קיים רק בשיחה הזו ונעלם כשהסשן מסתיים.
  • user:dietary_preferences כולל את התחילית user: – הוא מוגדר ברמת המשתמש. הוא משותף לכל הסשנים של המשתמש הזה.

ההבחנה הזו לא נראית בקוד (בשני המקרים נעשה שימוש ב-tool_context.state), אבל היא קובעת עד כמה הנתונים מגיעים. תוכלו לראות את זה בפעולה במבחן הבא.

שיחה 2: אימות מצב המשתמש בסשנים שונים

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

57408cfae5f041ac.png

הנה הנחיה לדוגמה:

What do you recommend for me?

בודקים את הכרטיסייה State (מצב) בסשן החדש. המפתח user:dietary_preferences מועבר, אבל current_order נעלם – המצב הזה היה קשור לסשן הקודם.

764eb3885251307d.png

7. התבוננות במגבלת האחסון המקומי

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

הפעלה מחדש של הסוכן

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

cd ~/build-agent-adk-cloudsql
rm -f cafe_concierge/.adk/session.db
uv run adk web

עכשיו פותחים את התצוגה המקדימה של האתר ביציאה 8000 ובוחרים באפשרות cafe_concierge.

בדיקה של שליפת ההעדפות

סוג:

Do you remember my dietary preferences?

לסוכן אין זיכרון. ההעדפות התזונתיות, היסטוריית ההזמנות – הכול נעלם.

82a5e05434cafe83.png

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

הפתרון: מציינים DatabaseSessionService, ובמדריך הזה כל נתוני הסשן יאוחסנו במסד נתונים של PostgreSQL ב-Cloud SQL. קוד הסוכן והכלים נשארים בדיוק אותו הדבר – רק קצה העורף של האחסון משתנה.

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

8. בדיקת ההגדרות של מסד הנתונים

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

gcloud sql instances describe cafe-concierge-db --format="value(state)"

הפלט הבא אמור להופיע, צריך לסמן אותו כסיום

RUNNABLE

יצירת מסד הנתונים

יוצרים מסד נתונים ייעודי לנתוני הסשן של הסוכן:

gcloud sql databases create agent_db --instance=cafe-concierge-db

הפעלת שרת proxy ל-Cloud SQL Auth

שרת proxy ל-Cloud SQL Auth מספק חיבור מאובטח ומאומת מ-Cloud Shell למכונת Cloud SQL, בלי שתצטרכו להוסיף כתובות IP לרשימת ההיתרים. הוא כבר מותקן מראש ב-Cloud Shell.

cloud-sql-proxy ${GOOGLE_CLOUD_PROJECT}:${REGION}:cafe-concierge-db --port 5432 &

הסיומת & בפקודה גורמת להרצת ה-Proxy ברקע. הפלט אמור להיראות כך:

[your-project-id:your-region:cafe-concierge-db] Listening on 127.0.0.1:5432
The proxy has started successfully and is ready for new connections!

אימות החיבור

בודקים שאפשר להתחבר למסד הנתונים דרך שרת ה-proxy:

psql "host=127.0.0.1 port=5432 dbname=agent_db user=postgres password=$DB_PASSWORD" -c "SELECT 'Connection ok' AS status;"

הפרטים שמוצגים הם:

      status
---------------------
 Connection ok
(1 row)

9. אימות של זיכרון מתמשך בסשנים

השלב הזה מוכיח שהזיכרון של הסוכן שורד איפוס, כי אנחנו מוודאים ש-cafe_concierge/.adk/session_db (מסד הנתונים המקומי) מוסר ומתפרס על פני סשנים של שיחות.

הפעלת הנציג

מוודאים ששרת ה-proxy ל-Cloud SQL Auth עדיין פועל (בודקים באמצעות משימות). אם הוא לא מופיע, מפעילים אותו מחדש:

if ss -tlnp | grep -q ':5432 '; then
  echo "Cloud SQL Auth Proxy is already running."
else
  cloud-sql-proxy ${GOOGLE_CLOUD_PROJECT}:${REGION}:cafe-concierge-db --port 5432 &
fi

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

uv run adk web --session_service_uri postgresql+asyncpg://postgres:${DB_PASSWORD}@127.0.0.1:5432/agent_db

פותחים את התצוגה המקדימה של האתר ביציאה 8000 ובוחרים באפשרות cafe_concierge.

בדיקה 1: ביצוע הזמנה והגדרת העדפות

בסשן הראשון, מריצים את ההנחיות הבאות:

Show me the menu
I'm vegan
What can I eat?
I'll have a cold brew and banana bread

בדיקה 2: שרידות לאחר הפעלה מחדש

מפסיקים את ממשק המשתמש למפתחים באמצעות Ctrl+C ומוודאים שה-session.db המקומי הוסר.

rm -f cafe_concierge/.adk/session.db

לאחר מכן, מריצים מחדש את שרת ממשק המשתמש למפתחים.

uv run adk web --session_service_uri postgresql+asyncpg://postgres:${DB_PASSWORD}@127.0.0.1:5432/agent_db

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

What are my dietary preferences?

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

9c139bf89becb748.png

בדיקה ישירה של מסד הנתונים

פותחים כרטיסייה חדשה של מסוף ב-Cloud Shell ומריצים שאילתה במסד הנתונים כדי לראות את הנתונים המאוחסנים:

psql "host=127.0.0.1 port=5432 dbname=agent_db user=postgres password=$DB_PASSWORD" -c "\dt"

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

                List of relations
 Schema |         Name          | Type  |  Owner   
--------+-----------------------+-------+----------
 public | adk_internal_metadata | table | postgres
 public | app_states            | table | postgres
 public | events                | table | postgres
 public | sessions              | table | postgres
 public | user_states           | table | postgres
(5 rows)

סיכום של התנהגות הסטטוס

מפתח מצב

קידומת

היקף

משותף בין סשנים?

current_order

(none)

סשן

לא

user:dietary_preferences

user:

משתמש

כן

10. Congratulations / Clean Up

מעולה! יצרתם בהצלחה סוכן AI מתמשך עם שמירת מצב באמצעות ADK ו-Cloud SQL.

מה למדתם

  • איך יוצרים סוכן ADK עם כלים מותאמים אישית שקוראים וכותבים את מצב הסשן
  • ההבדל בין מצב ברמת הסשן (ללא קידומת) לבין מצב ברמת המשתמש (הקידומת user:)
  • למה ברירת המחדל של adk local‏ session.db מתאימה רק לפיתוח – כל הנתונים אובדים בהסרה (וההסרה קלה, אין גיבוי), לא מתאימה לפריסה ללא שרת (serverless) שהיא חסרת מצב (stateless)
  • איך להקצות מכונה של Cloud SQL PostgreSQL ולהתחבר אליה באמצעות שרת proxy ל-Cloud SQL Auth
  • איך מתחברים ל-DatabaseSessionService באמצעות PostgreSQL ב-CloudSQL עם שינוי מינימלי בקוד – אותם כלים, אותו סוכן, בק-אנד שונה
  • איך מצב ברמת המשתמש נשמר בסשנים נפרדים של שיחות

פינוי נפח

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

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

gcloud projects delete ${GOOGLE_CLOUD_PROJECT}

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

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

gcloud sql instances delete cafe-concierge-db --quiet