שיטות מומלצות ודפוסים

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

כללי

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

התחלה פשוטה

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

ההוראות צריכות להיות ספציפיות

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

שימוש בהוראות מובנות

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

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

אם אתם רוצים שהסוכנים יגיבו בדרכים שונות בהתאם לאמצעי התקשורת (טקסט או קול) או לערוץ (API, טלפוניה או אינטרנט), כדאי להשתמש ברכיבים מודולריים ובבדיקות של הערוץ ואמצעי התקשורת בכלי Python ובפונקציות callback.

כלים

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

שימוש בכלי Python לעטיפת ממשקי API

סכימות של ממשקי API חיצוניים עשויות להגדיר הרבה פרמטרים של קלט ופלט שלא רלוונטיים לסוכן. אם משתמשים בכלים של OpenAPI במקרים כאלה, יכול להיות שמוסיפים למודל הקשר לא נחוץ, מה שיכול להפחית את רמת המהימנות. לדוגמה, נניח שכלי של מפרט OpenAPI מקבל 3 פרמטרים כקלט ומחזיר אובייקט JSON גדול עם 100 צמדי מפתח/ערך. הסוכן מנבא את 3 ארגומנטי הקלט של הכלי הזה, וכשהוא חוזר, הוא רואה את מטען ה-JSON המלא עם כל 100 צמדי המפתח/ערך. אם רק 3 מהזוגות האלה רלוונטיים לשיחה, 97 הזוגות האחרים הם נתונים לא רלוונטיים שמוסיפים טוקנים להיסטוריית השיחות. יכול להיות שזה נראה לא מזיק, אבל זה עלול ליצור בלבול מיותר אצל הסוכן, להגדיל את זמן החשיבה ולהגדיל את זמן האחזור.

מומלץ להשתמש בכלי Python כדי לעטוף קריאות ל-API. ההצפנה מאפשרת להסתיר נתונים מיותרים מהסוכן ומנתוני ההקשר ההיסטוריים. אתם יכולים לשלוט בהקשר המדויק שהסוכן רואה על ידי החזרת הנתונים שרלוונטיים לסוכן באותו רגע. כך יש לכם שליטה מלאה על פרמטרי הקלט והפלט שהוגדרו בכלי ומשותפים עם המודל. השיטה הזו היא סוג של הנדסת הקשר בעזרת כלים.

קוד לדוגמה:

def python_wrapper(arg_a: str, arg_b: str) -> dict:
  """
  Call the scheduling service to schedule an appointment,
  returning only relevant fields.
  """
  res = complicated_external_api_call(...)
  # Process result to extract only relevant key-value pairs.
  processed_res = {
      "appointment_time": res.json()["appointment_time"],
      "appointment_location": res.json()["appointment_location"],
      "confirmation_id": res.json()["confirmation_id"],
  }
  return processed_res

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

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

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

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

שרשור קריאות לכלי

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

דפוס לא טוב לשרשור של קריאות כלים

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

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

לדוגמה, רצף הכלים הבא:

  • tool_1(arg_a, arg_b) -> output c
  • tool_2(arg_c) -> output d
  • tool_3(arg_d) -> output e

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

  • הטקסט של המשתמש
  • מודל -> סוכן חוזה tool_1(arg_a, arg_b)
  • המוצר tool_1_response.json() מוחזר
  • הסוכן מפרש את tool_1_response.json() ומחלץ את arg_c
  • מודל -> סוכן חוזה tool_2(arg_c)
  • המוצר tool_2_response.json() מוחזר
  • הסוכן מפרש את tool_2_response.json() ומחלץ את arg_d
  • מודל -> סוכן חוזה tool_3(arg_d)
  • המוצר tool_3_response.json() מוחזר
  • מודל -> הסוכן מספק תשובה סופית

יש 4 קריאות למודל, 3 חיזויים של כלי ו-4 ארגומנטים של קלט.

דפוס טוב לשרשור של קריאות כלים

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

הכלי הבא קורא לשלושה כלים אחרים:

def python_wrapper(arg_a: str, arg_b: str) -> dict:
  """Makes some sequential API calls."""
  res1 = tools.tool_1({"arg_a": arg_a, "arg_b": arg_b})
  res2 = tools.tool_2(res1.json())
  res3 = tools.tool_3(res2.json())

  return res3.json()

כדאי לעיין ברצף האירועים של קריאה אחת לכלי:

  • הטקסט של המשתמש
  • מודל -> סוכן חוזה python_wrapper(arg_a, arg_b)
  • המוצר python_wrapper_response.json() מוחזר
  • מודל -> הסוכן מספק תשובה סופית

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

הגדרות ברורות ומובחנות של הכלים

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

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

    דוגמאות טובות: first_name, phone_number, url.

    דוגמאות לשימוש לא נכון: i, arg1, fn, pnum, rqst.

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

תהליך עבודה של פיתוח

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

הגדרת תהליך פיתוח לשיתוף פעולה בין סוכנים

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

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

שימוש בגרסאות כדי לשמור את מצב הסוכן

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

מומלץ ליצור גרסאות לעיתים קרובות, אולי אחרי כל 10-15 שינויים משמעותיים. כדאי גם לתת שמות לגרסאות באופן סמנטי, ולכן צריך להחליט עם צוות הפיתוח באיזו מוסכמת מתן שמות להשתמש. לדוגמה, שמות תיאוריים כמו pre-prod-instruction-changes או prod-ready-for-testing. אפשר גם להשתמש בתקנים כמו Semantic Versioning, ולהשתמש בשמות כמו v1.0.0, v1.0.1 וכו'. לגרסאות יש גם שדה תיאור שמאפשר להוסיף פרטים נוספים, בדומה לגוף ההודעה של commit. שם הגרסה והתיאור שלה צריכים להיות קצרים, בעלי משמעות וקלים להבנה, למקרה שתצטרכו לחזור לגרסה הזו.

ביצוע בדיקות מקצה לקצה

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

הערכות

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

שימוש בהערכות

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

טיפול בסשנים

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

ברכות דטרמיניסטיות וזמן טעינה מופחת עם תשובות סטטיות

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

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

def before_model_callback(
    callback_context: CallbackContext,
    llm_request: LlmRequest
) -> Optional[LlmResponse]:
  for part in callback_context.get_last_user_input():
    # Or other events or texts
    if part.text == "<event>session start</event>":
      return LlmResponse.from_parts(parts=[
          Part.from_text(text="Hello how can I help you today?")
      ])
  return None

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

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

ההגדרה הזו מסתמכת על ההגדרה partial = True. בדרך כלל, תגובה שאינה FuctionCall נחשבת לתגובה סופית. השימוש ב-partial = True מאלץ את הסוכן להמשיך בתהליך אחרי התשובה.

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

def before_model_callback(callback_context: CallbackContext, llm_request: LlmRequest) -> Optional[LlmResponse]:
  for part in callback_context.get_last_user_input():
    if part.text == "<event>session start</event>":
      response = LlmResponse.from_parts([Part.from_text("Hello, I'm Gemini, your personal AI assistant.")])
      response.partial = True
      return response
  return None

אימות תוכן חובה ואכיפה שלו

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

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

משתנים לדוגמה:

שם המשתנה ערך ברירת המחדל
first_turn True
DISCLAIMER = "THIS CONVERSATION MAY BE RECORDED FOR LEGAL PURPOSES."

def after_model_callback(
    callback_context: CallbackContext,
    llm_response: LlmResponse
) -> Optional[LlmResponse]:
  if callback_context.variables.get("first_turn"):
    callback_context.variables["first_turn"] = False

    # Check if the agent's response already contains the disclaimer.
    # The agent might have produced it based on instructions.
    for part in callback_context.get_last_agent_output():
      if part.text and DISCLAIMER in part.text:
        return None

    # If the agent failed to produce the disclaimer, force it.
    return LlmResponse.from_parts(parts=[
        Part.from_text(DISCLAIMER),
        *llm_response.content.parts
    ])

  return None

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

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

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

def post_call_logging(session_id: str) -> dict:
  """Logs the session ID to external API."""
  API_URL = "https://api.example.com"
  response = ces_requests.post(
    url=API_URL,
    data={"session_id": session_id}
  )

  return response.json()

אפשר להשתמש ב-after_model_callback כדי לבצע את הרצף הבא:

  1. בודקים אם יש קריאה לכלי end_session בתשובה של הסוכן.
  2. יוצרים את חלק הכלי post_call_logging.
  3. מוסיפים את הקריאה לכלי post_call_logging לפני הקריאה לכלי end_session.

כך אפשר לוודא שהנציג יפעיל את כלי הרישום לפני סיום הסשן.

def after_model_callback(
    callback_context: CallbackContext,
    llm_response: LlmResponse
) -> Optional[LlmResponse]:
  for index, part in enumerate(llm_response.content.parts):
    if part.has_function_call('end_session'):
      # Add an additional "post_call_logging" function call before "end_session",
      # so the agent will execute the tool before ending the session.
      tool_call = Part.from_function_call(
          name="post_call_logging",
          args={"sessionId": callback_context.session_id}
      )
      return LlmResponse.from_parts(
          parts=llm_response.content.parts[:index] + [tool_call] + llm_response.content.parts[index:]
      )
  return None

שימוש בתשובות חלקיות לעדכונים בזמן אמת בממשק המשתמש

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

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

ההגדרה הזו מסתמכת על ההגדרה partial = True. בדרך כלל, תגובה שאינה FuctionCall נחשבת לתגובה סופית. השימוש ב-partial = True מאלץ את הסוכן להמשיך בתהליך אחרי התשובה.

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

def before_model_callback(
    callback_context: CallbackContext,
    llm_request: LlmRequest
) -> Optional[LlmResponse]:
  if  llm_request.contents[-1].parts[-1].has_function_response('update_order'):
    order_state =  llm_request.contents[-1].parts[-1].function_response.response['result']['order_state']
    # Return a custom JSON payload before calling the model to generate the final agent response.
    response = LlmResponse.from_parts([Part.from_json(data=json.dumps(order_state))])
    response.partial = True
    return response
  return None

שילוב בצד הלקוח

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

שימוש במטענים ייעודיים (payloads) בהתאמה אישית כדי להפעיל את ממשק המשתמש

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

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

import json

def after_model_callback(
    callback_context: CallbackContext,
    llm_response: LlmResponse
) -> Optional[LlmResponse]:
  prefix = 'Available options are:'
  payload = {}
  for part in llm_response.content.parts:
    if part.text is not None and part.text.startswith(prefix):
      # Return available options as chip list
      payload['chips'] = part.text[len(prefix):].split(',')
      break

  new_parts = []
  # Keep the original agent response part, as the custom payload won't be sent
  # back to the model in the next turn.
  new_parts.extend(llm_response.content.parts)
  new_parts.append(Part.from_json(data=json.dumps(payload)))
  return LlmResponse.from_parts(parts=new_parts)

הצגת Markdown ו-HTML

אם ממשק השיחה שלכם תומך ב-Markdown וב-HTML לתשובות של סוכנים, אתם יכולים להשתמש בסימולטור כדי לבדוק את התשובות האלה, כי הסימולטור תומך גם ב-Markdown וב-HTML.

הוראות לדוגמה:

<role>
    You are a "Markdown Display Assistant," an AI agent designed to demonstrate
    various rich content formatting options like images, videos, and deep links
    using HTML-style markdown. Your purpose is to generate and display this
    content directly to the user based on their requests.
</role>
<persona>
    Your primary goal is to showcase the rich content rendering capabilities of
    the platform by generating HTML markdown for elements like images, videos,
    and hyperlinks. You are a helpful and direct assistant. When asked to show
    something, you generate the markdown for it and present it.
    You should not engage in conversations outside the scope of generating and
    displaying markdown. If the user asks for something unrelated, politely
    state that you can only help with displaying rich content. Adhere strictly
    to the defined constraints and task flow.
</persona>
<constraints>
    1.  **Scope Limitation:** Only handle requests related to displaying
        markdown content (images, videos, links, etc.). Do not answer general
        knowledge questions or perform other tasks.
    2.  **Tool Interaction Protocol:** You must use the \`display_markdown\`
        tool to generate the formatted content string.
    3.  **Direct Output:** Your final response to the user must be the raw
        markdown string returned by the \`display_markdown\` tool. Do not add
        any conversational text around it unless the tool returns an error.
        For example, if the tool returns \`"<img src='...'>"\`, your response
        should be exactly \`"<img src='...'>"\`.
    4.  **Clarity and Defaults:** If a user's request is vague (e.g., "show me
        an image"), use the tool's default values to generate a response. There
        is no need to ask for clarification.
    5.  **Error Handling:** If the tool call fails or returns an error, inform
        the user about the issue in a conversational manner.
</constraints>
<taskflow>
    These define the conversational subtasks that you can take. Each subtask
    has a sequence of steps that should be taken in order.
    <subtask name="Generate and Display Markdown">
        <step name="Parse Request and Call Tool">
            <trigger>
                User initiates a request to see any form of rich content (image,
                video, link, etc.).
            </trigger>
            <action>
                1.  Identify the types of content the user wants to see (e.g.,
                    image, video, deep link).
                2.  Call the \`display_markdown\` tool. Set the corresponding
                    boolean arguments to \`True\` based on the user's request.
                    For example, if the user asks for a video and a link, call
                    \`display_markdown(show_video=True, show_deep_link=True)\`.
                3.  If the user makes a general request like "show me something
                    cool", you can enable all flags.
            </action>
        </step>
        <step name="Output Tool Response">
            <trigger>
                The \`display_markdown\` tool returns a successful response
                containing a \`markdown_string\`.
            </trigger>
            <action>
                1.  Extract the value of the \`markdown_string\` key from the
                    tool's output.
                2.  Use this value as your direct and final response to the
                    user, without any additional text or formatting.
            </action>
        </step>
    </subtask>
</taskflow>

דוגמה לכלי Python:

from typing import Any

def display_markdown(show_image: bool, show_video: bool, show_deep_link: bool) -> dict[str, Any]:
    """
    Constructs a markdown string containing HTML for various rich media elements.

    This function generates an HTML-formatted string based on the boolean flags provided.
    It can include an image, a video, and a hyperlink (deep link). The content for
    these elements is pre-defined.

    Args:
        show_image (bool): If True, an <img> tag will be included in the output.
        show_video (bool): If True, a <video> tag will be included in the output.
        show_deep_link (bool): If True, an <a> tag will be included in the output.

    Returns:
        dict[str, Any]: A dictionary with a single key 'markdown_string' containing the
                        generated HTML markdown. If no flags are set, it returns a
                        message indicating nothing was requested.
    """
    # MOCK: This is a mock implementation. It does not fetch any dynamic content.
    # It assembles a markdown string from hardcoded HTML snippets to demonstrate
    # the agent's ability to render rich content.

    markdown_parts = []

    if show_image:
        image_html = "This is a sample image:\n<img src='https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png' alt='Google Logo' width='272' height='92' />"
        markdown_parts.append(image_html)

    if show_video:
        video_html = "This is a sample video:\n<video controls width='320' height='240'><source src='https://www.w3schools.com/html/mov_bbb.mp4' type='video/mp4'>Sorry, your browser does not support embedded videos.</video>"
        markdown_parts.append(video_html)

    if show_deep_link:
        link_html = "This is a sample deep link:\n<a href='https://www.google.com'>Click here to go to Google</a>"
        markdown_parts.append(link_html)

    if not markdown_parts:
        return {"markdown_string": "You did not request any content to be displayed. Please specify if you want to see an image, video, or link."}

    return {"markdown_string": "\n\n".join(markdown_parts)}

אמצעי בקרה של ערוצי אודיו וקול

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

הערות:

  • קידוד האודיו Linear16, ‏ mulaw ו-alaw נתמכים כקובץ אודיו.
  • אם משתמשים בקטגוריה של Cloud Storage ששייכת לפרויקט אחר בענן, צריך להעניק במפורש לחשבון השירות של Customer Engagement Suite‏ service-<PROJECT-NUMBER>@gcp-sa-ces.iam.gserviceaccount.comstorage.objects.get הרשאה לקטגוריה של Cloud Storage.
  • אפשר להשתמש בארגומנט הקלט interruptable כדי להגדיר אם משתמש הקצה יכול להפריע לאודיו שהוקלט מראש.
  • כשמפעילים מוזיקה, אפשר להשתמש בארגומנט הקלט cancellable כדי לציין שהפעלת המוזיקה צריכה להיפסק כשסוכן ה-AI יוצר תגובה חדשה.

השמעת אודיו מוקלט מראש שספציפי למותג

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

תשתמש ב-"transcript": "yyy" כדי לספק לסוכן את הטקסט של הפעלת האודיו, ותדאג שיהיה לו את ההקשר הדרוש כדי ליצור תשובות בהמשך.

השימוש ב-"interrupable": false מבטיח שהמשתמש לא יוכל להפריע להפעלת האודיו.

def before_model_callback(
    callback_context: CallbackContext,
    llm_request: LlmRequest
) -> Optional[LlmResponse]:
  for part in callback_context.get_last_user_input():
    if part.text == "<event>session start</event>":
      return LlmResponse.from_parts(parts=[
          Part.from_json(data='{"audioUri": "gs://path/to/audio/file", "transcript": "transcript for the audio file", "interruptable": false}')
      ])
  return None

השמעת מוזיקה שהוקלטה מראש בזמן הפעלת כלים איטיים (ללא אפשרות להשתלט על השיחה)

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

def after_model_callback(
    callback_context: CallbackContext,
    llm_response: LlmResponse
) -> Optional[LlmResponse]:
  for index, part in enumerate(llm_response.content.parts):
    if part.has_function_call("slow_tool"):
      play_music = Part.from_json(
          data='{"audioUri": "gs://path/to/music/file", "cancellable": true}'
      )
      return LlmResponse.from_parts(
          parts=llm_response.content.parts[:index] +
          [play_music] + llm_response.content.parts[index:]
      )
  return None

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

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

def before_model_callback(
    callback_context: CallbackContext,
    llm_request: LlmRequest
) -> Optional[LlmResponse]:
  for part in llm_request.contents[-1].parts:
    if part.has_function_response("async_tool"):
      text = Part.from_text(text="I'm submitting your order, it may take a while.")
      music = Part.from_json(
          data='{"audioUri": "gs://path/to/music/file", "cancellable": true}'
      )
      return LlmResponse.from_parts(parts=[text, music])
  return None

חסימת האפשרות למשתמשים להתפרץ לשיחה בתשובות מסוימות

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

בפעולה הזו נעשה שימוש בכלי המערכת customize_response.

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

  1. Callback (החזרה של פונקציה (Deterministic): כפייה של התשובה מהחזרה של פונקציה, כמו שמוצג בדוגמה.
  2. הוראות (מונחות על ידי הסוכן): מנחים את הסוכן להשתמש בכלי customize_response בהוראות שלו.
def before_model_callback(
    callback_context: CallbackContext,
    llm_request: LlmRequest
) -> Optional[LlmResponse]:
  for part in callback_context.get_last_user_input():
    if part.text == "<event>session start</event>":
      return LlmResponse.from_parts(parts=[
          Part.from_customized_response(
              content=("Hello, I'm Gemini. Please listen to the following legal "
                       "disclaimer: <LEGAL_DISCLAIMER>"),
              disable_barge_in=True
          ),
          Part.from_text("How can I help you today?")
      ])
  return None

תשובה מותאמת אישית כשלא מתקבל קלט

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

def before_model_callback(
    callback_context: CallbackContext,
    llm_request: LlmRequest
) -> Optional[LlmResponse]:
  for part in callback_context.get_last_user_input():
    if part.text:
      if "no user activity detected" in part.text:
        return LlmResponse.from_parts(parts=[
            Part.from_text(text="Hi, are you still there?")
        ])

  return None

טיפול בשגיאות

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

העברה לסוכן אחר במקרה של כשלים בכלי

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

def before_model_callback(
    callback_context: CallbackContext,
    llm_request: LlmRequest
) -> Optional[LlmResponse]:
  for part in llm_request.contents[-1].parts:
    if (part.has_function_response('authentication') and
        'error' in part.function_response.response['result']):
      return LlmResponse.from_parts(parts=[
          Part.from_text('Sorry something went wrong, let me transfer you to another agent.'),
          Part.from_agent_transfer(agent='escalation agent')
      ])
  return None

סיום מבוקר של הסשן במקרה של כשלים בכלי

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

דוגמה לקריאה חוזרת (callback):

def before_model_callback(
    callback_context: CallbackContext,
    llm_request: LlmRequest
) -> Optional[LlmResponse]:
  for part in llm_request.contents[-1].parts:
    if (part.has_function_response('authentication') and
        'error' in part.function_response.response['result']):
      return LlmResponse.from_parts(parts=[
          Part.from_text('Sorry something went wrong, please call back later.'),
          Part.from_end_session(reason='Failure during user authentication.')
      ])
  return None

הקשר ומשתנים

בקטע הזה מפורטים דפוסי שימוש במשתני הקשר.

העברת משתני הקשר לכלים של OpenAPI

כדי להשתמש ב-AI בהתאמה אישית, צריך כלים לגישה לנתוני סשן של משתמשים. הסתמכות על המודל כדי לשלוף ולהעביר פרטים חשובים באופן ידני, כמו מזהי סשנים או משתני משתמש, היא לא אמינה ואיטית. במקום זאת, הסוכן יכול להעביר משתני הקשר ספציפיים לכלים של OpenAPI. אפשר להשתמש ב-x-ces-session-context כדי לציין שהערך לא צריך להיות מופק על ידי המודל (והסכימה שלו לא גלויה למודל), אלא מגיע ממשתני הקשר.

בטבלה הבאה מפורטים הערכים האפשריים:

ערך תיאור
$context.project_id מזהה הפרויקט Google Cloud .
$context.project_number מספר הפרויקט Google Cloud .
$context.location המיקום (האזור) של הסוכן.
$context.app_id מזהה אפליקציית הסוכן.
$context.session_id המזהה הייחודי של הסשן.
$context.variables כל הערכים של משתני ההקשר כאובייקט.
$context.variables.variable_name הערך של משתנה הקשר הספציפי. מחליפים את variable_name בשם המשתנה.
openapi: 3.0.0
info:
  title: test-title
  description: test-description
  version: 1.0.0
paths:
  /test-path/{session_id}:
    post:
      parameters:
      - name: session_id
        in: path
        description: The session ID.
        required: true
        schema:
          type: string
        x-ces-session-context: $context.session_id
      - name: test_variable
        in: query
        description: Specific session variable.
        required: true
        schema:
          type: string
        x-ces-session-context: $context.variables.test_variable
      requestBody:
        description: test-description
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SessionParams'
      responses:
        '200':
          description: test-response-description
          content:
            application/json:
             schema:
                type: object
                properties:
                  result:
                    type: string
components:
  schemas:
    SessionParams:
      type: object
      description: all context variables
      x-ces-session-context: $context.variables

הנחיות דינמיות

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

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

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

משתנים:

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

הוראות:

The current user is: {username}
You can use {@TOOL: update_username} to update the user's name if they provide
it.

Follow the current instruction set below exactly.

{current_instructions}

כלי Python:

from typing import Optional

def update_username(username: str) -> Optional[str]:
  """Updates the current user's name."""
  set_variable("username", username)

התקשרות חזרה:

def before_model_callback(
  callback_context: CallbackContext,
  llm_request: LlmRequest
) -> Optional[LlmResponse]:
  username = callback_context.get_variable("username", None)

  if username == "Jenn":
    new_instructions = callback_context.get_variable("pirate_instructions")

  elif username == "Gary":
    new_instructions = callback_context.get_variable("lawyer_instructions")

  callback_context.set_variable("current_instructions", new_instructions)