Criando Seu Primeiro Chatbot com Java e Spring Boot: Um Guia Prático
- #Java
Você já conversou com um chatbot hoje? Provavelmente sim! Seja no atendimento de um e-commerce, no WhatsApp Business de alguma empresa, ou até mesmo pedindo para a Alexa tocar sua música favorita. A verdade é que os chatbots estão por toda parte, e com Java combinado ao Spring AI, criar um é mais simples do que você imagina.
O mercado de chatbots está explodindo - e por boas razões. Segundo pesquisas recentes, empresas que implementam chatbots conseguem reduzir custos de atendimento em até 30% enquanto atendem clientes 24/7. Para nós desenvolvedores, isso significa oportunidades incríveis tanto para projetos próprios quanto para soluções corporativas.
Mas aqui vem o plot twist: muita gente acha que precisa dominar Python e bibliotecas complexas de machine learning para entrar nesse mundo. A realidade? Se você já programa em Java, tem uma vantagem gigantesca que talvez nem perceba.
Pense assim: um chatbot é essencialmente uma aplicação que recebe uma requisição (a mensagem do usuário), processa essa informação, consulta alguma fonte de dados ou serviço externo (a IA), e retorna uma resposta estruturada. Soa familiar? É praticamente o mesmo fluxo de uma API REST que você já constrói todos os dias!
Como desenvolvedor que já trabalha com Spring Boot criando APIs REST, posso garantir: você já tem 70% do conhecimento necessário para construir seu primeiro chatbot inteligente. O Controller para receber mensagens? Você sabe fazer. Service para processar a lógica? Moleza. Integração com APIs externas? Spring AI resolve. Repository para salvar histórico? JPA resolve.
O que falta? Apenas algumas pitadas de IA para temperar nossa aplicação! E o melhor: você não precisa se tornar um especialista em machine learning. O Spring AI oferece abstrações elegantes que fazem o trabalho pesado - você só precisa saber integrar, e isso Java faz melhor que ninguém.
O Que São Chatbots e Por Que Spring AI É Uma Escolha Inteligente?
Um chatbot é basicamente um programa que simula conversas humanas. Pense nele como uma API REST que, ao invés de retornar JSON com dados de produtos ou usuários, retorna respostas conversacionais baseadas no que o usuário está perguntando.
Spring AI se destaca nesse cenário por revolucionar como integramos IA em aplicações Java:
- Abstrações Spring-friendly: APIs familiares que seguem os padrões que você já conhece
- Suporte multi-modelo: OpenAI, Azure OpenAI, Anthropic, Ollama e muito mais
- Integração natural: Funciona perfeitamente com Spring Boot e todo o ecossistema Spring
- Portabilidade: Troque de provedor de IA sem reescrever código
- Design modular: Use apenas os componentes que precisa
Arquitetura do Nosso Chatbot com Spring AI
Antes de colocar a mão na massa, vamos entender como nosso chatbot vai funcionar. A arquitetura é surpreendentemente simples:
Componentes Principais
Controller REST: Recebe as mensagens dos usuários (igual suas APIs de sempre)
Serviço de Processamento: Analisa a intenção do usuário e prepara a resposta
ChatClient Spring AI: Interface elegante para comunicação com modelos de IA
Gerenciador de Contexto: Mantém o histórico da conversa para respostas mais inteligentes
Basicamente, é como se você estivesse criando uma API REST comum, mas ao invés de buscar dados no banco, você está "conversando" com uma IA para gerar respostas inteligentes usando as abstrações do Spring AI.
Mãos à Obra: Construindo o Chatbot
Vamos criar um chatbot assistente virtual para uma loja online. Ele vai conseguir responder perguntas sobre produtos, status de pedidos e suporte básico.
Configurando o Projeto Spring Boot
<!-- pom.xml - Dependências essenciais -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M4</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
Configuração do Spring AI
# application.yml
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-3.5-turbo
temperature: 0.7
max-tokens: 150
Modelando as Conversas
@Entity
@Table(name = "conversas")
public class Conversa {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String sessaoId;
private String mensagemUsuario;
private String respostaBot;
private LocalDateTime timestamp;
// getters, setters e construtores
public Conversa() {}
public Conversa(String sessaoId, String mensagemUsuario, String respostaBot) {
this.sessaoId = sessaoId;
this.mensagemUsuario = mensagemUsuario;
this.respostaBot = respostaBot;
this.timestamp = LocalDateTime.now();
}
// getters e setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getSessaoId() { return sessaoId; }
public void setSessaoId(String sessaoId) { this.sessaoId = sessaoId; }
public String getMensagemUsuario() { return mensagemUsuario; }
public void setMensagemUsuario(String mensagemUsuario) { this.mensagemUsuario = mensagemUsuario; }
public String getRespostaBot() { return respostaBot; }
public void setRespostaBot(String respostaBot) { this.respostaBot = respostaBot; }
public LocalDateTime getTimestamp() { return timestamp; }
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
}
Classes de Request e Response
public class MensagemRequest {
private String sessaoId;
private String mensagem;
public MensagemRequest() {}
public MensagemRequest(String sessaoId, String mensagem) {
this.sessaoId = sessaoId;
this.mensagem = mensagem;
}
public String getSessaoId() { return sessaoId; }
public void setSessaoId(String sessaoId) { this.sessaoId = sessaoId; }
public String getMensagem() { return mensagem; }
public void setMensagem(String mensagem) { this.mensagem = mensagem; }
}
public class RespostaChatbot {
private String resposta;
private LocalDateTime timestamp;
public RespostaChatbot() {}
public RespostaChatbot(String resposta) {
this.resposta = resposta;
this.timestamp = LocalDateTime.now();
}
public String getResposta() { return resposta; }
public void setResposta(String resposta) { this.resposta = resposta; }
public LocalDateTime getTimestamp() { return timestamp; }
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
}
O Controller: Porta de Entrada do Chatbot
@RestController
@RequestMapping("/api/chatbot")
@CrossOrigin(origins = "*")
public class ChatbotController {
@Autowired
private ChatbotService chatbotService;
@PostMapping("/conversar")
public ResponseEntity<RespostaChatbot> conversar(@RequestBody MensagemRequest request) {
try {
RespostaChatbot resposta = chatbotService.processarMensagem(
request.getSessaoId(),
request.getMensagem()
);
return ResponseEntity.ok(resposta);
} catch (Exception e) {
return ResponseEntity.status(500)
.body(new RespostaChatbot("Ops! Algo deu errado. Tente novamente."));
}
}
}
O Cérebro: Serviço de Processamento com Spring AI
@Service
public class ChatbotService {
@Autowired
private ConversaRepository conversaRepository;
private final ChatClient chatClient;
public ChatbotService(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder
.defaultSystem("Você é um assistente virtual de uma loja online. " +
"Seja prestativo, cordial e objetivo nas suas respostas.")
.build();
}
public RespostaChatbot processarMensagem(String sessaoId, String mensagem) {
// Busca histórico da conversa para manter contexto
List<Conversa> historico = conversaRepository
.findBySessaoIdOrderByTimestampDesc(sessaoId);
// Constrói o contexto da conversa
String contextoConversa = construirContexto(historico);
// Chama a IA usando Spring AI
String respostaIA = chatClient.prompt()
.system(sp -> sp.text("Histórico da conversa:\n" + contextoConversa))
.user(mensagem)
.call()
.content();
// Salva a conversa
salvarConversa(sessaoId, mensagem, respostaIA);
return new RespostaChatbot(respostaIA);
}
private String construirContexto(List<Conversa> historico) {
if (historico.isEmpty()) {
return "Esta é uma nova conversa.";
}
StringBuilder contexto = new StringBuilder();
// Pega apenas as últimas 5 interações para não sobrecarregar o contexto
for (Conversa conv : historico.subList(0, Math.min(historico.size(), 5))) {
contexto.append("Usuario: ").append(conv.getMensagemUsuario()).append("\n");
contexto.append("Assistente: ").append(conv.getRespostaBot()).append("\n");
}
return contexto.toString();
}
private void salvarConversa(String sessaoId, String mensagem, String resposta) {
Conversa conversa = new Conversa(sessaoId, mensagem, resposta);
conversaRepository.save(conversa);
}
}
Repository para Persistência
@Repository
public interface ConversaRepository extends JpaRepository<Conversa, Long> {
List<Conversa> findBySessaoIdOrderByTimestampDesc(String sessaoId);
@Query("SELECT COUNT(c) FROM Conversa c WHERE c.timestamp >= :inicio")
Long countConversasHoje(@Param("inicio") LocalDateTime inicio);
@Query("SELECT c.sessaoId, COUNT(c) as total FROM Conversa c " +
"GROUP BY c.sessaoId ORDER BY total DESC")
List<Object[]> findSessoesComMaisInteracoes();
}
Adicionando Inteligência ao Chatbot
O segredo de um bom chatbot não está apenas na IA, mas em como você estrutura as interações. Aqui estão algumas técnicas que fazem toda a diferença:
Reconhecimento de Intenções com Spring AI
@Component
public class AnalisadorIntencoes {
private final ChatClient chatClient;
public AnalisadorIntencoes(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder
.defaultSystem("""
Analise a mensagem do usuário e identifique a intenção principal.
Responda apenas com uma das opções:
- CONSULTA_PRODUTO
- STATUS_PEDIDO
- SUPORTE
- CONVERSA_GERAL
""")
.build();
}
public TipoIntencao identificarIntencao(String mensagem) {
String resposta = chatClient.prompt()
.user("Mensagem: " + mensagem)
.call()
.content();
try {
return TipoIntencao.valueOf(resposta.trim().toUpperCase());
} catch (IllegalArgumentException e) {
return TipoIntencao.CONVERSA_GERAL;
}
}
}
enum TipoIntencao {
CONSULTA_PRODUTO,
STATUS_PEDIDO,
SUPORTE,
CONVERSA_GERAL
}
Sistema de Contexto Inteligente
@Service
@Component
public class GerenciadorContexto {
private final Map<String, ContextoSessao> sessoes = new ConcurrentHashMap<>();
public void atualizarContexto(String sessaoId, String mensagem, String resposta) {
ContextoSessao contexto = sessoes.computeIfAbsent(sessaoId,
k -> new ContextoSessao());
contexto.adicionarInteracao(mensagem, resposta);
// Remove contextos antigos para economizar memória
if (contexto.getIdade().compareTo(Duration.ofHours(2)) > 0) {
sessoes.remove(sessaoId);
}
}
public ContextoSessao obterContexto(String sessaoId) {
return sessoes.get(sessaoId);
}
}
class ContextoSessao {
private final List<String> interacoes = new ArrayList<>();
private final LocalDateTime criacao = LocalDateTime.now();
public void adicionarInteracao(String mensagem, String resposta) {
interacoes.add("Usuario: " + mensagem);
interacoes.add("Bot: " + resposta);
// Mantém apenas as últimas 10 interações
if (interacoes.size() > 20) {
interacoes.subList(0, 10).clear();
}
}
public Duration getIdade() {
return Duration.between(criacao, LocalDateTime.now());
}
public int getNumeroInteracoes() {
return interacoes.size() / 2;
}
public boolean temHistoricoSobre(String termo) {
return interacoes.stream()
.anyMatch(i -> i.toLowerCase().contains(termo.toLowerCase()));
}
}
Testando e Validando o Chatbot
Um chatbot sem testes é como um carro sem freios - pode até andar, mas não é seguro! Vamos criar alguns testes para garantir que nossa IA está respondendo adequadamente:
Testes de Integração
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase
class ChatbotIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void deveResponderSaudacaoCorretamente() {
MensagemRequest request = new MensagemRequest("123", "Olá!");
ResponseEntity<RespostaChatbot> response = restTemplate
.postForEntity("/api/chatbot/conversar", request, RespostaChatbot.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody().getResposta()).isNotEmpty();
}
@Test
void deveManterContextoEntreInteracoes() {
String sessaoId = "test-session";
// Primeira interação
MensagemRequest request1 = new MensagemRequest(sessaoId, "Quero comprar um notebook");
restTemplate.postForEntity("/api/chatbot/conversar", request1, RespostaChatbot.class);
// Segunda interação - deve lembrar do contexto
MensagemRequest request2 = new MensagemRequest(sessaoId, "Qual o preço?");
ResponseEntity<RespostaChatbot> response = restTemplate
.postForEntity("/api/chatbot/conversar", request2, RespostaChatbot.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody().getResposta()).containsIgnoringCase("notebook");
}
}
Métricas de Qualidade
@Component
public class MetricasChatbot {
private static final Logger log = LoggerFactory.getLogger(MetricasChatbot.class);
public void avaliarQualidadeResposta(String mensagem, String resposta) {
// Verifica se a resposta não é muito curta
if (resposta.length() < 10) {
log.warn("Resposta muito curta para: {}", mensagem);
}
// Verifica se não há respostas repetitivas
if (isRespostaRepetitiva(resposta)) {
log.warn("Possível resposta repetitiva detectada");
}
// Verifica se a resposta é relevante ao contexto
if (!isRespostaRelevante(mensagem, resposta)) {
log.warn("Resposta pode não ser relevante para: {}", mensagem);
}
}
private boolean isRespostaRepetitiva(String resposta) {
// Implementação simplificada - pode ser expandida
return resposta.toLowerCase().matches(".*\\b(\\w+)\\s+\\1\\b.*");
}
private boolean isRespostaRelevante(String mensagem, String resposta) {
// Implementação básica - pode usar NLP mais avançado
return resposta.length() > mensagem.length() * 0.5;
}
}
Expandindo as Capacidades do Chatbot
Agora que temos nossa base sólida, podemos adicionar funcionalidades mais avançadas que realmente fazem a diferença:
Integração com Base de Conhecimento
@Service
public class BaseConhecimento {
@Autowired
private ProdutoRepository produtoRepository;
private final ChatClient chatClient;
public BaseConhecimento(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
public String buscarInformacoesProduto(String nomeProduto) {
List<Produto> produtos = produtoRepository
.findByNomeContainingIgnoreCase(nomeProduto);
if (produtos.isEmpty()) {
return "Não encontrei produtos com esse nome. Que tal me dar mais detalhes?";
}
StringBuilder info = new StringBuilder("Encontrei estes produtos:\n");
produtos.forEach(p ->
info.append(String.format("• %s - R$ %.2f\n", p.getNome(), p.getPreco())));
return info.toString();
}
public String gerarRecomendacao(String pergunta, List<Produto> produtos) {
StringBuilder contexto = new StringBuilder("Produtos disponíveis:\n");
produtos.forEach(p ->
contexto.append(String.format("- %s: R$ %.2f - %s\n",
p.getNome(), p.getPreco(), p.getDescricao())));
return chatClient.prompt()
.system("Você é um especialista em produtos. Use as informações dos produtos para fazer recomendações personalizadas.")
.system(contexto.toString())
.user(pergunta)
.call()
.content();
}
}
@Entity
class Produto {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nome;
private Double preco;
private String descricao;
// construtores, getters e setters
public Produto() {}
public Produto(String nome, Double preco, String descricao) {
this.nome = nome;
this.preco = preco;
this.descricao = descricao;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getNome() { return nome; }
public void setNome(String nome) { this.nome = nome; }
public Double getPreco() { return preco; }
public void setPreco(Double preco) { this.preco = preco; }
public String getDescricao() { return descricao; }
public void setDescricao(String descricao) { this.descricao = descricao; }
}
@Repository
interface ProdutoRepository extends JpaRepository<Produto, Long> {
List<Produto> findByNomeContainingIgnoreCase(String nome);
}
Respostas Contextuais Avançadas
@Component
public class GeradorRespostasContextuais {
private final ChatClient chatClient;
public GeradorRespostasContextuais(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
public String criarRespostaPersonalizada(String mensagem, ContextoSessao contexto) {
StringBuilder prompt = new StringBuilder();
prompt.append("Personalize sua resposta baseado no histórico da conversa:\n");
// Se o usuário já perguntou sobre produtos antes
if (contexto != null && contexto.temHistoricoSobre("produto")) {
prompt.append("- O usuário já demonstrou interesse em produtos\n");
}
// Se é um usuário recorrente
if (contexto != null && contexto.getNumeroInteracoes() > 5) {
prompt.append("- Este é um usuário recorrente\n");
}
prompt.append("Mensagem atual: ").append(mensagem);
return chatClient.prompt()
.system("Seja natural e personalizado nas suas respostas.")
.user(prompt.toString())
.call()
.content();
}
}
Performance e Otimização
Um chatbot lento é um chatbot que ninguém usa. Aqui estão algumas estratégias para manter tudo rodando suavemente:
Cache Inteligente para Respostas
@Service
public class CacheRespostas {
private final ChatClient chatClient;
public CacheRespostas(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@Cacheable(value = "respostas-ia", key = "#prompt.hashCode()")
public String buscarRespostaCacheada(String prompt) {
return chatClient.prompt()
.user(prompt)
.call()
.content();
}
@CacheEvict(value = "respostas-ia", allEntries = true)
@Scheduled(fixedRate = 3600000) // Limpa cache a cada hora
public void limparCache() {
log.info("Cache de respostas limpo");
}
}
Configuração de Cache
@Configuration
@EnableCaching
@EnableScheduling
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager("respostas-ia");
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(30, TimeUnit.MINUTES));
return cacheManager;
}
}
Configuração do ChatClient para Performance
@Configuration
public class ChatbotConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultOptions(OpenAiChatOptions.builder()
.withModel("gpt-3.5-turbo")
.withTemperature(0.7f)
.withMaxTokens(150)
.build())
.build();
}
}
Monitoramento e Análise de Conversas
Para melhorar continuamente nosso chatbot, precisamos entender como ele está performando:
Dashboard de Métricas
@RestController
@RequestMapping("/api/admin/metricas")
public class MetricasController {
@Autowired
private MetricasService metricasService;
@GetMapping("/conversas-hoje")
public MetricasResponse getConversasHoje() {
return metricasService.calcularMetricasDiarias();
}
@GetMapping("/topicos-frequentes")
public List<TopicoPair> getTopicosMaisFrequentes() {
return metricasService.analisarTopicosFrequentes();
}
@GetMapping("/satisfacao")
public Double getSatisfacaoMedia() {
return metricasService.calcularSatisfacaoMedia();
}
}
@Service
class MetricasService {
@Autowired
private ConversaRepository conversaRepository;
public MetricasResponse calcularMetricasDiarias() {
LocalDateTime inicio = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0);
Long totalConversas = conversaRepository.countConversasHoje(inicio);
return new MetricasResponse(totalConversas, inicio);
}
public List<TopicoPair> analisarTopicosFrequentes() {
// Implementação simplificada - pode usar NLP mais avançado
return conversaRepository.findAll().stream()
.collect(Collectors.groupingBy(
c -> extrairTopicoPrincipal(c.getMensagemUsuario()),
Collectors.counting()))
.entrySet().stream()
.map(e -> new TopicoPair(e.getKey(), e.getValue()))
.sorted((a, b) -> Long.compare(b.getCount(), a.getCount()))
.limit(10)
.collect(Collectors.toList());
}
private String extrairTopicoPrincipal(String mensagem) {
// Implementação básica - pode ser expandida com NLP
if (mensagem.toLowerCase().contains("produto")) return "Produtos";
if (mensagem.toLowerCase().contains("pedido")) return "Pedidos";
if (mensagem.toLowerCase().contains("ajuda")) return "Suporte";
return "Geral";
}
public Double calcularSatisfacaoMedia() {
// Implementação simulada - poderia integrar com sistema de feedback
return 4.2;
}
}
class MetricasResponse {
private Long totalConversas;
private LocalDateTime periodo;
public MetricasResponse(Long totalConversas, LocalDateTime periodo) {
this.totalConversas = totalConversas;
this.periodo = periodo;
}
public Long getTotalConversas() { return totalConversas; }
public LocalDateTime getPeriodo() { return periodo; }
}
class TopicoPair {
private String topico;
private Long count;
public TopicoPair(String topico, Long count) {
this.topico = topico;
this.count = count;
}
public String getTopico() { return topico; }
public Long getCount() { return count; }
}
Próximos Passos: Evoluindo seu Chatbot
Este é apenas o começo da sua jornada com chatbots e IA em Java! Algumas direções interessantes para explorar:
Integração com Múltiplas IAs usando Spring AI
O Spring AI suporta múltiplos provedores de IA, permitindo combinar diferentes serviços para resultados ainda melhores. Enquanto estudo essas possibilidades, vejo muito potencial em usar OpenAI para conversas gerais, Azure OpenAI para análise de sentimentos, e modelos locais com Ollama para dados sensíveis.
A estratégia seria criar um orquestrador de IAs que escolhe o melhor serviço para cada tipo de pergunta:
@Service
public class OrquestradorIA {
private final ChatClient openAiClient;
private final ChatClient azureClient;
private final ChatClient ollamaClient;
public OrquestradorIA(
@Qualifier("openAiChatClient") ChatClient openAiClient,
@Qualifier("azureChatClient") ChatClient azureClient,
@Qualifier("ollamaChatClient") ChatClient ollamaClient) {
this.openAiClient = openAiClient;
this.azureClient = azureClient;
this.ollamaClient = ollamaClient;
}
public RespostaCompleta processarMensagem(String mensagem) {
// Primeiro, analisa o sentimento com Azure
String sentimento = azureClient.prompt()
.system("Analise o sentimento da mensagem: positivo, negativo ou neutro")
.user(mensagem)
.call()
.content();
// Para dados sensíveis, usa modelo local
if (contemDadosSensiveis(mensagem)) {
String resposta = ollamaClient.prompt()
.user(mensagem)
.call()
.content();
return new RespostaCompleta(resposta, sentimento, "local");
}
// Para conversas gerais, usa OpenAI
String resposta = openAiClient.prompt()
.user(mensagem)
.call()
.content();
return new RespostaCompleta(resposta, sentimento, "openai");
}
private boolean contemDadosSensiveis(String mensagem) {
return mensagem.toLowerCase().matches(".*\\b(cpf|rg|senha|cartão|email|telefone)\\b.*");
}
}
class RespostaCompleta {
private String resposta;
private String sentimento;
private String modelo;
public RespostaCompleta(String resposta, String sentimento, String modelo) {
this.resposta = resposta;
this.sentimento = sentimento;
this.modelo = modelo;
}
public String getResposta() { return resposta; }
public String getSentimento() { return sentimento; }
public String getModelo() { return modelo; }
}
Imagino as possibilidades: um chatbot que não apenas responde, mas entende o humor do cliente e adapta o tom da conversa. Se alguém está frustrado, o bot pode ser mais empático. Se está animado, pode ser mais entusiástico.
Configuração Multi-Provider
@Configuration
public class MultiProviderConfig {
@Bean
@Qualifier("openAiChatClient")
public ChatClient openAiChatClient(OpenAiChatModel openAiChatModel) {
return ChatClient.builder(openAiChatModel)
.defaultSystem("Você é um assistente virtual prestativo e cordial.")
.build();
}
@Bean
@Qualifier("azureChatClient")
public ChatClient azureChatClient(AzureOpenAiChatModel azureChatModel) {
return ChatClient.builder(azureChatModel)
.defaultSystem("Analise sentimentos e seja preciso nas suas classificações.")
.build();
}
@Bean
@Qualifier("ollamaChatClient")
public ChatClient ollamaChatClient(OllamaChatModel ollamaChatModel) {
return ChatClient.builder(ollamaChatModel)
.defaultSystem("Processe dados sensíveis com máxima segurança e privacidade.")
.build();
}
}
Embedding e Busca Semântica
O Spring AI também oferece suporte a embeddings, permitindo busca semântica na sua base de conhecimento:
@Service
public class BuscaSemantica {
private final EmbeddingClient embeddingClient;
private final VectorStore vectorStore;
public BuscaSemantica(EmbeddingClient embeddingClient, VectorStore vectorStore) {
this.embeddingClient = embeddingClient;
this.vectorStore = vectorStore;
}
public List<String> buscarDocumentosRelevantes(String pergunta) {
// Gera embedding da pergunta
List<Double> embedding = embeddingClient.embed(pergunta);
// Busca documentos similares
List<Document> documentos = vectorStore.similaritySearch(
SearchRequest.query(pergunta)
.withTopK(5)
.withSimilarityThreshold(0.7)
);
return documentos.stream()
.map(Document::getContent)
.collect(Collectors.toList());
}
public void indexarConhecimento(List<String> documentos) {
List<Document> docs = documentos.stream()
.map(content -> new Document(UUID.randomUUID().toString(), content))
.collect(Collectors.toList());
vectorStore.add(docs);
}
}
Integração Multicanal
Expandir para WhatsApp, Telegram, Slack... Spring Boot torna essa integração bem natural através de webhooks e APIs REST.
A beleza dessa abordagem é que seu chatbot vira uma espécie de "cérebro central" que pode conversar em qualquer lugar:
@RestController
@RequestMapping("/webhooks")
public class WebhookController {
@Autowired
private ChatbotService chatbotService;
@Autowired
private WhatsAppService whatsAppService;
@Autowired
private TelegramService telegramService;
@PostMapping("/whatsapp")
public ResponseEntity<?> receberWhatsApp(@RequestBody WhatsAppMessage message) {
String resposta = chatbotService.processarMensagem(
message.getFrom(),
message.getBody()
);
whatsAppService.enviarResposta(message.getFrom(), resposta);
return ResponseEntity.ok().build();
}
@PostMapping("/telegram")
public ResponseEntity<?> receberTelegram(@RequestBody TelegramUpdate update) {
// Mesma lógica, canal diferente
String resposta = chatbotService.processarMensagem(
update.getMessage().getFrom().getId().toString(),
update.getMessage().getText()
);
telegramService.enviarMensagem(update.getMessage().getChatId(), resposta);
return ResponseEntity.ok().build();
}
@PostMapping("/slack")
public ResponseEntity<?> receberSlack(@RequestBody SlackEvent event) {
if (event.getType().equals("message")) {
String resposta = chatbotService.processarMensagem(
event.getUser(),
event.getText()
);
slackService.enviarMensagem(event.getChannel(), resposta);
}
return ResponseEntity.ok().build();
}
}
// Classes de modelo para os webhooks
class WhatsAppMessage {
private String from;
private String body;
public String getFrom() { return from; }
public void setFrom(String from) { this.from = from; }
public String getBody() { return body; }
public void setBody(String body) { this.body = body; }
}
class TelegramUpdate {
private TelegramMessage message;
public TelegramMessage getMessage() { return message; }
public void setMessage(TelegramMessage message) { this.message = message; }
}
class TelegramMessage {
private TelegramUser from;
private String text;
private Long chatId;
public TelegramUser getFrom() { return from; }
public void setFrom(TelegramUser from) { this.from = from; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public Long getChatId() { return chatId; }
public void setChatId(Long chatId) { this.chatId = chatId; }
}
class TelegramUser {
private Long id;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
}
class SlackEvent {
private String type;
private String user;
private String text;
private String channel;
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getUser() { return user; }
public void setUser(String user) { this.user = user; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public String getChannel() { return channel; }
public void setChannel(String channel) { this.channel = channel; }
}
O que mais me empolgana é pensar que um único chatbot pode estar presente onde seus clientes estão - site, WhatsApp, Telegram, até mesmo integrado com Alexa ou Google Assistant. Spring AI com Spring Boot torna essa expansão natural e escalável.
RAG (Retrieval-Augmented Generation)
Uma das técnicas mais poderosas do Spring AI é o RAG, que combina busca de informações com geração de respostas:
@Service
public class RAGService {
private final ChatClient chatClient;
private final BuscaSemantica buscaSemantica;
public RAGService(ChatClient chatClient, BuscaSemantica buscaSemantica) {
this.chatClient = chatClient;
this.buscaSemantica = buscaSemantica;
}
public String responderComRAG(String pergunta) {
// Busca documentos relevantes
List<String> documentosRelevantes = buscaSemantica.buscarDocumentosRelevantes(pergunta);
// Constrói contexto com os documentos encontrados
String contexto = String.join("\n", documentosRelevantes);
// Gera resposta baseada no contexto
return chatClient.prompt()
.system("""
Use as informações fornecidas para responder à pergunta.
Se a informação não estiver disponível no contexto, diga que não sabe.
Contexto:
""" + contexto)
.user(pergunta)
.call()
.content();
}
}
Funções e Tools
O Spring AI suporta function calling, permitindo que o chatbot execute ações específicas:
@Component
public class ChatbotFunctions {
@Autowired
private ProdutoRepository produtoRepository;
@Autowired
private PedidoRepository pedidoRepository;
@Bean
@Description("Busca produtos por nome ou categoria")
public Function<BuscaProdutoRequest, BuscaProdutoResponse> buscarProdutos() {
return request -> {
List<Produto> produtos = produtoRepository
.findByNomeContainingIgnoreCase(request.nome());
return new BuscaProdutoResponse(produtos.stream()
.map(p -> new ProdutoInfo(p.getNome(), p.getPreco(), p.getDescricao()))
.collect(Collectors.toList()));
};
}
@Bean
@Description("Consulta status de pedido por ID")
public Function<ConsultaPedidoRequest, ConsultaPedidoResponse> consultarPedido() {
return request -> {
Optional<Pedido> pedido = pedidoRepository.findById(request.pedidoId());
if (pedido.isPresent()) {
return new ConsultaPedidoResponse(
pedido.get().getId(),
pedido.get().getStatus(),
pedido.get().getDataCriacao()
);
}
return new ConsultaPedidoResponse(null, "Pedido não encontrado", null);
};
}
}
// Records para as funções
record BuscaProdutoRequest(String nome) {}
record BuscaProdutoResponse(List<ProdutoInfo> produtos) {}
record ProdutoInfo(String nome, Double preco, String descricao) {}
record ConsultaPedidoRequest(Long pedidoId) {}
record ConsultaPedidoResponse(Long id, String status, LocalDateTime dataCriacao) {}
// Configuração do ChatClient com funções
@Configuration
public class FunctionConfig {
@Bean
public ChatClient chatClientComFuncoes(
ChatClient.Builder builder,
@Qualifier("buscarProdutos") Function<BuscaProdutoRequest, BuscaProdutoResponse> buscarProdutos,
@Qualifier("consultarPedido") Function<ConsultaPedidoRequest, ConsultaPedidoResponse> consultarPedido) {
return builder
.defaultFunctions("buscarProdutos", "consultarPedido")
.build();
}
}
Monitoramento Avançado com Observabilidade
@Component
public class ChatbotObservability {
private final MeterRegistry meterRegistry;
private final Counter mensagensProcessadas;
private final Timer tempoResposta;
public ChatbotObservability(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.mensagensProcessadas = Counter.builder("chatbot.mensagens.processadas")
.description("Número de mensagens processadas")
.register(meterRegistry);
this.tempoResposta = Timer.builder("chatbot.tempo.resposta")
.description("Tempo de resposta do chatbot")
.register(meterRegistry);
}
public <T> T executarComMonitoramento(String operacao, Supplier<T> supplier) {
return Timer.Sample.start(meterRegistry)
.stop(tempoResposta.timer("operacao", operacao))
.recordCallable(() -> {
mensagensProcessadas.increment();
return supplier.get();
});
}
}
// Uso no serviço
@Service
public class ChatbotServiceComObservability {
@Autowired
private ChatbotObservability observability;
public RespostaChatbot processarMensagem(String sessaoId, String mensagem) {
return observability.executarComMonitoramento("processar_mensagem", () -> {
// Lógica de processamento aqui
return new RespostaChatbot("Resposta processada");
});
}
}
Segurança e Boas Práticas
Validação e Sanitização
@Component
public class ValidadorMensagens {
private static final int MAX_LENGTH = 1000;
private static final Pattern PATTERN_SUSPEITO = Pattern.compile("(script|javascript|<|>)", Pattern.CASE_INSENSITIVE);
public boolean validarMensagem(String mensagem) {
if (mensagem == null || mensagem.trim().isEmpty()) {
return false;
}
if (mensagem.length() > MAX_LENGTH) {
return false;
}
return !PATTERN_SUSPEITO.matcher(mensagem).find();
}
public String sanitizarMensagem(String mensagem) {
return mensagem.trim()
.replaceAll("[<>\"']", "")
.substring(0, Math.min(mensagem.length(), MAX_LENGTH));
}
}
Rate Limiting
@Component
public class RateLimiter {
private final Map<String, List<LocalDateTime>> requests = new ConcurrentHashMap<>();
private static final int MAX_REQUESTS_PER_MINUTE = 20;
public boolean isAllowed(String sessaoId) {
LocalDateTime now = LocalDateTime.now();
LocalDateTime oneMinuteAgo = now.minusMinutes(1);
requests.compute(sessaoId, (key, timestamps) -> {
if (timestamps == null) {
timestamps = new ArrayList<>();
}
// Remove timestamps antigos
timestamps.removeIf(timestamp -> timestamp.isBefore(oneMinuteAgo));
// Verifica se excedeu o limite
if (timestamps.size() >= MAX_REQUESTS_PER_MINUTE) {
return timestamps;
}
timestamps.add(now);
return timestamps;
});
return requests.get(sessaoId).size() <= MAX_REQUESTS_PER_MINUTE;
}
}
Ainda estou mapeando todas as possibilidades de integração, mas já vejo empresas usando essa estratégia multicanal com muito sucesso. É o futuro do atendimento ao cliente!
Considerações Finais
Criar um chatbot com Java e Spring AI é mais acessível do que muitos imaginam. Se você já domina Spring Boot e APIs REST, tem praticamente todo o conhecimento necessário. O Spring AI é apenas mais uma integração elegante - como conectar com qualquer API externa, mas com abstrações que seguem os padrões Spring que você já conhece.
O segredo está em começar simples e ir evoluindo. Não tente criar o chatbot mais inteligente do mundo no primeiro dia. Comece com funcionalidades básicas, teste com usuários reais, colete feedback e vá melhorando.
Spring AI continua provando a relevância do Java no mundo da IA não por ser a linguagem mais "sexy" para machine learning, mas por oferecer a base sólida, escalável e confiável que aplicações empresariais precisam. Com abstrações elegantes, suporte multi-provider e integração natural com o ecossistema Spring, é a escolha perfeita para desenvolvedores Java que querem entrar no mundo da IA.
O futuro dos chatbots é promissor, e com Spring AI, você tem todas as ferramentas necessárias para fazer parte dessa revolução. Que tal começar hoje mesmo seu primeiro chatbot?
Referências:
- Spring AI Documentation
- Spring AI GitHub Repository
- Spring Boot Documentation
- OpenAI API Documentation
- Building Chatbots with Java and Spring Boot
Este artigo faz parte do meu aprendizado contínuo em IA e Java. Compartilhe suas experiências e dúvidas nos comentários - vamos aprender juntos, pessoal!