PHPackages                             ferdiunal/laravel-ai-router - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. ferdiunal/laravel-ai-router

ActiveLibrary[API Development](/categories/api)

ferdiunal/laravel-ai-router
===========================

Laravel AI SDK Laravel AI Router text provider with local key/model routing.

v0.4.10(2mo ago)40[2 PRs](https://github.com/ferdiunal/laravel-ai-router/pulls)MITPHPPHP ^8.4CI passing

Since May 23Pushed 1mo agoCompare

[ Source](https://github.com/ferdiunal/laravel-ai-router)[ Packagist](https://packagist.org/packages/ferdiunal/laravel-ai-router)[ Docs](https://github.com/ferdiunal/laravel-ai-router)[ GitHub Sponsors](https://github.com/ferdiunal)[ RSS](/packages/ferdiunal-laravel-ai-router/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (10)Dependencies (13)Versions (20)Used By (0)

[![Laravel Ai Router](./art/cover.png)](./art/cover.png)

Laravel AI Router
=================

[](#laravel-ai-router)

English | [Türkçe](README.TR.md)

Laravel AI Router is a Laravel AI SDK text provider that routes prompts through locally managed provider keys, available-model caches, fallback metadata, rate-limit windows, and usage analytics. It is designed for applications that want one Laravel AI provider name (`laravel-ai-router`) while using multiple OpenAI-compatible providers and multiple labeled API keys behind a local routing layer.

The package stores its own operational state in a dedicated package database connection by default. The default storage target is `database/laravel-ai-router.sqlite`, which keeps provider keys, model cache rows, fallback routing rows, rate-limit counters, usage records, runtime custom provider definitions, and package settings out of the host application's main tables.

What this is / what this is not
-------------------------------

[](#what-this-is--what-this-is-not)

Laravel AI Router is a Laravel AI SDK text-provider router. It is not a standalone OpenAI-compatible HTTP proxy and it does not expose `/v1/chat/completions` or `/v1/models` routes. Host applications call it through Laravel AI (`ai()->using('laravel-ai-router', 'auto')`), and the package routes those calls to configured provider keys.

Built-in routable providers in this release are limited to adapters that are implemented and covered by tests:

ProviderAdapterGoogle AI StudioNative Gemini `generateContent` / `streamGenerateContent`CohereOpenAI-compatible compatibility APIGroqOpenAI-compatibleCerebrasOpenAI-compatibleSambaNovaOpenAI-compatibleNVIDIA NIMOpenAI-compatible, seeded disabled by defaultMistralOpenAI-compatibleOpenRouterOpenAI-compatibleGitHub ModelsOpenAI-compatibleCloudflare Workers AIAccount-scoped OpenAI-compatible Workers AI endpointZhipu AIOpenAI-compatibleOllama CloudOpenAI-compatibleKilo GatewayOpenAI-compatible, anonymous placeholder key supportedPollinationsOpenAI-compatible, anonymous placeholder key supportedLLM7OpenAI-compatible, anonymous placeholder key supportedGoogle AI Studio uses Google API-key query authentication. Cloudflare Workers AI asks for the account ID separately from the API token; the token is encrypted as the provider key and the account ID is stored as credential metadata so the adapter can build account-scoped Workers AI URLs without packing both values into one secret.

Free-tier and anonymous providers can change limits, model availability, authentication behavior, or terms of service without notice. Treat free-tier routing as development/prototype infrastructure unless you have reviewed each upstream provider's terms, quota, and SLA posture for your production use case.

Features
--------

[](#features)

- Laravel AI driver name: `laravel-ai-router`.
- Global `ai()` helper with `using(...)->prompt(...)->asText()` convenience flow for Tinker and small call sites.
- Default text model: `auto`.
- Provider API key management: add, list, enable, disable, remove.
- Runtime custom OpenAI-compatible provider definitions: add, list, enable, disable, remove.
- Native Google AI Studio and Cloudflare Workers AI adapters with provider model discovery/cache support.
- Encrypted API-key storage through Laravel encryption.
- Masked CLI output; raw provider keys are not printed.
- Provider + label scoped available-model cache with free/credits metadata.
- `LaravelAiRouterProvider::models()` access to cached model IDs.
- Non-streaming text generation through Laravel AI `TextProvider`.
- Streaming text generation through Laravel AI stream events.
- Structured output support through Laravel AI structured response types.
- Non-stream OpenAI-compatible function tool-call loop.
- Internal bounded retry/failover across eligible provider keys plus Laravel AI failover exception mapping for retryable provider errors.
- Local request/usage analytics by provider, label, model, status, token counts, latency, and error category.
- Bounded SQLite optimization with WAL, foreign keys, busy timeout, synchronous mode, temp store, and cache-size controls.
- Interactive Artisan commands implemented with Laravel Prompts.

Requirements
------------

[](#requirements)

- PHP `^8.4`
- Laravel components `^13.0`
- `laravel/ai ^0.7`
- `laravel/prompts ^0.3.6`

The package is a Laravel package and is auto-discovered through the service provider declared in `composer.json`.

Installation
------------

[](#installation)

```
composer require ferdiunal/laravel-ai-router
php artisan laravel-ai-router:install
```

The install command performs the package setup in this order:

1. Creates the configured SQLite file when the package connection targets SQLite storage.
2. Runs the package-owned internal migrations against the configured package connection.
3. Seeds the curated model catalog and fallback ordering rows.
4. Applies safe SQLite PRAGMA optimizations when enabled and applicable.
5. Starts the provider-key setup wizard in an interactive console.

By default, no migration file has to be published into the host application's `database/migrations` directory. The package migrations are internal package migrations and are executed by `laravel-ai-router:install` against the package connection.

Configuration
-------------

[](#configuration)

Publish the package config only when you need to override defaults:

```
php artisan vendor:publish --tag=laravel-ai-router-config
```

Register the provider in `config/ai.php`:

```
'providers' => [
    'laravel-ai-router' => [
        'driver' => 'laravel-ai-router',
    ],
],

'default' => 'laravel-ai-router',
```

The package config keeps `auto` as the default text model:

```
// config/laravel-ai-router.php
return [
    'driver' => env('LARAVEL_AI_ROUTER_DRIVER', 'laravel-ai-router'),

    'models' => [
        'text' => [
            'default' => env('LARAVEL_AI_ROUTER_DEFAULT_MODEL', 'auto'),
        ],
        'cache_ttl_minutes' => env('LARAVEL_AI_ROUTER_MODELS_CACHE_TTL', 1440),
    ],

    'routing' => [
        // random_provider is the default. It first shuffles selected provider keys,
        // then shuffles the selected models inside that provider. priority, random,
        // and balanced_random remain available for legacy fallback-list routing.
        'auto_strategy' => env('LARAVEL_AI_ROUTER_AUTO_STRATEGY', 'random_provider'),
        'random_pool_size' => env('LARAVEL_AI_ROUTER_RANDOM_POOL_SIZE', 5),
        'random_priority_window' => env('LARAVEL_AI_ROUTER_RANDOM_PRIORITY_WINDOW', 3),
    ],
];
```

### Package database connection

[](#package-database-connection)

The default package connection name is `laravel-ai-router`. When this connection name is used, the service provider registers a dedicated SQLite connection backed by `database/laravel-ai-router.sqlite` unless you override the SQLite path.

```
# Dedicated package SQLite path. This is used by the default laravel-ai-router connection.
LARAVEL_AI_ROUTER_SQLITE_DATABASE=/absolute/path/laravel-ai-router.sqlite

# Optional: point package storage to an existing host application connection.
# Use a connection name already defined in config/database.php, such as mysql, pgsql, or sqlite.
LARAVEL_AI_ROUTER_DB_CONNECTION=mysql
```

Use `LARAVEL_AI_ROUTER_DB_CONNECTION` only when you intentionally want package-owned state to live on an existing host connection. The value `laravel-ai-router` means the package's default dedicated SQLite connection.

Provider Key Management
-----------------------

[](#provider-key-management)

Provider keys are identified by `provider + label`. This allows multiple keys for the same provider, for example `openrouter / Primary`, `openrouter / Backup`, or `groq / Team`.

```
php artisan laravel-ai-router:provider:add
php artisan laravel-ai-router:provider:list
php artisan laravel-ai-router:provider:models
php artisan laravel-ai-router:provider:sync --key-id=1
php artisan laravel-ai-router:provider:sync --key-id=1 --json
php artisan laravel-ai-router:provider:enable
php artisan laravel-ai-router:provider:disable
php artisan laravel-ai-router:provider:remove
```

`laravel-ai-router:provider:add` uses Laravel Prompts to collect:

1. Provider platform.
2. Provider-specific credential metadata when required, for example Cloudflare account ID.
3. API key or API token.
4. Provider-key label.
5. Optional multi-select of cached available models that should participate in `auto` / `random_provider` routing.

The raw API key is encrypted before persistence and is never rendered in command output. Lists and prompts show masked credentials only.

Use `laravel-ai-router:provider:sync` for operational health checks. It requires an explicit target (`--all`, `--provider=openrouter`, or `--key-id=1`), validates the stored credential, optionally refreshes the provider model cache, reports selected auto-model counts, and includes a `local_estimate` quota snapshot from package-owned rate/token windows. Default table output is model-level: each selected auto-routing model gets its own row with blocked status, RPM/RPD/TPM/TPD remaining limits, and cooldown. Add `--no-refresh-models` to validate credentials and local quota without model discovery, `--fail-on-invalid` for CI/ops checks, and `--json` for stable secret-free automation output with the same model quota data nested under `quota.models`. The command never prints raw API keys, bearer tokens, or composed credentials.

Cloudflare Workers AI stores `account_id` separately in `credential_metadata` while encrypting only the API token in the key field. During routing and model discovery the package composes the adapter-facing `account_id:api_token` credential internally, then sends only the token as the upstream bearer credential. Legacy `account_id:api_token` input is still split into separate storage fields when adding a key.

Runtime Custom OpenAI-compatible Providers
------------------------------------------

[](#runtime-custom-openai-compatible-providers)

Runtime custom providers let you add OpenAI-compatible gateways, proxies, or providers without changing package code.

```
php artisan laravel-ai-router:provider-definition:add
php artisan laravel-ai-router:provider-definition:list
php artisan laravel-ai-router:provider-definition:models
php artisan laravel-ai-router:provider-definition:enable
php artisan laravel-ai-router:provider-definition:disable
php artisan laravel-ai-router:provider-definition:remove

php artisan laravel-ai-router:provider:add
php artisan laravel-ai-router:provider:models
```

A runtime provider definition contains:

- Provider slug, for example `my-openai-proxy`.
- Display name.
- OpenAI-compatible base URL, for example `https://api.example.com/v1`.
- Optional metadata headers as JSON, for example `{"X-Title":"Laravel AI Router"}`.
- Timeout in milliseconds.
- Optional anonymous placeholder-key support.
- Optional declared model list for gateways that do not expose a compatible `/models` endpoint.
- Live `/models` discovery mode (`enabled` or `disabled`).
- Credential validation method: `models` for `GET /models`, or `chat` for a minimal `POST /chat/completions` request using the configured validation model.

Security constraints are enforced before persistence and before request dispatch:

- Base URLs must use public `https://` URLs.
- Base URLs must point at the API root, for example `https://api.example.com/v1`, not a final `/chat/completions`, `/completions`, or `/models` endpoint.
- Credentials, query strings, fragments, localhost, local/test/internal hostnames, private IPs, and reserved IPs are rejected.
- DNS resolution must return public addresses only.
- Redirects are not followed for runtime provider validation.
- Authentication-bearing headers are rejected, including `Authorization`, `Proxy-Authorization`, `X-Api-Key`, and token/secret/password-like header names.
- Extra headers are for metadata/proxy headers only; provider credentials must be stored through provider keys.

You can also define static custom providers in config:

```
// config/laravel-ai-router.php
'providers' => [
    'custom' => [
        'my-openai-proxy' => [
            'name' => 'My OpenAI Proxy',
            'base_url' => 'https://api.example.com/v1',
            'headers' => [
                'X-Title' => 'Laravel AI Router',
            ],
            'timeout_ms' => 30000,
            'requires_placeholder_key' => false,
            'models_endpoint_enabled' => false,
            'validation_method' => 'chat',
            'validation_model' => 'mimo-v2.5-pro',
            'declared_models' => [
                ['id' => 'mimo-v2.5-pro', 'name' => 'MIMO v2.5 Pro', 'auto_enabled' => true],
            ],
        ],
    ],
],
```

When a routable OpenAI-compatible provider returns model IDs from its `/models` endpoint, Laravel AI Router can cache those model IDs by provider + label and create runtime model/fallback rows so they can participate in exact model routing.

For OpenAI-compatible gateways that do not provide a compatible `/models` endpoint, disable live model discovery and declare the routable chat models explicitly. You can set those fields while adding the provider definition or update them later:

```
php artisan laravel-ai-router:provider-definition:models \
    --id=1 \
    --models=mimo-v2.5-pro \
    --models-endpoint=disabled \
    --validation-method=chat \
    --validation-model=mimo-v2.5-pro
```

Declared model rows are cached with source `definition` when provider keys are refreshed. Exact model routing can use declared models. For `auto` / `random_provider`, either include `auto_enabled: true` in declared model JSON metadata or select the cached rows later with `php artisan laravel-ai-router:provider:models`; simple comma-separated IDs remain exact-routeable but are not auto-selected by default.

Model Cache and Auto Routing Selection
--------------------------------------

[](#model-cache-and-auto-routing-selection)

Provider model caches are scoped by provider key. A cache row stores the provider platform, provider label, model ID, display name, context window, rate limits, token limits, free-tier marker, tool support marker, source, and refresh timestamp.

Refresh and inspect the cache with:

```
php artisan laravel-ai-router:provider:models
```

The command can refresh the selected key's cache, list cached available models, and edit which rows are selected for `auto` / `random_provider` routing. Model listing and auto selection expose only rows that match all of these constraints:

- The provider platform has a routable adapter.
- The provider key is enabled.
- The provider key status is not `invalid`.
- The provider key model cache has not expired.
- The cache row matches the provider key ID, platform, and label.
- The cache row is enabled.

Live model discovery is provider-agnostic for routable providers: valid `/models` rows are cached even when their IDs do not end in `:free`. Free-tier status is stored as metadata (`is_free`); NVIDIA NIM live rows are marked as free credit-backed models (`free` + `credits-based`), while other non-free live rows default to the `credits-based` budget label. Exact live model IDs are routeable directly, even when the cache row is not selected for `auto`. Automatic routing is narrower than cache visibility: newly refreshed rows are selected for `auto` only when they match the provider adapter's chat contract and the package's provider-specific safe policy. Google AI Studio auto selection is limited to stable Gemini `generateContent` models (`gemini-2.5-flash`, `gemini-2.5-flash-lite`); embeddings, media, live/Interactions, and unprobed preview/pro models stay cached for visibility but are not auto-selected. Cloudflare Workers AI auto selection is limited to the conservative Workers AI chat-completions baseline until additional models are verified by a live probe. Review each upstream provider's quota and billing terms before using key-backed providers in production.

The package config default remains `auto`. The `random_provider` strategy first randomizes selected provider keys and then randomizes the selected models inside the chosen provider, so a provider with many selected rows does not automatically outweigh a provider with one selected row. The legacy `random` strategy is still available when you explicitly want model-row weighted fallback-list shuffling; `priority` and `balanced_random` are also preserved.

Programmatic model access:

```
use Ferdiunal\LaravelAiRouter\LaravelAiRouterProvider;
use Laravel\Ai\AiManager;

$provider = app(AiManager::class)->textProvider('laravel-ai-router');

assert($provider instanceof LaravelAiRouterProvider);

$modelIds = $provider->models('openrouter', 'Primary');
// ['auto', 'paid/model', 'qwen/qwen3-coder:free', ...]
```

Prompt Usage
------------

[](#prompt-usage)

Before sending prompts, run the install/setup flow and add at least one enabled provider key:

```
php artisan laravel-ai-router:install
php artisan laravel-ai-router:provider:add
```

Laravel AI Router can be called in two ways:

1. The package convenience helper: `ai()->using(...)->prompt(...)->asText()`.
2. Native Laravel AI agents: classes that implement `Laravel\Ai\Contracts\Agent` and use `Laravel\Ai\Promptable`.

### Quick Tinker usage with the `ai()` helper

[](#quick-tinker-usage-with-the-ai-helper)

The package ships a global `ai()` helper for Tinker and small application call sites. It returns Laravel AI Router's convenience helper, not Laravel AI SDK's facade.

```
php artisan tinker
```

```
ai()
    ->using('laravel-ai-router', 'auto')
    ->instructions('Answer concisely and include operationally relevant details.')
    ->prompt('Summarize this ticket in three bullet points.')
    ->asText();
```

`auto` lets Laravel AI Router choose from the provider-key/model cache rows selected for automatic routing. By default the auto chain uses `routing.auto_strategy=random_provider`: it shuffles selected provider keys first, then shuffles selected models within the chosen provider while still enforcing disabled/invalid-key skips, model-cache compatibility, tool support, cooldowns, and rate/token-limit checks. Set `LARAVEL_AI_ROUTER_AUTO_STRATEGY=priority` for deterministic fallback order, `random` for legacy model-row weighted fallback-list shuffling, or `balanced_random` to shuffle only the configured top fallback pool (`LARAVEL_AI_ROUTER_RANDOM_POOL_SIZE`, bounded by `LARAVEL_AI_ROUTER_RANDOM_PRIORITY_WINDOW`). Exact model calls can still route to a cached model ID that was not selected for `auto`, as long as the provider key/cache/model is healthy.

You can also route an exact cached model ID:

```
ai()
    ->using('laravel-ai-router', 'qwen/qwen3-coder:free')
    ->prompt('Write a short release note.')
    ->asText();
```

When you need the full Laravel AI response instead of only text, call `response()`:

```
$response = ai()
    ->using('laravel-ai-router', 'auto')
    ->timeout(20)
    ->prompt('Summarize the order status.')
    ->response();

(string) $response;              // response text
$response->usage->promptTokens;  // input token count
$response->usage->completionTokens;
$response->meta->provider;       // "laravel-ai-router"
$response->meta->model;          // routed upstream model ID
```

The helper also exposes the underlying Laravel AI manager when you need low-level SDK access:

```
use Ferdiunal\LaravelAiRouter\LaravelAiRouterProvider;

$provider = ai()->manager()->textProvider('laravel-ai-router');

assert($provider instanceof LaravelAiRouterProvider);

$models = $provider->models('openrouter', 'Primary');
// ['auto', 'paid/model', 'qwen/qwen3-coder:free', ...]
```

Unknown method calls on `ai()` are proxied to the underlying `Laravel\Ai\AiManager`, so `ai()->textProvider('laravel-ai-router')` also works.

### Native Laravel AI agent usage

[](#native-laravel-ai-agent-usage)

For production code, a named agent class is usually easier to test and reuse:

```
use Laravel\Ai\Attributes\Model;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;

#[Provider('laravel-ai-router')]
#[Model('auto')]
final class SupportAgent implements Agent
{
    use Promptable;

    public function instructions(): string
    {
        return 'Answer concisely and include operationally relevant details.';
    }
}
```

```
$response = SupportAgent::make()->prompt('Summarize the order status.');

echo (string) $response;
```

You can still override provider/model per call:

```
$response = SupportAgent::make()->prompt(
    'Summarize the order status.',
    provider: 'laravel-ai-router',
    model: 'qwen/qwen3-coder:free',
    timeout: 20,
);
```

Failover usage through Laravel AI provider arrays:

```
$response = SupportAgent::make()->prompt(
    'Summarize the order status.',
    provider: [
        'laravel-ai-router' => 'auto',
        'openai' => 'gpt-4o-mini',
    ],
    timeout: 20,
);
```

Retryable rate-limit, temporary overload, timeout, and insufficient-credit conditions are mapped to Laravel AI failover-compatible exception types where applicable.

Function Tools
--------------

[](#function-tools)

Laravel AI Router supports non-streaming OpenAI-compatible tool calls. Tools are regular Laravel AI `Tool` classes; the router maps their schema to OpenAI-compatible `tools`, executes requested calls locally, and sends tool result messages back to the selected provider.

> Streaming tool calls are intentionally not supported yet. Use `prompt()` / `asText()` / `response()` for tool workflows. Calling `stream()` with tools fails before opening the upstream stream.

### Define a tool

[](#define-a-tool)

```
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\JsonSchema\Types\Type;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;

final class LookupOrder implements Tool
{
    public function name(): string
    {
        return 'lookup_order';
    }

    public function description(): string
    {
        return 'Look up an order total by order ID.';
    }

    public function handle(Request $request): string
    {
        $orderId = $request['order_id'];

        // Replace this with your application lookup.
        return "Order {$orderId} total is 42 TRY";
    }

    /** @return array */
    public function schema(JsonSchema $schema): array
    {
        return [
            'order_id' => $schema->string()->required(),
        ];
    }
}
```

`name()` is optional in the `Tool` contract, but recommended. If it is missing, Laravel AI derives the tool name from the class basename.

### Use tools with the `ai()` helper

[](#use-tools-with-the-ai-helper)

```
$response = ai()
    ->using('laravel-ai-router', 'auto')
    ->instructions('Use tools when needed to answer order-related questions.')
    ->withTools([new LookupOrder])
    ->prompt('What is the total for order A-100?')
    ->response();

echo (string) $response;
// "The total for order A-100 is 42 TRY."

$response->toolCalls;   // Collection of ToolCall objects
$response->toolResults; // Collection of ToolResult objects
$response->steps;       // Collection of Step objects, including tool loop iterations
```

### Use tools with a named agent

[](#use-tools-with-a-named-agent)

```
use Laravel\Ai\Attributes\Model;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Promptable;

#[Provider('laravel-ai-router')]
#[Model('auto')]
final class OrderAgent implements Agent, HasTools
{
    use Promptable;

    public function instructions(): string
    {
        return 'Use tools when needed to answer order-related questions.';
    }

    /** @return iterable */
    public function tools(): iterable
    {
        return [new LookupOrder];
    }
}
```

```
$response = OrderAgent::make()->prompt('What is the total for order A-100?');

echo (string) $response;
```

Streaming Usage
---------------

[](#streaming-usage)

Laravel AI Router supports text streaming through Laravel AI stream events:

```
use Laravel\Ai\Streaming\Events\TextDelta;

$stream = ai()
    ->using('laravel-ai-router', 'auto')
    ->instructions('Answer with a short streaming response.')
    ->prompt('Explain the current order status.')
    ->stream();

foreach ($stream as $event) {
    if ($event instanceof TextDelta) {
        echo $event->delta;
    }
}
```

The same behavior is available through named agents:

```
$stream = SupportAgent::make()->stream(
    'Explain the current order status.',
    provider: 'laravel-ai-router',
    model: 'auto',
);
```

Structured Output Usage
-----------------------

[](#structured-output-usage)

Laravel AI Router supports Laravel AI structured output for non-streaming text requests. The package sends JSON-mode style options upstream and maps valid JSON content back into Laravel AI structured response objects.

```
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\JsonSchema\Types\Type;
use Laravel\Ai\Attributes\Model;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;

#[Provider('laravel-ai-router')]
#[Model('auto')]
final class TicketAnalyzer implements Agent, HasStructuredOutput
{
    use Promptable;

    public function instructions(): string
    {
        return 'Return a JSON response with the requested ticket analysis.';
    }

    /** @return array */
    public function schema(JsonSchema $schema): array
    {
        return [
            'summary' => $schema->string()->required(),
            'priority' => $schema->integer()->required(),
        ];
    }
}
```

```
$response = TicketAnalyzer::make()->prompt(
    'Analyze this ticket.',
    provider: 'laravel-ai-router',
    model: 'auto',
);

$response['summary'];  // "..."
$response['priority']; // 3
$response->usage;
$response->meta;
```

Laravel AI SDK Capability Matrix
--------------------------------

[](#laravel-ai-sdk-capability-matrix)

CapabilityStatusNotesText generationSupportedReturns Laravel AI `TextResponse` or `StructuredTextResponse`, including usage and metadata.Text streamingSupportedConverts OpenAI-compatible SSE chunks into Laravel AI `StreamStart`, `TextStart`, `TextDelta`, `TextEnd`, and `StreamEnd` events.Structured outputSupportedSends JSON-mode style options and maps valid JSON content into Laravel AI structured response types.Function toolsSupported for non-streamingExecutes OpenAI-compatible `tools` / `tool_calls` loops and sends tool result messages back to the provider.Streaming toolsNot supportedFails before opening the upstream stream with a clear `LogicException`.FailoverSupportedRetries eligible internal provider keys up to `routing.max_attempts`, then maps rate limits, insufficient credit/quota, timeout, and overload errors to Laravel AI failover exception types. `auto` defaults to provider-first random rotation across selected cached provider-key/model rows; `priority`, legacy model-row `random`, and bounded `balanced_random` remain available without bypassing key/cache/limit eligibility checks.Images, audio, transcription, embeddings, reranking, files, storesNot supportedThe package advertises only the text provider contract and throws explicit capability errors for unsupported methods.Usage Analytics
---------------

[](#usage-analytics)

Display usage analytics with:

```
php artisan laravel-ai-router:usage
```

Tracked fields include:

- Provider platform.
- Provider label.
- Model ID.
- Success or error status.
- Input tokens.
- Output tokens.
- Total tokens.
- Latency in milliseconds.
- Error type.
- Error category.
- Redacted error message.
- Request timestamps.

Usage rows are stored in package-owned storage and can be used to inspect provider reliability, model distribution, latency, token volume, and error trends.

### Retention and pruning

[](#retention-and-pruning)

Laravel AI Router keeps usage rows for `LARAVEL_AI_ROUTER_USAGE_RETENTION_DAYS` days by default and rate-window/cooldown rows for `LARAVEL_AI_ROUTER_RATE_WINDOW_RETENTION_DAYS` days by default. Prune old package storage rows with:

```
php artisan laravel-ai-router:prune
```

Use `--vacuum` only when you explicitly want SQLite to compact the package database after pruning:

```
php artisan laravel-ai-router:prune --vacuum
```

SQLite Optimization and Package Storage
---------------------------------------

[](#sqlite-optimization-and-package-storage)

When the package connection uses SQLite and optimization is enabled, the package applies bounded PRAGMA settings:

- `journal_mode = WAL`, except for in-memory databases.
- `foreign_keys = ON`.
- `busy_timeout` from config, clamped to safe bounds.
- `synchronous` from an allowed enum-like set.
- `temp_store = MEMORY` when enabled.
- `cache_size` from config, clamped to safe bounds.

Relevant config shape:

```
'database' => [
    'connection' => env('LARAVEL_AI_ROUTER_DB_CONNECTION', 'laravel-ai-router'),
    'sqlite' => [
        'database' => env('LARAVEL_AI_ROUTER_SQLITE_DATABASE', database_path('laravel-ai-router.sqlite')),
        'optimize' => env('LARAVEL_AI_ROUTER_SQLITE_OPTIMIZE', true),
        'journal_mode' => env('LARAVEL_AI_ROUTER_SQLITE_JOURNAL_MODE', 'WAL'),
        'synchronous' => env('LARAVEL_AI_ROUTER_SQLITE_SYNCHRONOUS', 'NORMAL'),
        'busy_timeout_ms' => env('LARAVEL_AI_ROUTER_SQLITE_BUSY_TIMEOUT_MS', 5000),
        'cache_size_kb' => env('LARAVEL_AI_ROUTER_SQLITE_CACHE_SIZE_KB', 20000),
    ],
],
```

Internal migrations are idempotent enough to tolerate existing package tables on the package connection. They do not need to be published into host application migrations.

Security Model
--------------

[](#security-model)

- Provider API keys are encrypted with Laravel `Crypt` before persistence.
- CLI output uses masked credentials only.
- Runtime custom provider URLs must be public HTTPS endpoints.
- Runtime custom provider header names cannot carry authentication headers or token/secret/password-like names.
- Provider model cache refresh marks keys invalid on authentication failures and avoids curated fallback for auth failures.
- Usage logging redacts bearer tokens from exception messages before persistence.
- Streaming parsers enforce line and aggregate event byte limits.
- SQLite PRAGMA config values are clamped before execution.

Upgrade Notes
-------------

[](#upgrade-notes)

Earlier versions exposed publishable migration stubs for host application migrations. The current install flow keeps package-owned migrations internal and executes them through `laravel-ai-router:install` against the package connection.

If you previously published package migration stubs into the host application's `database/migrations` directory and stored Laravel AI Router data in host tables, the new install command will not automatically move that data into the dedicated package database. To preserve existing provider keys, usage rows, model cache rows, or settings:

1. Back up the host database.
2. Install or update the package.
3. Run `php artisan laravel-ai-router:install` to prepare the package storage.
4. Write and run a one-off migration or command that copies the old host rows into the package connection tables.
5. Verify provider-key encryption, model cache freshness, and usage analytics after the migration.

Development and Validation
--------------------------

[](#development-and-validation)

```
composer format:check
composer analyse
composer test
composer ci
```

`composer ci` runs formatting checks, PHPStan analysis, and the full Pest test suite.

Troubleshooting
---------------

[](#troubleshooting)

### The package SQLite database file is missing

[](#the-package-sqlite-database-file-is-missing)

Run:

```
php artisan laravel-ai-router:install
```

If you use a custom path, verify `LARAVEL_AI_ROUTER_SQLITE_DATABASE` points to a writable location and that the parent directory can be created by the application user.

### A provider key is marked invalid

[](#a-provider-key-is-marked-invalid)

Authentication failures during model refresh or routed requests mark the selected key invalid. Add a new key or re-enable/update the key after verifying the credential with the provider.

### Cached models are not shown

[](#cached-models-are-not-shown)

`laravel-ai-router:provider:models` exposes only routable, enabled, non-invalid, non-expired cache rows for the selected provider key. Refresh the selected key's cache and verify the provider adapter is implemented.

### Streaming tools fail before the provider stream opens

[](#streaming-tools-fail-before-the-provider-stream-opens)

Streaming tool calls are intentionally unsupported. Use non-streaming text generation when tools are required.

### A custom provider base URL is rejected

[](#a-custom-provider-base-url-is-rejected)

Ensure the URL uses public HTTPS, has no credentials/query/fragment, does not point to localhost or private/reserved networks, and resolves only to public IP addresses.

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance91

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

17

Last Release

61d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/51f5bc45a8653e16229499476a51988197350c7648ec3b961c62c45cc92d8872?d=identicon)[ferdiunal](/maintainers/ferdiunal)

---

Top Contributors

[![ferdiunal](https://avatars.githubusercontent.com/u/5059851?v=4)](https://github.com/ferdiunal "ferdiunal (53 commits)")

---

Tags

ai-sdkfree-llm-apifree-llmslaravellaravel-ai-sdklaravelailaravel-ailaravel-ai-router

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/ferdiunal-laravel-ai-router/health.svg)

```
[![Health](https://phpackages.com/badges/ferdiunal-laravel-ai-router/health.svg)](https://phpackages.com/packages/ferdiunal-laravel-ai-router)
```

###  Alternatives

[laravel/ai

The official AI SDK for Laravel.

1.0k3.2M246](/packages/laravel-ai)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9762.4M133](/packages/roots-acorn)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

77922.3M186](/packages/laravel-mcp)[api-platform/laravel

API Platform support for Laravel

58174.6k17](/packages/api-platform-laravel)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5022.6k](/packages/simplestats-io-laravel-client)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
