← All writing

Building AI-Powered Features in Laravel with the First-Party AI SDK

Laravel's first-party AI SDK went production-stable with Laravel 13, offering a provider-agnostic interface for text generation, tool calling, RAG, and agent workflows. Here is a practical guide to integrating it without coupling your application to a single AI provider.


Laravel has shipped AI tooling before — the Pennant feature flags package, the Pulse observability dashboard — but the Laravel AI SDK released with Laravel 13 in March 2026 is a different category of addition. It is not a wrapper around a single provider. It is a first-party abstraction layer for integrating generative AI capabilities into Laravel applications with the same testability and provider flexibility the framework applies to queues, caches, and mail.

The SDK handles provider selection, retry logic, streaming, error normalisation, and observability. Your application code calls a stable interface regardless of whether the model behind it is OpenAI, Anthropic, Gemini, or another supported provider.

Installation and configuration

composer require laravel/ai
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
php artisan migrate

The migration creates the tables the SDK uses for agent conversation memory and observability. Configuration goes in config/ai.php:

return [
    'default' => env('AI_PROVIDER', 'anthropic'),

    'providers' => [
        'anthropic' => [
            'api_key' => env('ANTHROPIC_API_KEY'),
            'model'   => env('AI_MODEL', 'claude-3-5-sonnet-20241022'),
        ],
        'openai' => [
            'api_key' => env('OPENAI_API_KEY'),
            'model'   => env('AI_MODEL', 'gpt-4o'),
        ],
    ],
];

Switching providers in production is a config change, not a code change.

Text generation: the simplest integration

For most features — document summarisation, copy generation, content classification — AI::text() is the entry point:

use Laravel\Ai\Facades\AI;

final class DocumentSummariser
{
    public function summarise(string $content, int $maxWords = 150): string
    {
        return AI::text(
            prompt: "Summarise the following document in under {$maxWords} words. "
                  . "Write in plain language for a general audience.\n\n{$content}",
        );
    }
}

The call is synchronous. For long documents or high-latency paths, dispatch a queued job and store the result:

use Illuminate\Contracts\Queue\ShouldQueue;
use Laravel\Ai\Facades\AI;

final class SummariseDocumentJob implements ShouldQueue
{
    public function __construct(
        public readonly int $documentId,
    ) {}

    public function handle(): void
    {
        $document = Document::findOrFail($this->documentId);

        $summary = AI::text(
            prompt: "Summarise this document for a product manager:\n\n{$document->content}",
        );

        $document->update(['ai_summary' => $summary]);
    }
}

Tool calling: giving the model access to your application

Tool calling allows the model to invoke named functions in your application and use the results before composing a final response. The SDK wraps this through a Tool interface:

use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Facades\AI;

final class GetSubscriptionStatus implements Tool
{
    public string $name = 'get_subscription_status';
    public string $description = 'Returns the active subscription plan and renewal date for a customer account.';

    /** @param array{customer_id: string} $arguments */
    public function handle(array $arguments): string
    {
        $subscription = Subscription::where('customer_id', $arguments['customer_id'])
            ->where('status', 'active')
            ->latest('started_at')
            ->first();

        if (!$subscription) {
            return json_encode(['status' => 'no_active_subscription']);
        }

        return json_encode([
            'plan'       => $subscription->plan,
            'renews_at'  => $subscription->renews_at->toIso8601String(),
            'status'     => 'active',
        ]);
    }
}

// In a support agent context
$response = AI::withTools([new GetSubscriptionStatus()])->text(
    "The customer with ID {$customerId} is asking about their next renewal date.",
);

The model decides when to call the tool, calls it with the arguments it infers, receives the JSON result, and incorporates it into the final response. Your application code does not orchestrate the back-and-forth — the SDK manages the tool call loop.

Retrieval-Augmented Generation with Eloquent

For grounding responses in your application’s own documents, the SDK integrates with PostgreSQL’s pgvector extension through Eloquent. Embeddings are generated and stored alongside records:

use Laravel\Ai\Facades\AI;

// Generate and store an embedding when content is created
final class StoringDocumentEmbedding
{
    public function handle(Document $document): void
    {
        $embedding = AI::embed($document->content);
        $document->update(['embedding' => $embedding]);
    }
}

// Retrieve semantically similar documents at query time
$query = "Which documents discuss refund policy?";
$queryEmbedding = AI::embed($query);

$relevant = Document::query()
    ->orderByVectorDistance('embedding', $queryEmbedding)
    ->limit(5)
    ->get();

$context = $relevant->pluck('content')->join("\n\n---\n\n");

$answer = AI::text(
    "Answer this question using only the documents below:\n\n{$context}\n\nQuestion: {$query}",
);

This pattern — embed, store, retrieve by similarity, compose — is the foundation of most practical RAG implementations. The database stores the embeddings; the SDK handles the embedding API call and the similarity query expression.

Testing: faking the SDK

The SDK ships a fake() method that prevents real API calls in tests and returns controlled responses:

use Laravel\Ai\Facades\AI;

it('stores a summary when a document is summarised', function () {
    AI::fake(['Summarised content.']);

    $document = Document::factory()->create(['content' => 'Long document text…']);

    SummariseDocumentJob::dispatchSync($document->id);

    expect($document->fresh()->ai_summary)->toBe('Summarised content.');
});

Fakes are sequenced — the first call returns the first item in the array, the second call returns the second, and so on. For tool calling scenarios, AI::fake() also accepts structured responses that simulate a tool call sequence, letting you test the full loop without an API key.

What the SDK does not replace

The SDK is an integration layer, not a product. It does not handle:

  • Prompt versioning or management. Store prompts in your database or config if they change frequently and need audit history.
  • Cost management. Track token usage via the SDK’s lifecycle events (StepCompleted) and store usage alongside the request for billing or alerting.
  • Content safety. If your application allows users to influence prompts, input validation and output filtering are your responsibility before responses reach users.

The first-party nature of the SDK means it integrates cleanly with Laravel’s testing utilities, queue system, and config management. That is its primary advantage over wrapping a third-party HTTP client directly.