Chcesz zaplanować wycieczkę do Goa. Masz pięć dni, budżet 25 000 rupii i wyraźną preferencję do plaż oraz owoców morza. Zazwyczaj oznacza to otwieranie dziesięciu kart w przeglądarce, czytanie nieaktualnych wpisów na forach i ręczne składanie planu podróży. Zamiast tego wyobraź sobie wysłanie pojedynczego żądania POST i otrzymanie ustrukturyzowanego, dziennego planu z sugestiami posiłków, listami aktywności i dokładnym podziałem budżetu. To właśnie oferuje ten projekt.

Zbudujemy REST API przy użyciu Spring Boot i Azure OpenAI. API przyjmuje cel podróży, budżet, czas trwania i zainteresowania. Zwraca czysty format JSON, który aplikacja frontendowa lub mobilna może natychmiast wyrenderować. Bez scrapowania, bez sztywno zakodowanych planów. Po prostu model AI z promptem, aby działał jako planista podróży.

Co zwraca API

Odpowiedź nie jest blokiem tekstu Markdown, który musisz rozdzielać za pomocą wyrażeń regularnych. Jest to ustrukturyzowany obiekt JSON zawierający codzienne aktywności, rekomendacje posiłków i podział budżetu. W przypadku wycieczki do Goa możesz otrzymać segment pierwszego dnia, który przydziela 500 rupii na śniadanie w barze przy plaży, poranek w Palolem i wieczorną kolację z owocami morza w konkretnej okolicy. Każdy dzień zawiera przedziały czasowe, szacowane koszty i tagi, takie jak „beach” czy „food”. Ta struktura jest istotna, ponieważ nowoczesne aplikacje podróżnicze nie chcą analizować akapitów. Chcą obiektów, które mogą przypisać do RecyclerView lub komponentów React.

Stos technologiczny i dlaczego pasuje

Projekt wykorzystuje Spring Boot 3.5 wraz ze Spring AI. Spring AI jest kluczowym elementem. Zapewnia ujednoliconą abstrakcję ChatModel, dzięki czemu nie musisz pisać surowych klientów HTTP dla Azure OpenAI. Zmieniasz zależności i właściwości, a nie kod usługi.

W pliku build potrzebujesz czterech zależności:

  • spring-boot-starter-web dla warstwy REST.
  • spring-ai-starter-model-azure-openai do połączenia z LLM poprzez interfejs Spring AI.
  • springdoc-openapi do automatycznej dokumentacji Swagger.
  • Lombok w celu ograniczenia ilości kodu boilerplate w klasach POJO żądań i odpowiedzi.

Spring AI znajduje się pomiędzy Twoją logiką biznesową a dostawcą LLM. To celowe rozwiązanie. Dzięki temu klasy @Service pozostają czyste i niezależne od dostawcy.

Prompt Engineering z użyciem PromptTemplates

Twarde kodowanie promptów wewnątrz ciągów znaków Java to szybka droga do stworzenia nieutrzymywalnego oprogramowania. Jeśli zespół produktowy zdecyduje, że AI powinno brzmieć bardziej swobodnie lub odmawiać szacowania budżetu powyżej pewnego progu, nie powinieneś musieć ponownie kompilować swojej usługi.

Spring AI udostępnia PromptTemplate. Szablon promptu przechowujesz w pliku zasobów lub dedykowanym ciągu znaków szablonu, pozostawiając miejsca na zmienne, takie jak {destination}, {budget}, {days} i {interests}. W czasie wykonywania programu usługa tworzy obiekt Prompt i wstrzykuje wartości użytkownika.

Oddziel wiadomości systemowe od wiadomości użytkownika. Użyj wiadomości systemowej do zdefiniowania persony. Na przykład informujesz model, że jest planistą podróży specjalizującym się w indyjskich kierunkach, dbającym o budżet i rygorystycznie zwracającym wyłącznie JSON bez znaczników markdown. Użyj wiadomości użytkownika do przekazania szczegółów konkretnej podróży. Ten podział pomaga, gdy później będziesz chciał przeprowadzić testy A/B person bez zmiany kontraktu API.

Warstwa serwisu: Komunikacja z Azure OpenAI

Klasa @Service ma jedno zadanie. Buduje prompt, wywołuje model, czyści odpowiedź i parsuje wynik.

Wstrzyknij ChatClient lub ChatModel ze Spring AI. Wyrenderuj PromptTemplate przy użyciu wartości z nadchodzącego żądania, a następnie wywołaj metodę czatu. Odpowiedź przychodzi jako String. To tutaj wiele samouczków się kończy, a zaczyna prawdziwy kod produkcyjny.

Modele LLM czasami dodają uprzejme wstępy. Możesz otrzymać odpowiedź, która zaczyna się od „Oto Twój plan podróży”, a następnie wyrzuca JSON opakowany w potrójne backticki. Jeśli spróbujesz odserializować to bezpośrednio za pomocą Jacksona, Twoja aplikacja ulegnie awarii. Dodaj małą metodę pomocniczą, która przeszuka surowy ciąg znaków, znajdzie pierwszą otwierającą klamrę i ostatnią zamykającą klamrę oraz wyodrębni tylko ładunek JSON. Następnie zweryfikuj wyodrębniony blok. Sprawdź, czy wymagane pola istnieją i czy wartości liczbowe mają sens, zanim zwrócisz obiekt do kontrolera.

Takie defensywne parsowanie nie jest opcjonalne. To granica między demem a niezawodnym API.

Obsługa błędów jak w dojrzałym systemie

Zewnętrzne API zawodzą. Azure OpenAI będzie zwracać błędy limitu zapytań (rate limit), błędy uwierzytelniania lub przejściowe błędy 500. Jeśli pozwolisz, aby te błędy trafiły do użytkownika w formie stosów wywołań (stack traces), stracisz wiarygodność.

Use @RestControllerAdvice to intercept exceptions globally. Map Spring AI exceptions, HttpClientErrorException, and generic RuntimeExceptions to consistent error responses. Return a JSON body with a clear message, an HTTP status like 429 for rate limits, and enough detail for the client to retry or log the issue. The user should see something like "Service temporarily busy. Please retry in 30 seconds," not a screen full of Java class names.

Never Hardcode Secrets

Your Azure OpenAI API key does not belong in application.properties checked into Git. Externalize it. Use environment variables referenced in your Spring configuration, such as ${AZURE_OPENAI_KEY} and ${AZURE_OPENAI_ENDPOINT}. Keep a local .env file for development, add it to .gitignore, and load it through Spring Boot’s relaxed binding. If a key leaks, you rotate it in one place rather than rebuilding your artifact.

Testing Through Swagger

The springdoc-openapi dependency exposes a Swagger UI endpoint at runtime. Once your application starts, open /swagger-ui.html in a browser. You can fill in the Goa example directly: destination as "Goa," budget as 25000, days as 5, interests as "beaches, food." Hit execute and watch the JSON itinerary appear. This lets you validate prompt changes, verify serialization, and share a live playground with frontend developers before either side writes a unit test.

Swapping Providers Without Rewriting Code

Startups change providers. Maybe Azure credits expire, or you want to run inference against a local Ollama instance to cut costs. Because Spring AI abstracts the ChatModel interface, the swap is mechanical. Change the Maven dependency from spring-ai-starter-model-azure-openai to another starter, update your properties file with the new endpoint and key, and leave your service class alone. The API contract seen by your mobile app stays identical.

That portability makes this architecture particularly useful for real products. You are not marrying Azure. You are using it as one engine plugged into a clean Spring pipeline.

The Real Takeaway

An AI model is not your application. It is an external service that returns unpredictable text. Treat it with the same rigor you would give a payment gateway or a third-party weather API. Externalize your credentials. Validate every response. Clean the payload before parsing. Handle errors globally so your users never see a stack trace.

Let the AI handle the creative work of building a Goa itinerary on a 25,000-rupee budget. You handle the plumbing. When the two stay separate, you get a system that actually ships.

The original walkthrough that inspired this article can be found here.

Interested in discussing Spring AI and similar projects? Join the GyaanSetu learning community.