You add your first AI call. It's one line, a synchronous openai()->chat(), and it works perfectly.
Six months later you have seventeen of those scattered across the codebase, no idea which ones are slow, no retry logic, a tenant who blew through $300 in one night, and a queue that processes bulk image generation at the same priority as real-time chat responses.
Making AI work in production Laravel apps involves more than calling an API. You need queues, audit logs, cost tracking, retry logic, and tests that do not hit real providers. That is what fomvasss/laravel-ai-tasks is for.
It sits on top of laravel/ai (the official Laravel AI SDK) and adds the orchestration and observability layer you actually need in production.
What you get
- Task classes with a generator command (
ai:make-task) - Sync, queued, and streaming execution via the
AIfacade - Every run logged to
ai_runstable (request, response, tokens, cost, status) - Driver routing with fallback chains
- Multi-tenant budget tracking
- Prompt caching support for Anthropic
- Built-in dashboard at
/ai-tasks -
AI::fake()with assertions for testing - Events at every stage of execution
- Text, image, embeddings, TTS, and transcription modalities
Requirements
- PHP 8.3+
- Laravel 12 or 13
-
laravel/ai^0.8
Install
composer require fomvasss/laravel-ai-tasks
php artisan vendor:publish --tag=ai-tasks-config
php artisan vendor:publish --tag=ai-migrations
php artisan migrate
Define a task
class SummarizeTask extends AiTask
{
public function __construct(private readonly string $text) {}
public function modality(): string { return 'text'; }
public function toPayload(): AiPayload
{
return new AiPayload(
modality: 'text',
messages: [new UserMessage("Summarize: {$this->text}")],
systemPrompt: 'Reply in 3 sentences max.',
options: ['temperature' => 0.3],
);
}
public function postprocess(AiResponse $response): AiResponse|array
{
return $response;
}
}
Run it
// Sync
$response = AI::send(new SummarizeTask($text));
// Queued
$runId = AI::queue(new SummarizeTask($text));
// Streaming (no timeout, good for long outputs)
AI::stream(new SummarizeTask($text), fn($chunk) => print($chunk));
// Runtime driver override
AI::send(new SummarizeTask($text), drivers: 'anthropic');
Driver routing with fallback
// config/ai-tasks.php
'routing' => [
'summarize' => ['openai', 'anthropic'], // tries openai, falls back to anthropic
],
Cost tracking
'anthropic' => [
'model' => 'claude-sonnet-4-6',
'price' => ['in' => 3.00, 'out' => 15.00],
],
Query spend by tenant:
AiRun::where('tenant_id', $tenantId)->where('status', 'ok')->sum('cost');
shouldRun() guard
Prevent stale queued tasks from consuming tokens:
public function shouldRun(): bool
{
return Product::find($this->productId)?->needs_analysis ?? false;
}
Testing without real API calls
$fake = AI::fake([
'summarize' => 'This is a summary.',
'*' => 'Default fallback.',
]);
$fake->assertSent(SummarizeTask::class);
$fake->assertSentCount(1);
Supported providers
OpenAI, Anthropic, Gemini, DeepSeek, Groq, Mistral, xAI, Ollama, ElevenLabs, and any laravel/ai-compatible provider.
- GitHub: https://github.com/fomvasss/laravel-ai-tasks
- Packagist: https://packagist.org/packages/fomvasss/laravel-ai-tasks
Happy to answer questions in the comments. Stars and issues welcome 🙂
Support
If this package saves you time, consider supporting development (and this will help me 🥹):
Top comments (0)