Como usar a chamada de função com o Serviço OpenAI do Azure (Visualização)

As versões mais recentes do gpt-35-turbo e gpt-4 são ajustadas para trabalhar com funções e são capazes de determinar quando e como uma função deve ser chamada. Se uma ou mais funções forem incluídas em sua solicitação, o modelo determinará se alguma das funções deve ser chamada com base no contexto do prompt. Quando o modelo determina que uma função deve ser chamada, ele responde com um objeto JSON, incluindo os argumentos para a função.

Os modelos formulam chamadas de API e estruturam saídas de dados, tudo com base nas funções especificadas. É importante notar que, embora os modelos possam gerar essas chamadas, cabe a você executá-las, garantindo que você permaneça no controle.

Em um nível alto, você pode dividir o trabalho com funções em três etapas:

  1. Chame a API de conclusão de bate-papo com suas funções e a entrada do usuário
  2. Use a resposta do modelo para chamar sua API ou função
  3. Chame a API de conclusão de chat novamente, incluindo a resposta da sua função para obter uma resposta final

Importante

Os functions parâmetros e function_call foram preteridos com o 2023-12-01-preview lançamento da versão da API. O substituto para functions é o tools parâmetro. O substituto para function_call é o tool_choice parâmetro.

Suporte de chamada de função

Chamada de função paralela

  • gpt-35-turbo (1106)
  • gpt-35-turbo (0125)
  • gpt-4 (1106-Pré-visualização)
  • gpt-4 (0125-Pré-visualização)
  • gpt-4 (visão-pré-visualização)
  • gpt-4 (2024-04-09)
  • gpt-4o (2024-05-13)
  • gpt-4o-mini (2024-07-18)

O suporte para a função paralela foi adicionado pela primeira vez na versão da API 2023-12-01-preview

Chamada de função básica com ferramentas

  • Todos os modelos que suportam chamada de função paralela
  • gpt-4 (0613)
  • gpt-4-32k (0613)
  • gpt-35-turbo-16k (0613)
  • gpt-35-turbo (0613)

Exemplo de chamada de ferramenta/função única

Primeiro, demonstraremos uma chamada de função de brinquedo simples que pode verificar o tempo em três locais codificados com uma única ferramenta/função definida. Adicionamos instruções de impressão para ajudar a tornar a execução do código mais fácil de seguir:

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())

Saída:

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 estivermos usando uma implantação de modelo que ofereça suporte a chamadas de função paralela, podemos converter isso em um exemplo de chamada de função paralela alterando a matriz de mensagens para solicitar o tempo em vários locais em vez de um.

Para realizar essa troca, os comentários nestas duas linhas:

    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

Para ter esta aparência e executar o código novamente:

    #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

Isso gera a seguinte saída:

Saída:

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

As chamadas de função paralelas permitem que você execute várias chamadas de função juntas, permitindo a execução paralela e a recuperação de resultados. Isso reduz o número de chamadas para a API que precisam ser feitas e pode melhorar o desempenho geral.

Por exemplo, em nosso aplicativo de tempo simples, recuperamos várias vezes ao mesmo tempo. Isso resultou em uma mensagem de conclusão de bate-papo com três chamadas de tool_calls função na matriz, cada uma com um arquivo id. Se você quisesse responder a essas chamadas de função, adicionaria três novas mensagens à conversa, cada uma contendo o resultado de uma chamada de função, com uma tool_call_id referência a id de tools_calls.

Para forçar o modelo a chamar uma função específica, defina o tool_choice parâmetro com um nome de função específico. Você também pode forçar o modelo a gerar uma mensagem voltada para o usuário definindo tool_choice: "none".

Nota

O comportamento padrão (tool_choice: "auto") é que o modelo decida por conta própria se deseja chamar uma função e, em caso afirmativo, qual função chamar.

Chamada de função paralela com várias funções

Agora vamos demonstrar outro exemplo de chamada de função de brinquedo, desta vez com duas ferramentas/funções diferentes definidas.

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())

Saída

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

A resposta JSON pode nem sempre ser válida, então você precisa adicionar lógica adicional ao seu código para poder lidar com erros. Para alguns casos de uso, você pode achar que precisa usar o ajuste fino para melhorar o desempenho da chamada de função.

Engenharia rápida com funções

Quando você define uma função como parte de sua solicitação, os detalhes são injetados na mensagem do sistema usando sintaxe específica na qual o modelo foi treinado. Isso significa que as funções consomem tokens em seu prompt e que você pode aplicar técnicas de engenharia de prompt para otimizar o desempenho de suas chamadas de função. O modelo usa o contexto completo do prompt para determinar se uma função deve ser chamada, incluindo a definição da função, a mensagem do sistema e as mensagens do usuário.

Melhorar a qualidade e a fiabilidade

Se o modelo não está chamando sua função quando ou como você espera, há algumas coisas que você pode tentar para melhorar a qualidade.

Forneça mais detalhes na definição da sua função

É importante que você forneça um significado description da função e forneça descrições para qualquer parâmetro que possa não ser óbvio para o modelo. Por exemplo, na descrição do location parâmetro, você pode incluir detalhes e exemplos adicionais sobre o formato do local.

"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)"
},
Fornecer mais contexto na mensagem do sistema

A mensagem do sistema também pode ser usada para fornecer mais contexto ao modelo. Por exemplo, se você tiver uma função chamada search_hotels , você pode incluir uma mensagem do sistema como a seguinte para instruir o modelo a chamar a função quando um usuário pedir ajuda para encontrar um 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."}
Instruir o modelo a fazer perguntas esclarecedoras

Em alguns casos, você deseja instruir o modelo a fazer perguntas esclarecedoras para evitar fazer suposições sobre quais valores usar com funções. Por exemplo, com search_hotels você gostaria que o modelo pedisse esclarecimentos se a solicitação do usuário não incluísse detalhes sobre location. Para instruir o modelo a fazer uma pergunta esclarecedora, você pode incluir conteúdo como o próximo exemplo na mensagem do sistema.

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

Redução de erros

Outra área em que a engenharia rápida pode ser valiosa é na redução de erros em chamadas de função. Os modelos são treinados para gerar chamadas de função correspondentes ao esquema que você definir, mas os modelos produzem uma chamada de função que não corresponde ao esquema definido ou tentam chamar uma função que você não incluiu.

Se você achar que o modelo está gerando chamadas de função que não foram fornecidas, tente incluir uma frase na mensagem do sistema que diz "Only use the functions you have been provided with.".

Usando a chamada de função de forma responsável

Como qualquer sistema de IA, usar chamadas de função para integrar modelos de linguagem com outras ferramentas e sistemas apresenta riscos potenciais. É importante entender os riscos que a chamada de função pode apresentar e tomar medidas para garantir que você use os recursos de forma responsável.

Aqui estão algumas dicas para ajudá-lo a usar as funções com segurança:

  • Validar chamadas de função: sempre verifique as chamadas de função geradas pelo modelo. Isso inclui verificar os parâmetros, a função que está sendo chamada e garantir que a chamada esteja alinhada com a ação pretendida.
  • Usar dados e ferramentas confiáveis: use apenas dados de fontes confiáveis e verificadas. Dados não confiáveis na saída de uma função podem ser usados para instruir o modelo a escrever chamadas de função de uma maneira diferente da pretendida.
  • Siga o Princípio do Menor Privilégio: Conceda apenas o acesso mínimo necessário para que a função execute seu trabalho. Isso reduz o impacto potencial se uma função for usada ou explorada indevidamente. Por exemplo, se você estiver usando chamadas de função para consultar um banco de dados, você só deve conceder ao seu aplicativo acesso somente leitura ao banco de dados. Você também não deve depender apenas da exclusão de recursos na definição de função como um controle de segurança.
  • Considere o impacto no mundo real: esteja ciente do impacto no mundo real das chamadas de função que você planeja executar, especialmente aquelas que acionam ações como executar código, atualizar bancos de dados ou enviar notificações.
  • Implementar etapas de confirmação do usuário: particularmente para funções que executam ações, recomendamos incluir uma etapa em que o usuário confirme a ação antes de ser executada.

Para saber mais sobre nossas recomendações sobre como usar modelos do Azure OpenAI de forma responsável, consulte Visão geral das práticas de IA responsável para modelos OpenAI do Azure.

Próximos passos