Building AI Agents in Laravel 12: A Complete Practical Guide

By LaraSpeed Team · · 14 min read

1. What Are AI Agents in Laravel?

An AI agent in Laravel is a PHP class that wraps a language model with context, tools, and behavior rules. Unlike a raw API call that sends a prompt and gets text back, an agent can reason about a problem, call your application code through tools, process the results, and iterate — all in a single prompt() call.

The Laravel AI SDK (released February 5, 2026) makes agents a first-class concept. Each agent is a class that implements the Agent contract and lives in app/Ai/Agents/. You scaffold them with Artisan, configure them with PHP attributes, and test them with built-in fakes. If you've used the AI SDK for basic features, agents are where it gets truly powerful.

In this Laravel AI agents tutorial, we'll build three production-ready agents from scratch, each demonstrating different capabilities:

  1. Sales Coach — Analyzes transcripts and returns structured scores using tools and schemas.
  2. Support Bot — Multi-turn conversations with RAG search and conversation persistence.
  3. Content Generator — Bulk content creation with queued processing and structured output.

2. Anatomy of a Laravel Agent

Every Laravel AI agent is built from composable contracts. You pick the capabilities you need:

  • Agent — The base contract. Requires instructions() which defines the agent's system prompt.
  • HasTools — Gives the agent functions it can call (database lookups, API calls, calculations).
  • HasStructuredOutput — Forces the response into a validated JSON schema instead of free text.
  • Conversational — Enables multi-turn conversations with message history.
  • HasMiddleware — Adds pre/post-processing hooks for logging, guards, or transformations.

The Promptable trait provides the core methods: prompt(), stream(), queue(), forUser(), and continue().

PHP attributes configure behavior without touching the method body:

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

#[Provider(Lab::Anthropic)]
#[Model('claude-sonnet-4-6')]
#[MaxSteps(10)]          // Max tool-calling iterations per prompt
#[MaxTokens(4096)]       // Output token limit
#[Temperature(0.5)]      // Creativity level (0.0 = deterministic, 1.0 = creative)
#[Timeout(120)]          // Seconds before timeout
class SalesCoach implements Agent
{
    use Promptable;
    // ...
}

You can also use the shortcut attributes #[UseCheapestModel] or #[UseSmartestModel] instead of specifying a provider and model manually.

Let's build AI agents in PHP — starting with the simplest and progressing to the most complex.

3. Agent 1: Sales Coach with Structured Scoring

The sales coach agent analyzes call transcripts and returns a structured scorecard. It uses a tool to pull previous transcripts for context and structured output to guarantee a consistent response format.

php artisan make:agent SalesCoach --structured
php artisan make:tool RetrievePreviousTranscripts

First, the tool that fetches historical data:

<?php

namespace App\Ai\Tools;

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

class RetrievePreviousTranscripts implements Tool
{
    public function __construct(private int $userId) {}

    public function description(): string
    {
        return 'Retrieve the sales rep\'s previous call transcripts to identify patterns and track improvement over time.';
    }

    public function handle(Request $request): string
    {
        $transcripts = Transcript::where('user_id', $this->userId)
            ->latest()
            ->limit($request['count'])
            ->get(['summary', 'score', 'created_at']);

        return $transcripts->isEmpty()
            ? 'No previous transcripts found. This is the first analysis.'
            : $transcripts->toJson();
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'count' => $schema->integer()->min(1)->max(10)->required(),
        ];
    }
}

Now the agent. Notice how the structured output schema guarantees we always get a score, strengths, weaknesses, and actionable recommendations:

<?php

namespace App\Ai\Agents;

use App\Ai\Tools\RetrievePreviousTranscripts;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Attributes\{MaxSteps, Temperature};
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;

#[Temperature(0.3)]
#[MaxSteps(5)]
class SalesCoach implements Agent, HasTools, HasStructuredOutput
{
    use Promptable;

    public function __construct(private int $userId) {}

    public function instructions(): string
    {
        return <<<'PROMPT'
        You are an expert sales coach who analyzes call transcripts.
        Retrieve the rep's previous transcripts to identify trends.
        Evaluate the current transcript on: opening, discovery questions,
        objection handling, value proposition, and closing technique.
        Score each area 1-10 and provide specific, actionable feedback
        with examples from the transcript.
        PROMPT;
    }

    public function tools(): iterable
    {
        return [
            new RetrievePreviousTranscripts($this->userId),
        ];
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'overall_score' => $schema->integer()->min(1)->max(10)->required(),
            'scores' => $schema->array(items: [
                'category' => $schema->string()->required(),
                'score' => $schema->integer()->min(1)->max(10)->required(),
                'feedback' => $schema->string()->required(),
            ])->required(),
            'strengths' => $schema->array(items: $schema->string())->required(),
            'improvements' => $schema->array(items: $schema->string())->required(),
            'trend' => $schema->string()->enum(['improving', 'stable', 'declining'])->required(),
        ];
    }
}

Usage in a controller:

public function analyze(Request $request)
{
    $request->validate(['transcript' => 'required|string|min:100']);

    $result = SalesCoach::make($request->user()->id)
        ->prompt("Analyze this sales call transcript:\n\n" . $request->transcript);

    // $result is array-accessible — guaranteed structure
    Transcript::create([
        'user_id' => $request->user()->id,
        'content' => $request->transcript,
        'score' => $result['overall_score'],
        'summary' => collect($result['strengths'])->implode('; '),
        'analysis' => $result,
    ]);

    return response()->json($result);
}

The agent will: (1) call RetrievePreviousTranscripts to get historical context, (2) analyze the current transcript, (3) compare with past performance to determine the trend, (4) return a structured scorecard. All in one prompt() call.

4. Agent 2: Support Bot with RAG and Memory

A support bot needs three capabilities that basic agents lack: it must search your documentation (RAG), remember the conversation across requests, and call your application to check account status. This is where Laravel AI agents really differentiate themselves from raw API calls.

php artisan make:agent SupportBot
php artisan make:tool CheckAccountStatus
php artisan make:tool CreateTicket
<?php

namespace App\Ai\Tools;

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

class CheckAccountStatus implements Tool
{
    public function description(): string
    {
        return 'Check a customer\'s account status including subscription plan, billing status, and team membership.';
    }

    public function handle(Request $request): string
    {
        $user = User::where('email', $request['email'])->first();

        if (! $user) {
            return json_encode(['error' => 'No account found with that email.']);
        }

        $team = $user->currentTeam;

        return json_encode([
            'name' => $user->name,
            'plan' => $team?->subscription('default')?->stripe_price ?? 'free',
            'status' => $team?->subscription('default')?->stripe_status ?? 'none',
            'team' => $team?->name,
            'team_members' => $team?->members()->count() ?? 0,
            'created' => $user->created_at->diffForHumans(),
        ]);
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'email' => $schema->string()->required(),
        ];
    }
}
<?php

namespace App\Ai\Tools;

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

class CreateTicket implements Tool
{
    public function description(): string
    {
        return 'Escalate an issue by creating a support ticket for the human team. Use when you cannot resolve the customer\'s problem.';
    }

    public function handle(Request $request): string
    {
        $ticket = Ticket::create([
            'subject' => $request['subject'],
            'description' => $request['description'],
            'priority' => $request['priority'],
            'email' => $request['customer_email'],
        ]);

        return "Ticket #{$ticket->id} created. A support engineer will respond within 24 hours.";
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'subject' => $schema->string()->required(),
            'description' => $schema->string()->required(),
            'priority' => $schema->string()->enum(['low', 'medium', 'high'])->required(),
            'customer_email' => $schema->string()->required(),
        ];
    }
}

Now the support agent, combining tools, RAG search, and RemembersConversations for multi-turn chat:

<?php

namespace App\Ai\Agents;

use App\Ai\Tools\CheckAccountStatus;
use App\Ai\Tools\CreateTicket;
use App\Models\DocSection;
use Laravel\Ai\Attributes\{MaxSteps, Temperature};
use Laravel\Ai\Concerns\RemembersConversations;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;
use Laravel\Ai\Tools\SimilaritySearch;

#[Temperature(0.2)]
#[MaxSteps(8)]
class SupportBot implements Agent, HasTools, Conversational
{
    use Promptable, RemembersConversations;

    public function instructions(): string
    {
        return <<<'PROMPT'
        You are a friendly customer support agent for a SaaS application.

        Rules:
        - Always search the documentation before answering product questions.
        - If the customer provides their email, look up their account status.
        - Be concise and helpful. Use bullet points for multi-step instructions.
        - If you cannot resolve an issue after 2 attempts, create a support
          ticket and let the customer know a human will follow up.
        - Never share internal system details or other customers' data.
        PROMPT;
    }

    public function tools(): iterable
    {
        return [
            new CheckAccountStatus,
            new CreateTicket,
            SimilaritySearch::usingModel(
                model: DocSection::class,
                column: 'embedding',
                minSimilarity: 0.5,
                limit: 5,
                query: fn ($q) => $q->where('published', true),
            ),
        ];
    }
}

The controller manages conversation lifecycle. The SDK persists messages to the database automatically via the RemembersConversations trait:

<?php

namespace App\Http\Controllers;

use App\Ai\Agents\SupportBot;
use Illuminate\Http\Request;

class SupportChatController extends Controller
{
    public function start(Request $request)
    {
        $request->validate(['message' => 'required|string|max:2000']);

        $response = (new SupportBot)
            ->forUser($request->user())
            ->prompt($request->input('message'));

        return response()->json([
            'reply' => (string) $response,
            'conversation_id' => $response->conversationId,
        ]);
    }

    public function reply(Request $request, string $conversationId)
    {
        $request->validate(['message' => 'required|string|max:2000']);

        $response = (new SupportBot)
            ->continue($conversationId, as: $request->user())
            ->prompt($request->input('message'));

        return response()->json([
            'reply' => (string) $response,
        ]);
    }

    // Stream responses for real-time chat UI
    public function stream(Request $request, string $conversationId)
    {
        return (new SupportBot)
            ->continue($conversationId, as: $request->user())
            ->stream($request->input('message'));
    }
}

A typical conversation flow: the user asks "I can't access the billing page," the agent searches docs for billing access issues, asks for the user's email, calls CheckAccountStatus to see their plan, and provides a targeted solution. If it can't resolve the issue, it calls CreateTicket to escalate. All within the same persistent conversation.

5. Agent 3: Content Generator with Queued Processing

For bulk operations like generating descriptions for 500 products, you need queued agents. This agent generates SEO-optimized content with structured output and processes items in the background.

php artisan make:agent ContentWriter --structured
<?php

namespace App\Ai\Agents;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Attributes\{Temperature, UseCheapestModel};
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;

#[Temperature(0.8)]
#[UseCheapestModel]
class ContentWriter implements Agent, HasStructuredOutput
{
    use Promptable;

    public function instructions(): string
    {
        return <<<'PROMPT'
        You are an expert copywriter and SEO specialist. Generate compelling,
        unique content for product listings. Each description should:
        - Open with a benefit-driven hook
        - Include 2-3 feature highlights
        - End with a subtle call to action
        - Be between 80-150 words for short, 200-350 words for long
        - Use natural language that includes the target keyword
        PROMPT;
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'headline' => $schema->string()->required(),
            'short_description' => $schema->string()->required(),
            'long_description' => $schema->string()->required(),
            'meta_title' => $schema->string()->required(),
            'meta_description' => $schema->string()->required(),
            'tags' => $schema->array(items: $schema->string())->required(),
        ];
    }
}

Process items individually or queue them in bulk:

use App\Ai\Agents\ContentWriter;
use App\Models\Product;
use Laravel\Ai\Responses\AgentResponse;

// Single generation (synchronous)
$result = (new ContentWriter)->prompt(
    "Write content for: {$product->name}. Category: {$product->category}. "
    . "Features: {$product->features}. Target keyword: {$product->keyword}"
);

$product->update([
    'headline' => $result['headline'],
    'short_description' => $result['short_description'],
    'long_description' => $result['long_description'],
    'meta_title' => $result['meta_title'],
    'meta_description' => $result['meta_description'],
    'tags' => $result['tags'],
]);

// Bulk generation (queued)
$products = Product::whereNull('long_description')->get();

foreach ($products as $product) {
    (new ContentWriter)
        ->queue(
            "Write content for: {$product->name}. "
            . "Category: {$product->category}. Features: {$product->features}. "
            . "Target keyword: {$product->keyword}"
        )
        ->then(function (AgentResponse $response) use ($product) {
            $product->update([
                'headline' => $response['headline'],
                'long_description' => $response['long_description'],
                'meta_title' => $response['meta_title'],
                'meta_description' => $response['meta_description'],
                'tags' => $response['tags'],
            ]);
        })
        ->catch(function (\Throwable $e) use ($product) {
            logger()->error("Content generation failed for product {$product->id}", [
                'error' => $e->getMessage(),
            ]);
        });
}

The #[UseCheapestModel] attribute selects the most cost-efficient model, which is ideal for bulk operations where you're optimizing for cost over reasoning quality.

6. Tools Deep Dive: Building Custom Tools

Tools are the bridge between the AI and your application. The agent decides when to call a tool based on the conversation, and the SDK handles the tool-calling loop automatically (up to #[MaxSteps] iterations).

Every tool needs three things: a description() the AI reads to decide when to use it, a schema() defining the input parameters, and a handle() that executes the logic:

php artisan make:tool CalculateDiscount
<?php

namespace App\Ai\Tools;

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

class CalculateDiscount implements Tool
{
    public function description(): string
    {
        return 'Calculate the final price after applying a coupon code. Returns the discount amount and final price.';
    }

    public function handle(Request $request): string
    {
        $coupon = Coupon::where('code', $request['coupon_code'])
            ->where('expires_at', '>', now())
            ->first();

        if (! $coupon) {
            return json_encode(['error' => 'Invalid or expired coupon code.']);
        }

        $originalPrice = $request['price'];
        $discount = $coupon->type === 'percentage'
            ? $originalPrice * ($coupon->value / 100)
            : $coupon->value;
        $finalPrice = max(0, $originalPrice - $discount);

        return json_encode([
            'original_price' => $originalPrice,
            'discount' => round($discount, 2),
            'final_price' => round($finalPrice, 2),
            'coupon' => $coupon->code,
        ]);
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'coupon_code' => $schema->string()->required(),
            'price' => $schema->number()->min(0)->required(),
        ];
    }
}

The SDK also provides built-in tools you can use immediately:

use Laravel\Ai\Providers\Tools\WebSearch;
use Laravel\Ai\Providers\Tools\WebFetch;
use Laravel\Ai\Tools\SimilaritySearch;

public function tools(): iterable
{
    return [
        // Search the web (with domain restrictions)
        (new WebSearch)->max(5)->allow(['laravel.com', 'php.net']),

        // Fetch and parse web pages
        (new WebFetch)->max(3)->allow(['docs.laravel.com']),

        // Vector similarity search on your Eloquent models
        SimilaritySearch::usingModel(Document::class, 'embedding'),
    ];
}

Write clear, specific tool descriptions. The agent reads them to decide which tool to call. A vague description like "do something with data" leads to incorrect tool usage. A precise description like "Look up a customer's subscription status by email address" gives the agent clear intent.

7. Agent Middleware: Logging, Rate Limiting, and Guards

Agent middleware intercepts prompts before they reach the AI and responses after they return. This is the right place for logging, cost tracking, content filtering, and access control.

php artisan make:agent-middleware TrackUsage
php artisan make:agent-middleware GuardPremiumAgents
<?php

namespace App\Ai\Middleware;

use Closure;
use Illuminate\Support\Facades\DB;
use Laravel\Ai\AgentPrompt;
use Laravel\Ai\Contracts\AgentMiddleware;
use Laravel\Ai\Responses\AgentResponse;

class TrackUsage implements AgentMiddleware
{
    public function handle(AgentPrompt $prompt, Closure $next)
    {
        $startTime = microtime(true);

        return $next($prompt)->then(function (AgentResponse $response) use ($prompt, $startTime) {
            DB::table('ai_usage')->insert([
                'user_id' => auth()->id(),
                'team_id' => currentTeam()?->id,
                'agent' => class_basename($prompt->agent),
                'input_tokens' => $response->usage->inputTokens ?? 0,
                'output_tokens' => $response->usage->outputTokens ?? 0,
                'duration_ms' => round((microtime(true) - $startTime) * 1000),
                'created_at' => now(),
            ]);
        });
    }
}
<?php

namespace App\Ai\Middleware;

use Closure;
use Laravel\Ai\AgentPrompt;
use Laravel\Ai\Contracts\AgentMiddleware;

class GuardPremiumAgents implements AgentMiddleware
{
    public function handle(AgentPrompt $prompt, Closure $next)
    {
        if (! currentTeam()?->subscribed('default')) {
            throw new \Laravel\Ai\Exceptions\AgentException(
                'AI features require an active subscription.'
            );
        }

        return $next($prompt);
    }
}

Apply middleware to agents via the HasMiddleware contract:

use Laravel\Ai\Contracts\HasMiddleware;

class SalesCoach implements Agent, HasTools, HasStructuredOutput, HasMiddleware
{
    use Promptable;

    public function middleware(): array
    {
        return [
            new GuardPremiumAgents,
            new TrackUsage,
        ];
    }

    // ...
}

8. Streaming Agent Responses

For chat interfaces, swap prompt() for stream(). The SDK handles Server-Sent Events automatically:

// Return a streaming response from any agent
Route::post('/ai/coach/stream', function (Request $request) {
    return SalesCoach::make($request->user()->id)
        ->stream("Analyze this transcript:\n\n" . $request->transcript);
});

For post-stream processing (e.g., saving the full response after streaming completes):

use Laravel\Ai\Responses\StreamedAgentResponse;

return SalesCoach::make($request->user()->id)
    ->stream("Analyze this transcript:\n\n" . $request->transcript)
    ->then(function (StreamedAgentResponse $response) {
        // Called after streaming completes
        logger()->info('Stream completed', [
            'text' => $response->text,
            'tokens' => $response->usage->outputTokens,
        ]);
    });

If you're building a React or Vue frontend, the SDK supports the Vercel AI SDK wire protocol:

return (new SupportBot)
    ->continue($conversationId, as: $request->user())
    ->stream($request->message)
    ->usingVercelDataProtocol();

For broadcasting agent responses to multiple clients via WebSockets (e.g., a team watching an AI analysis together):

use Illuminate\Broadcasting\Channel;

(new SalesCoach($userId))->broadcastOnQueue(
    "Analyze this transcript:\n\n" . $transcript,
    new Channel("team.{$teamId}.ai-coaching"),
);

9. Testing Agents Without API Costs

The SDK's ::fake() method intercepts all agent calls and returns predetermined responses. No API calls, no costs, deterministic tests:

<?php

use App\Ai\Agents\SalesCoach;
use App\Ai\Agents\SupportBot;
use App\Ai\Agents\ContentWriter;

test('sales coach returns structured analysis', function () {
    SalesCoach::fake([json_encode([
        'overall_score' => 7,
        'scores' => [
            ['category' => 'Opening', 'score' => 8, 'feedback' => 'Strong introduction.'],
            ['category' => 'Discovery', 'score' => 6, 'feedback' => 'Ask more open-ended questions.'],
        ],
        'strengths' => ['Confident tone', 'Good product knowledge'],
        'improvements' => ['Ask more discovery questions', 'Handle pricing objection better'],
        'trend' => 'improving',
    ])]);

    $result = SalesCoach::make(1)->prompt('Analyze this transcript...');

    expect($result['overall_score'])->toBe(7);
    expect($result['scores'])->toHaveCount(2);
    expect($result['trend'])->toBe('improving');

    SalesCoach::assertPrompted(fn ($prompt) =>
        str_contains($prompt->prompt, 'Analyze this transcript')
    );
});

test('support bot handles conversation flow', function () {
    SupportBot::fake([
        'I\'d be happy to help! Could you share your email so I can look up your account?',
        'I found your account. You\'re on the Pro plan. Let me search our docs for a solution.',
    ]);

    $user = User::factory()->create();

    // First message
    $response = $this->actingAs($user)
        ->postJson('/ai/support', ['message' => 'I can\'t access billing']);

    $response->assertOk()->assertJsonStructure(['reply', 'conversation_id']);

    // Follow-up
    $response = $this->actingAs($user)
        ->postJson("/ai/support/{$response->json('conversation_id')}", [
            'message' => 'My email is user@example.com',
        ]);

    $response->assertOk();
    SupportBot::assertPrompted();
});

test('content writer generates valid structured output', function () {
    ContentWriter::fake([json_encode([
        'headline' => 'Premium Analytics Dashboard',
        'short_description' => 'Track your metrics in real-time.',
        'long_description' => 'A comprehensive analytics solution...',
        'meta_title' => 'Premium Analytics Dashboard | YourSaaS',
        'meta_description' => 'Track business metrics with our analytics dashboard.',
        'tags' => ['analytics', 'dashboard', 'reporting'],
    ])]);

    $result = (new ContentWriter)->prompt('Write content for: Analytics Dashboard');

    expect($result['headline'])->toBe('Premium Analytics Dashboard');
    expect($result['tags'])->toHaveCount(3);
});

test('agents are never called without faking in tests', function () {
    SalesCoach::fake()->preventStrayPrompts();
    SupportBot::fake()->preventStrayPrompts();
    ContentWriter::fake()->preventStrayPrompts();

    // If any agent is called without a fake response, the test fails.
    // Add this to your base TestCase for safety.
});

You can also use closures for dynamic fake responses:

SalesCoach::fake(fn (AgentPrompt $prompt) =>
    str_contains($prompt->prompt, 'good transcript')
        ? json_encode(['overall_score' => 9, ...])
        : json_encode(['overall_score' => 3, ...])
);

10. Deploying Agents to Production

Before shipping AI agents to production, handle these essentials:

Provider Failover

Pass an array of providers. If the first fails, the SDK automatically tries the next:

use Laravel\Ai\Enums\Lab;

$response = SalesCoach::make($userId)->prompt(
    $transcript,
    provider: [Lab::Anthropic, Lab::OpenAI],
);

Rate Limiting by Plan

// app/Providers/AppServiceProvider.php
RateLimiter::for('ai-agents', function ($request) {
    $team = $request->user()?->currentTeam;
    $limit = match ($team?->subscription('default')?->stripe_price) {
        'price_enterprise' => 1000,
        'price_pro' => 200,
        default => 20,
    };

    return Limit::perHour($limit)->by($team?->id ?? $request->ip());
});

Queue Workers for Async Agents

Queued agents run on your standard Laravel queue. Use a dedicated queue for AI to avoid blocking other jobs:

# Supervisor config for AI queue worker
[program:ai-queue]
command=php /var/www/artisan queue:work redis --queue=ai --sleep=3 --tries=2 --max-time=3600
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/storage/logs/ai-worker.log

Monitor Costs

Use the TrackUsage middleware from section 7 to log every agent call. Build a simple dashboard query to monitor spending:

// Monthly AI cost estimate (approximate)
$usage = DB::table('ai_usage')
    ->where('created_at', '>=', now()->startOfMonth())
    ->selectRaw('SUM(input_tokens) as input, SUM(output_tokens) as output')
    ->first();

$estimatedCost = ($usage->input * 0.000003) + ($usage->output * 0.000015);

11. Conclusion

Building AI agents in Laravel with the AI SDK is a genuinely practical skill in 2026. Agents aren't experimental — they're the building block for AI-powered features that users expect: intelligent support, automated content, personalized coaching, and data analysis.

Here's what we built in this guide:

  1. Sales Coach — Tool-calling for historical data, structured scoring with validated schemas.
  2. Support Bot — RAG documentation search, account lookups, ticket escalation, persistent multi-turn conversations.
  3. Content Writer — Bulk generation with queued processing, structured output, cost-optimized model selection.

Plus the infrastructure to make them production-ready: custom tools, agent middleware for logging and access control, streaming for real-time UIs, comprehensive testing with fakes, and deployment with failover and rate limiting.

The pattern is always the same: create an agent class, define instructions, add tools for your domain, choose structured or free-text output, and call prompt(). The SDK handles the rest — tool-calling loops, provider management, conversation persistence, and response validation.

Build your SaaS foundation first, then add AI agents on top.

LaraSpeed gives you auth, billing, multi-tenancy, admin panel, API, and 100+ tests out of the box. Combine it with the Laravel AI SDK and start shipping intelligent features in your first week.

One-time purchase · Full source code · 14-day money-back guarantee