Alura > Cursos de Inteligência Artificial > Cursos de IA para Programação > Conteúdos de IA para Programação > Primeiras aulas do curso OpenAI: avalie e otimize o seu código com Code Interpreter

OpenAI: avalie e otimize o seu código com Code Interpreter

Alocação de tarefas em servidores com a OpenAI - Apresentação

Boas-vindas a mais este curso de OpenAI, ministrado pelo André Santana.

Audiodescrição: André é um homem branco de cabelo curto, barba média, todos na cor castanho-claro. Usa óculos redondo e camiseta verde. Ao fundo, estúdio da Alura, com uma estante cheia de objetos diversos e uma luz azul e roxa incidindo sobre a parede.

O que vamos aprender?

Neste curso, vamos aprender a criar uma série de assistentes que vão ajudar a analisar códigos sob perspectivas diferentes. Teremos alguns assistentes que trabalharão com a parte de análise de código diretamente de complexidade de tempo e de memória, alguns assistentes que avaliarão a adequação do código à PEP8 e outros assistentes que orquestrarão todo o processo de ferramentas que teremos acesso para poder melhorar nossa forma de codificar.

Além disso, vamos trabalhar com a OpenAI no modo JSON e também conseguiremos garantir que geremos relatórios em formato HTML com alguns gráficos que mostram o desempenho da nossa solução.

Para quem é este curso?

Então, este curso é para você que gosta de Python, já tem alguma experiência na parte de programação e, principalmente, quer utilizar a OpenAI para poder melhorar a produtividade na hora de codificar.

Pré-requisitos

Para aproveitar melhor esse conteúdo, recomendamos que você acesse o curso anterior. Vamos ver tudo isso em um projeto que associa a construção de agentes e assistentes da OpenAI com a resolução de um problema complexo da computação que é o problema de alocação de tarefas em um servidor.

Para aproveitar também melhor a experiência do curso, recomendamos que você acesse os vídeos das aulas, nossa comunidade no Discord, nosso Fórum e também resolva nossas atividades.

Então, nos vemos na nossa primeira aula!

Alocação de tarefas em servidores com a OpenAI - Apresentando o projeto base

Olá, pessoal! Estamos aqui mais uma vez para avançar na formação de OpenAI. Neste curso, vamos nos aprofundar um pouco mais sobre como podemos utilizar assistentes inteligentes com Code Interpreter, para que possamos ter um auxiliar de co-design de aplicações.

Para começar nossa jornada, temos uma aplicação que envolve uma tarefa computacional que consiste em imaginar que temos um time de DevOps. Esse time vai precisar executar uma série de tarefas em alguns servidores, como, por exemplo, executar e consultar uma base de dados.

As aplicações que temos na camada de DevOps podem ser bastante grandes e exaustivas, e por algum motivo pode ser necessário que tenhamos mais de um computador para executar esse tipo de tarefa.

Para otimizar nosso processo de criação de novos assistentes, e por consequência poder utilizar a IA generativa como uma ferramenta para auxiliar no processo de codificação, vamos usar essa aplicação como uma aplicação base. Se você acessar nosso projeto base, vai ver que tem alguns arquivos que já estão estruturados.

Conhecendo o projeto

O primeiro deles que vamos abrir é o arquivo de servidor "servidor.py". Esse arquivo tem uma implementação em Python que tem como objetivo simular um servidor que vai receber um conjunto de tarefas.

É uma representação análoga ao que seria um servidor, em que basicamente temos a capacidade de adicionar uma tarefa nova e verificar o tempo total de execução dessa tarefa com tempo_total_execucao(self).

Esse servidor vai trabalhar com algumas outras classes que implementamos. Podemos voltar para o nosso diretório de aplicações, abrir agora o script de tarefa "tarefa.py".

Esse script também tem uma classe orientada ao objeto, com o construtor __init__, que vai receber um ID de uma tarefa, uma descrição, um tempo esperado de execução para essa tarefa e uma criticidade, que é o quão importante ela é ou quão prioritária ela precisa ser executada dentro de um servidor. Logo abaixo, temos as atribuições dessas propriedades no construtor.

    def __init__(self, id, descricao, tempo_execucao, criticidade):
        :param id: Identificador único da tarefa.
        :param descricao: Descrição da tarefa.
        :param tempo_execucao: Tempo de execução necessário para a tarefa.
        :param criticidade: Nível de criticidade da tarefa (1 a 5, sendo 5 a mais crítica).
        '''
        self.id = id
        self.descricao = descricao
        self.tempo_execucao = tempo_execucao
        self.criticidade = criticidade

Um pouco mais para baixo temos a representação textual dessa tarefa para que possamos consultar. Estamos simulando o quanto que conseguimos alocar dessas tarefas em um servidor, e quantos servidores são necessários para que possamos executar essa tarefa.

    def __repr__(self):
        '''
        Representação textual da tarefa, usada para exibição e debug.
        '''
        return f"Tarefa(id={self.id}, descricao='{self.descricao}', tempo_execucao={self.tempo_execucao}, criticidade={self.criticidade})"

Se formos rodar essa aplicação, vamos perceber que temos ainda mais três scripts que são importantes. Um deles é o "alocador.py", ele vai ter o papel de observar quais são as tarefas que temos existentes, e por consequência decidir qual servidor que vamos usar para poder alocar.

Então, uma classe chamada AlocadorTarefas, temos um construtor mais abaixo, que recebe tarefas e um conjunto de servidores. O que ele faz é basicamente inicializar as tarefas e definir quais são os servidores que vão ser utilizados nessa simulação.

class AlocadorTarefas:
    '''
    Classe responsável por encontrar a melhor alocação de tarefas em servidores.
    '''

    def __init__(self, tarefas, servidores):
        '''
        Inicializa o alocador com uma lista de tarefas e uma lista de servidores.
        :param tarefas: Lista de objetos Tarefa.
        :param servidores: Lista de objetos Servidor.
        '''
        self.tarefas = tarefas
        self.servidores = servidores
        self._monitorando = False 

Mas o método principal dessa classe, que vamos avaliar e no qual vamos usar os assistentes da OpenAI para poder avaliar, é o método alocar_exaustivo().

Ele basicamente vai pegar todas as tarefas que temos disponíveis, que estão ali representadas pelo time de DevOps e vai tentar uma explosão combinatória por permutação, para tentar garantir que tenhamos a menor quantidade de servidores, executando e trabalhando na menor quantidade de tempo.

Não é relevante para o nosso processo de uso da OpenAI nesse momento entender detalhe por detalhe o que tem aqui dentro, mas basicamente o que ele faz é definir esses registros, e garantir que vamos ter a melhor combinação de tarefas espalhadas pelos servidores.

    def alocar_exaustivo(self, nome_arquivo = "padrao"):
        '''
        Método que realiza a alocação exaustiva de tarefas nos servidores.
        :return: Melhor alocação de tarefas e o tempo total mínimo de execução.
        '''
        start_time = time.time()
        self.memoria_registros = []
        self.memoria_maxima = 0
        self.iteracoes = 0
        self.registro_iteracoes = []
        print("Alocando de forma exaustiva...")
        self._monitorando = True

Voltando para o nosso projeto, temos os dois scripts que vamos usar para poder fazer a simulação. O primeiro deles é o "api_simulacao_tarefas.py".

Ele vai estruturar uma aplicação em Flask para nós. Nessa aplicação em Flask vamos ter três exemplos diferentes, um exemplo que é um exemplo mais simples, onde vamos ter basicamente três tarefas distintas, cada uma delas com seu ID, descrição, tempo esperado e criticidade,

Para efeito depois de testes vamos ver que temos mais alguns cenários que tornam esse processo bastante cansativo para o computador.

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/api/tarefas/exemplo1', methods=['GET'])
def exemplo1():
    """
    API endpoint para fornecer um exemplo de lista de tarefas com tempos de execução, descrições e criticidade.

    :return: JSON contendo uma lista de tarefas.
    """
    return jsonify({
        "tarefas": [
            {"id": 1, "descricao": "Compilar módulo A", "tempo_execucao": 10, "criticidade": 3},
            {"id": 2, "descricao": "Rodar testes unitários", "tempo_execucao": 20, "criticidade": 4},
            {"id": 3, "descricao": "Gerar relatório de cobertura", "tempo_execucao": 15, "criticidade": 2}
        ]
    })

E por último, temos um script de teste de alocação de tarefas, o "teste_alocacao_tarefas.py". O que esse script faz é basicamente definir qual é o tipo de conjunto de tarefas que vamos utilizar, nesse nosso exemplo vamos usar o "exemplo1", que é aquele conjunto que tem menos tarefas.

Vamos nos conectar com esse servidor, ou seja, especificamos que é uma URL local, mas poderia ser eventualmente qualquer servidor que você queira tirar ou extrair algum tipo de dado, e verificamos então se conseguimos estabelecer uma conexão com esse servidor.

def main():
    """
    Função principal para simular a alocação de tarefas em servidores.
    """
    exemplo_id = "exemplo1" 
    url = f"http://127.0.0.1:5000/api/tarefas/{exemplo_id}"
    response = requests.get(url)
    if response.status_code == 200:
        print("Conexão Estabelecida")
        tarefas_data = response.json()['tarefas']
        tarefas = criar_tarefas_de_api(tarefas_data)
        print(f"Tarefas Criadas: {tarefas}")

Se essa conexão for estabelecida, puxamos as tarefas com tarefas_data, que vão ser consumidas desse servidor no formato de um JSON e criamos as tarefas com craiar_tarefas_de_api(tarefas_data).

Na linha seguinte, ordenamos as tarefas, especificamos mais abaixo que vamos ter o número máximo de servidores necessários disponíveis (num_servidores). Então, vamos ter de 0 a 2 servidores dependendo do número de tarefas.

Depois, identifiquemos com o método alocar_exaustivo, qual que vai ser a melhor combinação de distribuição (melhor_alocacao).

Chamamos essa abordagem dentro da computação de abordagem de make-span, que é essa configuração de alocação ótima que temos quando vamos tentar definir, seja um processo, tarefas, distribuir para o nosso time ou dentro da computação mesmo.

Usando a abordagem make span

Para poder executar esse script e ver o que ele vai trazer de resposta, tem um processo de execução um pouco diferente, que ele consiste em executar primeiro o servidor.

Então, vamos abrir novamente o "api_simulacao_tarefas.py", podemos abrir ele pela aba que já está aberta ou acessar novamente pelo diretório de arquivos, e ao invés de simplesmente executar esse script em Python, vamos clicar no lado direito superior, em "Run or Debug", e pedir para ele executar esse script em Python em um terminal dedicado ("Run Python File in a Dedicated Terminal").

Vamos marcar essa opção, e o que ele vai fazer agora vai ser executar, ele vai dar um endereço de servidor, não precisamos abrir ele no navegador, porque vamos consumir diretamente pela nossa aplicação.

E agora vamos abrir o "teste_alocacao_tarefas.py", e vamos fazer o mesmo processo, pedir para ele executar agora em um terminal dedicado.

Atenção: É importante que primeiro executemos a API e depois executemos o script de simulação.

Quando executamos o script de simulação, o que ele vai fazer de forma bem rápida, é dizer que o melhor tempo de execução possível envolve 25 unidades de tempo.

Então, se pegarmos todas as tarefas que estão sendo distribuídas, esse vai ser o tempo máximo que vamos ocupar de servidor. Ele aloca a tarefa 1 e a tarefa 3 no servidor 1, a tarefa 2 no servidor 2, e traz essa solução ótima.

Esse problema é um problema computacional bastante difícil de resolver, e só para entender um pouco sobre essa complexidade, vamos fechar essas duas execuções, vamos voltar para a nossa aba de teste_alocacao_tarefas.py, e vamos mudar agora para o "exemplo3", que é um exemplo mais complexo, com 14 tarefas.

O primeiro teve 3 tarefas, o exemplo 3 tem 14 tarefas, não é uma quantidade exorbitante a mais, mas o computador já vai sentir bastante para poder executar essa tarefa.

Então voltamos para o "api_simulacao_tarefas.py" e executamos primeiro a nossa API. Feito isso, acessamos o nosso script de "teste_alocacao_tarefas.py", vamos executá-lo também no terminal dedicado.

E agora se observarmos, temos 14 tarefas. Ele está alocando de forma exaustiva, e podemos deixar o computador por muito tempo tentando resolver esse problema de definir qual é a quantidade de servidores que precisamos para poder alocar cada tarefa.

E não vamos conseguir observar isso num tempo em que não fiquemos entediados, porque ele vai demorar cerca de 50 a 60 minutos para poder fazer essa tarefa que é distribuir 14 tarefas em 2 ou 3 servidores diferentes.

Esse é um problema na computação que é bastante recorrente, vamos explorá-lo bastante ao longo deste curso, e vamos utilizar a OpenAI para poder ajudar a entender como melhorar esse tipo de código, como cocriar algum tipo de solução que faça sentido, e ao mesmo tempo ajudar a identificar métricas ou oportunidades de melhoria em outras partes do nosso código. Até o nosso próximo vídeo!

Alocação de tarefas em servidores com a OpenAI - Iniciando a implementação do assistente base

Agora, já temos o nosso projeto base que vamos utilizar para testar os assistentes que vamos criar. Esses assistentes vão nos ajudar a co-desenhar soluções envolvendo algoritmos para problemas mais complexos na área de computação.

Para isso, é necessário que você tenha instalado todos os procedimentos e bibliotecas que estão informados no nosso "Preparando o Ambiente". Caso tenha alguma dúvida, você pode acessar o arquivo "README.md", garantindo que vai seguir o processo de acordo com o seu sistema operacional.

Nesse caso específico, já executamos o processo de instalação. Assim, garantimos que todas as bibliotecas necessárias já estão dentro do nosso ambiente virtual associado ao terminal do Python. Vamos começar a implementação do nosso assistente.

Preparando a estrutura do assistente

O primeiro passo será criar, dentro da nossa infraestrutura, um arquivo que vai garantir que tenha as versões dos modelos que serão utilizados pelo Code Interpreter (Interpretador de Código), durante o processo de avaliação dos nossos códigos.

Para isso, vamos clicar em "New File" e vamos criar um script chamado "helper_models.py". Nesse script, vamos simplesmente trazer o nome dos modelos que serão utilizados ao longo do nosso curso.

Vamos dizer, através de uma constante, que vamos usar o modelo de GPT e esse modelo será a versão mais atual que temos, que será o GPT-4o. Se você não tiver disponível na sua conta o GPT-4o, pode usar o GPT-3.5 também, lembrando sempre que é um modelo que tem uma janela de contexto menor e, por consequência, ele vai ter uma qualidade menor também no processo de avaliação.

MODELO_GPT ="gpt-4o"

Agora que já temos especificado qual será o modelo, vamos precisar de uma chave de API. Já fizemos esse processo em outros cursos da nossa formação. Uma coisa que você precisa garantir é que tenha dentro do seu código um arquivo de variável de ambiente.

Vamos criar esse arquivo chamado dotenv, e dentro dele uma variável de ambiente que vamos chamar de OPENAI_API_KEY e vamos armazenar essa chave de acesso que criamos durante esse processo.

Feito isso, vamos para a próxima fase que envolve criar ou começar a criar o nosso assistente. Vamos criar um script chamado "assistente.py". Agora, vamos começar a criar um assistente mais generalista, mas ele vai conseguir nos ajudar a resolver quase todos os problemas de análise de código que vamos ter ao longo desse curso e sem uma implementação que seja mais exaustiva depois que conseguirmos ter esse assistente mais genérico.

Vamos começar agora importando a biblioteca da OpenAI. Da biblioteca da OpenAI vamos importar a classe OpenAI. Feito isso, agora vamos importar também o nosso modelo que vamos utilizar dentro do nosso assistente.

Vamos dizer que do nosso helper_models, vamos importar o MODELO_GPT. Se, em algum momento, precisarmos alterar esse tipo de modelo, basta ir no helper e alterar o nome desse modelo que estamos utilizando.

E também vamos ler do dotenv, que são as nossas variáveis de ambiente, o método load_dotenv. Feito isso, vamos precisar importar também o nosso sistema operacional para que ele possa nos fornecer acesso às nossas variáveis de ambiente.

E aí, por consequência, vamos ler essas variáveis de ambiente que é onde está associada a nossa chave da OpenAI.

from openai import OpenAI
from helper_models import MODELO_GPT
from dotenv import load_dotenv
import os

load_dotenv()

Implementando o assistente

O próximo passo consiste em iniciar o processo de implementação do nosso assistente. Vamos fazer isso usando um pouco de programação orientada a objetos.

Vamos criar uma classe chamada Assistente, que vai ter a responsabilidade de garantir que consigamos criar novos assistentes, tanto quanto quisermos, sem precisar fazer grandes alterações, exceto durante o processo de descrição do comportamento desse assistente.

Logo abaixo da classe Assistente, vamos criar agora o nosso construtor, o método que é invocado quando estamos criando um objeto de um novo tipo, nesse caso, do tipo Assistente.

Para criar esse construtor, usamos o def. Usamos dois underlines para poder indicar que ele é um método que vai ser desenhado de forma implícita e é o formato padrão que temos para os construtores do Python.

E vamos definir que ele vai ter pelo menos um argumento, que é o argumento self. Ele sugeriu no autocompletar um retorno, vamos apagar esse retorno, não precisamos dele. E ele colocou o pass para que possamos ter esse método mesmo sem nenhuma implementação. Vamos apagar o pass também.

class Assistente:
  def __init__(self):

Como vamos criar um assistente, todo assistente precisa de pelo menos duas informações dentro da OpenAI. Então, do lado do nosso parâmetro self, vamos adicionar novos dois parâmetros, o nome e as instrucoes, que é o que vai direcionar qual vai ser o comportamento desse assistente da OpenAI: como ele vai funcionar e como queremos que ele atenda aos nossos pedidos durante o processo de implementação.

Feito isso, vamos criar uma constante que vai ser a nossa chave de API (CHAVE_API)', e vamos pegar essa chave de API do nosso sistema operacional, das nossas variáveis de ambiente, e precisamos passar exatamente o nome que colocamos para a nossa variável de ambiente lá no nosso arquivo .env, que nesse caso foi OPENAI_API_KEY.

Dessa forma, vamos resgatar essa informação do sistema operacional e vamos atribuir a nossa constante CHAVE_API.

Feito isso, vamos criar agora um conjunto de propriedades que vamos utilizar o tempo todo para garantir a comunicação entre nós, pessoas que estão desenvolvendo alguma solução, e o assistente inteligente da OpenAI.

E o primeiro conjunto serve para poder dizer qual vai ser o cliente que vamos utilizar e a qual chave de API está associada. Para isso, vamos utilizar essa diretriz que é o self, para garantir que é esse assistente que vai invocar essa propriedade.

Vamos criar uma propriedade chamada cliente e ela vai ser construída através do construtor da OpenAI, que vai ter um único parâmetro que vai ser a nossa CHAVE_API. Feito isso, já temos o nosso cliente que vai estabelecer essa comunicação com a API.

Como podemos precisar das informações desse assistente em algumas partes da nossa codificação, na hora que formos perguntar para ele, por exemplo, se um script está bem desenhado ou não, pode ser interessante que tenhamos essas diretrizes fáceis de serem acessadas.

Então, vamos criar duas propriedades, uma para poder armazenar o nome e outra para as instruções que estamos dando para esse assistente. Vamos criar uma propriedade chamada nome que vai receber o parâmetro nome que veio do construtor e vamos criar uma propriedade chamada instrucoes que vai receber o parâmetro instrucoes que também veio do nosso construtor.

Após isso, o que vamos fazer agora será criar outras duas variáveis que serão necessárias para que possamos estabelecer a comunicação com o nosso assistente.

A primeira delas é uma thread, que vai começar vazia.

Relembrando: É na thread que vamos armazenar a nossa janela de contexto.

Podemos imaginar que estamos nos comunicando com um assistente que vai ser uma espécie de professor ou auxiliar nosso de aprendizado e que só conseguimos fazer essas perguntas através dessa janela de contexto.

Para exemplificar, poderia ser, por exemplo, uma folha de papel com algumas linhas vazias. Então, imaginamos que vamos nos comunicar com esse assistente através dessa folha de papel, escrevemos a pergunta na primeira linha, passamos essa folha para o assistente, ele analisa o que tem ali dentro e escreve a resposta.

A thread seria essa folha que armazena todo o diálogo que vamos ter entre o assistente virtual e nós que queremos fazer alguma pergunta para ele. Então, vamos criar essa thread, que ela começa, por enquanto, vazia.

E vamos ter também arquivos que vamos enviar, que serão os nossos scripts de programação. Então, vamos criar também uma variável arquivo, que começa vazia, e que vai ser alterada de acordo com o assistente que vamos criar e o script que vamos informar.

Por último, dentro desse método, vamos criar o nosso agente inteligente. Vamos chamar o método que vamos criar agora, chamado criar_agente. Por padrão, podemos dar uma nomenclatura que quisermos para o método, mas se não quisermos que esse método seja invocado fora desse script, é uma boa prática que coloquemos um underline no começo.

Então, vamos colocar esse underline. Esse método não existe ainda, por isso que ele não está com uma coloração diferente, mas conseguimos definir para quem for utilizar esse assistente que a ideia não é invocar ou criar_agente fora desse escopo.

class Assistente:
  def __init__(self, nome, instrucoes):
    CHAVE_API = os.getenv("OPENAI_API_KEY")
    self.cliente = OpenAI(api_key=CHAVE_API)

    self.nome = nome
    self.instrucoes = instrucoes

    self.thread = None
    self.arquivo = None

    self._criar_agente()

E agora, já temos a estrutura base do nosso assistente. O processo que vamos fazer agora é um processo que já fizemos em outros cursos da formação.

Ele vai envolver, basicamente, criar o nosso assistente, permitir que atribuamos um arquivo ao assistente, nessa descrição que vamos fazer, e criar e apagar threads ou arquivos.

Então, se você quiser tentar fazer, pode ser bastante interessante praticar o que você já viu em outros cursos da formação. E no começo do nosso próximo vídeo, vamos abordar um pouco mais rápido esse processo de criação dos assistentes. Até lá!

Sobre o curso OpenAI: avalie e otimize o seu código com Code Interpreter

O curso OpenAI: avalie e otimize o seu código com Code Interpreter possui 157 minutos de vídeos, em um total de 45 atividades. Gostou? Conheça nossos outros cursos de IA para Programação em Inteligência Artificial, ou leia nossos artigos de Inteligência Artificial.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda IA para Programação acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas