تطبيق ويب عادي يتكلم مع نموذج ذكاء اصطناعي مباشرة من كود PHP بتاعك، بدون أي أداة خارجية أو مساعد منفصل؟ ليش لا؟ خلنا نشوف كيف بالضبط.
Laravel AI SDK مو أداة تساعدك تكتب كود، هذي وظيفة أدوات ثانية زي Laravel Boost. وظيفة هذي الحزمة مختلفة تمامًا: هي تخلي تطبيقك نفسه ذكي. يعني لو تبي المستخدم يقدر يولّد صورة "دونات فضائي" من نموذج، أو يترجم تعليق تلقائيًا، أو يتفاعل مع بوت محادثة يستخدم نماذج محلية أو خارجية — هنا بالضبط تستخدم هذا الـ SDK. هي مكتبة تخليك تطلق أوامر (prompts) مباشرة من كود PHP بتاعك، وبخلاف Laravel Boost، هذي تعتبر اعتمادية (dependency) عادية للتطبيق نفسه، لأنها بتكون جزء من المزايا اللي المستخدم النهائي بيستفيد منها فعليًا.
طيب كيف تركّب وتضبط Laravel AI SDK؟
بخلاف Laravel Boost، حزمة Laravel AI SDK هي اللي تخليك فعليًا "تلخبط إيدك" وتبني منطق الذكاء الاصطناعي والأوامر الخاصة بك داخل التطبيق.
الخطوات الأولى:
أولًا التركيب، شغّل أمر التركيب:
$ composer require laravel/ai
$ php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
$ php artisan migrate
بتلاحظ إن الهجرات (migrations) بتنشئ جداول خاصة بـ"المحادثات" و"الوكلاء" (agents). لازم تشغّل أمر الهجرة عشان الـ SDK يكون عنده مكان يحفظ فيه التاريخ والإعدادات.
بعدها، مفتاح الـ API: في ملف .env بتاعك، لازم تضبط مفتاحك. الملف بيحتوي على متغيرات زي:
ANTHROPIC_API_KEY=
COHERE_API_KEY=
ELEVENLABS_API_KEY=
GEMINI_API_KEY=
MISTRAL_API_KEY=
OLLAMA_API_KEY=
OPENAI_API_KEY=
JINA_API_KEY=
VOYAGEAI_API_KEY=
XAI_API_KEY=
لو تدور على خيار مجاني تبدأ فيه، Google AI Studio وحد من أفضل الخيارات — بس تدخل، تولّد مفتاح Gemini API، وتلصقه في مشروعك.
طيب وش هو قلب الـ SDK فعليًا؟ الوكلاء (Agents)
الفرق الكبير هنا إن كل شي يشتغل عن طريق الوكلاء (Agents). الوكيل هو زي "موظف افتراضي" تعطيه مهمة محددة، مثلًا: "أنت مترجم"، "أنت خبير SEO"، "أنت مولّد صور". وبمجرد ما يكون الوكيل مضبوط، تتفاعل معه عن طريق الأوامر (prompts):
use Laravel\Ai\Enums\Lab;
use function Laravel\Ai\agent;
Route::get('/laravel-ai-text', function () {
$response = agent(
instructions: 'You are an expert assistant in Laravel.',)->prompt(
'Generate a list of 3 Laravel 13 topics in JSON format',
provider: Lab::Gemini,
model: <YOURIDLOCALMODAL>);
dd($response);
});
هذا اللي شفناه يسمى "وكيل مجهول" (Anonymous Agent)، لأن البنية الموصى فيها هي إنشاء وكلاء منفصلين بكلاسات خاصة، وهذا بنشوفه بعدين بالتفصيل.
Google Antigravity: قوة بدون تعقيد
بما إننا تجاوزنا منحنى التعلم الخاص بـ Laravel Boost وOpenCode، فـ Google Antigravity بيكون سهل جدًا. هو أبسط لأنه أقل تجريدًا: هو بيئة تطوير (IDE) عندها الذكاء الاصطناعي في "الحمض النووي" بتاعها من الأساس.
بس قبل ما نكمل، خلني أوضح أكثر الفرق الجوهري: التكامل مقابل التخصيص. الاثنين ما "يتطابقان" أبدًا:
- OpenCode (VS Code): بيئة مفتوحة. تركّب الوكلاء بنفسك، تضبط ملفات Markdown، وعندك تحكم دقيق جدًا.
- Antigravity: بيئة مغلقة. أكثر محدودية من ناحية القواعد الخارجية (ما تقدر تنسخ وتلصق أدلة OpenCode هنا مباشرة)، لكن بالمقابل يوفر لك نماذج من الدرجة الأولى (Gemini 3 Pro، Claude، Sonnet) مجانًا في خطته الحالية.
طيب وش قصة الإعدادات والقواعد المخصصة؟
رغم إنه أكثر انغلاقًا، هذا ما يعني إنك ما تقدر تعطيه أوامر. في نقاط الواجهة الثلاث، تقدر تخصص القواعد، إما بشكل عام أو لمساحة عمل محددة. لما تحفظ هذي القواعد، بينشئ ملف gemini.md (شبيه جدًا بالي شفناه سابقًا). النصيحة هنا: ما أضيّع وقت كثير في ضبط هذا الجزء — بستخدم Antigravity للتطويرات المحددة اللي أعطيه فيها كل المعلومات اللازمة داخل الأمر نفسه.
طيب وش الفرق بين وضع التخطيط ووضع السرعة؟
زي ما هو الحال في OpenCode، عندنا هنا وضعين للعمل:
- وضع التخطيط (Planning Mode): مثالي للمهام المعقدة. الذكاء الاصطناعي يحلل المشروع كامل، يقترح خطة، ولو وافقت، ينفذها. حتى يقدر يفتح متصفح يتأكد إن الشي اللي سواه شغّال فعليًا لحظيًا.
- وضع السرعة (Fast Mode / Phase): هذا اللي أستخدمه أكثر شي. للتعديلات السريعة والمباشرة. تقول له: "عدّل هذا الملف عشان يضيف إضافة CKEditor" وهو يسويها فورًا.
طيب وش سير العمل اللي تتبعه شخصيًا؟
لما أشتغل مع AntiGravity، أتبع دورة واضحة جدًا:
- التكرار: أطلب التعديل وأنتظر لين يخلص.
- الاختبار: لو اشتغل، أزامن مع Git (git commit).
- التصحيح: لو ما اشتغل، أطلب منه يصحح.
- الرجوع: لو لاحظت إن الذكاء الاصطناعي صار يتخبط وما يفهم، أستخدم git checkout أو git reset، أنظف الطاولة وأبدأ من نقطة آمنة.
الشي اللي يعجبني فعلًا في هذي الأداة هو المساعدة البصرية. تحدد باللون الأصفر بالضبط أي أسطر بتتغير قبل ما تطبقها. هذا يعطيك أمان ما تحصله دايمًا في ترمنال VS Code.
طيب أي وحدة تختار؟ OpenCode ولا Antigravity؟
لو أنت على الخطة المجانية، هذي هي المعضلة:
- OpenCode: تحكم كامل، تقدر تستخدم كل مهارات (skills) بتاعت Laravel Boost، ومرونة هائلة، لكنها تتطلب إنك تعرف الأداة زين.
- Antigravity: عندك أفضل النماذج بالسوق (شامل Claude وGPT) مجانًا، وواجهة أكثر ودّية للتطوير السريع، لكن بقدرة أقل على التدقيق العميق.
بالخلاصة: أستخدم OpenCode للتدقيقات، مراجعات قاعدة البيانات، أو معماريات لارافيل المعقدة، وأستخدم Antigravity لإنشاء مزايا جديدة بسرعة وبصريًا.
تمرين عملي: إعادة تصميم باستخدام Google Antigravity
خلنا نختبر Google Antigravity في تمرين حقيقي: إعادة تصميم بسيطة لتطبيق لارافيل اللي نبنيه. رغم إني أثق بالأداة، توصيتي الأولى دايمًا واحدة: زامن مع Git قبل ما تبدأ. بهالطريقة لو النتيجة ما عجبتك، أمر git reset يرجعك خلال ثواني.
تجهيز السياق: عشان الذكاء الاصطناعي ما "يهلوس"، الأفضل تعطيه سياق. في هذي الحالة، نسخت المسار النسبي لمجلد css (اللي فيه أنماط المدونة ولوحة التحكم)، ودورت على صورة إلهام من الإنترنت أعطيها كمرجع بصري.
الأمر: واضح ومباشر. هدفي مو تغيير هيكل HTML (عشان ما نعقد الدورة)، بس تغيير النمط. استخدمت وضع السرعة ونموذج Gemini 3 Flash. الأمر كان تقريبًا:
"سوّي إعادة تصميم بسيطة للتطبيق، اكتب فوق ملفات CSS الموجودة في resources/css/blog.css وresources/css/dashboard.css قدر الإمكان، وأبي تستوحي من الصورة المرفقة."
ملاحظة: لو عندك شك كيف تتعامل مع تغيير كبير، استخدم وضع التخطيط؛ بس لشي محدد زي هذا، وضع السرعة مثالي.
التنفيذ والنتائج: بخلاف OpenCode، هنا تشوف الذكاء الاصطناعي يبدأ يفحص المشروع بصريًا. يحدد باللون الأصفر بالضبط أي أسطر CSS بيغيرها أو يضيفها. مساعدة بصرية قوية جدًا. هذا مثال مثالي عن كيف تتفاعل مع الذكاء الاصطناعي في Antigravity: تطلب "أعد تصميم هذا"، تنتظر وتشوف كيف يطبّق التغييرات بصريًا، تختبر (تسجل، توثّق، وتتصفح التطبيق)، وتقرر — لو عجبتك النتيجة تزامن، لو لأ ترجع بـGit. في حالتي، رغم وجود أخطاء بسيطة راح أصلحها لاحقًا، النتيجة كانت مقبولة عندي.
إضافة مربع تأكيد عند الحذف
لنختم هذا التمرين مع Google Antigravity، بنحل تفصيلة معلّقة: مربع تأكيد الحذف. حاليًا، لو تضغط "حذف"، المنشور ينحذف مباشرة بدون ما يسألك، وهذا خطير.
الحلو في Antigravity إنك ما تحتاج تكون دقيق جدًا لو الكود منظم زين. بس مررت له ملف index.blade.php الخاص بالفئات وطلبت:
"طبّق مربع تأكيد لزر الحذف: resources/views/dashboard/post/index.blade.php وresources/views/dashboard/category/index.blade.php"
ما احتجت أوضح له أي زر بالضبط؛ الذكاء الاصطناعي حلل السياق، لقى العلامة اللي مكتوب فيها "حذف"، وفهم بالضبط وين لازم يتدخل. شوف الحل اللي طلع فيه، JavaScript أصلي وفعّال، طبّقه مباشرة داخل حدث onsubmit في النموذج:
<form action="{{ route('category.destroy', $category) }}" method="POST" onsubmit="return confirm('Are you sure you want to delete this category?')">
كيف تشتغل هذي المنطقة؟ تستخدم دالة confirm() الأصلية، اللي تعرض نافذة منبثقة في المتصفح. هذي الدالة ترجّع true (لو ضغطت موافق) أو false (لو ضغطت إلغاء). ولما تحطها داخل onsubmit، لو المستخدم ألغى، النموذج يستلم false ويوقف الإرسال تلقائيًا. طريقة نظيفة ومباشرة لحماية البيانات بدون ما تحتاج تركّب مكتبات ثقيلة.
هذا التمرين الثاني يوضح ليش أستخدم Antigravity كثير لما أسميه "التطوير الروحاني" (تلك التعديلات السريعة اللي تعطيك راحة بال). لو كنا نبي شي أكثر تعقيدًا، زي وحدة منفصلة في dashboard.js، الذكاء الاصطناعي كان بيتكفل بإنشاء الملف، تصدير الوحدة، واستيرادها بشكل صحيح. الشي اللي كان بياخذ منا دقايق من "نجارة" الكود، الذكاء الاصطناعي يحله في 3 أو 4 دقايق.
طيب كيف تستخدم نموذج محلي (Local LLM) مع Jan.ai أو LM Studio؟
في هذا القسم، بأوريك كيف تستخدم نموذج لغوي كبير محلي (LLM) باستخدام Jan.ai أو LM Studio بالتزامن مع Laravel AI SDK. هذا حل مثالي لو أنت في منطقة فيها خدمات زي Gemini أو ChatGPT محظورة، أو ببساطة تدور على خصوصية وتوفير في التكلفة.
وش هو النموذج اللغوي المحلي أصلًا؟ الـ LLM هو ذكاء اصطناعي مصمم يفهم ويولّد اللغة البشرية. لما تشغّله محليًا، تستفيد من قوة جهازك بنفسك بدون ما تعتمد على اتصال إنترنت أو خطط مدفوعة. المزايا واضحة: خصوصية (بياناتك ما تطلع من جهازك أبدًا)، بدون تكاليف (ما فيه حدود توكنز أو اشتراكات شهرية)، وبدون حظر (ما تعتمد على قيود جغرافية).
الأدوات والمتطلبات: عشان تشغّل ذكاء اصطناعي محلي، تحتاج برنامج يدير النموذج وجهاز عنده قدرة معينة. أشهر الخيارات هي Ollama وLM Studio وJan.ai.
اعتبارات العتاد (RAM مقابل VRAM): قوة النموذج اللي تختاره بتعتمد على الذاكرة عندك:
- ويندوز/لينكس: تعتمد بشكل كبير على ذاكرة كرت الشاشة (VRAM). لو النموذج تجاوز قدرتك (مثلًا عندك 12 جيجا VRAM وتحاول تشغّل نموذج ثقيل)، النظام بيستخدم الرام العادية، والأداء بينخفض بشكل كبير.
- ماك (شرائح M): تستخدم ذاكرة موحدة، يعني المعالج وكرت الشاشة يتشاركون نفس الرام (مثلًا 24 جيجا)، وهذا يخلي تشغيل نماذج أكبر (زي اللي عندها 31 مليار معلمة) أكثر سلاسة.
إعداد السيرفر المحلي: كل من Jan.ai وLM Studio يتبعون معيار OpenAI API، وهذا يسهّل كثير التكامل مع لارافيل. تحمّل وتركّب Jan.ai من موقعه الرسمي، تروح لـSettings > Local API Server وتفعّله. افتراضيًا، السيرفر بيشتغل على http://localhost:1337/v1. وتقدر تضبط مفتاح بسيط (مثلًا 12345) في إعدادات السيرفر المحلي.
LM Studio، زي Jan.ai، خيار ممتاز آخر متاح، وعنده سيرفر محلي بنفس الطريقة، فتقدر تستخدمه بنفس المشروع تقريبًا بدون أي تغيير، لكن بخلاف Jan، LM Studio ما يستخدم رمز حماية Bearer وافتراضيًا منفذه 1234 (تقدر تغيّره). هذا جدول يلخص الفروقات:
Jan.ai: الرابط الافتراضي http://localhost:1337/v1، مفتاح API أي نص (مثل "sk-local")، التوافق بأسلوب OpenAI (v1)، الترويسات تحتاج Content-Type + Authorization، المحرك الرئيسي nitro (متخصص في GGUF).
LM Studio: الرابط الافتراضي http://localhost:1234/v1، مفتاح API غير مطلوب/يتجاهل، توافق 100% مع OpenAI، الترويسات Content-Type فقط (Auth اختياري)، المحرك الرئيسي llama.cpp.
تقدر تحمّل LM Studio من lmstudio.ai، وعمله شبيه جدًا بـJan.ai، فما بتتوه. زي Jan، تقدر تتصل بـLM Studio بنفس الطريقة تقريبًا لأنه يعرض واجهة برمجية متوافقة 100% مع OpenAI، وفيها نقاط نهاية مهمة زي v1/chat/completions و/v1/models. بس تنتبه: LM Studio ما يستخدم مفتاح API، يعني لازم تشيل ترويسة Authorization لأنه إما يتجاهلها أو يرجّع خطأ 401 في بعض الحالات.
التكامل مع Laravel AI SDK: عشان توصل لارافيل بذكائك الاصطناعي المحلي، لازم تسوي "حيلة" صغيرة في ملف .env. بما إن Laravel SDK ما عنده درايفر أصلي لـJan.ai، بنستخدم درايفر Deepseek أو OpenAI عشان "يجسّر" (محاكاة درايفر OpenAI) الاتصال.
الإعدادات في .env:
# We use the Deepseek driver to take advantage of its endpoint compatibility
AI_DRIVER=deepseek
DEEPSEEK_API_KEY=your_local_key_12345
DEEPSEEK_BASE_URL=http://localhost:1337/v1
وتنشر ملف الإعدادات:
$ php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
طيب كيف تربط نموذج محلي عبر Jan.ai بتطبيق Python باستخدام Flask؟
في هذا القسم بنتصل بالنموذج المحلي. أول شي، لازم نفعّل السيرفر المحلي لـJan.ai من Settings → Local App Server → اضبط مفتاحك → Start. وفي LM Studio: روح لتبويب Local Server (أيقونة السهمين المزدوجين أو أيقونة المطور)، اضغط زر Start Server (بخلاف Jan.ai، ما تحتاج تضبط مفتاح API هنا؛ الوصول مباشر عبر الرابط الأساسي اللي البرنامج يوفره).
بالنسبة للجزء الأساسي، وحدة التحكم (controller) اللي بتتصل بالنموذج، عندنا رابط الطلب وترويساته. نحدد رابط نقطة النهاية اللي بنرسل لها الطلب:
http://localhost:port/v1/chat/completions
والترويسات اللي يحتاجها Jan:
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer 12345"
}
النموذج هو الي حددته في Jan (لازم تكتبه بالضبط، مثال: gemma-3-12b). والرسالة هي مصفوفة JSON فيها دور المستخدم والمحتوى:
data = {
"model": "gemma-3-12b-it-IQ4_XS",
"messages": [
# {"role": "user", "content": request.form.get("prompt", "Tell me a joke.")}
{"role": "user", "content": prompt}
]
}
بما إن هذا ممكن يفشل، نغلفه بـtry block. نسوّي requests.post() نرسل فيه: الرابط، الترويسات، وjson=data. بعدها نتحقق من الاستجابة: لو response.status_code == 200، كل شي تمام. رمز 404 يعني الرابط مو موجود، ورمز 500 يعني شي انفجر بالسيرفر أو أرسلت شي غير صالح.
التنفيذ في الكود: عشان تسوي طلب، تقدر تستخدم وكيل أو استدعاء مباشر. مهم جدًا إن اسم النموذج يطابق بالضبط الرقم التعريفي المعطى من Jan.ai:
use Illuminate\Support\Facades\AI;
$response = AI::chat('Generate a list of 3 Laravel 13 topics in JSON format')
->withDriver('deepseek')
->withModel('gemma-2-9b-it') // Replace with your local model ID
->send();
return $response;
أي نموذج محلي تختار؟ لو ما تعرف أي نموذج تحمّل، أنصحك تبدأ بـGemma (من جوجل) أو Llama. لو عندك شك عن أي نموذج جهازك يدعم، تقدر تاخذ لقطة شاشة لمواصفاتك التقنية وتسأل الذكاء الاصطناعي نفسه؛ بيقولك لو نموذج خفيف بـ7 مليار معلمة يناسبك، أو نموذج أقوى بـ31 مليار.
مثال: عندك ماك ميني M4 Pro، أي نموذج تستخدم؟
- الفئة الثقيلة (31 مليار معلمة) – للتفكير المعقد: أذكى نماذج القائمة. مناسبة لما تحتاج الذكاء الاصطناعي يفهم معمارية برمجية معقدة، يحل أخطاء صعبة في لارافيل 13، أو يكتب محتوى تعليمي عميق. الأداء على M4 Pro: بيشتغل زين، بس بيستهلك رام كثير (حوالي 16-18 جيجا للنموذج بس). لو عندك 24 جيجا رام أو أكثر، هذا نموذجك الأساسي.
- الفئة المتوازنة (26 مليار معلمة) – النموذج الشامل: معمارية "خليط خبراء" (Mixture of Experts)، أسرع من نموذج الـ31 مليار لكنها تحافظ على نفس القوة تقريبًا. مثالية للتوليد اليومي للكود واختبارات الوحدة. الأداء على M4 Pro: بيطير، هي النقطة المثالية بين السرعة والذكاء.
- الفئة الخفيفة (E4B / E2B) – للأتمتة والسرعة: نماذج بـ4 و2 مليار معلمة. مصممة عشان تكون فائقة السرعة. تستخدمها للمهام البسيطة زي إكمال الكود التلقائي، تنسيق JSON، أو تلخيص نصوص قصيرة. الأداء على M4 Pro: فوري، بالكاد بتلاحظ إنها تشتغل.
بالنسبة لكرت RTX 5070، بخلاف معالج آبل، الكرت هذا عنده أنوية Tensor مصممة خصيصًا للذكاء الاصطناعي، يعني النماذج اللي تختارها بترد تقريبًا بوقت فعلي. النموذج المتوسط (26 مليار، خليط خبراء) موصى فيه لأنه ينشط جزء بسيط بس من المعاملات (4 مليار نشطة)، وبيتناسب زين في ذاكرة الكرت مع ضغط من نوع Q4_K_M أو Q5، وبتحصل سرعة توليد نص أعلى من الماك. النموذج الثقيل (31 مليار) هو "الحد الأقصى" — بما إنه كثيف ودايمًا يستخدم كل الـ31 مليار، غالبًا بيفيض عن ذاكرة الكرت وبينتقل للرام العادية، وهناك السرعة تنخفض بشكل كبير (من 50 توكن بالثانية إلى 2 أو 3). استخدمه بس لو تحتاج أعلى دقة ممكنة وما يضايقك تنتظر ثواني إضافية. والنموذج الخفيف (4 مليار) مثالي للتطوير، فوري تمامًا، تقدر تستخدمه كمساعد يقترح كود بوقت فعلي وأنت تكتب كتبك أو دوراتك بدون ما مروحة الكرت حتى تتحرك.
بهذا، صار عندك بيئة ذكاء اصطناعي خاصة وشغّالة بالكامل داخل لارافيل!
أمثلة عملية على الوكلاء المجهولين: محادثة، كود، وتحليل مشاعر
عشان أعطيك تنوع أكثر، جهزت ثلاثة أمثلة إضافية. رغم إن الأساس شبيه بالي شفناه، هذي الحالات بتساعدك تتخيل كيف تبني مشروع حقيقي وفعّال في المستقبل، أبعد من مجرد طلب ثابت.
أنشأت وحدة تحكم جديدة فيها ثلاث دوال محددة: محادثة عامة (محادثة مفتوحة)، توليد كود (إنشاء هياكل زي هجرات لارافيل)، وتحليل مشاعر (تحديد لو الرسالة إيجابية، سلبية، أو محايدة).
// Basic Chat
Route::get('/agents/chat', [AgentsTestController::class, 'chat']);
// Code generation
Route::get('/agents/generate-code', [AgentsTestController::class, 'generateCode']);
// Sentiment analysis
Route::get('/agents/analyze', [AgentsTestController::class, 'analyze']);
<?php
namespace App\Http\Controllers;
use App\Ai\Agents\PokemonAgent;
use App\Ai\Agents\QuizAgent;
use App\Ai\Agents\QuizGenerator;
use App\Ai\Agents\SalesCoach;
use App\Models\Post;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Laravel\Ai\Enums\Lab;
use function Laravel\Ai\agent;
class AgentsTestController extends Controller
{
public function chat(Request $request): JsonResponse
{
$message = $request->input('message', 'Hello, tell me what you know about Laravel');
$response = agent(
instructions: 'You are a helpful and concise assistant.',
)->prompt(
$message,
model: <YOURIDLOCALMODAL>
);
return response()->json([
'answer' => $response->text,
'model' => 'gemma3:12b-it-IQ4_XS (Ollama)',
]);
}
public function generateCode(Request $request): JsonResponse
{
$type = $request->input('type', 'users migration');
$response = agent(
instructions: 'You are a Laravel expert. You generate clean code and follow best practices.',
)->prompt(
"Generate {$type} in Laravel 13. Only give me the code, without explanations.",
model: <YOURIDLOCALMODAL>
);
return response()->json([
'code' => $response->text,
'request' => $type,
]);
}
public function analyze(Request $request): JsonResponse
{
$text = $request->input('text', 'This product is incredible, I totally recommend it');
$response = agent(
instructions: 'You are a sentiment analyzer. You respond only with positive, negative, or neutral.',
)->prompt(
"Classify the following text: {$text}",
model: <YOURIDLOCALMODAL>
);
return response()->json([
'sentiment' => trim($response->text),
'original_text' => $text,
]);
}
}
تحليل الأمثلة الجديدة: أول شي، الديناميكية مع request()->input(). عشان يكون هذا تطبيق ويب حقيقي، نحتاج المستخدم يتفاعل. ضبطت الدوال تدور على حقل اسمه "text" في الطلب، ولو ما كان موجود، نستخدم قيمة افتراضية:
$input = $request->input('text', 'What do you know about Laravel?');
هذا بيخلينا نربط منطقنا بسهولة بنموذج مستخدم في المستقبل. ثانيًا، الوكلاء المجهولين وتحليل المشاعر: في هذي الأمثلة نستخدم وكلاء مجهولين، يعني وكلاء نعرّفهم "أثناء التنفيذ" بتعليمات محددة. ثالثًا، توليد كود مخصص: هنا نجمع بين أمر ثابت ومدخل المستخدم، ونقول للذكاء الاصطناعي إنه خبير في لارافيل 13 ولازم يولّد كود بدون شروحات إضافية.
أمثلة على الاستجابات الفعلية:
من http://larapackage.test/agents/chat جاءت إجابة تشرح لارافيل بتفصيل (Eloquent ORM، Routes، Middleware، Blade، Migrations، Queues، Security).
من http://larapackage.test/agents/generate-code جاء كود PHP كامل لهجرة جدول المستخدمين.
من http://larapackage.test/agents/analyze جاءت النتيجة: sentiment إيجابي (positive) للنص المرسل.
ملاحظات أخيرة عن الاستجابات: تذكر إنه حتى لو كان الأمر نفسه، أنظمة الذكاء الاصطناعي عشوائية إحصائيًا، يعني ما راح تعطيك دايمًا نفس الإجابة بالضبط، وهذا جزء من سحر معالجة اللغة الطبيعية. لو لاحظت إن الاتصال بياخذ وقت أو يفشل أحيانًا، هذا عادة بسبب الحمل على العتاد (المعالج/كرت الشاشة) وقت معالجة النموذج محليًا، مو خطأ برمجي. أخطاء شائعة تشوفها زي انتهاء مهلة الاتصال (cURL error 28) أو خطأ 400.
الوكلاء المنظمين: أبعد من الوكلاء المجهولين
لحد الحين اشتغلنا مع وكلاء مجهولين، وهي أسرع وأبسط طريقة للبدء. لكن في توثيق Laravel AI SDK، بنلاقي شي نسميه "الوكلاء المسمّون" أو كلاسات الوكلاء. هذي تخلينا ننظم منطق الذكاء الاصطناعي بطريقة أكثر احترافية وقابلية لإعادة الاستخدام.
بنية الوكيل: لما تنشئ وكيل (زي مثال SalesCoach)، بتلاقي بنية مثيرة للاهتمام: constructor (يسمح تمرر بيانات خارجية للوكيل)، instructions (يحدد سلوك أو دور الذكاء الاصطناعي)، messages (دالة داخلية لتحميل محادثات أو سياق تاريخي سابق — مهمة جدًا عشان الذكاء الاصطناعي ما "يبدأ من الصفر" في كل تفاعل)، وtools (تسمح للذكاء الاصطناعي ينفذ دوال برمجية، مثل توليد رقم عشوائي أو استعلام واجهة برمجية خارجية، لحل مهام محددة).
إنشاء الوكلاء عبر سطر الأوامر: الـ SDK يوفر أمرين رئيسيين تحدد كيف بيكون شكل استجابة الذكاء الاصطناعي:
وكيل قياسي:
$ php artisan make:agent SalesCoach
ينشئ بنية أساسية فيها تعليمات، رسائل، وأدوات، لكن بدون تنسيق مخرجات إلزامي.
وكيل ببنية محددة (Schema):
$ php artisan make:agent SalesCoach --structured
<?php
namespace App\Ai\Agents;
use App\Ai\Tools\RetrievePreviousTranscripts;
use App\Models\User;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Messages\Message;
use Laravel\Ai\Promptable;
use Stringable;
class SalesCoach implements Agent, HasTools //Conversational
{
use Promptable;
public function __construct(public ?User $user = null) {}
public function instructions(): Stringable|string
{
return 'You are a sales coach, analyzing transcripts and providing feedback and an overall sales strength score.';
}
public function tools(): iterable
{
***
}
public function schema(JsonSchema $schema): array
{
return [
'feedback' => $schema->string()->required(),
'score' => $schema->integer()->min(1)->max(10)->required(),
];
}
}
هذا هو الأكثر أهمية لنا، لأنه يطبّق مخطط بنية (Schema). في لارافيل، إحنا متعودين إن كل شي منظم بشكل صارم. لو استعلمت عن نموذج Post، تعرف بالضبط أي حقول متوقعة. لو استخدمت دالة all()، تتوقع مجموعة (collection). مع الذكاء الاصطناعي، لو ما حددنا مخطط، شكل الاستجابة ممكن يتغير ويكسر تطبيقنا.
ليش نستخدم استجابات منظمة؟ بتحديد مخطط، نجبر الذكاء الاصطناعي إنه يخلي استجابته تطابق بالضبط الشكل اللي نحتاجه (عادة JSON). هذا ممتاز لأن: التكامل (تقدر تربط استجابة الذكاء الاصطناعي مباشرة بوحدة Livewire أو وحدة تحكم)، الاتساق (لو عندك تطبيق كواكب، تقدر تفرض إن الذكاء الاصطناعي دايمًا يرجّع: الاسم، الوصف، القطر، الكثافة)، والأمان (تضمن إن كودك يقدر يعالج البيانات بدون أخطاء تنسيق غير متوقعة).
مثال البوكيمون: لهذا المثال، ضبطت وكيل خبير في البوكيمون. التعليمة واضحة: "أنت خبير بوكيمون، ولّد بيانات واقعية بصيغة JSON دقيقة."
Route::get('/agents/pokemon-list', [AgentsTestController::class, 'listPokemon']);
public function listPokemon(Request $request): JsonResponse
{
$result = (new PokemonAgent)->prompt(
'Generate a list of 3 different Pokemon',
model: <YOURIDLOCALMODAL>
timeout: 120
);
return response()->json([
'result' => $result->toArray(),
'schema' => [
'pokemon' => [
'*' => [
'name' => 'string',
'type' => 'enum',
'size' => 'integer',
],
],
],
]);
}
<?php
namespace App\Ai\Agents;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;
use Stringable;
class PokemonAgent implements Agent, HasStructuredOutput
{
use Promptable;
public function instructions(): Stringable|string
{
return 'You are a Pokemon expert. You generate realistic Pokemon lists in exact JSON format.';
}
public function schema(JsonSchema $schema): array
{
return [
'pokemon' => $schema->array()->items(
$schema->object([
'name' => $schema->string()->required(),
'type' => $schema->string()->enum([
'fire', 'water', 'electric', 'grass', 'normal', 'flying', 'psychic', 'rock', 'ground',
])->required(),
'size' => $schema->integer()->min(10)->max(500)->required(),
])
)->required(),
];
}
}
عشان نضمن إن الذكاء الاصطناعي يرجّع بالضبط الي تطبيقنا يحتاجه، نستخدم طرق التحقق الداخلية بتاعة الـ SDK: الاسم (نص، إلزامي)، الأنواع (قائمة قيم مسموحة)، والحجم (رقم صحيح بمدى محدد).
مثال مولّد الاختبارات (سؤال وجواب): التعليمة الرئيسية لهذا الوكيل: "ولّد أسئلة وأجوبة بناءً على محتوى المنشورات؛ ولّد أسئلة من نوع صح أو خطأ." عشان نضمن إن المخرجات قابلة للمعالجة، نحدد مخطط بثلاث حقول إلزامية: السؤال (نص الاستفسار)، الإجابة (قيمة بوليانية صح/خطأ)، والتفسير (السبب وراء الإجابة، يساعد الطالب).
Route::get('/agents/quiz-simple', [AgentsTestController::class, 'quizPostsSimple']);
نقدر نمرر عدد الأسئلة اللي نبيها (افتراضيًا 5) وقائمة معرّفات (IDs). لمعالجة المعرّفات، نستخدم دالة PHP explode، اللي تحول نص مفصول بفواصل (مثل 1,2,3) إلى مصفوفة، فتخلينا نستخدم طريقة whereIn الخاصة بـEloquent.
public function quizPostsSimple(Request $request): JsonResponse
{
$ids = $request->input('ids');
$cantidad = $request->input('cantidad', 5);
$posts = Post::when($ids, fn($query) => $query->whereIn('id', array_filter(explode(',', $ids))), fn($query) => $query->where('posted', 'yes')->limit(1))
->get(['title', 'content']);
if ($posts->isEmpty()) {
return response()->json(['error' => 'No hay contenido'], 404);
}
$contenido = $posts->map(fn($post) => "Título: {$post->title}\nContenido: {$post->content}")->join("\n\n---\n\n");
$prompt = "Basándote en los siguientes posts, genera {$cantidad} preguntas de verdadero o falso:\n\n{$contenido}";
$resultado = (new QuizAgent)->prompt(
$prompt,
model: <YOURIDLOCALMODAL>
);
return response()->json([
'quiz' => $resultado->toArray(),
'posts_utilizados' => $posts->count(),
]);
}
بعد ما نحصل على المنشور، نستخرج العنوان والمتن عشان نغذّي الوكيل. المهم هنا نفرّق بين تعليمة الوكيل (دوره العام) والمهمة المحددة (الأمر الحالي)، اللي في هذي الحالة يخبره بالضبط كم سؤال يولّد بناءً على النص المعطى.
<?php
namespace App\Ai\Agents;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;
use Stringable;
class QuizAgent implements Agent, HasStructuredOutput
{
use Promptable;
public function instructions(): Stringable|string
{
return 'You are a quiz question generator. Based on the content of the posts, you generate true or false questions. The questions must be clear and the correct answer must be based on the content.';
}
public function schema(JsonSchema $schema): array
{
return [
'preguntas' => $schema->array()->items(
$schema->object([
'pregunta' => $schema->string()->required(),
'respuesta' => $schema->boolean()->required(),
'explicacion' => $schema->string()->required(),
])
)->required(),
];
}
}
مثال على المخرجات: السؤال "هل Laravel Volt بيئة تشغيل لـPHP؟" الإجابة: "خطأ". التفسير: "Laravel Volt هي واجهة برمجية وظيفية لـLivewire تسمح بكتابة المنطق والقوالب في نفس الملف."
طيب وش أهمية المخطط (Schema) فعليًا؟
هنا نوصل لما تسميه "كعب أخيل" للذكاء الاصطناعي في تطوير البرمجيات: غياب البنية. زي ما ذكرنا، بتحديد مخطط، نجبر الذكاء الاصطناعي إنه يفصّل استجابته بالشكل اللي نحتاجه بالضبط. الآن أنت عارف كيف تولّد هذي الوكلاء وليش البنية هي المفتاح لدمج الذكاء الاصطناعي في سير عمل المطور. ما راح نبي دايمًا نص بسيط على الشاشة؛ أغلب الوقت بنحتاج بيانات يقدر تطبيقنا يفهمها ويعالجها.
وكيل الاختبارات: من مثال نظري إلى فائدة حقيقية
أبي أتعمق أكثر في وكيل "الاختبار" اللي طبقناه سابقًا. بعيدًا عن الأمثلة النظرية، أبينا نحلله من منظور وظيفي وواقعي. الهدف إنك تفهم كيف تكيّف نظام مشابه لأي مهمة ثانية تحتاجها في مشاريعك.
هذا الوكيل عنده مهمة ثابتة: توليد أسئلة وأجوبة بصيغة محددة. في هذي المرة، ما أستخدم مشروع الدورة، بل أستخدم أحد مشاريعي الخاصة اللي فيها أهاجر تطبيقي الحالي لبيئة نظيفة. قررت أدمج مولّد الاختبارات هذا لأن كتبي ودوراتي مرتبطة بعلاقة واحد لواحد؛ فكل المحتوى المكتوب مادة مثالية لتقييم الطالب.
مشكلة الإنشاء اليدوي: إنشاء اختبارات يدويًا (مثلًا لـUdemy) عملية مزعجة فعلًا. تتطلب وقت كثير للتفكير في أي أسئلة تناسب كل قسم، كتابة الخيارات الصحيحة والخاطئة، وإعطائها تنوع. بسبب ضيق الوقت، غالبًا ما أدرجها، وهنا بالضبط يصير الذكاء الاصطناعي أداة لا تقدر بثمن لحل مشكلة حقيقية في سير عملي.
التطبيق التقني باستخدام Livewire وPHP: في هذا التطبيق، أستخدم Livewire. لاستدعاء الدالة اللي تولّد الاختبار، أستخدم wire:click من الواجهة. لو ما كنت متمكن من Livewire بعد، لا تقلق؛ لحد الآن، فكر في هذا كنموذج، عند إرساله، ينفذ دالة على السيرفر مكتوبة بـPHP.
الميزة الرئيسية هنا هي التكامل الكامل. بخلاف طرق ثانية ممكن تستخدم فيها أداة خارجية أو مساعد منعزل (زي GPTs مخصصة أو أدوات CLI)، هنا كل شي يصير داخل نفس التطبيق. ما نحتاج نربط قواعد بيانات خارجية أو نصدّر ملفات؛ كود PHP يقرأ فصل الكتاب مباشرة من قاعدة البيانات ويمرره للوكيل. المحتوى واسع بما إنها فصول كاملة من كتبي. عمل 30 أو 40 سؤال من هذا المحتوى يدويًا ممكن ياخذ ساعة لكل فصل. مع الوكيل، بس أمرر النص الكامل وهو يتكفل بالمجهود الثقيل.
المزايا مقابل نموذج المساعدين الخارجيين: قبل ما أوصل لهذا الحل، استخدمت نظام أكثر بدائية: أستخرج المعلومة من قاعدة البيانات، ألصقها في مساعد محلي (باستخدام Jan.ai أو LM Studio)، أطلب المخرجات بصيغة JSON، وأحول ذلك JSON إلى SQL عشان أدخله في قاعدة بياناتي. هذا التدفق كان فظيع وكان يفشل باستمرار بسبب حدود السياق أو استجابات ناقصة. التكامل المباشر في لارافيل يلغي هذي الغموضات. بوجود الوكيل مغلّف، أقدر أمرر "أوامر خارقة" تحتوي المحتوى الكامل للفصل بدون ما أطلع من بيئة التطوير بتاعتي.
الاختبار الواقعي والأداء المحلي: حاليًا أختبر هذا النظام بنموذج محلي. مهم أذكر إنه رغم إن عندي ماك قوي، محاولة تشغيل نماذج ثقيلة جدًا (زي واحد بـ31 مليار معلمة) ممكن تسبب أخطاء 500 بدون وصف واضح، بس لأن النظام يفرغ من الذاكرة أو يتجاوز حدود المهلة. لهذي التجارب، ضبطت Gemma 3 12B وأقيّم النتائج. لاحظ إن العملية ممكن تاخذ وقت (ولهذا استخدام الطوابير والوظائف في لارافيل مثالي)، لكن النتيجة متماسكة — السؤال المولّد كان متسق منطقيًا مع النص الأصلي، والتفسير المعطى له تماسك تربوي كامل.
طيب كيف تستخدم وكيل الذكاء الاصطناعي مع طوابير العمل؟
بندفع الوكيل خطوة أبعد باستخدام طوابير العمل. السبب بسيط: أنظمة الذكاء الاصطناعي غالبًا بطيئة في الرد، خصوصًا لما نعتمد على خدمات خارجية أو معالجات محلية ثقيلة.
جمال إن يكون عندك لارافيل كفريمورك قوي جدًا يخليك تسوي كل شي، مع الذكاء الاصطناعي، هو إننا في هذي الحالة ما بنستخدم لارافيل كناقل للتطوير، بل كأداة لاستدعاء وتنفيذ عملية. لارافيل يوفر لنا إطار عمل قوي للتعامل مع هذي المهام الخلفية، بيخلي الذكاء الاصطناعي يشتغل كأداة خارجية بدون ما يوقف الخيط الرئيسي لتطبيقنا.
التحدي التقني: معالجة بيانات ضخمة بالذكاء الاصطناعي. في بيئة تطوير حقيقية، المشكلة الأساسية مو مجرد الاتصال بالذكاء الاصطناعي، بل كيف تدير تدفق المعلومات بفعالية. حالة استخدامي الحالية مثال واضح: أحتاج أولّد كمية كبيرة من الأسئلة والأجوبة بناءً على المحتوى النظري لكتبي.
خطر توقف الواجهة: ما هو عملي إننا نعالج هذي الطلبات مباشرة على الخيط الرئيسي. لو حاولنا نستدعي الذكاء الاصطناعي بشكل متزامن، التطبيق حتمًا بيتوقف لثواني أو حتى دقايق، وهذا يخلق تجربة مستخدم سيئة وممكن يسبب أخطاء انتهاء مهلة في المتصفح أو السيرفر.
الكفاءة وقابلية التوسع: عشان المعالجة تكون فعّالة فعليًا، لازم نطبق معمارية تسمح بـ: فصل الطلب عن المعالجة (المستخدم يطلب التوليد والتطبيق يرد فورًا، بينما العمل الثقيل يصير بالخلفية)، إدارة طوابير العمل (استخدام طوابير لارافيل عشان تدير المهام تشتغل وحدة بعد وحدة بتنظيم)، وتقسيم المعلومة (Chunking) لتقسيم المعلومة لقطع يمكن إدارتها عشان ما نشبع ذاكرة السيرفر أو حدود سياق الذكاء الاصطناعي.
مشكلة الخيط الرئيسي المحظور: سابقًا، استدعينا الوكيل مباشرة من كلاس Livewire. بعمل هذا عن طريق wire:click، المتصفح والتطبيق كانوا "يتجمدون" وقت انتظار استجابة الذكاء الاصطناعي. في بيئة حقيقية، لو تحتاج تعالج كميات ضخمة من البيانات (زي توليد مئات الأسئلة بناءً على كتب)، ما تقدر تسمح للواجهة تتجمد. لحل هذا، نقلنا المنطق لوظيفة (Job) في لارافيل.
إدارة الحمل واستخدام الذكاء الاصطناعي المحلي: عشان نظامنا يكون مستقر، مو كافي إننا نرسل مهام للطابور؛ لازم نكون استراتيجيين باستهلاك الموارد. لو أشبعت جهازك، نظام التشغيل (سواء ويندوز أو ماك) ممكن ينهي العمليات بسبب نقص الذاكرة أو الحمل الزايد، وهذا بيقاطع توليد المحتوى.
استراتيجية "التنقيط" للمهام: التوصية الأساسية هي تجنب إرسال كمية ضخمة من الوظائف دفعة وحدة. أفضل بكثير تمرر مجموعة صغيرة من المهام، ولما عمّال لارافيل (Workers) يصيرون متاحين، ترسل الدفعة التالية. في حالتي، أعالج مقاطع من كتبي. بدل ما أحاول أخلي الذكاء الاصطناعي "يقرأ" الكتاب كامل دفعة وحدة، أمرر مقاطع محددة مباشرة في الرسالة.
تنسيق المدخلات: السياق داخل الأمر. حاليًا نستخدم الطريقة الأكثر تقليدية: تضمين كل المحتوى النظري مباشرة في مقدمة الأمر. الميزة: مباشر وسهل التطبيق في البداية. القيد: حجم الأمر محدود (نافذة السياق). لو المقطع كبير جدًا، الذكاء الاصطناعي ممكن يفقد الدقة أو ببساطة يفشل.
التطبيق بذكاء اصطناعي محلي: وحدة من أعظم مزايا هذا التدفق هي إمكانية استخدام نماذج محلية. مثالي للعمل ببيانات حساسة أو ببساطة توفير تكاليف الواجهة البرمجية. Ollama حاليًا الخيار الأكثر رسوخًا و"رسمية" لدمج النماذج المحلية في التطبيقات. رغم وجود أدوات ممتازة زي Jan.ai أو LM Studio، التكامل المباشر عبر المكتبات عادة أكثر تحسينًا لـOllama.
ليش تستخدم ذكاء اصطناعي محلي؟ استخدام نماذج محلية (زي Llama 3 أو Mistral) يسمح لنا نجري اختبارات غير محدودة بدون تكلفة إضافية. بس نحتاج نتأكد إن جهازنا عنده القوة اللازمة (خصوصًا في كرت الشاشة) عشان يرد بأوقات مقبولة، بالإضافة إلى التعامل مع كل البيانات بشكل خاص تمامًا.
بنية طابور العمل (Job): الوظيفة مسؤولة عن استلام مقاطع المحتوى (مقسّمة لقطع) عشان الذكاء الاصطناعي ما ينهار. هذي نقاط الإعداد الرئيسية: وقت التنفيذ (ضبطت مهلة عالية، مثلًا 900 ثانية، ودالة set_time_limit(0) عشان نمنع PHP أو نظام الطابور من إنهاء العملية قبل الأوان)، والمحاولات (مهم جدًا التعامل مع الاستثناءات؛ لو فشل الذكاء الاصطناعي بسبب حمل العمل، النظام بيعيد المحاولة تلقائيًا):
public $timeout = 900;
public $maxExceptions = 3;
public $memory = 512;
public $tries = 3;
تقسيم المحتوى: ما نمرر الكتاب كامل. نقسّم النص لمقاطع تقريبًا 1500 حرف. هذا يضمن إن النماذج المحلية (زي Gemma 3 12B عبر Jan.ai أو Ollama) تقدر تعالج المعلومة بفعالية:
foreach ($sections as $s) {
$chunks = mb_str_split($s->content, 15000, 'UTF-8');
foreach ($chunks as $content) {
GenerateChunkQuizJob::dispatch(
تعريف مخطط JSON ونموذج البيانات: عشان استجابة الذكاء الاصطناعي تكون مفيدة، لازم تكون منظمة. حدّثت المخطط عشان يرجّع صيغة نوع "Trivia" (أسلوب Duolingo): السؤال (نص التحدي)، الخيارات (مصفوفة من بالضبط 4 خيارات)، الإجابة الصحيحة (رقم الخيار الصحيح مع تفسير ليش صح أو غلط).
$resultado = (new QuizAgent)
->prompt(
"
Act as a programming teacher.
Read this fragment of my book: '{$content}'
Generate a quiz of {$this->numQuestions} questions based solely on that text.
Respond ONLY with valid JSON, without additional text or markdown.
Required structure:
{
\"questions\": [
{
\"text\": \"question\",
\"options\": [\"option1\", \"option2\", \"option3\", \"option4\"],
\"options_message\": [\"message1\", \"message2\", \"message3\", \"message4\"],
\"correct_answer_index\": 0,
\"language\": \"spanish\"
}
]
}
Give it to me in {$this->language}
",
model: $this->modelAi
);
public function schema(JsonSchema $schema): array
{
return [
'questions' => $schema->array()->items(
$schema->object([
'text' => $schema->string()->required(),
'options' => $schema->array()->items(
$schema->string()
)->min(4)->max(4)->required(),
'options_message' => $schema->array()->items(
$schema->string()
)->min(4)->max(4)->required(),
'correct_answer_index' => $schema->integer()->required(),
'language' => $schema->string()->enum(['english', 'spanish'])->required(),
])
)->required(),
];
}
الهجرة تكون على هذا الشكل:
Schema::create('learning_questions', function (Blueprint $table) {
$table->id();
$table->text('text');
$table->json('options');
$table->json('options_message');
$table->unsignedInteger('correct_answer_index');
$table->enum('language', ['english', 'spanish'])->default('spanish');
$table->foreignId('book_section_id')->nullable()->constrained('book_sections')->onDelete('cascade');
$table->foreignId('tutorial_section_id')->nullable()->constrained('tutorial_sections')->onDelete('cascade');
$table->timestamps();
});
الشي المثير للاهتمام في هذا النظام هو التآزر بين نموذج Eloquent والاستجابات المولّدة من الذكاء الاصطناعي. باستخدام حقول من نوع JSON في هجرات قاعدة البيانات، نلغي الاحتكاك المرتبط بربط كل حقل بشكل فردي، ونخلي البيانات تتدفق مباشرة من استجابة النموذج اللغوي إلى تطبيقنا.
استخدام حقول JSON في الهجرات: في نموذج Learning، حددنا بنية الجدول بأعمدة JSON، وهذا يوفر مرونة هائلة: الحفظ المباشر (نحفظ الكائن بالضبط زي ما رجّعه الذكاء الاصطناعي بعد التحقق المسبق)، وسهولة المعالجة (لارافيل يحوّل تلقائيًا هذي الحقول إلى مصفوفات أو مجموعات PHP، يسهّل استخدامها في الواجهة الأمامية أو منطق العمل).
الربط بين لارافيل والذكاء الاصطناعي: المفتاح هنا هو العلاقة المباشرة. ما نستلم مجرد نص بسيط؛ ندمج قدرة تفكير الذكاء الاصطناعي مع قوة لارافيل. التدفق نظيف: نرسل فصل من الكتاب كسياق، الذكاء الاصطناعي يولّد الأسئلة والخيارات تحت مخطط محدد مسبقًا، ولارافيل يستلم، يتحقق، ويدخل النتيجة في قاعدة البيانات.
أتمتة تدفق البيانات: الهدف النهائي هو الأتمتة الكاملة. بخلاف عملية يدوية تحتاج تنسخ وتلصق كل سؤال، هنا الذكاء الاصطناعي يعمل كمعالج بيانات ذكي: التحقق من الجودة (رغم إن الذكاء الاصطناعي يسوي المجهود الثقيل، النظام فيه طبقة تحقق تضمن إن الاستجابة JSON صالح ومطابق للمتطلبات قبل الإدخال)، والاتساق (بربط الأمر بفصل كتاب محدد، نضمن إن الأجوبة مو "مختلقة" عشوائيًا، بل مبنية بشكل صارم على المحتوى النظري المعطى).
هذا النهج يحول لارافيل لأكثر من مجرد فريمورك تطوير ويب؛ يحوله لمحرك إدارة معرفة. بتفويض إنشاء المحتوى المنظم للذكاء الاصطناعي وإدارة البيانات لـEloquent، نقدر نولّد منصات تعليمية ضخمة بأقل جهد يدوي.
التحقق من الاستجابة و"التنظيف": ما نقدر نثق عمياني إن الذكاء الاصطناعي بيرجّع JSON مثالي. لذلك، نطبّق منطق تحقق: استخراج JSON (طريقة تنظف استجابة الذكاء الاصطناعي وتستخرج بس كتلة كود JSON، وتشيل النص الإضافي)، فحص السلامة (نتحقق إن الـJSON قابل للفك ويحتوي كل الحقول المطلوبة قبل ما ندخله لقاعدة البيانات)، وسجلات الأخطاء (لو فشل الفك، الخطأ يُسجّل في storage/logs/laravel.log عشان نصحح المشكلة).
if (! $jsonText) {
\Log::error('GenerateQuizJob: No JSON found in response', [
'book_section_id' => $this->bookSection->id,
'response' => mb_substr($responseText, 0, 1000),
]);
return;
}
$data = json_decode($jsonText, true);
if (json_last_error() !== JSON_ERROR_NONE) {
\Log::error('GenerateQuizJob: Invalid JSON after extract', [
'book_section_id' => $this->bookSection->id,
'json_error' => json_last_error_msg(),
]);
return;
}
if (! isset($data['preguntas'])) {
\Log::warning('GenerateQuizJob: No preguntas key in response', [
'book_section_id' => $this->bookSection->id,
'keys' => array_keys($data ?? []),
]);
return;
}
foreach ($data['preguntas'] as $pregunta) {
LearningQuestion::create([
'text' => $pregunta['text'],
'options' => $pregunta['options'],
'options_message' => $pregunta['options_message'] ?? [],
'correct_answer_index' => $pregunta['correct_answer_index'],
'language' => $pregunta['language'],
'book_section_id' => $this->bookSection->id,
]);
}
التنفيذ بالوقت الفعلي: عشان يشتغل كل شي، ننفذ الأمر:
$ php artisan queue:work
الأكواد الكاملة موجودة في ملفين رئيسيين: app\Jobs\GenerateChunkQuizJob.php (الوظيفة اللي تستلم المقطع، تولّد الأسئلة، تستخرج وتتحقق من JSON، وتخزن النتائج)، وapp\Ai\Agents\QuizAgent.php (الوكيل المسؤول عن التوليد نفسه). ودالة تشغيل العملية كاملة تكون على هذا الشكل:
public function generateQuiz()
{
$sections = BookSection::where('book_id', $this->model->id)
->where('posted', 'yes')
->where('orden', '>', 0)
->get();
foreach ($sections as $s) {
$chunks = mb_str_split($s->content, 15000, 'UTF-8');
foreach ($chunks as $content) {
GenerateChunkQuizJob::dispatch(
$s,
$content,
3,
'gemma-3-12b-it-IQ4_XS',
'español'
);
}
}
Session::flash('status', 'Tasks stored in the queue to generate the book questions');
}
اعتبارات ثانية: ممكن تحتاج تعدّل معايير سيرفر ثانية في ملف php.ini بتاعك، زي max_execution_time (غالبًا مضبوط على 60، غيّرها لـ0 أو 900) وmemory_limit (لو نموذج الذكاء الاصطناعي أو معالجة النص تستهلك رام كثير، العملية بتموت؛ اضبطها على الأقل 512M). كل هذي التغييرات عشان نتجنب أخطاء زي توقف العامل (Worker) تلقائيًا بعد دقيقة أو وقت ثابت — تذكر إن أنظمة الذكاء الاصطناعي تاخذ وقت طويل للرد، ولو أضفنا لهذا الأمر والمهمة الثقيلة، لازم نحافظ على توازن بين موارد جهازنا والمهمة اللي نسويها.
طيب وش إمكانيات الأدوات (Tools) في Laravel AI SDK؟
الأدوات (Tools) آليات قوية جدًا، لما تجمعها مع الوكلاء وأنظمة استرجاع المعرفة (زي RAG)، تخلينا نبني مساعدين مخصصين بمستوى عالٍ. رغم إن أدوات زي OpenAI أو Claude هي المرجع، تطبيق حلولنا الخاصة يعطينا تحكم كامل بالمهام الروتينية وخصوصية البيانات.
بهذا النظام البيئي نقدر ندير: محادثات مستمرة (حفظ واسترجاع سياقات سابقة)، تعدد الوسائط (توليد صور، صوت، ونصوص منسوخة)، والاتصال (السماح للوكيل يتفاعل مع العالم الخارجي أو مع بنيتنا التحتية).
وش هي الأدوات وليش نحتاجها؟ زي ما يوحي الاسم، الأدوات هي مرافق توفر وظائف إضافية لوكلائنا. نقدر نشوفها كنقطة وسط بين وكيل أساسي ونظام MCP (بروتوكول سياق النموذج).
مشكلة النماذج اللغوية المعزولة: أغلب النماذج اللغوية الكبيرة، خصوصًا المحلية، تفشل لما تُسأل عن معلومات لحظية (الطقس، التاريخ الحالي، الأخبار). بما إنها مو متصلة بالشبكة افتراضيًا، معرفتها محدودة بتدريبها المسبق.
مثال على استخدام أداة: لو فعّلنا أداة زي WebSearch، الوكيل يقدر يستشير الإنترنت عشان يجاوب أي يوم اليوم أو وش حالة الطقس الحالية. بدون هذي "الأداة"، الوكيل ببساطة بيعترف بعدم قدرته أو بيهلوس الإجابة:
class Assistant implements Agent, HasTools
{
use Promptable;
public function instructions(): Stringable|string
{
return 'You are a helpful assistant. For URL requests, summarize the content provided by the user.';
}
public function tools(): iterable
{
return [
new WebFetch,
];
}
}
في هذا المثال، WebFetch أداة موجودة أصلًا في لارافيل، بس نقدر نبني أدواتنا الخاصة أيضًا. WebFetch تسمح للوكيل يبحث في جوجل أو بينج عشان يحصل سياق محدّث.
التطبيق التقني في لارافيل: لارافيل يسهّل إنشاء أدوات مخصصة عبر أوامر artisan:
$ php artisan make:tool ToolName
بمجرد إنشائها، البنية الرئيسية تُحدد في دالة handle، وهنا يصير السحر. هنا نحدد المنطق اللي الوكيل بينفذه لما يقرر إنه محتاج هذي الأداة بالذات. أمثلة عملية على الأدوات: التقويم والمواعيد (مساعد شخصي يقدر يستخدم أداة عشان يقرأ أو يكتب في ملف أو قاعدة بيانات ويجدول اجتماعات)، والتواصل الخارجي (تقدر تنشئ أداة تستخدم واجهة برمجية لـTelegram عشان الوكيل يرسل رسائل تلقائيًا عند إنهاء مهمة).
حالة عملية: وكيل المواعيد (AppointmentAgent)
مثال نفصل فيه المنطق لثلاث قطع: الأمر، الوكيل، والأداة. الأداة (CreateAppointment) تحدد مخطط منظم (تاريخ، رسالة، تاريخ إنشاء) وهي مسؤولة عن التحقق لو الملف موجود في التخزين وتسجيل المعلومة بشكل دائم. الوكيل يستلم تعليمة المستخدم ويقرر يستدعي أداة المواعيد عشان يحفظ المعلومة.
<?php
namespace App\Ai\Tools;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;
class ScheduleAppointment implements Tool
{
public function description(): Stringable|string
{
return 'Schedule an appointment by storing the date and message in a JSON file within the storage/app directory.';
}
public function handle(Request $request): Stringable|string
{
$data = [
'date' => $request['date'],
'message' => $request['message'],
'created_at' => now()->toIso8601String(),
];
$filename = storage_path('app/appointments/'.now()->format('Y-m-d_His').'.json');
if (! is_dir(dirname($filename))) {
mkdir(dirname($filename), 0755, true);
}
file_put_contents($filename, json_encode($data, JSON_PRETTY_PRINT));
$relativePath = 'appointments/'.now()->format('Y-m-d_His').'.json';
return "Appointment scheduled successfully. Date: {$data['date']}, Message: {$data['message']}. Stored in: {$relativePath}";
}
public function schema(JsonSchema $schema): array
{
return [
'date' => $schema->string()->required()->description('The appointment date and time in any format.'),
'message' => $schema->string()->required()->description('A brief description or note about the appointment.'),
];
}
}
<?php
namespace App\Ai\Agents;
use App\Ai\Tools\ScheduleAppointment;
use Laravel\Ai\Attributes\Model;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;
use Stringable;
class Appointment implements Agent, HasTools
{
use Promptable;
public function instructions(): Stringable|string
{
return 'You are an appointment scheduling assistant. When the user provides a date and message, use the ScheduleAppointment tool to save it. Call the tool immediately with the provided date and message.';
}
public function tools(): iterable
{
return [
new ScheduleAppointment,
];
}
}
<?php
namespace App\Console\Commands\Ai;
use App\Ai\Agents\Appointment;
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;
#[Signature('ai:appointment {action?}')]
#[Description('Schedule appointments or list them')]
class AppointmentCommand extends Command
{
public function handle(): int
{
$action = $this->argument('action') ?? $this->choice(
'Action',
['schedule', 'list'],
'schedule'
);
if ($action === 'list') {
$this->listAppointments();
return Command::SUCCESS;
}
$date = $this->ask('Appointment date (e.g., 2026-05-15 14:00)');
$message = $this->ask('Message');
$this->info('Scheduling appointment...');
$response = (new Appointment)->prompt("Schedule an appointment. Date: {$date}, Message: {$message}", model: 'gemma-3-12b-it-IQ4_XS');
$this->info('Response:');
$this->line($response->text);
return Command::SUCCESS;
}
private function listAppointments(): void
{
$files = glob(storage_path('app/appointments/*.json'));
if (empty($files)) {
$this->warn('No appointments found.');
return;
}
$this->table(['Date', 'Message', 'Created'], collect($files)->map(function ($file) {
$data = json_decode(file_get_contents($file), true);
return [$data['date'] ?? '-', $data['message'] ?? '-', $data['created_at'] ?? '-'];
})->all());
}
}
لاستخدام الوكيل أعلاه:
$ php artisan ai:appointment
>> action [schedule]:
>> [0] schedule
>> [1] list
خيار schedule يخليك تسجل موعد محليًا، وخيار list يخليك تشوف المواعيد الحالية.
القيود في البيئات المحلية: مهم نذكر إنه لما تشتغل بنماذج محلية (زي المدارة عبر Jan.ai أو Ollama)، دعم استخدام الأدوات ممكن يكون محدود. في اختباراتي، رغم إن الكود صحيح، بعض النماذج المحلية لسه تفشل "تقرر" متى تستدعي الأداة تلقائيًا، بخلاف نماذج سحابية زي GPT-4 أو Gemini.
طيب وش هو الـEmbeddings وقواعد بيانات المتجهات؟
لحد الآن اشتغلنا مع وكلاء يعالجون قطع نص عشان يولّدون استجابات. رغم إنها فعّالة، هذي الطريقة عندها قيود: لو حجم البيانات كبير جدًا، بنتجاوز "نافذة السياق" بتاعت الذكاء الاصطناعي أو بنحتاج قوة معالجة يصعب توفيرها في بيئة محلية.
الموضوع الجاي من أكثر المواضيع إثارة بتوثيق Laravel AI: التضمينات (Embeddings). خلني أوريك وش يصير لما نستخدم هذي التقنية. التضمينات هي الحل الاحترافي. التضمين يحول مقطع نص إلى متجه (مصفوفة رقمية معقدة تمثل المعنى الدلالي للنص). هذا يخلينا نسوي بحث ذكي وندير كميات ضخمة من المعلومات عبر قواعد بيانات متجهات.
مشكلة السياق المحدود: تذكر لما نشتغل مع الوكلاء (زي توليد أسئلة لكتاب)، "السياق" هو قطعة المعلومة اللي نرسلها للذكاء الاصطناعي. المثالي إننا نرسل الكتاب كامل أو فصل كامل، لكن نواجه حاجزين: نافذة السياق (أنظمة الذكاء الاصطناعي عندها حد لكمية البيانات اللي تعالجها دفعة وحدة)، وقوة الحوسبة (كل ما زادت البيانات المرسلة، كل ما زاد استهلاك الموارد؛ لما نشغّل نماذج محلية، هذا ممكن يعطّل جهازنا).
وش هو الـEmbedding وقاعدة بيانات المتجهات؟ ببساطة، التضمين هو تحويل النص لمجموعة متجهات (مصفوفة أرقام). لما نطبع تضمين، نشوف قائمة طويلة من أرقام عشرية تمثل "المعنى الدلالي" لذلك النص بالنسبة للذكاء الاصطناعي. لمعالجة هذي المعلومة، قاعدة بيانات تقليدية ما تكفي. نحتاج قاعدة بيانات متجهات. بينما في SQL نخزن نصوص، أرقام صحيحة، أو قيم بوليانية، هنا نتعامل مع نوع بيانات خاص يسمى متجه. حاليًا، أقوى قاعدة بيانات بدعم لهذا هي PostgreSQL عبر إضافة تسمى pgvector.
الأدوات اللازمة: عشان نطبق هذا، نحتاج ثلاث مكونات رئيسية:
أولًا، PostgreSQL مع إضافة pgvector. بخلاف MySQL أو MariaDB، PostgreSQL عنده pgvector، الخيار الوحيد القوي اللي يسمح بمعالجة متجهات أصلية. وش هي؟ نوع بيانات خاص، "عمود متجه" يخزن هذي المصفوفات الرقمية. للتركيب، تحتاج تثبت PostgreSQL وتفعّل الإضافة بتشغيل هذا الأمر داخل قاعدة بياناتك (تقدر تستخدم Dbngin وتنشئ قاعدة بيانات PSQL مباشرة منه):
$ CREATE EXTENSION IF NOT EXISTS vector;
ثانيًا، حزمة PHP للارافيل. ركّب الدرايفر اللازم لدمج PostgreSQL مع دوال الذكاء الاصطناعي:
$ composer require pgvector/pgvector-php
وتضبط قاعدة بياناتك:
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=laravel
DB_USERNAME=postgres
DB_PASSWORD=postgres
ثالثًا، Ollama ("الدماغ" المحلي). نحتاج محرك محلي يشغّل النموذج اللغوي الكبير المسؤول عن تحويل النص إلى متجه (Embedding). اخترت Ollama لاستقراره، لكن تقدر تراجع الجدول اللي ذكرناه سابقًا وتستخدم Gemini، OpenAI، أو أي نموذج يدعم Embedding. النموذج الموصى فيه هو نموذج خفيف وفعّال مخصص للتضمينات:
$ ollama pull nomic-embed-text
هذا النموذج صغير، سريع، وعنده نافذة سياق 8 آلاف توكن، مثالي لمعالجة كميات كبيرة من البيانات. تقدر تتحقق إنه نشط عبر ollama list، ومن المتصفح على http://localhost:11434/.
رابعًا، الإعدادات في لارافيل. في ملف إعدادات الذكاء الاصطناعي (config/ai.php أو مشابه)، لازم تحدد أي نموذج بتستخدمه لعملية التضمين. حتى لو عندك نموذج مضبوط أصلًا للمحادثة (زي llama3 أو gemma)، للتضمينات تقدر (ولازم) تحدد نموذج Ollama:
'default' => 'openai',
'default_for_embeddings' => 'ollama',
طيب وش هو عمود المتجه فعليًا؟ في التطبيق التقليدي، عندنا أعمدة عادية: معرّف، نص، تواريخ. لكن في قاعدة بيانات متجهات، يظهر نوع بيانات خاص:
Schema::create('documents', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->vector('embedding', dimensions: 768)->index(); // Ollama
$table->timestamps();
});
تقدر تتخيل المتجه كسحابة نقاط في فضاء متعدد الأبعاد. بينما إحنا متعودين على بعدين (X، Y) أو ثلاثة (X، Y، Z)، النماذج اللغوية الكبيرة تتعامل مع مئات أو آلاف الأبعاد. OpenAI يستخدم حوالي 1536 بُعد. Nomic Embed Text (محلي) يستخدم حوالي 768 بُعد. لو ما أدخلت الأبعاد الصحيحة، بتحصل خطأ زي: "expected 1536 dimensions, not 768".
في النهاية، هذا العمود، لما نسوي عملية التضمين، بيكون شكله شبيه بهذا:
use Laravel\Ai\Embeddings;
$response = Embeddings::for([
'Napa Valley has great wine.',
'Laravel is a PHP framework.',
])->generate();
$response->embeddings; // [[0.123, 0.456, ...], [0.789, 0.012, ...]]
هذي "المصفوفة" من الأرقام هي الصيغة اللي الذكاء الاصطناعي يفهمها مباشرة. بخلاف قاعدة بيانات عادية تقدر تدخل وتعدل نص فيها يدويًا، المتجه هو تمثيل مجرد لمعنى ذلك النص.
تحذير: إدارة السياق وحدود البيانات. لما نشتغل مع نموذج لغوي كبير، لازم نتذكر إن "نافذة السياق" مو لا نهائية. لو حاولت تمرر معلومة كثيرة دفعة وحدة، النظام بيفشل. مثلًا، زي في الـSeeder بتاعنا، عندنا بس 5 سجلات بنص قليل، النموذج يدعم هذا بسهولة تامة. لكن سيناريو الخطر: لو عندك 1000 سجل، كل واحد فيه 10000 حرف، هنا النموذج اللغوي بيفشل لأنك بتتجاوز حد التوكنز المدعوم، سواء تشتغل بنموذج محلي أو سحابي. كيف تصلح هذا؟ لو تتعامل مع كمية كبيرة من البيانات، الحل المنطقي مو إرسال الكتلة كاملة دفعة وحدة. النهج المثالي هو وضع المنطق داخل حلقة foreach والتكرار عبر السجلات وحدة وحدة (أو مجموعات صغيرة)، حسب ما النموذج يدعم. هذا تفصيل تقني أساسي لازم تاخذه بعين الاعتبار لما توسّع تطبيقك عشان ما يفشل وقت معالجة كميات كبيرة من المعلومات.
القرب الدلالي: الي يهمنا فعليًا لتمارينا هو البحث عن القرب بين النصوص. نموذج ذكاء اصطناعي، بعد ما اتدرب على ملايين البيانات، "يعرف" أصلًا أي كلمات أو مفاهيم مترابطة. مثلًا، مصطلحات "كلب"، "نباح"، و"بيطري" بتُمثّل بنقاط قريبة جدًا من بعض في سحابة المتجهات، بينما "كلب" و"كوكب" بتكون بعيدة جدًا عن بعض. هذا القرب يُقاس رياضيًا بخوارزميات زي التشابه الجيبي (Cosine Similarity) أو المسافة الإقليدية (Euclidean Distance). التشابه الجيبي يُقاس من 0 إلى 1: لو 1، النصوص متطابقة؛ لو 0، ما فيها علاقة إطلاقًا. المسافة الإقليدية تُقاس من 0 فأعلى: لو المسافة 0، النقطتين متطابقتين.
كيف تتم معالجة البيانات: من نص إلى متجه؟ عشان نحول النص إلى مصفوفة رقمية نقدر نخزنها في PostgreSQL، الذكاء الاصطناعي يتبع هذي الخطوات: التقطيع (Tokenization، يقسّم النص لقطع صغيرة تسمى توكنز)، المعالجة العصبية (التوكنز تمر عبر طبقات شبكة عصبية)، والمخرجات (النموذج يرجّع قائمة أرقام عشرية، وهي المتجه). هي نوع من "الثنائيات المعقدة"، رغم إننا ما نقدر نقرأها، بالنسبة للذكاء الاصطناعي هي طريقته لفهرسة المعرفة واسترجاعها بسرعة فائقة عبر استعلام SQL.
لتوليد هذي المصفوفة، لازم نستخدم نموذج لغوي كبير يسوي هذا الشغل؛ يعني PostgreSQL هو التخزين، والنموذج اللغوي هو الدماغ اللي يخلينا نولّد هذي البنية. خلنا نولّد بيانات اختبار:
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class DocumentSeeder extends Seeder
{
public function run(): void
{
$documents = [
['title' => 'Napa Valley Wineries', 'content' => 'Napa Valley is famous for its world-class wineries and premium wine production. The region offers tasting rooms, vineyard tours, and stunning views.'],
['title' => 'Laravel Tutorial', 'content' => 'Laravel is a PHP framework known for its elegant syntax and developer-friendly features. It provides routing, authentication, and caching out of the box.'],
['title' => 'React.js Guide', 'content' => 'React is a JavaScript library for building user interfaces. It uses a component-based architecture and virtual DOM for efficient rendering.'],
['title' => 'Wine Tasting Tips', 'content' => 'When tasting wine, look at the color, smell the aroma, and savor the flavor. Start with white wines and move to reds for the best experience.'],
['title' => 'PHP Best Practices', 'content' => 'PHP 8 introduced many new features like named arguments, attributes, and match expressions. Follow PSR standards for clean code.'],
];
foreach ($documents as $doc) {
DB::table('documents')->insert([
'title' => $doc['title'],
'content' => $doc['content'],
'embedding' => null,
'created_at' => now(),
'updated_at' => now(),
]);
}
}
}
وننفذ:
$ php artisan migrate:fresh
$ php artisan db:seed
بالبيانات السابقة، بتلاحظ إن عمود embedding (المتجه) غير معبّى بعد، لأنه مو محتوى مباشر. لتعبيته، لازم نستخدم الذكاء الاصطناعي اللي ضبطناه سابقًا:
public function generateEmbeddings(): JsonResponse
{
$documents = Document::all();
$contents = $documents->pluck('content')->toArray();
$response = Embeddings::for($contents)->generate();
foreach ($documents as $index => $document) {
$document->embedding = $response->embeddings[$index];
$document->save();
}
return response()->json([
'message' => 'Embeddings generated via AI SDK',
'method' => 'php-ai-sdk',
'count' => count($response->embeddings),
]);
}
طيب لما الذكاء الاصطناعي يخطئ؟ دراسة حالة: مولّد الأسئلة
لما نستخدم Laravel AI SDK لتوليد بيانات ضخمة في بيئات إنتاج، لازم ننطلق من مبدأ أساسي: النماذج اللغوية الكبيرة، سواء سحابية أو محلية، تخطئ، تعاني من الهلوسة، وممكن تغيّر بنية البيانات المطلوبة.
دراسة حالة واقعية: أتمتة وحدة التقييم الذاتي لمنصات تعليمية. هدف النظام كان معالجة المحتوى التقني لعدة كتب ودورات عشان يبني اختبارات أسئلة وأجوبة تلقائيًا في نهاية كل فصل. عبر وكيل رئيسي يشتغل محليًا باستخدام نموذج Ollama (Llama 3.1 12B) على عتاد مخصص، تم توليد حجم أولي أكثر من 6500 سؤال في قاعدة بيانات التطوير. لكن بعد عملية فلترة وتصحيح برمجية صارمة، بس 5787 سؤال اعتُمد للإنتاج، يعني تم استبعاد أكثر من 800 سجل مشوّه أو غير صالح دلاليًا.
الأعطال الشائعة في توليد البيانات المنظمة: رغم إن الوكيل الرئيسي كان عنده تعليمات دقيقة ومخطط JSON صارم، النموذج المحلي أظهر أعطال متكررة تحت معدل معالجة ضخم:
- بنيات ناقصة: النموذج أحيانًا يحذف مفاتيح إلزامية من كائن JSON، ويترك نصوص فارغة في رسائل التغذية الراجعة للخيارات الخاطئة.
- أسئلة سياقية مجردة: لما يستلم مقاطع نص من الكتاب، الذكاء الاصطناعي كان يولّد أسئلة نخبوية بدون قيمة تعليمية عامة (مثلًا يسأل عن Git tag محدد استُخدم في فصل فرعي معين، بدل ما يقيّم المفهوم البرمجي الأساسي).
- تضارب الفهارس: حالات حرجة اكتُشفت وين الاستجابة JSON صراحة تحدد خيار كـ"صحيح"، لكن الحقل المسؤول عن تخزين الفهرس الرقمي (correct_option_index) يشير لخيار خاطئ بسبب تأخر منطقي في النموذج.
- حقن محتوى خارجي: النموذج كان يدمج مصطلحات تقنية أو مفاهيم غير مرتبطة بمادة القراءة المعطاة، مما يضر بأمانة المحتوى التعليمي الأصلي للمؤلف.
استراتيجية التخفيف: نمط الوكيل المدقق. مراجعة قاعدة بيانات فيها آلاف عناصر الاختبار موزعة على خيارات متعددة يدويًا غير عملي إطلاقًا ضمن جداول الإنتاج. الحل المعماري كان تصميم سير عمل تحقق من طبقتين: سكريبت تحكم نحوي، ووكيل مشرف ثاني.
الطبقة الأولى: التحقق البرمجي. قبل ما ندخل ذكاء اصطناعي ثاني، سكريبت لارافيل يحلل سلامة السجل باستخدام قواعد برمجية تقليدية. هذي العملية الفرعية تتحقق إن مصفوفة الخيارات تحتوي بالضبط العدد المطلوب من العناصر، ما فيه نصوص مبتورة، وتصحح تلقائيًا التناقضات البسيطة في فهارس الإجابات الصحيحة، وتولّد تقرير أولي.
الطبقة الثانية: إعداد الوكيل المدقق. لتقييم جودة الكتابة والملاءمة التربوية للسؤال، وكيل ثاني اتنشأ بدور تدقيق متخصص. نقطة أساسية في هذي الاستراتيجية: ما نعطي المدقق مقطع النص الأصلي من الكتاب. بدون هذا السياق فائق التحديد، لو الوكيل المدقق شاف السؤال مجرد جدًا أو غير مفهوم من منظور معرفة عامة، يصنّفه تلقائيًا كباطل.
<?php
namespace App\Ai\Agents;
use App\Models\LearningQuestion;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Log;
use Laravel\Ai\Attributes\Timeout;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;
use Stringable;
#[Timeout(300)]
class QuizReviewAgent implements Agent, HasStructuredOutput
{
use Promptable;
public function instructions(): Stringable|string
{
return 'Eres un experto educador encargado de auditar y verificar preguntas de opción múltiple (quizzes) de desarrollo de software y programación. Tu tarea es analizar rigurosamente si la pregunta es clara, si las opciones son válidas y coherentes, y si el índice de respuesta correcta indicado es realmente correcto.';
}
public function schema(JsonSchema $schema): array
{
return [
'is_correct' => $schema->boolean()->required(),
'feedback' => $schema->string()->required(),
];
}
public function review(LearningQuestion $question, string $modelAi = 'gemma3:12b'): array
{
$optionsFormatted = '';
foreach ($question->options as $index => $option) {
$optionsFormatted .= "- [Índice {$index}]: {$option}\n";
}
$prompt = "Revisa la siguiente pregunta:\n\n"
."Pregunta: {$question->text}\n"
."Opciones disponibles:\n{$optionsFormatted}"
."Índice de la respuesta correcta marcado en la base de datos: {$question->correct_answer_index}\n\n"
.'Determina si el índice de respuesta correcta marcado es correcto, si la pregunta no tiene ambigüedades y si las opciones están bien redactadas.';
$response = $this->prompt($prompt, model: $modelAi);
$isCorrect = (bool) ($response['is_correct'] ?? true);
$feedback = (string) ($response['feedback'] ?? '');
if (! $isCorrect) {
Log::error('http://dashboard.desarrollolibre.net/learning/question/'.$question->id);
}
return [
'is_correct' => $isCorrect ?? true,
'feedback' => $feedback ?? '',
];
}
}
تحسين سير العمل وواجهة المراجعة: في البداية، نظام التدقيق كان يفرّغ سجل مفصّل في ملفات نصية عادية. هذا النهج صعّب التصفح بسبب كثافة الشروحات المفاهيمية اللي الذكاء الاصطناعي يولّدها. الحل الأمثل كان تبسيط التقرير بحصر مخرجات السكريبت في قائمة معرّفات فريدة وروابط داخلية من بيئة التطوير المحلية. عبر واجهة إدارية في لارافيل، المطور يقدر يسوي فلترة سريعة بالتفاعل مع السجلات المُبلّغ عنها، مثل: dashboard.test/learning/question/5542 وdashboard.test/learning/question/5538.
مقاييس الأداء في البيئات المحلية: البنية التحتية المحلية المبنية على معمارية Apple Silicon (M4 Pro) وفرت نافذة تنفيذ بتكلفة صفر من ناحية استهلاك توكنز واجهات برمجية خارجية، وأعطت هذي المقاييس الزمنية: التوليد الرئيسي أخذ حوالي 15 يوم من التنفيذ المتقطع (بمعدل 8 ساعات حوسبة باليوم). كل كتلة سؤال، خيارات، وتغذية راجعة منظمة تطلبت بين 20 و30 ثانية استنتاج بالنموذج. التدقيق والإشراف اشتغل بسرعة أعلى بكثير، وحل التحقق الدلالي لكل عنصر بمدى 1 إلى 3 ثواني للسجل. مرحلة التدقيق الآلي الكاملة انتهت خلال 3 إلى 4 أيام.
التطور المعماري: تطبيق وكلاء فرعيين (Subagents). الـ SDK بيدعم أصليًا تنسيق وكلاء فرعيين في توثيقه. بدل تصميم عمليتين مستقلتين (توليد الكون الكامل من البيانات ثم تدقيقه في كتلة أيام ثانية)، المعمارية الموصى فيها للتخفيف من الأخطاء بوقت فعلي هي تفويض المراجعة بشكل متزامن:
class CustomerSupportAgent implements Agent, HasTools
{
use Promptable;
public function tools(): iterable
{
return [
new RefundsAgent,
];
}
}
بتبني هذا النمط الهرمي، الوكيل الرئيسي يعالج المحتوى، وقبل ما يحفظ المعلومة في قاعدة البيانات، يمرر التحكم لوكيل فرعي مدقق. لو الوكيل الفرعي اكتشف خلل بنيوي أو تضارب فهرس، النظام يقدر يرفض السجل فورًا في الذاكرة، يطلب إعادة توليد متفائلة من النموذج، أو يعلّم السجل لمراجعة يدوية إلزامية. هذا التمركز المنطقي يحسّن التخزين ويضمن سلامة البنية من أول دورة استنتاج.
حالة أخرى: مشاكل حقيقية في توليد الأسئلة
أبي أحكي لك كيف أستخدم كل الي ذكرناه، من الوكلاء وRAG إلى الطوابير والوظائف، لتوليد أسئلة وأجوبة آلية لدوراتي وكتبي. أستخدم تقنية RAG (التوليد المعزز بالاسترجاع)، وين مصدر البيانات حصرًا هو محتوى كتبي. هذا يخلي الذكاء الاصطناعي يتجنب اختلاق إجابات بناءً على تدريبه العام، ويلتزم بدقة بمنهجيتي.
الفرق بين تطوير الذكاء الاصطناعي ودمجه: مهم نفهم الفرق بين أدوات تطوير زي Laravel Prompter أو GitHub Copilot وبين اللي نسويه هنا. أدوات التطوير تساعدك تكتب كود. الذكاء الاصطناعي داخل الكود (Laravel AI SDK) يُستخدم عشان تعامل الذكاء الاصطناعي كجزء آخر من منطق عملك — زي إن عندك "دماغ" مُقسّم لوحدات داخل الفريمورك المفضل عندك. في دراسة حالتي، الهدف بسيط: أتمتة إنشاء آلاف أسئلة التدريب لطلابي بدون ما أطلع من بيئة التطوير.
سير العمل: ذكاء اصطناعي محلي وخاص 100%. وحدة من أعظم مزايا هذا النظام إنه مجاني وخاص بالكامل. ما أستهلك توكنز من OpenAI أو Anthropic؛ كل شي يشتغل على جهازي (ماك ميني M4 Pro) باستخدام نماذج محلية. أستخدم Jan.ai / Ollama كسيرفرات نماذج محلية، ونموذج Gemma 3 (12B) — اختبرت نماذج أكبر (زي 27B أو 30B)، لكنها عادة تسبب أخطاء ذاكرة في بيئات محلية متكاملة. النموذج 12B يوفر توازن مثالي بين التفكير والسرعة. أضبط بيئتي عشان لارافيل "يعتقد" إنه يتكلم مع OpenAI، لكن أعيد توجيه الطلبات لسيرفر Ollama المحلي بتاعي.
التطبيق التقني: الوظائف والطوابير. توليد الأسئلة مهمة ثقيلة ممكن تاخذ ثواني أو حتى دقايق لكل فصل. لذلك، ما أنفذها مطلقًا مباشرة داخل وحدة التحكم. أستخدم طوابير لارافيل مع Redis عشان أرسل الوظائف بالخلفية. هذا يمنع الصفحة من التجمد ويسمح للسيرفر يعالج الفصول وحدة وحدة بدون استعجال. ما أرسل الكتاب كامل للذكاء الاصطناعي دفعة وحدة؛ السكريبت بتاعي يتنقل بين الفصول وياخذ قطع تقريبًا 15 ألف حرف. هذا الحجم مثالي عشان النموذج يكون عنده سياق كافي بدون ما يتشبع أو يبدأ يهلوس. إجبار الذكاء الاصطناعي يرجّع صيغة محددة تحدي حقيقي. رغم إن الـSDK يسمح بتحديد مخططات، أعزز الأمر بطلب دايمًا: سؤال واضح، بالضبط 4 خيارات إجابة، فهرس للإجابة الصحيحة، ورسالة تفسيرية لكل خيار.
مشكلة عدم الاتساق: الذكاء الاصطناعي مو مثالي. من حوالي 4000 سؤال مولّد، حوالي 65 عرضت أخطاء (هامش خطأ منخفض جدًا، لكن موجود). أكثر المشاكل شيوعًا اللي اكتشفتها عبر سكريبتات التحقق: فهارس غير محددة بدقة (الذكاء الاصطناعي يقول الإجابة "1"، لكن ما تطابق المنطق)، رسائل فارغة (أحيانًا ينسى يولّد رسالة التغذية الراجعة التفسيرية)، وخيارات ناقصة (يولّد 3 خيارات بدل الـ4 المطلوبة). درس مستفاد: لازم دايمًا تراجع النتائج. أنشأت لوحة إدارية أقدر فيها أعدل أو أحذف بسرعة الأسئلة اللي سكريبتاتي تعلّمها كـ"غير متسقة".
الخلاصة والنتائج: بفضل سير العمل هذا، تمكنت من توليد بنك أسئلة بجودة عالية مكوّن من 4000 سؤال بتكلفة 0.0 دولار. لو سويت هذا يدويًا أو بفريق بشري، كان بياخذ شهور وآلاف الدولارات. الآن، طلابي يقدرون يوصلون لاختبارات عشوائية حسب الموضوع أو الفصل مباشرة من منصتي، وهذا يحسّن تجربة تعلمهم بشكل كبير.
إضافة: كيف تربط لارافيل بـChatGPT خطوة بخطوة (واجهة OpenAI البرمجية)؟
خلنا نشوف خطوات ربط ChatGPT بمشروع لارافيل، رغم إن الخطوات تقريبًا نفسها لأي تقنية ثانية. التغيير البسيط الوحيد هو كيف تسوي الاستدعاء.
الخطوة الأولى: ركّب حزمة طلبات HTTP. أول شي نحتاجه هو حزمة لعمل طلبات HTTP، لو ما كانت مركبة عندك. في لارافيل، عادة تجي افتراضيًا، فما بتواجه مشكلة. لكن لو ما عندك، ركبها بالطريقة المعتادة:
$ composer require guzzlehttp/guzzle
الخطوة الثانية: احصل على مفتاح OpenAI API. الخطوة الجاية هي الحصول على مفتاح API الخاص بـChatGPT. عشان تسوي هذا، روح لموقع OpenAI الرسمي على platform.openai.com/api-keys. بتشوف شاشة، اضغط "Create new secret key"، أعطها أي اسم (ممكن يكون اسم مشروعك)، وأنشئ المفتاح. بمجرد ما يتولد، احفظه في مشروع لارافيل بتاعك.
الخطوة الثالثة: أنشئ الواجهة (View). ننشئ وحدة تحكم بواجهة HTML بسيطة فيها نموذج لإرسال الاستعلام: مربع نص (textarea) لإدخال السؤال، وزر إرسال:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="{{ route('gtp-1') }}" method="post">
@csrf
<textarea name="ask"></textarea>
<button type="submit">{{ __('Send') }}</button>
</form>
</body>
</html>
هنا، زي ما تشوف، عندنا رابطين: رابط GET لعرض النموذج، ورابط POST لمعالجة السؤال:
Route::get('chatgtp', [ChatGTPController::class, 'textarea']);
Route::post('chatgtp/ask1', [ChatGTPController::class, 'ask'])->name('gtp-1');
بالإضافة إلى إننا أعطينا الرابط اسم عشان نسهّل الإشارة له من الواجهة.
الخطوة الرابعة: وحدة التحكم — سوّي الطلب للواجهة البرمجية. باستخدام حزمة HTTP اللي ركبناها، نسوي طلب لواجهة OpenAI البرمجية. هذا مثال أساسي: رابط نقطة نهاية OpenAI، النموذج المستخدم (في حالتي، النسخة المجانية gpt-3.5-turbo)، والرسالة بدور "user" ومحتوى السؤال المدخل، ودرجة الحرارة (temperature) اللي تحدد إبداعية الإجابة (تقدر تجرب 0.7 مثلًا). تقدر أيضًا تحط dd() أو dump() عشان تشوف وش رجّعت الواجهة البرمجية:
<?php
namespace App\Http\Controllers\Dashboard;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Http;
class ChatGTPController extends Controller
{
public function textarea()
{
return view('chatgtp.textarea');
}
public function ask(): string
{
$response = Http::withToken(env('OPENAI_API_KEY'))
->post('https://api.openai.com/v1/chat/completions', [
'model' => 'gpt-3.5-turbo',
'messages' => [
['role' => 'user', 'content' => request('ask')],
],
'temperature' => 0.7,
]);
dd($response->json());
return $response->json()['choices'][0]['message']['content'] ?? 'No hubo respuesta.';
}
}
الخطوة الخامسة: اضبط المفتاح في .env. تقدر تحفظ المفتاح مباشرة في ملف .env بتاعك، مثلًا:
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx
وبعدين تسترجعه في كودك بـenv(‘OPENAI_API_KEY’).
الملخص
باختصار: Laravel AI SDK يحول تطبيق لارافيل من مجرد فريمورك ويب إلى منصة ذكية بالكامل، عبر وكلاء (Agents) مجهولين أو منظمين بمخططات JSON صارمة، أدوات (Tools) تربط الوكيل بالعالم الخارجي، طوابير عمل تمنع تجمد الواجهة أثناء المعالجة الثقيلة، وتضمينات (Embeddings) مع قواعد بيانات متجهات (pgvector) للبحث الدلالي على نطاق واسع — كل هذا يشتغل مع نماذج سحابية (OpenAI، Gemini، Anthropic) أو نماذج محلية بالكامل (Ollama، Jan.ai، LM Studio) لخصوصية وتكلفة صفر. جرب تركّب الـ SDK الحين بأمر composer require laravel/ai، وابنِ أول وكيل مجهول بسيط قبل ما تنتقل للوكلاء المنظمين والأدوات المخصصة.


