تريد التخطيط لرحلة إلى غوا (Goa). لديك خمسة أيام، وميزانية قدرها 25,000 روبية، وتفضيل واضح للشواطئ والمأكولات البحرية. عادةً ما يعني هذا فتح عشر علامات تبويب في المتصفح، وقراءة منشورات قديمة في المنتديات، وتجميع مسار رحلة يدويًا. بدلاً من ذلك، تخيل إرسال طلب POST واحد والحصول على خطة مهيكلة يومًا بيوم مع اقتراحات للوجبات، وقوائم للأنشطة، وتقسيم دقيق للميزانية. هذا هو بالضبط ما يقدمه هذا المشروع.

سنقوم ببناء REST API باستخدام Spring Boot و Azure OpenAI. تقبل الـ API الوجهة، والميزانية، والمدة، والاهتمامات، وتُرجع JSON نظيفًا يمكن لتطبيق الواجهة الأمامية أو تطبيق الهاتف المحمول عرضه فورًا. لا يوجد كشط بيانات (scraping)، ولا مسارات رحلات مكتوبة مسبقًا (hardcoded). فقط نموذج ذكاء اصطناعي تمت صياغة مطالباته ليعمل كمخطط رحلات.

ما الذي ترجعه الـ API

الاستجابة ليست مجرد كتلة من نص Markdown تضطر لتقسيمها باستخدام التعبيرات النمطية (regex). بل هي كائن JSON مهيكل يحتوي على الأنشطة اليومية، وتوصيات الوجبات، وتفاصيل الميزانية. بالنسبة لرحلة إلى غوا، قد تتلقى جزءًا لليوم الأول يخصص 500 روبية للإفطار في كوخ شاطئي، وفترة صباحية في Palolem، وعشاء مأكولات بحرية في المساء ضمن منطقة محددة. يتضمن كل يوم فترات زمنية، وتكاليف تقديرية، وعلامات (tags) مثل "beach" أو "food". هذه البنية مهمة لأن تطبيقات السفر الحديثة لا تريد تحليل الفقرات النصية، بل تريد كائنات يمكنها ربطها بـ RecyclerViews أو مكونات React.

التقنيات المستخدمة وسبب ملاءمتها

يستخدم المشروع Spring Boot 3.5 مع Spring AI. وتعد Spring AI هي القطعة الأساسية هنا؛ فهي توفر تجريدًا موحدًا لـ ChatModel بحيث لا تضطر إلى كتابة عملاء HTTP خام للتعامل مع Azure OpenAI. يمكنك تبديل التبعيات والخصائص، وليس كود الخدمة نفسه.

تحتاج إلى أربع تبعيات في ملف البناء الخاص بك:

  • spring-boot-starter-web لطبقة REST.
  • spring-ai-starter-model-azure-openai للاتصال بالنموذج اللغوي الكبير (LLM) من خلال واجهة Spring AI.
  • springdoc-openapi لتوثيق Swagger التلقائي.
  • Lombok لتقليل الأكواد المتكررة (boilerplate) في كائنات POJOs الخاصة بالطلب والاستجابة.

تستقر Spring AI بين منطق العمل (business logic) ومزود الـ LLM. هذا التموضع مقصود، فهو يحافظ على نظافة فئات @Service ويجعلها مستقلة عن المزود (provider-agnostic).

هندسة المطالبات باستخدام PromptTemplates

إن كتابة المطالبات (prompts) بشكل ثابت داخل سلاسل Java النصية هي وسيلة سريعة لإنشاء برمجيات يصعب صيانتها. إذا قرر فريق المنتج أن الذكاء الاصطناعي يجب أن يبدو أكثر عفوية أو يرفض تقديرات الميزانية التي تتجاوز حدًا معينًا، فلا يجب أن تضطر إلى إعادة تجميع (recompile) الخدمة الخاصة بك.

توفر Spring AI ميزة PromptTemplate. يمكنك تخزين هيكل المطالبة في ملف موارد أو سلسلة نصية مخصصة للقوالب، مع ترك نائبات (placeholders) للمتغيرات مثل {destination} و {budget} و {days} و {interests}. عند التشغيل، تقوم الخدمة بإنشاء كائن Prompt وتمرير قيم المستخدم إليه.

افصل بين رسائل النظام (system messages) ورسائل المستخدم (user messages). استخدم رسالة النظام لتحديد الشخصية (persona)؛ على سبيل المثال، تخبر النموذج أنه مخطط رحلات متخصص في الوجهات الهندية، ويهتم بالميزانية، ويلتزم بإرجاع JSON فقط بدون علامات markdown. واستخدم رسالة المستخدم لتمرير تفاصيل الرحلة المحددة. يساعد هذا الفصل عندما ترغب لاحقًا في إجراء اختبار A/B للشخصيات دون تغيير عقد الـ API.

طبقة الخدمة: التحدث مع Azure OpenAI

تمتلك فئة @Service وظيفة واحدة: بناء المطالبة، واستدعاء النموذج، وتنظيف الاستجابة، وتحليل النتيجة.

قم بحقن ChatClient أو ChatModel الخاص بـ Spring AI. قم بمعالجة PromptTemplate بقيم الطلب الواردة، ثم استدعِ طريقة الدردشة (chat method). تصل الاستجابة كسلسلة نصية String. وهنا تتوقف العديد من الدروس التعليمية ويبدأ كود الإنتاج الحقيقي.

أحيانًا تضيف نماذج اللغة الكبيرة (LLMs) مقدمات مهذبة. قد تتلقى استجابة تبدأ بعبارة "إليك مسار رحلتك" ثم تتبعها ببيانات JSON مغلفة بعلامات backticks ثلاثية. إذا حاولت إلغاء تسلسل (deserialize) ذلك مباشرة باستخدام Jackson، فسيتعطل تطبيقك. أضف طريقة مساعدة صغيرة تقوم بفحص السلسلة النصية الخام، وتجد أول قوس مفتوح وآخر قوس مغلق، وتستخرج حمولة JSON فقط. ثم قم بالتحقق من صحة الكتلة المستخرجة؛ تأكد من وجود الحقول المطلوبة وأن القيم الرقمية منطقية قبل إرجاع الكائن إلى المتحكم (controller).

هذا التحليل الدفاعي (defensive parsing) ليس خيارًا، بل هو الحد الفاصل بين مجرد نموذج تجريبي وواجهة برمجة تطبيقات (API) موثوقة.

التعامل مع الأخطاء كنظام ناضج

الواجهات البرمجية الخارجية (External APIs) قد تفشل. قد تعيد Azure OpenAI أخطاء تجاوز حد المعدل (rate limit)، أو فشل في المصادقة، أو أخطاء 500 عابرة. إذا سمحت لهذه الأخطاء بالظهور للمستخدم على شكل تتبع للمكدس (stack traces)، فستفقد مصداقيتك.

استخدم @RestControllerAdvice لاعتراض الاستثناءات (exceptions) بشكل عالمي. قم بربط استثناءات Spring AI، و HttpClientErrorException و RuntimeException العامة باستجابات خطأ متسقة. أرجع جسم JSON يحتوي على رسالة واضحة، وحالة HTTP مثل 429 لتجاوز حدود المعدل (rate limits)، وتفاصيل كافية ليتمكن العميل من إعادة المحاولة أو تسجيل المشكلة. يجب أن يرى المستخدم شيئاً مثل "الخدمة مشغولة مؤقتاً. يرجى إعادة المحاولة خلال 30 ثانية"، وليس شاشة مليئة بأسماء فئات Java.

لا تضع الأسرار (Secrets) بشكل ثابت أبداً

مفتاح Azure OpenAI API الخاص بك لا ينبغي أن يكون في ملف application.properties الذي يتم رفعه إلى Git. اجعله خارجياً. استخدم متغيرات البيئة (environment variables) المشار إليها في تكوين Spring الخاص بك، مثل ${AZURE_OPENAI_KEY} و ${AZURE_OPENAI_ENDPOINT}. احتفظ بملف .env محلي للتطوير، وأضفه إلى .gitignore ، وقم بتحميله من خلال خاصية relaxed binding في Spring Boot. إذا تسرب مفتاح ما، يمكنك تدويره في مكان واحد بدلاً من إعادة بناء الـ artifact الخاص بك.

الاختبار عبر Swagger

توفر التبعية (dependency) springdoc-openapi نقطة نهاية (endpoint) لـ Swagger UI أثناء التشغيل. بمجرد بدء تشغيل تطبيقك، افتح /swagger-ui.html في المتصفح. يمكنك ملء مثال Goa مباشرة: الوجهة "Goa"، الميزانية 25000، الأيام 5، والاهتمامات "beaches, food". اضغط على execute وشاهد مسار الرحلة بتنسيق JSON يظهر أمامك. يتيح لك ذلك التحقق من تغييرات الـ prompt، والتحقق من عملية التسلسل (serialization)، ومشاركة بيئة تجريبية حية مع مطوري الواجهة الأمامية (frontend) قبل أن يكتب أي طرف اختبار وحدة (unit test).

تبديل المزودين دون إعادة كتابة الكود

الشركات الناشئة تغير المزودين باستمرار. ربما تنتهي صلاحية أرصدة Azure، أو قد ترغب في تشغيل الاستدلال (inference) مقابل نسخة Ollama محلية لتقليل التكاليف. نظرًا لأن Spring AI يقوم بتجريد (abstracts) واجهة ChatModel ، فإن عملية التبديل تكون آلية. قم بتغيير تبعية Maven من spring-ai-starter-model-azure-openai إلى starter آخر، وقم بتحديث ملف الخصائص (properties file) بنقطة النهاية والمفتاح الجديدين، واترك فئة الخدمة (service class) كما هي. سيبقى عقد الـ API الذي يراه تطبيق الهاتف الخاص بك متطابقاً.

تجعل هذه القابلية للنقل هذه البنية مفيدة بشكل خاص للمنتجات الحقيقية. أنت لا ترتبط بـ Azure للأبد، بل تستخدمه كمحرك واحد متصل بمسار Spring نظيف.

الخلاصة الحقيقية

نموذج الذكاء الاصطناعي ليس هو تطبيقك. إنه خدمة خارجية تعيد نصوصاً غير متوقعة. تعامل معه بنفس الصرامة التي تتعامل بها مع بوابة دفع أو API للطقس من طرف ثالث. اجعل بيانات الاعتماد (credentials) خارجية. تحقق من كل استجابة. قم بتنظيف الحمولة (payload) قبل تحليلها (parsing). تعامل مع الأخطاء بشكل عالمي حتى لا يرى مستخدموك تتبع المكدس (stack trace) أبداً.

اترك للذكاء الاصطناعي العمل الإبداعي المتمثل في بناء مسار رحلة إلى Goa بميزانية 25,000 روبية. وتولَّ أنت الجوانب التقنية الأساسية (plumbing). عندما يظل الاثنان منفصلين، ستحصل على نظام قابل للإطلاق الفعلي.

يمكن العثور على الشرح الأصلي الذي ألهم هذا المقال هنا.

هل أنت مهتم بمناقشة Spring AI ومشاريع مماثلة؟ انضم إلى مجتمع GyaanSetu التعليمي.