Loading...

This is taking longer than expected.

Back to the help centre

Connect an AI provider

The providers config/ai.php knows, the one feature that uses them, and how to call one yourself.

The kit ships the Laravel AI SDK (laravel/ai, version 0.6.8) and a config/ai.php that already names fifteen providers. Exactly one feature uses it: the automatic translation behind php artisan lang:sync. Read this to give that command a provider, to know which of the many keys in config/ai.php actually do something today, and to call a model from your own code without wiring anything new.

What is configured

config/ai.php has three parts.

Defaults. Which provider answers when a call does not name one, per kind of work. These are committed values, not environment variables; edit the file to change them.

Key Default Used for
default openai Text: agents and prompts.
default_for_images gemini Image generation.
default_for_audio openai Speech synthesis.
default_for_transcription openai Speech to text.
default_for_embeddings openai Vector embeddings.
default_for_reranking cohere Reranking search results.

Caching. caching.embeddings.cache is false; set it to true and the SDK stores generated embeddings in the cache store named by caching.embeddings.store (CACHE_STORE, default database).

Providers. Each entry pairs a driver with the environment variables that authenticate it. Setting a variable does nothing by itself: a provider is only called when it is the default for a kind of work or a call names it.

Provider Variables Default
anthropic ANTHROPIC_API_KEY, ANTHROPIC_URL URL https://api.anthropic.com/v1
azure AZURE_OPENAI_API_KEY, AZURE_OPENAI_URL, AZURE_OPENAI_API_VERSION, AZURE_OPENAI_DEPLOYMENT, AZURE_OPENAI_EMBEDDING_DEPLOYMENT, AZURE_OPENAI_IMAGE_DEPLOYMENT Version 2025-04-01-preview; deployments gpt-4o, text-embedding-3-small, gpt-image-1
bedrock AWS_BEARER_TOKEN_BEDROCK, AWS_BEDROCK_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_USE_DEFAULT_CREDENTIALS Region us-east-1; default credential provider on
cohere COHERE_API_KEY
deepseek DEEPSEEK_API_KEY
eleven ELEVENLABS_API_KEY
gemini GEMINI_API_KEY, GEMINI_URL URL https://generativelanguage.googleapis.com/v1beta/
groq GROQ_API_KEY
jina JINA_API_KEY
mistral MISTRAL_API_KEY
ollama OLLAMA_API_KEY, OLLAMA_URL Key empty; URL http://localhost:11434
openai OPENAI_API_KEY, OPENAI_URL URL https://api.openai.com/v1
openrouter OPENROUTER_API_KEY
voyageai VOYAGEAI_API_KEY
xai XAI_API_KEY

Only OPENAI_API_KEY appears in .env.example, empty. Every other variable is read by config/ai.php and by nothing else in the kit: available, but not connected to any function. The bedrock entry shares AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY with the S3 filesystem, so filling them for file storage also fills Bedrock's credentials, without anything calling Bedrock.

config/services.php holds no AI credentials: its entries are Mailgun, Postmark, Resend, SES, Telnyx, the Weblabor world service, Stripe and Facebook.

What the kit uses today

One path, from the command to the network:

php artisan lang:sync
  → App\Services\LangSyncService
    → App\Services\LangTranslatorService::translate()
      → App\Ai\Agents\TranslationAgent::make(locale: $locale)->prompt($text, provider: config('ai.default'))

lang:sync walks every locale in lang/ and, for each key that English has and the locale lacks, asks the agent for a translation. The agent's instructions tell the model to translate into the target locale, keep the meaning and tone, preserve placeholders, and return only the translated text. Before the call the service replaces every Laravel placeholder such as :name with a marker (__PH0__) and restores it afterwards, so a placeholder can never come back translated.

The provider is whatever default names in config/ai.php, openai as shipped. The model is the provider's default text model, because neither the configuration nor the agent names one; for OpenAI in this version of the SDK that is gpt-5.4. To choose another model without touching the agent, add a models block to the provider entry:

'openai' => [
    'driver' => 'openai',
    'key' => env('OPENAI_API_KEY'),
    'url' => env('OPENAI_URL', 'https://api.openai.com/v1'),
    'models' => [
        'text' => ['default' => 'gpt-4.1-mini'],
    ],
],

The command decides whether a provider is configured before the first call. The default provider counts as configured when its key is filled, or when its driver is ollama, which needs no key. Otherwise every missing key is written in English with the warning "AI translation provider is not configured", and the command still finishes: nothing else in lang:sync depends on the network. A call that fails or returns nothing is handled the same way, with the warning "Translation failed for", so a bad key or an exhausted quota leaves you with English text to translate by hand, never with a broken file.

So, to turn translation on with OpenAI:

OPENAI_API_KEY=sk-your-key

To translate with another provider instead, set its key and change default in config/ai.php to its name, for example 'default' => 'anthropic'. To translate locally with no key at all, install Ollama, pull a model, and set 'default' => 'ollama'; the default model there is llama3.1:8b, and OLLAMA_URL points the SDK at another host.

The full translation workflow, including the languages a user can pick and the other lang:* commands, is in /help/languages-and-translations.

Two more things exist because the SDK was installed, and are unused by the kit:

  • Migration 2026_05_15_155312_create_agent_conversations_table creates the tables agent_conversations and agent_conversation_messages. They are the SDK's conversation store, for agents that remember a chat. No feature writes to them.
  • stubs/agent.stub, structured-agent.stub, agent-middleware.stub and tool.stub are the templates make:agent, make:agent --structured, make:agent-middleware and make:tool use.

Nothing in the kit sends user data, content or prompts to any provider other than the translation strings above, and only when you run lang:sync.

Call a provider from your own code

You do not need a client library or a service class. Generate an agent, give it instructions, and prompt it; the response's text is the model's answer.

php artisan make:agent SummaryAgent
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;
use Stringable;

class SummaryAgent implements Agent
{
    use Promptable;

    public function instructions(): Stringable|string
    {
        return 'Summarize the text you receive in one sentence. Return only the sentence.';
    }
}
$summary = SummaryAgent::make()->prompt('The long text to summarize...')->text;

That call uses the default provider and its default text model. To pin a provider or a model, pass them to prompt() or declare them on the class:

use Laravel\Ai\Attributes\{Model, Provider};
use Laravel\Ai\Enums\Lab;

#[Provider(Lab::Anthropic)]
#[Model('claude-sonnet-4-6')]
class SummaryAgent implements Agent
{
    // ...
}

// or per call
SummaryAgent::make()->prompt($text, provider: 'gemini', model: 'gemini-3-flash-preview');

Lab is the enum of the fifteen provider names; a plain string with the same name works too. stream() returns the answer as it is produced, queue() runs the prompt as a job. For a one-off prompt with no class, the agent() function builds an anonymous agent:

use function Laravel\Ai\agent;

$answer = agent('You answer in one word.')->prompt('What colour is the sky?')->text;

Images and embeddings have their own entry points, routed to default_for_images and default_for_embeddings:

use Laravel\Ai\{Embeddings, Image};

$image = Image::of('A lighthouse at dusk')->square()->generate();
$vectors = Embeddings::for(['first text', 'second text'])->generate();

In a test, SummaryAgent::fake(['a canned answer']) replaces the network with the answers you list, and Image::fake() and Embeddings::fake() do the same for theirs. php artisan agent:chat opens a conversation with one of your agents in the terminal, which is the quickest way to check a key works.

Follow the kit's own shape when you add one: keep the agent in app/Ai/Agents, call it from a model method or a service rather than from a Livewire component, and check the key before the call as LangTranslatorService does, so a missing provider degrades to a message instead of an exception.