Agente de IA confiável em prod com Java + Quarkus + Langchain4j - Parte 1 - AI as Service
Herbert Beckman
Posted on October 25, 2024
Autores
@herbertbeckman - LinkedIn
@rndtavares - LinkedIn
Partes do artigo
Agente de IA confiável em prod com Java + Quarkus + Langchain4j - Parte 1 - AI as Service (este artigo)
Agente de IA confiável em prod com Java + Quarkus + Langchain4j - Parte 2 - Memória (em breve)
Agente de IA confiável em prod com Java + Quarkus + Langchain4j - Parte 3 - RAG (em breve)
Agente de IA confiável em prod com Java + Quarkus + Langchain4j - Parte 4 - Guardrails (em breve)
Introdução
Sempre que temos um "boom" de uma tecnologia emergente, as empresas ficam ansiosas por aplicá-las e colher os resultados tão esperados do ponto de vista do negócio. É a corrida pela inovação e a disputa pelas vantagens do pioneirismo. No meio dessa corrida, muitas das vezes, as empresas, que antes estavam ansiosas, acabam desistindo por uma série de fatores, sendo um dos principais a confiabilidade de um sistema de forma geral. A inteligencia artificial (IA) está neste momento em uma das suas maiores provas de resistência e nosso trabalho como desenvolvedores de softwares é demostrar as empresas que sim, é possível realizar uma série de tarefas e processos com o uso consciente e correto da IA. Neste artigo iremos demonstrar, em 3 partes, quais são as funcionalidades e processos que devemos ter em um agente de IA confiável em produção para uma empresa ter os tão esperados resultados, bem como implementarmos juntos alguns conceitos utilizados no mercado. Iremos também detalhar os pontos de atenção desta solução e pedimos para que você, dev, realize o máximo de testes e nos dê o maior número de feedbacks possíveis para que, juntos, possamos melhorar ainda mais esse entendimento.
Funcionalidades Implementadas
- Chat
- Tools
- Chat Memory
- Retrieval-Augmented Generation (RAG)
- Guardrails
Conceitos e definições
Assistente vs Copiloto vs Agente
Uma das primeiras dúvidas que se pode ter é no que um agente se diferencia dos demais casos de uso da IA. O Agente tem funcionalidades mais ligadas pra automação, enquantos os outros tem suas atividades voltadas à finalidade de assistentcia e otimização do tempo. Abaixo detalho melhor cada um dos casos de uso.
Assistentes
Os assistentes conseguem nos auxiliar e economizar um bom tempo verificando informação e sendo uma boa fonte de troca de conhecimento. Eles falam SOBRE os assuntos mais variados e podem nos ser úteis quando precisamos de uma linha de raciocínio claro para analisar as premisas de uma argumentação. Claro, eles tem bem mais poderes que isso, mas quero que você foque no que um assistente faz: ele conversa com você e somente isso. Ele SÓ pode falar sobre, sumarizar, detalhar, etc. Como exemplos temos: ChatGPT, Claude AI e Gemini.
Copilotos
Já os copilotos são um pouco mais poderosos que os assistentes. Eles conseguem realmente fazer algo, uma ação mais concreta como alterar um texto e ou sugerir modificações em tempo real, bem como dar dicas durante uma modificação e/ou evento acontecendo dentro de um contexto. Porém, como dito antes, ele depende do contexto pra fazer isso e nem sempre ele tem todas as informações necessárias para realizar um boa sugestão, ele também depende de sua autorização expressa, criando uma dependência direta com o usuário. Exemplos bons são: Github Copilot, Codium e Microsoft Copilot.
Agentes
Os agentes tem como objetivo principal realizar tarefas com objetivos claros. Tem o seu foco na automatização, ou seja, eles realmente fazem concreto e de forma autônoma. Tudo isso só se faz possível através das ferramentas que disponibilizamos a eles. O Agente não é o LLM em si, mas sim a sua aplicação que coordena esse LLM. Entenda o LLM como o cérebro do sistema, que toma as decisões, e a sua aplicação como os membros do corpo desse cérebro. Do que adianta eu pensar em pegar um copo de água se não consigo alcançá-lo com a minha mão? O seu agente proporciona ao LLM o poder de fazer algo de forma segura, auditável e, principalmente, confiável.
Partindo pra ação
Nesta primeira parte do artigo iremos implementar o AIService no projeto, que nada mais é do que a camada de interface com o nosso provedor de IA. Nesse projeto utilizamos o LLM da OpenAI, mas vc pode adicionar o seu provedor favorito e ajustar as dependências com base nele.
Agora que temos os conceitos bem definidos e já sabemos o que iremos fazer aqui, vamos pra codificação!
Criando o projeto
Crie um projeto quarkus, escolhendo o seu gerenciador de dependências e as extensões em Quarkus - Start coding.
Dependencias do projeto
Iremos utilizar o maven como gerenciador de dependências do projeto. A seguir as dependências iniciais que adicionamos.
Mavem
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-websockets-next</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.langchain4j</groupId>
<artifactId>quarkus-langchain4j-core</artifactId>
<version>0.20.3</version>
</dependency>
<dependency>
<groupId>io.quarkiverse.langchain4j</groupId>
<artifactId>quarkus-langchain4j-openai</artifactId>
<version>0.20.3</version>
</dependency>
Configuração do projeto
Adicione no arquivo src/main/resources/application.properties
as seguintes propriedades:
quarkus.tls.trust-all=true
quarkus.langchain4j.timeout=60s
quarkus.langchain4j.openai.api-key=YOUR_OPENAI_API_KEY_HERE
Substitua YOUR_OPENAPI_KEY_HERE
pela chave (apiKey) que você cadastrou na Plataforma da OpenAI.
DICA: crie uma variável de ambiente na sua IDE e depois modifique a property quarkus.langchain4j.openai.api-key
para:
quarkus.langchain4j.openai.api-key=${OPEN_API_KEY:NAO_ENCONTREI_A_VAR}
Criando o nosso AIService
Primeiramente precisamos criar o nosso AIService que será a classe responsável por dar uma "personalidade" ao nosso agente. Para isso, no diretório src/main/java/<seupacote>
, criaremos a classe de nome Agent
com o seguinte código:
package <seupacote>;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import io.quarkiverse.langchain4j.RegisterAiService;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
@RegisterAiService
public interface Agent {
@SystemMessage("""
Você é um agente especializado em futebol brasileiro, seu nome é FutAgentBR
Você sabe responder sobre os principais títulos dos principais times brasileiros e da seleção brasileira
Sua resposta precisa ser educada, você pode deve responder em Português brasileiro e de forma relevante a pergunta feita
Quando você não souber a resposta, responda que você não sabe responder nesse momento mas saberá em futuras versões.
""")
String chat(@UserMessage String message);
}
Como podem perceper pelo nosso SystemPrompt (@SystemMessage
), criamos um agente especializado em futebol.
Criando o nosso chat
Agora que criamos o nosso agente, precisamos criar a classe que cuidará do nosso chat com ele. Para isso, no diretório src/main/java/<seupacote>
, criaremos a classe de nome AgentWSEndpoint
com o seguinte código:
package <seupacote>;
import io.quarkus.websockets.next.OnTextMessage;
import io.quarkus.websockets.next.WebSocket;
@WebSocket(path = "/ws")
public class BotWSEndpoint {
private final Agent agent;
BotWSEndpoint(Agent agent) {
this.agent = agent;
}
@OnTextMessage
String reply(String message) {
return agent.chat(message);
}
}
Agora você já consegue conversar com o seu agente, que no momento ainda é um assistente, através da dev ui do quarkus. Segue alguns prints pra você se orientar:
Adicionando as nossas ferramentas (Function Calling)
Agora vamos para o detalhe que faz toda a diferença entre um agente e um assistente. Vamos dar a possibilidade do nosso agente realizar tarefas e/ou processos, adicionando as ferramentas (function calling). Antes de codificarmos isso, temos um breve gráfico demonstrando como a chamada de uma ferramenta funciona de forma macro.
Source: superface.ai
Agora que sabemos como uma chamada de ferramenta funciona, precisamos criar a classe com nossas ferramentas, você também pode criar várias classes diferentes para cada ferramenta. Neste exemplo iremos criar uma "ToolBox", ou seja, uma caixa de ferramentas, agrupando as ferramentas que o nosso agente pode utilizar. Segue o código:
package <seupacote>;
import dev.langchain4j.agent.tool.Tool;
import jakarta.enterprise.context.ApplicationScoped;
import java.time.LocalDate;
import java.time.LocalTime;
@ApplicationScoped
public class AgentTools {
@Tool
LocalDate currentDate() {
System.out.println("Called currentDate()");
return LocalDate.now();
}
@Tool
LocalTime currentTime() {
System.out.println("Called currentTime()");
return LocalTime.now();
}
@Tool("Calcula a soma de dois números")
int add(int a, int b) {
System.out.println("Called add with a=" + a + ", b=" + b);
return a + b;
}
@Tool("Calcula a raiz quadrada de um número")
double sqrt(int x) {
System.out.println("Called sqrt with x=" + x);
return Math.sqrt(x);
}
}
Logo em seguida, adicionamos no nosso agente o anotação informando pra ele quais ferramentas ele tem disponível para utilizar, através da anotação @ToolBox(AgentTools.class)
. Ficando da seguinte maneira:
package <seupacote>;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import io.quarkiverse.langchain4j.RegisterAiService;
import io.quarkiverse.langchain4j.ToolBox;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
@RegisterAiService
public interface Agent {
@ToolBox(AgentTools.class)
@SystemMessage("""
Você é um agente especializado em futebol brasileiro, seu nome é FutAgentBR
Você sabe responder sobre os principais títulos dos principais times brasileiros e da seleção brasileira
Sua resposta precisa ser educada, você pode deve responder em Português brasileiro e de forma relevante a pergunta feita
Quando você não souber a resposta, responda que você não sabe responder nesse momento mas saberá em futuras versões.
""")
String chat(@UserMessage String message);
}
Agora você pode perguntar ao seu agente que horas são, qual é a data de hoje, pedir pra ele somar dois números e calcular a raiz quadrada. Essas são as ferramentas que utilizamos aqui para ilustrar, mas você pode substituir isso por uma chamada HTTP, por uma função de hashing, por uma query SQL, etc. As possibilidades aqui são muitas.
Testando via Quarkus DEV UI
Segue o print de um dos testes realizados após adicionar as ferramentas:
Como pode ver, pra cada chamada de ferramenta teremos um log, evidenciando que o LLM realmente chamou o código que autorizamos ele a executar.
Próximos passos
Isso encerra o início da criação no nosso Agente. Em breve adicionaremos memória ao nosso Agente na parte 2, o RAG (Retrieval-Augmented Generation) na parte 3 e os Guardrails na parte 4 deste artigo. Espero que tenham gostado e até breve.
Mas você pode já acompanhar e ver TODO o código do artigo neste repositório do GitHub.
Posted on October 25, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.