Como usar OpenAI Function Calling para usar ferramentas em agentes

Entenda como se servir desse recurso poderoso, que mudou o trabalho com modelos de linguagem e permitiu a obtenção de resultados mais regulares

O OpenAI Function Calling é um recurso poderoso que revolucionou a forma como desenvolvedores trabalham com modelos de linguagem. Neste tutorial completo, vamos explorar como essa funcionalidade permite que os modelos GPT-3.5 e GPT-4 gerem outputs estruturados em JSON, resolvendo problemas comuns causados por resultados irregulares.

O que é OpenAI Function Calling?

A API da OpenAI é excelente para gerar respostas de forma sistemática. Você pode gerenciar seus prompts, otimizar o output do modelo e construir aplicações de linguagem com poucas linhas de código.

Apesar de todas essas vantagens, a API da OpenAI era um pesadelo para desenvolvedores e engenheiros. Por quê? Eles estão acostumados a trabalhar com tipos de dados estruturados, e lidar com dados não estruturados como strings é desafiador.

Para obter resultados consistentes, os desenvolvedores precisavam usar expressões regulares (RegEx) ou técnicas de prompt engineering para extrair informações de strings de texto.

É aqui que entra a funcionalidade de function calling da OpenAI. Ela permite que os modelos GPT-3.5 e GPT-4 recebam funções definidas pelo usuário como entrada e gerem outputs estruturados. Com isso, você não precisa mais escrever RegEx ou realizar prompt engineering complexo.

Usando OpenAI sem Function Calling

Nesta seção, vamos gerar respostas usando o modelo GPT-3.5-Turbo sem function calling para verificar se obtemos outputs consistentes ou não.

Antes de instalar a API Python da OpenAI, você precisa obter uma chave de API e configurá-la em seu sistema local.

Nota: A OpenAI não oferece mais créditos gratuitos para novos usuários, então você precisa comprar créditos para usar a API.

Atualize a API Python da OpenAI para V1 usando:

pip install --upgrade openai -q

Depois disso, inicie o cliente OpenAI usando a chave da API:

import os
from openai import OpenAI

client = OpenAI(
  api_key=os.environ['OPENAI_API_KEY'],
)

Vamos criar uma descrição aleatória de um estudante:

student_1_description = "David Nguyen is a sophomore majoring in computer science at Stanford University. He is Asian American and has a 3.8 GPA. David is known for his programming skills and is an active member of the university's Robotics Club. He hopes to pursue a career in artificial intelligence after graduating."

Na próxima parte, vamos escrever um prompt para extrair informações do estudante do texto e retornar o output como um objeto JSON. Vamos extrair o nome, curso principal, escola, notas e clubes da descrição do estudante.

# Um prompt simples para extrair informações de "student_description" em formato JSON
prompt1 = f'''
Please extract the following information from the given text and return it as a JSON object:

name
major
school
grades
club

This is the body of text to extract the information from:
{student_1_description}
'''

Adicione o prompt ao módulo de chat completion da API OpenAI para gerar a resposta:

# Gerando resposta do gpt-3.5-turbo
openai_response = client.chat.completions.create(
    model = 'gpt-3.5-turbo',
    messages = [{'role': 'user', 'content': prompt_1}]
)

openai_response.choices[0].message.content

A resposta é bastante boa. Vamos convertê-la em JSON para entendê-la melhor:

'{\n  "name": "David Nguyen",\n  "major": "computer science",\n  "school": "Stanford University",\n  "grades": "3.8 GPA",\n  "club": "Robotics Club"\n}'

Vamos usar a biblioteca json para converter o texto em um objeto JSON:

import json

# Carregando a resposta como um objeto JSON
json_response = json.loads(openai_response.choices[0].message.content)
json_response

O resultado final é praticamente perfeito. Então, por que precisamos de Function Calling?

{'name': 'David Nguyen',
 'major': 'computer science',
 'school': 'Stanford University',
 'grades': '3.8 GPA',
 'club': 'Robotics Club'}

O problema da inconsistência

Vamos tentar o mesmo prompt, mas usando uma descrição diferente de estudante:

student_2_description="Ravi Patel is a sophomore majoring in computer science at the University of Michigan. He is South Asian Indian American and has a 3.7 GPA. Ravi is an active member of the university's Chess Club and the South Asian Student Association. He hopes to pursue a career in software engineering after graduating."

Vamos apenas alterar o texto da descrição do estudante no prompt e executar a função de chat completion:

# Gerando resposta do gpt-3.5-turbo
openai_response = client.chat.completions.create(
    model = 'gpt-3.5-turbo',
    messages = [{'role': 'user', 'content': prompt_2}]
)

# Carregando a resposta como um objeto JSON
json_response = json.loads(openai_response.choices[0].message.content)
json_response

Como você pode ver, não é consistente. Em vez de retornar um clube, retornou uma lista de clubes dos quais Ravi participa. É diferente do primeiro estudante.

{'name': 'Ravi Patel',
 'major': 'computer science',
 'school': 'University of Michigan',
 'grades': '3.7 GPA',
 'club': ['Chess Club', 'South Asian Student Association']}

Exemplo de OpenAI Function Calling

Para resolver esse problema, vamos agora usar o recurso recentemente introduzido chamado Function Calling. É essencial criar uma função customizada para adicionar informações necessárias a uma lista de dicionários para que a API da OpenAI possa entender sua funcionalidade.

  • name: escreva o nome da função Python que você criou recentemente

  • description: a funcionalidade da função

  • parameters: dentro de "properties", vamos escrever o nome dos argumentos, tipo e descrição. Isso ajudará a API OpenAI a identificar as informações que estamos procurando

Nota: Certifique-se de seguir o padrão correto.

student_custom_functions = [
    {
        'name': 'extract_student_info',
        'description': 'Get the student information from the body of the input text',
        'parameters': {
            'type': 'object',
            'properties': {
                'name': {
                    'type': 'string',
                    'description': 'Name of the person'
                },
                'major': {
                    'type': 'string',
                    'description': 'Major subject.'
                },
                'school': {
                    'type': 'string',
                    'description': 'The university name.'
                },
                'grades': {
                    'type': 'integer',
                    'description': 'GPA of the student.'
                },
                'club': {
                    'type': 'string',
                    'description': 'School club for extracurricular activities. '
                }
                
            }
        }
    }
]

Em seguida, vamos gerar respostas para duas descrições de estudantes usando uma função customizada adicionada ao argumento "functions". Depois disso, vamos converter a resposta de texto em um objeto JSON e imprimi-lo.

student_description = [student_1_description,student_2_description]
for i in student_description:
    response = client.chat.completions.create(
        model = 'gpt-3.5-turbo',
        messages = [{'role': 'user', 'content': i}],
        functions = student_custom_functions,
        function_call = 'auto'
    )

    # Carregando a resposta como um objeto JSON
    json_response = json.loads(response.choices[0].message.function_call.arguments)
    print(json_response)

Como podemos ver, obtivemos output uniforme. Conseguimos até notas em formato numérico em vez de string. Output consistente é essencial para criar aplicações de AI sem bugs.

{'name': 'David Nguyen', 'major': 'computer science', 'school': 'Stanford University', 'grades': 3.8, 'club': 'Robotics Club'} 

{'name': 'Ravi Patel', 'major': 'computer science', 'school': 'University of Michigan', 'grades': 3.7, 'club': 'Chess Club'}

Múltiplas funções customizadas

Você pode adicionar múltiplas funções customizadas à função de chat completion. Nesta seção, veremos as capacidades mágicas da API OpenAI e como ela seleciona automaticamente a função correta e retorna os argumentos certos.

Na lista Python de dicionários, vamos adicionar outra função chamada "extract_school_info", que nos ajudará a extrair informações da universidade do texto.

Para conseguir isso, você precisa adicionar outro dicionário de função com name, description e parameters.

custom_functions = [
    {
        'name': 'extract_student_info',
        'description': 'Get the student information from the body of the input text',
        'parameters': {
            'type': 'object',
            'properties': {
                'name': {
                    'type': 'string',
                    'description': 'Name of the person'
                },
                'major': {
                    'type': 'string',
                    'description': 'Major subject.'
                },
                'school': {
                    'type': 'string',
                    'description': 'The university name.'
                },
                'grades': {
                    'type': 'integer',
                    'description': 'GPA of the student.'
                },
                'club': {
                    'type': 'string',
                    'description': 'School club for extracurricular activities. '
                }
                
            }
        }
    },
    {
        'name': 'extract_school_info',
        'description': 'Get the school information from the body of the input text',
        'parameters': {
            'type': 'object',
            'properties': {
                'name': {
                    'type': 'string',
                    'description': 'Name of the school.'
                },
                'ranking': {
                    'type': 'integer',
                    'description': 'QS world ranking of the school.'
                },
                'country': {
                    'type': 'string',
                    'description': 'Country of the school.'
                },
                'no_of_students': {
                    'type': 'integer',
                    'description': 'Number of students enrolled in the school.'
                }
            }
        }
    }
]

Vamos criar uma descrição da "Stanford University" para testar nossa função:

school_1_description = "Stanford University is a private research university located in Stanford, California, United States. It was founded in 1885 by Leland Stanford and his wife, Jane Stanford, in memory of their only child, Leland Stanford Jr. The university is ranked #5 in the world by QS World University Rankings. It has over 17,000 students, including about 7,600 undergraduates and 9,500 graduates23. "

Crie a lista de descrições de estudante e escola e passe-a pela função de chat completion da OpenAI para gerar a resposta. Certifique-se de ter fornecido a função customizada atualizada.

description = [student_1_description, school_1_description]
for i in description:
    response = client.chat.completions.create(
        model = 'gpt-3.5-turbo',
        messages = [{'role': 'user', 'content': i}],
        functions = custom_functions,
        function_call = 'auto'
    )

    # Carregando a resposta como um objeto JSON
    json_response = json.loads(response.choices[0].message.function_call.arguments)
    print(json_response)

O modelo GPT-3.5-Turbo selecionou automaticamente a função correta para diferentes tipos de descrição. Obtemos output JSON perfeito para o estudante e a escola.

{'name': 'David Nguyen', 'major': 'computer science', 'school': 'Stanford University', 'grades': 3.8, 'club': 'Robotics Club'} 

{'name': 'Stanford University', 'ranking': 5, 'country': 'United States', 'no_of_students': 17000}

Podemos até verificar que a resposta foi gerada usando a função "extract_school_info".

Aplicações do Function Calling

Nesta seção, vamos construir um sumarizador de texto estável que resumirá as informações da escola e do estudante de uma maneira específica.

Primeiro, vamos criar duas funções Python, extract_student_info e extract_school_info, que recebem os argumentos do function calling e retornam uma string resumida.

def extract_student_info(name, major, school, grades, club):
    
    """Get the student information"""

    return f"{name} is majoring in {major} at {school}. He has {grades} GPA and he is an active member of the university's {club}."

def extract_school_info(name, ranking, country, no_of_students):
    
    """Get the school information"""

    return f"{name} is located in the {country}. The university is ranked #{ranking} in the world with {no_of_students} students."

Agora vamos implementar a lógica completa:

  1. Criar a lista Python, que consiste na descrição do estudante um, um prompt aleatório e a descrição da escola um. O prompt aleatório é adicionado para validar a mecânica automática de chamada de função.

  2. Vamos gerar a resposta usando cada texto na lista descriptions.

  3. Se uma chamada de função for usada, obteremos o nome da função e, com base nisso, aplicaremos os argumentos relevantes à função usando a resposta. Caso contrário, retornaremos a resposta normal.

  4. Imprimiremos os outputs de todas as três amostras.

descriptions = [
    student_1_description, 
    "Who was a Abraham Lincoln?",
    school_1_description
                ]

for i, sample in enumerate(descriptions):
    response = client.chat.completions.create(
        model = 'gpt-3.5-turbo',
        messages = [{'role': 'user', 'content': sample}],
        functions = custom_functions,
        function_call = 'auto'
    )
    
    response_message = response.choices[0].message
    
    if dict(response_message).get('function_call'):
        
        # Qual chamada de função foi invocada
        function_called = response_message.function_call.name
        
        # Extraindo os argumentos
        function_args  = json.loads(response_message.function_call.arguments)
        
        # Nomes das funções
        available_functions = {
            "extract_school_info": extract_school_info,
            "extract_student_info": extract_student_info
        }
        
        fuction_to_call = available_functions[function_called]
        response_message = fuction_to_call(*list(function_args .values()))
        
    else:
        response_message = response_message.content
    
    print(f"\nSample#{i+1}\n")
    print(response_message)

Resultados:

  • Sample#1: O modelo GPT selecionou "extract_student_info" e obtivemos um resumo curto sobre o estudante.

  • Sample#2: O modelo GPT não selecionou nenhuma função e tratou o prompt como uma pergunta regular, e como resultado, obtivemos a biografia de Abraham Lincoln.

  • Sample#3: O modelo GPT selecionou "extract_school_info" e obtivemos um resumo curto sobre Stanford University.

Sample#1

David Nguyen is majoring in computer science at Stanford University. He has 3.8 GPA and he is an active member of the university's Robotics Club.

Sample#2

Abraham Lincoln was the 16th President of the United States. He served as president from March 1861 until his assassination in April 1865. Lincoln led the country through its greatest internal crisis, the American Civil War, and his Emancipation Proclamation declared slaves in Confederate territory to be free. He is known for his leadership, his commitment to preserving the Union, and his efforts to abolish slavery. Lincoln's presidency is widely regarded as one of the most transformative in American history.

Sample#3

Stanford University is located in the United States. The university is ranked #5 in the world with 17000 students.

Casos de uso práticos

O OpenAI Function Calling abre diversas possibilidades para desenvolvedores. Aqui estão alguns casos de uso práticos:

1. Integração com APIs externas

Você pode criar funções que chamam APIs externas, como serviços de clima, conversão de moedas ou busca de informações em tempo real. O modelo GPT decide quando chamar essas APIs com base no contexto da conversação.

2. Acesso a bancos de dados

Implemente funções que consultam bancos de dados para recuperar informações específicas. O modelo pode gerar consultas SQL estruturadas ou chamar procedimentos armazenados com base nas perguntas dos usuários.

3. Automação de tarefas

Crie assistentes que podem executar tarefas específicas, como agendar reuniões, enviar emails ou criar documentos, tudo através de interfaces de linguagem natural.

4. Validação de dados

Use function calling para garantir que os dados extraídos de textos sigam um formato específico e atendam a critérios de validação predefinidos.

Boas práticas

Ao implementar OpenAI Function Calling, considere estas boas práticas:

Defina descrições claras

As descrições de suas funções e parâmetros devem ser claras e específicas. O modelo usa essas descrições para decidir quando e como chamar as funções.

Valide os argumentos

Sempre valide os argumentos retornados pelo model antes de executar suas funções. Isso previne erros e garante a segurança da aplicação.

Trate exceções

Implemente tratamento de erros robusto para casos em que a função não pode ser executada ou retorna resultados inesperados.

Teste extensivamente

Teste suas funções com diversos cenários e tipos de entrada para garantir comportamento consistente.

FAQ

Como o OpenAI function calling lida com outputs JSON aninhados complexos?

O OpenAI function calling permite que você defina estruturas JSON aninhadas no schema da função. Ao especificar as relações hierárquicas dentro da propriedade parameters, você pode garantir que o modelo gere outputs JSON adequadamente aninhados e estruturados para requisitos de dados complexos.

O OpenAI function calling pode ser usado com APIs externas ou bancos de dados?

Sim, o OpenAI function calling pode ser integrado com APIs externas ou bancos de dados criando funções customizadas que executam chamadas de API ou consultas de banco de dados com base nos argumentos passados pelo modelo. Isso permite interações dinâmicas com sistemas externos mantendo respostas consistentes e estruturadas.

O que acontece se a chamada de função do modelo não corresponder a nenhuma função definida?

Se a chamada de função do modelo não corresponder a uma função definida ou ao schema fornecido, a chamada de função não é acionada, e o modelo trata a entrada como um prompt baseado em texto padrão, retornando uma resposta típica baseada em texto. Isso garante flexibilidade no tratamento de tipos de entrada variados.

Conclusão

Neste tutorial, aprendemos sobre o function calling da OpenAI. Também aprendemos como usá-lo para gerar outputs consistentes, criar múltiplas funções e construir um sumarizador de texto confiável.

O OpenAI Function Calling representa um avanço significativo na forma como podemos interagir com modelos de linguagem, permitindo que desenvolvedores criem aplicações mais robustas e confiáveis. Ao fornecer outputs estruturados e consistentes, essa funcionalidade elimina muitos dos desafios que desenvolvedores enfrentavam ao trabalhar com respostas de texto livre.

Se você deseja aprender mais sobre a API OpenAI e construir aplicações de IA mais sofisticadas, experimente implementar suas próprias funções customizadas e explore as possibilidades que o function calling oferece. A chave para o sucesso está em definir schemas claros, testar extensivamente e sempre validar os outputs antes de usá-los em produção.