Installation
Requirements
Section titled “Requirements”- PHP ^8.3
- Laravel ^12 | ^13
- laravel/ai ^1.2
Install
Section titled “Install”composer require fomvasss/laravel-ai-tasksPublish the configs and run the migrations:
# laravel/ai provider config (credentials go here)php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider" --tag=ai-config
# this package config (routing, budgets, queues)php artisan vendor:publish --tag=ai-tasks-config
php artisan vendor:publish --tag=ai-migrationsphp artisan migrateAdd API keys to .env — credentials are read by laravel/ai:
AI_DEFAULT=openai
OPENAI_API_KEY=sk-...ANTHROPIC_API_KEY=sk-ant-...GEMINI_API_KEY=...DEEPSEEK_API_KEY=sk-...GROQ_API_KEY=gsk_...php artisan about has an AI Tasks section — check it after installing and after every upgrade:
| Row | Shows |
|---|---|
Schema | whether the ai_runs table has every column — a new migration to publish |
Config | keys the published config/ai-tasks.php lacks compared with the package’s, and keys the package no longer uses. composer update never updates a published config. The application’s own lists (drivers, routing, budgets, pipes, middleware) are not compared |
Queue | the connection the ai queue is worked on (the Horizon supervisor’s, if Horizon consumes it) and its retry_after, highlighted when it isn’t above the job timeout — see below |
Two config files
Section titled “Two config files”| File | Purpose |
|---|---|
config/ai.php | laravel/ai — API keys, provider URLs |
config/ai-tasks.php | this package — models, prices, routing, budgets |
The API key is never stored in config/ai-tasks.php. See Configuration and Providers.
Queues and Horizon
Section titled “Queues and Horizon”Two queues are used by default, split by workload so a burst of slow provider calls can’t starve fast postprocessing behind it:
ai—ProcessAiPayload, the actual provider call. Slow (seconds), so it needs more processes and a longtimeoutai-post—PostprocessAiResult, runningpostprocess()/isAcceptable()and dispatching retries/completion. Fast and lightweight, so a couple of processes and a shorttimeoutare enough
AI_QUEUE=aiAI_QUEUE_POST=ai-postA provider call takes far longer than an ordinary job, and that needs its own queue connection. Redis hands a reserved job out again once the connection’s retry_after passes — 90 seconds on the default redis connection — while a worker may still be on it for up to 300. The result is the same task executed twice. Give the ai queue a connection whose retry_after is larger than the highest job timeout:
'redis-ai' => [ 'driver' => 'redis', 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), 'queue' => env('REDIS_QUEUE', 'default'), 'retry_after' => 360, // > the highest jobTimeout() (300 by default) 'block_for' => null,],'supervisor-ai' => [ 'connection' => 'redis-ai', 'queue' => ['ai'], 'balance' => 'auto', 'minProcesses' => 1, 'maxProcesses' => 6, 'tries' => 3, 'timeout' => 300,],The jobs are still dispatched on the default connection: both connections use the same Redis database and the same queue key, and retry_after takes effect when the worker reserves the job. No task needs onConnection().
ai-post jobs are short (postprocess(), onCompleted()) and usually few, so they don’t need their own supervisor — add the queue to an existing pool of short jobs on the regular redis connection:
'supervisor-default' => [ 'connection' => 'redis', 'queue' => ['default', 'ai-post'], 'timeout' => 60, // ...],Rules of thumb:
- the supervisor
timeoutis at least as large as the highestjobTimeout()of your tasks, and the connection’sretry_afteris larger still - the package jobs set their own
tries(3) andbackoff, which take precedence over the supervisor’stries, see Failures and job retries - every queue a task returns from
viaQueues()must be consumed by a supervisor - locally one
aiprocess is enough; schedulehorizon:snapshotso the Horizon metrics are filled
More on running in production — Production checklist.
Laravel Octane
Section titled “Laravel Octane”No configuration needed:
TenantResolveris bound asscoped— a new instance per request/job- The
AiManagerdriver cache and runtime provider aliases created byproviderOverrideare flushed on everyRequestReceivedandTaskReceivedOctane event
A custom TenantResolver that holds per-request state is reset correctly between requests thanks to the scoped binding.
Securing the dashboard
Section titled “Securing the dashboard”The dashboard at /ai-tasks is enabled by default with middleware => ['web'] — no authorization. Before deploying, add your auth middleware, see Dashboard.