Laravel AI SDK: Tool-Calling Agents Guide | 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 and Conversation Persistence        On this page       1. [  Why Tool-Calling Beats Prompt Engineering Alone ](#why-tool-calling-beats-prompt-engineering-alone)
2. [  Defining Typed Tools ](#defining-typed-tools)
3. [  The Agent Loop ](#the-agent-loop)
4. [  Persisting Conversation History ](#persisting-conversation-history)
5. [  Handling Failures Gracefully ](#handling-failures-gracefully)
6. [  Key Takeaways ](#key-takeaways)

  ![Laravel AI SDK: Tool-Calling Agents and Conversation Persistence](https://cdn.msaied.com/383/aa051dd55f1a11417224f74a3b2f0e49.png)

  #laravel   #ai   #agents   #prism  

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

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

       Table of contents

1. [  01   Why Tool-Calling Beats Prompt Engineering Alone  ](#why-tool-calling-beats-prompt-engineering-alone)
2. [  02   Defining Typed Tools  ](#defining-typed-tools)
3. [  03   The Agent Loop  ](#the-agent-loop)
4. [  04   Persisting Conversation History  ](#persisting-conversation-history)
5. [  05   Handling Failures Gracefully  ](#handling-failures-gracefully)
6. [  06   Key Takeaways  ](#key-takeaways)

 Why Tool-Calling Beats Prompt Engineering Alone
-----------------------------------------------

Large language models are good at reasoning but bad at side effects. Tool-calling (function calling) gives the model a typed contract: it decides *when* to call a tool, your application decides *how*. The result is an agent loop that is auditable, testable, and safe to retry.

The [Laravel AI SDK](https://github.com/prism-php/prism) (Prism) provides a fluent interface over multiple providers. This article focuses on building a practical agent with persistent conversation history and real tool execution.

---

Defining Typed Tools
--------------------

A tool is a plain PHP class implementing `EchoedLabs\Prism\Contracts\Tool`. You declare its name, description, and parameter schema — the SDK serialises this into the provider's function-calling format automatically.

```php
use EchoedLabs\Prism\Contracts\Tool;
use EchoedLabs\Prism\Schema\StringSchema;
use EchoedLabs\Prism\Schema\ObjectSchema;

class GetOrderStatusTool implements Tool
{
    public function name(): string
    {
        return 'get_order_status';
    }

    public function description(): string
    {
        return 'Returns the current fulfilment status for an order ID.';
    }

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

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

```

Keep `handle()` side-effect-free where possible. If it must write, wrap it in a database transaction and return a deterministic result so retries are safe.

---

The Agent Loop
--------------

Prism handles the loop for you, but understanding it matters for debugging:

1. Send messages + tool definitions to the provider.
2. If the response contains a `tool_call`, execute the matching tool.
3. Append the tool result as a `tool` role message.
4. Repeat until the model returns a plain text response.

```php
use EchoedLabs\Prism\Prism;
use EchoedLabs\Prism\Enums\Provider;
use EchoedLabs\Prism\ValueObjects\Messages\UserMessage;

$response = Prism::text()
    ->using(Provider::Anthropic, 'claude-3-5-sonnet-20241022')
    ->withMaxSteps(5)                    // hard cap on loop iterations
    ->withTools([new GetOrderStatusTool])
    ->withMessages($this->buildHistory($conversationId))
    ->withPrompt('What is the status of order 018f2a3b-...')
    ->generate();

```

`withMaxSteps` is your circuit breaker. Without it, a confused model can loop indefinitely.

---

Persisting Conversation History
-------------------------------

Stateless HTTP means you must serialise and restore the message thread on every request. Store messages in a `conversation_messages` table:

```php
Schema::create('conversation_messages', function (Blueprint $table) {
    $table->id();
    $table->ulid('conversation_id')->index();
    $table->string('role');          // user | assistant | tool
    $table->text('content');
    $table->json('tool_calls')->nullable();
    $table->string('tool_call_id')->nullable();
    $table->timestamps();
});

```

Rehydrate with a repository:

```php
class ConversationRepository
{
    public function messages(string $conversationId): array
    {
        return ConversationMessage::where('conversation_id', $conversationId)
            ->orderBy('id')
            ->get()
            ->map(fn ($row) => MessageFactory::fromRow($row))
            ->all();
    }

    public function append(string $conversationId, array $messages): void
    {
        foreach ($messages as $message) {
            ConversationMessage::create([
                'conversation_id' => $conversationId,
                'role'            => $message->role->value,
                'content'         => $message->content,
                'tool_calls'      => $message->toolCalls ?? null,
                'tool_call_id'    => $message->toolCallId ?? null,
            ]);
        }
    }
}

```

After `generate()`, persist `$response->messages` — Prism returns the full updated thread including intermediate tool messages.

---

Handling Failures Gracefully
----------------------------

Tool execution can fail. Return a structured error string rather than throwing — the model can reason about it and ask the user for clarification:

```php
public function handle(string $order_id): string
{
    try {
        $order = Order::findOrFail($order_id);
        return json_encode(['status' => $order->status->label()]);
    } catch (ModelNotFoundException) {
        return json_encode(['error' => "Order {$order_id} not found."]);
    }
}

```

This keeps the agent loop alive and produces a user-friendly response instead of a 500.

---

Key Takeaways
-------------

- **Typed tool classes** give you IDE support, unit-testability, and a clean separation between AI reasoning and application logic.
- **`withMaxSteps`** is non-negotiable in production — always cap the loop.
- **Persist the full message thread** including tool call/result pairs; partial history breaks the model's context.
- **Return errors as JSON strings** from tools rather than throwing exceptions to keep the agent loop alive.
- **Idempotent tool handlers** make retries safe and simplify observability.

 Found this useful?

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

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

  3 questions  

     Q01  Can I use the Laravel AI SDK (Prism) with OpenAI and Anthropic interchangeably?        Yes. Prism abstracts provider differences behind a unified interface. Swap `Provider::Anthropic` for `Provider::OpenAI` and the tool-calling serialisation is handled automatically, though model names differ per provider. 

      Q02  How do I prevent the agent from calling tools in an infinite loop?        Pass `withMaxSteps(n)` to the Prism builder. This hard-caps the number of tool-call/response iterations. A value of 5–10 covers most real-world tasks while protecting against runaway loops. 

      Q03  Should tool handlers be synchronous or can they dispatch jobs?        Keep tool handlers synchronous. The model is waiting for a result to continue reasoning. If the underlying work is slow, optimise the query or cache the result — dispatching a job and returning a pending status usually confuses the model's next step. 

  Continue reading

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

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

 [ ![HTTP Query Method Support in Laravel 13.19](https://cdn.msaied.com/394/4c917aad559beddb5eeb9a7a1e107f4c.png) Laravel Laravel 13 HTTP Client 

### HTTP Query Method Support in Laravel 13.19

Laravel 13.19 adds Http::query() for the HTTP QUERY verb, query/queryJson testing helpers, a reduceInto() coll...

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

 8 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/http-query-method-support-in-laravel-1319) [ ![Blackfire & Xdebug Profiling in Laravel: Finding Real Bottlenecks in Production-Like Environments](https://cdn.msaied.com/393/a8dfc4ec52c4febc3ef41a491eb452ea.png) laravel performance profiling 

### Blackfire &amp; Xdebug Profiling in Laravel: Finding Real Bottlenecks in Production-Like Environments

Stop guessing where your Laravel app is slow. This guide walks through practical Blackfire and Xdebug profilin...

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

 8 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/blackfire-xdebug-profiling-in-laravel-finding-real-bottlenecks-in-production-like-environments) [ ![Laravel Cloud Security Defaults Behind Every Deploy](https://cdn.msaied.com/395/28df8f542fbe81ecee3ba4ad897f36d6.png) Laravel Cloud Security PHP 

### Laravel Cloud Security Defaults Behind Every Deploy

Laravel Cloud ships WAF rules, DDoS mitigation, automatic PHP patching, SOC 2 Type II compliance, and deploy-t...

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

 8 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/laravel-cloud-security-defaults-behind-every-deploy) 

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