Vuoi pianificare un viaggio a Goa. Hai cinque giorni, un budget di 25.000 rupie e una chiara preferenza per le spiagge e i frutti di mare. Normalmente, questo significa aprire dieci schede del browser, leggere post di forum obsoleti e comporre manualmente un itinerario. Invece, immagina di inviare una singola richiesta POST e ricevere in risposta un piano strutturato giorno per giorno, con suggerimenti per i pasti, elenchi di attività e una suddivisione esatta del budget. È proprio questo ciò che offre questo progetto.

Costruiremo un'API REST utilizzando Spring Boot e Azure OpenAI. L'API accetta una destinazione, un budget, la durata e gli interessi. Restituisce un JSON pulito che un frontend o un'app mobile può renderizzare immediatamente. Niente scraping. Niente itinerari predefiniti. Solo un modello AI istruito per agire come un pianificatore di viaggi.

Cosa restituisce l'API

La risposta non è un blocco di testo Markdown da analizzare con le regex. È un oggetto JSON strutturato che contiene attività giornaliere, raccomandazioni per i pasti e una ripartizione del budget. Per un viaggio a Goa, potresti ricevere un segmento relativo al primo giorno che alloca 500 rupie per la colazione in un chiosco sulla spiaggia, una mattinata a Palolem e una cena a base di pesce in una specifica località. Ogni giorno include fasce orarie, costi stimati e tag come "beach" o "food". Questa struttura è importante perché le moderne app di viaggio non vogliono analizzare paragrafi. Vogliono oggetti che possano mappare su RecyclerView o componenti React.

Lo stack tecnologico e perché è la scelta giusta

Il progetto utilizza Spring Boot 3.5 con Spring AI. Spring AI è l'elemento fondamentale. Fornisce un'astrazione ChatModel unificata, così non dovrai scrivere client HTTP grezzi per Azure OpenAI. Cambierai le dipendenze e le proprietà, non il codice del servizio.

Hai bisogno di quattro dipendenze nel tuo file di build:

  • spring-boot-starter-web per lo strato REST.
  • spring-ai-starter-model-azure-openai per connettersi al LLM tramite l'interfaccia di Spring AI.
  • springdoc-openapi per la documentazione Swagger automatica.
  • Lombok per ridurre il codice boilerplate nei POJO di richiesta e risposta.

Spring AI si posiziona tra la tua logica di business e il provider del LLM. Questa posizione è intenzionale. Mantiene le classi @Service pulite e indipendenti dal provider.

Prompt Engineering con PromptTemplates

Scrivere i prompt direttamente come stringhe Java è un modo rapido per creare software difficili da mantenere. Se il team di prodotto decide che l'IA debba avere un tono più informale o debba rifiutare stime di budget superiori a una certa soglia, non dovresti dover ricompilare il servizio.

Spring AI fornisce PromptTemplate. Memorizzi lo scheletro del prompt in un file di risorse o in una stringa di template dedicata, lasciando dei segnaposto per variabili come {destination}, {budget}, {days} e {interests}. A runtime, il servizio crea un oggetto Prompt e inietta i valori dell'utente.

Separa i messaggi di sistema dai messaggi dell'utente. Usa il messaggio di sistema per definire la persona. Ad esempio, dici al modello che è un pianificatore di viaggi specializzato in destinazioni indiane, attento al budget e rigoroso nel restituire solo JSON senza delimitatori markdown. Usa il messaggio dell'utente per passare i dettagli specifici del viaggio. Questa separazione è utile quando vorrai effettuare A/B test sulle "persona" senza modificare il contratto dell'API.

Il Service Layer: comunicare con Azure OpenAI

La classe @Service ha un solo compito. Costruisce il prompt, chiama il modello, pulisce la risposta e analizza il risultato.

Inietta ChatClient o ChatModel di Spring AI. Renderizza il PromptTemplate con i valori della richiesta in arrivo, quindi chiama il metodo chat. La risposta arriva come una String. È qui che molti tutorial si fermano e inizia il vero codice di produzione.

I LLM a volte aggiungono preamboli cortesi. Potresti ricevere una risposta che inizia con "Ecco il tuo itinerario" e poi espone un JSON racchiuso tra tripli backtick. Se provi a deserializzarlo direttamente con Jackson, la tua app andrà in crash. Aggiungi un piccolo metodo helper che scansiona la stringa grezza, trova la prima parentesi graffa aperta e l'ultima parentesi graffa chiusa, ed estrae solo il payload JSON. Quindi valida il blocco estratto. Verifica che i campi richiesti esistano e che i valori numerici abbiano senso prima di restituire l'oggetto al controller.

Questo parsing difensivo non è opzionale. È il confine tra una demo e un'API affidabile.

Gestire gli errori come un sistema maturo

Le API esterne possono fallire. Azure OpenAI restituirà errori di limite di frequenza (rate limit), fallimenti di autenticazione o errori 500 transitori. Se permetti a questi errori di propagarsi fino all'utente sotto forma di stack trace, perderai credibilità.

Usa @RestControllerAdvice per intercettare le eccezioni a livello globale. Mappa le eccezioni di Spring AI, HttpClientErrorException e le RuntimeException generiche in risposte di errore coerenti. Restituisci un corpo JSON con un messaggio chiaro, un codice di stato HTTP come 429 per i limiti di velocità (rate limits) e dettagli sufficienti affinché il client possa riprovare o registrare il problema. L'utente dovrebbe vedere qualcosa come "Servizio temporaneamente occupato. Riprova tra 30 secondi", non uno schermo pieno di nomi di classi Java.

Non inserire mai le credenziali nel codice

La tua chiave API di Azure OpenAI non deve trovarsi in application.properties salvato su Git. Esternalizzala. Usa variabili d'ambiente referenziate nella tua configurazione Spring, come ${AZURE_OPENAI_KEY} e ${AZURE_OPENAI_ENDPOINT}. Mantieni un file .env locale per lo sviluppo, aggiungilo a .gitignore e caricalo tramite il relaxed binding di Spring Boot. Se una chiave viene compromessa, puoi ruotarla in un unico punto invece di dover ricostruire l'intero artifact.

Testare tramite Swagger

La dipendenza springdoc-openapi espone un endpoint Swagger UI a runtime. Una volta avviata l'applicazione, apri /swagger-ui.html in un browser. Puoi compilare direttamente l'esempio di Goa: destinazione "Goa", budget 25000, giorni 5, interessi "beaches, food". Clicca su execute e osserva apparire l'itinerario in JSON. Questo ti permette di convalidare le modifiche ai prompt, verificare la serializzazione e condividere un playground attivo con gli sviluppatori frontend prima che uno dei due team scriva un test unitario.

Cambiare provider senza riscrivere il codice

Le startup cambiano spesso provider. Magari i crediti Azure scadono, o vuoi eseguire l'inferenza su un'istanza locale di Ollama per ridurre i costi. Poiché Spring AI astrae l'interfaccia ChatModel, il passaggio è puramente meccanico. Cambia la dipendenza Maven da spring-ai-starter-model-azure-openai a un altro starter, aggiorna il file delle proprietà con il nuovo endpoint e la nuova chiave, e non toccare la tua classe di servizio. Il contratto API visto dalla tua app mobile rimarrà identico.

Questa portabilità rende questa architettura particolarmente utile per prodotti reali. Non ti stai sposando con Azure. Lo stai usando come un motore collegato a una pipeline Spring pulita.

La lezione principale

Un modello AI non è la tua applicazione. È un servizio esterno che restituisce testo imprevedibile. Trattalo con lo stesso rigore che riserveresti a un gateway di pagamento o a un'API meteo di terze parti. Esternalizza le tue credenziali. Valida ogni risposta. Pulisci il payload prima di analizzarlo. Gestisci gli errori globalmente in modo che i tuoi utenti non vedano mai uno stack trace.

Lascia che l'AI si occupi del lavoro creativo di costruire un itinerario per Goa con un budget di 25.000 rupie. Tu occupati dell'infrastruttura. Quando i due aspetti rimangono separati, ottieni un sistema che è effettivamente pronto per il rilascio.

La guida originale che ha ispirato questo articolo può essere trovata qui.

Ti interessa discutere di Spring AI e progetti simili? Unisciti alla community di apprendimento di GyaanSetu.