Alura > Cursos de Inteligência Artificial > Cursos de IA para Programação > Conteúdos de IA para Programação > Primeiras aulas do curso GPT e Java: desenvolva um Chatbot com IA

GPT e Java: desenvolva um Chatbot com IA

Stream de mensagens - Apresentação

Olá, tudo bem? Me chamo Rodrigo, sou instrutor na Alura e te convido a mais um curso relacionado com Programação e Inteligência Artificial.

Audiodescrição: Rodrigo se declara como uma pessoa de pele clara, com olhos castanhos e cabelos também castanhos e lisos. Usa uma camiseta vermelha com um fone de ouvido branco. Está sentado em uma cadeira preta. Ao fundo, há uma luz gradiente azul e alguns quadros pendurados.

Este é o segundo curso voltado para a programação usando Inteligência Artificial com a OpenIA, onde continuaremos os estudos entendendo como usar a integração com a API da OpenIA para desenvolver aplicações.

O que vamos aprender?

Neste curso, o foco será o desenvolvimento de uma aplicação de um chatbot para uma empresa fictícia de um e-commerce chamado EcoMart.

A EcoMart é uma empresa que vende produtos sustentáveis e ecológicos.

A proposta é desenvolver um assistente virtual para responder às dúvidas comuns que as clientes possam ter durante a navegação. Desenvolveremos esse projeto e as respostas que o chatbot vai devolver serão todas geradas pela Inteligência Artificial da OpenAI.

Vamos revisitar a integração de nossa aplicação com a API da OpenAI, seguindo a abordagem do curso anterior. Desta vez, iremos implementar um projeto em Java, utilizando Spring e as principais tecnologias da linguagem.

Exploraremos recursos novos disponíveis na API da OpenAI, como o esquema de streaming de mensagens, que permite a exibição das mensagens de forma progressiva na tela, palavra por palavra, ou seja, token por token, semelhante ao funcionamento do chat GPT. No Chat GPT, ao enviarmos uma pergunta, as respostas são exibidas aos poucos.

Além disso, aprenderemos a usar um outro modo da API, que é o modo de Assistant, um modo onde podemos criar uma thread (sequência de mensagens) que mantém o histórico de conversas. Isso é importante para que o sistema leve em consideração as mensagens anteriores na hora de responder.

Assim, ao fazermos uma pergunta obtemos como retorno uma resposta considerando o histórico da conversa. Esse modo possui algumas ferramentas a mais também que abordaremos ao longo deste curso.

Uma delas que aprenderemos a utilizar é a ferramenta denominada Knowledge Retrieval (Recuperação de Conhecimento). Realizaremos o upload de documentos contendo informações do Ecomart, nossa empresa fictícia. Dessa forma, quando a ferramenta gerar as respostas, poderá ler esses arquivos e ter uma base de conhecimentos para responder de maneira mais específica, evitando respostas genéricas.

Por fim, empregaremos um recurso bastante poderoso denominado Function Calling (Chamada de Função). Podemos definir uma função, neste caso, em Java, uma classe com um método que executa algo dinâmico. Isso nos permite gerar respostas mais dinâmicas, pois uma parte é gerada pela API, outra pela nossa aplicação, e a OpenAI realiza toda essa integração.

Esses são os objetivos deste segundo curso focado em integração de uma aplicação Java com a API da OpenAI.

Pré-requisitos para este curso

Os pré-requisitos para este curso são: ter feito o curso anterior, IA Generativa, ter conhecimento de Java e de orientação a objetos, conhecer o Maven e o Spring Boot.

A IA Generativa que gera os textos e conhecimento em Java e Orientação a objetos dado que usaremos essa linguagem neste curso. Também é importante conhecer o Maven, isso porque o projeto usará o Maven para gerenciar as dependências e a estrutura de diretórios seguirá o padrão do Maven. A novidade para este curso seria o Spring Boot, dado que usaremos uma aplicação web desenvolvida em Spring Boot.

Já temos um projeto inicial, em que a parte do front-end está sendo desenvolvida e foi feita em Spring Boot. O foco deste curso não é como desenvolver e usar o Spring Boot, já temos formações aqui na Alura para isso.

Esses pré-requisitos são importantes para que você consiga acompanhar esse curso da melhor maneira possível.

Tire suas dúvidas!

Ao longo do curso, se você tiver alguma dúvida, não deixe de recorrer ao nosso fórum, participe também da nossa comunidade do Discord e ao final do curso lembre de deixar a sua avaliação.

Vamos lá, te encontro na primeira aula!

Stream de mensagens - Conhecendo a aplicação

Neste curso, focaremos em uma empresa fictícia chamada EcoMart, um e-commerce especializado em produtos sustentáveis. Além do e-commerce, a empresa está trabalhando no desenvolvimento de um assistente virtual, um chatbot. Essa aplicação possui um robô que responde às dúvidas frequentes dos clientes.

Nosso objetivo é concluir o desenvolvimento desse chatbot e integrá-lo à OpenAI para aproveitar as ferramentas de inteligência artificial.

A empresa já iniciou o desenvolvimento do chatbot, mas ele está incompleto. Durante o curso, nossa meta será finalizar esse projeto e realizar a integração com a OpenAI para utilizar as ferramentas de inteligência artificial.

Entendendo a aplicação

Com o projeto inicial aberto no IntelliJ, antes deste vídeo, será realizada uma atividade de preparação do ambiente, onde você encontrará o link para baixar o projeto inicial. Descompacte o arquivo .zip em algum diretório do seu computador e abra a pasta no IntelliJ.

O projeto é uma aplicação Java que utiliza o Spring Boot como framework principal. Para executar uma aplicação com o Spring Boot, há uma classe com o método main. No project, à esquerda do IntelliJ, expandimos o projeto, navegamos até "src > main > java". No pacote br.com.alura.ecomart.chatbot, encontramos a classe ChatbotApplication. Essa é a classe que contém o método main().

ChatbotApplication

package br.com.alura.ecomart.chatbot;

import…

@SpringBootApplication
public class ChatbotApplication {

    public static void main(String[] args) {
    SpringApplication.run(ChatbotApplication.class, args);
    }
}

Executamos a aplicação daqui. Para isso, clicamos com o botão direito do mouse no arquivo e escolhemos a opção "Run ChatbotApplication main()" ou podemos usar o atalho "Ctrl + Shift + F10".

Ao iniciar, o log exibe a inicialização do Spring Boot, e o projeto é lançado com êxito na porta 8080.

O retorno abaixo foi parcialmente transcrito. Para conferi-lo na íntegra, execute o código na sua máquina

Tomcat started on port 8080 (http) with context path ''

Abrimos o navegador e acessamos esse endereço para verificar o que já está pronto no projeto.

http://localhost:8080

A tela principal da aplicação é simples; o destaque não está na aplicação, no chatbot, nas funcionalidades, na tela ou na interface, mas na integração desse sistema com a API da OpenAI para aproveitarmos os recursos de inteligência artificial discutidos no curso anterior.

A proposta do projeto é um chatbot. Ao abrir a tela, ele mostra a mensagem:

"Olá, sou o assistente virtual da EcoMart. Como posso te ajudar?".

Na parte inferior desta página, tem um campo de texto para a pessoa usuária digitar uma dúvida. Podemos digitar "olá!" e teclar "Enter" ou clicar no ícone do lado direito do campo de enviar pergunta. Ao enviar a pergunta, a resposta do assistente virtual aparece no lado esquerdo da tela e a nossa pergunta do lado direito.

Entretanto, essa funcionalidade do chatbot não está funcionando corretamente. Ele está devolvendo a mesma pergunta como resposta, a mesma pergunta que a pessoa usuária envia. Podemos enviar "teste" que obtemos como retorno do chat "teste".

Já temos um projeto inicial, um projeto base pronto, com esse comportamento no momento, e vamos ajustar isso para fazer a chamada para a API da OpenAI.

A aplicação possui uma tela de chat, um formulário na parte inferior, e no canto superior direito da tela tem um botão de "Limpar Conversa", que recarrega a página e limpa a conversa anterior. Esse é o projeto que vamos trabalhar neste curso.

Analisando o código

Agora, analisaremos o código. A aplicação utiliza Spring Boot e o Maven para o gerenciamento das dependências, seguindo a estrutura de diretórios do Maven. Encontramos "src > main > java" para pacotes e classes Java e "src > main > resources" para os recursos.

Por exemplo, onde estão as páginas HTML. No caso, a página do chat, representada pelo arquivo chat.html.

Na pasta static, encontramos os arquivos do front-end, CSS, JavaScript e imagens. Apesar de ser uma aplicação web tradicional, há um código JavaScript responsável pela lógica de enviar perguntas para a aplicação.

No projeto, temos um pacote chamado infra.openai. Nele, já temos classes similares ao que fizemos no curso anterior. Tem uma classe que faz a contagem de tokens, e uma classe que é chamada de OpenAIClient, que faz a chamada para a API da OpenAI. Só que está adaptado para ser um componente do Spring.

A parte visual do projeto está pronta por não ser o foco do curso.

No diretório raiz da aplicação tem o arquivo pom.xml, o arquivo do Maven que têm as declarações das dependências. No campo de dependências tem as dependências do Springdas tecnologias que estamos utilizando.

E também tem aquelas duas dependências que usamos no curso anterior. A biblioteca Java que se integra com a OpenAI e a biblioteca para fazer contagem de tokens.

pom.xml

<!-- código omitido -->

<dependency>
    <groupId>com.theokanning.openai-gpt3-java</groupId>
    <artifactId>service</artifactId>
    <version>0.18.2</version>
</dependency>

<dependency>
    <groupId>com.knuddels</groupId>
    <artifactId>jtokkit</artifactId>
    <version>0.6.1</version>
</dependency>

<!-- código omitido -->

Conclusão

Agora você já conhece o projeto que vamos trabalhar, a funcionalidade que vamos desenvolver, e exploramos o código fonte, na estrutura. Na sequência, começaremos a entender qual é o nosso desafio e como que faremos essa ponte deste projeto com a API da OpenAI.

Stream de mensagens - Integrando com a API

Agora que conhecemos o projeto e exploramos o código-fonte, partimos para o trabalho.

Integrando com a API

Na página do navegador no endereço localhost:8080, a página do nosso chatbot.

http://localhost:8080

Conforme evidenciado no vídeo anterior, esse chatbot não está funcionando corretamente, pois responde essencialmente com a mesma pergunta enviada.

É neste ponto que começaremos a fazer as modificações. A proposta é que, ao receber uma pergunta, pegaremos o texto digitado e enviaremos uma requisição à API da OpenAI para obter uma resposta via inteligência artificial. Essa etapa de integração assemelha-se ao que foi abordado no curso anterior.

Como mencionamos, é uma aplicação web, e essa parte do front-end já está implementada, não vamos modificar, não é o foco deste curso. Mas, basicamente, quando enviamos uma mensagem nesse formulário e clicamos no botão ou teclamos um "Enter", uma requisição do tipo post (postagem) é disparada para a URL /chat.

No código, estamos usando o Spring, seguindo o padrão MVC, e terá uma classe Controller que está recebendo essa requisição. É lá que vamos começar a analisar e verificar o que está acontecendo para fazer as adaptações.

No IntelliJ, vamos analisar o projeto à esquerda, no "src > main > java", no pacote br.com.alura.ecomart.chatbot, tem um pacote chamado web e dentro dele um subpacote chamado controller, e lá dentro está a classe ChatController.

Todos esses arquivos já foram preparados para você no ambiente, é o nosso projeto inicial.

Numa classe Controller do Spring, que recebe requisições do navegador, faz o processamento e devolve uma resposta.

Quando abrimos a página no navegador, localhost:8080, / ou /chat, ela cai no método CarregarPaginaChatBot(), que basicamente navega para a página chat.html, que está usando o Thymeleaf (tecnologia para fazer o desenho da página HTML do front-end).

ChatController.java

// código omitido

private static final String PAGINA_CHAT = "chat";

@GetMapping
public String carregarPaginaChatbot() {
    return PAGINA_CHAT;
}

// código omitido

Mas o que interessa para nós é o segundo método chamado ResponderPergunta(). Esse é o método que é chamado quando a pessoa usuária envia uma pergunta na página.

ChatController.java

// código omitido

@PostMapping
@ResponseBody
public String responderPergunta(@RequestBody PerguntaDto dto) {
    return dto.pergunta();
}

// código omitido

A pergunta chega como parâmetro nesse objeto do tipo PerguntaDTO, que é só um record do Java, está seguindo aquele padrão DTO (Data Transfer Object), para encapsular as informações que estão chegando da página, que no nosso caso é só uma informação que é a pergunta: o texto que a pessoa digitou.

E observem que esse método está fazendo no Controller no momento. A única coisa que ele faz é return dto.pergunta. Por isso que ele não está funcionando como esperado. Ele está simplesmente devolvendo a própria pergunta que foi enviada.

Neste ponto, precisamos efetuar uma mudança. Não buscamos apenas devolver a pergunta; desejamos extrair o texto dessa pergunta, enviar uma requisição à API da OpenAI, solicitar o chat completion (um recurso), gerar uma resposta, capturar essa resposta e exibi-la na página.

No entanto, a chamada da API não será incluída na classe Controller. Criaremos uma classe Service, seguindo o padrão de projeto Service, na qual isolaremos a lógica de disparar a requisição.

Utilizar uma classe Service é uma prática recomendada para evitar a inserção de lógicas, validações ou algoritmos dentro de uma classe Controller.

Dentro do pacote Domain, já tem um pacote chamado Service, que possui uma classe chamada CalculadoraDeFrete, que analisaremos mais adiante no curso. No pacote Service, criamos uma nova classe.

Com o pacote selecionado, teclamos "Alt + Insert" e escolhemos a opção Class, e chamamos de ChatbotService.

ChatbotService

package br.com.alura.ecomart.chatbot.domain.service;

public class ChatbotService {

}

Essa será a classe Service que vai encapsular essas lógicas relacionadas com o chatbot. Acima da classe, deve ter a anotação @Service, porque desejamos injetar essa classe no Controller e o Spring precisa conhecer essa classe. Portanto, tem que ter alguma anotação, neste caso, será a anotação Service.

Na classe ChatbotService, criamos o método que chama a classe que faz a requisição para API da OpenAI. Criamos um método public que devolve uma String.

Essa String que vai ser devolvida é a resposta que a API nos devolveu. O nome do método será responderPergunta(), que receberá como parâmetro a String com a pergunta. O Controller vai chamar essa classe e ela é que chama quem sabe disparar a requisição.

ChatbotService

package br.com.alura.ecomart.chatbot.domain.service;

import org.sparingframework.stereotype.Service;

@Service
public class ChatbotService {

    public String responderPergunta(String pergunta) {

    }

}

No entanto, não será na classe Service que terá o código de infraestrutura, todo aquele código que usa aquela biblioteca Java para disparar a requisição. No vídeo anterior, mostramos que já tem uma classe no projeto que dispara a requisição.

No pacote infra.openai, tem uma classe chamada OpenAIClient. Nela, temos o código que dispara a requisição. E o método é esse, chamado enviarRequisicaoChatCompletion().

Para chamarmos a API da OpenAI, temos que pegar essa classe OpenAIClient e chamar esse método. Isso é o que desejamos fazer na classe Service.

Na classe ChatBotService, declaramos um atributo private do tipo OpenAIClient denominado client. É necessário informar ao Spring que ele injetará esse atributo na classe. Existem várias maneiras de fazer isso, uma delas é através da declaração de um construtor.

Assim, abaixo do atributo, implementamos um construtor. Para isso, teclamos "Alt + Insert" escolhemos a opção "construtor" e teclamos "Enter".

ChatbotService

package br.com.alura.ecomart.chatbot.domain.service;

import br.com.alura.ecomart.chatbot.infra.openai.OpenAIClient;
import org.sparingframework.stereotype.Service;

@Service
public class ChatbotService {

    private OpenAIClient client;
    
    public ChatbotService(OpenAIClient client) {
        this.client = client;
    }

    public String responderPergunta(String pergunta) {

    }
}

Dessa forma, implementamos um construtor que recebe esse client, e o Spring reconhece que, ao executar a service, é necessário fornecer o client, pois ele saberá instanciar o client devido à classe client possuir a anotação @Component.

O que o método responderPergunta() fará? Delegará, return client.enviarRequisicaoChatCompletion();. Assim, a nossa Service está invocando o método da classe client. Contudo, esse método requer um parâmetro, mas não podemos passar a pergunta diretamente, pois esse método não aceita a String com a pergunta.

ChatbotService

package br.com.alura.ecomart.chatbot.domain.service;

import br.com.alura.ecomart.chatbot.infra.openai.OpenAIClient;
import org.sparingframework.stereotype.Service;

@Service
public class ChatbotService {

    private OpenAIClient client;
    
    public ChatbotService(OpenAIClient client) {
        this.client = client;
    }

    public String responderPergunta(String pergunta) {
        return client.enviarRequisicaoChatCompletion(pergunta);
    }
}

Ao analisarmos a classe OpenAIClient, observamos que o método enviarRequisicaoChatCompletion() recebe um objeto do tipo DadosRequisicaoChatCompletion.

Este é outro record presente no Java, contendo o prompt do sistema e o prompt do usuário. É uma classe que encapsula esses dois prompts.

Portanto, na classe Service, não podemos passar a pergunta como parâmetro, temos que passar os dados. No entanto, essa variável não existe, vamos criá-la. Teclamos "Alt + Enter" e escolhemos a opção "Create local variable 'dados'".

Trocaremos a variável DadosRequisicaoChatCompletion para utilizar a palavra var. Assim, na linha acima, temos var dados, e então definimos dados como new DadosRequisicaoChatCompletion.

ChatbotService

// código omitido

    public String responderPergunta(String pergunta) {
        var dados = new DadosRequisicaoChatCompletion();
        return client.enviarRequisicaoChatCompletion(dados);
    }
}

Ao instanciar esse objeto, DadosRequisicaoChatCompletion(), são necessários dois parâmetros, ambos strings. O primeiro é a string contendo o prompt do sistema, e o segundo é a string contendo o prompt da pessoa usuária.

O prompt do sistema será atribuído a uma variável chamada promptSistema, que ainda não foi criada. Quanto ao prompt da pessoa usuária, este será a própria pergunta digitada na tela, utilizada como o prompt da pessoa usuária.

Como está ocorrendo um erro devido à ausência da variável promptSistema, na linha anterior, declaramos var promptSistemae o igualamos aoprompt já copiado, apenas colando com "Ctrl + V".

ChatbotService

// código omitido

    public String responderPergunta(String pergunta) {
        var promptSistema = "Você é um chatbot de atendimento a clientes de um ecommerce e deve responder apenas perguntas relacionadas com o ecommerce";
        var dados = new DadosRequisicaoChatCompletion(promptSistema, pergunta);
        return client.enviarRequisicaoChatCompletion(dados);
    }
    }

Esse é o prompt do sistema. Salvamos, e agora está pronta a nossa service, ela já sabe chamar a classe Client que dispara a requisição.

Agora, na classe ChatController.java , no método responderPergunta(), é necessário devolver o retorno do método responderPergunta() da classe Service. No ChatController, utilizaremos a classe Service.

Desta forma, declaramos um atributo private chamado ChatbotService do tipo service na classe Controller. Para isso, é necessário que o Spring faça a injeção, então declaramos um construtor no Controller que recebe a Service como parâmetro, permitindo que o Spring realize a injeção automaticamente. Para isso, usamos o "Alt + Insert" e optamos por "construtor".

ChatController.java

// código omitido

private ChatbotService service;

public ChatController(ChatbotService service) {
        this.service = service;
}

// código omitido

No método responderPergunta(), a resposta agora não será do tipo dto.Pergunta; será do tipo service.responderPergunta(). Passamos como parâmetro o dto.pergunta(), que contém a pergunta digitada pela pessoa usuária na página.

ChatController.java

// código omitido

@PostMapping
@ResponseBody
public String responderPergunta(@RequestBody PerguntaDto dto) {
    return service.responderPergunta(dto.pergunta());
}

// código omitido

Assim, o Controller chama a Service, e está encapsula a chamada para a classe Client, responsável por disparar a requisição para a API.

A princípio, está tudo certo e podemos testar.

Entraremos novamente no localhost:8080/chat, ou apenas localhost:8080 também carregará a página. Digitaremos uma pergunta, por exemplo, "Bom dia, tudo bem?". Observaremos a resposta ao pressionar "Enter".

Nesse processo, inicialmente, o Controller invoca a Service, que por sua vez aciona o Client. O Client então envia a requisição para a API, a qual retorna uma resposta que o Client recebe, repassa para a Service, esta retorna ao Controller, e o Controller finalmente entrega à página.

Obtemos como resposta:

Bom dia! Estou bem, obrigado por perguntar. Como posso ajudá-lo hoje com relação ao nosso ecommerce? Precisa de informações sobre produtos, entregas, pagamentos ou tem alguma outra questão?

Há todo esse fluxo, e ele ocorreu perfeitamente. Observem que ele capturou o prompt do sistema corretamente, reconhecendo que se trata de um assistente de um e-commerce.

Entretanto, talvez tenham percebido que a resposta foi exibida de uma vez, sem ser apresentada palavra por palavra ou token por token. Se já tiverem utilizado o chat GPT, notaram que, ao fazer uma pergunta, a resposta não é exibida integralmente de imediato; ao contrário, o texto é gerado gradualmente, com os tokens aparecendo progressivamente na tela.

Conclusão

Podemos implementar essa abordagem neste projeto também, tornando-o mais semelhante a um chat onde as respostas são geradas conforme a pessoa digita, assemelhando-se ao chat GPT. A seguir, aprenderemos como fazer isso.

Sobre o curso GPT e Java: desenvolva um Chatbot com IA

O curso GPT e Java: desenvolva um Chatbot com IA possui 132 minutos de vídeos, em um total de 46 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