Laravel AI SDK
На этой странице
Введение
Laravel AI SDK предоставляет единый выразительный API для работы с AI-провайдерами, такими как OpenAI, Anthropic, Gemini и другими. С AI SDK можно создавать интеллектуальных агентов с инструментами и структурированным выводом, генерировать изображения, синтезировать и транскрибировать аудио, создавать vector embeddings и многое другое через последовательный Laravel-friendly интерфейс.
Установка
Установите Laravel AI SDK через Composer:
composer require laravel/ai
Затем опубликуйте конфигурацию и миграции AI SDK с помощью Artisan-команды vendor:publish:
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
После этого выполните миграции приложения. Они создадут таблицы agent_conversations и agent_conversation_messages, которые AI SDK использует для хранения разговоров:
php artisan migrate
Конфигурирование
Учетные данные AI-провайдеров можно определить в файле config/ai.php или через переменные окружения в .env:
ANTHROPIC_API_KEY=
AZURE_OPENAI_API_KEY=
COHERE_API_KEY=
DEEPSEEK_API_KEY=
ELEVENLABS_API_KEY=
GEMINI_API_KEY=
GROQ_API_KEY=
MISTRAL_API_KEY=
OLLAMA_API_KEY=
OPENAI_API_KEY=
OPENAI_COMPATIBLE_API_KEY=
OPENAI_COMPATIBLE_URL=
OPENROUTER_API_KEY=
JINA_API_KEY=
VOYAGEAI_API_KEY=
XAI_API_KEY=
Модели по умолчанию для текста, изображений, аудио, транскрипций и embeddings также настраиваются в config/ai.php.
Пользовательские базовые URL
По умолчанию Laravel AI SDK подключается напрямую к публичному API-эндпоинту каждого провайдера. Иногда запросы нужно направлять через другой эндпоинт, например через прокси-сервис для централизованного управления API-ключами, ограничения частоты запросов или корпоративный шлюз.
Пользовательские базовые URL настраиваются через параметр url в конфигурации провайдера:
'providers' => [
'openai' => [
'driver' => 'openai',
'key' => env('OPENAI_API_KEY'),
'url' => env('OPENAI_URL'),
],
'anthropic' => [
'driver' => 'anthropic',
'key' => env('ANTHROPIC_API_KEY'),
'url' => env('ANTHROPIC_BASE_URL'),
],
],
Это полезно при маршрутизации запросов через прокси-сервисы вроде LiteLLM или Azure OpenAI Gateway, а также при использовании альтернативных эндпоинтов. Пользовательские URL поддерживаются для OpenAI, Anthropic, Gemini, Groq, Cohere, DeepSeek, xAI и OpenRouter.
OpenAI-совместимые провайдеры
Если вы используете OpenAI-совместимый API, например LM Studio, vLLM, Together, Fireworks или локальный шлюз, вы можете настроить провайдер openai-compatible. Опция url обязательна, а опция key необязательна и при наличии будет отправляться как bearer token:
'providers' => [
'local' => [
'driver' => 'openai-compatible',
'url' => env('LOCAL_AI_URL'),
'key' => env('LOCAL_AI_API_KEY'),
],
],
После настройки именованный провайдер можно использовать как любой другой:
agent()->prompt('What is Laravel?', provider: 'local', model: 'local-model');
Вы также можете настроить модель по умолчанию для текста, чтобы не передавать модель явно:
'local' => [
'driver' => 'openai-compatible',
'url' => env('LOCAL_AI_URL'),
'key' => env('LOCAL_AI_API_KEY'),
'models' => [
'text' => [
'default' => env('LOCAL_AI_MODEL'),
],
],
],
OpenAI-совместимые провайдеры поддерживают генерацию текста, потоковую передачу, tools, structured output и вложения изображений. Если вашему эндпоинту требуются дополнительные поля тела запроса, передайте их с помощью опций провайдера.
Поддержка провайдеров
AI SDK поддерживает разных провайдеров для разных возможностей:
| Возможность | Провайдеры |
|---|---|
| Text | OpenAI, OpenAI Compatible, Anthropic, Gemini, Azure, Bedrock, Groq, xAI, DeepSeek, Mistral, Ollama, OpenRouter |
| Images | OpenAI, Gemini, xAI, Azure, Bedrock, OpenRouter |
| TTS | OpenAI, ElevenLabs, Gemini |
| STT | OpenAI, ElevenLabs, Mistral, Gemini |
| Embeddings | OpenAI, Gemini, Azure, Bedrock, Cohere, Mistral, Jina, VoyageAI, Ollama, OpenRouter |
| Reranking | Cohere, Jina, VoyageAI |
| Files | OpenAI, Anthropic, Gemini, Azure |
Enum Laravel\Ai\Enums\Lab можно использовать для ссылки на провайдеров в коде вместо строк:
use Laravel\Ai\Enums\Lab;
Lab::Anthropic;
Lab::OpenAI;
Lab::OpenAiCompatible;
Lab::Gemini;
// ...
Агенты
Агенты – основной строительный блок для взаимодействия с AI-провайдерами в Laravel AI SDK. Каждый агент является отдельным PHP-классом, инкапсулирующим инструкции, контекст разговора, инструменты и схему вывода, необходимые для работы с большой языковой моделью. Думайте об агенте как о специализированном помощнике: sales coach, анализатор документов, support bot, которого вы настраиваете один раз и затем вызываете по мере необходимости.
Агента можно создать Artisan-командой make:agent:
php artisan make:agent SalesCoach
php artisan make:agent SalesCoach --structured
В сгенерированном классе можно определить system prompt / instructions, контекст сообщений, доступные tools и схему вывода:
<?php
namespace App\Ai\Agents;
use App\Ai\Tools\RetrievePreviousTranscripts;
use App\Models\History;
use App\Models\User;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Messages\Message;
use Laravel\Ai\Promptable;
use Stringable;
class SalesCoach implements Agent, Conversational, HasTools, HasStructuredOutput
{
use Promptable;
public function __construct(public User $user) {}
public function instructions(): Stringable|string
{
return 'You are a sales coach, analyzing transcripts and providing feedback and an overall sales strength score.';
}
public function messages(): iterable
{
return History::where('user_id', $this->user->id)
->latest()
->limit(50)
->get()
->reverse()
->map(function ($message) {
return new Message($message->role, $message->content);
})->all();
}
public function tools(): iterable
{
return [
new RetrievePreviousTranscripts,
];
}
public function schema(JsonSchema $schema): array
{
return [
'feedback' => $schema->string()->required(),
'score' => $schema->integer()->min(1)->max(10)->required(),
];
}
}
Запросы к агентам
Чтобы обратиться к агенту, создайте экземпляр через make или обычный конструктор, затем вызовите prompt:
$response = (new SalesCoach)
->prompt('Analyze this sales transcript...');
return (string) $response;
Метод make разрешает агента из контейнера, поэтому работает автоматический dependency injection. В конструктор агента также можно передавать аргументы:
$agent = SalesCoach::make(user: $user);
Передав дополнительные аргументы в prompt, можно переопределить provider, model или HTTP timeout:
$response = (new SalesCoach)->prompt(
'Analyze this sales transcript...',
provider: Lab::Anthropic,
model: 'claude-sonnet-5',
timeout: 120,
);
Контекст разговора
Если агент реализует интерфейс Conversational, метод messages может возвращать предыдущий контекст разговора:
use App\Models\History;
use Laravel\Ai\Messages\Message;
public function messages(): iterable
{
return History::where('user_id', $this->user->id)
->latest()
->limit(50)
->get()
->reverse()
->map(function ($message) {
return new Message($message->role, $message->content);
})->all();
}
Запоминание разговоров
Warning: Перед использованием трейта
RemembersConversationsопубликуйте и выполните миграции AI SDK черезvendor:publish, чтобы создать таблицы для хранения разговоров.
Если вы хотите, чтобы Laravel автоматически сохранял и извлекал историю разговоров агента, используйте трейт RemembersConversations. Он позволяет сохранять сообщения в базе данных без ручной реализации Conversational:
use Laravel\Ai\Concerns\RemembersConversations;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Promptable;
class SalesCoach implements Agent, Conversational
{
use Promptable, RemembersConversations;
public function instructions(): string
{
return 'You are a sales coach...';
}
}
При использовании trait RemembersConversations не определяйте метод messages вручную в классе агента. Если метод messages присутствует, он получит приоритет над реализацией trait, и history разговора не будет загружена из базы данных.
Чтобы начать новый разговор для пользователя, вызовите forUser перед prompt:
$response = (new SalesCoach)->forUser($user)->prompt('Hello!');
$conversationId = $response->conversationId;
ID разговора возвращается в response и может быть сохранен для дальнейшего использования. Если вы хотите получать все conversations пользователя через Eloquent, добавьте trait HasConversations к вашей user model:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Ai\Concerns\HasConversations;
class User extends Authenticatable
{
use HasConversations;
}
После добавления trait к модели conversations пользователя можно получать и запрашивать через relationship conversations:
$conversations = $user->conversations()
->latest('updated_at')
->paginate(20);
Продолжить существующий разговор можно методом continue:
$response = (new SalesCoach)
->continue($conversationId, as: $user)
->prompt('Tell me more about that.');
При использовании RemembersConversations предыдущие сообщения автоматически загружаются и включаются в контекст при prompt. Новые сообщения пользователя и ассистента сохраняются после каждого взаимодействия.
Участники разговоров
Хотя пользователи являются наиболее распространенными участниками разговоров, разговоры могут принадлежать любой модели Eloquent. Используйте метод forParticipant, чтобы начать разговор для модели другого типа:
$response = (new SalesCoach)
->forParticipant($team)
->prompt('Review our latest sales results.');
Morph-класс и первичный ключ участника сохраняются вместе с разговором. Поэтому модели разных типов с одинаковым первичным ключом, например User с ID 1 и Team с ID 1, имеют отдельные истории разговоров. Метод forUser является псевдонимом forParticipant.
Вы можете продолжить самый последний разговор участника с помощью метода continueLastConversation:
$response = (new SalesCoach)
->continueLastConversation($team)
->prompt('Tell me more about that.');
При продолжении конкретного разговора передайте участника в метод continue:
$response = (new SalesCoach)
->continue($conversationId, as: $team)
->prompt('Tell me more about that.');
Трейт HasConversations можно добавить к любой модели Eloquent, которая участвует в разговорах. Полученное отношение conversations является полиморфным отношением, ограниченным типом и первичным ключом этой модели. Вы также можете получить участника, которому принадлежит разговор, через обратное отношение:
$conversations = $team->conversations;
$participant = $conversation->participant;
Если ваше приложение использует несколько типов моделей-участников, стоит определить Eloquent morph map, чтобы сохраненные типы участников не были привязаны к именам классов моделей.
Метод
continueне проверяет, что переданный участник владеет разговором. Ваше приложение должно авторизовать доступ к разговору перед его продолжением.
Структурированный вывод
Если агент должен возвращать структурированный вывод, реализуйте интерфейс HasStructuredOutput и определите метод schema:
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;
class SalesCoach implements Agent, HasStructuredOutput
{
use Promptable;
public function schema(JsonSchema $schema): array
{
return [
'score' => $schema->integer()->required(),
];
}
}
Ответ StructuredAgentResponse можно читать как массив:
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');
return $response['score'];
Nested objects
Чтобы определить nested structured output, используйте метод object с closure:
<?php
namespace App\Ai\Agents;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;
class SalesCoach implements Agent, HasStructuredOutput
{
use Promptable;
// ...
/**
* Get the agent's structured output schema definition.
*/
public function schema(JsonSchema $schema): array
{
return [
'score' => $schema->integer()->required(),
'metadata' => $schema->object(fn ($schema) => [
'confidence' => $schema->string()->enum(['low', 'medium', 'high'])->required(),
'language' => $schema->string()->required(),
])->required(),
];
}
}
Arrays of objects
Если agent должен вернуть список structured items, объедините методы array и object:
public function schema(JsonSchema $schema): array
{
return [
'feedback' => $schema->array()
->items(
$schema->object(fn ($schema) => [
'comment' => $schema->string()->required(),
'score' => $schema->integer()->required(),
])
)
->required(),
];
}
Если значение может соответствовать одной из нескольких schemas, используйте метод anyOf:
public function schema(JsonSchema $schema): array
{
return [
'content' => $schema->anyOf([
$schema->object(fn ($schema) => [
'type' => $schema->string()->enum(['article'])->required(),
'title' => $schema->string()->required(),
]),
$schema->object(fn ($schema) => [
'type' => $schema->string()->enum(['image'])->required(),
'url' => $schema->string()->required(),
]),
])->required(),
];
}
Вложения
При prompt можно передать вложения, чтобы модель могла анализировать изображения и документы:
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Files;
$response = (new SalesCoach)->prompt(
'Analyze the attached sales transcript...',
attachments: [
Files\Document::fromStorage('transcript.pdf'),
Files\Document::fromPath('/home/laravel/transcript.md'),
$request->file('transcript'),
]
);
Класс Laravel\Ai\Files\Image используется для прикрепления изображений:
use App\Ai\Agents\ImageAnalyzer;
use Laravel\Ai\Files;
$response = (new ImageAnalyzer)->prompt(
'What is in this image?',
attachments: [
Files\Image::fromStorage('photo.jpg'),
Files\Image::fromPath('/home/laravel/photo.jpg'),
$request->file('photo'),
]
);
Потоковая передача
Ответ агента можно передавать потоком через метод stream. Возвращаемый StreamableAgentResponse можно вернуть из маршрута, чтобы автоматически отправить клиенту streaming response (SSE):
use App\Ai\Agents\SalesCoach;
Route::get('/coach', function () {
return (new SalesCoach)->stream('Analyze this sales transcript...');
});
Метод then позволяет выполнить замыкание после завершения потока:
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Responses\StreamedAgentResponse;
Route::get('/coach', function () {
return (new SalesCoach)
->stream('Analyze this sales transcript...')
->then(function (StreamedAgentResponse $response) {
// $response->text, $response->events, $response->usage...
});
});
Также можно вручную итерироваться по streamed events:
$stream = (new SalesCoach)->stream('Analyze this sales transcript...');
foreach ($stream as $event) {
// ...
}
Потоковая передача через протокол Vercel AI SDK
Чтобы передавать события через stream protocol Vercel AI SDK, вызовите usingVercelDataProtocol:
use App\Ai\Agents\SalesCoach;
Route::get('/coach', function () {
return (new SalesCoach)
->stream('Analyze this sales transcript...')
->usingVercelDataProtocol();
});
Broadcasting
Streamed events можно транслировать несколькими способами. Например, вызвать broadcast или broadcastNow на streamed event:
use App\Ai\Agents\SalesCoach;
use Illuminate\Broadcasting\Channel;
$stream = (new SalesCoach)->stream('Analyze this sales transcript...');
foreach ($stream as $event) {
$event->broadcast(new Channel('channel-name'));
}
Или вызвать broadcastOnQueue, чтобы поставить операцию агента в очередь и транслировать streamed events по мере готовности:
(new SalesCoach)->broadcastOnQueue(
'Analyze this sales transcript...',
new Channel('channel-name'),
);
Пропуск слишком больших событий
Некоторые broadcasting-платформы ограничивают WebSocket-сообщения примерно 10KB. События stream с большим объемом данных, например крупные результаты tools, могут превысить этот лимит и привести к ошибке broadcasting. Вы можете исключить определенные типы событий из broadcasting с помощью атрибута WithoutBroadcasting:
<?php
namespace App\Ai\Agents;
use Laravel\Ai\Attributes\WithoutBroadcasting;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;
use Laravel\Ai\Streaming\Events\ToolCall;
use Laravel\Ai\Streaming\Events\ToolResult;
#[WithoutBroadcasting(ToolCall::class, ToolResult::class)]
class SearchAgent implements Agent, HasTools
{
use Promptable;
// ...
}
Исключенные события никогда не транслируются, но по-прежнему записываются в таблицу agent_conversation_messages, поэтому ваш фронтенд сможет загрузить полные данные tool после завершения stream. Это работает как для queued broadcasting (broadcastOnQueue), так и для синхронного broadcasting (broadcast / broadcastNow).
Очереди
Метод queue позволяет отправить prompt агенту и обработать ответ в фоне, сохраняя приложение быстрым и отзывчивым. Методы then и catch регистрируют callbacks, которые вызываются при готовом ответе или исключении:
use Illuminate\Http\Request;
use Laravel\Ai\Responses\AgentResponse;
use Throwable;
Route::post('/coach', function (Request $request) {
(new SalesCoach)
->queue($request->input('transcript'))
->then(function (AgentResponse $response) {
// ...
})
->catch(function (Throwable $e) {
// ...
});
return back();
});
Инструменты
Tools дают агентам дополнительные возможности, которыми они могут пользоваться при ответе. Tool создается Artisan-командой make:tool:
php artisan make:tool RandomNumberGenerator
Сгенерированный tool будет размещен в app/Ai/Tools. Каждый tool содержит метод handle, вызываемый агентом при необходимости:
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;
class RandomNumberGenerator implements Tool
{
public function description(): Stringable|string
{
return 'This tool may be used to generate cryptographically secure random numbers.';
}
public function handle(Request $request): Stringable|string
{
return (string) random_int($request['min'], $request['max']);
}
public function schema(JsonSchema $schema): array
{
return [
'min' => $schema->integer()->min(0)->required(),
'max' => $schema->integer()->required(),
];
}
}
Tool возвращается из метода tools любого агента:
use App\Ai\Tools\RandomNumberGenerator;
public function tools(): iterable
{
return [
new RandomNumberGenerator,
];
}
Similarity Search
Tool SimilaritySearch позволяет агентам искать документы, похожие на запрос, используя vector embeddings из базы данных. Это удобно для retrieval-augmented generation (RAG), когда агенту нужен доступ к данным приложения.
Самый простой способ создать similarity search tool – метод usingModel с Eloquent-моделью, имеющей vector embeddings:
use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;
public function tools(): iterable
{
return [
SimilaritySearch::usingModel(Document::class, 'embedding'),
];
}
Первый аргумент – класс Eloquent-модели, второй – столбец с vector embeddings. Можно указать минимальный порог сходства, лимит и замыкание для настройки запроса:
SimilaritySearch::usingModel(
model: Document::class,
column: 'embedding',
minSimilarity: 0.7,
limit: 10,
query: fn ($query) => $query->where('published', true),
),
Для полного контроля создайте tool с пользовательским замыканием:
use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;
public function tools(): iterable
{
return [
new SimilaritySearch(using: function (string $query) {
return Document::query()
->where('user_id', $this->user->id)
->whereVectorSimilarTo('embedding', $query)
->limit(10)
->get();
}),
];
}
Описание tool можно настроить методом withDescription:
SimilaritySearch::usingModel(Document::class, 'embedding')
->withDescription('Search the knowledge base for relevant articles.'),
Инструменты файлового хранилища
Фабрика tools FileStorage позволяет предоставить agents доступ к диску файловой системы Laravel. Метод all возвращает tools, которые позволяют agent просматривать список файлов, читать, инспектировать, генерировать URL, записывать, удалять и копировать файлы на указанном диске:
use Laravel\Ai\Tools\FileStorage;
public function tools(): iterable
{
return FileStorage::all('local');
}
Если agent должен иметь возможность только инспектировать файлы, используйте метод readOnly:
return FileStorage::readOnly('local');
Эти методы возвращают Illuminate\Support\Collection, что позволяет дополнительно фильтровать tools, предоставляемые agent:
use Laravel\Ai\Tools\Filesystem\DeleteFile;
return FileStorage::all('s3')
->reject(fn ($tool) => $tool instanceof DeleteFile);
MCP Tools
Если приложение использует Laravel MCP, вы можете предоставить agents tools, опубликованные серверами Model Context Protocol. С помощью Laravel MCP client можно подключиться к remote или local MCP server и передать его tools напрямую agent.
MCP tools требуют, чтобы в приложении был установлен package Laravel MCP.
Поскольку метод tools MCP client возвращает collection, разверните ее в массив tools вашего agent с помощью оператора ...:
use App\Ai\Tools\RandomNumberGenerator;
use Laravel\Mcp\Client;
/**
* Get the tools available to the agent.
*
* @return Tool[]
*/
public function tools(): iterable
{
return [
...Client::web('https://mcp.example.com')
->withToken($token)
->tools(),
new RandomNumberGenerator,
];
}
AI SDK автоматически оборачивает каждый MCP tool, чтобы agent мог вызывать его как любой другой tool. Также можно использовать именованный MCP client:
use Laravel\Mcp\Facades\Mcp;
public function tools(): iterable
{
return [
...Mcp::client('github')->tools(),
];
}
Или подключиться к локальному MCP server:
use Laravel\Mcp\Client;
public function tools(): iterable
{
return [
...Client::local('php', ['artisan', 'mcp:start'])->tools(),
];
}
Подробнее о создании и аутентификации MCP-клиентов, включая bearer-токены и OAuth, смотрите в документации MCP-клиента.
Инструменты провайдеров
Инструменты провайдеров – специальные инструменты, нативно реализованные AI-провайдерами. Они дают возможности вроде веб-поиска, получения содержимого URL и поиска по файлам. В отличие от обычных инструментов, инструменты провайдеров выполняются самим провайдером, а не вашим приложением, и возвращаются из метода tools агента.
Web Search
Provider tool WebSearch позволяет агентам искать в интернете актуальную информацию. Это полезно для вопросов о текущих событиях, свежих данных или темах, которые могли измениться после даты обучения модели.
Поддерживаемые провайдеры: Anthropic, OpenAI, Gemini, OpenRouter
use Laravel\Ai\Providers\Tools\WebSearch;
public function tools(): iterable
{
return [
new WebSearch,
];
}
Поиск можно ограничить количеством запросов или доменами:
(new WebSearch)->max(5)->allow(['laravel.com', 'php.net']),
Для уточнения результатов по местоположению используйте location:
(new WebSearch)->location(
city: 'New York',
region: 'NY',
country: 'US'
);
Web Fetch
Provider tool WebFetch позволяет агентам получать и читать содержимое веб-страниц. Это полезно, когда агент должен анализировать конкретные URL или получить подробную информацию с известных страниц.
Поддерживаемые провайдеры: Anthropic, Gemini
use Laravel\Ai\Providers\Tools\WebFetch;
public function tools(): iterable
{
return [
new WebFetch,
];
}
Количество fetch-запросов и домены можно ограничить:
(new WebFetch)->max(3)->allow(['docs.laravel.com']),
File Search
Provider tool FileSearch позволяет агентам искать по файлам, сохраненным в vector stores. Это включает RAG-сценарии, где агент ищет релевантную информацию в загруженных документах.
Поддерживаемые провайдеры: OpenAI, Gemini
use Laravel\Ai\Providers\Tools\FileSearch;
public function tools(): iterable
{
return [
new FileSearch(stores: ['store_id']),
];
}
Можно указать несколько vector store IDs:
new FileSearch(stores: ['store_1', 'store_2']);
Если у файлов есть metadata, результаты можно фильтровать через аргумент where:
new FileSearch(stores: ['store_id'], where: [
'author' => 'Taylor Otwell',
'year' => 2026,
]);
Для сложных фильтров передайте замыкание с FileSearchQuery:
use Laravel\Ai\Providers\Tools\FileSearchQuery;
new FileSearch(stores: ['store_id'], where: fn (FileSearchQuery $query) =>
$query->where('author', 'Taylor Otwell')
->whereNot('status', 'draft')
->whereIn('category', ['news', 'updates'])
);
Sub-agents
Agents также можно возвращать из метода tools другого agent. Когда agent возвращается как tool, parent agent может делегировать sub-agent конкретную задачу и использовать response sub-agent при ответе на исходный prompt. Это удобно, когда general-purpose agent должен иметь доступ к specialized agents со своими instructions, tools, model configuration или provider preferences.
Например, customer support agent может делегировать вопросы о refund eligibility отдельному refunds agent:
<?php
namespace App\Ai\Agents;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;
class CustomerSupportAgent implements Agent, HasTools
{
use Promptable;
/**
* Get the instructions that the agent should follow.
*/
public function instructions(): string
{
return 'You help customers with account, order, and billing questions. Delegate refund policy questions to the refunds specialist.';
}
/**
* Get the tools available to the agent.
*
* @return Tool[]
*/
public function tools(): iterable
{
return [
new RefundsAgent,
];
}
}
Чтобы настроить, как sub-agent будет представлен parent agent, реализуйте interface CanActAsTool на sub-agent и определите name и description, видимые как tool:
<?php
namespace App\Ai\Agents;
use App\Ai\Tools\LookupOrder;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\CanActAsTool;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;
#[Provider(Lab::Anthropic)]
class RefundsAgent implements Agent, CanActAsTool, HasTools
{
use Promptable;
/**
* Get the instructions that the agent should follow.
*/
public function instructions(): string
{
return 'You are a refunds specialist. Use order details and the refund policy to give concise eligibility guidance.';
}
/**
* Get the agent's tool name.
*/
public function name(): string
{
return 'refunds_specialist';
}
/**
* Get the agent's tool description.
*/
public function description(): string
{
return 'Determine whether an order is eligible for a refund and explain the next step.';
}
/**
* Get the tools available to the agent.
*
* @return Tool[]
*/
public function tools(): iterable
{
return [
new LookupOrder,
];
}
}
Если sub-agent не реализует CanActAsTool, Laravel использует class basename agent как tool name и generic description, который просит parent agent передать clear, self-contained task description. Каждый вызов sub-agent выполняется изолированно и не получает conversation history parent agent.
Middleware
Агенты поддерживают middleware, позволяя перехватывать и изменять prompts до отправки провайдеру. Middleware создается командой make:agent-middleware:
php artisan make:agent-middleware LogPrompts
Чтобы добавить middleware к агенту, реализуйте HasMiddleware и верните список middleware:
use App\Ai\Middleware\LogPrompts;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasMiddleware;
use Laravel\Ai\Promptable;
class SalesCoach implements Agent, HasMiddleware
{
use Promptable;
public function middleware(): array
{
return [
new LogPrompts,
];
}
}
Middleware-класс определяет метод handle, получающий AgentPrompt и Closure для передачи запроса дальше:
use Closure;
use Laravel\Ai\Prompts\AgentPrompt;
class LogPrompts
{
public function handle(AgentPrompt $prompt, Closure $next)
{
Log::info('Prompting agent', ['prompt' => $prompt->prompt]);
return $next($prompt);
}
}
Метод then на response позволяет выполнить код после завершения обработки, как для синхронных, так и для streaming responses:
public function handle(AgentPrompt $prompt, Closure $next)
{
return $next($prompt)->then(function (AgentResponse $response) {
Log::info('Agent responded', ['text' => $response->text]);
});
}
Анонимные агенты
Иногда нужно быстро обратиться к модели без отдельного класса агента. Для этого используйте функцию agent:
use function Laravel\Ai\{agent};
$response = agent(
instructions: 'You are an expert at software development.',
messages: [],
tools: [],
)->prompt('Tell me about Laravel')
Анонимные агенты также могут возвращать структурированный вывод:
use Illuminate\Contracts\JsonSchema\JsonSchema;
use function Laravel\Ai\{agent};
$response = agent(
schema: fn (JsonSchema $schema) => [
'number' => $schema->integer()->required(),
],
)->prompt('Generate a random number less than 100')
Конфигурация агента
Параметры генерации текста можно задать через PHP-атрибуты:
MaxSteps: максимальное количество шагов агента при использовании tools.MaxTokens: максимальное количество tokens, которое может сгенерировать модель.Model: модель агента.Provider: AI-провайдер или список провайдеров для failover.Temperature: sampling temperature для генерации (от0.0до1.0).Timeout: HTTP timeout в секундах (по умолчанию 60).TopP: nucleus sampling probability для генерации (от0.0до1.0).UseCheapestModel: использовать самую дешевую text-модель провайдера.UseSmartestModel: использовать самую мощную text-модель провайдера.
use Laravel\Ai\Attributes\MaxSteps;
use Laravel\Ai\Attributes\MaxTokens;
use Laravel\Ai\Attributes\Model;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Attributes\Temperature;
use Laravel\Ai\Attributes\Timeout;
use Laravel\Ai\Attributes\TopP;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;
#[Provider(Lab::Anthropic)]
#[Model('claude-sonnet-5')]
#[MaxSteps(10)]
#[MaxTokens(4096)]
#[Temperature(0.7)]
#[Timeout(120)]
#[TopP(0.9)]
class SalesCoach implements Agent
{
use Promptable;
}
Атрибуты UseCheapestModel и UseSmartestModel автоматически выбирают наиболее экономичную или мощную модель без явного имени модели:
use Laravel\Ai\Attributes\UseCheapestModel;
use Laravel\Ai\Attributes\UseSmartestModel;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;
#[UseCheapestModel]
class SimpleSummarizer implements Agent
{
use Promptable;
}
#[UseSmartestModel]
class ComplexReasoner implements Agent
{
use Promptable;
}
Underlying model, выбранная
UseCheapestModelиUseSmartestModel, может изменяться между релизами Laravel AI SDK по мере выхода новых моделей у провайдеров. Смена модели может приводить к behavioral changes, deprecated parameters и существенной разнице в стоимости. Если вам нужны стабильная, предсказуемая model и pricing, явно укажите model с помощью атрибутаModel.
Опции провайдера
Если агенту нужно передать опции конкретного провайдера, например OpenAI reasoning effort или настройки штрафов, реализуйте HasProviderOptions и метод providerOptions:
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasProviderOptions;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;
class SalesCoach implements Agent, HasProviderOptions
{
use Promptable;
public function providerOptions(Lab|string $provider): array
{
return match ($provider) {
Lab::OpenAI => [
'reasoning' => ['effort' => 'low'],
'frequency_penalty' => 0.5,
'presence_penalty' => 0.3,
],
Lab::Anthropic => [
'thinking' => ['budget_tokens' => 1024],
'cache_control' => ['type' => 'ephemeral'],
],
default => [],
};
}
}
Метод получает текущего провайдера (Lab enum или строку), поэтому можно возвращать разные опции для каждого провайдера. Это особенно полезно с failover, где каждый fallback-провайдер может иметь свою конфигурацию.
Пример Anthropic выше также включает prompt caching через cache_control.
Подтверждение инструментов человеком
Подтверждение инструментов требует агента
Conversational, история разговора которого сохраняется, чтобы приостановленный вызов можно было возобновить. ТрейтRemembersConversationsпредоставляет необходимое сохранение.
Инструменты, выполняющие чувствительные или необратимые действия, могут требовать подтверждения человеком перед выполнением. Чтобы инструмент можно было подтверждать, реализуйте контракт Approvable и используйте трейт InteractsWithApprovals. Подтверждаемые инструменты по умолчанию требуют подтверждения:
<?php
namespace App\Ai\Tools;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Storage;
use Laravel\Ai\Concerns\InteractsWithApprovals;
use Laravel\Ai\Contracts\Approvable;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;
class DeleteFile implements Approvable, Tool
{
use InteractsWithApprovals;
/**
* Get the description of the tool's purpose.
*/
public function description(): Stringable|string
{
return 'Delete a file from storage.';
}
/**
* Execute the tool.
*/
public function handle(Request $request): Stringable|string
{
Storage::delete($request['path']);
return "Deleted [{$request['path']}].";
}
/**
* Get the tool's schema definition.
*/
public function schema(JsonSchema $schema): array
{
return [
'path' => $schema->string()->required(),
];
}
}
Чтобы определить, нужно ли подтверждение на основе аргументов вызова инструмента, определите в инструменте метод needsApproval. Этот метод может вернуть boolean или экземпляр Approval, включающий причину запроса подтверждения:
use Laravel\Ai\Approvals\Approval;
/**
* Determine whether the tool needs approval for the given request.
*/
protected function needsApproval(Request $request): Approval|bool
{
return str_starts_with($request['path'], 'temporary/')
? false
: Approval::required('This will permanently delete a file.');
}
Вы можете переопределить требование подтверждения инструмента при возврате его из метода tools агента:
public function tools(): iterable
{
return [
(new SendNotification)->withoutApproval(),
(new DeleteFile)->requireApproval('Deletion review required.'),
];
}
Когда вызывается подтверждаемый инструмент, агент приостанавливается перед его выполнением. Pending approvals можно изучить в response: они содержат ID каждого tool call, имя инструмента, аргументы и причину подтверждения:
$response = (new FileAssistant)
->forUser($user)
->prompt('Delete the old invoice.');
if ($response->hasPendingApprovals()) {
foreach ($response->pendingApprovals as $approval) {
// $approval->id
// $approval->tool
// $approval->arguments
// $approval->reason
}
}
Чтобы возобновить агента, продолжите разговор и передайте экземпляр Decisions, содержащий решение для каждого ожидающего подтверждения tool call. Решения могут подтвердить вызов, отклонить его или изменить аргументы перед выполнением:
use Laravel\Ai\Approvals\Decision;
use Laravel\Ai\Approvals\Decisions;
$response = (new FileAssistant)
->continue($conversationId, as: $user)
->prompt(Decisions::from([
'call_abc' => Decision::approve(),
'call_ghi' => Decision::reject('The invoice must be retained.'),
]));
Boolean-значения true и false можно использовать как сокращения для подтверждения и отклонения. Каждый ожидающий подтверждения tool call должен получить решение. Неизвестные, отсутствующие или уже разрешенные ID tool call приведут к исключению ApprovalMismatchException. Для вызовов без явного решения можно задать значение по умолчанию с помощью методов approveRemaining или rejectRemaining:
$decisions = Decisions::from([
'call_abc' => true,
])->rejectRemaining('Not approved.');
$response = (new FileAssistant)
->continue($conversationId, as: $user)
->prompt($decisions);
Отклонение с результатом, например Decision::reject('Not approved.'), возвращается модели, чтобы она могла продолжить ответ. Отклонение без результата останавливает цикл генерации после записи отклонения.
Подтверждение инструментов поддерживается методами prompt, stream, queue, broadcast, broadcastNow и broadcastOnQueue.
Во время streaming и broadcasting пауза представляется событием tool_approval_request. При использовании stream protocol Vercel AI SDK запросы подтверждения и результаты отправляются с помощью нативных частей протокола для подтверждения инструментов.
Для агентов, поставленных в очередь, итоговый response передается в callback then, а Laravel также отправляет событие ToolApprovalRequested.
Laravel сохраняет результат подтвержденного инструмента перед тем, как попросить модель продолжить. Если после этого генерация завершится ошибкой, подтверждение уже будет разрешено. Продолжайте разговор обычным текстовым prompt вместо повторной отправки тех же решений.
Полный процесс подтверждения
Следующие маршруты демонстрируют полный процесс подтверждения. Маршрут GET возвращает экран чата, а маршрут POST принимает либо новый текстовый prompt, либо решения подтверждения с экрана чата. Этот пример предполагает, что модель User приложения использует трейт HasConversations:
use App\Ai\Agents\FileAssistant;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Route;
use Illuminate\Validation\Rule;
use Laravel\Ai\Approvals\Decision;
use Laravel\Ai\Approvals\Decisions;
use Laravel\Ai\Models\Conversation;
Route::get('/chat/{conversation}', function (Request $request, Conversation $conversation) {
Gate::authorize('view', $conversation);
return view('chat', [
'conversation' => $conversation,
]);
})->middleware('auth');
Route::post('/chat/{conversation}', function (Request $request, Conversation $conversation) {
Gate::authorize('view', $conversation);
$validated = $request->validate([
'message' => ['nullable', 'string', 'required_without:decisions', 'prohibited_with:decisions'],
'decisions' => ['nullable', 'array', 'required_without:message', 'prohibited_with:message'],
'decisions.*.action' => ['required_with:decisions', Rule::in(['approve', 'reject'])],
'decisions.*.result' => ['nullable', 'string'],
]);
$prompt = isset($validated['decisions'])
? Decisions::from($validated->collect('decisions')->map(
fn (array $decision) => match ($decision['action']) {
'approve' => Decision::approve(),
'reject' => Decision::reject($decision['result'] ?? null),
}
)->all())
: $validated['message'];
$response = (new FileAssistant)
->continue($conversation->id, as: $request->user())
->prompt($prompt);
return [
'conversation_id' => $response->conversationId,
'status' => $response->hasPendingApprovals() ? 'awaiting_approval' : 'complete',
'message' => $response->text,
'approvals' => $response->pendingApprovals,
];
})->middleware('auth');
Когда статус response равен awaiting_approval, экран чата должен отобразить pending approvals и отправить выбор пользователя в тот же endpoint, используя ID tool call как ключ каждого решения:
{
"decisions": {
"call_abc": {
"action": "approve"
},
"call_def": {
"action": "reject",
"result": "The invoice must be retained."
}
}
}
Для обычного сообщения чата экран вместо этого может отправить значение message:
{
"message": "Delete the old invoice."
}
Изображения
Класс Laravel\Ai\Image используется для генерации изображений через провайдеры openai, gemini или xai:
use Laravel\Ai\Image;
$image = Image::of('A donut sitting on the kitchen counter')->generate();
$rawContent = (string) $image;
Методы square, portrait и landscape управляют aspect ratio, quality задает желаемое качество (high, medium, low), а timeout задает HTTP timeout:
$image = Image::of('A donut sitting on the kitchen counter')
->quality('high')
->landscape()
->timeout(120)
->generate();
Можно прикреплять reference images:
use Laravel\Ai\Files;
use Laravel\Ai\Image;
$image = Image::of('Update this photo of me to be in the style of an impressionist painting.')
->attachments([
Files\Image::fromStorage('photo.jpg'),
// Files\Image::fromPath('/home/laravel/photo.jpg'),
// Files\Image::fromUrl('https://example.com/photo.jpg'),
// $request->file('photo'),
])
->landscape()
->generate();
Сгенерированные изображения легко сохранить на default disk из config/filesystems.php:
$image = Image::of('A donut sitting on the kitchen counter');
$path = $image->store();
$path = $image->storeAs('image.jpg');
$path = $image->storePublicly();
$path = $image->storePubliclyAs('image.jpg');
Генерацию изображений можно поставить в очередь:
use Laravel\Ai\Image;
use Laravel\Ai\Responses\ImageResponse;
Image::of('A donut sitting on the kitchen counter')
->portrait()
->queue()
->then(function (ImageResponse $image) {
$path = $image->store();
});
Аудио
Класс Laravel\Ai\Audio генерирует аудио из текста:
use Laravel\Ai\Audio;
$audio = Audio::of('I love coding with Laravel.')->generate();
$rawContent = (string) $audio;
Также можно сгенерировать аудио из строки с помощью метода toAudio, доступного через Laravel Stringable class:
use Illuminate\Support\Str;
$audio = Str::of('I love coding with Laravel.')->toAudio();
Методы male, female и voice задают голос:
$audio = Audio::of('I love coding with Laravel.')
->female()
->generate();
$audio = Audio::of('I love coding with Laravel.')
->voice('voice-id-or-name')
->generate();
Метод instructions позволяет динамически подсказать модели, как должно звучать аудио:
$audio = Audio::of('I love coding with Laravel.')
->female()
->instructions('Said like a pirate')
->generate();
Аудио можно сохранить на default disk и генерировать через очередь:
$audio = Audio::of('I love coding with Laravel.')->generate();
$path = $audio->store();
$path = $audio->storeAs('audio.mp3');
$path = $audio->storePublicly();
$path = $audio->storePubliclyAs('audio.mp3');
use Laravel\Ai\Audio;
use Laravel\Ai\Responses\AudioResponse;
Audio::of('I love coding with Laravel.')
->queue()
->then(function (AudioResponse $audio) {
$path = $audio->store();
});
Транскрипции
Класс Laravel\Ai\Transcription создает transcript для аудио:
use Laravel\Ai\Transcription;
$transcript = Transcription::fromPath('/home/laravel/audio.mp3')->generate();
$transcript = Transcription::fromStorage('audio.mp3')->generate();
$transcript = Transcription::fromUpload($request->file('audio'))->generate();
return (string) $transcript;
Метод diarize добавляет diarized transcript, сегментированный по говорящим:
$transcript = Transcription::fromStorage('audio.mp3')
->diarize()
->generate();
Транскрипцию также можно поставить в очередь:
use Laravel\Ai\Transcription;
use Laravel\Ai\Responses\TranscriptionResponse;
Transcription::fromStorage('audio.mp3')
->queue()
->then(function (TranscriptionResponse $transcript) {
// ...
});
Суммаризация текста
Текст можно суммаризировать с помощью метода summarize, доступного через класс Laravel Stringable. По умолчанию summary будет содержать не более трех предложений и будет сгенерирован с использованием самой дешевой текстовой модели настроенного провайдера:
use Illuminate\Support\Str;
$summary = Str::of($article)->summarize();
Можно указать максимальное количество предложений, провайдера, модель и timeout, используемые для генерации summary. Класс Str также предоставляет статическую версию метода:
use Laravel\Ai\Enums\Lab;
$summary = Str::of($article)->summarize(
sentences: 4,
provider: Lab::Anthropic,
model: 'claude-sonnet-5',
timeout: 30,
);
$summary = Str::summarize($article, sentences: 4);
Embeddings
Vector embeddings для строки можно сгенерировать через метод toEmbeddings, доступный в Stringable:
use Illuminate\Support\Str;
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings();
Для нескольких входов используйте класс Embeddings:
use Laravel\Ai\Embeddings;
$response = Embeddings::for([
'Napa Valley has great wine.',
'Laravel is a PHP framework.',
])->generate();
$response->embeddings; // [[0.123, 0.456, ...], [0.789, 0.012, ...]]
Можно указать размеры и провайдера:
$response = Embeddings::for(['Napa Valley has great wine.'])
->dimensions(1536)
->generate(Lab::OpenAI, 'text-embedding-3-small');
Мультимодальные embeddings
Помимо строк, метод Embeddings::for принимает изображения, аудио, документы и видео, позволяя генерировать embeddings для нетекстового контента. Gemini поддерживает embeddings для изображений, аудио, документов и видео, а VoyageAI – для изображений и видео:
use Laravel\Ai\Embeddings;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Files\Image;
use Laravel\Ai\Files\Video;
$response = Embeddings::for([
'A vineyard at sunset.',
Image::fromStorage('vineyard.jpg'),
Video::fromPath('/home/laravel/tour.mp4'),
])->generate(Lab::Gemini);
Мультимодальные входные данные используют те же классы файлов, что и attachments. Такие файлы можно создать из локального пути, filesystem disk, удаленного URL или Base64-encoded content. Изображения, документы и видео также можно создавать из uploaded files, а документы – из raw string content:
use Laravel\Ai\Files\Audio;
use Laravel\Ai\Files\Document;
use Laravel\Ai\Files\Image;
use Laravel\Ai\Files\Video;
Image::fromPath('/home/laravel/photo.jpg');
Image::fromStorage('photo.jpg');
Image::fromUpload($request->file('photo'));
Audio::fromPath('/home/laravel/clip.mp3');
Audio::fromStorage('clip.mp3');
Audio::fromUpload($request->file('clip.mp3'));
Video::fromPath('/home/laravel/video.mp4');
Video::fromStorage('video.mp4');
Video::fromUpload($request->file('video'));
Document::fromUrl('https://example.com/report.pdf');
Document::fromString('Laravel is a PHP framework.', 'text/plain');
Document::fromUpload($request->file('report'));
VoyageAI не позволяет смешивать медиа по удаленным URL и Base64-encoded media в одном запросе. Локальные, сохраненные и загруженные файлы отправляются как Base64-encoded content, а текстовые входные данные можно комбинировать с любым источником медиа. Обратитесь к документации провайдера, чтобы узнать, какие мультимодальные модели и входные данные доступны.
Запросы по embeddings
Обычно embeddings сохраняются в vector-столбце базы данных для последующих запросов. Laravel поддерживает vector-столбцы PostgreSQL через расширение pgvector:
Schema::ensureVectorExtensionExists();
Schema::create('documents', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->vector('embedding', dimensions: 1536);
$table->timestamps();
});
Для ускорения similarity search добавьте vector index. При вызове index Laravel создаст HNSW-индекс с cosine distance:
$table->vector('embedding', dimensions: 1536)->index();
В Eloquent-модели приведите vector-столбец к array:
protected function casts(): array
{
return [
'embedding' => 'array',
];
}
Для поиска похожих записей используйте whereVectorSimilarTo. Метод фильтрует результаты по минимальному cosine similarity (от 0.0 до 1.0) и сортирует по сходству:
use App\Models\Document;
$documents = Document::query()
->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
->limit(10)
->get();
$queryEmbedding может быть массивом floats или обычной строкой. Если передана строка, Laravel автоматически сгенерирует embeddings:
$documents = Document::query()
->whereVectorSimilarTo('embedding', 'best wineries in Napa Valley')
->limit(10)
->get();
Для низкоуровневого контроля используйте whereVectorDistanceLessThan, selectVectorDistance и orderByVectorDistance:
$documents = Document::query()
->select('*')
->selectVectorDistance('embedding', $queryEmbedding, as: 'distance')
->whereVectorDistanceLessThan('embedding', $queryEmbedding, maxDistance: 0.3)
->orderByVectorDistance('embedding', $queryEmbedding)
->limit(10)
->get();
Если агенту нужен similarity search как tool, смотрите раздел Similarity Search.
Vector-запросы сейчас поддерживаются только на PostgreSQL-соединениях с расширением
pgvector.
Кеширование embeddings
Генерацию embeddings можно кешировать, чтобы избежать повторных API-вызовов для одинаковых входов. Для включения кеша установите ai.caching.embeddings.cache в true:
'caching' => [
'embeddings' => [
'cache' => true,
'store' => env('CACHE_STORE', 'database'),
// ...
],
],
При включенном кеше embeddings хранятся 30 дней. Ключ кеша строится из провайдера, модели, размерности и входного содержимого, поэтому одинаковые запросы получают кешированные результаты, а разные конфигурации генерируют новые embeddings.
Кеш можно включить для конкретного запроса методом cache, даже если глобально он отключен:
$response = Embeddings::for(['Napa Valley has great wine.'])
->cache()
->generate();
Можно указать duration в секундах:
$response = Embeddings::for(['Napa Valley has great wine.'])
->cache(seconds: 3600)
->generate();
Метод toEmbeddings также принимает аргумент cache:
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: true);
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: 3600);
Реранжирование
Reranking позволяет переупорядочить список документов по релевантности заданному запросу. Это полезно для улучшения результатов поиска через семантическое понимание:
use Laravel\Ai\Reranking;
$response = Reranking::of([
'Django is a Python web framework.',
'Laravel is a PHP web application framework.',
'React is a JavaScript library for building user interfaces.',
])->rerank('PHP frameworks');
$response->first()->document; // "Laravel is a PHP web application framework."
$response->first()->score; // 0.95
$response->first()->index; // 1 (original position)
Метод limit ограничивает количество возвращаемых результатов:
$response = Reranking::of($documents)
->limit(5)
->rerank('search query');
Реранжирование коллекций
Для удобства коллекции Laravel можно rerank через macro rerank. Первый аргумент задает поле или поля, второй – запрос:
$posts = Post::all()
->rerank('body', 'Laravel tutorials');
$reranked = $posts->rerank(['title', 'body'], 'Laravel tutorials');
$reranked = $posts->rerank(
fn ($post) => $post->title.': '.$post->body,
'Laravel tutorials'
);
Также можно ограничить количество результатов и указать provider:
$reranked = $posts->rerank(
by: 'content',
query: 'Laravel tutorials',
limit: 10,
provider: Lab::Cohere
);
Файлы
Класс Laravel\Ai\Files или отдельные file-классы позволяют сохранять файлы у AI-провайдера для последующего использования в разговорах. Это полезно для больших документов или файлов, на которые нужно ссылаться многократно без повторной загрузки:
use Laravel\Ai\Files\Document;
use Laravel\Ai\Files\Image;
$response = Document::fromPath('/home/laravel/document.pdf')->put();
$response = Image::fromPath('/home/laravel/photo.jpg')->put();
$response = Document::fromStorage('document.pdf', disk: 'local')->put();
$response = Image::fromStorage('photo.jpg', disk: 'local')->put();
$response = Document::fromUrl('https://example.com/document.pdf')->put();
$response = Image::fromUrl('https://example.com/photo.jpg')->put();
return $response->id;
Также можно сохранять raw content или uploaded files:
use Laravel\Ai\Files;
use Laravel\Ai\Files\Document;
$stored = Document::fromString('Hello, World!', 'text/plain')->put();
$stored = Document::fromUpload($request->file('document'))->put();
После сохранения файла можно ссылаться на него при генерации текста через agents, не загружая файл повторно:
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Files;
$response = (new SalesCoach)->prompt(
'Analyze the attached sales transcript...'
attachments: [
Files\Document::fromId('file-id')
]
);
Чтобы получить ранее сохраненный файл, используйте метод get у экземпляра файла:
use Laravel\Ai\Files\Document;
$file = Document::fromId('file-id')->get();
$file->id;
$file->mimeType();
Чтобы удалить файл у провайдера, используйте метод delete:
Document::fromId('file-id')->delete();
По умолчанию Files использует default AI provider из config/ai.php. Для большинства операций можно указать другого provider:
$response = Document::fromPath(
'/home/laravel/document.pdf'
)->put(provider: Lab::Anthropic);
Вы можете передать параметры загрузки для конкретного провайдера с помощью метода withProviderOptions. Например, можно указать purpose файла OpenAI:
use Laravel\Ai\Files\Document;
$response = Document::fromPath('/home/laravel/knowledge.txt')
->withProviderOptions(['purpose' => 'assistants'])
->put();
Чтобы задать параметры отдельно для каждого провайдера, передайте замыкание, которое получает текущего провайдера:
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Files\Document;
$response = Document::fromPath('/home/laravel/training.jsonl')
->withProviderOptions(fn (Lab|string $provider) => match ($provider) {
Lab::OpenAI => ['purpose' => 'fine-tune'],
default => [],
})
->put();
Использование сохраненных файлов в разговорах
После сохранения файла у провайдера можно ссылаться на него в agent conversations через fromId у Document или Image:
use App\Ai\Agents\DocumentAnalyzer;
use Laravel\Ai\Files\Document;
$stored = Document::fromPath('/path/to/report.pdf')->put();
$response = (new DocumentAnalyzer)->prompt(
'Summarize this document.',
attachments: [
Document::fromId($stored->id),
],
);
Сохраненные изображения подключаются аналогично:
use Laravel\Ai\Files;
use Laravel\Ai\Files\Image;
$stored = Image::fromPath('/path/to/photo.jpg')->put();
$response = (new ImageAnalyzer)->prompt(
'What is in this image?',
attachments: [
Image::fromId($stored->id),
],
);
Vector stores
Vector stores позволяют создавать searchable collections файлов для retrieval-augmented generation (RAG). Класс Laravel\Ai\Stores предоставляет методы для создания, получения и удаления vector stores:
use Laravel\Ai\Stores;
$store = Stores::create('Knowledge Base');
$store = Stores::create(
name: 'Knowledge Base',
description: 'Documentation and reference materials.',
expiresWhenIdleFor: days(30),
);
return $store->id;
Получить существующее хранилище можно методом get:
use Laravel\Ai\Stores;
$store = Stores::get('store_id');
$store->id;
$store->name;
$store->fileCounts;
$store->ready;
Удалить vector store можно через Stores::delete или экземпляр store:
use Laravel\Ai\Stores;
Stores::delete('store_id');
$store = Stores::get('store_id');
$store->delete();
Добавление файлов в хранилища
После создания vector store добавьте в него файлы методом add. Файлы автоматически индексируются для семантического поиска через file search provider tool:
use Laravel\Ai\Files\Document;
use Laravel\Ai\Stores;
$store = Stores::get('store_id');
$document = $store->add('file_id');
$document = $store->add(Document::fromId('file_id'));
$document = $store->add(Document::fromPath('/path/to/document.pdf'));
$document = $store->add(Document::fromStorage('manual.pdf'));
$document = $store->add($request->file('document'));
$document->id;
$document->fileId;
Note: Обычно при добавлении ранее сохраненного файла в vector store возвращаемый document ID совпадает с ID файла, но некоторые провайдеры могут вернуть новый “document ID”. Поэтому рекомендуется хранить оба ID в базе данных.
К файлам можно добавить metadata, чтобы затем фильтровать результаты при использовании file search provider tool:
$store->add(Document::fromPath('/path/to/document.pdf'), metadata: [
'author' => 'Taylor Otwell',
'department' => 'Engineering',
'year' => 2026,
]);
Удалить файл из store можно методом remove:
$store->remove('file_id');
Удаление файла из vector store не удаляет его из file storage провайдера. Чтобы удалить файл и из store, и из file storage, используйте deleteFile:
$store->remove('file_abc123', deleteFile: true);
Failover
При prompt или генерации медиа можно передать массив providers / models, чтобы автоматически переключаться на резервного провайдера / модель при сбое сервиса или rate limit основного провайдера:
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Image;
$response = (new SalesCoach)->prompt(
'Analyze this sales transcript...',
provider: [Lab::OpenAI, Lab::Anthropic],
);
$image = Image::of('A donut sitting on the kitchen counter')
->generate(provider: [Lab::Gemini, Lab::xAI]);
Failover происходит только когда выброшен FailoverableException, например rate limit (RateLimitedException), перегруженный или недоступный provider (ProviderOverloadedException) или недостаток credits (InsufficientCreditsException). Обычные ошибки, например validation или bad request error, не запускают failover.
Когда вы передаете простой список providers, например [Lab::OpenAI, Lab::Anthropic], каждый provider использует свою default model. Чтобы указать конкретную model для каждого provider в failover chain, передайте associative array с ключами по provider, используя value enum Lab как ключ (enum cases нельзя напрямую использовать как PHP array keys):
use Laravel\Ai\Enums\Lab;
$response = (new SalesCoach)->prompt(
'Analyze this sales transcript...',
provider: [
Lab::Gemini->value => 'gemini-3-flash-preview',
Lab::DeepSeek->value => 'deepseek-v4-pro',
],
);
Тестирование
Агенты
Чтобы подменить ответы агента в тестах, вызовите fake на классе агента. Можно передать список ответов или замыкание:
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Prompts\AgentPrompt;
SalesCoach::fake();
SalesCoach::fake([
'First response',
'Second response',
]);
SalesCoach::fake(function (AgentPrompt $prompt) {
return 'Response for: '.$prompt->prompt;
});
При подмене агента, который возвращает structured output, можно передавать массивы в качестве ответов. Агент вернет structured response с переданными данными:
SalesCoach::fake([
['score' => 87],
]);
Также можно подменить response, ожидающий подтверждения инструмента:
use Laravel\Ai\Approvals\PendingApproval;
use Laravel\Ai\Responses\AgentResponse;
FileAssistant::fake([
AgentResponse::fakeWithPendingApprovals([
new PendingApproval(
id: 'call_abc',
tool: 'DeleteFile',
arguments: ['path' => 'invoice.pdf'],
reason: 'This will permanently delete a file.',
),
]),
]);
$response = (new FileAssistant)->prompt('Delete the invoice.');
$response->hasPendingApprovals(); // true
Note: Если
Agent::fake()вызван для агента со structured output и fake output не был передан явно, Laravel автоматически сгенерирует fake data, соответствующие схеме вывода.
После prompt можно утверждать, какие prompts были получены:
use Laravel\Ai\Prompts\AgentPrompt;
SalesCoach::assertPrompted('Analyze this...');
SalesCoach::assertPrompted(function (AgentPrompt $prompt) {
return $prompt->contains('Analyze');
});
SalesCoach::assertNotPrompted('Missing prompt');
SalesCoach::assertNeverPrompted();
При проверке продолжения с подтверждениями можно изучить решения подтверждений в prompt:
use Laravel\Ai\Approvals\Decisions;
use Laravel\Ai\Prompts\AgentPrompt;
FileAssistant::fake();
(new FileAssistant)->prompt(Decisions::from([
'call_abc' => true,
]));
FileAssistant::assertPrompted(function (AgentPrompt $prompt) {
return $prompt->hasApprovalDecisions()
&& $prompt->approvalDecisions->get('call_abc')->isApproved();
});
Для вызовов, поставленных в очередь, используйте queued assertions:
use Laravel\Ai\QueuedAgentPrompt;
SalesCoach::assertQueued('Analyze this...');
SalesCoach::assertQueued(function (QueuedAgentPrompt $prompt) {
return $prompt->contains('Analyze');
});
SalesCoach::assertNotQueued('Missing prompt');
SalesCoach::assertNeverQueued();
Метод preventStrayPrompts гарантирует, что для каждого обращения к агенту есть fake response:
SalesCoach::fake()->preventStrayPrompts();
Изображения
Генерацию изображений можно подменить методом fake класса Image, после чего доступны assertions по записанным prompts:
use Laravel\Ai\Image;
use Laravel\Ai\Prompts\ImagePrompt;
use Laravel\Ai\Prompts\QueuedImagePrompt;
Image::fake();
Image::fake([
base64_encode($firstImage),
base64_encode($secondImage),
]);
Image::fake(function (ImagePrompt $prompt) {
return base64_encode('...');
});
Image::assertGenerated(function (ImagePrompt $prompt) {
return $prompt->contains('sunset') && $prompt->isLandscape();
});
Image::assertNotGenerated('Missing prompt');
Image::assertNothingGenerated();
Для queued image generations используйте queued assertion methods:
Image::assertQueued(
fn (QueuedImagePrompt $prompt) => $prompt->contains('sunset')
);
Image::assertNotQueued('Missing prompt');
Image::assertNothingQueued();
Чтобы убедиться, что для каждой генерации изображения есть соответствующий fake response, используйте preventStrayImages. Если изображение будет сгенерировано без заданного fake response, будет выброшено исключение:
Image::fake()->preventStrayImages();
Аудио
Аудиогенерацию можно подменить методом fake класса Audio:
use Laravel\Ai\Audio;
use Laravel\Ai\Prompts\AudioPrompt;
use Laravel\Ai\Prompts\QueuedAudioPrompt;
Audio::fake();
Audio::fake([
base64_encode($firstAudio),
base64_encode($secondAudio),
]);
Audio::fake(function (AudioPrompt $prompt) {
return base64_encode('...');
});
Audio::assertGenerated(function (AudioPrompt $prompt) {
return $prompt->contains('Hello') && $prompt->isFemale();
});
Audio::assertNotGenerated('Missing prompt');
Audio::assertNothingGenerated();
Для queued audio generations используйте queued assertion methods:
Audio::assertQueued(
fn (QueuedAudioPrompt $prompt) => $prompt->contains('Hello')
);
Audio::assertNotQueued('Missing prompt');
Audio::assertNothingQueued();
Чтобы убедиться, что для каждой генерации аудио есть соответствующий fake response, используйте preventStrayAudio. Если аудио будет сгенерировано без заданного fake response, будет выброшено исключение:
Audio::fake()->preventStrayAudio();
Транскрипции
Транскрипции можно подменить методом fake класса Transcription:
use Laravel\Ai\Transcription;
use Laravel\Ai\Prompts\TranscriptionPrompt;
use Laravel\Ai\Prompts\QueuedTranscriptionPrompt;
Transcription::fake();
Transcription::fake([
'First transcription text.',
'Second transcription text.',
]);
Transcription::fake(function (TranscriptionPrompt $prompt) {
return 'Transcribed text...';
});
Transcription::assertGenerated(function (TranscriptionPrompt $prompt) {
return $prompt->language === 'en' && $prompt->isDiarized();
});
Transcription::assertNotGenerated(
fn (TranscriptionPrompt $prompt) => $prompt->language === 'fr'
);
Transcription::assertNothingGenerated();
Для queued transcriptions используйте queued assertion methods:
Transcription::assertQueued(
fn (QueuedTranscriptionPrompt $prompt) => $prompt->isDiarized()
);
Transcription::assertNotQueued(
fn (QueuedTranscriptionPrompt $prompt) => $prompt->language === 'fr'
);
Transcription::assertNothingQueued();
Чтобы убедиться, что для каждой транскрипции есть соответствующий fake response, используйте preventStrayTranscriptions. Если транскрипция будет создана без заданного fake response, будет выброшено исключение:
Transcription::fake()->preventStrayTranscriptions();
Embeddings
Генерацию embeddings можно подменить методом fake класса Embeddings:
use Laravel\Ai\Embeddings;
use Laravel\Ai\Prompts\EmbeddingsPrompt;
use Laravel\Ai\Prompts\QueuedEmbeddingsPrompt;
Embeddings::fake();
Embeddings::fake([
[$firstEmbeddingVector],
[$secondEmbeddingVector],
]);
Embeddings::fake(function (EmbeddingsPrompt $prompt) {
return array_map(
fn () => Embeddings::fakeEmbedding($prompt->dimensions),
$prompt->inputs
);
});
Embeddings::assertGenerated(function (EmbeddingsPrompt $prompt) {
return $prompt->contains('Laravel') && $prompt->dimensions === 1536;
});
Embeddings::assertNotGenerated(
fn (EmbeddingsPrompt $prompt) => $prompt->contains('Other')
);
Embeddings::assertNothingGenerated();
Для queued embeddings используйте queued assertion methods:
Embeddings::assertQueued(
fn (QueuedEmbeddingsPrompt $prompt) => $prompt->contains('Laravel')
);
Embeddings::assertNotQueued(
fn (QueuedEmbeddingsPrompt $prompt) => $prompt->contains('Other')
);
Embeddings::assertNothingQueued();
Чтобы убедиться, что для каждой генерации embeddings есть соответствующий fake response, используйте preventStrayEmbeddings. Если embeddings будут сгенерированы без заданного fake response, будет выброшено исключение:
Embeddings::fake()->preventStrayEmbeddings();
Реранжирование
Операции reranking можно подменить методом fake класса Reranking:
use Laravel\Ai\Reranking;
use Laravel\Ai\Prompts\RerankingPrompt;
use Laravel\Ai\Responses\Data\RankedDocument;
Reranking::fake();
Reranking::fake([
[
new RankedDocument(index: 0, document: 'First', score: 0.95),
new RankedDocument(index: 1, document: 'Second', score: 0.80),
],
]);
Reranking::assertReranked(function (RerankingPrompt $prompt) {
return $prompt->contains('Laravel') && $prompt->limit === 5;
});
Reranking::assertNotReranked(
fn (RerankingPrompt $prompt) => $prompt->contains('Django')
);
Reranking::assertNothingReranked();
Файлы
File operations можно подменить методом fake класса Files:
use Laravel\Ai\Files;
Files::fake();
После этого можно проверять uploads и deletions:
use Laravel\Ai\Contracts\Files\StorableFile;
use Laravel\Ai\Files\Document;
Document::fromString('Hello, Laravel!', mimeType: 'text/plain')
->as('hello.txt')
->put();
Files::assertStored(fn (StorableFile $file) =>
(string) $file === 'Hello, Laravel!' &&
$file->mimeType() === 'text/plain';
);
Files::assertNotStored(fn (StorableFile $file) =>
(string) $file === 'Hello, World!'
);
Files::assertNothingStored();
Для deletions можно передать file ID:
Files::assertDeleted('file-id');
Files::assertNotDeleted('file-id');
Files::assertNothingDeleted();
Vector stores
Операции vector store можно подменить методом fake класса Stores. Это автоматически подменит и file operations:
use Laravel\Ai\Stores;
Stores::fake();
Далее можно проверять созданные или удаленные stores:
use Laravel\Ai\Stores;
$store = Stores::create('Knowledge Base');
Stores::assertCreated('Knowledge Base');
Stores::assertCreated(fn (string $name, ?string $description) =>
$name === 'Knowledge Base'
);
Stores::assertNotCreated('Other Store');
Stores::assertNothingCreated();
Для удаления:
Stores::assertDeleted('store_id');
Stores::assertNotDeleted('other_store_id');
Stores::assertNothingDeleted();
Чтобы проверить добавление или удаление файлов из store, используйте assertion methods на экземпляре Store:
Stores::fake();
$store = Stores::get('store_id');
$store->add('added_id');
$store->remove('removed_id');
$store->assertAdded('added_id');
$store->assertRemoved('removed_id');
$store->assertNotAdded('other_file_id');
$store->assertNotRemoved('other_file_id');
Если файл одновременно сохраняется у провайдера и добавляется в vector store, provider ID может быть неизвестен. В этом случае передайте замыкание в assertAdded:
use Laravel\Ai\Contracts\Files\StorableFile;
use Laravel\Ai\Files\Document;
$store->add(Document::fromString('Hello, World!', 'text/plain')->as('hello.txt'));
$store->assertAdded(fn (StorableFile $file) => $file->name() === 'hello.txt');
$store->assertAdded(fn (StorableFile $file) => $file->content() === 'Hello, World!');
События
Laravel AI SDK отправляет разные события, включая:
AddingFileToStoreAgentPromptedAgentStreamedAudioGeneratedCreatingStoreEmbeddingsGeneratedFileAddedToStoreFileDeletedFileRemovedFromStoreFileStoredGeneratingAudioGeneratingEmbeddingsGeneratingImageGeneratingTranscriptionImageGeneratedInvokingToolPromptingAgentRemovingFileFromStoreRerankedRerankingStoreCreatedStoringFileStreamingAgentToolApprovalRequestedToolApprovalResolvedToolInvokedTranscriptionGenerated
Вы можете слушать любые из этих событий, чтобы логировать или сохранять информацию об использовании AI SDK.