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_tablecreates the tablesagent_conversationsandagent_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.stubandtool.stubare the templatesmake:agent,make:agent --structured,make:agent-middlewareandmake:tooluse.
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.