Laravel AI SDK: Tool-Calling Agents Guide | 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 and Conversation Persistence

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

 Build reliable tool-calling AI agents in Laravel using the AI SDK, with typed tool definitions, conversation history persistence, and safe retry semantics — no hand-rolled prompt hacks required.

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

ShareCopy linkCopied

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

  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)

 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.

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

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

  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.

   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.

   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.

   ![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 articleLaravel MPP: Charge AI Agents for API Access with 402 Payment Required](https://www.msaied.com/public/articles/laravel-mpp-charge-ai-agents-for-api-access-with-402-payment-required) [Next articleFilament v3 to v4: Breaking Changes, Migration Patterns, and Refactor Strategies](https://www.msaied.com/public/articles/filament-v3-to-v4-breaking-changes-migration-patterns-and-refactor-strategies)  

   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)

 ###  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)
