Você quer planejar uma viagem para Goa. Você tem cinco dias, um orçamento de 25.000 rúpias e uma preferência clara por praias e frutos do mar. Normalmente, isso significa abrir dez abas no navegador, ler postagens desatualizadas em fóruns e montar manualmente um itinerário. Em vez disso, imagine enviar uma única requisição POST e receber de volta um plano estruturado dia a dia com sugestões de refeições, listas de atividades e uma divisão exata do orçamento. É isso que este projeto entrega.
Vamos construir uma API REST usando Spring Boot e Azure OpenAI. A API aceita um destino, orçamento, duração e interesses. Ela retorna um JSON limpo que um frontend ou aplicativo móvel pode renderizar imediatamente. Sem scraping. Sem itinerários fixos no código. Apenas um modelo de IA instruído para agir como um planejador de viagens.
O que a API retorna
A resposta não é um bloco de texto Markdown que você precisa separar via regex. É um objeto JSON estruturado contendo atividades diárias, recomendações de refeições e um detalhamento do orçamento. Para uma viagem a Goa, você pode receber um segmento do primeiro dia que aloca 500 rúpias para o café da manhã em um quiosque de praia, uma manhã em Palolem e um jantar de frutos do mar à noite em uma localidade específica. Cada dia contém intervalos de tempo, custos estimados e tags como "praia" ou "comida". Essa estrutura é importante porque aplicativos de viagem modernos não querem analisar parágrafos. Eles querem objetos que possam mapear para RecyclerViews ou componentes React.
A Stack e por que ela se encaixa
O projeto utiliza Spring Boot 3.5 com Spring AI. O Spring AI é a peça fundamental. Ele fornece uma abstração unificada de ChatModel, para que você não precise escrever clientes HTTP brutos para o Azure OpenAI. Você troca dependências e propriedades, não o código do serviço.
Você precisará de quatro dependências no seu arquivo de build:
spring-boot-starter-webpara a camada REST.spring-ai-starter-model-azure-openaipara se conectar ao LLM através da interface do Spring AI.springdoc-openapipara documentação Swagger automática.Lombokpara reduzir o boilerplate nos seus POJOs de requisição e resposta.
O Spring AI fica entre sua lógica de negócio e o provedor de LLM. Esse posicionamento é intencional. Ele mantém suas classes @Service limpas e agnósticas em relação ao provedor.
Engenharia de Prompt com PromptTemplates
Inserir prompts fixos dentro de strings Java é uma maneira rápida de criar um software de difícil manutenção. Se a equipe de produto decidir que a IA deve soar mais casual ou recusar estimativas de orçamento acima de um certo limite, você não deve precisar recompilar seu serviço.
O Spring AI fornece o PromptTemplate. Você armazena o esqueleto do prompt em um arquivo de recursos ou em uma string de template dedicada, deixando placeholders para variáveis como {destination}, {budget}, {days} e {interests}. Em tempo de execução, o serviço cria um objeto Prompt e injeta os valores do usuário.
Separe as mensagens de sistema das mensagens de usuário. Use a mensagem de sistema para definir a persona. Por exemplo, você diz ao modelo que ele é um planejador de viagens especializado em destinos indianos, consciente do orçamento e rigoroso ao retornar apenas JSON, sem delimitadores de markdown. Use a mensagem de usuário para passar os detalhes específicos da viagem. Essa divisão ajuda quando você quiser realizar testes A/B de personas posteriormente, sem alterar o contrato da API.
A Camada de Serviço: Conversando com o Azure OpenAI
A classe @Service tem apenas um trabalho. Ela constrói o prompt, chama o modelo, limpa a resposta e analisa o resultado.
Injete o ChatClient ou ChatModel do Spring AI. Renderize o PromptTemplate com os valores da requisição recebida e, em seguida, chame o método de chat. A resposta chega como uma String. É aqui que muitos tutoriais param e o código de produção real começa.
LLMs às vezes adicionam preâmbulos educados. Você pode receber uma resposta que começa com "Aqui está o seu itinerário" e depois despeja um JSON envolvido em crases triplas. Se você tentar desserializar isso diretamente com o Jackson, seu aplicativo irá travar. Adicione um pequeno método auxiliar que percorra a string bruta, encontre a primeira chave de abertura e a última chave de fechamento, e extraia apenas o payload JSON. Em seguida, valide o bloco extraído. Verifique se os campos obrigatórios existem e se os valores numéricos fazem sentido antes de retornar o objeto ao controller.
Esse parsing defensivo não é opcional. É a fronteira entre uma demonstração e uma API confiável.
Tratando Erros como um Sistema Maduro
APIs externas falham. O Azure OpenAI retornará erros de limite de taxa (rate limit), falhas de autenticação ou erros 500 transitórios. Se você permitir que esses erros subam até o usuário como stack traces, você perderá credibilidade.
Use @RestControllerAdvice para interceptar exceções globalmente. Mapeie exceções do Spring AI, HttpClientErrorException e RuntimeExceptions genéricas para respostas de erro consistentes. Retorne um corpo JSON com uma mensagem clara, um status HTTP como 429 para limites de taxa (rate limits) e detalhes suficientes para que o cliente possa tentar novamente ou registrar o problema. O usuário deve ver algo como "Serviço temporariamente ocupado. Por favor, tente novamente em 30 segundos", e não uma tela cheia de nomes de classes Java.
Nunca coloque segredos diretamente no código (Hardcode)
Sua chave de API do Azure OpenAI não deve estar no application.properties enviado ao Git. Externalize-a. Use variáveis de ambiente referenciadas em sua configuração do Spring, como ${AZURE_OPENAI_KEY} e ${AZURE_OPENAI_ENDPOINT}. Mantenha um arquivo .env local para desenvolvimento, adicione-o ao .gitignore e carregue-o através do relaxed binding do Spring Boot. Se uma chave vazar, você a rotaciona em um único lugar em vez de reconstruir seu artefato.
Testando através do Swagger
A dependência springdoc-openapi expõe um endpoint do Swagger UI em tempo de execução. Assim que sua aplicação iniciar, abra /swagger-ui.html no navegador. Você pode preencher o exemplo de Goa diretamente: destino como "Goa", orçamento como 25000, dias como 5, interesses como "beaches, food". Clique em execute e observe o itinerário JSON aparecer. Isso permite validar mudanças de prompts, verificar a serialização e compartilhar um ambiente de testes (playground) ao vivo com desenvolvedores frontend antes que qualquer uma das partes escreva um teste unitário.
Trocando de Provedores sem Reescrever Código
Startups mudam de provedores. Talvez os créditos do Azure expirem, ou você queira executar a inferência em uma instância local do Ollama para reduzir custos. Como o Spring AI abstrai a interface ChatModel, a troca é mecânica. Altere a dependência do Maven de spring-ai-starter-model-azure-openai para outro starter, atualize seu arquivo de propriedades com o novo endpoint e chave, e não mexa na sua classe de serviço. O contrato da API visto pelo seu aplicativo móvel permanece idêntico.
Essa portabilidade torna essa arquitetura particularmente útil para produtos reais. Você não está "se casando" com o Azure. Você o está usando como um motor conectado a um pipeline limpo do Spring.
A Principal Lição
Um modelo de IA não é a sua aplicação. É um serviço externo que retorna texto imprevisível. Trate-o com o mesmo rigor que você daria a um gateway de pagamento ou a uma API de clima de terceiros. Externalize suas credenciais. Valide cada resposta. Limpe o payload antes de fazer o parsing. Trate erros globalmente para que seus usuários nunca vejam um stack trace.
Deixe a IA cuidar do trabalho criativo de construir um itinerário para Goa com um orçamento de 25.000 rúpias. Você cuida da infraestrutura (plumbing). Quando os dois permanecem separados, você obtém um sistema que realmente vai para produção.
O tutorial original que inspirou este artigo pode ser encontrado aqui.
Interessado em discutir Spring AI e projetos similares? Junte-se à comunidade de aprendizado GyaanSetu.
