image

Acesse bootcamps ilimitados e +650 cursos pra sempre

60
%OFF
Article image
Alexandre Lima
Alexandre Lima25/06/2025 18:29
Compartilhe

Criando Seu Primeiro Chatbot com Java e Spring Boot: Um Guia Prático

    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

    image

    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

    java
    @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

    java
    @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:

    image

    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:

    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!

    Compartilhe
    Recomendados para você
    Savegnago - Lógica de Programação
    meutudo - Mobile Developer
    NTT DATA - Java e IA para Iniciantes
    Comentários (0)