Search by

omarseyam / laravel-chatbot

OmarSeyam

Conversation-persisted AI chat scaffolding for Laravel, built on the Laravel AI SDK.

Package info

github.com/OmarSeyam/laravel-chatbot

Homepage

Issues

pkg:composer/omarseyam/laravel-chatbot

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

v1.0.0 2026-08-13 09:44 UTC

This package is auto-updated.

Last update: 2026-08-13 09:48:02 UTC


README

Conversation-persisted AI chat scaffolding for Laravel, built on top of the Laravel AI SDK. It gives you a ChatService you can call from a controller or job, stores every conversation and message in your database, and ships a ready-to-use chat drawer UI you can drop into any page.

Requirements

  • PHP 8.3+
  • Laravel 13.x
  • laravel/ai — install it yourself with composer require laravel/ai, publish and run its migrations, and configure at least one provider. It isn't bundled automatically, and this package builds directly on the tables it creates (see "Database setup" below).

Installation

composer require omarseyam/laravel-chatbot
composer require laravel/ai
php artisan chatbot:install

chatbot:install publishes config/agents.php, publishes this package's migrations to database/migrations, and copies:

  • the chat source files into app/Ai/...
  • the two models into app/Models/...
  • the chat UI (a Blade component + its JS/CSS) into app/View/Components/Chat and resources/...

These become part of your application once copied/published, so edit them freely — running the command again won't overwrite anything that already exists unless you pass --force.

Database setup

This package does not create its own tables. laravel/ai already creates agent_conversations and agent_conversation_messages (that's what backs its RemembersConversations trait), and this package's models use those same tables — so laravel/ai's migrations have to run first:

php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
php artisan migrate

This package's own migrations are published by chatbot:install into database/migrations (so they're visible and editable, not hidden in vendor/), and only add the extra columns AgentConversation/AgentConversationMessage need on top of those two tables — agent, structured, tool_calls, tool_results, usage, meta, and a user_id foreign key on both tables. They're dated far in the future so they always sort — and run — after laravel/ai's migration, whatever timestamp that one happens to get published with. Each column is guarded with hasColumn(): added if it's missing, or adjusted with ->change() to the shape this package expects if a column by that name already exists — so running php artisan migrate again is safe.

Configuration

Register your agents in config/agents.php by mapping an alias to an Agent class:

return [
    'assistant' => \App\Ai\Agents\ExampleAgent::class,
];

The alias is what you pass as agent on a ChatRequest.

Usage

use App\Ai\Data\ChatRequest;
use App\Ai\Services\ChatService;

$response = app(ChatService::class)->ask(new ChatRequest(
    prompt: 'What can you help me with?',
    conversationId: null, // pass an existing conversation id to continue it
    agent: 'assistant',
));

// $response = ['conversation_id' => '...', 'content' => '...' | [...]]

content is a plain string for a normal text reply, or an array if the agent implements HasStructuredOutput. MessageService records which case each stored message is in a structured column, so anything reading history back — the chat UI, an agent's messages() — knows whether to json_decode() it.

Conversations and messages are scoped to the authenticated user (Auth::id()), so a conversationId can only be resumed by the user who started it.

Preparing an agent to receive conversation history

For an agent to actually see prior turns of a conversation (rather than only the latest prompt), it needs to accept a $messages array and implement Conversational. AgentManager::resolve() already passes the mapped history in for you when one is available — the agent just needs to be shaped to accept it:

<?php

namespace App\Ai\Agents;

use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Messages\Message;
use Laravel\Ai\Promptable;
use Stringable;

class ExampleAgent implements Agent, Conversational, HasTools, HasStructuredOutput
{
    use Promptable;

    public function __construct(public ?array $messages = null) {}

    /**
     * Get the instructions that the agent should follow.
     */
    public function instructions(): Stringable|string
    {
        return "Your role is ...";
    }

    /**
     * Get the list of messages comprising the conversation so far.
     *
     * @return Message[]
     */
    public function messages(): iterable
    {
        return $this->messages ?? [];
    }
}

A copy of this lives at app/Ai/Agents/ExampleAgent.php after running chatbot:install — rename it, register it in config/agents.php, and fill in instructions() (plus tools()/schema() if you keep the HasTools/ HasStructuredOutput interfaces).

Fetching a conversation's history directly

MessageService::history() returns messages already mapped into Laravel\Ai\Messages\Message objects, ready to hand to an agent. If you just need the raw rows — e.g. to render a chat thread in a view — query the model directly:

AgentConversationMessage::query()
    ->where('conversation_id', $conversationId)
    ->oldest()
    ->select('role', 'content', 'structured')
    ->get();

The included AIChat Blade component (see "UI" below) already does exactly this.

Models and linking your own models to a conversation

  • App\Models\AgentConversation — one row per conversation, with a messages() relation and a title generated from the first prompt.
  • App\Models\AgentConversationMessage — one row per message, storing role, content, structured, agent, usage, meta, attachments, tool_calls, and tool_results.

AgentConversation ships with a post() relation to a Post model as an example only — it isn't something the package requires. To link your own model(s) back to the conversation that produced them (and pull the full message history behind them), add a conversation_id foreign key column to that model's own migration, then define the matching relations:

// On your model:
public function conversation(): BelongsTo
{
    return $this->belongsTo(AgentConversation::class, 'conversation_id');
}

// On AgentConversation, replacing the example post() relation:
public function yourModel(): HasOne // or HasMany
{
    return $this->hasOne(YourModel::class, 'conversation_id');
}

Pruning old conversations

AgentConversation uses Laravel's Prunable trait to clean up conversations older than an hour, skipping any tied to the example post() relation above (adjust whereDoesntHave('post') in prunable() to match whatever relation you replace it with, or drop it entirely if nothing needs protecting). Deleting a conversation — by hand or via pruning — also deletes its messages, so nothing is left orphaned. Schedule pruning in routes/console.php:

use Illuminate\Support\Facades\Schedule;

Schedule::command('model:prune')->daily();

UI

chatbot:install copies a ready-made chat drawer: a floating button that opens a panel with message history, a text input, and typing indicator.

  • app/View/Components/Chat/AIChat.php — loads a conversation's messages and passes them to the view.
  • resources/views/components/chat/a-i-chat.blade.php — the drawer markup.
  • resources/js/ai/*.js — the frontend logic (sending messages, rendering markdown with marked, syntax highlighting with highlight.js, sanitizing with dompurify).

Drop it into any page (a class-based component under App\View\Components\Chat resolves to the <x-chat.a-i-chat> tag):

<x-chat.a-i-chat :conversation-id="$conversationId ?? null" />

A few things need wiring up by hand — none of these are copied automatically, since they touch files this package can't safely overwrite:

  1. CSRF token. The chat JS sends a X-CSRF-TOKEN header read from a meta tag. Add this to the <head> of the layout that renders the chat component:

    <meta name="csrf-token" content="{{ csrf_token() }}">
  2. The API endpoint. resources/js/ai/api.js has a placeholder fetch URL — set it to whichever route in your app accepts { prompt, conversation_id } and calls ChatService::ask():

    const response = await fetch('add the fetch URL here', {

    For example:

    // routes/web.php
    Route::post('/ai/chat', function (\Illuminate\Http\Request $request) {
        return app(\App\Ai\Services\ChatService::class)->ask(new \App\Ai\Data\ChatRequest(
            prompt: $request->string('prompt')->toString(),
            conversationId: $request->input('conversation_id'),
            agent: 'assistant',
        ));
    })->middleware('auth');

    Then point api.js at /ai/chat.

  3. Import the chat JS. Add this to your existing resources/js/app.js:

    import './ai';
  4. Styling. The drawer uses Tailwind utility classes plus a handful of custom ones (.ai-assistant-bubble, .ai-assistant-header, .ai-assistant-copy, and dark-mode variants) that aren't defined by default Tailwind — add them to your own CSS. It also assumes Material Design 3–style color tokens (bg-primary, text-on-surface, border-outline-variant, etc.) are already part of your Tailwind theme; if they aren't, either define them or adjust the classes in a-i-chat.blade.php to match your own design system.

  5. npm packages. The chat JS depends on marked, highlight.js, and dompurify, and the message bubbles use Tailwind's typography plugin:

    npm install marked highlight.js dompurify @tailwindcss/typography

Contributing

Issues and pull requests are welcome at github.com/omarseyam/laravel-chatbot.

Author

Omar Seyam (@omarseyam) — oseyam02@gmail.com

License

The MIT License (MIT). See LICENSE for details.