Implementing AI Agents
Parabéns por concluir a Section 1! Você aprendeu a construir aplicações com chatbots, RAG, function calling, MCP e guardrails. Agora, na Section 2, o foco muda: em vez de um chatbot que responde a perguntas, você vai construir agentes autônomos que tomam decisões, invocam tools e colaboram em workflows.
Este capítulo cobre o Step 01 da Section 2, onde você constrói o primeiro agente autônomo usando o módulo quarkus-langchain4j-agentic.
AI Services vs. AI Agents
Antes de mergulhar no código, entenda a diferença entre os dois padrões:
| Aspecto | AI Services (Section 1) | AI Agents (Section 2) |
|---|---|---|
| Propósito | Responder perguntas do usuário | Executar tarefas autônomas |
| Interação | Reativa (responde a prompts) | Reativa e proativa (toma ações) |
| Uso de tools | Pode chamar tools quando necessário | Chama tools para atingir objetivos |
| Workflows | Interações single-agent | Colaboração multi-agente |
| Anotação | @SystemMessage + @UserMessage | Um método com @Agent |
| Casos de uso | Chatbots, Q&A, geração de conteúdo | Automação, decisão, orquestração |
Cenário: gestão de frota
A Miles of Smiles precisa gerenciar a frota de veículos. O fluxo de negócio:
- Devolução: a equipe de locação registra feedback sobre o estado do carro
- Limpeza: o sistema decide automaticamente se o carro precisa de limpeza
- Retorno da limpeza: após limpeza, o carro volta ao pool disponível
- Disponibilidade: carros limpos e sem problemas ficam disponíveis para locação
Seu trabalho: construir um agente de limpeza que analisa o feedback e decide inteligentemente se deve solicitar limpeza.
Arquitetura da aplicação
A aplicação tem quatro componentes principais:
CarManagementResource → REST API (endpoint de devolução)
CarManagementService → lógica de negócio + invocação do agente
CleaningAgent → agente AI que decide se limpeza é necessária
CleaningTool → tool que solicita serviços de limpeza
Dependência
No pom.xml, a dependência que habilita agentes:
<dependency>
<groupId>io.quarkiverse.langchain4j</groupId>
<artifactId>quarkus-langchain4j-agentic</artifactId>
</dependency>
Componente 1: REST API
O CarManagementResource expõe o endpoint de devolução de carros:
@Path("/car-management")
public class CarManagementResource {
@Inject
CarManagementService carManagementService;
@POST
@Path("/return/{carNumber}")
public Response processReturn(Integer carNumber, @RestQuery String feedback) {
try {
String result = carManagementService.processCarReturn(
carNumber, feedback != null ? feedback : "");
return Response.ok(result).build();
} catch (Exception e) {
Log.error(e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Error processing return: " + e.getMessage())
.build();
}
}
}
Componente 2: lógica de negócio
O CarManagementService orquestra a devolução e invoca o agente:
@Inject
CleaningAgent cleaningAgent;
@Transactional
public String processCarReturn(Integer carNumber, String feedback) {
CarInfo carInfo = CarInfo.findById(carNumber);
if (carInfo == null) {
return "Car not found with number: " + carNumber;
}
String result = cleaningAgent.processCleaning(carInfo, carNumber, feedback);
if (result.toUpperCase().contains("CLEANING_NOT_REQUIRED")) {
carInfo.status = CarStatus.AVAILABLE;
carInfo.persist();
}
return result;
}
Se a resposta contém CLEANING_NOT_REQUIRED, o carro volta para AVAILABLE. Caso contrário, a tool já atualizou o status para AT_CLEANING.
Componente 3: CleaningAgent
Aqui está o coração do agente, uma interface com anotações especiais:
public interface CleaningAgent {
@SystemMessage("""
You handle intake for the cleaning department of a car rental company.
It is your job to submit a request to the provided requestCleaning function to take action based on the provided feedback.
Be specific about what services are needed.
If no cleaning is needed based on the feedback, respond with "CLEANING_NOT_REQUIRED".
""")
@UserMessage("""
Car Information:
Make: {carInfo.make}
Model: {carInfo.model}
Year: {carInfo.year}
Car Number: {carNumber}
Feedback: {feedback}
""")
@Agent("Cleaning specialist. Determines what cleaning services are needed.")
@ToolBox(CleaningTool.class)
String processCleaning(
CarInfo carInfo,
Integer carNumber,
String feedback);
}
Anotações explicadas
| Anotação | Papel |
|---|---|
@SystemMessage | Define o papel e a lógica de decisão do agente |
@UserMessage | Fornece contexto por requisição (dados do carro + feedback) |
@Agent | Marca o método como ponto de entrada do agente (apenas um por interface) |
@ToolBox | Atribui tools que o agente pode invocar |
Dica: a
@SystemMessageé crítica. Ela diz ao agente quem ele é, o que fazer, quando agir e como responder (CLEANING_NOT_REQUIREDou chamar a tool).
Atenção: diferente de um AI Service, um agente tem apenas um método anotado com
@Agent. Ele é projetado para ação autônoma, não para conversação contínua.
Não há implementação manual; o LangChain4j gera o código que envia system + user messages ao LLM, invoca a tool se necessário e retorna a resposta.
Componente 4: CleaningTool
Tools em agentes funcionam como na Section 1: métodos anotados com @Tool:
@ApplicationScoped
public class CleaningTool {
@Tool("Requests a cleaning with the specified options")
@Transactional
public String requestCleaning(
Integer carNumber,
String carMake,
String carModel,
Integer carYear,
boolean exteriorWash,
boolean interiorCleaning,
boolean detailing,
boolean waxing,
String requestText) {
CarInfo carInfo = CarInfo.findById(carNumber);
if (carInfo != null) {
carInfo.status = CarStatus.AT_CLEANING;
carInfo.persist();
}
var result = generateCleaningSummary(carNumber, carMake, carModel, carYear,
exteriorWash, interiorCleaning, detailing,
waxing, requestText);
System.out.println("🚗 CleaningTool result: " + result);
return result;
}
}
Como tudo funciona junto
Cenário 1: carro precisa de limpeza
sequenceDiagram
participant User as Usuário
participant REST as CarManagementResource
participant Service as CarManagementService
participant Agent as CleaningAgent
participant LLM as OpenAI
participant Tool as CleaningTool
User->>REST: POST /return/6 feedback dog hair
REST->>Service: processCarReturn(6, feedback)
Service->>Agent: processCleaning(...)
Agent->>LLM: system + user message
LLM->>LLM: analisa feedback
LLM->>Tool: requestCleaning(interiorCleaning=true)
Tool->>Tool: status AT_CLEANING
Tool-->>LLM: resumo da limpeza
LLM-->>Agent: resposta
Agent-->>Service: resultado
Service-->>REST: 200 OK
REST-->>User: confirmação
Feedback: "Car has dog hair all over the back seat" → agente reconhece necessidade de limpeza interior → chama CleaningTool → carro vai para AT_CLEANING.
Cenário 2: carro está limpo
sequenceDiagram
participant User as Usuário
participant REST as CarManagementResource
participant Service as CarManagementService
participant Agent as CleaningAgent
participant LLM as OpenAI
User->>REST: POST /return/3 feedback Car looks good
REST->>Service: processCarReturn(3, feedback)
Service->>Agent: processCleaning(...)
Agent->>LLM: system + user message
LLM->>LLM: analisa feedback
LLM-->>Agent: CLEANING_NOT_REQUIRED
Agent-->>Service: CLEANING_NOT_REQUIRED
Service->>Service: status AVAILABLE
Service-->>REST: 200 OK
REST-->>User: confirmação
Feedback: "Car looks good" → agente decide que não precisa de limpeza → retorna CLEANING_NOT_REQUIRED sem chamar a tool → carro volta para AVAILABLE. Isso demonstra raciocínio autônomo.
Testando a aplicação
cd section-2/step-01
./mvnw quarkus:dev
Abra http://localhost:8080. A UI mostra a frota e um formulário de feedback para carros alugados ou em limpeza.
| Teste | Feedback | Resultado esperado |
|---|---|---|
| 1 | Car has dog hair all over the back seat | Status → AT_CLEANING, log da CleaningTool |
| 2 | Car looks good | Status → AVAILABLE, resposta CLEANING_NOT_REQUIRED |
O que aprendemos
- AI Agents tomam decisões autônomas e executam ações via tools
- A anotação
@Agentmarca o ponto de entrada (um método por interface) - Agents reutilizam
@SystemMessage,@UserMessagee@ToolBoxda Section 1 - O agente pode decidir não agir (sem chamar a tool) com base no contexto
- A integração com CDI é transparente: injete o agente como qualquer bean Quarkus
Experimentos sugeridos
- Edge cases: teste
"The trunk smells like fish","Minor scratch on the bumper","Spotless condition". O agente chama a tool? - System message: altere para um especialista mais exigente que pede detail completo a menos que o carro esteja perfeito
- Novo parâmetro: adicione
tireCleaningaoCleaningToole veja se o agente aprende a usá-lo.
Troubleshooting
| Problema | Solução |
|---|---|
OPENAI_API_KEY not set | Exporte a variável e reinicie a aplicação |
| Tool nunca é chamada | Revise a @SystemMessage: inclua instruções claras sobre quando usar a tool |
| Tool sempre é chamada | Adicione exemplos explícitos de quando retornar CLEANING_NOT_REQUIRED |
| Tool não registrada | Verifique @Tool na tool e referência em @ToolBox |
Tarefa para casa
Objetivo: executar o Step 01 da Section 2 e validar os dois cenários de devolução de carro.
Referência: Quarkus LangChain4j Workshop, Section 2, Step 01.
O que fazer
- Navegar até
section-2/step-01e subir com./mvnw quarkus:dev. - Abrir
http://localhost:8080e localizar um carro alugado na grade. - Testar devolução com feedback de limpeza (
Car has dog hair all over the back seat) e verificar statusAT_CLEANINGe log da tool. - Testar devolução com carro limpo (
Car looks good) e verificar statusAVAILABLE. - Observar nos logs a decisão do agente em cada caso.
Referência
Quarkus LangChain4j Workshop, Section 2, Step 01

CC BY 4.0 DEED