Building Stateful and Personalized Agents with ADK

1. מבוא

title

בעיית "דג הזהב"

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

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

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

הסוכן עונה בהתלהבות:

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

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

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

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

"שלום, איך אוכל לעזור לך לתכנן נסיעה היום?"

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

המשימה שלך להיום

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

רמה

קונספט

"כוח העל"

רמה 1

סשן ומצב

ניהול שיחה בלי לשכוח

רמה 2

מצב של כמה נציגים

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

רמה 3

התמדה

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

רמה 4

קריאות חוזרות (callback)

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

רמה 5

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

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

רמה 6

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

איך Gemini "רואה" וזוכר תמונות וסרטונים

מחסנית הזיכרון של ADK

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

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

2. הגדרה

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

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

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

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

  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. הבסיס – סשן ומצב

עם שמירת מצב

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

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

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

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

שלב 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: בפנייה השנייה ("אתה זוכר מה אהבתי באוכל?"), הנציג נכשל לחלוטין כי זה סשן חדש. המשמעות של השגיאה היא "אני לא יודע על מה אתה מדבר".

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

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

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

אוכל

הקונספט: "טלפון שבור"

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

ב-ADK, "התיקייה" הזו היא המדינה.

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

שלב 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, מריצים את Agentic Workflows:

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 של הזיכרון: משתמשים במצב כדי להעביר מידע מובנה בין נציגים. משתמשים ב-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: Cross-Session Retrieval.

👉 מאתרים את התגובה # 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. נסו את זה: מריצים את הסקריפט פעמיים.

  • בהפעלה השנייה, מחפשים את ההודעה "Resumed existing session" (הסשן הקיים חודש).
  • הסוכן יזכור את ההקשר מההרצה הראשונה כי הוא נטען מקובץ מסד הנתונים.

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

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

6. המרגל – התקשרות חזרה

עם שמירת מצב

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

ב-ADK, המרגל הזה הוא הקריאה החוזרת (Callback). 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 של הזיכרון: משתמשים בקריאות חוזרות (callbacks) כדי להפוך את ניהול המצב לאוטומטי. הסוכן בונה את ההקשר שלו פשוט על ידי ביצוע העבודה שלו.

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. "היי, תתכנן ארוחת ערב". -> הסוכן בודק את מסד הנתונים ולא מוצא כלום. מבקש העדפות.
  2. "אני טבעונית". -> הסוכן שומר את המילה "טבעוני" במסד הנתונים.
  3. מפעילים מחדש את הסקריפט.
  4. "היי, תתכנן ארוחת ערב". -> הנציג בודק את מסד הנתונים, רואה "טבעוני" ומציע מיד מסעדה טבעונית.

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

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

8. המוח – זיכרון מולטי-מודאלי

עם שמירת מצב

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

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

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

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

שלב 4: מפעילים את Brain

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

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

צפו בשלב האימות הסופי:

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

הנציג יענה:

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

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

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

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

9. סיכום

התחלת בתור דג זהב ששוכח הכול, ועכשיו אתה פיל מולטי-מודאלי.

הבנייה שלך

היכולת

Session Agent

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

Multi-Agent

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

Persistent Agent

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

Stateful Agent

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

סוכן פרופיל

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

סוכן מולטי-מודאלי

זיכרון חושי דמוי אנוש

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

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