Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

USAIGE

Observability for Laravel AI SDK interactions — run lifecycle, token counts, cost, and provider metadata, recorded automatically with two helpers.

$run = ai_run('summarize-document');          // provider & model inferred from config/ai.php

$response = Ai::text('Summarize: ' . $document->content);

$usage = ai_usage($run, $response);
// AiRun   → status: completed, finished_at: now
// AiUsage → prompt_tokens: 180, completion_tokens: 67, total_tokens: 247

Requirements


Installation

composer require laraveljutsu/usaige
php artisan migrate

Package auto-discovery registers the service provider and the Usaige facade. The ai_run() and ai_usage() helpers are available everywhere immediately.


Usage

The minimal call

provider and model are optional — USAIGE reads them from config/ai.php automatically:

$run      = ai_run('generate-reply');
$response = Ai::text('Write a reply to: ' . $ticket->body);
$usage    = ai_usage($run, $response);

How provider & model are resolved

USAIGE reads the same config/ai.php the SDK uses:

// config/ai.php
'default' => 'openai',

'providers' => [
    'openai' => [
        'driver' => 'openai',
        'models' => [
            'text' => ['default' => 'gpt-4o-mini'],
        ],
        // ...
    ],
],

These three calls are equivalent when the config above is in place:

$run = ai_run('summarize-document');
$run = ai_run('summarize-document', provider: 'openai');
$run = ai_run('summarize-document', provider: 'openai', model: 'gpt-4o-mini');

Explicit values always win. If no AI SDK config is present, provider and model are stored as null.

Using the Lab enum

provider accepts Laravel\Ai\Enums\Lab directly — no string conversion needed:

use Laravel\Ai\Enums\Lab;

$run = ai_run('summarize-document', provider: Lab::OpenAI);
$run = ai_run('summarize-document', provider: Lab::Anthropic, model: 'claude-sonnet-4-6');

Explicit provider & model

$run = ai_run('summarize-document', provider: 'anthropic', model: 'claude-sonnet-4-6');

User ID

Resolved from auth()->id() by default. Override it per-call or globally:

// Per-call override (queue jobs, impersonation, …)
$run = ai_run('translate', userId: $targetUser->id);
// Global override — call once in AppServiceProvider::boot()
use Laraveljutsu\Usaige\Usaige;

Usaige::resolveUsersUsing(fn () => auth('api')->id());

Metadata

Attach any app-specific identifiers as JSON:

$run = ai_run('classify-ticket', metadata: [
    'team_id'   => $team->id,
    'ticket_id' => $ticket->id,
]);

$run->metadata['team_id']; // → 42

Handling failures

$run = ai_run('translate');

try {
    $response = Ai::text('Translate to FR: ' . $text);
    $usage    = ai_usage($run, $response);
} catch (Throwable $e) {
    $run->fail($e->getMessage());
    throw $e;
}

Manual token overrides

For streaming accumulators or unsupported response shapes:

$usage = ai_usage(
    $run,
    promptTokens:     1_240,
    completionTokens: 380,
    costUsd:          0.001620,
);

Facade

use Laraveljutsu\Usaige\Facades\Usaige;

$run   = Usaige::createRun('ocr-scan', model: 'gpt-4o');
$usage = Usaige::recordUsage($run, $response);

Querying runs

use Laraveljutsu\Usaige\Models\AiRun;

// All runs for the current user
AiRun::where('user_id', auth()->id())->with('usage')->latest()->get();

// Total tokens spent on a feature
AiRun::where('feature_key', 'summarize-document')
    ->join('ai_usages', 'ai_runs.id', '=', 'ai_usages.ai_run_id')
    ->sum('ai_usages.total_tokens');

// Failed runs in the last 24 hours
AiRun::where('status', 'failed')->where('started_at', '>=', now()->subDay())->get();

// Wall-clock duration
$run->durationMs(); // → 891

Configuration

php artisan vendor:publish --tag=usaige-config
// config/usaige.php
return [
    'path'       => 'usaige',   // dashboard URL
    'middleware' => [],          // e.g. ['auth', 'can:admin']
    'table_names' => [
        'ai_runs'   => 'ai_runs',
        'ai_usages' => 'ai_usages',
    ],
];

Note: Don't put closures in the published config — Laravel can't cache them. Use Usaige::resolveUsersUsing() and Usaige::auth() in AppServiceProvider::boot() instead.


Dashboard

USAIGE ships a web dashboard at /usaige listing all runs with status, tokens, cost, and duration.

Access control

Via config (middleware-based):

// config/usaige.php
'middleware' => ['auth'],              // require login
'middleware' => ['auth', 'can:admin'], // Gate / Policy
'middleware' => ['auth:admin'],        // custom guard

Via callback (code-based):

Usaige::auth(fn ($request) => $request->user()?->isAdmin() ?? false);

Both can be combined — the callback runs after any configured middleware.


API Reference

ai_run()

Creates an AiRun with status = running and started_at = now().

Parameter Type Default Description
$feature string Stable key identifying the AI feature (e.g. 'summarize-document').
$provider Lab|string|null null Provider name or Lab enum. Inferred from config/ai.php when null.
$model ?string null Model identifier. Inferred from config/ai.php when null.
$userId ?int null Overrides the user resolver.
$metadata array [] Arbitrary key-value pairs stored as JSON.
$inputHash ?string null Pre-computed hash of the prompt (for deduplication).

ai_usage()

Calls $run->complete(), extracts tokens from the response, and returns a new AiUsage.

Parameter Type Default Description
$run AiRun The run returned by ai_run().
$response mixed null AI SDK response — tokens extracted automatically.
$promptTokens ?int null Override the extracted prompt token count.
$completionTokens ?int null Override the extracted completion token count.
$costUsd ?float null Cost in USD. Not computed automatically.

AiRun methods

Method Description
complete(): void Sets status = completed, finished_at = now(). Called internally by ai_usage().
fail(string $message): void Sets status = failed, stores the error, finished_at = now().
durationMs(): ?int Wall-clock duration in milliseconds. null if not finished.
usage(): HasOne Relationship to the associated AiUsage.

Token Extraction

UsageExtractor detects the response shape automatically:

SDK / Shape Prompt field Completion field
Laravel AI SDK usage->inputTokens usage->outputTokens
OpenAI PHP SDK usage->prompt_tokens usage->completion_tokens
Plain array ['usage']['prompt_tokens'] ['usage']['completion_tokens']
Unknown / null 0 — override manually 0 — override manually

Schema

ai_runs

Column Type Notes
id bigint Primary key
user_id bigint, nullable No FK — works with any auth setup
feature_key string Developer-defined feature identifier
status string running · completed · failed
provider string, nullable e.g. openai, anthropic
model string, nullable e.g. gpt-4o-mini
input_hash string, nullable For deduplication
started_at timestamp, nullable Set by ai_run()
finished_at timestamp, nullable Set by complete() or fail()
error_message text, nullable Set by fail()
metadata json, nullable App-specific context
created_at / updated_at timestamp

ai_usages

Column Type Notes
id bigint Primary key
ai_run_id bigint FK → ai_runs, cascade delete
prompt_tokens uint Defaults to 0
completion_tokens uint Defaults to 0
total_tokens uint, nullable Sum of prompt + completion
cost_usd decimal(10, 6) 6 decimal places for sub-cent precision
created_at / updated_at timestamp

Testing

./vendor/bin/pest

In your application tests, use RefreshDatabase and call the helpers as normal:

it('tracks the summarize run', function () {
    $run   = ai_run('summarize', model: 'gpt-4o-mini');
    $usage = ai_usage($run, promptTokens: 100, completionTokens: 40);

    expect($run->fresh()->status->value)->toBe('completed')
        ->and($usage->total_tokens)->toBe(140);
});

License

MIT — Ludovic Guenet

About

USAIGE — Laravel AI SDK Usage Tracker

Resources

Stars

33 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages