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

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

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

  [ home ](https://www.msaied.com)    [ articles ](https://www.msaied.com/articles)    Laravel AI SDK: Tool-Calling Agents with Conversation Persistence        On this page       1. [  Building Tool-Calling AI Agents in Laravel ](#building-tool-calling-ai-agents-in-laravel)
2. [  Persisting Conversation History ](#persisting-conversation-history)
3. [  Registering Tools ](#registering-tools)
4. [  The Agent Loop ](#the-agent-loop)
5. [  Enforcing Structured Output ](#enforcing-structured-output)
6. [  Key Takeaways ](#key-takeaways)

  ![Laravel AI SDK: Tool-Calling Agents with Conversation Persistence](https://cdn.msaied.com/465/a3f3acc02afd0ab7084573cab1424c97.png)

  #laravel   #ai   #agents   #llm  

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

     24 Jul 2026      3 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   Persisting Conversation History  ](#persisting-conversation-history)
3. [  03   Registering Tools  ](#registering-tools)
4. [  04   The Agent Loop  ](#the-agent-loop)
5. [  05   Enforcing Structured Output  ](#enforcing-structured-output)
6. [  06   Key Takeaways  ](#key-takeaways)

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

The Laravel AI SDK (`prism-php/prism`, or the first-party `laravel/ai` package landing in Laravel 13) gives you a clean abstraction over LLM providers. The interesting engineering challenge is not the API call itself — it is making agents *reliable*: persisting conversation turns, enforcing structured output, and keeping tool execution auditable.

This article focuses on those three concerns with concrete, production-ready patterns.

---

### Persisting Conversation History

Every multi-turn agent needs a conversation store. A simple `agent_conversations` table works well:

```php
Schema::create('agent_conversations', function (Blueprint $table) {
    $table->ulid('id')->primary();
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->string('agent');
    $table->json('messages')->default('[]');
    $table->timestamps();
});

```

The `messages` column stores the raw message array that the LLM expects, so replaying or resuming a conversation is trivial.

```php
final class ConversationRepository
{
    public function append(AgentConversation $conv, array $newMessages): void
    {
        $conv->messages = array_merge($conv->messages, $newMessages);
        $conv->save();
    }

    public function forUser(int $userId, string $agent): AgentConversation
    {
        return AgentConversation::firstOrCreate(
            ['user_id' => $userId, 'agent' => $agent],
            ['messages' => []]
        );
    }
}

```

---

### Registering Tools

Tools are plain PHP callables with a schema. Keep each tool in its own class so it can be unit-tested independently.

```php
final class GetOrderStatusTool
{
    public string $name = 'get_order_status';
    public string $description = 'Returns the current status of an order by ID.';

    public function parameters(): array
    {
        return [
            'order_id' => ['type' => 'string', 'description' => 'The order UUID'],
        ];
    }

    public function __invoke(string $order_id): string
    {
        $order = Order::findOrFail($order_id);
        return json_encode([
            'status'      => $order->status->value,
            'updated_at'  => $order->updated_at->toIso8601String(),
        ]);
    }
}

```

Bind all tools through the service container so the agent class stays slim:

```php
$this->app->tag([
    GetOrderStatusTool::class,
    CancelOrderTool::class,
], 'agent.tools.order');

```

---

### The Agent Loop

A tool-calling agent runs in a loop until the model stops requesting tools or a step limit is reached.

```php
final class OrderSupportAgent
{
    public function __construct(
        private readonly ConversationRepository $repo,
        private readonly PrismClient $prism,
        private readonly iterable $tools,
    ) {}

    public function handle(int $userId, string $userMessage): string
    {
        $conv = $this->repo->forUser($userId, 'order-support');

        $this->repo->append($conv, [['role' => 'user', 'content' => $userMessage]]);

        $steps = 0;
        do {
            $response = $this->prism->chat(
                model: 'gpt-4o-mini',
                messages: $conv->messages,
                tools: $this->tools,
            );

            $this->repo->append($conv, $response->newMessages());

            if ($response->finishReason() === 'tool_calls') {
                $toolResults = $this->executeTools($response->toolCalls());
                $this->repo->append($conv, $toolResults);
            }

            $steps++;
        } while ($response->finishReason() === 'tool_calls' && $steps < 5);

        return $response->text();
    }

    private function executeTools(array $calls): array { /* ... */ }
}

```

The `$steps < 5` guard prevents runaway loops — a must in production.

---

### Enforcing Structured Output

When you need machine-readable responses (not just prose), use a typed DTO and validate the model output against it:

```php
final readonly class RefundDecision
{
    public function __construct(
        public bool   $approved,
        public string $reason,
        public ?float $amount,
    ) {}

    public static function fromArray(array $data): self
    {
        return new self(
            approved: (bool) ($data['approved'] ?? false),
            reason:   (string) ($data['reason'] ?? ''),
            amount:   isset($data['amount']) ? (float) $data['amount'] : null,
        );
    }
}

```

Request JSON mode from the provider and decode into the DTO immediately. If decoding fails, throw a domain exception — never let a malformed LLM response silently corrupt downstream state.

---

### Key Takeaways

- **Persist messages as a JSON column** keyed by user + agent; replay is free.
- **One tool = one class**: testable, taggable, and swappable via the container.
- **Always cap the agent loop** with a step limit to prevent infinite tool-call cycles.
- **Decode LLM output into typed DTOs** immediately; validate at the boundary.
- **Log every tool invocation** with its input/output for debugging and auditing.

 Found this useful?

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

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

  3 questions  

     Q01  Why store messages as JSON rather than individual rows?        LLM APIs expect the full message array on every request. A single JSON column lets you load and append the history in one query without reassembling rows, which keeps the hot path simple and fast. 

      Q02  How do you prevent a tool-calling agent from looping indefinitely?        Track the number of tool-call iterations and break the loop once a configurable maximum (e.g. 5) is reached. Return a graceful fallback message to the user rather than throwing an exception, so the conversation remains usable. 

      Q03  Can I unit-test individual tools without hitting the LLM?        Yes. Because each tool is a plain invokable class, you can instantiate it directly in a Pest test, pass mock arguments, and assert the returned JSON string — no HTTP call required. 

  Continue reading

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

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

 [ ![Filament v4 Migrating from v3: Breaking Changes and Refactor Patterns](https://cdn.msaied.com/472/bb7fbf8441c775fcfadd23850e1756e8.png) filament laravel migration 

### Filament v4 Migrating from v3: Breaking Changes and Refactor Patterns

A practical, opinionated guide to the most impactful Filament v3→v4 breaking changes: schema-based forms, the...

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

 26 Jul 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/filament-v4-migrating-from-v3-breaking-changes-and-refactor-patterns) [ ![Concurrency in Laravel: Process Pools, Fibers, and the Concurrency Facade](https://cdn.msaied.com/471/ba1a628c196e92cbb81033c865c9ad26.png) laravel concurrency php 

### Concurrency in Laravel: Process Pools, Fibers, and the Concurrency Facade

Laravel's Concurrency facade and process pools let you run independent work in parallel without reaching for a...

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

 26 Jul 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/concurrency-in-laravel-process-pools-fibers-and-the-concurrency-facade) [ ![Laravel Queues in Production: Horizon Tuning, Metrics, and Graceful Scaling](https://cdn.msaied.com/470/afd2bee56d15842d72c1c6d5c238d236.png) laravel horizon queues 

### Laravel Queues in Production: Horizon Tuning, Metrics, and Graceful Scaling

Go beyond basic queue setup. Learn how to tune Horizon supervisor processes, interpret queue metrics, handle b...

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

 26 Jul 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-queues-in-production-horizon-tuning-metrics-and-graceful-scaling) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.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://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.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)
