Building Stateful and Personalized Agents with ADK

1. מבוא

title

בעיית דג הזהב

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

אתם נכנסים למשרד שלהם ואומרים:

"היי! אני רוצה לתכנן טיול של יומיים לטוקיו. אני מתעניין באתרים היסטוריים ובסושי".

הסוכן משיב בהתלהבות:

"מעולה! תכננתי ביקור בארמון הקיסרי וארוחת ערב של סושי במסעדת סוקיאבאשי ג'ירו".

אתם מחייכים ואומרים:

"זה נשמע מושלם! תוכל לשלוח לי את מסלול הנסיעה?"

הסוכן מסתכל עליך במבט ריק ושואל:

"היי! איך אוכל לעזור לך לתכנן טיול היום?"

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

המשימה שלכם היום

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

רמה

קונספט

ה'כוח העל'

רמה 1

Session & State

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

רמה 2

Multi-Agent State

שיתוף הערות בין חברי הצוות

רמה 3

התמדה

זוכר אתכם גם אחרי הפעלה מחדש של המערכת

רמה 4

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

עדכון הזיכרון באופן אוטונומי לחלוטין

רמה 5

כלים בהתאמה אישית

קריאה וכתיבה של פרופילים מובנים של משתמשים

רמה 6

זיכרון מולטימודאלי

'לראות' ולזכור תמונות וסרטונים

The ADK Memory Stack

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

  1. סשן: הקונטיינר של השיחה. היא מכילה את ההיסטוריה של מה שנאמר.
  2. מצב: 'לוח טיוטה' של צמד מפתח/ערך שמצורף לסשן. הסוכנים משתמשים בזה כדי לאחסן עובדות ספציפיות (למשל, destination="Tokyo").
  3. MemoryService: אחסון לטווח ארוך. כאן אנחנו שומרים דברים לתמיד, כמו העדפות משתמשים או מסמכים שעברו ניתוח.

‫2. הגדרה

כדי להפעיל את סוכני ה-AI שלנו, אנחנו צריכים שני דברים: פרויקט ב-Google Cloud שיספק את הבסיס.

חלק ראשון: הפעלת החשבון לחיוב

  • כדי לממש את הזיכוי בסך 5 דולר בחשבון לחיוב, תצטרכו אותו לפריסה. חשוב לוודא שאתם מחוברים לחשבון Gmail.

חלק שני: סביבה פתוחה

  1. 👈 לוחצים על הקישור הזה כדי לעבור ישירות אל Cloud Shell Editor
  2. ‫👉 אם מתבקשים לאשר בשלב כלשהו היום, לוחצים על אישור כדי להמשיך. לוחצים כדי לתת הרשאה ל-Cloud Shell
  3. ‫👈 אם הטרמינל לא מופיע בחלק התחתון של המסך, פותחים אותו:
    • לוחצים על הצגה.
    • לוחצים על Terminal (טרמינל)פתיחת טרמינל חדש ב-Cloud Shell Editor.
  4. ‫👈💻 בטרמינל, מוודאים שכבר עברתם אימות ושהפרויקט מוגדר למזהה הפרויקט שלכם באמצעות הפקודה הבאה:
    gcloud auth list
    
  5. ‫👈💻 משכפלים את פרויקט ה-bootstrap מ-GitHub:
    git clone https://github.com/cuppibla/memory_agent_starter
    
    
  6. ‫👈💻 מריצים את סקריפט ההגדרה מהספרייה של הפרויקט.
    cd ~/memory_agent_starter
    ./init.sh
    
    הסקריפט יטפל בשאר תהליך ההגדרה באופן אוטומטי.
  7. ‫👈💻 מגדירים את מזהה הפרויקט הנדרש:
    gcloud config set project $(cat ~/project_id.txt) --quiet
    

חלק שלישי: הגדרת הרשאות

  1. ‫👉💻 מפעילים את ממשקי ה-API הנדרשים באמצעות הפקודה הבאה. היא עשויה להימשך כמה דקות.
    gcloud services enable \
        cloudresourcemanager.googleapis.com \
        servicenetworking.googleapis.com \
        run.googleapis.com \
        aiplatform.googleapis.com \
        compute.googleapis.com
    
  2. ‫👈💻 מעניקים את ההרשאות הנדרשות על ידי הרצת הפקודות הבאות בטרמינל:
    . ~/memory_agent_starter/set_env.sh
    

שימו לב שנוצר לכם קובץ .env. יוצג מידע על הפרויקט.

‫3. הקרן – סשן ומצב

שומר מצב

הקונספט: ההקשר הוא המלך

הצורה הבסיסית ביותר של זיכרון היא זיכרון סשן. כך הסוכן יודע שהמילה it במשפט "I want to buy it" מתייחסת לנעליים שדיברתם עליהן לפני 10 שניות.

ב-ADK, אנחנו מנהלים את זה באמצעות האובייקט Session.

  • גישה ללא שמירת מצב: יצירת סשן חדש לכל הודעה.
  • גישה עם שמירת מצב: יצירת סשן אחד ושימוש חוזר בו לאורך כל השיחה.

שלב 1: בודקים את הסוכן

‫👈💻 בטרמינל של Cloud Shell, פותחים את הקובץ ב-Cloud Shell Editor על ידי הפעלת הפקודה:

cloudshell edit ~/memory_agent_starter/01_session_agent/agent.py

פתיחת ~/memory_agent_starter/01_session_agent/agent.py.

‫👈 מאתרים את התגובה # TODO: Create a root agent בתוך הפונקציה agent.py.

מחליפים את כל השורה הזו בקוד הבא:

root_agent = LlmAgent(
    name="multi_day_trip_agent",
    model="gemini-2.5-flash",
    description="Agent that progressively plans a multi-day trip, remembering previous days and adapting to user feedback.",
    instruction="""
    You are the "Adaptive Trip Planner" 🗺️ - an AI assistant that builds multi-day travel itineraries step-by-step.

    Your Defining Feature:
    You have short-term memory. You MUST refer back to our conversation to understand the trip's context, what has already been planned, and the user's preferences. If the user asks for a change, you must adapt the plan while keeping the unchanged parts consistent.

    Your Mission:
    1.  **Initiate**: Start by asking for the destination, trip duration, and interests.
    2.  **Plan Progressively**: Plan ONLY ONE DAY at a time. After presenting a plan, ask for confirmation.
    3.  **Handle Feedback**: If a user dislikes a suggestion (e.g., "I don't like museums"), acknowledge their feedback, and provide a *new, alternative* suggestion for that time slot that still fits the overall theme.
    4.  **Maintain Context**: For each new day, ensure the activities are unique and build logically on the previous days. Do not suggest the same things repeatedly.
    5.  **Final Output**: Return each day's itinerary in MARKDOWN format.
    """,
    tools=[google_search]
)

ההוראה אומרת ל-LLM לזכור, אבל הקוד צריך לספק את היכולת לזכור.

שלב 2: שני התרחישים

פתיחת ~/memory_agent_starter/01_session_agent/main.py.

‫👈 בטרמינל של Cloud Shell, פותחים את הקובץ ב-Cloud Shell Editor על ידי הפעלת הפקודה:

cloudshell edit ~/memory_agent_starter/01_session_agent/main.py

פותחים את ~/memory_agent_starter/01_session_agent/main.py, מאתרים את התגובה # TODO: Create a runner with in memorysession service בתוך הפונקציה main.py.

מחליפים את כל השורה הזו בקוד הבא:

    runner = Runner(
        agent=agent,
        session_service=session_service,
        app_name=agent.name
    )

‫👈 מאתרים את התגובה # TODO: create a different session to test בתוך הפונקציה main.py.

מחליפים את כל השורה הזו בקוד הבא:

    tokyo_session_2 = await session_service.create_session(
        app_name=multi_day_agent.name,
        user_id=user_id
    )

בדיקה

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

תרחיש 1: עם שמירת מצב (סשן משותף)

async def run_trip_same_session_scenario(session_service, user_id):
    # 1. Create ONE session
    trip_session = await session_service.create_session(...)

    # 2. Turn 1
    await run_agent_query(..., trip_session, ...)

    # 3. Turn 2 - REUSING the same session!
    # The agent can "see" Turn 1 because it's in the session history.
    await run_agent_query(..., trip_session, ...)

תרחיש 2: חוסר מצב (סשן חדש בכל פעם)

async def run_trip_different_session_scenario(session_service, user_id):
    # Turn 1
    tokyo_session = await session_service.create_session(...)
    await run_agent_query(..., tokyo_session, ...)

    # Turn 2 - Creating a FREASH session
    # The agent has NO IDEA what happened in Turn 1.
    tokyo_session_2 = await session_service.create_session(...)
    await run_agent_query(..., tokyo_session_2, ...)

שלב 3: מריצים את הסוכן

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

‫👈💻 בשורת הפקודה, מריצים את שורת הפקודה שלמטה:

cd ~/memory_agent_starter
uv run python ~/memory_agent_starter/01_session_agent/main.py

תסריט 1: הסוכן זוכר את ההעדפות שלכם מההודעה הראשונה ומתאים את התוכנית בהודעה השנייה.

תרחיש 2: בתור השני ("do you remember what I liked about the food?"), הסוכן נכשל לחלוטין כי זו שיחה חדשה. התגובה הזו בעצם אומרת: "אין לי מושג על מה אתה מדבר".

נקודות מרכזיות

כלל מספר 1 של הזיכרון: תמיד משתמשים ב-session.id כדי לשמור על הקשר בשיחה. אובייקט Session הוא מאגר הזיכרון לטווח קצר של הסוכן.

4. הצוות – סטטוס של כמה נציגים

אוכל

הקונספט: "משחק הטלפון"

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

ב-ADK, ה'תיקייה' הזו היא State.

  • State הוא מילון ({"key": "value"}) שנמצא בתוך Session.
  • כל נציג בסשן יכול לקרוא את ההערות או לכתוב בהן.

שלב 1: בודקים את תהליך העבודה

‫👈💻 בטרמינל של Cloud Shell, פותחים את הקובץ ב-Cloud Shell Editor על ידי הפעלת הפקודה:

cloudshell edit ~/memory_agent_starter/02_multi_agent/agent.py

‫👈 בקובץ ~/memory_agent_starter/02_multi_agent/agent.py, מאתרים את התגובה # TODO: foodie agent.

מחליפים את כל השורה הזו בקוד הבא:

foodie_agent = LlmAgent(
    name="foodie_agent",
    model="gemini-2.5-flash",
    tools=[google_search],
    instruction="""You are an expert food critic. Your goal is to find the best restaurant based on a user's request.

    When you recommend a place, you must output *only* the name of the establishment and nothing else.
    For example, if the best sushi is at 'Jin Sho', you should output only: Jin Sho
    """,
    output_key="destination"  # ADK will save the agent's final response to state['destination']
)

‫👈 מאתרים את התגובה # TODO: transportation agent בתוך הפונקציה agent.py.

מחליפים את כל השורה הזו בקוד הבא:

transportation_agent = LlmAgent(
    name="transportation_agent",
    model="gemini-2.5-flash",
    tools=[google_search],
    instruction="""You are a navigation assistant. Given a destination, provide clear directions.
    The user wants to go to: {destination}.

    Analyze the user's full original query to find their starting point.
    Then, provide clear directions from that starting point to {destination}.
    """,
)

‫👈 מאתרים את התגובה # TODO: root_agent בתוך הפונקציה agent.py.

מחליפים את כל השורה הזו בקוד הבא:

root_agent = SequentialAgent(
    name="find_and_navigate_agent",
    sub_agents=[foodie_agent, transportation_agent],
    description="A workflow that first finds a location and then provides directions to it."
)

עכשיו יש לנו שני סוכנים שפועלים ברצף:

  1. סוכן חובב אוכל: מוצא מסעדה.
  2. סוכן תחבורה: נותן הנחיות הגעה למסעדה.

העברה קסומה: שימו לב איך foodie_agent מעביר את השרביט ל-transportation_agent.

foodie_agent = LlmAgent(
    # ...
    # CRITICAL: This tells ADK to save the agent's output to state['destination']
    output_key="destination"
)

transportation_agent = LlmAgent(
    # ...
    # CRITICAL: This injects state['destination'] into the prompt
    instruction="""
    The user wants to go to: {destination}.
    Provide clear directions...
    """,
)
  1. output_key="destination": התשובה של סוכן המזון נשמרת ביעילות.
  2. {destination}: סוכן התחבורה קורא את התשובה באופן אוטומטי.

(לא נדרשת פעולה) שלב 2: כלי התזמור

פתיחת 02_multi_agent/main.py.

אנחנו משתמשים ב-SequentialAgent כדי להריץ אותם לפי הסדר.

# 1. Create a single session for the sequential agent
session = await session_service.create_session(...)

# 2. Run the query
# The SequentialAgent manages the state flow:
# Query -> Foodie -> state['destination'] -> Transportation -> Final Answer
await run_agent_query(root_agent, query, ...)

המשתמש שולח הנחיה אחת:

"Find best sushi in Palo Alto and then tell me how to get there."

הסוכנים עובדים יחד כדי לענות על השאלה.

שלב 3: הפעלת הצוות

‫👈💻 בטרמינל של Cloud Shell, מריצים את תהליך העבודה של כמה סוכנים:

cd ~/memory_agent_starter
uv run python ~/memory_agent_starter/02_multi_agent/main.py

מה קורה?

  1. סוכן לאוהבי אוכל: מוצא את Jin Sho (או מקום דומה).
  2. ADK: שומר את Jin Sho ב-state['destination'].
  3. סוכן תחבורה: מקבל את ההוראה Jin Sho.
  4. תוצאה: "כדי להגיע ל-Jin Sho מתחנת Caltrain, צריך ללכת לאורך University Ave..."

נקודות מרכזיות

כלל מספר 2 של Memory: משתמשים במצב כדי להעביר מידע מובנה בין סוכנים. משתמשים ב-output_key כדי לכתוב וב-{placeholders} כדי לקרוא.

‫5. הפעלה מחדש – התמדה

שומר מצב

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

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

כדי לפתור את הבעיה, צריך להפעיל את האפשרות התמדה. אנחנו מחליפים את InMemorySessionService ב-DatabaseSessionService.

שלב 1: החלפת מסד הנתונים

‫👈💻 בטרמינל של Cloud Shell, פותחים את הקובץ ב-Cloud Shell Editor על ידי הפעלת הפקודה:

cloudshell edit ~/memory_agent_starter/03_persistent_agent/main.py

‫👈 בקובץ ~/memory_agent_starter/03_persistent_agent/main.py, מאתרים את התגובה # TODO: Configuration for Persistent Sessions.

מחליפים את כל השורה הזו בקוד הבא:

SESSIONS_DIR = Path(os.path.expanduser("~")) / ".adk_codelab" / "sessions"
os.makedirs(SESSIONS_DIR, exist_ok=True)
SESSION_DB_FILE = SESSIONS_DIR / "trip_planner.db"
SESSION_URL = f"sqlite:///{SESSION_DB_FILE}"

מעכשיו, כל סשן וכל אירוע נשמרים בקובץ SQLite.

שלב 2: אחזור נתונים בין סשנים

המשכיות מאפשרת לא רק להמשיך שיחה, אלא גם ללמוד משיחות קודמות.

באותו קובץ ~/memory_agent_starter/03_persistent_agent/main.py, מעיינים בתרחיש בדיקה 3: אחזור בין סשנים.

‫👈 איתור התגובה # TODO: retrieve the previous session manually

מחליפים את כל השורה הזו בקוד הבא:

    old_session = await session_service.get_session(
        app_name=root_agent.name, user_id="user_01", session_id=session_id
    )

‫👈 מאתרים את התגובה # TODO: Extract content from the OLD session בתוך הפונקציה main.py.

מחליפים את כל השורה הזו בקוד הבא:

                    previous_context += f"- {role}: {text}\n"

‫👈 מאתרים את התגובה # TODO: Manually inject the context to the query בתוך הפונקציה main.py.

מחליפים את כל השורה הזו בקוד הבא:

    query_3 = f"""
    {previous_context}

    I'm planning a new trip to Osaka this time. 
    Based on my previous preferences (above), what should I eat?
    """

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

שלב 3: שורדים את האתחול מחדש

‫👈💻 בטרמינל, מריצים את הסקריפט:

cd ~/memory_agent_starter
uv run python ~/memory_agent_starter/03_persistent_agent/main.py

נוצר קובץ ~/memory_agent_starter/trip_planner.db. כדאי לנסות את הפעולות הבאות: מריצים את הסקריפט פעמיים.

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

נקודות מרכזיות

כלל מספר 3 של Memory: משתמשים ב-DatabaseSessionService לייצור. הוא מבטיח שהשיחות של המשתמשים יישמרו גם אחרי הפעלה מחדש של השרת, ומאפשר ניתוח של ההיסטוריה לטווח ארוך.

6. המרגל – קריאות חוזרות (callbacks)

שומר מצב

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

ב-ADK, הריגול הזה הוא התקשרות חזרה. adk_callback

  • after_tool_callback: פונקציה שמופעלת בכל פעם שהסוכן פועל.
  • ToolContext: דרך לכתוב ל-State מתוך הפונקציה.

שלב 1: הלוגיקה

‫👈💻 בטרמינל של Cloud Shell, פותחים את הקובץ ב-Cloud Shell Editor על ידי הפעלת הפקודה:

cloudshell edit ~/memory_agent_starter/04_stateful_agent/agent.py

‫👉 בקובץ ~/memory_agent_starter/04_stateful_agent/agent.py, מאתרים את התגובה # TODO: Implement call back logic

מחליפים את כל השורה הזו בקוד הבא:

def save_activity_type_callback(
    tool,
    args: Dict[str, Any],
    tool_context: ToolContext,
    tool_response: Dict[str, Any],
) -> Optional[Dict[str, Any]]:
    """
    Callback to save the TYPE of activity just planned into the session state.
    """
    # 1. Get the actual agent name.
    if tool.name == "transfer_to_agent":
         agent_name = args.get("agent_name")
    else:
         agent_name = tool.name

    activity_type = "unknown"

    # 2. Determine the type based on which agent was actually used
    if agent_name == "museum_expert":
        activity_type = "CULTURAL"
    elif agent_name == "restaurant_expert":
        activity_type = "FOOD"
    elif agent_name == "outdoor_expert":
        activity_type = "OUTDOOR"

    print(f"\n🔔 [CALLBACK] The planner transferred to '{agent_name}'.")

    # 3. Update the state directly
    tool_context.state["last_activity_type"] = activity_type
    print(f"💾 [STATE UPDATE] 'last_activity_type' is now set to: {activity_type}\n")

    return tool_response

‫👈 באותו קובץ, מאתרים את התגובה # TODO: add callback to root agent בתוך הפונקציה 04_stateful_agent/agent.py.

מחליפים את כל השורה הזו בקוד הבא:

    after_tool_callback=save_activity_type_callback,

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

def get_planner_instruction(context):
    last_activity = context.state.get("last_activity_type", "None")
    
    return f"""
    The last activity was: {last_activity}
    
    If last_activity is 'CULTURAL' -> `museum_expert` is BANNED.
    """

שלב 3: בדיקת הכלי Spy

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

cd ~/memory_agent_starter
uv run python ~/memory_agent_starter/04_stateful_agent/main.py

כשמריצים את הסוכן הזה, רואים לולאה.

  1. תור 1: אתם מבקשים מוזיאון. סטים של מרגלים last_activity="CULTURAL".
  2. תור 2: אתם מבקשים מוזיאון אחר.
  3. עדכונים בהוראות לסוכן: "השימוש במונח CULTURAL אסור".
  4. הנציג אומר: "אין לי אפשרות להוסיף עוד מוזיאון. מה דעתך על פארק?"

צופים ביומני המסוף של [CALLBACK] ושל [STATE UPDATE]. אתם יכולים לראות את הזיכרון משתנה בזמן אמת בזמן שהנציג עובד.

נקודות מרכזיות

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

7. ארון התיוק – כלים בהתאמה אישית

הקונספט: 'זיכרון מובנה'

שומר מצב

עד עכשיו, 'זיכרון' היה יומן צ'אט או צמד פשוט של מפתח/ערך. אבל מה קורה אם צריך לזכור פרופיל משתמש מורכב? למשל: diet: vegan, budget: high, pets: [cat, dog].

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

שלב 1: הכלים

‫👈💻 בטרמינל של Cloud Shell, פותחים את הקובץ ב-Cloud Shell Editor על ידי הפעלת הפקודה:

cloudshell edit ~/memory_agent_starter/05_profile_agent/tools.py

‫👈 בקובץ הזה: ~/memory_agent_starter/05_profile_agent/tools.py.

אנחנו צריכים להטמיע את שני הכלים הספציפיים האלה:

  1. save_user_preferences: כתיבה למסד נתונים.
  2. recall_user_preferences: קריאה ממסד נתונים.

מאתרים את התגובה # TODO: implement save_user_preferences tools בתוך הפונקציה ~/memory_agent_starter/05_profile_agent/tools.py.

מחליפים את כל השורה הזו בקוד הבא:

def save_user_preferences(tool_context: ToolContext, new_preferences: Dict[str, Any]) -> str:
    user_id = tool_context.session.user_id
    with sqlite3.connect(USER_DB_FILE) as conn:
        for key, value in new_preferences.items():
            conn.execute("INSERT INTO user_preferences (user_id, pref_key, pref_value) VALUES (?, ?, ?) ON CONFLICT(user_id, pref_key) DO UPDATE SET pref_value = excluded.pref_value;",
                         (user_id, key, json.dumps(value)))
    return f"Preferences updated: {list(new_preferences.keys())}"

‫👈 מאתרים את התגובה # TODO: implement recall_user_preferences tools בתוך הפונקציה 05/tools.py.

מחליפים את כל השורה הזו בקוד הבא:

def recall_user_preferences(tool_context: ToolContext) -> Dict[str, Any]:
    user_id = tool_context.session.user_id
    preferences = {}
    with sqlite3.connect(USER_DB_FILE) as conn:
        rows = conn.execute("SELECT pref_key, pref_value FROM user_preferences WHERE user_id = ?", (user_id,)).fetchall()
        if not rows: return {"message": "No preferences found."}
        for key, value_str in rows: preferences[key] = json.loads(value_str)
    return preferences

ההוראה כופה תהליך עבודה:

instruction="""
1. RECALL FIRST: First action MUST be `recall_user_preferences`.
3. LEARN: If a user states a new preference, use `save_user_preferences`.
"""

שלב 2: ההפעלה

‫👈💻 בטרמינל של Cloud Shell, פותחים את הקובץ ב-Cloud Shell Editor על ידי הפעלת הפקודה:

cloudshell edit ~/memory_agent_starter/05_profile_agent/main.py

פתיחת ~/memory_agent_starter/05_profile_agent/main.py.

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

  • הוא בוחר להתקשר אל recall_user_preferences בהתחלה.
  • הוא יבחר להתקשר אל save_user_preferences אם תגידו לו "אני טבעוני".

שלב 3: בניית הפרופיל

‫👈💻 מריצים את הסקריפט:

cd ~/memory_agent_starter
uv run python ~/memory_agent_starter/05_profile_agent/main.py

כדאי לנסות את רצף הפעולות הבא:

  1. ‫"Hi, plan a dinner" (שלום, תכנן ארוחת ערב). ‫-> הסוכן בודק את מסד הנתונים ולא מוצא כלום. מבקש העדפות.
  2. "I'm vegan" (אני טבעוני/ת). ‫-> הסוכן שומר את הערך 'טבעוני' במסד הנתונים.
  3. מפעילים מחדש את הסקריפט.
  4. ‫"Hi, plan a dinner" (שלום, תכנן ארוחת ערב). ‫-> הנציג בודק את מסד הנתונים, רואה את המילה 'טבעוני' ומציע מסעדה טבעונית באופן מיידי.

נקודות מרכזיות

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

8. The Brain - Multimodal Memory

שומר מצב

הקונספט: "החוויה האנושית"

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

Vertex AI Memory Bank מאפשר לסוכן שלכם לטפל בזיכרון מולטימודאלי. הוא יכול לקבל תמונות, סרטונים ואודיו, 'להבין' אותם ולאחזר אותם מאוחר יותר.

שלב 1: ההגדרה

‫👈💻 בטרמינל של Cloud Shell, פותחים את הקובץ ב-Cloud Shell Editor על ידי הפעלת הפקודה:

cloudshell edit ~/memory_agent_starter/06_multimodal_agent/main.py

‫👈 פותחים את 06_multimodal_agent/main.py. מחפשים את התגובה # TODO: Configure Memory Bank Topic.

מחליפים את כל השורה הזו בקוד הבא:

travel_topics = [
    MemoryTopic(
        managed_memory_topic=ManagedMemoryTopic(
            managed_topic_enum=ManagedTopicEnum.USER_PREFERENCES
        )
    ),
    MemoryTopic(
        managed_memory_topic=ManagedMemoryTopic(
            managed_topic_enum=ManagedTopicEnum.USER_PERSONAL_INFO
        )
    ),
    MemoryTopic(
        custom_memory_topic=CustomMemoryTopic(
            label="travel_experiences",
            description="""Memorable travel experiences including:
                - Places visited and impressions
                - Favorite restaurants, cafes, and food experiences
                - Preferred accommodation types and locations
                - Activities enjoyed (museums, hiking, beaches, etc.)
                - Travel companions and social preferences
                - Photos and videos from trips with location context""",
        )
    ),
    MemoryTopic(
        custom_memory_topic=CustomMemoryTopic(
            label="travel_preferences",
            description="""Travel style and preferences:
                - Budget preferences (luxury, mid-range, budget)
                - Transportation preferences (flying, trains, driving)
                - Trip duration preferences
                - Season and weather preferences
                - Cultural interests and language abilities
                - Dietary restrictions and food preferences""",
        )
    ),
    MemoryTopic(
        custom_memory_topic=CustomMemoryTopic(
            label="travel_logistics",
            description="""Practical travel information:
                - Passport and visa information
                - Frequent flyer numbers and hotel loyalty programs
                - Emergency contacts
                - Medical considerations and insurance
                - Packing preferences and essentials
                - Time zone preferences and jet lag strategies""",
        )
    ),
]

איתור התגובה # TODO: Configure Memory Bank Customization

מחליפים את כל השורה הזו בקוד הבא:

memory_bank_config = {
    "customization_configs": [
        {
            "memory_topics": travel_topics,
        }
    ],
    "similarity_search_config": {
        "embedding_model": f"projects/{PROJECT_ID}/locations/{LOCATION}/publishers/google/models/gemini-embedding-001"
    },
    "generation_config": {
        "model": f"projects/{PROJECT_ID}/locations/{LOCATION}/publishers/google/models/gemini-2.5-flash"
    },
}

שלב 2: הטמעה של נתונים מכל העולם

ב-test_trip_planner, אנחנו שולחים:

  1. הודעת טקסט ("שלום")
  2. תמונה (ציון דרך)
  3. סרטון (הים התיכון)
  4. קליפ אודיו (הודעה קולית על גאטה)

מאתרים את התגובה # TODO create session service and memory service בתוך הפונקציה 6_multimodal_agent/main.py.

מחליפים את כל השורה הזו בקוד הבא:

session_service = VertexAiSessionService(
    project=PROJECT_ID, location=LOCATION, agent_engine_id=agent_engine_id
)
memory_service = VertexAiMemoryBankService(
    project=PROJECT_ID, location=LOCATION, agent_engine_id=agent_engine_id
)

‫👉 באותו קובץ 06_multimodal_agent/main.py, מאתרים את התגובה # TODO: create memory from session

מחליפים את כל השורה הזו בקוד הבא:

    await memory_service.add_session_to_memory(final_session_state)

זהו הקו הקסום. הוא שולח את כל המדיה העשירה הזו ל-Vertex AI, שמעבד ומאנדקס אותה.

שלב 3: השחזור

‫👈💻 בטרמינל של Cloud Shell, פותחים את הקובץ ב-Cloud Shell Editor על ידי הפעלת הפקודה:

cloudshell edit ~/memory_agent_starter/06_multimodal_agent/agent.py

לסוכן יש PreloadMemoryTool.

tools=[PreloadMemoryTool(), budget_tool]

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

שלב 4: מריצים את ה-Brain

‫👈💻 במסוף Cloud Shell, מריצים את הסקריפט (הערה: נדרש פרויקט ב-Google Cloud עם Vertex AI מופעל):

cd ~/memory_agent_starter
uv run python ~/memory_agent_starter/06_multimodal_agent/main.py

צפייה בשלב האחרון של האימות:

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

הנציג ישיב:

‫"You should visit Gaeta! הראית לי סרטון של הים התיכון וקליפ אודיו שבו אמרת שאתה אוהב את גאאטה".

הוא קישר בין נקודות בנתונים מסוגי מדיה שונים מהעבר.

נקודות מרכזיות

כלל מספר 6 בנושא זיכרון: כדי ליהנות מחוויית זיכרון אולטימטיבית, מומלץ להשתמש ב-Vertex AI Memory Bank. הוא מאחד טקסט, תמונות וסרטונים למוח אחד שאפשר לחפש בו.

9. סיכום

התקדמתם מדג זהב שכחן לפיל מולטימודאלי.

יצרת

היכולת

Session Agent

זיכרון שיחה לטווח קצר

Multi-Agent

זיכרון משותף של הצוות

Persistent Agent

היסטוריה לטווח ארוך

Stateful Agent

זיכרון דינמי שמתעדכן בעצמו

סוכן פרופיל

זיכרון של נתונים מובְנים

Multimodal Agent

זיכרון חושי שדומה לזיכרון של בני אדם

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

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