لارافيل, Others / 2026-08-16

Laravel AI SDK بالعربي: ابنِ AI Agent كامل مع RAG خطوة بخطوة

Laravel AI SDK بالعربي: ابنِ AI Agent كامل مع RAG خطوة بخطوة

2026-08-16 وقت القراءه : 9 دقائق

لم يعد الذكاء الاصطناعي ميزة إضافية في تطبيقات الويب، بل أصبح جزءًا من البنية الأساسية: تحليل بيانات، بحث داخل المستندات، توليد صور، تحويل صوت إلى نص، وبناء AI Agents تنفذ مهام حقيقية داخل التطبيق.

مؤخرًا أطلق فريق Laravel رسميًا حزمة Laravel AI SDK كحزمة First-Party، أي مقدمة ومدعومة مباشرة من Laravel نفسها. الحزمة مبنية فوق مكتبة Prism، والعلاقة بينهما — كما وصفها Taylor Otwell — أشبه بالعلاقة بين Query Builder وEloquent: طبقة أعلى مستوى وأكثر تكاملًا مع الـ Framework.

في هذا المقال سنبني فعليًا: Agent كامل، ثم نضيف له Structured Output، ثم Tool تتصل بقاعدة البيانات، ثم نظام RAG كامل بالـ Embeddings، وننتهي بالـ Failover والاختبارات.


المشكلة التي تحلها حزمة Laravel AI SDK

قبل الـ SDK، كان دمج AI في مشروع Laravel يعني:

  • قراءة وثائق OpenAI وAnthropic وGemini كلٌّ على حدة.
  • كتابة HTTP Integration مختلف لكل مزود، بطريقة Authentication وشكل Response مختلفين.
  • إذا قررت لاحقًا تبديل المزود، ستعيد كتابة أجزاء كبيرة من الكود.

حاليًا تدعم الحزمة OpenAI وAnthropic وGemini وAzure وBedrock وGroq وxAI وDeepSeek وMistral وOllama وOpenRouter، بالإضافة لأي مزود متوافق مع OpenAI API (مثل LM Studio وvLLM للنماذج المحلية). وهناك مزودون متخصصون لوظائف أخرى مثل ElevenLabs للصوت وCohere وVoyageAI للـ Embeddings والـ Reranking.


تثبيت حزمة Laravel AI SDK

composer require laravel/ai

php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"

php artisan migrate

الـ migrations تنشئ جدولين: agent_conversations وagent_conversation_messages، وهما ما يستخدمه الـ SDK لتخزين محادثات الـ Agents تلقائيًا (سنرى كيف لاحقًا).

ثم أضف مفاتيح المزودين الذين ستستخدمهم في ملف .env:

OPENAI_API_KEY=
ANTHROPIC_API_KEY=
GEMINI_API_KEY=

النماذج الافتراضية لكل وظيفة (نص، صور، صوت، Embeddings) تُضبط من ملف config/ai.php.


الوحدة الأساسية: الـ Agent

الـ Agent في الـ SDK هو كلاس PHP يغلّف كل ما يخص التفاعل مع النموذج: التعليمات (System Prompt)، سياق المحادثة، الأدوات، وشكل المخرجات. فكّر فيه كمساعد متخصص — مدرب مبيعات، محلل مستندات، بوت دعم فني — تعرّفه مرة واحدة وتستدعيه من أي مكان في التطبيق.

سنحاول الآن بناء Agent لتحليل مكالمات المبيعات:

php artisan make:agent SalesCoach
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;
use Stringable;

class SalesCoach implements Agent
{
    use Promptable;

    public function instructions(): Stringable|string
    {
        return 'أنت مدرب مبيعات خبير. حلّل نصوص المكالمات وقدّم ملاحظات
                عملية ودرجة تقييم لأداء موظف المبيعات.';
    }
}

هذا كل شيء. الآن يمكن استدعاؤه من أي Controller:

$response = (new SalesCoach)->prompt('حلّل نص المكالمة التالية: ...');

return (string) $response;

وإذا أردت تحديد المزود والنموذج لهذا الطلب فقط:

use Laravel\Ai\Enums\Lab;

$response = (new SalesCoach)->prompt(
    'حلّل نص المكالمة التالية: ...',
    provider: Lab::Anthropic,
    model: 'claude-sonnet-5',
    timeout: 120,
);

أو تثبيت الإعدادات على مستوى الكلاس نفسه باستخدام PHP Attributes:

use Laravel\Ai\Attributes\{Provider, Model, Temperature, MaxSteps};
use Laravel\Ai\Enums\Lab;

#[Provider(Lab::Anthropic)]
#[Model('claude-sonnet-5')]
#[Temperature(0.7)]
#[MaxSteps(10)]
class SalesCoach implements Agent
{
    use Promptable;
    // ...
}

هناك أيضًا Attributes ذكية مثل #[UseCheapestModel] لاختيار أرخص نموذج لدى المزود تلقائيًا (مناسب للتلخيص البسيط) و#[UseSmartestModel] لأقوى نموذج (مناسب للمهام المعقدة).


Structured Output: من نص حر إلى بيانات برمجية

هنا تبدأ القيمة الحقيقية. في Backend حقيقي لا نريد فقرة نصية من النموذج، بل نريد بيانات منظمة نخزنها في CRM أو نبني عليها منطقًا برمجيًا:

{
    "sentiment": "positive",
    "lead_score": 85,
    "recommended_action": "call_customer"
}

لتحقيق ذلك، يطبّق الـ Agent واجهة HasStructuredOutput ويعرّف دالة schema:

<?php

namespace App\Ai\Agents;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, HasStructuredOutput
{
    use Promptable;

    public function instructions(): string
    {
        return 'أنت مدرب مبيعات. حلّل المكالمة وأعد النتائج بالشكل المطلوب.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'sentiment' => $schema->string()
                ->enum(['positive', 'neutral', 'negative'])
                ->required(),

            'lead_score' => $schema->integer()->min(0)->max(100)->required(),

            'recommended_action' => $schema->string()->required(),

            'objections' => $schema->array()
                ->items(
                    $schema->object(fn ($schema) => [
                        'objection' => $schema->string()->required(),
                        'suggested_reply' => $schema->string()->required(),
                    ])
                )
                ->required(),
        ];
    }
}

لاحظ أننا عرّفنا الـ Schema بنفس أسلوب Laravel المألوف — Fluent API يشبه الـ Validation Rules. والاستخدام أبسط مما تتوقع، لأن الـ Response يتصرف كمصفوفة:

$response = (new SalesCoach)->prompt($transcript);

Lead::where('id', $leadId)->update([
    'score'     => $response['lead_score'],
    'sentiment' => $response['sentiment'],
]);

foreach ($response['objections'] as $objection) {
    // اعتراضات العميل مع الرد المقترح، جاهزة كبيانات منظمة
}

لا Parsing يدوي، لا Regex، لا «أرجوك يا نموذج أعد JSON فقط» في الـ Prompt.


Tools: عندما يحتاج النموذج بيانات تطبيقك

النموذج لا يعرف شيئًا عن قاعدة بياناتك. فإذا سأل العميل بوت الدعم: «ما حالة طلبي رقم 4521؟» فالنموذج بلا أدوات سيخمّن أو يعتذر.

الحل هو Tool: دالة داخل Laravel يستطيع الـ Agent استدعاءها عند الحاجة:

php artisan make:tool GetOrderStatus
<?php

namespace App\Ai\Tools;

use App\Models\Order;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;

class GetOrderStatus implements Tool
{
    public function __construct(protected int $userId) {}

    public function description(): Stringable|string
    {
        return 'تُستخدم هذه الأداة لجلب حالة طلب معين للعميل الحالي من قاعدة البيانات.';
    }

    public function handle(Request $request): Stringable|string
    {
        $order = Order::where('user_id', $this->userId)
            ->findOrFail($request['order_id']);

        return json_encode([
            'status'      => $order->status,
            'shipped_at'  => $order->shipped_at?->toDateString(),
            'total'       => $order->total,
        ]);
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'order_id' => $schema->integer()->required(),
        ];
    }
}

لاحظ نقطة أمان مهمة: مرّرنا userId عبر الـ Constructor وقيّدنا الاستعلام به، فلا يستطيع النموذج — مهما حاول المستخدم التلاعب بالـ Prompt — قراءة طلبات عميل آخر.


الآن نربط الأداة بالـ Agent:

use Laravel\Ai\Contracts\HasTools;

class SupportAgent implements Agent, HasTools
{
    use Promptable;

    public function __construct(public User $user) {}

    public function instructions(): string
    {
        return 'أنت وكيل دعم فني ودود. استخدم الأدوات المتاحة لجلب البيانات
                الحقيقية بدل التخمين.';
    }

    public function tools(): iterable
    {
        return [
            new GetOrderStatus($this->user->id),
        ];
    }
}

عندما يُسأل الـ Agent عن حالة طلب، سيستدعي الأداة، يستلم البيانات الحقيقية، ثم يصيغ إجابة طبيعية بناءً عليها. الـ AI هنا أصبح طبقة ذكية فوق الـ Business Logic، وليس بديلًا عنه.


موافقة بشرية قبل الأدوات الخطرة (Human Tool Approval)

للأدوات الحساسة (حذف ملف، إصدار استرداد مالي)، تدعم الحزمة Human Tool Approval: الـ Agent يتوقف قبل تنفيذ الأداة وينتظر موافقة بشرية، عبر تطبيق واجهة Approvable على الأداة. ثم تفحص:

$response->hasPendingApprovals()

وتعرض الطلب للمستخدم ليوافق أو يرفض قبل استكمال التنفيذ. ميزة نادرة في أطر مماثلة، وأساسية لأي Agent يمس عمليات لا رجعة فيها.


RAG عمليًا: البحث الدلالي في بياناتك

RAG (Retrieval-Augmented Generation) ببساطة: بدل أن يجيب النموذج من ذاكرته، يبحث أولًا في بياناتك ثم يجيب بناءً على ما وجده. سنبنيه بثلاث خطوات.

الخطوة 1: عمود Vector في قاعدة البيانات

الحزمة تدعم أعمدة الـ Vector مباشرة على PostgreSQL عبر امتداد pgvector:

Schema::ensureVectorExtensionExists();

Schema::create('documents', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content');
    $table->vector('embedding', dimensions: 1536)->index();
    $table->timestamps();
});

استدعاء index() ينشئ تلقائيًا فهرس HNSW بمسافة Cosine لتسريع البحث.

الخطوة 2: توليد الـ Embeddings عند الحفظ

الـ Embedding هو تمثيل رقمي لمعنى النص. توليده بسيط لدرجة أنه أصبح دالة على Str:

use Illuminate\Support\Str;

$document = Document::create([
    'title'     => $request->title,
    'content'   => $request->content,
    'embedding' => Str::of($request->content)->toEmbeddings(),
]);

ولا تنسَ الـ Cast في الموديل:

protected function casts(): array
{
    return ['embedding' => 'array'];
}

الخطوة 3: البحث بالمعنى

$documents = Document::query()
    ->whereVectorSimilarTo('embedding', 'مشكلة في دفع الاشتراك')
    ->limit(10)
    ->get();

مرّرنا نصًا عاديًا وLaravel ولّد الـ Embedding له تلقائيًا وبحث بالتشابه الدلالي. فالبحث عن «مشكلة في دفع الاشتراك» قد يعيد مستندًا عنوانه «Failed subscription payments» رغم عدم تطابق أي كلمة حرفيًا.


ربط كل ذلك بالـ Agent

الآن الخطوة الأجمل: إعطاء الـ Agent قدرة البحث هذه كأداة جاهزة، بسطر واحد:

use Laravel\Ai\Tools\SimilaritySearch;

public function tools(): iterable
{
    return [
        SimilaritySearch::usingModel(Document::class, 'embedding')
            ->withDescription('ابحث في قاعدة المعرفة عن مقالات ذات صلة بسؤال العميل.'),
    ];
}

وبهذا أصبح لديك AI Assistant يبحث في آلاف مستندات شركتك (سياسات، عقود، أسئلة شائعة) ويجيب بناءً عليها — نظام RAG كامل داخل Laravel بدون أي خدمة خارجية إضافية.

وإذا فضّلت عدم إدارة الـ Vectors بنفسك، توفر الحزمة بديلًا: Vector Stores مُدارة لدى المزود نفسه (Stores::create('Knowledge Base')) مع أداة FileSearch للبحث فيها، وهي مدعومة حاليًا مع OpenAI وGemini.


Provider Tools: البحث في الإنترنت

بعض الأدوات ينفذها المزود نفسه وليس تطبيقك. أهمها WebSearch لإعطاء الـ Agent وصولًا لمعلومات حديثة:

use Laravel\Ai\Providers\Tools\WebSearch;

public function tools(): iterable
{
    return [
        (new WebSearch)->max(5)->allow(['laravel.com', 'php.net']),
    ];
}

لاحظ إمكانية تقييد عدد عمليات البحث وحصر النتائج بدومينات محددة — مفيد جدًا للتحكم بالتكلفة والجودة. الأداة مدعومة مع Anthropic وOpenAI وAzure وGemini وOpenRouter. وهناك أيضًا WebFetch لقراءة صفحات ويب محددة (Anthropic وGemini).


ذاكرة المحادثات: بدون كتابة سطر SQL

تذكر جدولي agent_conversations من خطوة التثبيت؟ هذا وقتهما. أضف Trait واحدًا:

use Laravel\Ai\Concerns\RemembersConversations;
use Laravel\Ai\Contracts\Conversational;

class SupportAgent implements Agent, Conversational
{
    use Promptable, RemembersConversations;
    // ...
}

وأصبح لديك محادثات محفوظة تلقائيًا لكل مستخدم:

// بدء محادثة جديدة
$response = (new SupportAgent)->forUser($user)->prompt('مرحبًا!');

$conversationId = $response->conversationId;

// استكمالها لاحقًا — السياق السابق يُحمّل تلقائيًا
$response = (new SupportAgent)
    ->continue($conversationId, as: $user)
    ->prompt('أخبرني المزيد عن ذلك.');

وبإضافة Trait HasConversations على موديل User تستطيع عرض محادثات المستخدم بـ Eloquent عادي:

$conversations = $user->conversations()->latest('updated_at')->paginate(20);


Streaming وQueueing: تجربة مستخدم سريعة

Streaming

بدل انتظار الإجابة كاملة، ابثّها للمستخدم كلمة بكلمة. الـ Response القابل للبث يُعاد من الـ Route مباشرة كـ SSE:

Route::get('/chat', function () {
    return (new SupportAgent)->stream('اشرح لي سياسة الاسترجاع');
});

وإذا كانت واجهتك الأمامية تستخدم Vercel AI SDK (شائع مع React/Next.js)، فهناك توافق مباشر مع بروتوكوله:

return (new SupportAgent)
    ->stream($prompt)
    ->usingVercelDataProtocol();

Queueing

للمهام الثقيلة (تحليل مستند طويل مثلًا)، نفّذها في الخلفية عبر Queues بنفس أسلوب Laravel المعتاد:

Route::post('/analyze', function (Request $request) {
    (new SalesCoach)
        ->queue($request->input('transcript'))
        ->then(function (AgentResponse $response) {
            // خزّن النتيجة، أرسل إشعارًا...
        })
        ->catch(function (Throwable $e) {
            // عالج الخطأ
        });

    return back();
});


أبعد من النصوص: صور وصوت وتفريغ

توليد الصور

use Laravel\Ai\Image;

$path = Image::of('لوحة تحكم مالية مستقبلية')
    ->quality('high')
    ->landscape()
    ->generate()
    ->store();   // تُحفظ مباشرة على الـ Disk الافتراضي

تحويل النص إلى صوت (TTS)

use Laravel\Ai\Audio;

$path = Audio::of('أهلًا بك في متجرنا!')
    ->female()
    ->generate()
    ->storeAs('welcome.mp3');

تفريغ الصوت إلى نص (STT)

وهنا مثال عملي جميل: خذ تسجيل مكالمة دعم، فرّغه نصيًا مع تمييز المتحدثين، ثم مرّر النص لـ Agent التحليل:

use Laravel\Ai\Transcription;

$transcript = Transcription::fromStorage('call-recording.mp3')
    ->diarize()   // تمييز كلام كل متحدث على حدة
    ->generate();

$analysis = (new SalesCoach)->prompt((string) $transcript);

سطران فقط، وحصلت على Pipeline كامل: صوت ← نص ← تحليل منظم يُحفظ في الـ CRM.


Failover: عندما يتعطل المزود الأساسي

في Production، المزود قد يتوقف مؤقتًا أو تصل حد الـ Rate Limit. بدل أن يفشل تطبيقك، عرّف سلسلة احتياطية:

use Laravel\Ai\Enums\Lab;

$response = (new SalesCoach)->prompt(
    'حلّل نص المكالمة...',
    provider: [Lab::OpenAI, Lab::Anthropic],
);

ويمكن تحديد نموذج معين لكل مزود في السلسلة:

$response = (new SalesCoach)->prompt(
    'حلّل نص المكالمة...',
    provider: [
        Lab::Gemini->value   => 'gemini-3-flash-preview',
        Lab::DeepSeek->value => 'deepseek-v4-pro',
    ],
);

نقطة ذكية في التصميم: الـ Failover لا يحدث لأي خطأ، بل فقط للأخطاء «القابلة للتحويل» — Rate Limit، مزود غير متاح، رصيد منتهٍ. أما أخطاء الطلب نفسه (Validation مثلًا) فلن تنتقل لمزود آخر لأنها ستفشل هناك أيضًا.

الاختبارات

كيف تختبر كودًا يعتمد على AI بدون استهلاك API حقيقي في كل Test؟ الحزمة توفر Fakes بنفس أسلوب Mail::fake() المألوف:

use App\Ai\Agents\SalesCoach;

public function test_transcript_analysis_updates_lead_score(): void
{
    // زوّد ردًا وهميًا يطابق الـ Schema
    SalesCoach::fake([
        ['sentiment' => 'positive', 'lead_score' => 87, /* ... */],
    ]);

    $this->post('/analyze', ['transcript' => '...'])
        ->assertOk();

    // تأكد أن الـ Agent استُدعي فعلًا بالمحتوى الصحيح
    SalesCoach::assertPrompted(
        fn ($prompt) => $prompt->contains('transcript')
    );

    $this->assertEquals(87, Lead::first()->score);
}

بل إن fake() بدون أي وسيط سيولّد تلقائيًا بيانات وهمية مطابقة للـ Schema المعرّف في الـ Agent. وهناك Fakes مماثلة لكل شيء: Image::fake() وAudio::fake() وEmbeddings::fake() وغيرها، مع preventStrayPrompts() لرمي Exception إذا استُدعي Agent بلا Fake — تمامًا كما تتوقع من Laravel.


لماذا هذه الخطوة مهمة فعلًا؟

القيمة ليست في أن Laravel أصبح يرسل Prompt لـ ChatGPT — كان ذلك ممكنًا دائمًا بـ HTTP Client. القيمة في أن Laravel وفّر بنية تطبيقية كاملة للذكاء الاصطناعي:

Laravel Application
        ↓
     AI Agent
        ↓
 ┌──────┼──────┐
 ↓      ↓      ↓
Tools   RAG   Database
        ↓
   AI Provider (+ Failover)
 ┌──────┼────────┐
 ↓      ↓        ↓
OpenAI Claude  Gemini

Agents وTools وStructured Output وConversations وStreaming وQueues وEmbeddings وVector Search وFailover وHuman Approval وTesting — كلها بأسلوب Laravel المألوف، وكلها في حزمة رسمية واحدة.


ماذا يمكن أن تبني الآن؟

بالمكونات التي رأيناها عمليًا في هذا المقال، تستطيع تركيب أنظمة حقيقية:

  • AI Customer Support: الـ SupportAgent + أداة GetOrderStatus + SimilaritySearch على قاعدة المعرفة + RemembersConversations للسياق.
  • AI CRM Assistant: الـ SalesCoach + Transcription للمكالمات + Structured Output يُحفظ مباشرة في الـ Leads.
  • Document Analysis System: رفع العقود كـ Attachments + Agent بـ Schema يستخرج البنود والتواريخ والمبالغ.
  • بحث دلالي داخل موقعك: Embeddings + whereVectorSimilarTo بدل الـ Keyword Search التقليدي.


الخلاصة

لم يعد السؤال: «كيف أربط Laravel مع ChatGPT؟»

أصبح السؤال: «كيف أبني AI Agent متكامل داخل Laravel يستخدم أدوات التطبيق وبياناته وملفاته، ويتعامل مع أكثر من مزود، ويُختبر كأي كود آخر؟»

ومع Laravel AI SDK، أصبحت إجابة هذا السؤال — لأول مرة في عالم PHP — حزمة رسمية واحدة تثبّتها بـ composer require laravel/ai.

Laravel + AI لم يعد مجرد Integration… بل أصبح جزءًا من طريقة بناء التطبيق نفسه. 🚀

المصادر الرسمية

إضافة تعليق
Loading...