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) 

 [ ![Laravel Eloquent Global Scopes: Pitfalls, Testing, and Composing Them Safely](https://cdn.msaied.com/211/8b9b19e7ecbf690b182ffbe6bffc9530.png) laravel eloquent testing 

### Laravel Eloquent Global Scopes: Pitfalls, Testing, and Composing Them Safely

Global scopes are powerful but easy to misuse. Learn how to write, test, and safely compose Eloquent global sc...

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

 16 Jun 2026     1 min read  

  Read    

 ](https://msaied.com/articles/laravel-eloquent-global-scopes-pitfalls-testing-and-composing-them-safely) [ ![Eloquent Custom Relations: Polymorphic Pivots, HasManyThrough Tricks, and Raw Join Relations](https://cdn.msaied.com/210/b47272214946c6adcd02ddf74b7df816.png) laravel eloquent database 

### Eloquent Custom Relations: Polymorphic Pivots, HasManyThrough Tricks, and Raw Join Relations

Beyond belongsTo and hasMany lies a set of underused Eloquent relation techniques. This guide covers custom re...

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

 16 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/eloquent-custom-relations-polymorphic-pivots-hasmanythrough-tricks-and-raw-join-relations) [ ![New in Laravel 12: Features, Helpers, and Upgrade Notes](https://cdn.msaied.com/209/c713447686bc1eb0a921b4027e4e4df8.png) laravel php upgrade 

### New in Laravel 12: Features, Helpers, and Upgrade Notes

Laravel 12 ships with a refined starter kit system, per-request context propagation, and several quality-of-li...

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

 16 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/new-in-laravel-12-features-helpers-and-upgrade-notes) 

   [  ![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)
