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 and Conversation Persistence

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

 Build reliable tool-calling AI agents in Laravel using the Prism package. Learn how to wire tools, persist conversation history, and keep agents stateless between HTTP requests without losing context.

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

ShareCopy linkCopied

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

  On this page +1. [Building Tool-Calling Agents in Laravel with Prism](#building-tool-calling-agents-in-laravel-with-prism)
2. [Defining a Tool](#defining-a-tool)
3. [Running a Multi-Turn Agent Loop](#running-a-multi-turn-agent-loop)
4. [Persisting Conversation History](#persisting-conversation-history)
5. [Authorising Tool Execution](#authorising-tool-execution)
6. [Key Takeaways](#key-takeaways)

 Building Tool-Calling Agents in Laravel with Prism
--------------------------------------------------

Large language models become genuinely useful when they can *act* — querying a database, calling an API, or reading a file — rather than just generating text. The [Prism](https://prismphp.com) package gives Laravel developers a clean, driver-agnostic interface for exactly this. This article focuses on two hard problems: **wiring tools safely** and **persisting conversation state between HTTP requests** without leaking memory or context.

---

### Defining a Tool

Prism tools are plain PHP objects that declare their schema and a handler closure. Keep them thin — delegate real work to your existing service layer.

```php
use EchoLabs\Prism\Tool;

$orderLookup = Tool::as('get_order')
    ->for('Fetch an order by its ID')
    ->withStringParameter('order_id', 'The UUID of the order')
    ->using(function (string $order_id): string {
        $order = Order::findOrFail($order_id);
        return json_encode([
            'status'  => $order->status,
            'total'   => $order->total_cents,
            'shipped' => $order->shipped_at?->toIso8601String(),
        ]);
    });

```

The `using` closure **must return a string** — that string is injected back into the model's context as the tool result. Returning structured JSON is idiomatic.

---

### Running a Multi-Turn Agent Loop

A single `generate()` call is not enough for agents. You need an agentic loop that keeps running until the model stops requesting tools.

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

$messages = [
    new UserMessage('What is the status of order 550e8400-e29b-41d4-a716-446655440000?'),
];

$response = Prism::text()
    ->using(Provider::OpenAI, 'gpt-4o')
    ->withMessages($messages)
    ->withTools([$orderLookup])
    ->withMaxSteps(5)   // hard cap — never let the loop run unbounded
    ->generate();

echo $response->text;

```

`withMaxSteps` is your circuit breaker. Without it, a confused model can spin indefinitely, burning tokens and time.

---

### Persisting Conversation History

HTTP is stateless; agents are not. The naive approach — storing the full message array in the session — breaks under load and leaks data across users. A better pattern: persist messages to a `conversations` table and rehydrate on each request.

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

```

```php
// Rehydrating messages for Prism
use EchoLabs\Prism\ValueObjects\Messages\AssistantMessage;
use EchoLabs\Prism\ValueObjects\Messages\UserMessage;

$stored = ConversationMessage::where('conversation_id', $id)
    ->orderBy('id')
    ->get();

$messages = $stored->map(fn ($row) => match ($row->role) {
    'user'      => new UserMessage($row->content),
    'assistant' => new AssistantMessage($row->content),
    default     => null,
})->filter()->values()->all();

```

After each agent run, persist the new messages returned in `$response->messages` back to the table. This keeps your PHP process stateless while the conversation lives safely in Postgres.

---

### Authorising Tool Execution

Tools run server-side with your application's full privileges. Always scope them to the authenticated user:

```php
->using(function (string $order_id) use ($user): string {
    $order = Order::where('user_id', $user->id)
        ->findOrFail($order_id); // 404 if not owned
    // ...
})

```

Never trust the model to enforce ownership — it will not.

---

### Key Takeaways

- **Tools return strings.** Encode complex data as JSON; the model reads it as context.
- **Always set `withMaxSteps`.** Unbounded agentic loops are a production incident waiting to happen.
- **Persist messages in the database**, not the session. Rehydrate per request for true statelessness.
- **Authorise inside the tool closure**, not outside it. The model controls which tool is called; you control what it can see.
- **Keep tools thin.** Delegate to services so tools remain testable in isolation without mocking the LLM.

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

  Can I use providers other than OpenAI with Prism tool-calling?Yes. Prism supports Anthropic, Gemini, Ollama, and others via its driver system. Tool-calling availability depends on the underlying model's capabilities — check the provider's documentation for function-calling support before switching drivers.

   How do I test a tool-calling agent without hitting the real API?Prism ships with a fake driver you can bind in tests. Use `Prism::fake()` to return pre-scripted responses, then assert that your tool closures were invoked with the expected arguments using standard Pest expectations on the side-effects they produce.

   What is a safe value for withMaxSteps in production?It depends on your use case, but 5–10 steps covers the vast majority of real workflows. Set it conservatively, monitor average steps per conversation in your logs, and raise it only when you have evidence a legitimate task requires more.

   ![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 Livewire v3 Internals: Morph Markers, JS Hooks, and Alpine Integration](https://www.msaied.com/public/articles/laravel-livewire-v3-internals-morph-markers-js-hooks-and-alpine-integration) [Next articleFilament v3 to v4: Breaking Changes and Practical Refactor Patterns](https://www.msaied.com/public/articles/filament-v3-to-v4-breaking-changes-and-practical-refactor-patterns)  

   On this page
-------------

1. [Building Tool-Calling Agents in Laravel with Prism](#building-tool-calling-agents-in-laravel-with-prism)
2. [Defining a Tool](#defining-a-tool)
3. [Running a Multi-Turn Agent Loop](#running-a-multi-turn-agent-loop)
4. [Persisting Conversation History](#persisting-conversation-history)
5. [Authorising Tool Execution](#authorising-tool-execution)
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)
