Outils de code Python

Vous pouvez fournir du code Python intégré en tant qu'outil pour votre agent, ce qui vous permet d'étendre ses capacités. Ce code peut accepter les entrées fournies par l'agent et renvoyer un résultat que l'agent utilise dans la conversation. Vous pouvez implémenter n'importe quelle logique personnalisée, vous connecter à des API ou des bases de données propriétaires, et garantir des résultats déterministes pour les tâches qui nécessitent de la précision.

Nom et description

Lorsque vous créez l'outil, son nom et celui de la fonction principale à appeler doivent être identiques et écrits en snake case.

Les docstrings sont essentielles pour définir un outil Python. La docstring de la fonction est utilisée comme description de l'outil, qui est fournie aux agents qui l'utilisent. Considérez les docstrings comme une extension des requêtes. Une docstring claire, descriptive et bien structurée a une incidence directe sur la capacité du modèle à comprendre ce que fait votre outil, quand l'utiliser et quels arguments fournir. C'est la clé pour sélectionner des outils fiables et précis.

Environnement

Dans le code de votre outil Python, vous avez accès à certaines classes et fonctions qui vous aident à écrire le code. Pour en savoir plus, consultez la documentation de référence sur l'environnement d'exécution Python.

Par exemple, l'objet context de type ToolContext est une variable disponible à l'échelle mondiale qui fournit un instantané de l'état actuel de la conversation. Vous n'avez pas besoin de l'importer ni de le définir comme paramètre. Vous pouvez y accéder directement. Il contient des informations utiles pour exécuter une logique complexe.

Voici une liste des clés disponibles dans l'objet de contexte :

  • function_call_id : ID unique de l'appel d'outil spécifique en cours d'exécution.
  • user_content : dictionnaire contenant le message le plus récent de l'utilisateur, y compris le texte et le rôle. Il s'agit de l'un des attributs les plus couramment utilisés.
  • state : dictionnaire représentant l'état de la session. Vous pouvez l'utiliser pour stocker et récupérer des variables qui doivent persister sur plusieurs tours de conversation (profil utilisateur, contenu du panier, etc.).
  • events : liste de tous les événements de l'historique des conversations jusqu'à présent, qui vous permet de créer des outils avec une conscience contextuelle plus complexe.
  • session_id : identifiant unique de l'ensemble de la session de conversation.
  • invocation_id : identifiant unique du tour de conversation en cours.
  • agent_name : nom de l'agent qui exécute l'outil.

Actions spécifiques aux canaux et aux modalités

Lorsque vous implémentez des outils ou des rappels Python, il est souvent préférable d'avoir le même comportement pour toutes les modalités et tous les canaux, mais il existe des cas où un comportement spécifique à un canal ou à une modalité est souhaité. Vous pouvez vérifier le canal ou la modalité d'une conversation à l'aide de la variable globale _session.

Masquage déterministe des outils à l'aide de before_model_callback

Lorsqu'un agent prend en charge à la fois le texte et la voix, son ensemble d'outils contient souvent un mélange des éléments suivants :

  • Outils de chat/visuels (widgets d'interface utilisateur interactifs tels que custom_actions, card_builder, rich_carousel)
  • Outils de téléphonie (send_sms_link, transfer_call, dtmf_input)

Même si les instructions de requête pour une modalité donnée (comme la voix) ne font pas référence aux outils de chat, la présence de ces outils dans le schéma du modèle consomme des jetons de requête et peut entraîner des appels d'outils involontaires.

L'exemple suivant utilise before_model_callback avec llm_request.config.hide_tool() (ou excluded_tools). Le rappel vérifie _session.get("modality") et masque dynamiquement les outils qui ne s'appliquent pas à la modalité active avant que le modèle ne génère sa réponse :

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)

Outils Python appelant d'autres outils

Lorsque vous définissez des outils de code Python, vous pouvez appeler explicitement d'autres outils définis dans l'application d'agent. Par exemple, si vous disposez d'un outil OpenAPI appelé crm_service_get_cart_information, vous pouvez l'appeler avec le code suivant :

# 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({})

Exemples de code

Les sections suivantes fournissent des exemples.

Obtenir la dernière entrée utilisateur

Cet exemple illustre une fonctionnalité fondamentale : l'accès au message le plus récent de l'utilisateur. L'outil inspecte l'objet context.user_content et extrait le texte du dernier tour de l'utilisateur. Ce modèle est essentiel pour tout outil qui doit effectuer une action en fonction de ce que l'utilisateur vient de dire.

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

Obtenir et mettre à jour des variables

Les modèles de langage ne peuvent pas modifier directement l'état de la session. C'est intentionnel, car cela garantit que les changements d'état sont gérés de manière contrôlée et prévisible. L'état ne peut être modifié que par deux mécanismes : les outils Python ou les rappels.

Cet exemple montre comment un outil Python peut gérer l'état. L'outil lit d'abord le customer_profile actuel à partir de context.state. Il exécute ensuite sa logique métier (ajout de points) et réécrit le profil mis à jour dans context.state. Ce nouvel état est ensuite disponible pour l'agent et les autres outils pendant le reste de la session.

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

Requêtes réseau externes

Les outils Python peuvent effectuer des requêtes réseau externes, ce qui est utile pour récupérer des données en temps réel ou s'intégrer à des services tiers qui ne disposent pas de spécification OpenAPI. Cela constitue une alternative flexible à l'utilisation d'outils basés sur OpenAPI.

Principales limites des outils de code Python :

  • Aucun accès au réseau privé (ANP) : les outils de code Python ne peuvent pas accéder directement aux adresses IP privées ni résoudre les domaines DNS privés, même si l'Annuaire des services est configuré. Si vous devez accéder à des ressources dans un réseau privé, vous devez utiliser un type d'outil compatible avec l'accès au réseau privé (comme les outils OpenAPI ou MCP).
  • Accès à Internet public : les outils de code Python ne peuvent accéder qu'aux points de terminaison accessibles au public sur Internet.

L'exemple utilise la bibliothèque de requêtes standard (disponible dans l'environnement) pour appeler une API publique et récupérer une information aléatoire.

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