Strumenti per il codice Python

Puoi fornire codice Python incorporato come strumento per il tuo agente, il che offre flessibilità per estendere le funzionalità dell'agente. Questo codice può accettare gli input forniti dall'agente e restituire un risultato utilizzato dall'agente nella conversazione. Puoi implementare qualsiasi logica personalizzata, connetterti ad API o database proprietari e garantire risultati deterministici per le attività che richiedono precisione.

Nome e descrizione

Quando crei lo strumento, il nome dello strumento e il nome della funzione principale da chiamare devono essere uguali in snake case.

Le docstring sono una parte fondamentale della definizione di uno strumento Python. La docstring della funzione viene utilizzata come descrizione dello strumento, che viene fornita agli agenti che utilizzano lo strumento. Devi considerare le docstring come un'estensione dei prompt. Una docstring chiara, descrittiva e ben strutturata influisce direttamente sulla capacità del modello di comprendere cosa fa il tuo strumento, quando utilizzarlo e quali argomenti fornire. Questa è la chiave per ottenere una selezione di strumenti affidabile e accurata.

Ambiente

Nel codice dello strumento Python, hai accesso a determinate classi e funzioni che ti aiutano a scrivere il codice. Per saperne di più, consulta il riferimento al runtime Python.

Ad esempio, l'oggetto context di tipo ToolContext è una variabile disponibile a livello globale che fornisce un'istantanea dello stato attuale della conversazione. Non devi importarlo o definirlo come parametro, puoi accedervi direttamente. Contiene informazioni preziose per l'esecuzione di logiche complesse.

Di seguito è riportata una suddivisione delle chiavi disponibili nell'oggetto contesto:

  • function_call_id: un ID univoco per la chiamata specifica allo strumento in esecuzione.
  • user_content: un dizionario contenente il messaggio più recente dell'utente, inclusi il testo e il ruolo. Si tratta di uno degli attributi di uso comune.
  • state: un dizionario che rappresenta lo stato della sessione. Puoi utilizzarlo per archiviare e recuperare variabili che devono essere mantenute in più turni di una conversazione (profilo utente, contenuti del carrello e così via).
  • events: un elenco di tutti gli eventi nella cronologia della conversazione fino a questo punto, che ti consente di creare strumenti con una consapevolezza contestuale più complessa.
  • session_id: l'identificatore univoco dell'intera sessione di conversazione.
  • invocation_id: un identificatore univoco per il turno corrente della conversazione.
  • agent_name: il nome dell'agente che esegue lo strumento.

Azioni specifiche per canale e modalità

Quando implementi strumenti o callback Python, spesso è meglio avere lo stesso comportamento per tutte le modalità e i canali, ma ci sono casi in cui è auspicabile un comportamento specifico per canale o modalità. Puoi controllare il canale o la modalità di una conversazione utilizzando la variabile globale _session.

Mascheramento dello strumento deterministico utilizzando before_model_callback

Quando un agente supporta sia il testo che la voce, il suo set di strumenti spesso contiene un mix di:

  • Chat/Strumenti visivi (widget UI interattivi come custom_actions, card_builder, rich_carousel)
  • Strumenti di telefonia (send_sms_link, transfer_call, dtmf_input)

Anche se le istruzioni del prompt per una determinata modalità (ad esempio la voce) potrebbero non fare riferimento agli strumenti di chat, la presenza di questi strumenti nello schema del modello consuma token del prompt e potrebbe causare invocazioni involontarie degli strumenti.

L'esempio seguente utilizza before_model_callback insieme a llm_request.config.hide_tool() (o excluded_tools), il callback controlla _session.get("modality") e maschera dinamicamente gli strumenti non applicabili alla modalità attiva prima che il modello generi la risposta:

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)

Strumenti Python che chiamano altri strumenti

Quando definisci gli strumenti di codice Python, puoi chiamare esplicitamente altri strumenti definiti nell'applicazione agente. Ad esempio, se hai uno strumento OpenAPI chiamato crm_service_get_cart_information, puoi chiamarlo con il seguente codice:

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

Esempi di codice

Nelle sezioni seguenti sono riportati alcuni esempi.

Ottieni l'ultimo input utente'utente

Questo esempio mostra una funzionalità fondamentale: l'accesso al messaggio più recente dell'utente. Lo strumento esamina l'oggetto context.user_content ed estrae il testo dell'ultimo turno dell'utente. Questo pattern è essenziale per qualsiasi strumento che deve eseguire un'azione in base direttamente a ciò che l'utente ha appena detto.

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

Recuperare e aggiornare le variabili

I modelli linguistici non possono modificare direttamente lo stato della sessione. Si tratta di un comportamento intenzionale, in quanto garantisce che le modifiche dello stato vengano gestite in modo controllato e prevedibile. Lo stato può essere modificato solo tramite due meccanismi: strumenti Python o callback.

Questo esempio mostra come uno strumento Python può gestire lo stato. Lo strumento legge innanzitutto il valore customer_profile corrente da context.state. Quindi esegue la logica di business (aggiungendo punti) e riscrive il profilo aggiornato in context.state. Questo nuovo stato sarà quindi disponibile per l'agente e per altri strumenti per il resto della sessione.

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

Richieste di rete esterne

Gli strumenti Python possono effettuare richieste di rete esterne, il che è utile per recuperare dati in tempo reale o per l'integrazione con servizi di terze parti che non dispongono di una specifica OpenAPI. In questo modo, avrai a disposizione un'alternativa flessibile all'utilizzo di strumenti basati su OpenAPI.

Limitazioni principali per gli strumenti di codice Python:

  • Nessun accesso alla rete privata (PNA): gli strumenti di codice Python non possono accedere direttamente agli indirizzi IP privati o risolvere i domini DNS privati, anche se Service Directory è configurato. Se devi accedere alle risorse all'interno di una rete privata, devi utilizzare un tipo di strumento che supporti l'accesso alla rete privata (ad esempio gli strumenti OpenAPI o MCP).
  • Accesso a internet pubblico: gli strumenti di codice Python possono accedere solo agli endpoint disponibili pubblicamente su internet.

L'esempio utilizza la libreria requests standard (disponibile nell'ambiente) per chiamare un'API pubblica e recuperare un fatto casuale.

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