Anda ingin merancang perjalanan ke Goa. Anda mempunyai masa lima hari, bajet sebanyak 25,000 rupee, dan pilihan yang jelas untuk pantai dan makanan laut. Biasanya, ini bermakna anda perlu membuka sepuluh tab pelayar, membaca hantaran forum yang sudah lapuk, dan menyusun jadual perjalanan secara manual. Sebaliknya, bayangkan anda hanya menghantar satu permintaan POST dan mendapat kembali pelan harian berstruktur dengan cadangan hidangan, senarai aktiviti, dan pecahan bajet yang tepat. Itulah yang ditawarkan oleh projek ini.

Kita akan membina satu REST API menggunakan Spring Boot dan Azure OpenAI. API ini menerima destinasi, bajet, tempoh, dan minat. Ia mengembalikan JSON bersih yang boleh dipaparkan dengan segera oleh aplikasi hadapan (frontend) atau aplikasi mudah alih. Tiada proses scraping. Tiada jadual perjalanan yang disematkan secara tetap (hardcoded). Hanya model AI yang diarahkan untuk bertindak sebagai perancang perjalanan.

Apa yang Dikembalikan oleh API

Respons tersebut bukanlah satu blok teks Markdown yang perlu anda asingkan menggunakan regex. Ia adalah objek JSON berstruktur yang mengandungi aktiviti harian, cadangan hidangan, dan pecahan bajet. Untuk perjalanan ke Goa, anda mungkin menerima segmen hari pertama yang memperuntukkan 500 rupee untuk sarapan di gerai pantai, waktu pagi di Palolem, dan makan malam makanan laut pada waktu petang di lokasi tertentu. Setiap hari mengandungi slot masa, anggaran kos, dan tag seperti "beach" atau "food." Struktur ini penting kerana aplikasi pelancongan moden tidak mahu memproses perenggan. Mereka mahukan objek yang boleh dipetakan ke RecyclerView atau komponen React.

Teknologi yang Digunakan dan Mengapa Ia Sesuai

Projek ini menggunakan Spring Boot 3.5 dengan Spring AI. Spring AI adalah komponen yang kritikal. Ia menyediakan abstraksi ChatModel yang seragam supaya anda tidak perlu menulis klien HTTP mentah untuk Azure OpenAI. Anda hanya perlu menukar dependensi dan properti, bukan kod perkhidmatan.

Anda memerlukan empat dependensi dalam fail binaan (build file) anda:

  • spring-boot-starter-web untuk lapisan REST.
  • spring-ai-starter-model-azure-openai untuk menyambung ke LLM melalui antara muka Spring AI.
  • springdoc-openapi untuk dokumentasi Swagger automatik.
  • Lombok untuk mengurangkan kod boilerplate dalam POJO permintaan dan respons anda.

Spring AI terletak di antara logik perniagaan anda dan pembekal LLM. Kedudukan ini adalah disengajakan. Ia memastikan kelas @Service anda kekal bersih dan tidak bergantung kepada pembekal tertentu (provider-agnostic).

Kejuruteraan Prompt dengan PromptTemplates

Menyematkan prompt secara tetap di dalam string Java adalah cara cepat untuk menghasilkan perisian yang sukar diselenggara. Jika pasukan produk memutuskan bahawa AI perlu kedengaran lebih santai atau menolak anggaran bajet melebihi ambang tertentu, anda tidak sepatutnya perlu menyusun semula (recompile) perkhidmatan anda.

Spring AI menyediakan PromptTemplate. Anda menyimpan rangka prompt dalam fail sumber atau string templat khas, dengan meninggalkan tempat letak (placeholder) untuk pemboleh ubah seperti {destination}, {budget}, {days}, dan {interests}. Semasa masa larian (runtime), perkhidmatan akan mencipta objek Prompt dan menyuntik nilai pengguna.

Asingkan mesej sistem daripada mesej pengguna. Gunakan mesej sistem untuk menentukan persona. Sebagai contoh, anda memberitahu model bahawa ia adalah seorang perancang perjalanan yang pakar dalam destinasi India, mementingkan bajet, dan tegas untuk hanya mengembalikan JSON tanpa pagar Markdown (markdown fences). Gunakan mesej pengguna untuk menghantar butiran perjalanan yang khusus. Pengasingan ini membantu apabila anda ingin melakukan ujian A/B terhadap persona pada masa hadapan tanpa mengubah kontrak API.

Lapisan Perkhidmatan: Berkomunikasi dengan Azure OpenAI

Kelas @Service mempunyai satu tugas sahaja. Ia membina prompt, memanggil model, membersihkan respons, dan menghuraikan (parse) hasil tersebut.

Suntik (Inject) ChatClient atau ChatModel daripada Spring AI. Render PromptTemplate dengan nilai permintaan yang diterima, kemudian panggil kaedah sembang (chat method). Respons akan tiba sebagai String. Di sinilah kebanyakan tutorial terhenti dan kod pengeluaran (production) sebenar bermula.

LLM kadangkala menambah mukadimah yang sopan. Anda mungkin mendapat respons yang bermula dengan "Berikut adalah jadual perjalanan anda" dan kemudian memaparkan JSON yang dibungkus dalam tiga tanda backtick. Jika anda cuba melakukan deserialisasi secara terus dengan Jackson, aplikasi anda akan terhenti (crash). Tambah satu kaedah pembantu kecil yang mengimbas string mentah, mencari kurungan pembuka pertama dan kurungan penutup terakhir, serta mengekstrak payload JSON sahaja. Kemudian, sahkan blok yang diekstrak tersebut. Pastikan medan yang diperlukan wujud dan nilai numerik adalah munasabah sebelum anda mengembalikan objek tersebut kepada controller.

Penghuraian defensif ini bukanlah pilihan. Ia adalah sempadan antara demo dan API yang boleh dipercayai.

Mengendalikan Ralat Seperti Sistem yang Matang

API luaran boleh gagal. Azure OpenAI akan mengembalikan ralat had kadar (rate limit), kegagalan pengesahan, atau ralat 500 sementara. Jika anda membiarkan ralat ini muncul kepada pengguna sebagai stack traces, anda akan hilang kredibiliti.

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.