postare/filament-model-ai
Postare Filament Model AI integrates AI-powered generation into Laravel Filament admin panels for Eloquent models. Add AI actions to resources so admins can create and improve content without leaving the panel, with configurable providers and prompts to fit your workflow.
Add the package and its config in one pass:
composer require postare/filament-model-ai
php artisan vendor:publish --tag="filament-model-ai-config"
Then set the provider credentials in .env (the package config reads them, so don't hardcode keys in config/):
OPENAI_API_KEY=sk-...
OPENAI_ORGANIZATION= # optional
Register the plugin on the panel you actually want the assistant on — not every panel:
// app/Providers/Filament/AdminPanelProvider.php
public function panel(Panel $panel): Panel
{
return $panel
->plugin(\Postare\FilamentModelAi\FilamentModelAiPlugin::make());
}
Where to look first, in this order:
config/filament-model-ai.php — this is the whole package. Read it before writing any code; the model/driver settings, context limits, and the list of Eloquent models exposed as "knowledge" all live here.HasAi / InteractsWithAi) — you add it to the models you want the assistant to reason over.php artisan migrate.A minimal working model:
use Postare\FilamentModelAi\Concerns\HasAi; // verify exact namespace in README
class Product extends Model
{
use HasAi;
}
First use case to validate end-to-end: open a Product record in the admin, ask the assistant "summarise this product's pricing history" and confirm it (a) answers, (b) only sees that record, (c) persists the conversation. If any of those three fail, stop and fix before rolling out to more models — the failure modes compound.
Scope knowledge per model, not globally. The tempting mistake is to register ten models and let the assistant loose. Instead, attach the AI affordance to the resource where it's useful and let the trait define what's exposed:
class Product extends Model
{
use HasAi;
public function aiContext(): array
{
return [
'name' => $this->name,
'price' => $this->price,
'category' => $this->category?->name,
'recent_orders_count' => $this->orders()->where('created_at', '>=', now()->subMonth())->count(),
];
}
}
Returning a curated array beats dumping $this->toArray() — it keeps token usage predictable and stops accidental leakage of internal columns.
Treat the system prompt as application code. Put it in the config (or a dedicated prompts/ folder and reference a translation key), not inline in a resource. You want it version-controlled, diffable, and testable.
Gate it with policies. An AI panel is a data-access surface. Anything the assistant can read, the user can effectively read. Add an explicit ability and check it before rendering the chat:
public function useAi(User $user): bool
{
return $user->hasPermissionTo('view products') && $user->is_staff;
}
Stream when the answer is long. For summarisation or "explain this customer's churn risk" prompts, streaming avoids 10–20 second blank screens. If the package exposes a streaming flag, use it on record pages and keep non-streaming for background/bulk use.
Queue anything that isn't interactive. Bulk actions over many records ("classify these 200 tickets") should dispatch a job that calls the same underlying service and writes results back to a column, rather than looping inside a Filament action and hitting timeout.
Keep a thin service layer. Wrap calls in app/Services/AiAssistant.php so your Filament resources and console commands share one entry point. This is your extension point when you outgrow the package defaults or want to swap providers.
Seed conversations for evaluation. Persisted chats are free regression fixtures: record a handful of representative prompts per model, and re-run them after changing the prompt or the context array.
$this->orders inside aiContext() on a model with thousands of rows is both an N+1 and a context-window overflow. Always ->limit() and ->select() in the context builder.php artisan config:cache in deploy captures the key at build time. If you inject the key via a runtime secret, read it through env() only in config/ and clear/re-warm the cache in your deploy script — otherwise you get "invalid API key" in production but not locally.RateLimiter::for('ai')) and log token usage per call; a runaway loop in a Livewire component can quietly burn budget.Http::fake() in Pest/PHPUnit rather than mocking internals — assert your prompt content and context array, not the HTTP client. This keeps tests stable across package minor versions.^1.0 (or the exact minor), read the changelog before upgrading, and write a small integration test that opens the chat page so an upgrade failure is loud rather than silent.tenant_id and that the assistant can't pull context across tenants; the context builder must respect the current tenant scope explicitly.app()->getLocale(), otherwise a Dutch admin user gets English answers over Dutch data.aiContext() returning an empty array, or a policy returning false. Log the assembled payload at debug level before blaming the model.How can I help you explore Laravel packages today?