Share 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.

Chamada de função paralela

Há suporte para chamadas de função paralelas com:

Modelos com suporte

  • gpt-35-turbo (1106)
  • gpt-35-turbo (0125)
  • gpt-4 (1106-preview)
  • gpt-4 (0125-Preview)

Suporte a API

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

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, para um aplicativo de clima simples, talvez você queira recuperar o clima em vários locais ao mesmo tempo. Isso resultará em uma mensagem de conclusão de chat com três chamadas de função na matriz tool_calls, cada uma com um idexclusivo. Se você quiser 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.

Abaixo, fornecemos uma versão modificada do exemplo de get_current_weather do OpenAI. Este exemplo, assim como o original do OpenAI, serve para fornecer a estrutura básica, mas não é um exemplo autônomo totalmente funcional. A tentativa de executar esse código sem modificações adicionais resultaria em um erro.

Neste exemplo, uma única função get_current_weather é definida. O modelo chama a função várias vezes e, depois de enviar a resposta da função de volta para o modelo, ele decide a próxima etapa. Ele responde com uma mensagem voltada para o usuário, informando a temperatura em São Francisco, Tóquio e Paris. Dependendo da consulta, ele pode optar por chamar uma função novamente.

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.

import os
from openai import AzureOpenAI
import json

client = AzureOpenAI(
  azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT"), 
  api_key=os.getenv("AZURE_OPENAI_API_KEY"),  
  api_version="2024-03-01-preview"
)


# Example function hard coded to return the same weather
# In production, this could be your backend API or an external API
def get_current_weather(location, unit="fahrenheit"):
    """Get the current weather in a given location"""
    if "tokyo" in location.lower():
        return json.dumps({"location": "Tokyo", "temperature": "10", "unit": unit})
    elif "san francisco" in location.lower():
        return json.dumps({"location": "San Francisco", "temperature": "72", "unit": unit})
    elif "paris" in location.lower():
        return json.dumps({"location": "Paris", "temperature": "22", "unit": unit})
    else:
        return json.dumps({"location": location, "temperature": "unknown"})

def run_conversation():
    # Step 1: send the conversation and available functions to the model
    messages = [{"role": "user", "content": "What's the weather like in San Francisco, Tokyo, and Paris?"}]
    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 and state, e.g. San Francisco, CA",
                        },
                        "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                    },
                    "required": ["location"],
                },
            },
        }
    ]
    response = client.chat.completions.create(
        model="<REPLACE_WITH_YOUR_MODEL_DEPLOYMENT_NAME>",
        messages=messages,
        tools=tools,
        tool_choice="auto",  # auto is default, but we'll be explicit
    )
    response_message = response.choices[0].message
    tool_calls = response_message.tool_calls
    # Step 2: check if the model wanted to call a function
    if tool_calls:
        # Step 3: call the function
        # Note: the JSON response may not always be valid; be sure to handle errors
        available_functions = {
            "get_current_weather": get_current_weather,
        }  # only one function in this example, but you can have multiple
        messages.append(response_message)  # extend conversation with assistant's reply
        # Step 4: send the info for each function call and function response to the model
        for tool_call in tool_calls:
            function_name = tool_call.function.name
            function_to_call = available_functions[function_name]
            function_args = json.loads(tool_call.function.arguments)
            function_response = function_to_call(
                location=function_args.get("location"),
                unit=function_args.get("unit"),
            )
            messages.append(
                {
                    "tool_call_id": tool_call.id,
                    "role": "tool",
                    "name": function_name,
                    "content": function_response,
                }
            )  # extend conversation with function response
        second_response = client.chat.completions.create(
            model="<REPLACE_WITH_YOUR_1106_MODEL_DEPLOYMENT_NAME>",
            messages=messages,
        )  # get a new response from the model where it can see the function response
        return second_response
print(run_conversation())

Usar a função na API de conclusões de chat (preterido)

A chamada de função está disponível na versão da API 2023-07-01-preview e funciona com a versão 0613 de gpt-35-turbo, gpt-35-turbo-16k, gpt-4 e gpt-4-32k.

Para usar a chamada de função com a API de Conclusões de Chat, você precisa incluir duas novas propriedades em sua solicitação: functions e function_call. Você pode incluir uma ou mais functions em sua solicitação e pode saber mais sobre como definir funções na seção definindo funções. Tenha em mente que as funções são injetadas na mensagem do sistema nos bastidores para que as funções sejam contabilizadas em relação ao uso do token.

Quando as funções são fornecidas, por padrão, o function_call é definido como "auto" e o modelo decide se uma função deve ou não ser chamada. Como alternativa, defina o parâmetro function_call como {"name": "<insert-function-name>"} para forçar a API a chamar uma função específica ou defina o parâmetro como "none" para impedir que o modelo chame qualquer função.

Observação

A versão da biblioteca OpenAI Python 0.28.1 foi preterida. Recomendamos usar 1.x. Consulte nosso guia de migração para obter informações sobre como mudar de 0.28.1 para 1.x.


import os
import openai

openai.api_key = os.getenv("AZURE_OPENAI_API_KEY")
openai.api_version = "2023-07-01-preview"
openai.api_type = "azure"
openai.api_base = os.getenv("AZURE_OPENAI_ENDPOINT")

messages= [
    {"role": "user", "content": "Find beachfront hotels in San Diego for less than $300 a month with free breakfast."}
]

functions= [  
    {
        "name": "search_hotels",
        "description": "Retrieves hotels from the search index based on the parameters provided",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The location of the hotel (i.e. Seattle, WA)"
                },
                "max_price": {
                    "type": "number",
                    "description": "The maximum price for the hotel"
                },
                "features": {
                    "type": "string",
                    "description": "A comma separated list of features (i.e. beachfront, free wifi, etc.)"
                }
            },
            "required": ["location"]
        }
    }
]  

response = openai.ChatCompletion.create(
    engine="gpt-35-turbo-0613", # engine = "deployment_name"
    messages=messages,
    functions=functions,
    function_call="auto", 
)

print(response['choices'][0]['message'])
{
  "role": "assistant",
  "function_call": {
    "name": "search_hotels",
    "arguments": "{\n  \"location\": \"San Diego\",\n  \"max_price\": 300,\n  \"features\": \"beachfront,free breakfast\"\n}"
  }
}

A resposta da API incluirá uma propriedade function_call se o modelo determinar que uma função deve ser chamada. A propriedade function_call inclui o nome da função a ser chamada e os argumentos a serem passados para a função. Os argumentos são uma cadeia de caracteres JSON que você pode analisar e usar para chamar sua função.

Em alguns casos, o modelo gera tanto um content quanto um function_call. Por exemplo, para o prompt acima, o conteúdo poderia dizer algo como "Claro, posso ajudá-lo a encontrar hotéis em San Diego que correspondam aos seus critérios" juntamente com o function_call.

Trabalhar com a chamada de função

A seção a seguir apresenta mais detalhes sobre como usar funções efetivamente com a API de Conclusões de Chat.

Definindo funções

Uma função tem três parâmetros principais: name, descriptione parameters. O parâmetro description é usado pelo modelo para determinar quando e como chamar a função, portanto, é importante fornecer uma descrição significativa do que a função faz.

parameters é um objeto de esquema JSON que descreve os parâmetros que a função aceita. Você pode saber mais sobre objetos de esquema JSON na referência de esquema JSON.

Se você quiser descrever uma função que não aceita parâmetros, use {"type": "object", "properties": {}} como o valor da propriedade parameters.

Gerenciando o fluxo com funções

Exemplo em Python.


response = openai.ChatCompletion.create(
    deployment_id="gpt-35-turbo-0613",
    messages=messages,
    functions=functions,
    function_call="auto", 
)
response_message = response["choices"][0]["message"]

# Check if the model wants to call a function
if response_message.get("function_call"):

    # Call the function. The JSON response may not always be valid so make sure to handle errors
    function_name = response_message["function_call"]["name"]

    available_functions = {
            "search_hotels": search_hotels,
    }
    function_to_call = available_functions[function_name] 

    function_args = json.loads(response_message["function_call"]["arguments"])
    function_response = function_to_call(**function_args)

    # Add the assistant response and function response to the messages
    messages.append( # adding assistant response to messages
        {
            "role": response_message["role"],
            "function_call": {
                "name": function_name,
                "arguments": response_message["function_call"]["arguments"],
            },
            "content": None
        }
    )
    messages.append( # adding function response to messages
        {
            "role": "function",
            "name": function_name,
            "content": function_response,
        }
    ) 

    # Call the API again to get the final response from the model
    second_response = openai.ChatCompletion.create(
            messages=messages,
            deployment_id="gpt-35-turbo-0613"
            # optionally, you could provide functions in the second call as well
        )
    print(second_response["choices"][0]["message"])
else:
    print(response["choices"][0]["message"])

Exemplo no PowerShell.

# continues from the previous PowerShell example

$response = Invoke-RestMethod -Uri $url -Headers $headers -Body $body -Method Post -ContentType 'application/json'
$response.choices[0].message | ConvertTo-Json

# Check if the model wants to call a function
if ($null -ne $response.choices[0].message.function_call) {

    $functionName  = $response.choices[0].message.function_call.name
    $functionArgs = $response.choices[0].message.function_call.arguments

    # Add the assistant response and function response to the messages
    $messages += @{
        role          = $response.choices[0].message.role
        function_call = @{
            name      = $functionName
            arguments = $functionArgs
        }
        content       = 'None'
    }
    $messages += @{
        role          = 'function'
        name          = $response.choices[0].message.function_call.name
        content       = "$functionName($functionArgs)"
    }

    # Call the API again to get the final response from the model
    
    # these API arguments are introduced in model version 0613
    $body = [ordered]@{
        messages      = $messages
        functions         = $functions
        function_call   = 'auto'
    } | ConvertTo-Json -depth 6

    $url = "$($openai.api_base)/openai/deployments/$($openai.name)/chat/completions?api-version=$($openai.api_version)"

    $secondResponse = Invoke-RestMethod -Uri $url -Headers $headers -Body $body -Method Post -ContentType 'application/json'
    $secondResponse.choices[0].message | ConvertTo-Json
}

Saída de exemplo.

{
  "role": "assistant",
  "content": "I'm sorry, but I couldn't find any beachfront hotels in San Diego for less than $300 a month with free breakfast."
}

Nos exemplos, não fazemos nenhuma validação ou tratamento de erro, portanto, é importante garantir que você adicione isso ao seu código.

Para obter um exemplo completo de como trabalhar com funções, confira o notebook de exemplo na chamada de função. Você também pode aplicar uma lógica mais complexa para encadear várias chamadas de função, o que também é abordado no exemplo.

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