כלים לקוד Python

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

שם ותיאור

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

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

סביבה

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

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

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

  • ‫function_call_id: מזהה ייחודי של קריאה ספציפית לכלי שמופעלת.
  • ‫user_content: מילון שמכיל את ההודעה האחרונה מהמשתמש, כולל הטקסט והתפקיד. זה אחד המאפיינים הכי רווחיים.
  • ‫state: מילון שמייצג את מצב הסשן. אפשר להשתמש בזה כדי לאחסן ולאחזר משתנים שצריכים להישמר לאורך כמה תורות בשיחה (פרופיל משתמש, תוכן עגלת קניות וכו').
  • ‫events: רשימה של כל האירועים בהיסטוריית השיחות עד לנקודה הזו, שמאפשרת לכם ליצור כלים עם מודעות הקשר מורכבת יותר.
  • ‫session_id: המזהה הייחודי של כל סשן השיחה.
  • ‫invocation_id: מזהה ייחודי של התור הנוכחי בשיחה.
  • ‫agent_name: השם של הסוכן שמבצע את הכלי.

פעולות ספציפיות לערוץ ולסוג המדיה

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

אנונימיזציה דטרמיניסטית של כלים באמצעות before_model_callback

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

  • צ'אט/כלים ויזואליים (ווידג'טים אינטראקטיביים בממשק המשתמש כמו custom_actions, card_builder, rich_carousel)
  • כלי טלפוניה (send_sms_link, transfer_call, dtmf_input)

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

בדוגמה הבאה נעשה שימוש ב-before_model_callback לצד llm_request.config.hide_tool() (או excluded_tools). הפונקציה להחזרת ערך (callback) בודקת את _session.get("modality") ומסתירה באופן דינמי כלים שלא רלוונטיים לאופן הפעולה הפעיל לפני שהמודל יוצר את התגובה שלו:

def before_model_callback(
    callback_context: CallbackContext, llm_request: LlmRequest
) -> None:
  _session = callback_context.variables.get("_session", {})
  modality = _session.get("modality", "TEXT").upper()

  if modality == "VOICE":
    # Hide visual UI widgets & chat-only tools from the model on voice calls
    for tool_name in ["custom_actions", "plan_builder", "rich_carousel"]:
      llm_request.config.hide_tool(tool_name)
  else:
    # Hide telephony-specific tools on text chat
    for tool_name in ["send_sms_link", "hang_up_call"]:
      llm_request.config.hide_tool(tool_name)

כלי Python שקוראים לכלים אחרים

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

# Deterministically call another tool from this tool.
# This syntax for OpenAPI spec tool is:
# tools.<tool_name>_<endpoint_name>({tool_args})
res = tools.crm_service_get_cart_information({})

דוגמאות קוד

בקטעים הבאים מופיעות דוגמאות.

קבלת הקלט האחרון של המשתמש

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

from typing import Optional

# Docstrings in tools are important because they are directly
# sent to the model as the description for the tool. You should
# think of docstrings as an extension of prompting. Clear and
# descriptive docstrings will yield higher quality tool
# selection from the model.
def get_last_user_utterance() -> Optional[str]:
  """
  Retrieves the most recent message sent by the user from the conversation history.

  Returns:
    The text of the last user message, or None if no user messages are found.
  """
  # The 'context.user_content' contains the last input data
  # provided by the user.
  # We can filter it to find only the text input from the user.
  user_messages = [
    part.text for part in context.user_content.parts
    if context.user_content.role == "user"
  ]

  if user_messages:
    # The most recent message is the first one in the list.
    return user_messages[0]

  return None

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

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

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

from pydantic import BaseModel, Field
from typing import Optional, Dict, Any

# Using Pydantic defines the expected structure of your state variables. This makes your code more reliable and easier to
# debug.
class CustomerProfile(BaseModel):
  email: Optional[str] = None
  loyalty_points: int = Field(default=0, ge=0) # Must be >= 0
  plan: str = "Standard"

# Docstrings in tools are important because they are directly
# sent to the model as the description for the tool. You should
# think of docstrings as an extension of prompting. Clear and
# descriptive docstrings will yield higher quality tool
# selection from the model.
def update_customer_loyalty_points(points_to_add: int) -> Dict[str, Any]:
  """
  Adds loyalty points to the customer's profile and returns the updated profile.

  Args:
    points_to_add: The number of loyalty points to add to the existing total.

  Returns:
    A dictionary containing the customer's updated profile information.
  """
  # 1. Get the current profile data from the session state.
  # The .get() method safely returns an empty dict if
  # 'customer_profile' doesn't exist.
  current_profile_data = context.state.get("customer_profile", {})

  # 2. Load the data into a Pydantic model for validation and easy access.
  profile = CustomerProfile(**current_profile_data)

  # 3. Perform the business logic.
  # Print statements can be used for debugging and will show
  # up in the tracing details.
  profile.loyalty_points += points_to_add
  print(f"Updated loyalty points to: {profile.loyalty_points}")

  # 4. Save the updated data back into the session state.
  # .model_dump() converts the Pydantic model back to a
  # dictionary.
  context.state["customer_profile"] = profile.model_dump()

  return context.state["customer_profile"]

בקשות לאחזור מהרשת החיצונית

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

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

  • אין גישה לרשת פרטית (PNA): כלי קוד של Python לא יכולים לגשת ישירות לכתובות IP פרטיות או לפתור שרתי DNS פרטיים, גם אם Service Directory מוגדר. אם אתם צריכים לגשת למשאבים ברשת פרטית, אתם צריכים להשתמש בכלי שתומך בגישה לרשת פרטית (כמו כלי OpenAPI או MCP).
  • גישה לאינטרנט הציבורי: כלי קוד Python יכולים לגשת רק לנקודות קצה שזמינות לכולם באינטרנט.

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

from typing import Optional

# Docstrings in tools are important because they are directly sent to the model as the
# description for the tool. You should think of docstrings as an extension of prompting.
# Clear and descriptive docstrings will yield higher quality tool selection from the model.

def get_random_fact() -> Optional[str]:
  """
  Fetches a random fact from a public API.

  Returns:
      A string containing the fact on success, or None if an error occurs.
  """
  # The 'ces_requests' library is inspired by 'requests', a  standard and powerful way in Python
  # to make HTTP network calls to any external API, just like you would with `curl` or a web browser.
  url = "https://uselessfacts.jsph.pl/api/v2/facts/random"

  try:
      # This example calls a public API that is completely open and requires no authentication
      # (like an API key). Many other APIs for services like weather or e-commerce require you
      # to send credentials, often as an API key in the request headers or parameters.
      res = ces_requests.get(url=url, json=request_body, headers=headers)

      # Example POST request
      #res = ces_requests.post(url=url, json=request_body, headers=headers)

      # This is a standard practice with 'ces_requests' to check if the call was successful. It will
      # raise an error for statuses like 404 or 500.
      res.raise_for_status()

      fact_data = res.json()
      return fact_data.get("text")

  except:
      return None