Come usare la chiamata di funzioni con il Servizio OpenAI di Azure (anteprima)

Le versioni più recenti di gpt-35-turbo e gpt-4 sono state ottimizzate per funzionare con le funzioni e sono in grado di determinare quando e come deve essere chiamata una funzione. Se una o più funzioni sono incluse nella richiesta, il modello determina se una delle funzioni deve essere chiamata in base al contesto del prompt. Quando il modello determina che deve essere chiamata una funzione, risponde con un oggetto JSON, che include gli argomenti della funzione.

I modelli formulano chiamate API e generano output dei dati della struttura, tutti basati sulle funzioni specificate. È importante notare che, mentre i modelli possono generare queste chiamate, è necessario eseguirle, assicurandosi di mantenere il controllo.

A livello generale è possibile suddividere l'uso delle funzioni in tre passaggi:

  1. Chiamare l'API di completamento della chat con le funzioni e l'input dell'utente
  2. Usare la risposta del modello per chiamare l'API o la funzione
  3. Chiamare di nuovo l'API di completamento della chat, inclusa la risposta dalla funzione per ottenere una risposta finale

Importante

I parametri functions e function_call sono stati deprecati con il rilascio della versione 2023-12-01-preview dell’API. La sostituzione di functions è il parametro tools. La sostituzione di function_call è il parametro tool_choice.

Supporto per le chiamate di funzione

Chiamata di funzioni parallele

  • gpt-35-turbo (1106)
  • gpt-35-turbo (0125)
  • gpt-4 (1106-anteprima)
  • gpt-4 (0125-anteprima)
  • gpt-4 (vision-preview)
  • gpt-4 (2024-04-09)
  • gpt-4o (2024-05-13)
  • gpt-4o-mini (2024-07-18)

Il supporto per la funzione parallela è stato aggiunto per la prima volta nella versione dell'API 2023-12-01-preview

Chiamata di funzioni di base con strumenti

  • Tutti i modelli che supportano la chiamata di funzioni parallele
  • gpt-4 (0613)
  • gpt-4-32k (0613)
  • gpt-35-turbo-16k (0613)
  • gpt-35-turbo (0613)

Esempio di chiamata a uno strumento/funzione singolo

Prima di tutto verrà illustrata una semplice chiamata di funzione giocattolo che può controllare l'ora in tre posizioni hardcoded con un singolo strumento/funzione definito. Sono state aggiunte istruzioni di stampa per semplificare l'esecuzione del codice:

import os
import json
from openai import AzureOpenAI
from datetime import datetime
from zoneinfo import ZoneInfo

# Initialize the Azure OpenAI client
client = AzureOpenAI(
    azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT"), 
    api_key=os.getenv("AZURE_OPENAI_API_KEY"),  
    api_version="2024-05-01-preview"
)

# Define the deployment you want to use for your chat completions API calls

deployment_name = "<YOUR_DEPLOYMENT_NAME_HERE>"

# Simplified timezone data
TIMEZONE_DATA = {
    "tokyo": "Asia/Tokyo",
    "san francisco": "America/Los_Angeles",
    "paris": "Europe/Paris"
}

def get_current_time(location):
    """Get the current time for a given location"""
    print(f"get_current_time called with location: {location}")  
    location_lower = location.lower()
    
    for key, timezone in TIMEZONE_DATA.items():
        if key in location_lower:
            print(f"Timezone found for {key}")  
            current_time = datetime.now(ZoneInfo(timezone)).strftime("%I:%M %p")
            return json.dumps({
                "location": location,
                "current_time": current_time
            })
    
    print(f"No timezone data found for {location_lower}")  
    return json.dumps({"location": location, "current_time": "unknown"})

def run_conversation():
    # Initial user message
    messages = [{"role": "user", "content": "What's the current time in San Francisco"}] # Single function call
    #messages = [{"role": "user", "content": "What's the current time in San Francisco, Tokyo, and Paris?"}] # Parallel function call with a single tool/function defined

    # Define the function for the model
    tools = [
        {
            "type": "function",
            "function": {
                "name": "get_current_time",
                "description": "Get the current time in a given location",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "The city name, e.g. San Francisco",
                        },
                    },
                    "required": ["location"],
                },
            }
        }
    ]

    # First API call: Ask the model to use the function
    response = client.chat.completions.create(
        model=deployment_name,
        messages=messages,
        tools=tools,
        tool_choice="auto",
    )

    # Process the model's response
    response_message = response.choices[0].message
    messages.append(response_message)

    print("Model's response:")  
    print(response_message)  

    # Handle function calls
    if response_message.tool_calls:
        for tool_call in response_message.tool_calls:
            if tool_call.function.name == "get_current_time":
                function_args = json.loads(tool_call.function.arguments)
                print(f"Function arguments: {function_args}")  
                time_response = get_current_time(
                    location=function_args.get("location")
                )
                messages.append({
                    "tool_call_id": tool_call.id,
                    "role": "tool",
                    "name": "get_current_time",
                    "content": time_response,
                })
    else:
        print("No tool calls were made by the model.")  

    # Second API call: Get the final response from the model
    final_response = client.chat.completions.create(
        model=deployment_name,
        messages=messages,
    )

    return final_response.choices[0].message.content

# Run the conversation and print the result
print(run_conversation())

Output:

Model's response:
ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_pOsKdUlqvdyttYB67MOj434b', function=Function(arguments='{"location":"San Francisco"}', name='get_current_time'), type='function')])
Function arguments: {'location': 'San Francisco'}
get_current_time called with location: San Francisco
Timezone found for san francisco
The current time in San Francisco is 09:24 AM.

Se si usa una distribuzione di modelli che supporta le chiamate di funzione parallele, è possibile convertire questo esempio in una chiamata di funzione in parallela modificando la matrice di messaggi in modo da richiedere il tempo in più posizioni anziché una.

A tale scopo, scambiare i commenti in queste due righe:

    messages = [{"role": "user", "content": "What's the current time in San Francisco"}] # Single function call
    #messages = [{"role": "user", "content": "What's the current time in San Francisco, Tokyo, and Paris?"}] # Parallel function call with a single tool/function defined

Per avere un aspetto simile al seguente ed eseguire di nuovo il codice:

    #messages = [{"role": "user", "content": "What's the current time in San Francisco"}] # Single function call
    messages = [{"role": "user", "content": "What's the current time in San Francisco, Tokyo, and Paris?"}] # Parallel function call with a single tool/function defined

Verrà generato l'output seguente:

Output:

Model's response:
ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_IjcAVz9JOv5BXwUx1jd076C1', function=Function(arguments='{"location": "San Francisco"}', name='get_current_time'), type='function'), ChatCompletionMessageToolCall(id='call_XIPQYTCtKIaNCCPTdvwjkaSN', function=Function(arguments='{"location": "Tokyo"}', name='get_current_time'), type='function'), ChatCompletionMessageToolCall(id='call_OHIB5aJzO8HGqanmsdzfytvp', function=Function(arguments='{"location": "Paris"}', name='get_current_time'), type='function')])
Function arguments: {'location': 'San Francisco'}
get_current_time called with location: San Francisco
Timezone found for san francisco
Function arguments: {'location': 'Tokyo'}
get_current_time called with location: Tokyo
Timezone found for tokyo
Function arguments: {'location': 'Paris'}
get_current_time called with location: Paris
Timezone found for paris
As of now, the current times are:

- **San Francisco:** 11:15 AM
- **Tokyo:** 03:15 AM (next day)
- **Paris:** 08:15 PM

Le chiamate di funzione parallele consentono di eseguire più chiamate di funzione insieme, consentendo l'esecuzione parallela e il recupero dei risultati. In questo modo si riduce il numero di chiamate all'API da effettuare e può migliorare le prestazioni complessive.

Ad esempio, nell'app in tempo semplice è stato recuperato più volte contemporaneamente. In questo modo è stato generato un messaggio di completamento della chat con tre chiamate di funzione nella matrice tool_calls, ognuna con un id univoco. Se si desidera rispondere a queste chiamate di unzione, aggiungere tre nuovi messaggi alla conversazione, ognuno contenente il risultato di una chiamata di funzione, con un tool_call_id che fa riferimento al id da tools_calls.

Per forzare il modello a chiamare una funzione specifica, impostare il parametro tool_choice con un nome di funzione specifico. È anche possibile forzare il modello a generare un messaggio rivolto all'utente impostando tool_choice: "none".

Nota

Il comportamento predefinito (tool_choice: "auto") prevede che il modello decida da solo se chiamare una funzione e, in tal caso, quale funzione chiamare.

Chiamata di funzioni parallele con più funzioni

Verrà ora illustrato un altro esempio di chiamata di funzione giocattolo, questa volta con due diversi strumenti/funzioni definiti.

import os
import json
from openai import AzureOpenAI
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

# Initialize the Azure OpenAI client
client = AzureOpenAI(
    azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT"), 
    api_key=os.getenv("AZURE_OPENAI_API_KEY"),  
    api_version="2024-05-01-preview"
)

# Provide the model deployment name you want to use for this example

deployment_name = "YOUR_DEPLOYMENT_NAME_HERE" 

# Simplified weather data
WEATHER_DATA = {
    "tokyo": {"temperature": "10", "unit": "celsius"},
    "san francisco": {"temperature": "72", "unit": "fahrenheit"},
    "paris": {"temperature": "22", "unit": "celsius"}
}

# Simplified timezone data
TIMEZONE_DATA = {
    "tokyo": "Asia/Tokyo",
    "san francisco": "America/Los_Angeles",
    "paris": "Europe/Paris"
}

def get_current_weather(location, unit=None):
    """Get the current weather for a given location"""
    print(f"get_current_weather called with location: {location}, unit: {unit}")  
    
    for key in WEATHER_DATA:
        if key in location_lower:
            print(f"Weather data found for {key}")  
            weather = WEATHER_DATA[key]
            return json.dumps({
                "location": location,
                "temperature": weather["temperature"],
                "unit": unit if unit else weather["unit"]
            })
    
    print(f"No weather data found for {location_lower}")  
    return json.dumps({"location": location, "temperature": "unknown"})

def get_current_time(location):
    """Get the current time for a given location"""
    print(f"get_current_time called with location: {location}")  
    location_lower = location.lower()
    
    for key, timezone in TIMEZONE_DATA.items():
        if key in location_lower:
            print(f"Timezone found for {key}")  
            current_time = datetime.now(ZoneInfo(timezone)).strftime("%I:%M %p")
            return json.dumps({
                "location": location,
                "current_time": current_time
            })
    
    print(f"No timezone data found for {location_lower}")  
    return json.dumps({"location": location, "current_time": "unknown"})

def run_conversation():
    # Initial user message
    messages = [{"role": "user", "content": "What's the weather and current time in San Francisco, Tokyo, and Paris?"}]

    # Define the functions for the model
    tools = [
        {
            "type": "function",
            "function": {
                "name": "get_current_weather",
                "description": "Get the current weather in a given location",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "The city name, e.g. San Francisco",
                        },
                        "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                    },
                    "required": ["location"],
                },
            }
        },
        {
            "type": "function",
            "function": {
                "name": "get_current_time",
                "description": "Get the current time in a given location",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "The city name, e.g. San Francisco",
                        },
                    },
                    "required": ["location"],
                },
            }
        }
    ]

    # First API call: Ask the model to use the functions
    response = client.chat.completions.create(
        model=deployment_name,
        messages=messages,
        tools=tools,
        tool_choice="auto",
    )

    # Process the model's response
    response_message = response.choices[0].message
    messages.append(response_message)

    print("Model's response:")  
    print(response_message)  

    # Handle function calls
    if response_message.tool_calls:
        for tool_call in response_message.tool_calls:
            function_name = tool_call.function.name
            function_args = json.loads(tool_call.function.arguments)
            print(f"Function call: {function_name}")  
            print(f"Function arguments: {function_args}")  
            
            if function_name == "get_current_weather":
                function_response = get_current_weather(
                    location=function_args.get("location"),
                    unit=function_args.get("unit")
                )
            elif function_name == "get_current_time":
                function_response = get_current_time(
                    location=function_args.get("location")
                )
            else:
                function_response = json.dumps({"error": "Unknown function"})
            
            messages.append({
                "tool_call_id": tool_call.id,
                "role": "tool",
                "name": function_name,
                "content": function_response,
            })
    else:
        print("No tool calls were made by the model.")  

    # Second API call: Get the final response from the model
    final_response = client.chat.completions.create(
        model=deployment_name,
        messages=messages,
    )

    return final_response.choices[0].message.content

# Run the conversation and print the result
print(run_conversation())

Output

Model's response:
ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_djHAeQP0DFEVZ2qptrO0CYC4', function=Function(arguments='{"location": "San Francisco", "unit": "celsius"}', name='get_current_weather'), type='function'), ChatCompletionMessageToolCall(id='call_q2f1HPKKUUj81yUa3ITLOZFs', function=Function(arguments='{"location": "Tokyo", "unit": "celsius"}', name='get_current_weather'), type='function'), ChatCompletionMessageToolCall(id='call_6TEY5Imtr17PaB4UhWDaPxiX', function=Function(arguments='{"location": "Paris", "unit": "celsius"}', name='get_current_weather'), type='function'), ChatCompletionMessageToolCall(id='call_vpzJ3jElpKZXA9abdbVMoauu', function=Function(arguments='{"location": "San Francisco"}', name='get_current_time'), type='function'), ChatCompletionMessageToolCall(id='call_1ag0MCIsEjlwbpAqIXJbZcQj', function=Function(arguments='{"location": "Tokyo"}', name='get_current_time'), type='function'), ChatCompletionMessageToolCall(id='call_ukOu3kfYOZR8lpxGRpdkhhdD', function=Function(arguments='{"location": "Paris"}', name='get_current_time'), type='function')])
Function call: get_current_weather
Function arguments: {'location': 'San Francisco', 'unit': 'celsius'}
get_current_weather called with location: San Francisco, unit: celsius
Weather data found for san francisco
Function call: get_current_weather
Function arguments: {'location': 'Tokyo', 'unit': 'celsius'}
get_current_weather called with location: Tokyo, unit: celsius
Weather data found for tokyo
Function call: get_current_weather
Function arguments: {'location': 'Paris', 'unit': 'celsius'}
get_current_weather called with location: Paris, unit: celsius
Weather data found for paris
Function call: get_current_time
Function arguments: {'location': 'San Francisco'}
get_current_time called with location: San Francisco
Timezone found for san francisco
Function call: get_current_time
Function arguments: {'location': 'Tokyo'}
get_current_time called with location: Tokyo
Timezone found for tokyo
Function call: get_current_time
Function arguments: {'location': 'Paris'}
get_current_time called with location: Paris
Timezone found for paris
Here's the current information for the three cities:

### San Francisco
- **Time:** 09:13 AM
- **Weather:** 72°C (quite warm!)

### Tokyo
- **Time:** 01:13 AM (next day)
- **Weather:** 10°C

### Paris
- **Time:** 06:13 PM
- **Weather:** 22°C

Is there anything else you need?

Importante

La risposta JSON potrebbe non essere sempre valida, quindi è necessario aggiungere una logica aggiuntiva al codice per poter gestire gli errori. Per alcuni casi d'uso potrebbe essere necessario usare l'ottimizzazione per migliorare la funzione che chiama le prestazioni.

Progettazione di prompt con funzioni

Quando si definisce una funzione come parte della richiesta, i dettagli vengono inseriti nel messaggio di sistema usando una sintassi specifica su cui è stato eseguito il training del modello. Ciò significa che le funzioni usano i token nel prompt e che è possibile applicare tecniche di progettazione di prompt per ottimizzare le prestazioni delle chiamate di funzione. Il modello usa l'intero contesto del prompt per determinare se una funzione deve essere chiamata, compresi la definizione della funzione, il messaggio di sistema e i messaggi dell'utente.

Miglioramento della qualità e dell'affidabilità

Se il modello non chiama la funzione quando o come ci si aspetta, è possibile provare a migliorare la qualità.

Specificare altri dettagli nella definizione della funzione

È importante fornire un description significativo della funzione e fornire descrizioni per qualsiasi parametro che potrebbe non essere ovvio per il modello. Ad esempio, nella descrizione del parametro location è possibile includere dettagli aggiuntivi ed esempi sul formato della posizione.

"location": {
    "type": "string",
    "description": "The location of the hotel. The location should include the city and the state's abbreviation (i.e. Seattle, WA or Miami, FL)"
},
Fornire più contesto nel messaggio di sistema

Il messaggio di sistema può essere usato anche per fornire più contesto al modello. Ad esempio, se si dispone di una funzione denominata search_hotels è possibile includere un messaggio di sistema simile al seguente per indicare al modello di chiamare la funzione quando un utente chiede aiuto per trovare un hotel.

{"role": "system", "content": "You're an AI assistant designed to help users search for hotels. When a user asks for help finding a hotel, you should call the search_hotels function."}
Indicare al modello di porre domande chiare

In alcuni casi, si desidera indicare al modello di porre domande chiare per evitare di fare ipotesi sui valori da usare con le funzioni. Ad esempio, con search_hotels si vuole che il modello richieda chiarimenti se la richiesta dell'utente non include dettagli su location. Per indicare al modello di porre una domanda chiara, è possibile includere nel messaggio di sistema un contenuto come nell'esempio seguente.

{"role": "system", "content": "Don't make assumptions about what values to use with functions. Ask for clarification if a user request is ambiguous."}

Riduzione degli errori

Un'altra area in cui la progettazione di prompt può essere utile consiste nel ridurre gli errori nelle chiamate di funzione. I modelli vengono sottoposti a training per generare chiamate di funzione corrispondenti allo schema definito, ma i modelli generano una chiamata di funzione che non corrisponde allo schema definito o tenta di chiamare una funzione non inclusa.

Se il modello genera chiamate di funzione non specificate, provare a includere una frase nel messaggio di sistema che indica "Only use the functions you have been provided with.".

Uso responsabile della chiamata di funzioni

Analogamente a qualsiasi sistema di intelligenza artificiale, l'uso della chiamata di funzione per integrare i modelli linguistici con altri strumenti e sistemi presenta potenziali rischi. È importante comprendere i rischi che le chiamate di funzione potrebbero presentare e adottare misure per assicurarsi di usare le funzionalità in modo responsabile.

Ecco alcuni suggerimenti che consentono di usare le funzioni in modo sicuro e sicuro:

  • Convalidare le chiamate di funzione: verificare sempre le chiamate di funzione generate dal modello. Ciò include il controllo dei parametri, la funzione chiamata e la verifica dell'allineamento della chiamata all'azione desiderata.
  • Usare dati e strumenti attendibili: usare solo i dati provenienti da origini attendibili e verificate. I dati non attendibili presenti nell'output di una funzione potrebbero essere usati per indicare al modello di scrivere chiamate di funzione in modo diverso da quello previsto.
  • Seguire il principio dei privilegi minimi: concedere solo l'accesso minimo necessario per la funzione in modo da poter eseguire il proprio processo. In questo modo si riduce l'impatto potenziale se una funzione viene utilizzata o sfruttata in modo improprio. Ad esempio, se si usano chiamate di funzione per eseguire query su un database, è consigliabile concedere all'applicazione solo l'accesso in sola lettura al database. Inoltre, non è consigliabile dipendere solo dall'esclusione delle funzionalità nella definizione della funzione come controllo di sicurezza.
  • Considerare l'impatto reale: tenere presente l'impatto reale delle chiamate di funzione che si prevede di eseguire, in particolare quelle che attivano azioni come l'esecuzione di codice, l'aggiornamento di database o l'invio di notifiche.
  • Implementare i passaggi di conferma utente: in particolare per le funzioni che eseguono azioni, è consigliabile includere un passaggio in cui l'utente conferma l'azione prima dell'esecuzione.

Per altre informazioni sui suggerimenti su come usare i modelli OpenAI di Azure in modo responsabile, vedere la Panoramica delle procedure di intelligenza artificiale responsabili per i modelli OpenAI di Azure.

Passaggi successivi