backtik-ch/laravel-ai-usage

Track and log AI usage (tokens, cost, duration) for any Laravel application, with optional auto-discovery for laravel/ai SDK.

Maintainers

Package info

github.com/backtik-ch/laravel-ai-usage

pkg:composer/backtik-ch/laravel-ai-usage

Transparency log

Statistics

Installs: 59

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.5 2026-07-17 09:34 UTC

This package is auto-updated.

Last update: 2026-07-17 09:35:55 UTC


README

License

Track and log AI usage (tokens, cost, duration) for any Laravel application, with optional auto-discovery for the laravel/ai SDK and an optional Filament panel integration.

Requirements

  • PHP 8.3+
  • Laravel 12.x

Installation

composer require backtik-ch/laravel-ai-usage

Publish the config file and run the migrations:

php artisan vendor:publish --tag=ai-usage-config
php artisan migrate

How It Works

This package hooks into the laravel/ai SDK lifecycle via two events:

  1. PromptingAgent — fired when an agent starts. The package creates a pending record with the agent class, label, and an invocation ID.
  2. AgentPrompted — fired when the agent completes. The package locates the pending record, fills in token counts, duration, model/driver info, and snapshots the token prices from your config.

The result is a complete, immutable log of every AI call — no manual code required if you use laravel/ai.

If you're not using laravel/ai, you can log calls manually via the fluent AiUsage facade (see Manual logging).

Configuration

The config file (config/ai-usage.php) exposes the following options:

Key Default Description
table ai_usage Database table name
queue_logs false Dispatch log writes as queued jobs
log_system_prompt false Store system prompts in the log
log_response_text true Store AI response bodies
max_text_length 10_000 Truncate prompt/response text (chars); null disables
stale_timeout_minutes 60 Minutes before a processing record is considered stale
auto_discover true Auto-listen to laravel/ai events
attach_authenticated_user false Associate auto-discovered logs with the authenticated user
prices (see below) Token prices (USD / 1 M tokens) per driver & model

Token prices

Prices are snapshotted into each log row at write time so historical cost estimates remain accurate after you update the config.

// config/ai-usage.php
'prices' => [
    'openai' => [
        'gpt-4o' => ['prompt' => 2.50, 'completion' => 10.00],
        'gpt-4o-mini' => ['prompt' => 0.15, 'completion' => 0.60],
        // ...
    ],
    'anthropic' => [
        'claude-sonnet-4-5' => [
            'prompt' => 3.00, 'completion' => 15.00,
            'cache_read' => 0.30, 'cache_write' => 3.75,
        ],
        // ...
    ],
    'gemini' => [
        'gemini-2.0-flash' => ['prompt' => 0.10, 'completion' => 0.40],
        // ...
    ],
],

Supported price keys: prompt, completion, cache_read, cache_write, reasoning.

The package ships with indicative prices for common OpenAI, Anthropic and Gemini models. Always verify against your provider's current pricing page.

Usage

Tip

Using laravel/ai? You're already done. If auto-discovery is enabled (default) and laravel/ai is installed, every AI agent call is logged automatically — no code needed. The sections below are for manual logging or custom integrations.

Auto-discovery mode

When auto_discover is true (default) and laravel/ai is installed, the package automatically listens for Laravel\Ai\Events\PromptingAgent and Laravel\Ai\Events\AgentPrompted. A pending record is created when the agent starts, then updated with tokens, duration, model info, and cost data when the agent finishes.

To disable auto-discovery, set auto_discover to false in config/ai-usage.php.

Attach an owner automatically

To associate each automatically discovered log with the authenticated user, enable the following option:

// config/ai-usage.php
'attach_authenticated_user' => true,

The package uses the user from Laravel's default authentication guard. Numeric IDs, UUIDs, and ULIDs are supported. Guests and contexts without authentication, such as jobs and commands, are logged without an owner. If the authenticated user cannot be resolved, is not an Eloquent model, or has no primary key, the package writes a warning and creates the usage log without an owner.

Manual logging

Use the AiUsage facade with a fluent builder:

use BacktikCh\LaravelAiUsage\Facades\AiUsage;

AiUsage::driver('openai')
    ->model('gpt-4o')
    ->label('summarize-article')
    ->agentClass(MyAgent::class)          // optional — FQCN of the agent
    ->tokens(
        prompt: 512,
        completion: 256,
        cacheWrite: 0,                    // optional
        cacheRead: 0,                     // optional
        reasoning: 0,                     // optional
    )
    ->duration(milliseconds: 1200)
    ->prompt('Summarize this article...')
    ->response('The article discusses...')
    ->status('completed')
    ->requestMeta(['temperature' => 0.7]) // optional arbitrary metadata
    ->responseMeta(['finish_reason' => 'stop'])
    ->log();

Attach to a model (polymorphic owner)

AiUsage::driver('openai')
    ->model('gpt-4o')
    ->tokens(300, 150)
    ->owner($user) // any Eloquent model
    ->log();

Override prices at log time

AiUsage::driver('openai')
    ->model('gpt-4o')
    ->tokens(300, 150)
    ->costPrices(['prompt' => 2.50, 'completion' => 10.00])
    ->log();

If costPrices() is not called, prices are resolved automatically from config('ai-usage.prices').

How cost estimation works

Token prices (USD per 1 million tokens) are snapshotted from your config at log time and stored alongside each record. This means historical cost data remains accurate even when you update prices later.

The AiUsageLog model exposes an estimated_cost computed attribute derived from the stored token counts and prices:

$log = AiUsageLog::find(1);
echo $log->estimated_cost; // e.g. 0.003450

Returns null when no price data is available (i.e., the model isn't listed in your prices config).

To add a new model, simply add its prices to config('ai-usage.prices.{driver}.{model}'). Supported price keys: prompt, completion, cache_read, cache_write, reasoning.

Status values

Logs use the AiUsageStatus enum: pending, processing, completed, failed.

Filament integration

Requires filament/filament. Register the plugin in your panel provider:

use BacktikCh\LaravelAiUsage\Filament\AiUsagePlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugin(AiUsagePlugin::make());
}

This registers:

  • AiUsageResource — browsable, filterable list of all log entries with a detail view.
  • AiUsageStatsWidget — stats overview: total calls, prompt/completion tokens, average duration, failed count.

Summary widget (standalone)

AiUsageSummaryWidget is a full-width Livewire widget with a period selector and per-driver / per-model token breakdowns. Add it to any page:

use BacktikCh\LaravelAiUsage\Filament\Widgets\AiUsageSummaryWidget;

protected function getHeaderWidgets(): array
{
    return [AiUsageSummaryWidget::class];
}

Artisan Commands

Prune old records

php artisan ai-usage:prune --days=90

Mark stale records as failed

If an AI call throws an exception, the laravel/ai SDK never fires AgentPrompted, so the record stays in processing status indefinitely. Run this command on a schedule to mark those stale records as failed:

php artisan ai-usage:mark-stale-failed

The timeout is controlled by stale_timeout_minutes in your config (default: 60). You can also override it per-call:

php artisan ai-usage:mark-stale-failed --minutes=30

Add both commands to your scheduler in routes/console.php:

Schedule::command('ai-usage:mark-stale-failed')->hourly();
Schedule::command('ai-usage:prune')->daily();

Testing

composer test

License

MIT — see LICENSE.