Laravel AI Agents: Tool-Calling &amp; Conversation Persistence | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Laravel AI SDK: Tool-Calling Agents with Structured Output and Conversation Persistence        On this page       1. [  Building Tool-Calling AI Agents in Laravel ](#building-tool-calling-ai-agents-in-laravel)
2. [  1. Registering Tools the Right Way ](#1-registering-tools-the-right-way)
3. [  2. Enforcing Structured Output Contracts ](#2-enforcing-structured-output-contracts)
4. [  3. Persisting Conversation History ](#3-persisting-conversation-history)
5. [  4. Dispatching Agent Turns as Jobs ](#4-dispatching-agent-turns-as-jobs)
6. [  Takeaways ](#takeaways)

  ![Laravel AI SDK: Tool-Calling Agents with Structured Output and Conversation Persistence](https://cdn.msaied.com/197/625357a5c28aa7407da0eb0d985ed4d2.png)

  #laravel   #ai   #agents   #prism   #llm  

 Laravel AI SDK: Tool-Calling Agents with Structured Output and Conversation Persistence 
=========================================================================================

     15 Jun 2026      4 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Building Tool-Calling AI Agents in Laravel  ](#building-tool-calling-ai-agents-in-laravel)
2. [  02   1. Registering Tools the Right Way  ](#1-registering-tools-the-right-way)
3. [  03   2. Enforcing Structured Output Contracts  ](#2-enforcing-structured-output-contracts)
4. [  04   3. Persisting Conversation History  ](#3-persisting-conversation-history)
5. [  05   4. Dispatching Agent Turns as Jobs  ](#4-dispatching-agent-turns-as-jobs)
6. [  06   Takeaways  ](#takeaways)

 Building Tool-Calling AI Agents in Laravel
------------------------------------------

Most Laravel AI tutorials stop at a single `chat()` call. Production agents are different: they need to call your application's own services as tools, enforce typed output contracts, and remember what happened in previous turns — all without leaking state between users.

This article uses [Prism](https://prism.echolabs.dev), the first-class Laravel AI SDK, to wire all three concerns together cleanly.

---

### 1. Registering Tools the Right Way

Prism tools are plain PHP objects that implement `EchoLabs\Prism\Contracts\Tool`. Keep one tool per class and resolve dependencies through the service container.

```php
// app/AiTools/LookupOrderTool.php
use EchoLabs\Prism\Tool;
use EchoLabs\Prism\Schema\StringSchema;
use EchoLabs\Prism\Schema\ObjectSchema;

class LookupOrderTool extends Tool
{
    public function __construct(private OrderRepository $orders) {}

    public function name(): string { return 'lookup_order'; }

    public function description(): string
    {
        return 'Fetch order status and line items for a given order ID.';
    }

    public function parameters(): ObjectSchema
    {
        return new ObjectSchema(
            properties: [new StringSchema('order_id', 'The UUID of the order')],
            required: ['order_id'],
        );
    }

    public function handle(string $order_id): string
    {
        $order = $this->orders->findOrFail($order_id);
        return json_encode([
            'status' => $order->status,
            'total'  => $order->total_cents,
            'items'  => $order->lines->pluck('sku'),
        ]);
    }
}

```

Bind it in a service provider so Prism can resolve it:

```php
$this->app->bind(LookupOrderTool::class, fn ($app) =>
    new LookupOrderTool($app->make(OrderRepository::class))
);

```

---

### 2. Enforcing Structured Output Contracts

Free-form LLM text is a liability. Use Prism's `withSchema()` to force the model into a typed response shape and validate it immediately.

```php
use EchoLabs\Prism\Prism;
use EchoLabs\Prism\Schema\ObjectSchema;
use EchoLabs\Prism\Schema\StringSchema;
use EchoLabs\Prism\Schema\EnumSchema;

$schema = new ObjectSchema(
    properties: [
        new EnumSchema('intent', 'User intent', ['order_status', 'refund', 'other']),
        new StringSchema('order_id', 'Extracted order UUID, or empty string'),
    ],
    required: ['intent', 'order_id'],
);

$response = Prism::text()
    ->using('openai', 'gpt-4o-mini')
    ->withSchema($schema)
    ->withPrompt('Classify this message: "Where is order abc-123?"')
    ->generate();

$data = json_decode($response->text, associative: true);
// $data['intent'] === 'order_status'
// $data['order_id'] === 'abc-123'

```

This gives you a PHP array you can pass directly into a DTO or action without regex hacks.

---

### 3. Persisting Conversation History

Multi-turn agents need history. Store it in your database, not in a session or cache, so it survives queue workers and horizontal scaling.

```php
// migrations: ai_conversations (id, user_id, messages JSON, created_at, updated_at)

class ConversationRepository
{
    public function loadMessages(int $userId): array
    {
        return AiConversation::firstOrCreate(['user_id' => $userId])
            ->messages ?? [];
    }

    public function appendMessages(int $userId, array $newMessages): void
    {
        AiConversation::updateOrCreate(
            ['user_id' => $userId],
            ['messages' => array_merge($this->loadMessages($userId), $newMessages)]
        );
    }
}

```

Then feed history back into Prism on every turn:

```php
$history = $repo->loadMessages($userId);

$response = Prism::text()
    ->using('openai', 'gpt-4o-mini')
    ->withMessages($history)  // prior turns
    ->withTools([app(LookupOrderTool::class)])
    ->withMaxSteps(5)         // cap tool-call loops
    ->withPrompt($userMessage)
    ->generate();

$repo->appendMessages($userId, [
    ['role' => 'user',      'content' => $userMessage],
    ['role' => 'assistant', 'content' => $response->text],
]);

```

`withMaxSteps()` is critical — it prevents runaway tool-call loops from burning tokens when the model gets confused.

---

### 4. Dispatching Agent Turns as Jobs

For non-interactive flows (webhooks, scheduled summaries), dispatch each agent turn as a queued job and write results back to the database. This decouples the LLM latency from your HTTP response time entirely.

```php
class RunAgentTurnJob implements ShouldQueue
{
    use Dispatchable, Queueable;

    public function __construct(
        public readonly int $userId,
        public readonly string $message,
    ) {}

    public function handle(ConversationRepository $repo): void
    {
        // same Prism call as above
        // write $response->text back to a results table
    }
}

```

---

### Takeaways

- **One tool, one class** — keep tools small, injected via the container, and independently testable.
- **Schema-first output** — never parse free-form LLM text; enforce a JSON schema and validate immediately.
- **Database-backed history** — sessions and caches are wrong for conversation state; a proper table survives restarts and scales horizontally.
- **Cap tool-call steps** — `withMaxSteps()` is a hard budget, not optional.
- **Queue agent turns** — decouple LLM latency from HTTP with jobs; poll or broadcast results back to the UI.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-ai-sdk-tool-calling-agents-with-structured-output-and-conversation-persistence&text=Laravel+AI+SDK%3A+Tool-Calling+Agents+with+Structured+Output+and+Conversation+Persistence) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-ai-sdk-tool-calling-agents-with-structured-output-and-conversation-persistence) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Why store conversation history in the database instead of the cache?        Cache entries can be evicted under memory pressure and are not shared reliably across queue workers or multiple web servers. A database row gives you durability, queryability, and a single source of truth for every process that needs the history. 

      Q02  How do I prevent the agent from calling tools in an infinite loop?        Pass `withMaxSteps(n)` to your Prism chain. Prism will stop after n tool-call/response cycles regardless of what the model requests, protecting you from runaway token consumption. 

      Q03  Can I test tool-calling agents without hitting the OpenAI API?        Yes. Prism ships a fake driver you can swap in during tests. You can assert which tools were called, with what arguments, and return deterministic fixture responses — no network required. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![Pest 5 Released: Test Impact Analysis, Agent Verification, and Evals](https://cdn.msaied.com/493/a1d6434093b945e8a3291eb09c09e768.png) Pest PHP Testing PHP 8.4 

### Pest 5 Released: Test Impact Analysis, Agent Verification, and Evals

Pest v5 lands with the TIA engine that cut Laravel Cloud's 19,000-test suite from 3 minutes to 5 seconds, plus...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 31 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/pest-5-released-test-impact-analysis-agent-verification-and-evals) [ ![Job Batching, Chaining, and Rate-Limited Middleware in Laravel Queues](https://cdn.msaied.com/489/89d47dc6b618d5435f9d7f333b75e922.png) laravel queues jobs 

### Job Batching, Chaining, and Rate-Limited Middleware in Laravel Queues

Go beyond basic dispatch: learn how to compose Laravel job batches with callbacks, chain dependent jobs safely...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 30 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/job-batching-chaining-and-rate-limited-middleware-in-laravel-queues-3) [ ![Laravel Observers vs. Model Events: Choosing the Right Hook for Domain Side-Effects](https://cdn.msaied.com/488/451564d0a43aa33809619fa299027222.png) laravel eloquent domain-events 

### Laravel Observers vs. Model Events: Choosing the Right Hook for Domain Side-Effects

Model events and observers look similar but behave differently under bulk operations, transactions, and test i...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 30 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/laravel-observers-vs-model-events-choosing-the-right-hook-for-domain-side-effects) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
