Neste artigo, explico como implementei a integração com o ChatGPT, possibilitando a exploração colaborativa de um repositório de código e a construção de sistemas.
1 — Recursos: O que vamos usar
Dentre os recursos oferecidos pela OpenAI, escolhi a API completions[1], pela sua clareza e detalhamento. Achei a documentação da API clara, bem detalhada e com exemplos funcionais. A utilização de uma API proporciona maior flexibilidade em comparação com a interface web padrão do ChatGPT.
Como tecnologia para desenvolver o GPTCoder, utilizarei Java, devido à minha familiaridade com a linguagem. O projeto será do tipo Java padrão para desktop. A IDE escolhida foi o Eclipse.
2 — Solução pretendida
Abaixo teremos os passos que foram pensados para a primeira versão da solução. Tendo como foco que o desenvolvedor terá o ChatGPT como um ajudante de sua missão de programação. A figura1 representa o processo da solução.
- Desenvolvedor define necessidade: Nesse momento o desenvolvedor vai informar o que deseja fazer ou que problema deseja solucionar. Como estamos focados em programação, um cenário esperado seria de criar uma classe que resolva determinado problema.
- Preparar o contexto: Aqui será necessário buscar as informações necessárias para o chat GPT se situar quando for chamado. Antes de pedir precisamos dar o máximo de dados que o ajudem a entender qual o mundo onde nosso problema está inserido. Na sessão 4 detalharemos mais este tema.
- Preparar o prompt: Por mais que usemos linguagem natural é preciso delimitar bem as fronteitas do que desejamos pedir. Um bom prompt possui um texto que não deixe dúvidas de interpretação quanto aquilo que desejamos. Na sessão 4 detalharemos mais este tema.
- Solicitar solução: Aqui vamos enviar todos os dados que temos para a API do ChatGPT e aguardar o processamento e resposta. Estamos efetivamente pedindo uma solução para o problema que temos em mãos.
- Processar solicitação: Nesse momento ocorre a análise de todos dados que enviamos e criação de uma resposta.
- Processar resposta: Será necessário receber os dados da resposta para extrair exatamente o que desejamos.
- Salvar os dados: Vamos persistir em um arquivo a resposta que recebemos. No nosso caso desejamos que os dados sejam um código, funcional que resolva nosso problema. Esse código será salvo no formato de classe.
- Avaliar solução: O Código foi persistido e está disponível para ser executado. O desenvolvedor vai avaliar se o código está solucionando o problema e pode pedir correções em uma nova requisição caso seja necessário.
3 — Contexto: Explique primeiro antes de pedir
Podemos pensar o contexto como o conjunto de informações que o ChatGPT precisa conhecer para entender melhor seus pedidos e assim prover melhores respostas. Nosso objetivo é gerar código e por vezes o GPT precisa ter acesso a outros códigos que já existam no sistema que ele vai trabalhar.
Nesse caso vamos criar um recurso que pode ler vários arquivos de uma vez e deixá-los prontos para envio na próxima requisição. A classe ReadDataFromFile tem métodos para ler um ou vários arquivos de código e transformar em uma string que pode ser adicionada a uma requisição. Assim, se passarmos os caminhos de vários arquivos de um repositório de código, o GPTCoder será capaz de ler os dados desses arquivos. Abaixo a figura 2 ilustra a classe ReadDataFromFile.
3.1 — Limites
É importante lembrar que existe um limite de dados para gerar contexto. Isso vai depender do modelo do ChatGPT que está sendo usado. O modelo que melhor performou foi o modelo gpt-3.5-turbo-16k. Esse modelo respondeu com maior velocidade que o GPT4 e demais GPT3. Ele possui uma janela de contexto de 16k tokens que seria aproximadamente 12000 palavras [2].
4 — Prompt: Como pedir da maneira correta
O ChatGPT entende linguagem natural, isto significa que não precisamos falar com ele usando linguagens de programação como PHP ou Java. Basta falar como falaria-mos com outro ser humano e ele será capaz de entender. No entanto, mesmo usando linguagem natural, se pudermos estruturar melhor nosso diálogo temos maior chance de conseguir respostas mais precisas.
Outro ponto importante é que o ChatGPT consegue interpretar código e produzir código. Então podemos pedir a ele que nos envie a resposta em formato de código em uma determinada linguagem de programação. No caso deste post estou focando em Java. Abaixo está o que foi levado em conta para estruturar um padrão de prompt que funciona para a solução.
4.1 — Estrutura de PROMPT usado:
- Comportamento esperado: {Papel} — Como o GPT precisa raciocinar ou o que ele deve imitar. Exemplo: Gerador de código Java, PHP etc.
- Com base em: {dados de contexto} — Exemplo: Minhas classes Java existentes no projeto ou relativas ao meu problema.
- Faça: {ação desejada} — Gere uma classe que resolva determinado problema; Melhore a classe X adicionando metodo que faz deteminada ação.
- Dados adicionais: {Formato de entrada e Formato de saída} — Adicione marcadores que ajudem o ChatGPT a identificar o que queremos passar na hora de pedir e como desejamos receber a nossa resposta. Criei os marcadores de inicio e fim de contexto (solicitação), inicio e fim de código (resposta) e inicio e fim de observação (resposta). Esse formato funcionou bem e evitou, principalmente, mistura de dados nas respostas e com isso maior facilidade de extração dos dados de interesse. A figura 3 mostra os marcadores usados.
4.1.1 — Exemplo de PROMPT usado na solicitação:
Comporte-se como uma api geradora de código Java. Crie uma classe Java que resolverá o problema a seguir: Baseado no codigo informado { Nada ainda} . Crie uma classe Pessoa que tenha nome, idade, cpf, rg, endereco, contato. Resposta esperada: responda no seguinte formato texto delimitado pelos 2 marcadores a seguir: ==response-obs-inicio== obsevacoes para orientacoes sobre o codigo ==response-obs-fim== ==response-javacode-inicio== deve conter Somente o codigo ==response-javacode-fim==.
4.1.2 — Exemplo de resposta recebida:
{“id”: “MEU_ID_API_CHAT_GPT”,“object”: “chat.completion”,“created”:1699113491,“model”: “gpt-3.5-turbo-0613”,“choices”: [{“index”: 0“message”: {“role”: “assistant”,“content”: “==response-obs-inicio==\nA classe Pessoa deve ter os seguintes atributos: nome (String), idade (int), cpf (String), rg (String), endereco (String) e contato (String).\n==response-obs-fim==\n==response-javacode-inicio==\n“`java\npublic class Pessoa {\n private String nome;\n private int idade;\n private String cpf;\n private String rg;\n private String endereco;\n private String contato;\n\n // Construtor da classe Pessoa\n public Pessoa(String nome, int idade, String cpf, String rg, String endereco, String contato) {\n this.nome = nome;\n this.idade = idade;\n this.cpf = cpf;\n this.rg = rg;\n this.endereco = endereco;\n this.contato = contato;\n }\n\n // Getters e Setters dos atributos da classe Pessoa\n public String getNome() {\n return nome;\n }\n\n public void setNome(String nome) {\n this.nome = nome;\n }\n\n public int getIdade() {\n return idade;\n }\n\n public void setIdade(int idade) {\n this.idade = idade;\n }\n\n public String getCpf() {\n return cpf;\n }\n\n public void setCpf(String cpf) {\n this.cpf = cpf;\n }\n\n public String getRg() {\n return rg;\n }\n\n public void setRg(String rg) {\n this.rg = rg;\n }\n\n public String getEndereco() {\n return endereco;\n }\n\n public void setEndereco(String endereco) {\n this.endereco = endereco;\n }\n\n public String getContato() {\n return contato;\n }\n\n public void setContato(String contato) {\n this.contato = contato;\n }\n}\n“`\n==response-javacode-fim==”},“finish_reason”: “stop”}],“usage”: {“prompt_tokens”: 163,“completion_tokens”: 386,“total_tokens”: 549}}
5 — Código
Nessa sessão focarei nos aspectos principais do código:
5.1 — Conexão com a API: A classe ApiCaller faz uma requisição HTTP padrão de acordo com a documentação da openAI e transforma dos dados devolvidos pela API em uma string de texto. Os dados de entrada que são o prompt em si estão representados pela variável jsonSend que é passada por parâmetro para o método callGPT.
5.2 — A classe Prompt representa um prompt em si e, nesse momento, a estrutura do prompt tem 2 partes principais: system-content e user-content. A primeira é a instrução inicial de configuração do ChatGPT tendo como foco definir o comportamento esperado. A segunda tem como foco passar o que o usuário deseja que seja feito e o contexto para o ChatGPT. Em determinada parte do processo vamos configurar as 2 partes de acordo com o que está definido na figura 6. Na figura 5 temos o código da classe Prompt na integra.
Podemos ver ainda que o dado que está sendo preparado é um JSON que tem: 1 — A propriedade model onde definimos o modelo do ChatGPT que vamos usar; 2 — O parâmetro temperatura define o quão criativo queremos que o modelo seja na hora de gerar resposta, e como queremos um código não precisa que ele gere o mesmo código sempre de formas diferentes, ele pode gerar o mesmo código desde que resolva o problema, assim a temperatura será 0 (zero); 3 — O atributo messages é onde vamos colocar os dados da solicitação.
5.3 — Depois de algumas camadas de abstração chegamos ao processo de colaboração, mais simples do projeto, onde o desenvolvedor pede uma ação por vez e recebe uma resposta por vez. No método codingWithDeveloper é possível passar no parâmetros: Uma lista de arquivos (repositório); O texto com o que desejamos fazer (doesInstruction — é a parte que falta no prompt); Onde salvar os dados após receber a resposta (whereDoesPath — É a classe Java onde vou salvar o código). O processo será descrito abaixo e ilustrado na figura 7.
5.3.1 — Processo
- Leitura de todos os arquivos que vão servir para gerar o contexto na variável codeContext
- Formatação do JavaPrompt que um prompt especifico para gerar código Java de acordo com o item 5.2
- Chamada a API do ChatGPT, coletamos a resposta e extraímos o dado que nos interessa salvar.
- Salvamento dos dados de interesse (a classe Java) usando o SaveGPTResponseFile. O caminho onde o código deve ser salvo precisa ser informado. No caso definimos a pasta e acrescentamos o nome da classe para gerar o caminho completo do arquivo de destino.
- Por fim salvamento de todos os dados de requisição e resposta em um histórico.
5.4 — Com esse processo já é possível ao desenvolvedor passar instrucões para criar classes que resolvam problemas, analisar classes e melhora-las. Abaixo veremos alguns exemplos de prompts que foram usados pra um projeto piloto.
5.4.1 — A classe prompt executor tem o path de um repositório de código na variável sourcePath. Um objeto PromptExecutor chama o método codingWithDeveloper passando apenas um arquivo “crud/PessoaRequisicao.java” e pedindo para fazer melhorias nesse mesmo arquivo essa melhoria seria acrescentar o JavaDoc com base no código atual da classe. O Resultado é a mesma classe Java com a documentação acrescida no padrão JavaDoc. Parte da classe documentada está ilustrada na figura 9.
5.4.2 — Aqui já temos vários prompts pedindo para executar a criação de várias classes onde definimos os atributos ou objetivos das classes. Definimos onde os dados serão salvos e definimos o pacote onde as classes serão criadas como fonte de contexto. Nessa sequencia de execução de prompts novas classes são geradas e essas já servem como contexto para a próxima requisição. Possibilitando a criação em lote de um conjunto de classes do sistema.
Pela figura 10 vemos que quando a classe PessoaDAO for criada essa terá como informações de contexto as classes Pessoa, Contato e Endereço que ja foram criadas anteriormente. O contexto de PessoaRequisição será todas classes anteriores inclusive PessoaDAO. Isso é possível pois estamos sempre enviando todas as classes que foram criadas antes para o ChatGPT manter-se contextualizado.
6 — Conclusão
- Foi possível criar uma integração com o ChatGPT usando sua API, ler dados de um repositório de código para ajudar na contextualização e geração de novos códigos.
- Entendemos um pouco mais sobre contexto, sobre a importância de gerar bons prompts[3] e o potencial do ChatGPT como parceiro de desenvolvimento de software no dia a dia do desenvolvedor de sistemas.
- O sistema desenvolvido pode ser usado para criar sistemas em outras linguagens de programação ou outros tipos de conteúdo textual. Apenas fazendo extensões da classe que define os prompts.
- Como pontos de melhoria destaco a necessidade de um nível de abstração que permita ao desenvolvedor definir regras de negócio em alto nível e receber a regra implementada com as camadas de abstração corretamente escritas.
Referências
[1]APIIntegrate OpenAI models into your application or business
[3]https://platform.openai.com/docs/guides/prompt-engineering
Eu fiz uma leitura de quase todo o conteúdo disponibilizado, gosto da área da inteligência artificial tenho muitas idéias para contribuir para inovação, mas me falta oportunidade, tenho uma excelente ideia que pode ser implantada e gerar bons frutos dentro e fora da openai… Ideia realmente inovadora.