Compartilhar via


Como usar a chamada de função com o Serviço OpenAI do Azure (Versão prévia)

As versões mais recentes do gpt-35-turbo e gpt-4 são ajustadas para funcionar 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 determina se alguma das funções deve ser chamada com base no contexto do prompt. Quando o modelo determinar que uma função deve ser chamada, ele responde com um objeto JSON, incluindo os argumentos para a função.

Os modelos formulam chamadas à API e saídas de dados de estrutura, tudo com base nas funções especificadas. É importante observar 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. Chamar a API de conclusões do chat com suas funções e a entrada do usuário
  2. Usar a resposta do modelo para chamar sua API ou função
  3. Chame a API de conclusões de chat novamente, incluindo a resposta de sua função para obter uma resposta final

Importante

Os parâmetros functions e function_call foram preteridos com a versão 2023-12-01-preview da API. A substituição para functions é o parâmetro tools. A substituição para function_call é o parâmetro tool_choice.

Suporte à chamada de função

Chamada de função paralela

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

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

Chamada de função básica com ferramentas

  • Todos os modelos que dão suporte à 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 toy simples que pode verificar a hora em três locais codificados com uma única ferramenta/função definida. Adicionamos instruções impressas para ajudar a tornar a execução de 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 dê suporte a chamadas de função paralela, poderemos convertê-la 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 fazer essa troca, alterne 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 gerará os seguintes resultados:

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 paralela permitem que você execute várias chamadas de função em conjunto, permitindo a execução paralela e a recuperação de resultados. Isso reduz o número de chamadas à API que precisam ser feitas e podem melhorar o desempenho geral.

Por exemplo, em nosso aplicativo de duração simples, recuperamos várias vezes ao mesmo tempo. Isto resultou em uma mensagem de conclusão de chat com três chamadas de função na matriz tool_calls, cada uma com um id exclusivo. Caso queira responder a essas chamadas de função, adicione três novas mensagens à conversa, cada uma contendo o resultado de uma chamada de função, com um tool_call_id referenciando o id de tools_calls.

Para forçar o modelo a chamar uma função específica, defina o parâmetro tool_choice 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".

Observação

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

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

Agora, demonstraremos outro exemplo de chamada de função toy 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, portanto, você precisará adicionar lógica extra ao código para poder revolver erros. Para alguns casos de uso, talvez você precisará usar o ajuste fino para melhorar o desempenho de chamada de função.

Engenharia de prompt 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 uma 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.

Melhorando a qualidade e a confiabilidade

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

Fornecer mais detalhes em sua definição de função

É importante que você forneça uma description significativo 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 parâmetro location, você pode incluir detalhes extras e exemplos no 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, poderá incluir uma mensagem do sistema como a seguinte para instruir o modelo a chamar a função quando um usuário solicitar 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 assumir valores a serem usados com funções. Por exemplo, com search_hotels você gostaria que o modelo solicitasse 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 exemplo seguinte 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."}

Reduzindo erros

Outra área em que a engenharia de prompt pode ser valiosa é reduzir erros em chamadas de função. Os modelos são treinados para gerar chamadas de função correspondentes ao esquema que você definiu, 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ê descobrir 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 com responsabilidade

Como qualquer sistema de IA, o uso da chamada de função para integrar modelos de linguagem a 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 com responsabilidade.

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

  • Validar as 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 se alinhe 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 gravar chamadas de função de uma maneira diferente da pretendida.
  • Seguir o Princípio do Privilégio Mínimo: 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 mal utilizada ou explorada. Por exemplo, se você estiver usando chamadas de função para consultar um banco de dados, só deverá conceder ao 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.
  • Considerar o Impacto Real: esteja ciente do impacto real das chamadas de função que você planeja executar, especialmente aquelas que disparam ações como executar código, atualizar bancos de dados ou enviar notificações.
  • Implementar Etapas de Confirmação do Usuário: especialmente 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 OpenAI do Azure com responsabilidade, confira a Visão geral das práticas de IA responsável para modelos OpenAI do Azure.

Próximas etapas