Engenharia de Agentes de IA com LangChain4j, Spring Boot e Gemini


1. O Surgimento do Agente de Contexto


    O projeto kendo-assistant vai além do paradigma de um chatbot convencional baseado em regras.  Ele opera como um Agente de IA, um sistema que utiliza um LLM (Large Language Model) como motor de raciocínio para interagir com o mundo real.

    Ao integrar RAG (Retrieval-Augmented Generation) e Function Calling, o sistema resolve o problema das "alucinações" dos modelos, ancorando as respostas em dados factuais extraídos de fontes relacionais e documentos técnicos (informação não estruturada). Com base nesses requisitos, foi desenvolvido um Chat Assistente capaz de responder a dúvidas técnicas referentes ao Kendo e, também, levantar dados sobre treinos e alunos de um Dojo — ou seja, um Sensei Virtual.


 2. Arquitetura e Orquestração de Agentes


    A solução utiliza o padrão de Orquestração de Agentes, no qual o LangChain4j fornece a infraestrutura necessária para que a coordenação ocorra. Ele atua como o "meio de campo", sendo responsável por:

Levar a pergunta à LLM;

Traduzir a intenção do modelo ("Quero chamar a função X") em uma execução real de código Java;

Formatar o histórico para que a LLM não perca o contexto.

Diferente de uma chamada de API simples, o orquestrador permite que o modelo realize múltiplas iterações de "Pensamento → Ação → Observação" antes de entregar a resposta final ao usuário.

 O diagrama de sequência a seguir detalha o fluxo de comunicação entre os componentes do sistema:



 2.1 Padrão de Orquestração de Agentes


    É um modelo de arquitetura de software para sistemas de IA onde um componente central (o Orquestrador) coordena o fluxo de trabalho entre múltiplos agentes especializados ou ferramentas para resolver tarefas complexas que um único modelo não conseguiria realizar sozinho. Em vez de apenas enviar uma pergunta e receber uma resposta, a orquestração envolve planejamento, delegação e monitoramento.


 2.1.1 Componentes Fundamentais

1. O Orquestrador (Cérebro): Geralmente um LLM de alta capacidade que recebe a entrada do usuário, analisa o objetivo e decide qual "agente" ou "ferramenta" deve ser acionado.

2. Agentes Especializados: Subsistemas focados em tarefas únicas (ex.: um agente para consulta em banco de dados SQL, outro para pesquisa na web, outro para geração de código).

3. Malha de Controle: Define a lógica de como a informação passa de um agente para outro (sequencial, paralela ou baseada em condições).


2.1.2 Como funciona na prática?

    Imagine que você peça ao sistema para "Analisar as faltas do aluno X e sugerir um treino corretivo". O processo de orquestração seguiria estes passos:

1. Decomposição: O orquestrador entende que precisa de dados do banco de dados (frequência) e de conhecimento técnico (Kendo).

2. Execução de Ferramentas:

Ele aciona uma Function para buscar no banco de dados relacional o histórico do aluno.

Ele utiliza RAG para consultar documentos técnicos sobre fundamentos de Kendo.

3. Síntese: O orquestrador recebe os dados brutos das duas fontes, cruza as informações e gera a resposta final estruturada.


2.1.3 Vantagens da Orquestração

Redução de Alucinações: O sistema não "tenta adivinhar" fatos; ele é forçado a buscar dados reais antes de responder.

Modularidade: É possível adicionar novas capacidades (como integração com calendários ou APIs externas) sem a necessidade de retreinar o modelo principal.

Escalabilidade: Permite que tarefas complexas sejam quebradas em microtarefas gerenciáveis.


2.2 LangChain4j


    O LangChain4j atua como o elo entre o mundo não estruturado da IA e o mundo tipado do Java. Ele abstrai o gerenciamento de prompts e a serialização de dados para o modelo, permitindo que o código seja escrito de forma que o fornecedor da IA se torne intercambiável. Se hoje você utiliza o Gemini, mas amanhã desejar testar um modelo local via Ollama, a mudança no código será mínima.

    O framework possui suporte nativo para RAG, oferecendo ferramentas integradas para ler documentos técnicos, transformá-los em vetores (embeddings) e armazená-los em bancos vetoriais. Além disso, disponibiliza o recurso de Tools (Function Calling): funcionalidade que permite à LLM "chamar" métodos Java no seu backend para consultar dados dinâmicos diretamente no banco de dados.


 2.3 O Mecanismo RAG e Bases Vetoriais


    Para que a aplicação compreenda a etiqueta do Kendo (Reishiki), suas regras, exercícios básicos e critérios de avaliação em exames, utiliza-se a técnica de RAG. Este processo supera a limitação de conhecimento "congelado" do modelo original por meio de uma base vetorial, operando em três etapas principais:

Embeddings: Manuais em PDF são segmentados em fragmentos (chunks). Cada fragmento é processado por um modelo de embedding que converte o texto em vetores numéricos.

Busca Semântica: Diferente de uma busca por palavras-chave (como o SQL LIKE), a base vetorial permite encontrar informações pelo significado. Quando o usuário faz uma pergunta, o sistema calcula a similaridade de cosseno (https://brains.dev/2024/embeddings-medidas-de-distancia-e-similaridade/) entre o vetor da pergunta e os vetores armazenados.

Injeção de Contexto (Context Injection): O trecho mais relevante é recuperado e injetado no prompt do sistema. Isso garante que a resposta da LLM seja fundamentada em documentos oficiais, eliminando respostas inventadas.

 

 3.  Entendendo a Aplicação


1. Configuração inicial: Dependências do LangChain4j no pom.xml

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-google-ai-gemini-spring-boot-starter</artifactId>
<version>1.7.1-beta14</version>
</dependency>

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.7.1-beta14</version>
</dependency>

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
<version>1.7.1-beta14</version>
</dependency>

 2. Configuração da LLM no arquivo application.yaml:


langchain4j:
bitmap:
embedding-store:
type: in-memory
google-ai-gemini:
chat-model:
api-key: {{SUA_CHAVE_AQUI}}
model-name: gemini-2.5-flash
embedding-model:
api-key: {{SUA_CHAVE_AQUI}}
model-name: gemini-embedding-001

3. No projeto Java: Interface para o serviço de IA e anote com @AiService:

@AiService
public interface KendoAssistant {

@SystemMessage(">>> PROMPT <<<<")
String chat(String message);
}


    Durante a inicialização, o starter do LangChain4j realiza um scan no classpath para localizar interfaces que utilizam a anotação @AiService. O framework gera automaticamente as implementações para esses serviços, utilizando os componentes configurados, e as expõe como Spring Beans, facilitando a injeção de dependências em toda a aplicação.

4. A classe AssistantConfig

@Slf4j
@Configuration
public class AssistantConfig {

private static final int WAIT_TIME = 60000;
private static final String KENDO_VECTORS_JSON = "kendo_vectors.json";
private static final Path VECTOR_STORE_PATH = Paths.get(KENDO_VECTORS_JSON);

@Bean
public EmbeddingStore<TextSegment> embeddingStore() {

if (Files.exists(VECTOR_STORE_PATH)) {

log.info("### Carregando vetores do arquivo local... (Economizando Cota) ###");
return InMemoryEmbeddingStore.fromFile(VECTOR_STORE_PATH);
}

return new InMemoryEmbeddingStore<>();
}

@Bean
public ContentRetriever contentRetriever(
EmbeddingStore<TextSegment> embeddingStore,
EmbeddingModel embeddingModel) throws IOException {

if (!Files.exists(VECTOR_STORE_PATH)) {
log.info("### Arquivo de vetores não encontrado.
                                        Iniciando Ingestão de PDFs... ###");

EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.documentSplitter(DocumentSplitters.recursive(1500, 200))
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();

getDocuments().forEach(doc -> ingestFile(ingestor, doc));

((InMemoryEmbeddingStore<TextSegment>) embeddingStore)
                                        .serializeToFile(VECTOR_STORE_PATH);
log.info("### Vetores salvos com sucesso em: "
                            + VECTOR_STORE_PATH.toAbsolutePath() + " ###");
}

return EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(3)
.minScore(0.6)
.build();
}

@Bean
public KendoAssistant assistant(ChatModel model, KendoTools kendoTools,
ContentRetriever contentRetriever) {
return AiServices.builder(KendoAssistant.class)
.chatModel(model)
.tools(kendoTools)
.contentRetriever(contentRetriever)
.build();
}

private List<Document> getDocuments() throws IOException {
PathMatchingResourcePatternResolver resolver =
                                        new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath:fontes/*.pdf");

List<Document> documents = new ArrayList<>();
ApachePdfBoxDocumentParser pdfParser = new ApachePdfBoxDocumentParser();

for (Resource resource : resources) {
readFile(documents, pdfParser, resource);
}

return documents;
}

private void readFile(List<Document> documents, ApachePdfBoxDocumentParser pdfParser, Resource resource)
throws IOException {
Path tempFile = Files.createTempFile("rag-kendo-", ".pdf");
try (InputStream is = resource.getInputStream()) {
Files.copy(is, tempFile, StandardCopyOption.REPLACE_EXISTING);
Document doc = FileSystemDocumentLoader.loadDocument(tempFile, pdfParser);
documents.add(doc);
} finally {
Files.deleteIfExists(tempFile);
}
}

private void ingestFile(EmbeddingStoreIngestor ingestor, Document doc) {
log.info("Indexando PDF: " + doc.metadata().getString("file_name"));
ingestor.ingest(doc);
try {
Thread.sleep(WAIT_TIME);
} catch (InterruptedException _) {
Thread.currentThread().interrupt();
}
}
}

    A classe AssistantConfig é um componente de configuração do SpringBoot que utiliza o framework LangChain4j para instanciar e gerenciar um agente de inteligência artificial. Ela define a lógica de ingestão de documentos PDF (os PDFs estão armazenados dentro da pasta resources do projeto), converte esses dados em vetores numéricos e os armazena em um arquivo local para otimizar o tempo de resposta e reduzir custos de processamento. Ao centralizar a integração entre o modelo Gemini, a base de dados vetorial (RAG) e as funções de sistema (Tools), a classe automatiza a criação do serviço de IA e o disponibiliza para injeção de dependência em toda a aplicação.


5. A classe KendoTools

@Component
@Slf4j
public class KendoTools {

private TreinoService treinoService;
private AlunoService alunoService;

public KendoTools(TreinoService treinoService, AlunoService alunoService) {
this.treinoService = treinoService;
this.alunoService = alunoService;
}

@Tool("Lista todos os alunos cadastrados no Dojo para visão geral e
            comparação de graduações. Use essa tool também qual quiser
                saber qual o aluno mais vai a treinos")
public List<AlunoDTO> getAlunos() {
return alunoService.buscarTodos();
}

    ...

}

    A classe KendoTools atua como uma interface de integração que expõe métodos Java específicos do backend para serem executados pela inteligência artificial através da funcionalidade de Function Calling. Utilizando a anotação @Tool, ela disponibiliza capacidades de consulta ao banco de dados relacional, permitindo que o modelo de linguagem acesse informações atualizadas sobre o cadastro de alunos, histórico de frequências e períodos de treino. Ao receber uma pergunta do usuário que demande dados dinâmicos, o orquestrador aciona automaticamente esses métodos, recupera os objetos de dados (DTOs) e os utiliza para compor uma resposta fundamentada em informações reais do sistema.



6. A classe Controller


@Controller
@RequestMapping("/kendo-assistant")
public class ChatController {

private final KendoAssistant assistant;
private List<ChatMessage> history = new ArrayList<>();

public ChatController(KendoAssistant assistant) {
this.assistant = assistant;
}

@GetMapping
public String index(Model model) {
model.addAttribute("history", history);
return "chat";
}

@PostMapping("/send")
public String sendMessage(@RequestParam String message) {
history.add(new ChatMessage("Você", message));

String response = assistant.chat(message);

history.add(new ChatMessage("Sensei Digital", response));

return "redirect:/chat";
}

public record ChatMessage(String sender, String text) {}
}


    A classe ChatController é um componente Spring MVC responsável por gerenciar as requisições HTTP e a interface de comunicação entre o usuário e o sistema. Ela expõe dois endpoints principais: um método GET para renderizar a página de chat e um método POST que recebe as mensagens enviadas pelo usuário, as encaminha para o serviço KendoAssistant e armazena as respostas geradas pela IA em uma lista de mensagens. Ao final de cada interação, o controlador atualiza o histórico e redireciona o usuário para a interface visual, mantendo a continuidade do diálogo no navegador.


7. Testando


Exemplo 1: Consulta onde é necessária a utilização da RAG para formulação da resposta.




Exemplo 2: Consulta onde é necessária consulta a Base de Dados para formulação da resposta.




 5.  Código disponível no Github




Postagens mais visitadas deste blog

Micro Profile JavaEE com Wildfly Swarm

Mapeando Enuns com JPA.