Spring AI em Aplicações Java
tool calling e MCP, com exemplos e exercícios
Use o menu lateral fixo para navegar entre a capa, o índice completo e cada capítulo — o menu permanece visível ao rolar a página. Os números em verde no menu indicam a ordem didática (00 a 08). Configure chaves de API com variáveis de ambiente e consulte a documentação oficial do Spring AI para versões atuais.
Parte 0 — Contexto: inteligência artificial aplicada e engenharia de prompt
Antes de mergulhar no código, vale alinhar o que é usar IA em um backend e como formular solicitações ao modelo para obter respostas úteis e previsíveis.
0.1 O que muda quando você coloca IA no Spring?
Em muitas aplicações Java, a regra de negócio está no seu código e nos dados que você controla. Com modelos de linguagem (LLMs), parte da “resposta” vem de um serviço externo que você chama por API: você envia texto, o modelo devolve texto (ou outro formato).
Fluxo típico:
- Entrada — usuário ou sistema envia uma pergunta ou comando.
- Seu backend monta a mensagem (prompt), opcionalmente com instruções de sistema.
- Provedor de modelo processa e devolve a saída.
- Seu backend valida, formata e devolve a resposta ao chamador (por exemplo, app ou API).
O Spring AI existe para não acoplar você a detalhes de HTTP e JSON de cada fornecedor e para permitir trocar modelo ou provedor com menos atrito. Visão geral: Spring AI Reference — Introduction.
0.2 IA generativa em uma frase
IA generativa aqui significa: gerar texto novo (e, em outros capítulos, imagem ou áudio) a partir de uma entrada, em vez de só classificar ou pontuar dados com regras fixas. O modelo foi treinado em grandes volumes de texto; na inferência (quando você chama a API), ele prevê a próxima sequência de tokens — por isso respostas podem variar e, às vezes, confundir ou inventar se o contexto for fraco.
Exemplo mental
- Regra fixa: “Se o chamado está aberto há mais de 48h, escalar.” → sempre determinístico.
- Generativo: “Explique em duas frases o que significa escalar o chamado 742.” → pode variar na redação; você controla com prompt e parâmetros.
0.3 O que é o “modelo” e o que é “prompt”?
| Termo | Em linguagem simples |
|---|---|
| Modelo | O “motor” que recebe a entrada e gera a saída (ex.: GPT-4.x, o modelo que você configurar no Spring AI). |
| Prompt | O texto que você envia para orientar a tarefa: pergunta, instrução, formato desejado. |
| Mensagem de sistema | Instruções estáveis: papel do assistente, tom, restrições (“não invente dados”). |
| Mensagem do usuário | A solicitação concreta da vez. |
Mini-exemplo
Sistema: Você é um assistente de suporte técnico. Responda em português, em no máximo 3 frases.
Usuário: Como redefino a senha?
O Capítulo 1 implementa isso com ChatClient; o Capítulo 2 aprofunda parâmetros e testes no Playground.
0.4 Engenharia de prompt (antes do código)
Engenharia de prompt é o desenho cuidadoso do que você envia ao modelo para obter:
- resposta no formato que seu sistema consome (JSON, lista, uma palavra só);
- resposta alinhada ao domínio (suporte, escola, financeiro);
- menor risco de invenção ou fuga de assunto.
Princípios práticos:
- Seja específico — “Devolva apenas uma categoria: A, B ou C” é melhor que “classifique”.
- Diga o que não pode — “Se não houver dados no contexto, diga que não sabe.”
- Mostre o formato — um exemplo de saída desejada reduz ambiguidade.
- Combine com parâmetros — temperatura baixa para tarefas mais determinísticas (ver Cap. 2).
Antes / depois (didático)
| Menos adequado | Mais adequado para backend |
|---|---|
| “Fale sobre o assunto.” | “Liste 3 benefícios em bullet points, máximo 50 palavras por item.” |
| “Qual a categoria?” | “Responda com uma única etiqueta: Acesso, Bug, Faturamento ou Outros.” |
O Capítulo 2 liga isso à API da OpenAI e ao Java com Spring AI.
0.5 Limites e responsabilidade
- Modelos não têm acesso automático ao seu banco de dados: para isso existem tools (Cap. 6), MCP (Cap. 7) e RAG (Cap. 8).
- Chaves de API são segredos: use variáveis de ambiente (Cap. 1).
- Custos e tokens dependem do modelo e do tamanho das mensagens (Cap. 3).
Quando estiver confortável com esses conceitos, siga para o Capítulo 1 — Primeiros passos com Spring AI.
Capítulo 1 — Primeiros passos com Spring AI: entendendo a integração com IA e criando a primeira aplicação
Objetivo do capítulo
Neste capítulo, você vai aprender:
- o que é o Spring AI e por que ele facilita o desenvolvimento com IA;
- quais conceitos básicos você precisa entender antes de escrever código;
- como criar um projeto Spring Boot preparado para usar IA;
- como fazer a primeira chamada a um modelo com
ChatClient; - o que é uma API Key e por que ela precisa ser protegida;
- como configurar a chave com segurança usando variável de ambiente;
- como testar a aplicação localmente.
Ao final, a ideia é simples: você deve conseguir ler o capítulo, entender o fluxo e repetir o exemplo sozinho.
1.1 Antes do código: o que significa usar IA dentro de uma aplicação?
Quando usamos uma biblioteca comum no Java, grande parte do processamento acontece dentro do próprio projeto. Mas, no caso de modelos de IA, normalmente sua aplicação conversa com um serviço externo. Em vez de “rodar tudo localmente”, o sistema envia uma mensagem para um modelo, aguarda o processamento e recebe uma resposta.
Na prática, o fluxo é este:
- o usuário faz uma ação;
- o backend monta uma solicitação;
- essa solicitação é enviada ao modelo;
- o modelo processa a entrada;
- a resposta volta para a aplicação;
- o sistema entrega o resultado para o usuário.
Esse fluxo parece simples, mas envolve alguns pontos importantes:
- como montar a mensagem correta;
- como autenticar a aplicação;
- como organizar o código;
- como controlar o comportamento da resposta;
- como evitar acoplamento excessivo a um provedor específico.
É exatamente aqui que o Spring AI entra.
1.2 O que é o Spring AI?
O Spring AI é um framework voltado para engenharia de aplicações com inteligência artificial. O objetivo dele é aplicar no universo de IA princípios que já são comuns no ecossistema Spring, como modularidade, portabilidade e uso de componentes simples para montar a aplicação.
Explicando de forma prática
Sem uma abstração como o Spring AI, você poderia precisar:
- montar requisições HTTP manualmente;
- lidar diretamente com autenticação;
- configurar cabeçalhos e corpo JSON;
- tratar respostas brutas;
- acoplar sua aplicação a detalhes específicos de um provedor.
Com Spring AI, parte dessa complexidade já vem organizada em APIs mais amigáveis.
O que isso traz de vantagem?
O Spring AI oferece abstrações para trabalhar com recursos de IA e permite trocar componentes com mudanças menores no código, além de suportar APIs portáveis entre provedores para chat, embeddings e text-to-image, com opções síncronas e streaming.
Em termos simples, ele ajuda você a escrever um código mais limpo, mais fácil de entender e mais alinhado com o estilo de desenvolvimento do Spring.
1.3 O que o Spring AI cobre além de chat?
Embora muita gente comece usando apenas geração de texto, o ecossistema do Spring AI é mais amplo.
Ele cobre recursos como:
- chat completion;
- embeddings;
- text-to-image;
- transcription;
- text-to-speech;
- moderation.
O que cada um desses recursos significa?
Chat completion É o recurso mais comum. Você envia uma mensagem (ou uma conversa) e o modelo gera uma resposta textual. É o que usamos para criar assistentes, responder perguntas, gerar conteúdo e muito mais.
Entrada: "Explique o que é REST em duas frases."
Saída: "REST é um estilo arquitetural para APIs web. Ele usa HTTP e opera sobre recursos identificados por URLs."
Embeddings Em vez de gerar texto, o modelo transforma um texto em um vetor numérico (uma lista de números). Esse vetor representa o significado do texto e é usado para comparar similaridade entre frases, fazer busca semântica e alimentar bancos vetoriais (como no RAG, visto no Capítulo 8).
Entrada: "Como faço matrícula?"
Saída: [0.021, -0.134, 0.876, ...] ← representação vetorial do significado
Text-to-image
O modelo recebe uma descrição em texto e gera uma imagem. Útil para criação de banners, ilustrações e protótipos visuais. O Capítulo 5 mostra como usar isso com ImageModel no Spring AI.
Entrada: "Um robô lendo um livro de Java, estilo cartoon"
Saída: URL de uma imagem gerada
Transcription O modelo recebe um arquivo de áudio e converte o conteúdo falado em texto escrito. Útil para transcrever reuniões, aulas, atendimentos e gravações.
Entrada: arquivo .mp3 com fala
Saída: "Bom dia, gostaria de abrir uma conta..."
Text-to-speech O oposto da transcrição: o modelo recebe texto e gera áudio com voz sintetizada. Útil para leitores de acessibilidade, narrações automáticas e respostas por voz.
Entrada: "Sua solicitação foi registrada com sucesso."
Saída: arquivo de áudio com a frase narrada
Moderation O modelo analisa um texto e verifica se ele contém conteúdo inadequado (violência, discurso de ódio, conteúdo explícito etc.). Útil para validar entradas de usuários antes de processar ou armazenar.
Entrada: "Texto enviado pelo usuário"
Saída: { "flagged": false, "categories": { "hate": false, "violence": false } }
Visão geral dos recursos do Spring AI
Para este início de curso, vamos focar em chat, porque é o caminho mais fácil para entender a integração. Mas é importante você saber, desde já, que o framework não se limita a perguntas e respostas.
1.4 Conceitos que você precisa entender antes de programar
Antes de partir para o projeto, existem alguns conceitos fundamentais.
Modelo
Um modelo de IA é o componente que recebe uma entrada e gera uma saída.
Para o nosso contexto, pense no modelo como o “motor” que recebe a mensagem e produz a resposta.
Prompt
Prompt é a instrução enviada ao modelo. É o texto que orienta a resposta.
Exemplo:
Sugira 5 títulos curtos para posts de blog sobre Java e Spring Boot, em português.
O modelo vai interpretar essa instrução e produzir uma saída com base nela.
Mensagem de sistema
A mensagem de sistema serve para orientar o comportamento da conversa.
Exemplo:
Você é editor técnico: títulos objetivos, sem clickbait, para público desenvolvedor.
Isso ajuda o modelo a entender “como” ele deve se comportar.
Mensagem do usuário
É a solicitação direta enviada pela pessoa usuária.
Exemplo:
Inclua a palavra “tutorial” em pelo menos dois títulos da lista.
Resposta
É o conteúdo gerado pelo modelo com base nas instruções recebidas.
Opções do modelo
São configurações que afetam a chamada, como modelo usado, temperatura e outras preferências de geração.
1.5 Por que o ChatClient é importante?
O ChatClient é uma das peças centrais do Spring AI.
Em linguagem simples
Você pode pensar no ChatClient como o “canal de conversa” entre sua aplicação e o modelo.
Em vez de montar uma chamada complexa manualmente, você usa uma API fluente que organiza isso de forma mais natural.
1.6 Criando o projeto
Agora que a base conceitual está mais clara, podemos ir para a implementação.
Estrutura sugerida
- Build tool: Maven
- Language: Java
- Packaging: Jar
- Java: 21
- Dependências principais:
- Spring Web
- Spring AI com OpenAI
- DevTools
Exemplo de dependências
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
Esse conjunto já entrega uma base suficiente para criar endpoints e chamar um modelo.
1.7 Entendendo a classe principal da aplicação
Todo projeto Spring Boot possui uma classe principal com @SpringBootApplication. Essa classe é o ponto de entrada do sistema.
Exemplo
package br.com.seuprojeto.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AplicacaoApplication {
public static void main(String[] args) {
SpringApplication.run(AplicacaoApplication.class, args);
}
}
O que acontece quando ela é executada?
Quando esse main() roda:
- o Spring Boot inicia a aplicação;
- carrega as configurações;
- identifica os componentes anotados;
- prepara os beans;
- tenta montar a integração com IA;
- sobe o servidor da aplicação.
Dica: se a aplicação não subir e aparecer
BeanCreationExceptionouIllegalStateException, verifique primeiro se a variável de ambiente com a API Key está definida. O Spring AI tenta configurar o cliente do provedor durante o boot e falha se a chave estiver ausente.
1.8 Criando a primeira funcionalidade com IA
Um primeiro exemplo simples é pedir sugestões de títulos para conteúdo técnico via um endpoint REST.
O objetivo desse primeiro exemplo
Queremos que:
- o usuário acesse uma rota;
- o backend envie um prompt ao modelo;
- o modelo gere uma resposta textual;
- essa resposta volte ao navegador.
1.9 Criando o controller
package br.com.seuprojeto.app.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/gerador")
public class GeradorController {
}
O que esse código significa?
@RestController
Diz ao Spring que a classe responderá requisições HTTP e retornará dados diretamente no corpo da resposta.
@RequestMapping("/gerador")
Define a rota base que será usada.
1.10 Inserindo um método simples
Antes de falar com o modelo, podemos testar a rota com um retorno simples.
@GetMapping
public String gerarTitulos() {
return "Funcionando";
}
Isso ajuda Você a validar o primeiro passo: a aplicação está subindo e o endpoint está respondendo.
1.11 Fazendo a primeira chamada com ChatClient
Exemplo completo
package br.com.seuprojeto.app.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/gerador")
public class GeradorController {
private final ChatClient chatClient;
public GeradorController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping
public String gerarTitulos() {
String pergunta = "Sugira 5 títulos curtos para posts de blog sobre Java e Spring Boot, em português.";
return chatClient.prompt()
.user(pergunta)
.call()
.content();
}
}
1.12 Explicando o código linha por linha
private final ChatClient chatClient;
Aqui declaramos o cliente que será usado para conversar com o modelo.
public GeradorController(ChatClient.Builder chatClientBuilder)
O Spring injeta um Builder já configurado.
this.chatClient = chatClientBuilder.build();
O builder gera a instância final do cliente.
String pergunta = "Sugira 5 títulos curtos para posts de blog sobre Java e Spring Boot, em português.";
Esse é o prompt do usuário.
chatClient.prompt()
Inicia a montagem da chamada.
.user(pergunta)
Define a mensagem enviada pelo usuário.
.call()
Executa a chamada ao modelo.
.content()
Extrai o texto da resposta.
Visão do encadeamento (fluent API)
Cada método retorna o próximo objeto da cadeia. Você lê a chamada de cima para baixo como se fosse uma frase: "prepare um prompt, com esta mensagem de usuário, chame o modelo e me dê o conteúdo."
1.13 O que é uma API Key?
Para que sua aplicação consiga usar um serviço externo de IA, ela precisa se autenticar.
Em linguagem simples
A API Key funciona como uma credencial de acesso da aplicação.
Ela serve para:
- identificar quem está fazendo a chamada;
- autorizar o uso da API;
- permitir controle de consumo;
- associar o uso à conta correta.
1.14 Por que essa chave precisa ser protegida?
A chave de API é sensível. Se ela for exposta, outra pessoa pode usar a API como se fosse a sua aplicação.
Isso pode causar:
- consumo indevido;
- custos inesperados;
- necessidade de revogar a chave;
- impacto operacional no sistema.
Por isso, não é boa prática gravar a chave diretamente no código-fonte.
1.15 Configurando a chave no Spring
Exemplo recomendado
spring.application.name=meu-projeto
spring.ai.openai.api-key=${OPENAI_API_KEY}
Essa abordagem evita expor a chave diretamente no projeto.
1.16 O que é uma variável de ambiente?
Uma variável de ambiente é um valor armazenado fora do código, no ambiente onde a aplicação está rodando.
Ela é muito usada para guardar:
- senhas;
- tokens;
- chaves de API;
- URLs;
- configurações que mudam de um ambiente para outro.
Vantagens
Mais segurança
A credencial não fica escrita dentro do código.
Mais flexibilidade
Você pode usar uma chave em desenvolvimento e outra em produção sem alterar a aplicação.
Mais organização
O mesmo código pode rodar em ambientes diferentes mudando apenas a configuração externa.
1.17 Exemplo do que não fazer
Errado
spring.ai.openai.api-key=sk-valor-real-da-chave
Certo
spring.ai.openai.api-key=${OPENAI_API_KEY}
1.18 Onde configurar essa variável?
Em desenvolvimento local, você pode configurar a variável na IDE ou no sistema operacional.
Em projetos reais, a variável também pode ser definida em:
- terminal;
- servidor;
- container Docker;
- Kubernetes;
- pipeline de CI/CD.
1.19 O que acontece se a chave não for configurada?
Se a chave não estiver disponível, o Spring AI não consegue completar a configuração do cliente da OpenAI.
Didaticamente, isso é importante porque mostra:
- a dependência está correta;
- o projeto reconheceu a integração;
- o problema não está no controller;
- o que falta é autenticação.
1.20 Testando a aplicação
Depois de configurar a variável de ambiente e subir a aplicação, você pode testar a rota no navegador.
Exemplo
GET http://localhost:8080/gerador
Se tudo estiver correto, a aplicação deve retornar uma resposta gerada pela IA.
1.21 Exemplo consolidado do primeiro projeto
application.properties
spring.application.name=meu-projeto
spring.ai.openai.api-key=${OPENAI_API_KEY}
server.port=8080
Classe principal
package br.com.seuprojeto.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AplicacaoApplication {
public static void main(String[] args) {
SpringApplication.run(AplicacaoApplication.class, args);
}
}
Controller
package br.com.seuprojeto.app.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/gerador")
public class GeradorController {
private final ChatClient chatClient;
public GeradorController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping
public String gerarTitulos() {
String prompt = """
Sugira 5 títulos curtos para posts de blog sobre Java e Spring Boot, em português.
Responda em português.
Retorne apenas a lista.
""";
return chatClient.prompt()
.user(prompt)
.call()
.content();
}
}
1.22 O que você precisa entender desse exemplo?
Mais importante do que decorar o código é entender o fluxo:
- o usuário chama
/gerador; - o controller recebe a requisição;
- o método monta o prompt;
- o
ChatClientenvia a solicitação ao modelo; - o modelo responde;
- o backend devolve o texto.
Quando esse fluxo fica claro, você deixa de apenas copiar e passa a realmente aplicar.
1.23 Erros comuns no começo
Erro 1: achar que basta adicionar a dependência
Não basta. A integração também precisa de configuração da chave.
Erro 2: colocar a API Key direto no código
Funciona tecnicamente, mas é inseguro.
Erro 3: não testar a rota antes da integração
Primeiro valide se o endpoint responde. Depois conecte a IA.
Erro 4: copiar código sem entender o papel de cada parte
O importante é saber o que cada linha faz.
Erro 5: achar que prompt é “qualquer string”
O prompt é a instrução que orienta o modelo. A forma como ele é escrito influencia o resultado.
1.24 Boas práticas desde o primeiro capítulo
- mantenha a chave fora do código-fonte;
- use variável de ambiente;
- teste a rota localmente;
- escreva prompts claros;
- comece com exemplos pequenos;
- entenda o fluxo antes de avançar para casos mais complexos.
1.25 Resumo do capítulo
Neste capítulo, você aprendeu a base necessária para começar com Spring AI.
Vimos que:
- o Spring AI existe para simplificar a integração entre aplicações e modelos de IA;
- o framework cobre mais do que chat, incluindo embeddings, imagem, áudio e outros recursos;
ChatClienté a principal porta de entrada para conversar com modelos de chat;- prompts são compostos por mensagens, especialmente de sistema e de usuário;
- a API Key é necessária para autenticar a aplicação com a OpenAI;
- a forma recomendada de configurar a chave é via variável de ambiente;
- o primeiro exemplo com controller e
ChatClientjá permite entender o fluxo completo da integração.
1.26 Exercícios propostos
1. Conceitual
Explique com suas palavras qual problema o Spring AI ajuda a resolver.
2. Conceitual
Qual a diferença entre mensagem de sistema e mensagem do usuário?
3. Prático
Crie um endpoint que peça ao modelo 3 nomes para uma escola de tecnologia.
4. Prático
Altere o prompt para que a resposta venha em português e em lista numerada.
5. Segurança
Explique por que a API Key não deve ser salva diretamente no arquivo de configuração com valor fixo.
Desafio extra
Crie um segundo endpoint GET /gerador/sobre que envie ao modelo um system fixo (“Você explica conceitos de Spring em linguagem simples”) e uma pergunta vinda de query string (?q=...). Mostre um exemplo de chamada no navegador e um exemplo de resposta.
1.27 Fechamento
Agora você já tem a base para começar a trabalhar com IA em uma aplicação Spring de maneira organizada.
Esse primeiro passo é essencial porque constrói três pilares:
- entendimento conceitual;
- implementação prática;
- segurança mínima da integração.
Com essa base pronta, o próximo capítulo pode avançar para um tema muito importante: como controlar melhor o comportamento da IA, explorando parâmetros, Playground e engenharia de prompt.
Capítulo 2 — Explorando a API da OpenAI: parâmetros, Playground, triagem de chamados e engenharia de prompt
Objetivo do capítulo
Neste capítulo, você vai aprender:
- por que nem toda resposta da IA vem no formato ideal para uma aplicação;
- como usar o Playground para testar ideias antes de programar;
- o que são parâmetros como temperatura e como eles afetam a resposta;
- como transportar uma configuração testada para o código Java com Spring AI;
- por que respostas não padronizadas podem atrapalhar a aplicação;
- como usar mensagens de sistema e prompt engineering para guiar melhor o modelo;
- como construir prompts mais claros, restritos e úteis para o backend.
Ao final, você deve ser capaz de olhar para uma funcionalidade com IA e pensar assim:
“Não basta funcionar. A resposta precisa vir do jeito certo para o sistema conseguir usar.”
2.1 Introdução
No capítulo anterior, fizemos a primeira integração entre a aplicação e o modelo de IA. O resultado foi importante: conseguimos enviar um prompt, chamar o modelo e receber uma resposta.
Mas, em aplicações reais, isso é só o começo.
Quando começamos a usar IA dentro de um sistema, logo aparece um novo desafio:
a resposta pode estar correta, mas ainda assim vir em um formato ruim para a aplicação.
Por exemplo, imagine que seu sistema precise receber apenas uma categoria, como:
Esportes
Mas o modelo responde assim:
Categoria principal: Esportes. Subcategorias possíveis: futebol, vôlei, corrida e acessórios esportivos.
Para um ser humano, isso pode parecer uma boa resposta.
Para um sistema, pode ser um problema.
Isso acontece porque o modelo não sabe, sozinho, o nível de rigidez que sua aplicação exige. Por isso, além de integrar com a IA, você precisa aprender a:
- ajustar parâmetros;
- testar comportamento antes de codificar;
- escrever prompts mais específicos;
- restringir formato de saída.
É exatamente isso que veremos neste capítulo.
2.2 Por que explorar a API antes de alterar o código?
Quando o desenvolvedor já tem um projeto funcionando, é comum pensar:
“Vou ajustar tudo direto no código.”
Isso funciona, mas nem sempre é a melhor estratégia.
Quando estamos lidando com IA, existem muitas variáveis ao mesmo tempo:
- o texto do prompt;
- o papel definido no
system; - o nível de criatividade;
- a quantidade de texto gerada;
- a consistência da saída;
- o formato da resposta.
Se cada teste exigir:
- alterar código;
- subir a aplicação;
- chamar o endpoint;
- comparar a resposta;
- repetir tudo de novo;
o processo fica mais lento do que precisa.
Por isso, antes de fixar a implementação, vale explorar o comportamento da API em um ambiente de simulação.
Ideia principal
O Playground ajuda você a responder perguntas como:
- essa temperatura está alta demais?
- a resposta está criativa ou instável?
- esse prompt está claro?
- o modelo entendeu o papel que deve assumir?
- o formato está bom para a aplicação?
Depois de encontrar uma configuração boa, aí sim faz sentido levá-la para o código.
2.3 O que é o Playground?
O Playground é um ambiente de testes onde você pode conversar com o modelo diretamente pela interface web, ajustando o comportamento da geração sem precisar programar naquele momento.
Na prática, ele funciona como uma área de experimentação.
Você pode:
- escrever mensagens;
- definir um contexto de sistema;
- alterar parâmetros;
- executar testes rapidamente;
- comparar respostas;
- descobrir o que funciona melhor antes de implementar no backend.
Por que isso é importante?
Porque a IA não é uma função matemática que sempre responde do mesmo jeito.
Ela é sensível a:
- instrução;
- contexto;
- parâmetros;
- exemplos fornecidos.
Por isso, testar em uma área controlada acelera muito o desenvolvimento.
2.4 O que observar ao usar o Playground?
Ao usar o Playground, você não deve testar de forma aleatória. O ideal é observar alguns pontos com método.
1. A resposta veio no formato esperado?
Se você queria apenas uma categoria, mas recebeu uma explicação longa, isso já é um sinal importante.
2. A resposta ficou estável?
Se o mesmo teste gera saídas muito diferentes, talvez a configuração esteja aberta demais.
3. A resposta ficou criativa demais?
Em tarefas técnicas, excesso de criatividade pode atrapalhar.
4. A resposta ficou curta demais?
Controle excessivo também pode gerar empobrecimento da resposta.
5. O resultado é útil para a aplicação?
Nem sempre uma resposta bonita é uma resposta utilizável pelo sistema.
Esse olhar é essencial porque, em aplicações, a pergunta correta não é apenas:
“A IA respondeu?”
Mas sim:
“A IA respondeu do jeito certo para o meu sistema usar?”
2.5 Entendendo os parâmetros da API
A API permite configurar alguns parâmetros que afetam diretamente o comportamento do modelo. Esses parâmetros ajudam a controlar aspectos como:
- criatividade;
- previsibilidade;
- diversidade;
- repetição;
- tamanho da resposta.
2.6 Temperature: controlando criatividade e previsibilidade
A temperatura é um dos parâmetros mais conhecidos e mais importantes.
Ela controla o grau de aleatoriedade da resposta.
Explicando de forma simples
Podemos pensar assim:
- temperatura baixa → resposta mais estável, previsível e conservadora;
- temperatura alta → resposta mais criativa, mais variada e mais arriscada.
Exemplos práticos de interpretação
Temperatura baixa
Boa para:
- categorização;
- classificação;
- respostas técnicas;
- saídas padronizadas;
- prompts que exigem precisão.
Temperatura mais alta
Boa para:
- criatividade;
- brainstorm;
- nomes;
- slogans;
- textos mais livres.
Um alerta importante
Temperatura alta não significa automaticamente resposta melhor.
Em alguns casos, ela pode gerar resultados que fogem do objetivo da aplicação.
Faixa prática de leitura
De forma didática, você pode pensar assim:
0.0→ extremamente controlada;0.5a0.7→ mais técnica;0.8a0.9→ equilíbrio interessante para várias tarefas;1.0→ padrão razoável em muitos cenários;1.2ou mais → mais livre, com mais risco de instabilidade.
2.7 Max Tokens: controlando o tamanho da resposta
Outro parâmetro importante é o limite de tokens, que controla o tamanho máximo da resposta.
O que é um token?
Para fins de ebook, você pode entender token como uma unidade de texto processada pelo modelo. Nem sempre corresponde exatamente a uma palavra inteira, mas essa definição já é suficiente neste momento.
O que esse parâmetro faz?
Ele limita o quanto o modelo pode continuar escrevendo.
Quando isso é útil?
É útil quando sua aplicação precisa de respostas:
- curtas;
- objetivas;
- dentro de um limite;
- sem excesso de explicação.
2.8 TopP: controlando diversidade por amostragem
O parâmetro TopP também influencia diversidade, mas de uma forma diferente da temperatura.
Explicando de forma simples
Em vez de considerar todas as possibilidades para a próxima palavra, o modelo considera apenas um conjunto principal de opções mais prováveis.
O que isso significa na prática?
Quanto mais restrito esse conjunto, mais controlada tende a ser a escolha.
Dica didática
Na prática, muitos cenários começam ajustando primeiro a temperatura. TopP pode ser explorado depois, quando houver necessidade de ajuste mais fino.
2.9 Frequency Penalty e Presence Penalty
Esses dois parâmetros ajudam a controlar repetição.
Frequency Penalty
Reduz a chance de o modelo repetir demais palavras ou expressões que já apareceram na resposta.
Presence Penalty
Influencia a tendência de continuar reutilizando palavras ou temas já introduzidos no contexto.
Explicação didática
Para este início de curso, basta você entender:
- ambos ajudam no controle de repetição;
- podem ser úteis em textos mais longos;
- geralmente não são o primeiro ajuste a ser feito em exemplos iniciais;
- a temperatura costuma ser o primeiro parâmetro mais importante para aprender.
2.10 Stop Sequences: definindo onde a resposta deve parar
As sequências de parada servem para interromper a geração quando determinado padrão aparece.
Para que isso ajuda?
Ajuda quando queremos:
- delimitar a saída;
- impedir texto extra;
- controlar melhor o final da resposta;
- evitar que o modelo continue além do necessário.
2.11 O que o desenvolvedor deve aprender com esses parâmetros?
O ponto central não é decorar todos os nomes.
O que você precisa entender é:
a API oferece controles para moldar o comportamento do modelo.
Isso significa que o sistema não precisa aceitar qualquer saída como inevitável. Há espaço para ajuste.
2.12 Estudo de caso: triagem de chamados de suporte
Depois de entender Playground e parâmetros, um ótimo exemplo de uso é a triagem automática de chamados (help desk).
É um bom exemplo pedagógico porque mostra uma tarefa em que:
- a IA não precisa ser super criativa;
- a resposta precisa ser curta;
- o formato precisa ser estável;
- o backend ganha valor ao encaminhar filas sem análise manual linha a linha.
Cenário
Imagine um sistema em que cada chamado chega como texto livre. Em vez de rotular tudo à mão, a aplicação envia a descrição à IA e pede uma única etiqueta de encaminhamento.
Exemplos de descrição:
- Não consigo redefinir minha senha — o link expira na hora;
- O aplicativo trava ao salvar o formulário de cadastro;
- Apareceu cobrança duplicada no meu cartão neste mês.
Nesse contexto, o comportamento desejado é mais conservador. Por isso, uma temperatura em torno de 0.85 costuma fazer sentido.
2.13 O papel da mensagem de sistema
A mensagem de sistema tem papel importante porque orienta o comportamento geral do modelo.
Exemplo simples de system
Você classifica chamados de suporte em uma única área.
Essa frase já muda bastante o comportamento do modelo.
Se o usuário mandar apenas:
Não consigo redefinir minha senha.
o modelo tende a interpretar aquilo como um relato a rotular, não como uma conversa aberta.
2.14 Levando o teste do Playground para o código
Depois de validar o comportamento no Playground, o próximo passo é reproduzir a ideia no projeto Java.
Exemplo
@GetMapping
public String triar(String descricao) {
var system = "Você classifica chamados de suporte em uma única área.";
return this.chatClient.prompt()
.system(system)
.user(descricao)
.options(ChatOptionsBuilder.builder()
.withTemperature(0.85f)
.build())
.call()
.content();
}
2.15 Explicando essa implementação
String descricao
O texto do chamado é recebido dinamicamente pela URL. Isso torna a funcionalidade realmente utilizável.
var system
Define o papel do modelo.
.system(system)
Envia a instrução de sistema.
.user(descricao)
Envia o texto do chamado informado na requisição.
.options(...)
Permite ajustar o comportamento da chamada.
.withTemperature(0.85f)
Define uma temperatura mais apropriada para uma tarefa de triagem e rotulagem.
Como esses elementos se encaixam na chamada
2.16 Tornando a chamada dinâmica
No começo, muitos exemplos usam valores fixos no código para facilitar o entendimento. Mas, para uma aplicação real, o ideal é receber a entrada dinamicamente.
Exemplo de chamada
GET /triagem?descricao=Cobran%20a%20duplicada%20no%20cartao
ou
GET /triagem?descricao=Link%20de%20reset%20de%20senha%20expira
Isso melhora bastante o valor didático do exemplo, porque Você enxerga a IA sendo aplicada a entradas reais.
2.17 O primeiro problema real: a resposta não está padronizada
Mesmo funcionando, a resposta ainda não está boa o suficiente para a aplicação.
Por exemplo, o modelo pode responder de maneiras diferentes dependendo do chamado:
- em um caso, devolve apenas a categoria;
- em outro, traz categoria e subcategoria;
- em outro, vem com explicações adicionais.
Por que isso é um problema?
Porque o backend muitas vezes precisa de algo padronizado, como:
- um único valor;
- uma saída curta;
- uma categoria entre opções válidas;
- sem texto extra.
Se a resposta varia demais, isso dificulta:
- persistência em banco;
- comparação de resultados;
- automações posteriores;
- uso no front-end;
- regras de negócio.
Aqui Você começa a perceber uma lição muito importante:
resposta boa para conversa não é, necessariamente, resposta boa para sistema.
2.18 Entrando em engenharia de prompt
É neste ponto que entra a engenharia de prompt.
Engenharia de prompt é o conjunto de técnicas usadas para orientar melhor o modelo, reduzindo ambiguidade e aumentando a chance de a resposta sair no formato esperado.
Na prática, isso significa deixar a instrução:
- mais clara;
- mais restrita;
- mais específica;
- mais alinhada ao caso de uso.
2.19 Do prompt genérico ao prompt específico
Versão genérica
Você classifica chamados de suporte.
Esse prompt ajuda, mas ainda deixa muita liberdade para o modelo decidir:
- se vai explicar;
- se vai detalhar demais;
- se vai usar subcategorias;
- se vai responder em frase completa.
Versão melhorada
Você classifica chamados de suporte e deve responder apenas o nome da área (uma linha).
Esse ajuste já reduz bastante a ambiguidade.
2.20 Refinando com lista fixa de categorias
Depois, o prompt pode evoluir ainda mais, passando a informar explicitamente quais categorias são válidas.
Exemplo
Escolha apenas uma etiqueta dentro da lista abaixo:
1. Acesso e autenticação
2. Bug ou estabilidade
3. Faturamento e cobrança
4. Outros
Por que isso ajuda?
Porque o modelo deixa de inventar rótulos fora do universo permitido pelo roteamento interno.
Se o sistema só encaminha para quatro filas, isso precisa estar claro no prompt.
2.21 Refinando com exemplo de uso
Outro ponto muito importante é a inclusão de exemplo de pergunta e resposta.
Exemplo
Chamado: O app fecha sozinho ao exportar PDF
Resposta: Bug ou estabilidade
Por que exemplos funcionam tão bem?
Porque eles ensinam ao modelo, de forma prática:
- o formato da entrada;
- o formato desejado da saída;
- o nível de objetividade esperado;
- o padrão que deve ser seguido.
2.22 Usando Text Block no Java
Para prompts maiores, o uso de Text Block no Java melhora muito a legibilidade.
Exemplo
String system = """
Você classifica chamados de suporte e deve responder apenas o nome da área (uma linha).
Escolha apenas uma etiqueta dentro da lista abaixo:
1. Acesso e autenticação
2. Bug ou estabilidade
3. Faturamento e cobrança
4. Outros
Exemplo de uso:
Chamado: O app fecha sozinho ao exportar PDF
Resposta: Bug ou estabilidade
""";
Vantagens
- melhora leitura;
- evita concatenação de strings;
- facilita manutenção;
- deixa o prompt visualmente mais claro.
2.23 Versão consolidada da triagem
@GetMapping
public String triar(String descricao) {
var system = """
Você classifica chamados de suporte e deve responder apenas o nome da área (uma linha).
Escolha apenas uma etiqueta dentro da lista abaixo:
1. Acesso e autenticação
2. Bug ou estabilidade
3. Faturamento e cobrança
4. Outros
Exemplo de uso:
Chamado: O app fecha sozinho ao exportar PDF
Resposta: Bug ou estabilidade
""";
return this.chatClient.prompt()
.system(system)
.user(descricao)
.options(ChatOptionsBuilder.builder()
.withTemperature(0.85f)
.build())
.call()
.content();
}
2.24 O que você deve entender com esse exemplo?
Mais importante do que decorar a estrutura é compreender o raciocínio:
- a funcionalidade começou funcionando, mas com saída solta;
- percebemos que a aplicação precisava de mais controle;
- usamos Playground para entender comportamento;
- ajustamos temperatura;
- definimos melhor a mensagem de sistema;
- restringimos a lista de categorias;
- demos um exemplo de entrada e saída;
- a resposta ficou mais próxima do formato esperado.
Essa evolução mostra que desenvolver com IA é um processo de refinamento.
2.25 Boas práticas de prompt engineering
1. Diga qual papel o modelo deve assumir
Exemplo:
Você classifica chamados de suporte.
2. Diga exatamente o que ele deve devolver
Exemplo:
Responda apenas o nome da categoria
3. Restrinja o universo de resposta
Exemplo:
Escolha entre: Acesso e autenticação, Bug ou estabilidade, Faturamento e cobrança ou Outros
4. Forneça exemplos
Exemplos ajudam o modelo a reproduzir o padrão desejado.
5. Seja específico
Prompt vago gera saída vaga.
6. Ajuste a temperatura ao tipo de tarefa
Triagem e rotulagem pedem mais controle do que criatividade.
7. Pense no sistema, não só na conversa
Pergunte sempre: “Essa resposta é fácil para meu backend usar?”
2.26 Erros comuns nesta etapa
Erro 1: achar que a IA vai adivinhar o formato ideal
Não vai. Se você não disser claramente, a saída pode variar.
Erro 2: usar temperatura alta em tarefas técnicas
Isso pode aumentar a instabilidade sem necessidade.
Erro 3: não restringir etiquetas de fila
Se a aplicação encaminha apenas quatro filas, o prompt precisa refletir isso.
Erro 4: considerar qualquer resposta correta como suficiente
Para o sistema, formato importa.
Erro 5: testar pouco antes de implementar
O Playground existe justamente para evitar idas e vindas desnecessárias no backend.
2.27 Resumo do capítulo
Neste capítulo, você aprendeu que, depois de integrar com a IA, o próximo desafio é controlar melhor a resposta.
Vimos que:
- o Playground ajuda a testar comportamento antes de alterar o código;
- parâmetros como temperatura influenciam criatividade e previsibilidade;
- o
ChatClientpermite enviar mensagens de sistema, mensagens do usuário e opções do modelo; - uma funcionalidade pode funcionar e, ainda assim, estar ruim para a aplicação se a resposta vier sem padrão;
- engenharia de prompt ajuda a reduzir ambiguidade e aproximar a saída do formato esperado;
- restringir etiquetas de fila e fornecer exemplos melhora bastante a previsibilidade da resposta.
2.28 Exercícios propostos
1. Conceitual
Explique com suas palavras por que o Playground pode economizar tempo no desenvolvimento com IA.
2. Conceitual
Qual a diferença entre uma tarefa que pede temperatura mais alta e outra que pede temperatura mais baixa?
3. Prático
Crie um prompt de sistema para classificar cursos em apenas estas categorias:
- Programação
- Design
- Robótica
- Outros
4. Prático
Reescreva o prompt abaixo para deixá-lo mais específico:
Você lê chamados de suporte
5. Reflexão
Por que uma resposta como “Categoria principal: Eletrônicos. Subcategorias possíveis...” pode ser ruim para um sistema automatizado?
Desafio extra
No Playground, monte um único prompt de sistema que force a saída em JSON com chaves categoria e confianca (0 a 1) para um texto de e‑mail de suporte. Depois, traduza a mesma ideia para um system + user no Java e valide se a resposta pode ser parseada com Jackson sem pós-processamento manual.
2.29 Fechamento
Agora você já entende que integrar com IA não é apenas fazer uma chamada e aceitar qualquer resposta. Em aplicações reais, o formato da saída importa tanto quanto o conteúdo.
Este capítulo foi importante porque mostrou a transição entre:
- IA funcionando e
- IA funcionando de forma útil para o sistema
Esse é um passo decisivo na sua evolução.
No próximo capítulo, fica natural avançar para temas como:
- modelos;
- tokens;
- custo;
- seleção de modelo;
- troca dinâmica no Spring AI.
Capítulo 3 — Escolhendo o modelo certo: modelos, tokens, custo e troca dinâmica no Spring AI
Objetivo do capítulo
Neste capítulo, você vai aprender:
- por que a escolha do modelo impacta custo, velocidade e qualidade da resposta;
- como avaliar um modelo antes de adotá-lo na aplicação;
- o que são tokens e por que eles influenciam limites e cobrança;
- como trocar o modelo no Spring AI em diferentes níveis;
- como configurar um modelo global para a aplicação;
- como definir modelos diferentes para funcionalidades diferentes;
- como estimar tokens para apoiar decisões técnicas;
- como organizar melhor o código quando há mais de um
ChatClient.
Ao final, a ideia é que você consiga responder a esta pergunta com segurança:
“Para esta funcionalidade, qual modelo faz mais sentido usar e onde devo configurar isso no código?”
3.1 Introdução
Nos capítulos anteriores, aprendemos a integrar a aplicação com IA, configurar parâmetros e melhorar prompts para tornar as respostas mais previsíveis.
Agora surge uma nova pergunta:
“Será que o modelo que estou usando é mesmo o melhor para esse caso?”
Essa pergunta é muito importante.
Quando uma aplicação usa IA, normalmente não existe um único modelo perfeito para tudo. Em alguns cenários, você quer:
- mais qualidade em tarefas complexas;
- menor custo em alto volume;
- menor latência;
- respostas mais longas;
- melhor equilíbrio entre preço e desempenho.
Por isso, aprender a escolher modelo é parte do desenvolvimento com IA.
3.2 Por que a escolha do modelo importa?
Muita gente, no início, usa o primeiro modelo que funciona e segue em frente. Isso é normal. Mas, em projetos reais, essa decisão afeta diretamente o sistema.
O modelo influencia:
1. Qualidade da resposta
Modelos mais fortes tendem a lidar melhor com tarefas complexas.
2. Velocidade
Modelos menores ou mais enxutos podem responder mais rápido.
3. Custo
A cobrança varia conforme o modelo e a quantidade de tokens usada.
4. Limites de contexto e saída
Cada modelo possui limites de contexto e de tamanho máximo de resposta.
Conclusão prática
Escolher um modelo é uma decisão técnica e econômica ao mesmo tempo.
Não é apenas “qual responde melhor”, mas também:
- qual cabe no orçamento;
- qual responde no tempo certo;
- qual atende ao tamanho do contexto;
- qual faz sentido para a tarefa.
3.3 O catálogo de modelos muda com o tempo
Esse ponto é muito importante Para este ebook.
Os modelos disponíveis, as recomendações e até os preços podem mudar ao longo do tempo.
O que você precisa aprender com isso?
Ele não deve decorar um nome de modelo como se fosse uma verdade permanente.
O que ele precisa aprender é o critério de escolha:
- preciso de mais capacidade ou mais economia?
- meu volume é alto ou baixo?
- a tarefa é simples ou complexa?
- a resposta precisa ser longa?
- a latência importa muito?
Esse raciocínio continua válido mesmo quando o catálogo muda.
3.4 O que observar ao comparar modelos?
Antes de configurar um modelo na aplicação, vale olhar quatro dimensões principais.
1. Capacidade
Alguns modelos são mais indicados para tarefas mais exigentes, como raciocínio mais complexo, código e fluxos mais avançados.
2. Latência
Modelos menores tendem a responder mais rápido.
3. Custo
Os preços por milhão de tokens variam bastante entre modelos.
4. Limites de contexto e saída
Cada modelo pode ter diferenças de janela de contexto e tamanho máximo de resposta.
3.5 Entendendo tokens de forma simples
Para escolher bem um modelo, você precisa entender o básico sobre tokens.
O que são tokens?
Tokens são unidades de texto usadas pelo modelo para processar entrada e saída. Eles funcionam como blocos menores que o sistema consegue manipular internamente.
De forma didática, você pode imaginar assim:
- uma palavra pode virar um token;
- uma palavra maior pode virar mais de um token;
- números, símbolos e partes de palavras também podem virar tokens.
O importante, neste momento, é entender que o modelo não lê texto da mesma forma que uma pessoa lê. Ele processa tokens.
3.6 Por que tokens importam?
Tokens importam por três motivos principais.
1. Limite de contexto
Todo modelo tem um limite de quanto consegue considerar dentro da conversa ou da requisição.
2. Tamanho da resposta
Também existe limite para a quantidade de tokens que podem ser gerados na saída.
3. Custo
A cobrança da API é baseada em tokens.
Conclusão prática
Quanto mais tokens você usa, maior tende a ser:
- o custo;
- o tempo de processamento;
- a necessidade de escolher um modelo com limite adequado.
3.7 Exemplo simples para visualizar tokens
Texto de entrada:
O sol está brilhando hoje.
De forma simplificada, o modelo pode quebrar isso em unidades menores para processamento.
A quantidade exata varia conforme a tokenização do modelo, mas a ideia principal é esta:
- o texto entra;
- ele é dividido em tokens;
- os tokens são processados;
- a resposta é gerada em novos tokens;
- esses tokens voltam a virar texto legível.
Para este ebook, o mais importante não é o cálculo exato neste momento, e sim o conceito de que entrada e saída consomem tokens.
3.8 Tokens, contexto e histórico da conversa
Quando usamos chat, o modelo não olha apenas a última mensagem. Em geral, ele considera um contexto composto por mensagens anteriores, instruções de sistema e conteúdo atual, até o limite da janela suportada pelo modelo.
Isso significa que, em aplicações maiores, devemos pensar em:
- tamanho do histórico;
- tamanho do
system; - tamanho do
user; - tamanho esperado da resposta.
Tudo isso entra na conta.
3.9 Tokens também influenciam custo
A cobrança por tokens é um dos pontos mais importantes para aplicações em escala.
O que isso ensina para você?
Se sua aplicação:
- faz poucas chamadas;
- atende poucos usuários;
- usa prompts curtos;
o custo pode ser bem controlável.
Mas se ela:
- roda em alto volume;
- usa muito contexto;
- gera respostas longas;
- processa grandes lotes;
então escolher o modelo certo deixa de ser detalhe e vira decisão de arquitetura.
3.10 Quando faz sentido usar um modelo mais forte?
Use um modelo mais robusto quando a tarefa exigir:
- raciocínio mais sofisticado;
- melhor qualidade de resposta;
- tarefas mais complexas;
- maior exigência profissional;
- workflows com mais nuance.
3.11 Quando faz sentido usar um modelo mais enxuto?
Use um modelo menor quando a prioridade for:
- menor custo;
- menor latência;
- tarefas mais simples;
- alto volume de chamadas;
- classificações, rotulações ou respostas mais objetivas.
3.12 A pergunta certa não é “qual é o melhor modelo?”
Na prática, a pergunta certa é:
“Qual modelo é melhor para esta funcionalidade?”
Exemplos:
Gerador de rascunhos criativos (ex.: títulos, slogans)
Talvez você queira mais criatividade e qualidade de linguagem.
Triagem de chamados (rótulo único)
Talvez um modelo mais econômico já resolva muito bem.
Resumo técnico longo
Talvez você precise de melhor capacidade e mais tokens de saída.
Classificação em lote
Talvez a decisão passe muito mais por custo total do que por sofisticação máxima.
Essa mentalidade já ajuda Você a sair da ideia de “um modelo para tudo”.
3.13 Trocando o modelo no Spring AI: visão geral
O Spring AI é flexível para troca de modelo em diferentes níveis:
- no prompt específico;
- no
ChatClient; - globalmente na aplicação.
Isso é ótimo do ponto de vista didático, porque mostra que a decisão pode ser feita no nível mais adequado ao caso de uso.
3.14 Troca de modelo no prompt específico
A forma mais localizada de trocar o modelo é configurar isso diretamente na chamada de um método específico.
Exemplo
public String triar(String descricao) {
return this.chatClient.prompt()
.system(system)
.user(descricao)
.options(ChatOptionsBuilder.builder()
.withTemperature(0.8f)
.withModel("gpt-4o-mini")
.build())
.call()
.content();
}
Quando isso é útil?
Quando você quer que apenas aquele prompt use um modelo diferente.
Vantagem
Alto controle local.
Desvantagem
Se vários métodos precisarem da mesma configuração, você acaba repetindo código.
3.15 Troca de modelo no ChatClient
Se vários métodos do mesmo controller ou serviço devem usar o mesmo modelo, faz mais sentido configurar o modelo no próprio ChatClient.
Exemplo
public TriagemController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder
.defaultOptions(ChatOptionsBuilder
.builder()
.withModel("gpt-4o-mini")
.build())
.build();
}
O que isso significa?
Todos os prompts criados a partir desse ChatClient herdarão esse modelo por padrão.
Quando isso é útil?
Quando uma funcionalidade inteira usa o mesmo perfil de modelo.
3.16 O mais específico sobrescreve o mais genérico
Esse é um ponto muito importante.
Em ordem de especificidade
- configuração no prompt específico;
- configuração no
ChatClient; - configuração global da aplicação.
Isso é muito útil porque permite:
- ter um padrão global;
- ter exceções por funcionalidade;
- ter exceções ainda mais específicas por chamada.
3.17 Modelo padrão no Spring AI
Se você não definir explicitamente o modelo, o Spring AI usa valores padrão.
O que você precisa aprender aqui?
Se a aplicação funcionou sem você declarar explicitamente o modelo, isso não significa que “não existe modelo configurado”. Significa que alguma configuração padrão foi usada.
3.18 Configurando o modelo globalmente no application.properties
Quando você quer definir um modelo padrão para toda a aplicação, pode usar configuração global.
Exemplo
spring.ai.openai.chat.options.model=gpt-4o-mini
O que isso faz?
Todo ChatClient que não sobrescrever explicitamente o modelo passará a usar esse valor como padrão.
Quando isso é útil?
Quando o projeto inteiro ou a maior parte dele usa o mesmo modelo.
3.19 Qual nível escolher para configurar o modelo?
Aqui vai uma regra prática para você:
Use configuração global quando:
- quase toda a aplicação usa o mesmo modelo.
Use configuração no ChatClient quando:
- um grupo de funcionalidades compartilha o mesmo modelo.
Use configuração no prompt quando:
- só uma chamada específica precisa de comportamento diferente.
Essa lógica ajuda a manter o projeto organizado e evita repetição.
3.20 Quando criar mais de um ChatClient?
Quando a aplicação cresce, é comum ter funcionalidades com perfis diferentes.
Exemplo:
- um gerador de conteúdo pode pedir um modelo mais forte;
- uma triagem de chamados pode pedir um modelo mais barato.
Nesse cenário, uma abordagem melhor é criar uma classe de configuração central e expor diferentes ChatClients como beans gerenciados pelo Spring, cada um com seu @Qualifier.
Exemplo
@Configuration
public class ChatClientConfiguration {
@Bean
@Qualifier("modelo-rapido")
public ChatClient modeloRapidoChatClient(ChatClient.Builder chatClientBuilder) {
return chatClientBuilder
.defaultOptions(ChatOptionsBuilder
.builder()
.withModel("gpt-4o-mini")
.build())
.build();
}
@Bean
@Qualifier("modelo-capaz")
public ChatClient modeloCapazChatClient(ChatClient.Builder chatClientBuilder) {
return chatClientBuilder
.defaultOptions(ChatOptionsBuilder
.builder()
.withModel("gpt-4o")
.build())
.build();
}
}
3.21 Injetando ChatClient por @Qualifier
Depois, cada controller recebe o cliente certo.
Exemplo
@RestController
@RequestMapping("/gerador")
public class GeradorController {
private final ChatClient chatClient;
public GeradorController(@Qualifier("modelo-capaz") ChatClient chatClient) {
this.chatClient = chatClient;
}
}
@RestController
@RequestMapping("/triagem")
public class TriagemController {
private final ChatClient chatClient;
public TriagemController(@Qualifier("modelo-rapido") ChatClient chatClient) {
this.chatClient = chatClient;
}
}
Essa estratégia é melhor do que replicar configuração manual em vários controllers.
3.22 Por que essa organização é melhor?
Porque ela traz:
- centralização da configuração;
- menos duplicação;
- leitura mais clara;
- manutenção mais fácil;
- escalabilidade do projeto.
Em aplicações pequenas, configurar direto no controller pode até funcionar. Em aplicações maiores, isso começa a gerar confusão.
3.23 Contagem de tokens para apoiar escolha de modelo
Uma etapa mais avançada é usar uma biblioteca externa para contar tokens antes da chamada, ajudando na lógica de escolha de modelo.
Ideia prática
Antes de chamar a IA, você pode:
- contar tokens do
system+user; - estimar o tamanho do contexto;
- decidir qual modelo usar;
- evitar estourar limites;
- controlar custo.
3.24 Usando uma biblioteca para contar tokens
Uma biblioteca bastante usada para isso no Java é a JTokkit.
Exemplo de método
private int contarTokens(String system, String user) {
var registry = Encodings.newDefaultEncodingRegistry();
var enc = registry.getEncodingForModel(ModelType.GPT_4O_MINI);
return enc.countTokens(system + user);
}
Depois:
var tokens = contarTokens(system, descricao);
System.out.println("QTD de tokens: " + tokens);
O que isso mostra?
Você passa a medir o tamanho aproximado da entrada antes de decidir qual modelo usar.
3.25 Onde essa lógica deveria ficar?
Para fins didáticos, pode até aparecer dentro do controller.
Mas arquiteturalmente, o ideal é mover essa responsabilidade para uma classe separada.
Melhor abordagem arquitetural
- controller recebe a requisição;
- serviço decide a estratégia;
- componente de tokenização estima tokens;
- componente de seleção escolhe o modelo;
ChatClientfaz a chamada.
Assim, o projeto cresce de forma mais saudável.
3.26 Batch API: quando pensar em processamento em lote?
Quando a aplicação precisa processar muitas requisições que não exigem resposta imediata, vale pensar em processamento em lote.
Quando isso pode fazer sentido?
- classificar milhares de registros;
- gerar conteúdo em massa;
- processar documentos;
- produzir embeddings em lote;
- tarefas offline ou não imediatas.
Quando não faz sentido?
- endpoints síncronos que precisam responder imediatamente ao usuário.
3.27 O que você deve aprender de verdade neste capítulo?
Mais do que decorar propriedades ou nomes de modelos, ele precisa entender este raciocínio:
- diferentes funcionalidades podem exigir modelos diferentes;
- tokens influenciam limite e custo;
- custo e desempenho são parte da arquitetura;
- o Spring AI permite configurar o modelo em níveis diferentes;
- a configuração mais específica sobrescreve a mais genérica;
- em projetos maiores, centralizar
ChatClients melhora bastante a organização.
3.28 Boas práticas desta etapa
1. Não escolha modelo por hábito
Escolha pelo caso de uso.
2. Evite um único modelo para tudo sem necessidade
Funcionalidades diferentes podem pedir perfis diferentes.
3. Use configuração global com critério
Ela é boa quando quase tudo segue o mesmo padrão.
4. Crie ChatClients específicos quando o sistema crescer
Isso ajuda muito na organização.
5. Observe tokens desde cedo
Mesmo em projetos pequenos, isso forma boa base técnica.
6. Separe lógica de seleção de modelo do controller
O controller deve ficar simples.
7. Revise documentação periodicamente
Modelos, defaults e preços mudam com o tempo.
3.29 Erros comuns nesta etapa
Erro 1: usar sempre o mesmo modelo sem avaliar a tarefa
Isso pode gerar custo desnecessário ou qualidade insuficiente.
Erro 2: ignorar tokens
Quando o sistema cresce, isso cobra seu preço.
Erro 3: repetir withModel(...) em todo lugar
Isso dificulta manutenção.
Erro 4: misturar decisão de modelo diretamente no controller
Em aplicações maiores, isso suja a camada web.
Erro 5: decorar preços e nomes como se fossem fixos
Essas informações mudam. O importante é entender o critério de escolha.
3.30 Resumo do capítulo
Neste capítulo, você aprendeu que escolher o modelo certo é parte essencial do desenvolvimento com IA.
Vimos que:
- os modelos diferem em capacidade, custo, latência e limites de contexto/saída;
- tokens são a base de processamento do texto e influenciam tanto limites quanto cobrança;
- o Spring AI permite configurar o modelo no prompt, no
ChatClientou globalmente por propriedades; - a configuração mais específica sobrescreve a mais genérica;
- em aplicações com mais de uma necessidade, vale criar múltiplos
ChatClients e injetá-los com@Qualifier; - uma biblioteca de contagem de tokens pode ajudar na troca dinâmica de modelo;
- processamento em lote faz sentido em cenários assíncronos e de alto volume.
3.31 Exercícios propostos
1. Conceitual
Explique com suas palavras por que nem sempre vale usar o mesmo modelo para todas as funcionalidades.
2. Conceitual
O que são tokens e por que eles influenciam custo e limite da aplicação?
3. Prático
Mostre um exemplo de quando faz sentido configurar o modelo:
- globalmente;
- no
ChatClient; - no prompt específico.
4. Prático
Descreva um cenário em que o gerador de conteúdo use um modelo mais forte e a triagem de chamados use um modelo mais econômico.
5. Arquitetura
Por que criar uma classe de configuração com múltiplos ChatClients pode ser melhor do que repetir withModel(...) em vários controllers?
Desafio extra
Simule dois beans ChatClient (ex.: chatClientRapido e chatClientPreciso) apontando para modelos diferentes no application.properties. Exponha dois endpoints que respondam à mesma pergunta e compare latência e qualidade em 3 perguntas reais (anote tempos com System.nanoTime() ou logs).
3.32 Fechamento
Com este capítulo, você deixa de ver o modelo como um detalhe fixo e passa a entendê-lo como uma decisão de arquitetura.
Esse é um avanço importante, porque agora você começa a pensar em IA com mais maturidade:
- não só “funciona?”;
- mas também “vale a pena?”;
- “custa quanto?”;
- “escala bem?”;
- “faz sentido para esta funcionalidade?”.
Esse tipo de pensamento aproxima muito mais o uso da IA de cenários reais de desenvolvimento.
Capítulo 4 — Lidando com erros, retentativas e logs em integrações com Spring AI
Objetivo do capítulo
Neste capítulo, você vai aprender:
- por que toda integração com API externa precisa considerar falhas;
- quais erros podem acontecer ao chamar a OpenAI;
- o que significam códigos como 401, 403, 429, 500 e 503;
- por que nem todo erro deve ser tratado da mesma forma;
- como o Spring AI já faz retentativas automáticas;
- como personalizar o comportamento de retry no
application.properties; - quando vale a pena deixar o Spring tentar de novo e quando não vale;
- como registrar logs da integração com
SimpleLoggerAdvisor; - quais cuidados tomar para não vazar dados sensíveis nos logs.
Ao final, você deve entender algo essencial:
uma integração profissional com IA não depende só de prompt e modelo; ela depende também de resiliência, previsibilidade e observabilidade.
4.1 Introdução
Até aqui, nossa aplicação chamou a API da OpenAI e recebeu respostas com sucesso. Isso é ótimo para aprendizagem inicial, mas existe uma realidade que todo desenvolvedor precisa aceitar:
quando sua aplicação conversa com um serviço externo, falhas fazem parte do cenário.
No nosso projeto, quando executamos algo como:
return this.chatClient.prompt()
.system(system)
.user(descricao)
.options(ChatOptionsBuilder.builder()
.withTemperature(0.8f)
.build())
.call()
.content();
a execução sai do nosso sistema e depende de outra infraestrutura.
Isso significa que podem acontecer falhas por vários motivos:
- erro nosso de autenticação;
- uso incorreto da API;
- excesso de requisições;
- instabilidade de rede;
- indisponibilidade do provedor;
- sobrecarga temporária do serviço;
- problemas de configuração no projeto.
Portanto, um backend que usa IA precisa ser preparado não apenas para o cenário de sucesso, mas também para o cenário de falha.
4.2 Por que esse capítulo é tão importante?
Nos primeiros exemplos, é fácil acreditar que a API estará sempre disponível. Mas, em aplicações reais, isso não é verdade.
Se sua aplicação depende de um serviço externo e você não pensa em falhas, pode acabar com problemas como:
- endpoints quebrando sem tratamento claro;
- usuários vendo erro técnico bruto;
- loops excessivos de requisição;
- aumento desnecessário de custo;
- dificuldade para descobrir o que aconteceu;
- ausência de logs para auditoria;
- comportamento inconsistente em produção.
Em outras palavras: fazer a integração funcionar é só a primeira metade do trabalho. A segunda metade é garantir que ela continue utilizável quando as coisas não saírem como o esperado.
4.3 Entendendo o cenário de falha em APIs externas
Quando sua aplicação chama um serviço interno da própria empresa, às vezes você ainda tem controle sobre logs, infraestrutura e debugging. Mas, ao chamar uma API externa, parte do processamento fica fora do seu domínio.
Você não controla diretamente:
- a disponibilidade do serviço externo;
- a latência daquele servidor;
- a fila interna do provedor;
- o rate limit;
- a política de segurança da conta;
- incidentes temporários da plataforma.
Por isso, todo consumo de API externa precisa ser pensado como uma comunicação sujeita a falhas.
Essa mentalidade muda tudo. O desenvolvedor deixa de pensar apenas em “como chamar” e passa a pensar também em:
- o que pode dar errado;
- como o sistema deve reagir;
- quando tentar novamente;
- quando registrar e parar;
- o que mostrar ao usuário;
- o que monitorar.
4.4 Que tipos de erro podem acontecer?
APIs como a da OpenAI usam códigos HTTP para indicar o tipo de falha.
Entre os erros mais comuns estão:
- 401 para problemas de autenticação;
- 403 para restrição de acesso;
- 429 para limite excedido;
- 500 para erro interno do servidor;
- 503 para indisponibilidade temporária.
Vamos entender cada um de forma didática.
4.5 Erro 401 — autenticação inválida
O erro 401 aparece quando a autenticação falha.
O que isso significa na prática?
A aplicação tentou usar a API, mas não conseguiu provar que tinha permissão para isso.
Causas comuns
- variável de ambiente não carregada;
- chave digitada errada;
- chave revogada;
- chave pertencente a outro projeto;
- configuração incorreta de organização ou projeto.
Exemplo de impacto
Se a aplicação subir sem a chave correta, a chamada ao modelo falhará.
Esse não é um erro para “tentar de novo sem mudar nada”.
Primeiro é preciso corrigir a credencial.
O que você precisa aprender aqui?
Nem todo erro temporário justifica retry.
Se o problema é autenticação, repetir a mesma requisição 10 vezes com a mesma chave inválida não resolve.
4.6 Erro 403 — acesso negado
O erro 403 indica que a requisição foi compreendida, mas o acesso não é permitido.
Em linguagem simples
A aplicação conseguiu falar com a API, mas a API recusou o uso por restrição de permissão ou política.
O que isso ensina?
Esse também não costuma ser um erro que se resolve apenas repetindo a chamada.
É mais um caso de configuração, política de conta ou restrição do ambiente.
4.7 Erro 429 — limite excedido
O erro 429 é um dos mais importantes em integrações reais.
O que isso quer dizer?
Sua aplicação tentou usar a API mais rápido do que o limite permite, ou ultrapassou a capacidade disponível para aquela conta/projeto.
Situações comuns
- muitas requisições em loop;
- várias chamadas concorrentes sem controle;
- batch improvisado via múltiplas chamadas síncronas;
- aumento repentino de tráfego;
- falta de limitação do lado da aplicação;
- saldo ou limite de gasto esgotado.
Por que esse erro é crítico?
Porque ele costuma aparecer justamente quando o sistema começa a escalar.
Em ambiente de teste, talvez você nunca veja 429.
Em ambiente real, ele pode surgir rapidamente se não houver controle.
O que fazer?
- reduzir a taxa de chamadas;
- evitar chamadas redundantes;
- repensar concorrência;
- avaliar batching ou fila;
- monitorar volume e consumo.
Esse é um bom exemplo de erro que pode justificar retentativa com backoff, desde que o problema seja momentâneo e não estrutural.
4.8 Erro 500 — falha interna do servidor
O erro 500 significa que houve um problema no lado do provedor.
Em linguagem simples
A falha não é, necessariamente, da sua aplicação. O servidor da API pode estar com problema interno.
O que isso ensina?
Aqui sim a ideia de retry faz muito sentido.
Se a falha foi transitória, uma nova tentativa após alguns segundos pode resolver.
4.9 Erro 503 — sobrecarga ou indisponibilidade temporária
O erro 503 indica indisponibilidade temporária ou sobrecarga do serviço.
O que isso quer dizer?
O serviço está momentaneamente incapaz de atender no ritmo atual.
Esse é um erro clássico de integração resiliente
Não é, necessariamente, culpa do seu código.
Mas seu código precisa reagir bem a ele.
4.10 Nem todo erro deve ser tratado do mesmo jeito
Esse é um dos pontos mais importantes do capítulo.
Um erro 401 e um erro 500 são muito diferentes.
- 401 geralmente pede correção de configuração;
- 429 pode pedir desaceleração;
- 500/503 pedem tolerância a falha temporária.
Por isso, o desenvolvedor não deve pensar:
“Qualquer erro eu tento de novo.”
A pergunta correta é:
“Esse erro parece transitório ou estrutural?”
Erro estrutural
Algo está errado na configuração, autenticação ou permissão.
Erro transitório
Algo temporário aconteceu na infraestrutura, latência ou carga.
Essa distinção é essencial para construir um sistema maduro.
4.11 O que seria um tratamento ingênuo?
Uma primeira reação comum é envolver a chamada com try-catch e tratar tudo manualmente.
Exemplo conceitual:
try {
return this.chatClient.prompt()
.system(system)
.user(descricao)
.call()
.content();
}
catch (Exception e) {
// tratar erro
}
Por que um try-catch genérico não resolve sozinho?
Porque ele:
- não distingue bem os tipos de falha;
- pode esconder o erro real;
- pode duplicar trabalho já feito pelo framework;
- pode espalhar lógica de tratamento em vários controllers;
- não resolve sozinho a estratégia de retry.
4.12 O papel do Spring AI no tratamento de falhas
A boa notícia é que o Spring AI já oferece mecanismo de retry configurável.
Em termos práticos
Se ocorrer uma falha temporária, o framework pode:
- tentar novamente;
- esperar um intervalo;
- aplicar backoff;
- insistir até um limite;
- só depois lançar exceção.
Isso reduz a necessidade de implementar esse comportamento manualmente em cada endpoint.
4.13 O que é retry?
Retry é a estratégia de tentar novamente uma operação que falhou, supondo que aquela falha possa ser temporária.
Exemplo simples
A aplicação chama a API.
A API responde com falha momentânea.
Em vez de desistir imediatamente, o sistema espera um pouco e tenta de novo.
Quando isso é útil?
Quando o erro pode desaparecer em poucos segundos, como em:
- instabilidade de rede;
- timeout temporário;
- sobrecarga do servidor;
- falha transitória do provedor.
4.14 O que é backoff exponencial?
A ideia de aumentar o tempo de espera a cada nova tentativa corresponde ao backoff exponencial.
Explicando de forma simples
Em vez de repetir assim:
- espera 2 segundos;
- espera 2 segundos;
- espera 2 segundos;
o sistema pode fazer algo como:
- espera 2 segundos;
- depois espera mais;
- depois espera ainda mais;
- e assim por diante, até um limite.
Por que isso é melhor?
Porque, se o serviço está sobrecarregado, insistir rápido demais pode piorar o problema.
O backoff ajuda a:
- reduzir pressão sobre a API;
- dar tempo para recuperação do serviço;
- evitar tempestade de retentativas;
- melhorar comportamento sob falha.
4.15 Entendendo as propriedades de retry do Spring AI
O Spring AI expõe propriedades específicas para controlar esse comportamento.
spring.ai.retry.max-attempts
Define o número máximo de tentativas.
spring.ai.retry.backoff.initial-interval
Define o intervalo inicial antes da próxima tentativa.
spring.ai.retry.backoff.multiplier
Define o multiplicador do backoff.
spring.ai.retry.backoff.max-interval
Define o intervalo máximo entre tentativas.
4.16 O que esses valores significam na prática?
Eles definem a política de tolerância a falhas da sua aplicação.
Exemplo conceitual
Se uma chamada falhar:
- o Spring não desiste imediatamente;
- ele tenta novamente;
- espera um intervalo;
- aplica backoff;
- continua até atingir o máximo configurado;
- se mesmo assim não funcionar, lança exceção.
Isso torna o sistema mais resiliente a falhas temporárias.
4.17 Personalizando o retry no application.properties
Você pode sobrescrever os padrões do framework no application.properties.
Exemplo didático
spring.ai.retry.max-attempts=20
spring.ai.retry.backoff.initial-interval=10
spring.ai.retry.backoff.max-interval=300
Quando personalizar faz sentido?
- quando seu sistema suporta esperar mais;
- quando a operação é importante e vale insistir mais;
- quando a latência extra é aceitável;
- quando a API externa tem instabilidades frequentes e curtas.
Quando não exagerar?
Se você aumentar demais o retry, pode acabar:
- tornando a resposta muito lenta;
- segurando threads por tempo demais;
- piorando a experiência do usuário;
- mascarando incidentes mais sérios.
4.18 Retry resolve tudo?
Não.
Esse ponto precisa ficar muito claro para você.
Retry ajuda principalmente em falhas transitórias, não em falhas estruturais.
Retry ajuda em:
- indisponibilidade momentânea;
- sobrecarga temporária;
- oscilações de rede;
- timeout passageiro.
Retry não resolve sozinho:
- chave inválida;
- projeto sem permissão;
- configuração incorreta;
- região não suportada;
- lógica ruim de chamadas em excesso;
- falta de saldo/quota.
Ou seja, retry é uma ferramenta importante, mas não substitui boa arquitetura e boa operação.
4.19 O que mostrar para o usuário quando der erro?
Nem sempre faz sentido devolver o erro bruto da API para o usuário final.
O que evitar
- stack trace na tela;
- mensagem técnica exposta diretamente;
- texto interno do framework para o cliente;
- informação sensível no retorno.
O ideal
Em muitas aplicações, o usuário deve receber algo mais controlado, como:
- “Não foi possível processar sua solicitação agora.”
- “Tente novamente em instantes.”
- “Serviço temporariamente indisponível.”
Enquanto isso, os detalhes técnicos ficam registrados nos logs.
4.20 Onde tratar erros na aplicação?
Espalhar lógica de erro em todos os controllers não costuma ser a melhor abordagem.
Por que isso é importante?
Porque tratamento duplicado em controller tende a gerar:
- repetição;
- inconsistência;
- manutenção difícil;
- mistura de responsabilidade.
Melhor abordagem didática
O controller deve focar em:
- receber a requisição;
- chamar a regra de negócio;
- devolver o resultado.
O tratamento centralizado de exceções ajuda a manter o projeto mais limpo.
4.21 Erro tratado não basta: precisamos de observabilidade
Até aqui, falamos em falhar e tentar novamente. Mas existe outra pergunta importante:
quando algo acontece, como eu descubro o que realmente foi enviado e o que voltou?
Aí entra o tema de logs e observabilidade.
Sem logs adequados, você pode não saber:
- qual prompt foi enviado;
- qual modelo foi usado;
- qual temperatura estava configurada;
- quantos tokens entraram ou saíram;
- qual foi o retorno real da API;
- por que a chamada falhou.
4.22 O que é um advisor no Spring AI?
Um advisor funciona como um interceptador da conversa entre sua aplicação e o modelo.
Ele pode observar:
- o que está sendo enviado;
- o que está sendo recebido;
- o comportamento da chamada.
4.23 SimpleLoggerAdvisor: logando requisição e resposta
Uma forma simples de registrar request e response é usar o SimpleLoggerAdvisor.
Exemplo didático
return this.chatClient.prompt()
.system(system)
.user(descricao)
.options(ChatOptionsBuilder.builder()
.withTemperature(0.85f)
.build())
.advisors(new SimpleLoggerAdvisor())
.call()
.content();
O que isso traz?
Permite registrar:
- modelo;
- temperatura;
- prompt de sistema;
- prompt do usuário;
- resposta;
- metadados úteis para análise.
4.24 Habilitando o nível de log no application.properties
Para visualizar esses logs, é preciso ajustar o nível de log da categoria correspondente.
Exemplo
logging.level.org.springframework.ai.chat.client.advisor=DEBUG
Com isso, os logs produzidos pelo advisor passam a aparecer no console.
4.25 O que esses logs ajudam a responder?
Com o logging ativado, você consegue entender melhor perguntas como:
- o modelo realmente usado foi o esperado?
- a temperatura era a correta?
- o prompt de sistema estava certo?
- o usuário enviou o texto que imaginávamos?
- a resposta foi coerente?
- houve algum metadado importante retornado?
4.26 Logs ajudam em quatro frentes
1. Debug
Você entende por que a resposta veio daquele jeito.
2. Auditoria
Você consegue rastrear o comportamento da integração.
3. Observabilidade
Você passa a ter visibilidade sobre o que o sistema está fazendo.
4. Melhoria contínua
Você encontra padrões, ajusta prompts e descobre problemas recorrentes.
4.27 Cuidado: log em excesso também é risco
Registrar tudo pode ser perigoso se houver dados sensíveis.
O que pode ser sensível?
- dados pessoais;
- CPF;
- e-mail;
- telefone;
- conteúdo privado;
- informações de negócio restritas;
- chaves ou tokens;
- prompts com dados internos.
Boa prática
Em desenvolvimento, logs detalhados ajudam muito.
Em produção, é preciso avaliar com cuidado:
- o que será logado;
- quem terá acesso;
- por quanto tempo;
- se haverá mascaramento;
- se aquele log é mesmo necessário.
4.28 Resiliência + observabilidade = integração madura
Uma integração madura com IA precisa de dois pilares:
Resiliência
Capacidade de continuar operando ou se recuperar diante de falhas temporárias.
Observabilidade
Capacidade de entender o que aconteceu, por que aconteceu e como corrigir.
Se você tem só resiliência e não tem logs, o sistema até tenta se recuperar, mas você não sabe o que está acontecendo.
Se você tem só logs e não tem retry, você até enxerga a falha, mas o sistema quebra com facilidade.
Os dois juntos elevam bastante a qualidade da aplicação.
4.29 O que você deve guardar deste capítulo?
Mais do que decorar propriedades, ele precisa entender estas ideias:
- chamar API externa implica risco de falha;
- falha faz parte da arquitetura, não é exceção rara;
- erros diferentes exigem reações diferentes;
- retry ajuda em falhas temporárias;
- o Spring AI já oferece política de retentativa;
- essa política pode ser personalizada;
- logs são fundamentais para debugar e operar;
- logs precisam ser usados com responsabilidade.
4.30 Boas práticas desta etapa
1. Nunca assuma disponibilidade total da API
Serviços externos falham.
2. Diferencie erro transitório de erro estrutural
Nem toda falha deve ser tratada com retry.
3. Use os retries do framework antes de reinventar a roda
O Spring AI já traz suporte a isso.
4. Ajuste retry com critério
Mais tentativas nem sempre significam melhor experiência.
5. Centralize tratamento de erro quando fizer sentido
Evite duplicação em controller.
6. Logue o suficiente para investigar
Mas não transforme log em vazamento de dados.
7. Observe custo indireto de falhas
Retries também consomem tempo e, dependendo do caso, podem consumir chamadas.
4.31 Erros comuns nesta etapa
Erro 1: achar que a API sempre estará disponível
Não estará.
Erro 2: tentar resolver tudo com try-catch genérico
Isso pode esconder problema e gerar código espalhado.
Erro 3: tratar 401 como se fosse falha temporária
Se a chave está errada, insistir não resolve.
Erro 4: não monitorar 429
Esse erro costuma aparecer quando a aplicação começa a crescer.
Erro 5: ativar log detalhado em produção sem cuidado
Isso pode expor dados indevidos.
Erro 6: não registrar contexto suficiente
Sem contexto, o debug vira adivinhação.
4.32 Exemplo de configuração consolidada
application.properties
spring.ai.retry.max-attempts=20
spring.ai.retry.backoff.initial-interval=10
spring.ai.retry.backoff.max-interval=300
logging.level.org.springframework.ai.chat.client.advisor=DEBUG
Uso do advisor no prompt
return this.chatClient.prompt()
.system(system)
.user(descricao)
.options(ChatOptionsBuilder.builder()
.withTemperature(0.85f)
.build())
.advisors(new SimpleLoggerAdvisor())
.call()
.content();
Esse conjunto mostra, de forma prática, duas frentes do capítulo:
- tolerância a falha;
- visibilidade da integração.
4.33 Resumo do capítulo
Neste capítulo, você aprendeu que uma integração com IA precisa ser preparada para falhas e para análise operacional.
Vimos que:
- APIs externas podem devolver erros diferentes, cada um com significado próprio;
- erros de autenticação e permissão não devem ser tratados como simples falhas temporárias;
- o Spring AI já oferece mecanismo de retry configurável via propriedades;
- é possível sobrescrever esses valores no
application.properties; - o
SimpleLoggerAdvisorajuda a registrar request e response; - para visualizar esses logs, é preciso configurar o nível de log apropriado;
- logs ajudam muito em debug, auditoria e observabilidade, mas exigem cuidado com dados sensíveis.
4.34 Exercícios propostos
1. Conceitual
Explique com suas palavras por que uma API externa não deve ser tratada como se estivesse sempre disponível.
2. Conceitual
Qual a diferença entre um erro 401 e um erro 500?
3. Aplicação prática
Descreva um cenário em que retry faz sentido e outro em que retry não resolve o problema.
4. Configuração
Mostre como alterar no application.properties:
- quantidade máxima de tentativas;
- intervalo inicial;
- intervalo máximo.
5. Observabilidade
Explique por que logs detalhados ajudam no debug, mas podem ser perigosos em produção.
Desafio extra
Configure spring.ai.retry.* com valores baixos e force um erro 503 (ou simule indisponibilidade). Depois, aumente max-attempts e meça quantas linhas de log o SimpleLoggerAdvisor gera por requisição. Descreva em uma tabela: cenário → número de tentativas → risco de poluir log em produção.
4.35 Fechamento
Com este capítulo, você começa a dar um passo importante em direção a aplicações mais profissionais.
Agora você não pensa apenas em:
- “como chamar a IA?”,
mas também em:
- “o que acontece quando falha?”,
- “como meu sistema reage?”,
- “como descubro o que aconteceu?”,
- “como manter isso utilizável em produção?”.
Esse é um capítulo-chave porque muda a maturidade do projeto.
Capítulo 5 — Multimodalidade no Spring AI: geração de imagens e troca de provedores sem reescrever a aplicação
Objetivo do capítulo
Neste capítulo, você vai aprender:
- o que significa multimodalidade em aplicações com IA;
- por que a IA generativa não se limita apenas ao modo chat;
- como gerar imagens em uma aplicação Spring Boot usando Spring AI;
- quais classes do Spring AI são usadas no fluxo de geração de imagens;
- como funciona a chamada com
ImageModel; - como retornar a URL de uma imagem gerada;
- por que o código fica desacoplado do provedor quando usamos as abstrações do Spring;
- qual é o impacto real de trocar OpenAI por outro provedor suportado.
Ao final, você deve entender algo muito importante:
quando usamos corretamente as abstrações do Spring AI, a aplicação fica mais flexível, mais reaproveitável e menos presa a um único fornecedor.
5.1 Introdução
Até este ponto do curso, nosso foco principal foi o uso de IA para geração de texto. Isso faz sentido, porque o chat costuma ser a porta de entrada mais comum para quem começa a trabalhar com inteligência artificial em aplicações.
Mas os provedores atuais de IA vão além do texto.
Hoje, muitos deles oferecem modelos capazes de trabalhar com diferentes tipos de mídia, como:
- texto;
- imagem;
- áudio;
- fala;
- transcrição;
- e, em alguns contextos, vídeo.
Isso significa que, ao construir uma aplicação com Spring AI, você não está limitado a um chatbot. Você pode enriquecer o sistema com outras capacidades de IA, desde que isso faça sentido para o problema que está sendo resolvido.
Este capítulo mostra exatamente esse movimento: depois de trabalhar bastante com chat, agora vamos olhar para a geração de imagens e, ao mesmo tempo, entender uma das maiores vantagens arquiteturais do Spring AI: a facilidade para trocar de provedor sem reescrever a aplicação inteira.
5.2 O que é multimodalidade?
Multimodalidade é a capacidade de um sistema de IA de lidar com diferentes tipos de entrada e saída, em vez de trabalhar apenas com texto.
Exemplo simples
Um sistema pode:
- receber texto e gerar texto;
- receber texto e gerar imagem;
- receber áudio e gerar transcrição;
- receber texto e gerar fala;
- combinar diferentes tipos de mídia.
Por que isso é importante?
Porque amplia muito o tipo de aplicação que podemos construir.
Exemplos:
- comunicação institucional com artes ou ilustrações geradas sob demanda;
- sistema educacional com criação de ilustrações;
- chatbot com transcrição de áudio;
- assistente que transforma texto em fala;
- ferramenta que analisa conteúdo multimídia.
Ou seja, IA em software não é só conversa. É também conteúdo multimídia.
5.3 Por que esse tema importa para este ebook?
Esse capítulo é importante porque ajuda você a expandir a visão.
Até aqui, ele pode estar pensando:
“Spring AI serve para fazer perguntas ao modelo e receber texto.”
Mas isso seria uma visão limitada.
O Spring AI foi desenhado para oferecer abstrações específicas para diferentes tipos de modelos, e a geração de imagem é um ótimo exemplo disso.
Então este capítulo serve para mostrar duas coisas:
- o framework suporta outros modos além de chat;
- o desacoplamento oferecido pelo Spring AI se torna ainda mais valioso quando pensamos em múltiplos provedores e múltiplos tipos de modelo.
5.4 Gerar imagem não é a mesma coisa que gerar texto
Esse ponto precisa ficar muito claro para você.
No modo chat, usamos classes como:
ChatClient- mensagens de sistema e usuário
- opções de chat
- retorno textual
Na geração de imagem, o fluxo muda.
Em vez de trabalhar com um cliente de chat, passamos a trabalhar com uma abstração específica para imagem.
O que isso ensina?
Cada tipo de capacidade de IA tem suas próprias classes e seu próprio fluxo, mesmo que a filosofia geral continue parecida:
- há uma abstração do Spring;
- há um prompt ou request;
- há opções;
- há um
call(); - há um objeto de resposta.
Isso torna o aprendizado mais consistente.
5.5 Visão geral da API de imagem no Spring AI
Para entender o fluxo de geração de imagem, você precisa conhecer estas classes:
ImageModelImagePromptImageResponseImageOptionsBuilder
Em linguagem simples
ImageModel
É o componente que conversa com o modelo de geração de imagem.
ImagePrompt
É o objeto que encapsula a solicitação de geração.
ImageOptionsBuilder
Permite definir opções como largura e altura.
ImageResponse
É a resposta gerada pela API após a criação da imagem.
5.6 Criando um controller para geração de imagem
Uma boa prática é criar um controller dedicado para essa funcionalidade.
Estrutura inicial
@RestController
@RequestMapping("imagem")
public class GeradorDeImagensController {
@GetMapping
public String gerarImagem(String prompt) {
}
}
O que esse controller faz?
Ele cria uma nova rota dedicada à geração de imagens.
O usuário envia um prompt textual, e a aplicação usa esse texto para pedir que a IA gere uma imagem correspondente.
5.7 Injetando o ImageModel
Diferente do ChatClient, o ImageModel pode ser injetado diretamente.
Exemplo
import org.springframework.ai.image.ImageModel;
@RestController
@RequestMapping("imagem")
public class GeradorDeImagensController {
private final ImageModel imageModel;
public GeradorDeImagensController(ImageModel imageModel) {
this.imageModel = imageModel;
}
}
O que isso ensina?
O Spring já consegue disponibilizar a implementação apropriada do modelo de imagem.
Isso mantém o estilo de desenvolvimento consistente:
- você injeta uma abstração do Spring;
- usa essa abstração no controller ou serviço;
- não precisa acoplar o código diretamente ao provedor.
5.8 Montando as opções da imagem
Antes de chamar a geração, precisamos definir as opções da imagem.
Exemplo
var options = ImageOptionsBuilder
.builder()
.withHeight(1024)
.withWidth(1024)
.build();
O que isso significa?
Estamos pedindo que a imagem seja gerada com:
- altura: 1024 pixels
- largura: 1024 pixels
Por que isso importa?
Porque geração de imagem também envolve decisão de formato.
Em aplicações reais, você pode querer:
- imagem quadrada para redes sociais;
- imagem horizontal para banner;
- imagem menor para economizar custo e tempo;
- imagem maior para material promocional.
Mesmo em um exemplo simples, já vale mostrar para você que essas decisões fazem parte da implementação.
5.9 Criando o ImagePrompt
Depois das opções, criamos o prompt da imagem.
Exemplo
new ImagePrompt(prompt, options)
O que isso faz?
Esse objeto encapsula duas coisas:
- a instrução textual dada pelo usuário;
- as opções da imagem.
Exemplo de instrução:
Paisagem ao amanhecer com montanhas suaves e névoa leve, estilo ilustração digital
Ou seja, a IA vai tentar gerar uma imagem coerente com essa descrição.
5.10 Chamando o modelo de imagem
Agora chegamos ao núcleo da funcionalidade.
Exemplo
var response = imageModel.call(new ImagePrompt(prompt, options));
O que está acontecendo aqui?
- o usuário envia o texto;
- o backend cria o
ImagePrompt; - o
ImageModelchama a API do provedor; - o provedor gera a imagem;
- a resposta volta para a aplicação.
Perceba que a ideia geral lembra o modo chat, mas com classes específicas para imagem.
5.11 Extraindo a URL da imagem gerada
O retorno final ao navegador pode ser a URL da imagem.
Exemplo
return response.getResult().getOutput().getUrl();
O que isso significa?
A API não está necessariamente devolvendo o arquivo binário da imagem diretamente para o navegador nesse exemplo. Ela está devolvendo uma resposta que contém a URL da imagem gerada.
Por que isso é útil?
Porque você pode:
- abrir a URL em outra aba;
- salvar a URL em banco;
- armazenar a referência para uso posterior;
- exibir a imagem em um front-end;
- montar um fluxo mais completo de publicação ou biblioteca de mídias.
5.12 Exemplo completo do endpoint de imagem
import org.springframework.ai.image.ImageModel;
import org.springframework.ai.image.ImageOptionsBuilder;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("imagem")
public class GeradorDeImagensController {
private final ImageModel imageModel;
public GeradorDeImagensController(ImageModel imageModel) {
this.imageModel = imageModel;
}
@GetMapping
public String gerarImagem(String prompt) {
var options = ImageOptionsBuilder
.builder()
.withHeight(1024)
.withWidth(1024)
.build();
var response = imageModel.call(new ImagePrompt(prompt, options));
return response.getResult().getOutput().getUrl();
}
}
Esse exemplo mostra uma implementação simples, clara e reaproveitável.
5.13 Testando a geração de imagem
Você pode testar chamando a rota no navegador com um parâmetro prompt.
Exemplo de chamada
GET /imagem?prompt=Paisagem ao amanhecer com montanhas suaves e névoa leve
O que deve acontecer?
- a aplicação recebe o prompt;
- chama a API de geração de imagem;
- a IA gera a imagem;
- a aplicação devolve uma URL;
- a URL pode ser aberta para visualizar o resultado.
O que você deve entender aqui?
Mesmo sendo um exemplo simples, esse fluxo já demonstra uma aplicação real de multimodalidade.
5.14 O código continua simples, mesmo com outro tipo de modelo
Esse é um ponto muito valioso do capítulo.
Embora agora estejamos lidando com imagem e não texto, o estilo de programação continua simples:
- abstração do Spring;
- criação de um prompt;
- definição de opções;
- chamada do modelo;
- extração da resposta.
Isso reduz a curva de aprendizado, porque você percebe que o Spring AI mantém uma filosofia consistente entre diferentes tipos de recurso.
5.15 A grande vantagem: o código está desacoplado do provedor
Agora entramos em uma das ideias mais fortes do capítulo.
Classes como ImageModel, ChatClient e ChatOptionsBuilder pertencem ao pacote do Spring, não ao pacote de um provedor específico.
O que isso significa?
Significa que o código principal da aplicação está programado contra as abstrações do Spring AI, e não contra classes específicas da OpenAI.
Qual a vantagem disso?
Se amanhã você trocar de OpenAI para outro provedor suportado, o impacto no código tende a ser muito menor.
5.16 O que muda ao trocar de provedor?
Esse é o medo clássico de quem começa a trabalhar com APIs de IA:
“Se eu trocar de provedor, vou precisar reescrever tudo?”
Se a aplicação estiver muito acoplada ao SDK específico do fornecedor, talvez sim.
Mas se você estiver usando as abstrações do Spring AI, o impacto tende a se concentrar em poucos pontos.
Os principais pontos de mudança tendem a ser:
- a dependência no
pom.xml; - as propriedades no
application.properties.
Essa é uma das maiores vantagens arquiteturais do Spring AI.
5.17 Impacto no pom.xml
Se o projeto usa uma dependência específica de provedor, como o starter da OpenAI, a troca de provedor passa por alterar ou complementar essa dependência.
Ideia geral
Antes:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
Se você mudar para outro provedor compatível, trocará esse starter pelo correspondente.
5.18 Impacto no application.properties
O segundo ponto de impacto é a configuração.
Se você usa OpenAI, as propriedades têm prefixos como:
spring.ai.openai.api-key=...
spring.ai.openai.chat.options.model=...
Se trocar de provedor, o prefixo muda para o do novo fornecedor.
O que permanece semelhante?
Mesmo que os nomes exatos mudem, a lógica continua parecida:
- há uma chave de API;
- há propriedades de configuração;
- há modelos;
- há opções específicas;
- há integração via abstrações do Spring.
5.19 O que não deveria precisar mudar?
Se sua aplicação estiver bem construída sobre as abstrações do Spring AI, o ideal é que o código principal das funcionalidades permaneça praticamente igual.
Isso vale especialmente para componentes que usam:
ChatClientImageModel- classes de prompt e response do Spring
Esse ponto é o coração arquitetural do capítulo.
5.20 O risco de usar classes específicas do provedor
É possível usar classes específicas de um provedor diretamente no código, mas isso aumenta o acoplamento.
O que é acoplamento, aqui?
É quando seu código depende demais da implementação específica de um fornecedor.
Qual o problema disso?
Se você mudar de provedor, talvez tenha que:
- alterar imports;
- reescrever chamadas;
- mudar tipos de resposta;
- adaptar opções específicas;
- revisar várias classes do projeto.
Boa prática
Sempre que possível, use as classes do Spring AI:
- facilita manutenção;
- aumenta portabilidade;
- reduz impacto de troca;
- protege a arquitetura contra dependência excessiva de fornecedor.
5.21 O Spring AI como camada de abstração
Você pode pensar no Spring AI como uma camada intermediária entre:
- sua aplicação;
- e os fornecedores de IA.
Essa camada ajuda a evitar que o projeto fique “casado” com um único provedor.
5.22 Isso significa que trocar de provedor é sempre trivial?
Não necessariamente.
Embora o Spring AI ajude muito, ainda pode haver diferenças reais entre provedores, como:
- disponibilidade de determinados recursos;
- nomes e capacidades dos modelos;
- parâmetros específicos;
- formatos de resposta mais detalhados;
- custo;
- políticas de uso;
- recursos multimodais suportados.
O que isso quer dizer?
A troca tende a ser muito mais simples, mas não significa que toda funcionalidade avançada será 100% idêntica entre todos os provedores.
Mesmo assim, o ganho arquitetural continua enorme.
5.23 O que você deve aprender com esse capítulo?
Mais do que decorar a API de imagem, ele deve entender três ideias grandes:
1. IA é multimodal
Não se resume a chat.
2. O Spring AI oferece abstrações específicas para cada tipo de modelo
Como ImageModel para imagem.
3. Programar contra abstrações do Spring AI reduz acoplamento
E facilita a troca de provedor no futuro.
Essas três ideias mudam bastante a sua maturidade como desenvolvedor.
5.24 Exemplo de uso real da geração de imagem
É importante que este ebook também mostre para que isso serviria na prática.
Possíveis aplicações
- gerar capas ou ilustrações para artigos e documentação interna;
- criar mockups rápidos;
- produzir variações visuais para testes de interface;
- gerar imagens para conteúdo educacional;
- produzir material visual para marketing;
- criar ilustrações para portais internos;
- automatizar assets simples para testes.
O que observar em produção?
Mesmo que a geração de imagem funcione bem tecnicamente, ainda vale pensar em:
- custo por geração;
- tempo de resposta;
- política de armazenamento;
- expiração de URLs;
- tamanho ideal da imagem;
- política de moderação de conteúdo;
- controle de prompts enviados pelos usuários.
Essas perguntas mostram que o recurso é poderoso, mas precisa ser usado com critério.
5.25 Boas práticas desta etapa
1. Use abstrações do Spring AI sempre que possível
Isso reduz acoplamento ao provedor.
2. Separe funcionalidades por controller ou serviço
Não misture tudo em um único lugar.
3. Defina opções de imagem de acordo com o caso de uso
Nem toda geração precisa ser 1024x1024.
4. Pense no destino da imagem
Vai apenas exibir? Salvar URL? Persistir metadados?
5. Avalie o impacto da troca de provedor desde cedo
Mesmo que você não vá trocar agora.
6. Não assuma que todos os provedores oferecem exatamente os mesmos recursos
Portabilidade ajuda, mas não elimina diferenças de plataforma.
5.26 Erros comuns nesta etapa
Erro 1: achar que Spring AI serve só para chat
Não serve.
Erro 2: usar diretamente classes específicas do fornecedor sem necessidade
Isso aumenta o acoplamento.
Erro 3: ignorar o formato e tamanho da imagem
Essas decisões afetam custo, uso e experiência.
Erro 4: devolver apenas a URL sem pensar no fluxo real da aplicação
Em projetos reais, talvez seja necessário salvar ou tratar mais dados.
Erro 5: acreditar que trocar de provedor nunca exige validação
Mesmo com abstração, diferenças práticas sempre merecem teste.
5.27 Resumo do capítulo
Neste capítulo, você aprendeu que o Spring AI não se limita à geração de texto.
Vimos que:
- IA moderna é multimodal e pode trabalhar com texto, imagem e áudio;
- a geração de imagem usa classes como
ImageModel,ImagePrompteImageOptionsBuilder; - o resultado pode ser tratado como URL retornada pela API;
- usar abstrações do Spring AI reduz o acoplamento ao provedor;
- ao trocar de provedor, o impacto tende a ficar concentrado principalmente em dependências e propriedades;
- a arquitetura do Spring AI ajuda a manter o código principal mais estável.
5.28 Exercícios propostos
1. Conceitual
Explique com suas palavras o que significa multimodalidade.
2. Conceitual
Qual a diferença entre usar ChatClient e usar ImageModel?
3. Prático
Crie um endpoint de geração de imagem que receba o prompt como parâmetro.
4. Arquitetura
Por que usar abstrações do Spring AI ajuda na troca de provedor?
5. Reflexão
Se uma aplicação troca OpenAI por outro provedor suportado, quais partes do projeto tendem a ser mais impactadas?
Desafio extra
Implemente um endpoint que gere duas imagens na mesma requisição (dois prompts diferentes) e devolva um JSON com duas URLs. Trate falha de uma das gerações sem derrubar a outra (try/catch por chamada) e documente o comportamento.
5.29 Fechamento
Com este capítulo, você fecha uma etapa importante do curso entendendo que:
- o Spring AI vai além do chat;
- a geração de imagem segue a mesma filosofia de simplicidade;
- a arquitetura do framework foi pensada para reduzir dependência de fornecedor.
Esse é um excelente fechamento conceitual, porque prepara você para pensar em IA de forma mais ampla, mais arquitetural e mais profissional.
Capítulo 6 — Tool Calling no Spring AI: fazendo a IA usar ferramentas da aplicação
Objetivo do capítulo
Neste capítulo, você vai aprender:
- o que é tool calling;
- por que esse recurso muda o nível da aplicação;
- a diferença entre o modelo “responder com base no que sabe” e “usar uma ferramenta”;
- como transformar métodos Java em tools com Spring AI;
- como expor tools ao modelo usando
ChatClient; - como usar tool calling em cenários de consulta e ação;
- quais cuidados tomar ao deixar a IA acionar funcionalidades da aplicação.
Ao final, você deve entender a seguinte ideia:
uma aplicação com IA fica muito mais útil quando o modelo não apenas conversa, mas também consegue pedir acesso a funções reais do sistema.
6.1 Introdução
Até aqui, trabalhamos principalmente com prompts, parâmetros, modelos, erros e multimodalidade. Em todos esses casos, a aplicação enviava uma solicitação ao modelo e recebia uma resposta. (O MCP será tema do Capítulo 7; aqui o foco é tool calling com ferramentas definidas na sua aplicação.)
Mas existe um limite natural nesse fluxo.
O modelo pode gerar texto muito bem, mas ele não conhece automaticamente o estado atual do seu sistema, nem pode executar ações no mundo real por conta própria. Por exemplo, ele não sabe sozinho:
- a data e hora exatas do servidor;
- se existe um registro cadastrado no seu banco;
- qual é o status atual de um chamado;
- se um alarme foi configurado;
- se um e-mail foi realmente enviado;
- se um documento foi emitido no sistema.
É aqui que entra o tool calling.
6.2 O que é tool calling?
Tool calling é a capacidade de permitir que o modelo solicite o uso de uma ferramenta externa durante uma conversa.
Em linguagem simples
Pense assim:
- sem tool calling, o modelo só responde com base no que recebeu no prompt e no que “sabe” pelo treinamento;
- com tool calling, o modelo pode dizer algo como:
- “Para responder corretamente, preciso consultar esta ferramenta.”
A partir daí:
- o modelo escolhe uma tool;
- informa o nome da tool;
- envia os parâmetros necessários;
- a aplicação executa a tool;
- o resultado volta ao modelo;
- o modelo gera a resposta final usando esse resultado como contexto.
6.3 Por que isso é tão importante?
Porque esse recurso muda completamente o tipo de sistema que podemos construir.
Sem tool calling, a IA fica mais limitada a:
- explicar;
- sugerir;
- resumir;
- classificar;
- gerar texto.
Com tool calling, ela pode participar de fluxos mais úteis, como:
- consultar dados atuais;
- buscar informações do sistema;
- acionar processos;
- combinar raciocínio com dados reais;
- orquestrar pequenas automações.
6.4 A IA executa a ferramenta sozinha?
Não.
Esse ponto é essencial e precisa ficar muito claro para você.
O modelo não executa código Java diretamente.
Ele não chama o banco sozinho.
Ele não dispara métodos da aplicação por mágica.
Quem executa a tool é a aplicação.
O papel do modelo
O modelo:
- recebe a lista de tools disponíveis;
- analisa a pergunta;
- decide se precisa usar uma tool;
- informa qual tool quer usar e com quais parâmetros.
O papel da aplicação
A aplicação:
- identifica a tool solicitada;
- executa a tool com os parâmetros recebidos;
- coleta o resultado;
- envia esse resultado de volta ao modelo.
6.5 Quando usar tool calling?
Tool calling faz sentido quando a resposta depende de algo que está fora do conhecimento puro do modelo.
Exemplos de uso
Buscar informação
- hora atual;
- clima;
- status de chamado ou tarefa;
- dados cadastrais ou de perfil;
- resultado de consulta;
- prazo ou SLA retornado pelo sistema;
- agenda disponível.
Executar ação
- criar lembrete;
- disparar rotina;
- gerar documento;
- registrar evento;
- atualizar status;
- programar alarme;
- acionar um serviço interno.
6.6 O que são ToolCallback e ToolCallingManager?
Na prática, o Spring AI já resolve boa parte do trabalho operacional, então o desenvolvedor pode focar em:
- definir bem a tool;
- descrever bem o que ela faz;
- expor essa tool ao modelo;
- validar se o comportamento ficou coerente.
Em linguagem simples
ToolCallback
Representa a ferramenta que pode ser disponibilizada ao modelo.
ToolCallingManager
Cuida do processo de execução das tools.
6.7 Formas de definir tools no Spring AI
Existem duas formas principais de transformar métodos em tools:
- de forma declarativa, com
@Tool; - de forma programática, com implementações mais manuais.
Para um ebook introdutório e didático, a melhor porta de entrada é a abordagem com @Tool.
6.8 Criando uma tool com @Tool
A forma mais simples de começar é transformar um método Java em tool anotando-o com @Tool.
Exemplo simples: data e hora atual
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;
import java.time.ZonedDateTime;
@Component
public class DataHoraTools {
@Tool(description = "Obtém a data e hora atuais do sistema")
public String obterDataHoraAtual() {
return ZonedDateTime.now().toString();
}
}
O que esse código faz?
- cria uma classe Spring;
- define um método Java comum;
- marca esse método como tool;
- descreve claramente o propósito da tool.
Por que a descrição importa tanto?
Se a descrição for ruim, o modelo pode:
- não usar a tool quando deveria;
- usar a tool de forma errada;
- confundir a finalidade da ferramenta.
6.9 Entendendo os elementos da anotação @Tool
Ao usar @Tool, alguns pontos merecem destaque:
name
É o nome da tool. Se você não informar, o nome do método será usado.
description
É a descrição da ferramenta. É um dos pontos mais importantes, porque ajuda o modelo a decidir quando usar a tool.
returnDirect
Define se o resultado deve voltar direto ao cliente ou se deve primeiro voltar ao modelo.
Para o começo deste ebook, o essencial é você dominar bem description e entender a ideia de returnDirect.
6.10 Como disponibilizar a tool ao modelo
Depois de criar a tool, precisamos torná-la disponível para a conversa.
Exemplo de uso com ChatClient
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.stereotype.Service;
@Service
public class AssistenteService {
private final ChatClient chatClient;
private final DataHoraTools dataHoraTools;
public AssistenteService(ChatModel chatModel, DataHoraTools dataHoraTools) {
this.chatClient = ChatClient.create(chatModel);
this.dataHoraTools = dataHoraTools;
}
public String responder(String pergunta) {
return chatClient.prompt()
.user(pergunta)
.tools(dataHoraTools)
.call()
.content();
}
}
O que está acontecendo aqui?
- criamos o
ChatClient; - registramos a tool com
.tools(dataHoraTools); - enviamos a pergunta do usuário;
- se o modelo precisar da tool, o Spring AI cuida do fluxo.
6.11 Exemplo prático de uso
Imagine que o usuário pergunte:
Que horas são agora?
Se a tool estiver bem descrita e disponível, o modelo pode decidir usar a ferramenta obterDataHoraAtual().
Fluxo esperado
- usuário pergunta;
- modelo percebe que precisa de dado atual;
- modelo pede a tool;
- Spring executa a tool;
- o resultado volta ao modelo;
- o modelo responde algo como:
- “Agora são 14:23 no horário do sistema.”
6.12 Primeiro caso de uso: tools para buscar informação
Esse é o cenário mais simples e mais seguro para começar.
Exemplo: buscar status de chamado
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;
@Component
public class ChamadoTools {
@Tool(description = "Consulta o status atual de um chamado de suporte a partir do protocolo informado")
public String consultarStatusChamado(String protocolo) {
if ("CH-123".equals(protocolo)) {
return "Chamado CH-123 está com status: EM ANÁLISE";
}
return "Chamado não encontrado";
}
}
Serviço com ChatClient
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.stereotype.Service;
@Service
public class ChamadoAssistenteService {
private final ChatClient chatClient;
private final ChamadoTools chamadoTools;
public ChamadoAssistenteService(ChatModel chatModel, ChamadoTools chamadoTools) {
this.chatClient = ChatClient.create(chatModel);
this.chamadoTools = chamadoTools;
}
public String responder(String pergunta) {
return chatClient.prompt()
.system("Você é um assistente de suporte e deve usar tools quando precisar consultar dados reais.")
.user(pergunta)
.tools(chamadoTools)
.call()
.content();
}
}
Exemplo de pergunta
Qual o status do chamado CH-123?
O que esperamos?
O modelo deve entender que não sabe o status por conta própria e usar a tool consultarStatusChamado.
6.13 Segundo caso de uso: tools para executar ação
Agora vamos para um caso mais forte.
Exemplo: criar lembrete
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;
@Component
public class LembreteTools {
@Tool(description = "Cria um lembrete com a mensagem e o horário informados")
public String criarLembrete(String mensagem, String horario) {
return "Lembrete criado com sucesso para " + horario + ": " + mensagem;
}
}
Serviço com duas tools
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.stereotype.Service;
@Service
public class ProdutividadeAssistenteService {
private final ChatClient chatClient;
private final DataHoraTools dataHoraTools;
private final LembreteTools lembreteTools;
public ProdutividadeAssistenteService(
ChatModel chatModel,
DataHoraTools dataHoraTools,
LembreteTools lembreteTools
) {
this.chatClient = ChatClient.create(chatModel);
this.dataHoraTools = dataHoraTools;
this.lembreteTools = lembreteTools;
}
public String responder(String pergunta) {
return chatClient.prompt()
.user(pergunta)
.tools(dataHoraTools, lembreteTools)
.call()
.content();
}
}
Exemplo de pergunta
Crie um lembrete para daqui a 10 minutos me lembrar de beber água.
Fluxo provável
- o modelo percebe que precisa saber o horário atual;
- chama a tool de data/hora;
- calcula o horário desejado;
- chama a tool de criação de lembrete;
- devolve a resposta final ao usuário.
6.14 O que significa returnDirect?
Quando returnDirect=true, o resultado da tool pode ser devolvido diretamente ao cliente, sem que o modelo precise transformar esse resultado em resposta final.
Quando isso pode ser útil?
Quando você quer devolver o resultado bruto da ferramenta.
Exemplo
@Tool(description = "Busca informações resumidas de um colaborador interno pelo id", returnDirect = true)
public String buscarColaborador(Long id) {
return "Colaborador 10: Henrique Santiago";
}
O que muda?
Em vez de o modelo usar o resultado como contexto para montar uma frase, a aplicação pode devolver esse resultado diretamente.
Quando eu evitaria no começo?
Para capítulos iniciais, eu deixaria returnDirect=false na maioria dos casos, porque você primeiro precisa entender o fluxo padrão:
- modelo pede a tool;
- tool executa;
- modelo responde.
6.15 Tipos de parâmetros e retorno nas tools
As tools não precisam ficar restritas apenas a uma string simples.
Exemplo com objeto
public record ConsultaChamadoInput(String protocolo, String area) {}
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;
@Component
public class SuporteTools {
@Tool(description = "Consulta um chamado de suporte pelo protocolo e pela área informados")
public String consultarChamado(ConsultaChamadoInput input) {
return "Chamado consultado: " + input.protocolo() + " / " + input.area();
}
}
Isso abre caminho para tools mais organizadas e mais próximas do domínio real da aplicação.
6.16 Onde colocar as tools no projeto?
Em projeto pequeno
Você pode colocar tools em classes simples como:
DataHoraToolsChamadoToolsLembreteTools
Em projeto maior
O ideal é separar por responsabilidade:
CadastroToolsChamadoToolsFinanceiroToolsAgendaTools
Boa prática
A tool deve ser uma porta de acesso clara para uma capacidade do sistema.
Ela não deve virar um “super método genérico” que faz tudo.
6.17 Onde o modelo decide usar a tool?
O modelo decide usar a tool com base em três coisas:
- a pergunta do usuário;
- a descrição da tool;
- o contexto do prompt.
Por isso, muitas vezes não basta só registrar a ferramenta. Vale também orientar no system quando o modelo deve usá-la.
Exemplo de system útil
.system("Você é um assistente do sistema. Sempre que a pergunta depender de dados atuais ou ações do sistema, utilize as tools disponíveis.")
Isso ajuda bastante o comportamento.
6.18 O que pode dar errado com tools?
Muita coisa pode dar errado se a tool for mal projetada.
Problemas comuns
1. Descrição ruim
O modelo não entende quando usar a tool.
2. Tool ambígua
Duas ferramentas parecem fazer a mesma coisa.
3. Nome pouco claro
Aumenta a chance de uso incorreto.
4. Ação perigosa demais
O modelo pode pedir algo que deveria passar por validação humana.
5. Retorno confuso
Se a tool devolve algo bagunçado, a resposta final também pode ficar ruim.
6.19 Cuidados de segurança com tool calling
Tool calling é poderoso, mas também pode ser perigoso se mal usado.
Nunca exponha sem critério tools que:
- apagam dados;
- alteram registros sensíveis;
- disparam pagamentos;
- aprovam processos críticos;
- executam comandos administrativos;
- acessam dados pessoais sem controle.
Melhor abordagem
Comece com tools de leitura e baixo risco, como:
- hora atual;
- status de chamado;
- consulta cadastral de baixo risco;
- agenda disponível;
- artigo na base de conhecimento interna.
Depois, avance para ações com mais responsabilidade e, se necessário, inclua camadas extras de validação.
6.20 Tool calling não substitui regra de negócio
A IA não deve assumir o papel da regra de negócio central.
A tool chama sua aplicação.
Mas a sua aplicação continua responsável por:
- validações;
- regras;
- permissões;
- restrições;
- auditoria;
- consistência.
Ou seja: a IA orquestra a necessidade. O sistema continua sendo o dono da regra.
6.21 Exemplo de controller para uso prático
Para fechar a parte de implementação, aqui vai um exemplo simples de controller usando tool calling.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/assistente")
public class AssistenteController {
private final ChamadoAssistenteService chamadoAssistenteService;
public AssistenteController(ChamadoAssistenteService chamadoAssistenteService) {
this.chamadoAssistenteService = chamadoAssistenteService;
}
@GetMapping
public String responder(@RequestParam String pergunta) {
return chamadoAssistenteService.responder(pergunta);
}
}
Exemplo de chamada
GET /assistente?pergunta=Qual o status do chamado CH-123?
Resultado esperado
Se o fluxo estiver bem configurado, o modelo vai usar a tool de consulta de chamado e responder com base no resultado real da aplicação.
6.22 O que você precisa guardar deste capítulo?
Mais do que decorar a anotação @Tool, ele precisa entender estas ideias:
- o modelo não sabe tudo e não executa ações sozinho;
- tools conectam o modelo às capacidades reais da aplicação;
- o Spring AI já oferece o mecanismo para isso;
- descrição da tool é parte crítica da implementação;
- tool calling serve tanto para consulta quanto para ação;
- o sistema continua responsável por segurança e regra de negócio.
6.23 Boas práticas desta etapa
1. Dê descrições excelentes para as tools
A descrição é fundamental para o modelo usar a tool corretamente.
2. Comece com tools simples
Exemplo: data/hora, consulta de status de chamado, consulta cadastral simples.
3. Use nomes claros
Ferramenta precisa ser facilmente identificável.
4. Separe tools por domínio
Não concentre tudo em uma única classe gigante.
5. Use system para orientar o modelo
Ajuda bastante na decisão de uso.
6. Tenha cuidado com tools de ação
Nem tudo deve ser acionado automaticamente.
7. Faça logs e observabilidade
Especialmente em tools que tocam sistema real.
6.24 Erros comuns nesta etapa
Erro 1: achar que a IA vai usar a tool automaticamente sem boa descrição
Não necessariamente vai.
Erro 2: criar tool genérica demais
Isso confunde o modelo.
Erro 3: expor ação sensível cedo demais
Isso aumenta risco.
Erro 4: esquecer que a aplicação executa a tool
Tool calling não elimina responsabilidade do backend.
Erro 5: misturar tool com regra de negócio pesada no controller
A tool deve chamar lógica organizada, não virar um atalho bagunçado.
6.25 Resumo do capítulo
Neste capítulo, você aprendeu que o Spring AI permite expor ferramentas da aplicação ao modelo por meio de tool calling. Vimos que:
- tools conectam o modelo às capacidades reais da aplicação;
- o modelo não executa a ferramenta sozinho, a aplicação executa;
@Toolpermite transformar métodos Java em tools de forma simples;- a descrição da tool é essencial para o modelo entender quando usá-la;
ChatClientpode receber tools diretamente com.tools(...);- tool calling é útil tanto para busca de informação quanto para execução de ação;
- a aplicação continua responsável por segurança, validação e regra de negócio.
6.26 Exercícios propostos
1. Conceitual
Explique com suas palavras a diferença entre uma resposta normal do modelo e uma resposta baseada em tool calling.
2. Conceitual
Por que a descrição de uma tool é tão importante?
3. Prático
Crie uma tool chamada consultarResumoCadastral e descreva o que ela deveria fazer (sem expor dados sensíveis).
4. Prático
Monte um ChatClient que disponibilize duas tools para o modelo:
- uma para obter data/hora;
- outra para consultar status de chamado.
5. Reflexão
Por que tools de ação exigem mais cuidado do que tools de consulta?
Desafio extra
Implemente uma tool buscarCep(String cep) que valide o formato (apenas dígitos / tamanho) antes de chamar qualquer API externa e retorne erro claro ao modelo se for inválido. Escreva 3 perguntas em linguagem natural e mostre quando o modelo escolhe a tool ou responde só com texto.
6.27 Fechamento
Com este capítulo, você sai do uso “reativo” da IA e entra em um cenário mais poderoso: a IA agora pode interagir com capacidades reais da aplicação.
Esse é um passo muito importante porque abre caminho para:
- assistentes conectados ao sistema;
- automações orientadas por linguagem natural;
- fluxos mais inteligentes;
- aplicações mais úteis no mundo real.
Capítulo 7 — MCP no Spring AI: conectando modelos, ferramentas e recursos externos por um protocolo padronizado
Objetivo do capítulo
Neste capítulo, você vai aprender:
- o que é MCP;
- por que ele está ganhando tanta importância em aplicações com IA;
- como o MCP se relaciona com tools, resources e prompts;
- a diferença entre cliente MCP e servidor MCP;
- quais transportes o protocolo suporta;
- como consumir um servidor MCP em uma aplicação Spring AI;
- como expor um servidor MCP com Spring Boot;
- quando usar MCP em vez de integrações manuais;
- como isso se conecta com tool calling.
Ao final, você deve entender a seguinte ideia:
o MCP é uma forma padronizada de ligar modelos de IA a capacidades externas sem criar uma integração improvisada para cada caso.
7.1 Introdução
No capítulo anterior, vimos tool calling: o modelo pode pedir o uso de uma ferramenta, e a aplicação executa essa ferramenta.
Aquilo já representa um grande salto. Mas surge uma nova pergunta:
“E se eu quiser que minha aplicação converse com ferramentas externas de forma mais padronizada?”
Por exemplo:
- um servidor que expõe arquivos;
- um serviço que oferece tools de clima;
- um sistema que expõe recursos internos;
- um catálogo de prompts reutilizáveis;
- uma fonte remota de capabilities que podem ser descobertas em tempo de execução.
Se cada integração for feita manualmente, o projeto pode ficar:
- muito acoplado;
- difícil de manter;
- inconsistente entre fornecedores;
- complicado de evoluir.
É exatamente esse tipo de problema que o MCP busca resolver.
7.2 O que é MCP?
MCP significa Model Context Protocol.
Em linguagem simples
É um protocolo que define uma forma padronizada de:
- descobrir ferramentas;
- acessar recursos;
- usar prompts;
- trocar mensagens entre cliente e servidor;
- permitir que aplicações com IA se conectem a capacidades externas sem inventar uma solução diferente para cada integração.
Uma analogia útil
Pense no MCP como uma espécie de “tomada padrão” para IA.
Sem MCP, cada ferramenta precisaria de um encaixe diferente.
Com MCP, você passa a ter uma interface comum para conectar capacidades externas.
7.3 Por que o MCP é importante?
O MCP é importante porque ajuda a resolver um problema clássico de arquitetura:
como conectar IA a sistemas externos de forma organizada e reaproveitável?
Sem um protocolo padronizado, cada integração pode exigir:
- contrato próprio;
- modo próprio de descoberta;
- serialização própria;
- autenticação própria;
- convenções próprias;
- adaptação manual em cada projeto.
Com MCP, a ideia é ter uma interface comum para expor capacidades como:
- Tools
- Resources
- Prompts
7.4 Qual a relação entre MCP e Tool Calling?
Esse ponto é muito importante.
No capítulo anterior, vimos o modelo usando tools definidas localmente pela aplicação.
Com MCP, essas tools podem estar fora da aplicação, em um servidor MCP.
Em outras palavras
- Tool calling é a capacidade do modelo de pedir uma ferramenta;
- MCP é uma forma padronizada de disponibilizar essas ferramentas, recursos e prompts.
Então podemos pensar assim:
tool calling responde à pergunta “como o modelo usa uma ferramenta?”
MCP responde à pergunta “como essa ferramenta é exposta e descoberta de forma padronizada?”
7.5 Cliente MCP e servidor MCP
O MCP tem dois papéis principais.
Cliente MCP
É quem se conecta a um servidor MCP para consumir as capacidades expostas por ele.
Servidor MCP
É quem expõe capacidades para clientes.
Analogia prática
- o cliente MCP é quem quer usar recursos;
- o servidor MCP é quem disponibiliza esses recursos.
7.6 A arquitetura em camadas do MCP
O MCP pode ser entendido em três camadas:
- Camada de Cliente/Servidor
- Camada de Sessão
- Camada de Transporte
O que isso significa de forma didática?
Camada de Cliente/Servidor
É a camada onde moram os conceitos principais:
McpClientMcpServer
Camada de Sessão
Gerencia o estado da comunicação.
Camada de Transporte
Cuida da troca real de mensagens.
O que você precisa guardar?
Mesmo que ele não programe diretamente nessas camadas no começo, é importante entender que o MCP não é só uma anotação. Existe uma estrutura protocolar por trás.
7.7 Quais capacidades um servidor MCP pode expor?
Um servidor MCP pode expor várias capabilities.
As mais importantes para começar
- Tools — ferramentas invocáveis por modelos;
- Resources — acesso padronizado a conteúdos e dados;
- Prompts — templates de prompt expostos aos clientes.
Esses três já explicam muito bem o valor do MCP.
7.8 O que são resources no MCP?
Como esse termo aparece bastante, vale explicar.
Resource
É uma forma padronizada de expor um conteúdo acessável por URI.
Exemplo conceitual:
config://temaarquivo://manual-internocliente://123faq://matricula
O servidor MCP pode disponibilizar esse recurso, e o cliente pode acessá-lo de forma estruturada.
7.9 O que são prompts no MCP?
Além de tools e resources, o MCP também pode expor prompts.
Em linguagem simples
Um servidor MCP pode disponibilizar prompts prontos ou templates que outros clientes podem usar.
Isso é útil quando se quer centralizar instruções, fluxos ou comportamentos reutilizáveis.
7.10 Quais transportes o MCP suporta?
O MCP suporta múltiplos mecanismos de transporte. Entre os mais conhecidos estão:
- STDIO
- SSE
- Streamable-HTTP
Explicando cada um
STDIO
Comunicação por entrada e saída padrão. É muito usada quando o servidor roda como processo local.
SSE
Server-Sent Events, útil para atualizações em tempo real.
Streamable-HTTP
Modelo mais moderno de comunicação HTTP com possibilidade de mensagens múltiplas.
O que você precisa entender?
Não precisa dominar todos os transportes agora.
Mas precisa saber que o MCP não depende de um único meio de conexão.
7.11 Quando usar MCP em vez de integração manual?
Essa é uma pergunta central.
MCP faz mais sentido quando:
- você quer consumir ferramentas externas já expostas em protocolo padrão;
- deseja reutilizar capabilities em múltiplos clientes;
- quer evitar reinventar contrato para cada integração;
- precisa descobrir tools dinamicamente;
- quer separar melhor cliente e fornecedor de capabilities.
Integração manual ainda pode fazer sentido quando:
- o caso é simples;
- você controla totalmente os dois lados;
- não há necessidade de padronização com outros clientes;
- a solução seria menor que o custo de introduzir MCP.
Regra prática
Se a integração é pequena e totalmente local, @Tool pode bastar.
Se você quer padronização, descoberta e interoperabilidade, MCP começa a fazer muito sentido.
7.12 Spring AI e MCP no lado cliente
Quando sua aplicação é cliente MCP, ela consome capacidades expostas por um servidor MCP.
O que isso traz de vantagem?
Você consegue conectar sua aplicação Spring a um servidor MCP usando auto-configuração, em vez de montar tudo manualmente.
Tipos de uso
- consumir tools de um servidor externo;
- acessar resources remotos;
- reutilizar prompts disponibilizados por outra aplicação.
7.13 Propriedades do cliente MCP
Ao configurar um cliente MCP, algumas propriedades fazem bastante sentido para você conhecer.
As mais importantes para começar
spring.ai.mcp.client.type
Define se o cliente será síncrono ou assíncrono.
spring.ai.mcp.client.request-timeout
Define timeout das requisições MCP.
spring.ai.mcp.client.toolcallback.enabled
Ativa a integração das tools MCP com o mecanismo de tool calling do Spring AI.
O que isso significa?
Significa que o cliente MCP pode ser ajustado conforme o comportamento da aplicação e pode inclusive converter tools remotas em ferramentas utilizáveis pelo ChatClient.
7.14 Exemplo 1 — Consumindo um servidor MCP por STDIO
Uma forma de consumir um servidor MCP é configurando uma conexão por STDIO.
Exemplo de configuração
spring:
ai:
mcp:
client:
stdio:
connections:
filesystem-server:
command: /path/to/server
args:
- --mode=production
env:
API_KEY: minha-chave
DEBUG: "true"
O que isso significa?
Sua aplicação Spring vira cliente MCP e sobe um processo externo que funciona como servidor MCP, comunicando-se por STDIO.
Quando isso é útil?
- ferramenta local;
- servidor rodando na mesma máquina;
- integração com utilitários de processo;
- cenários de desktop ou automação local.
7.15 Exemplo 2 — Consumindo um servidor MCP por Streamable-HTTP
Outra opção é conectar-se a um servidor MCP remoto via HTTP.
Exemplo
spring:
ai:
mcp:
client:
streamable-http:
connections:
server1:
url: http://localhost:8080
server2:
url: http://otherserver:8081
endpoint: /custom-sse
O que isso significa?
Sua aplicação cliente pode conectar-se a servidores MCP remotos via HTTP.
Quando isso é útil?
- arquitetura distribuída;
- microserviços;
- servidores MCP independentes;
- ambientes cloud.
7.16 As tools MCP podem virar ToolCallbacks no Spring AI
Esse é um ponto muito importante para conectar MCP com o capítulo anterior.
Quando o cliente MCP está configurado para isso, as tools disponíveis em um servidor MCP podem ser transformadas em ferramentas utilizáveis pela aplicação Spring AI.
O que isso significa na prática?
Significa que um servidor MCP pode expor tools, e sua aplicação cliente Spring AI pode reaproveitar essas tools dentro do fluxo de tool calling.
Esse é um dos pontos mais fortes do MCP no ecossistema Spring AI.
7.17 Exemplo de uso das tools MCP no cliente
Um uso comum é recuperar as tools expostas pelo servidor MCP e passá-las ao ChatClient.
Exemplo didático
import org.springframework.ai.tool.ToolCallback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class McpToolService {
@Autowired
private SyncMcpToolCallbackProvider toolCallbackProvider;
public ToolCallback[] obterToolsDisponiveis() {
return toolCallbackProvider.getToolCallbacks();
}
}
O que isso faz?
Recupera as tools expostas pelos servidores MCP conectados.
O próximo passo natural
Essas tools podem então ser passadas ao ChatClient, do mesmo jeito que fizemos com tools locais no capítulo anterior.
7.18 Exemplo prático: usando tools MCP com ChatClient
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.stereotype.Service;
@Service
public class AssistenteMcpService {
private final ChatClient chatClient;
private final SyncMcpToolCallbackProvider toolCallbackProvider;
public AssistenteMcpService(ChatModel chatModel, SyncMcpToolCallbackProvider toolCallbackProvider) {
this.chatClient = ChatClient.create(chatModel);
this.toolCallbackProvider = toolCallbackProvider;
}
public String responder(String pergunta) {
ToolCallback[] tools = toolCallbackProvider.getToolCallbacks();
return chatClient.prompt()
.system("Você é um assistente que pode usar tools MCP quando precisar de dados externos.")
.user(pergunta)
.tools(tools)
.call()
.content();
}
}
O que esse exemplo mostra?
- a aplicação consome um servidor MCP;
- as tools desse servidor viram
ToolCallbacks; - o
ChatClientpassa a poder usá-las em conversa com o modelo.
Esse é um ótimo ponto de conexão entre tool calling local e capabilities remotas via MCP.
7.19 Spring AI e MCP no lado servidor
Agora vamos inverter a visão.
Em vez de consumir um servidor MCP, podemos criar um servidor MCP com Spring Boot.
O que isso permite?
Transformar sua aplicação Spring em um servidor MCP que expõe:
- tools;
- resources;
- prompts;
- e outras capacidades úteis para clientes MCP.
7.20 Configurando um servidor MCP
A configuração do servidor depende da forma como você quer expor o protocolo.
Exemplo para STDIO
spring.ai.mcp.server.stdio=true
Exemplo para Streamable-HTTP
spring.ai.mcp.server.protocol=STREAMABLE
O que você deve entender?
O protocolo e a forma de exposição precisam estar coerentes com o tipo de integração que você deseja.
7.21 Anotações do lado servidor MCP
No lado servidor, o Spring AI permite desenvolvimento declarativo com anotações específicas.
Anotações principais
@McpTool@McpResource@McpPrompt
Essas anotações ajudam a expor capacidades do servidor de forma clara e organizada.
7.22 Exemplo 3 — Criando um servidor MCP com @McpTool
Exemplo
import org.springframework.ai.mcp.annotation.McpTool;
import org.springframework.ai.mcp.annotation.McpToolParam;
import org.springframework.stereotype.Component;
@Component
public class CalculatorTools {
@McpTool(name = "add", description = "Somar dois números")
public int add(
@McpToolParam(description = "Primeiro número", required = true) int a,
@McpToolParam(description = "Segundo número", required = true) int b) {
return a + b;
}
}
O que isso faz?
Sua aplicação Spring passa a expor uma tool MCP chamada add, acessível a clientes MCP.
7.23 Exemplo 4 — Expondo um resource com @McpResource
Exemplo didático
import org.springframework.ai.mcp.annotation.McpResource;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class ConfigResources {
private final Map<String, String> configData = Map.of(
"tema", "escuro",
"idioma", "pt-BR"
);
@McpResource(uri = "config://{key}", name = "Configuration")
public String getConfig(String key) {
return configData.getOrDefault(key, "não encontrado");
}
}
O que isso ensina?
O servidor MCP pode expor recursos acessáveis por URI, e não apenas tools executáveis.
7.24 Exemplo 5 — Expondo prompts com @McpPrompt
Exemplo didático
import org.springframework.ai.mcp.annotation.McpPrompt;
import org.springframework.stereotype.Component;
@Component
public class PromptTemplates {
@McpPrompt(name = "triar-chamado", description = "Prompt para triagem de chamados de suporte")
public String triarChamadoPrompt() {
return """
Você classifica chamados de suporte em uma única etiqueta de fila.
Responda apenas com uma das categorias válidas:
Acesso e autenticação, Bug ou estabilidade, Faturamento e cobrança, Outros.
""";
}
}
O que isso mostra?
O servidor MCP também pode centralizar prompts reutilizáveis para outros clientes.
7.25 Auto-configuração do servidor MCP
Em uma aplicação Spring Boot, a auto-configuração pode detectar automaticamente os beans anotados e registrá-los no servidor MCP.
Exemplo de aplicação principal
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
}
Configuração mínima
spring:
ai:
mcp:
server:
type: SYNC
annotation-scanner:
enabled: true
7.26 Cliente MCP ou servidor MCP: quando usar cada um?
Use cliente MCP quando:
- sua aplicação quer consumir capabilities expostas por outro sistema;
- você quer usar tools/resources remotos;
- deseja integrar-se a servidores MCP prontos.
Use servidor MCP quando:
- sua aplicação quer expor suas próprias capabilities;
- você quer disponibilizar tools, resources ou prompts para outros clientes;
- deseja transformar um serviço Spring em fonte padronizada de contexto e ação.
Em alguns cenários, você pode ter os dois
Uma aplicação pode:
- consumir um servidor MCP externo;
- e também expor suas próprias capabilities como servidor MCP.
7.27 Sync e Async no MCP
Clientes e servidores MCP podem operar em modo síncrono ou assíncrono.
O que isso significa?
Você precisa manter coerência com o modelo de programação da sua aplicação.
- se a aplicação é mais simples e direta, o modo síncrono costuma bastar;
- se há fluxos reativos ou necessidade de maior escala, o modo assíncrono pode fazer mais sentido.
7.28 Benefícios arquiteturais do MCP
Principais benefícios
1. Padronização
Você deixa de criar uma integração artesanal para cada capability.
2. Descoberta de ferramentas
Clientes podem descobrir tools expostas por servidores.
3. Reuso
Uma capability pode atender múltiplos clientes.
4. Desacoplamento
O cliente não precisa conhecer detalhes internos do servidor.
5. Evolução mais saudável
O protocolo ajuda a manter contratos mais consistentes.
6. Integração com Spring AI
Tools MCP podem alimentar o fluxo de tool calling do ChatClient.
7.29 Quando o MCP pode ser “demais”?
Nem todo projeto precisa começar com MCP.
Pode ser excesso quando:
- o sistema é pequeno;
- há uma única integração simples;
- você controla totalmente os dois lados;
@Toollocal já resolve com clareza.
Faz mais sentido quando:
- há múltiplas capabilities;
- você quer interoperabilidade;
- a solução precisa crescer;
- vários clientes consumirão as mesmas ferramentas;
- o padrão de exposição traz benefício real.
7.30 Cuidados práticos ao usar MCP
1. Não comece pelo transporte, comece pelo problema
Escolha STDIO, SSE ou HTTP só depois de entender o caso de uso.
2. Mantenha tools e resources claros
Protocolo padronizado não salva design ruim.
3. Documente bem as capacidades expostas
Nome ruim e descrição ruim continuam sendo problema.
4. Pense em segurança
Expor capabilities externas exige responsabilidade.
5. Use observabilidade
Especialmente quando há múltiplas conexões e ferramentas externas.
7.31 Erros comuns nesta etapa
Erro 1: achar que MCP substitui todo tipo de integração
Ele ajuda muito, mas não é solução universal.
Erro 2: usar MCP sem necessidade real
Às vezes uma tool local basta.
Erro 3: confundir cliente e servidor
Consumir capabilities e expor capabilities são papéis diferentes.
Erro 4: não pensar no transporte
O meio de comunicação impacta bastante a arquitetura.
Erro 5: não conectar MCP ao tool calling
Um dos maiores ganhos é justamente integrar as tools MCP ao fluxo do ChatClient.
7.32 Resumo do capítulo
Neste capítulo, você aprendeu que o Model Context Protocol (MCP) é uma forma padronizada de conectar modelos de IA a ferramentas, recursos e prompts externos. Vimos que:
- o MCP funciona como uma ponte padronizada entre modelos e sistemas externos;
- o Spring AI pode atuar tanto como cliente quanto como servidor MCP;
- o cliente MCP pode consumir tools, resources e prompts;
- o servidor MCP pode expor capabilities reutilizáveis;
- tools MCP podem ser integradas ao mecanismo de tool calling do Spring AI;
- o protocolo ajuda a reduzir acoplamento e melhorar interoperabilidade em cenários mais complexos.
7.33 Exercícios propostos
1. Conceitual
Explique com suas palavras a diferença entre tool calling e MCP.
2. Conceitual
Qual a diferença entre cliente MCP e servidor MCP?
3. Prático
Escreva um exemplo de configuração YAML para um cliente MCP via streamable-http.
4. Prático
Crie uma classe com @McpTool para somar dois números.
5. Reflexão
Em que cenário você escolheria @Tool local em vez de MCP?
Desafio extra
Esboce (em pseudocódigo ou projeto mínimo) um servidor MCP que exponha uma tool horaServidor() e consuma essa tool em um ChatClient com toolcallback.enabled=true. Documente qual transporte você usou (STDIO ou HTTP) e por quê.
7.34 Fechamento
Com este capítulo, você passa a enxergar uma camada ainda mais madura das aplicações com IA. Agora você entende que não basta ter tools locais; também é possível trabalhar com um protocolo padronizado para descobrir e consumir capacidades externas.
Esse capítulo prepara muito bem o terreno para o próximo passo: RAG — Retrieval Augmented Generation, em que o modelo responde com base em contexto recuperado de documentos e fontes externas.
Capítulo 8 — RAG no Spring AI: fazendo a IA responder com base em contexto recuperado
Objetivo do capítulo
Neste capítulo, você vai aprender:
- o que é RAG;
- por que RAG é um dos padrões mais importantes em aplicações com IA;
- quais problemas ele resolve;
- como funciona o fluxo de recuperação de contexto;
- o que é um
VectorStore; - como usar o
QuestionAnswerAdvisor; - como aplicar filtros dinâmicos na busca;
- como personalizar o template usado no enriquecimento do prompt;
- como funciona o
RetrievalAugmentationAdvisor; - como pensar RAG de forma arquitetural e não apenas como “mais um recurso”.
Ao final, você deve entender esta ideia:
quando o modelo precisa responder com base em conhecimento específico, atualizado ou interno da aplicação, RAG costuma ser muito mais adequado do que confiar apenas no conhecimento do modelo.
8.1 Introdução
Até aqui, trabalhamos com prompts, parâmetros, modelos, multimodalidade, tools e MCP. Em todos esses capítulos, ficou claro que a IA pode ser muito útil dentro de uma aplicação.
Mas existe um problema importante:
o modelo não sabe automaticamente tudo o que a sua empresa, sistema ou documento interno sabe.
Mesmo um modelo poderoso pode ter limitações em situações como:
- responder com base em documentos internos;
- lidar com políticas atualizadas recentemente;
- consultar textos longos demais para o prompt bruto;
- manter precisão factual em conhecimento muito específico;
- responder sobre dados que não fizeram parte do treinamento.
É aqui que entra o RAG.
8.2 O que é RAG?
RAG significa Retrieval Augmented Generation.
Em linguagem simples
RAG é uma estratégia em que, antes de pedir a resposta ao modelo, a aplicação:
- recebe a pergunta do usuário;
- busca documentos ou trechos relevantes em uma base externa;
- injeta esse contexto recuperado no prompt;
- só então pede ao modelo que responda.
Ou seja, o modelo não responde apenas com base no que ele “já sabe”. Ele responde com base em contexto recuperado na hora.
Ideia principal
Sem RAG:
- usuário pergunta;
- modelo responde com base em treinamento + prompt atual.
Com RAG:
- usuário pergunta;
- sistema busca conteúdo relevante;
- modelo responde com base nesse conteúdo + pergunta.
Essa diferença é enorme do ponto de vista de confiabilidade e de valor entregue ao usuário.
8.3 Por que RAG é tão importante?
Porque muitos problemas reais não se resolvem apenas com prompt bom.
Exemplos:
- chatbot de empresa com FAQ interno;
- assistente jurídico com base documental;
- sistema escolar com regulamentos e material didático;
- portal de suporte com base de conhecimento;
- aplicação médica com protocolos internos;
- sistema financeiro com regras do negócio;
- help desk com documentação operacional.
Se você tentar resolver isso apenas com prompt manual, surgem vários problemas:
- contexto insuficiente;
- respostas genéricas;
- risco de alucinação;
- limitação de tamanho do prompt;
- dificuldade de manter o conhecimento atualizado.
RAG resolve esse problema trazendo conhecimento relevante no momento da pergunta.
8.4 Qual problema o RAG resolve de verdade?
Você precisa entender isso com clareza.
RAG não existe apenas para “melhorar a resposta”.
Ele existe para resolver três problemas centrais:
1. Conhecimento específico
O modelo pode não ter sido treinado com aquele conteúdo.
2. Conhecimento atualizado
O documento pode ter sido criado depois do treinamento do modelo.
3. Conhecimento grande demais
Mesmo que você tenha o conteúdo, talvez ele seja grande demais para sempre mandar inteiro no prompt.
8.5 Exemplo intuitivo: sem RAG vs com RAG
Cenário
Você tem uma escola e quer responder:
“Qual é a política de reposição de aulas do curso de programação?”
Sem RAG
O modelo pode:
- inventar uma política genérica;
- responder com algo plausível, mas errado;
- dizer algo que parece profissional, mas não veio do seu regulamento real.
Com RAG
A aplicação:
- busca o trecho correto do regulamento;
- injeta esse trecho no contexto;
- pede que o modelo responda com base nesse conteúdo.
Resultado:
- mais precisão;
- mais aderência ao documento;
- menos alucinação;
- mais segurança operacional.
8.6 Como o fluxo de RAG funciona?
O fluxo básico de RAG pode ser explicado assim:
- o usuário faz uma pergunta;
- a pergunta é transformada em algo pesquisável;
- o sistema busca documentos semelhantes em uma base vetorial;
- os trechos encontrados são anexados ao contexto;
- o modelo recebe:
- a pergunta;
- o contexto recuperado;
- o modelo gera a resposta.
8.7 O que é um VectorStore?
Para RAG funcionar bem, normalmente precisamos de uma base especializada em busca semântica. No Spring AI, essa abstração aparece como VectorStore.
Em linguagem simples
Um VectorStore é um repositório preparado para armazenar documentos em forma vetorial e recuperar os trechos mais semanticamente parecidos com a pergunta do usuário.
Por que isso é diferente de uma busca comum por palavra?
Porque a ideia não é apenas procurar texto idêntico.
A ideia é encontrar conteúdo semanticamente relacionado.
Exemplo:
Pergunta do usuário:
Como funciona a reposição de aula?
Mesmo que o documento tenha escrito:
Você poderá remarcar a aula prática em caso de ausência justificada.
uma busca vetorial pode entender que há relação entre esses textos, mesmo sem palavra exata igual.
8.8 O Spring AI suporta RAG de que formas?
O Spring AI suporta RAG de duas formas principais:
- com fluxos prontos via Advisor API;
- com arquitetura modular para montar fluxos customizados.
Para este ebook, isso é excelente
Porque podemos ensinar RAG em dois níveis:
Nível 1 — mais simples
Usar um advisor pronto, como:
QuestionAnswerAdvisor
Nível 2 — mais avançado
Montar fluxos mais completos com:
RetrievalAugmentationAdvisor- retriever
- augmenter
- filtros
- template customizado
8.9 Dependência para usar advisors com vector store
Para usar QuestionAnswerAdvisor e outros advisors ligados a vector store, você precisará das dependências apropriadas no projeto.
Exemplo de dependência
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
O que isso significa?
Que o suporte a esses advisors pode exigir um módulo específico além do starter básico.
8.10 Primeiro caminho: QuestionAnswerAdvisor
Esse é o caminho mais simples e mais didático para começar.
Exemplo básico
ChatResponse response = ChatClient.builder(chatModel)
.build()
.prompt()
.advisors(QuestionAnswerAdvisor.builder(vectorStore).build())
.user(userText)
.call()
.chatResponse();
O que esse código faz?
- cria um
ChatClient; - adiciona o
QuestionAnswerAdvisor; - envia a pergunta do usuário;
- o advisor consulta o
VectorStore; - os documentos encontrados são anexados ao contexto;
- o modelo responde com base nesse contexto.
8.11 Explicando o QuestionAnswerAdvisor de forma prática
Imagine que você tenha uma base de conhecimento com documentos já carregados no banco vetorial.
Quando o usuário pergunta:
Quais cursos têm aula na sexta?
o QuestionAnswerAdvisor:
- busca os trechos mais relevantes sobre grade de aulas;
- junta esse conteúdo com a pergunta;
- envia tudo ao modelo.
O modelo então responde com muito mais chance de aderir ao conteúdo real.
O que você precisa guardar?
O advisor funciona como uma camada automática de enriquecimento de contexto.
8.12 Configurando threshold e quantidade de resultados
O QuestionAnswerAdvisor pode ser configurado com um SearchRequest, incluindo:
similarityThresholdtopK
Exemplo
var qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(
SearchRequest.builder()
.similarityThreshold(0.8d)
.topK(6)
.build()
)
.build();
O que isso significa?
similarityThreshold(0.8d)
Define o nível mínimo de similaridade para considerar um documento relevante.
topK(6)
Define que queremos recuperar os 6 resultados mais relevantes.
Por que isso importa?
Porque RAG não é só “buscar qualquer coisa”.
É importante controlar a qualidade do contexto recuperado.
8.13 O perigo de contexto demais ou contexto de menos
Se você recuperar contexto de menos:
- a resposta pode ficar incompleta;
- o modelo pode não ter base suficiente.
Se você recuperar contexto demais:
- o prompt pode ficar poluído;
- o modelo pode perder foco;
- custo e latência podem aumentar;
- contexto irrelevante pode atrapalhar a resposta.
Por isso, parâmetros como topK e similarityThreshold são tão importantes.
8.14 Filtros dinâmicos na busca
Um recurso muito poderoso em RAG é a possibilidade de filtrar dinamicamente os documentos buscados.
Exemplo
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder().build())
.build()
)
.build();
String content = chatClient.prompt()
.user("Please answer my question XYZ")
.advisors(a -> a.param(QuestionAnswerAdvisor.FILTER_EXPRESSION, "type == 'Spring'"))
.call()
.content();
O que isso significa?
Você pode restringir a busca vetorial em tempo real.
Exemplo prático de escola
Imagine que sua base vetorial tem documentos de várias áreas:
- programação;
- informática básica;
- robótica;
- design.
Você pode filtrar em runtime para buscar apenas documentos de programação.
Isso deixa o RAG muito mais preciso.
8.15 Por que filtros dinâmicos são tão valiosos?
Porque muitas aplicações têm base de conhecimento heterogênea.
Exemplos:
- documentos por setor;
- conteúdo por idioma;
- dados por organização ou tenant;
- FAQs por módulo ou linha de negócio;
- políticas por unidade.
Se você não filtrar, a busca pode recuperar contexto irrelevante.
Com filtro, você reduz ruído e melhora a precisão da resposta.
8.16 Personalizando o template do contexto recuperado
O QuestionAnswerAdvisor pode usar um template customizado para combinar:
- a pergunta do usuário;
- o contexto recuperado.
Exemplo didático
PromptTemplate customPromptTemplate = PromptTemplate.builder()
.template("""
{query}
Context information is below.
---------------------
{question_answer_context}
---------------------
Given the context information and no prior knowledge, answer the query.
Follow these rules:
1. If the answer is not in the context, just say that you don't know.
2. Do not invent information.
""")
.build();
Por que isso é poderoso?
Porque RAG não é só recuperação de contexto.
Também importa como esse contexto é entregue ao modelo.
8.17 Exemplo prático com template customizado
QuestionAnswerAdvisor qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
.promptTemplate(customPromptTemplate)
.build();
String response = ChatClient.builder(chatModel)
.build()
.prompt("Quais são as regras de reposição de aula?")
.advisors(qaAdvisor)
.call()
.content();
O que esse exemplo mostra?
- você usa RAG;
- mas controla como o contexto entra no prompt;
- e ainda impõe regras explícitas contra alucinação.
Isso é excelente para aplicações corporativas.
8.18 O que é o RetrievalAugmentationAdvisor?
Depois do caminho mais simples, existe uma abordagem mais modular chamada RetrievalAugmentationAdvisor.
O que isso quer dizer?
Se o QuestionAnswerAdvisor é uma porta de entrada mais simples, o RetrievalAugmentationAdvisor já aponta para uma visão mais arquitetural do RAG.
8.19 Exemplo de Naive RAG com RetrievalAugmentationAdvisor
Exemplo
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(
VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.50)
.vectorStore(vectorStore)
.build()
)
.build();
String answer = chatClient.prompt()
.advisors(retrievalAugmentationAdvisor)
.user(question)
.call()
.content();
O que isso faz?
- define um document retriever;
- liga esse retriever a um vector store;
- configura o advisor de RAG;
- usa o advisor no
ChatClient.
O que é “naive RAG”?
É a forma mais direta:
- recuperar documentos relevantes;
- anexar contexto;
- pedir resposta ao modelo.
8.20 O que acontece quando não há contexto recuperado?
Por padrão, em fluxos mais rigorosos de RAG, o sistema pode optar por não responder quando não houver contexto suficiente.
Por quê?
Porque uma armadilha comum em IA é deixar o modelo responder mesmo sem contexto suficiente.
Em um sistema sensível, isso pode aumentar o risco de:
- alucinação;
- resposta genérica;
- falsa segurança.
8.21 Permitindo contexto vazio
Em alguns cenários, pode fazer sentido permitir contexto vazio.
Exemplo
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(
VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.50)
.vectorStore(vectorStore)
.build()
)
.queryAugmenter(
ContextualQueryAugmenter.builder()
.allowEmptyContext(true)
.build()
)
.build();
Quando isso pode fazer sentido?
Quando você quer que o modelo ainda tente responder mesmo sem recuperação relevante.
Quando eu teria cuidado?
Em aplicações empresariais, isso pode ser perigoso se a resposta precisa estar estritamente fundamentada na base.
8.22 QuestionAnswerAdvisor ou RetrievalAugmentationAdvisor?
Use QuestionAnswerAdvisor quando:
- você quer começar rápido;
- quer um fluxo mais simples;
- precisa de um RAG direto e fácil de entender.
Use RetrievalAugmentationAdvisor quando:
- você quer mais modularidade;
- pretende evoluir o fluxo de recuperação;
- precisa de uma arquitetura mais extensível;
- está pensando em RAG de forma mais avançada.
8.23 Exemplo completo de serviço com QuestionAnswerAdvisor
Aqui vai um exemplo didático consolidado.
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;
@Service
public class AssistenteRagService {
private final ChatClient chatClient;
private final VectorStore vectorStore;
public AssistenteRagService(ChatModel chatModel, VectorStore vectorStore) {
this.chatClient = ChatClient.builder(chatModel).build();
this.vectorStore = vectorStore;
}
public String responder(String pergunta) {
var qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(
SearchRequest.builder()
.similarityThreshold(0.8d)
.topK(5)
.build()
)
.build();
return chatClient.prompt()
.system("Responda apenas com base no contexto recuperado. Se não souber, diga que não sabe.")
.user(pergunta)
.advisors(qaAdvisor)
.call()
.content();
}
}
O que esse exemplo ensina?
- uso de
VectorStore; - enriquecimento de pergunta com contexto;
- threshold e topK;
- instrução explícita contra invenção.
8.24 Exemplo de controller para usar o RAG
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/faq")
public class FaqController {
private final AssistenteRagService assistenteRagService;
public FaqController(AssistenteRagService assistenteRagService) {
this.assistenteRagService = assistenteRagService;
}
@GetMapping
public String responder(@RequestParam String pergunta) {
return assistenteRagService.responder(pergunta);
}
}
Exemplo de chamada
GET /faq?pergunta=Como funciona a reposição de aula?
Fluxo esperado
- pergunta chega;
- busca vetorial roda;
- trechos relevantes são recuperados;
- o modelo responde com base nesses trechos.
8.25 Em que tipo de aplicação o RAG faz mais sentido?
Exemplos clássicos
1. FAQ inteligente
Baseado em regulamentos, perguntas frequentes, contratos ou políticas.
2. Base de conhecimento interna
Manuais, processos, playbooks e documentação da empresa.
3. Assistente de documentação interna
Funcionalidades do sistema, termos de uso, políticas e procedimentos da organização.
4. Portal educacional
Conteúdo de curso, regras acadêmicas, material de apoio.
5. Assistente jurídico ou financeiro
Quando o sistema precisa responder com base em documentos normativos específicos.
8.26 RAG não substitui tudo
Também é importante não tratar RAG como solução mágica.
RAG ajuda muito, mas ainda exige atenção em pontos como:
- qualidade dos documentos carregados;
- chunking e ingestão;
- relevância da recuperação;
- filtros;
- qualidade do prompt final;
- cuidado com contexto excessivo.
8.27 O que você deve guardar deste capítulo?
Mais do que decorar classes, ele precisa entender estas ideias:
- o modelo sozinho não basta para conhecimento específico e atualizado;
- RAG injeta contexto recuperado antes da resposta;
- o
VectorStoreé parte central dessa arquitetura; - o
QuestionAnswerAdvisoré a forma mais simples de começar; - o
RetrievalAugmentationAdvisorleva a um RAG mais modular; - filtros e templates importam muito;
- RAG é uma estratégia arquitetural, não só um detalhe de API.
8.28 Boas práticas desta etapa
1. Sempre diga ao modelo como agir quando o contexto não for suficiente
Exemplo:
- “Se não souber, diga que não sabe.”
2. Ajuste topK e similarityThreshold
Evite contexto demais e contexto irrelevante.
3. Use filtros quando houver múltiplos tipos de documentos
Isso melhora precisão.
4. Personalize o template quando o domínio exigir mais controle
Especialmente para ambientes corporativos.
5. Não deixe o modelo responder livremente quando o objetivo for resposta fundamentada
Esse é exatamente o tipo de cenário em que RAG existe.
6. Comece pelo QuestionAnswerAdvisor
E evolua para RetrievalAugmentationAdvisor quando precisar de mais modularidade.
8.29 Erros comuns nesta etapa
Erro 1: achar que RAG é só “jogar documento no prompt”
Não é. Há recuperação, seleção e augmentação de contexto.
Erro 2: recuperar documentos demais
Isso aumenta ruído.
Erro 3: não dizer ao modelo o que fazer quando a resposta não estiver no contexto
Isso aumenta alucinação.
Erro 4: não usar filtro em bases heterogêneas
Isso reduz precisão.
Erro 5: achar que qualquer busca é RAG
RAG envolve recuperação relevante e uso desse contexto na geração.
8.30 Resumo do capítulo
Neste capítulo, você aprendeu que RAG é uma técnica usada para melhorar respostas com base em contexto recuperado. Vimos que:
- o modelo sozinho pode não ter conhecimento específico, atualizado ou suficiente para responder corretamente;
- RAG recupera conteúdo relevante antes de pedir a resposta ao modelo;
- o
VectorStoreé central nesse processo; QuestionAnswerAdvisoré uma forma simples de começar;RetrievalAugmentationAdvisorpermite uma arquitetura mais modular;- filtros dinâmicos e templates customizados ajudam muito na qualidade da resposta;
- RAG é uma estratégia fundamental para aplicações reais baseadas em documentos e conhecimento interno.
8.31 Exercícios propostos
1. Conceitual
Explique com suas palavras a diferença entre uma resposta comum do modelo e uma resposta com RAG.
2. Conceitual
Por que RAG ajuda a reduzir alucinação em aplicações com base documental?
3. Prático
Monte um exemplo com QuestionAnswerAdvisor usando topK(5) e similarityThreshold(0.8).
4. Prático
Crie um filtro dinâmico para buscar apenas documentos do tipo "programacao".
5. Reflexão
Em que cenário você preferiria QuestionAnswerAdvisor e em que cenário escolheria RetrievalAugmentationAdvisor?
Desafio extra
Carregue dois conjuntos fictícios no VectorStore (ex.: “políticas 2024” e “políticas 2025”) e use filtro dinâmico para que a mesma pergunta recupere só um dos conjuntos. Mostre a pergunta, o filtro e um trecho do contexto recuperado.
8.32 Fechamento
Com este capítulo, fechamos uma sequência muito forte deste ebook.
Agora você já viu:
- integração básica;
- parâmetros;
- modelos;
- erros e retry;
- multimodalidade;
- tool calling;
- MCP;
- e RAG.
Isso forma uma base muito sólida para começar a construir aplicações com Spring AI que sejam:
- úteis;
- organizadas;
- integráveis;
- menos acopladas;
- mais próximas de cenários reais.