Laravel AI Agents: Tool-Calling &amp; Conversation Persistence | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Laravel AI SDK: Tool-Calling Agents with Structured Output and Conversation Persistence

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

 Build reliable tool-calling AI agents in Laravel using the Prism package — covering structured output contracts, tool registration, and persisting conversation history across HTTP requests.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 15 Jun 2026 · Updated 15 Jun 2026 · 4 min read

ShareCopy linkCopied

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

  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)

 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.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [ai](https://www.msaied.com/public/articles?search=ai)
- [agents](https://www.msaied.com/public/articles?search=agents)
- [prism](https://www.msaied.com/public/articles?search=prism)
- [llm](https://www.msaied.com/public/articles?search=llm)

 Frequently asked questions 
---------------------------

  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.

   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.

   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.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

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

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleLivewire v3 Internals: Morph Markers, JS Hooks, and Alpine Integration](https://www.msaied.com/public/articles/livewire-v3-internals-morph-markers-js-hooks-and-alpine-integration) [Next articleFilament v3 to v4 Migration: Breaking Changes and Practical Refactor Patterns](https://www.msaied.com/public/articles/filament-v3-to-v4-migration-breaking-changes-and-practical-refactor-patterns)  

   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)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
