メインコンテンツまでスキップ
バージョン: 13.x

Laravel AI SDK

Introduction​

Laravel AI SDK は、OpenAI、Anthropic、Gemini などの AI プロバイダと対話するための統合された表現力豊かな API を提供します。 AI SDK を使用すると、一貫した Laravel フレンドリーなインターフェイスを使用して、ツールと構造化された出力を備えたインテリジェント エージェントの構築、画像の生成、音声の合成と文字起こし、ベクトル埋め込みの作成などを行うことができます。

Installation​

Laravel AI SDK は Composer 経由でインストールできます。

composer require laravel/ai

次に、vendor:publish Artisan コマンドを使用して、AI SDK 構成ファイルと移行ファイルを公開する必要があります。

php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"

最後に、アプリケーションのデータベース移行を実行する必要があります。これにより、AI SDK が会話を保存するために使用する agent_conversations テーブルと agent_conversation_messages テーブルが作成されます。

php artisan migrate

Configuration​

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=
TYPESAFE_API_KEY=
VOYAGEAI_API_KEY=
XAI_API_KEY=

テキスト、画像、オーディオ、文字起こし、埋め込みに使用されるデフォルトのモデルは、アプリケーションの config/ai.php 構成ファイルで構成することもできます。

Custom Base URLs​

デフォルトでは、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_URL'),
],
],

これは、プロキシ サービス (LiteLLM や Azure OpenAI Gateway など) を介して要求をルーティングする場合、または代替エンドポイントを使用する場合に便利です。

次のプロバイダでは、カスタムのベース URL をサポートしています: OpenAI、Anthropic、Gemini、Groq、Cohere、DeepSeek、xAI、OpenRouter、TypeSafe。

OpenAI-Compatible Providers​

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'),
],
],
],

プロバイダへのすべての送信リクエストにカスタム HTTP ヘッダーを追加するには、設定で headers 配列を定義します。これは、エンドポイントが bearer トークン以外に識別用または認証用のヘッダーを必要とする場合に便利です。

'local' => [
'driver' => 'openai-compatible',
'url' => env('LOCAL_AI_URL'),
'key' => env('LOCAL_AI_API_KEY'),
'headers' => [
'X-Tenant-Id' => env('LOCAL_AI_TENANT_ID'),
],
],

OpenAI互換プロバイダは、テキスト生成、ストリーミング、ツール、構造化出力、画像の添付、埋め込み、文字起こし、分類をサポートしています。エンドポイントでリクエストボディに追加のフィールドが必要な場合は、provider options を使って指定してください。

OpenAI-Compatible Embeddings​

任意のエンドポイントには既知のモデルがないため、OpenAI互換プロバイダで embeddings() を使用するには、デフォルトの embeddings モデルを設定する必要があります。固定の次元数を設定することもできます。省略した場合、リクエストは dimensions パラメータなしで送信され、モデル固有の次元数が使用されます。

'local' => [
'driver' => 'openai-compatible',
'url' => env('LOCAL_AI_URL'),
'key' => env('LOCAL_AI_API_KEY'),
'models' => [
'embeddings' => [
'default' => 'text-embedding-qwen3-embedding-0.6b',
'dimensions' => 1024, // optional
],
],
],

OpenAI-Compatible Transcriptions​

同様に、OpenAI互換プロバイダで Transcription を使用するためのデフォルトの文字起こしモデルを設定する必要があります。音声は、標準的な multipart リクエストとしてエンドポイントの /audio/transcriptions ルートにアップロードされます。

'local' => [
'driver' => 'openai-compatible',
'url' => env('LOCAL_AI_URL'),
'key' => env('LOCAL_AI_API_KEY'),
'models' => [
'transcription' => [
'default' => 'whisper-1',
],
],
],

OpenAI互換プロバイダとGroqプロバイダは話者分離をサポートしていません。これらのプロバイダを使用しているときに diarize メソッドを呼び出すと、例外がスローされます。

OpenAI-Compatible Classification​

エンドポイントが OpenAI の decisions endpoint を実装している場合は、OpenAI 互換プロバイダで classification を使用できます。SDK はエンドポイントの /decisions ルートに質問を送信し、image attachments にも対応しています。SDK はエンドポイントがどのモデルに対応しているかを把握していないため、デフォルトの分類モデルを設定するか、classify メソッドにモデルを渡してください。

'local' => [
'driver' => 'openai-compatible',
'url' => env('LOCAL_AI_URL'),
'key' => env('LOCAL_AI_API_KEY'),
'models' => [
'classification' => [
'default' => env('LOCAL_AI_MODEL'),
],
],
],

On-Demand Providers​

マルチテナントアプリケーションの各テナントに対してデータベースに保存された API キーなど、アプリケーションの設定ファイルに定義されていないプロバイダの認証情報を使用する必要がある場合があります。設定配列からプロバイダを作成するには、Ai::build メソッドを使用できます。この配列は、アプリケーションの config/ai.php 設定ファイルにあるプロバイダエントリと同じ構造にしてください。

use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Ai;

$response = (new SalesCoach)->prompt('Analyze this sales transcript...', provider: [
Ai::build([
'driver' => 'anthropic',
'key' => $user->anthropic_key,
]),
]);

オンデマンドプロバイダは、プロバイダを受け付けるあらゆる場所で使用できます。これには failover リストや、画像、音声、文字起こし、埋め込みを生成する場合も含まれます。オンデマンドプロバイダにモデルを指定するには、設定に models 配列を含めます。

$response = (new SalesCoach)->prompt('Analyze this sales transcript...', provider: [
Ai::build(['driver' => 'anthropic', 'key' => $user->anthropic_key]),
Ai::build([
'driver' => 'openai',
'key' => $user->openai_key,
'models' => ['text' => ['default' => 'gpt-6']],
]),
]);

エージェントで常にオンデマンドプロバイダを使用する場合は、エージェントの provider メソッドからプロバイダを返せます。エージェントの使用時にプロバイダが再構築されるため、この方法は queued エージェントにも適しています。

use Laravel\Ai\Ai;
use Laravel\Ai\Providers\Provider;

public function provider(): Provider
{
return Ai::build($this->user->aiConfiguration());
}

オンデマンドプロバイダは、上記の例のように配列で渡してください。オンデマンドプロバイダの設定配列で name を指定する場合、組み込みプロバイダや config/ai.php 設定ファイルで定義したプロバイダと同じ名前を使用してはいけません。

Provider Support​

AI SDK は、その機能全体にわたってさまざまなプロバイダをサポートします。次の表は、各機能で利用できるプロバイダをまとめたものです。

機能プロバイダ
テキストOpenAI, OpenAI Compatible, Anthropic, Gemini, Azure, Bedrock, Groq, xAI, DeepSeek, Mistral, Ollama, OpenRouter, Cohere
画像OpenAI, Gemini, xAI, Azure, Bedrock, OpenRouter
TTSOpenAI, ElevenLabs, Gemini, Mistral, OpenRouter
STTOpenAI, OpenAI Compatible, ElevenLabs, Groq, Mistral, Gemini, OpenRouter
埋め込みOpenAI, OpenAI Compatible, Gemini, Azure, Bedrock, Cohere, Mistral, Jina, VoyageAI, Ollama, OpenRouter
リランキングCohere, Jina, VoyageAI, Bedrock, OpenRouter
分類OpenAI, OpenAI Compatible, TypeSafe, OpenRouter
ファイルOpenAI, Anthropic, Gemini, Azure, OpenRouter

Laravel\Ai\Enums\Lab 列挙型は、プレーン文字列を使用する代わりに、コード全体でプロバイダを参照するために使用できます。

use Laravel\Ai\Enums\Lab;

Lab::Anthropic;
Lab::OpenAI;
Lab::OpenAICompatible;
Lab::Gemini;
// ...

Agents​

エージェントは、Laravel AI SDK で AI プロバイダと対話するための基本的な構成要素です。各エージェントは、大規模な言語モデルと対話するために必要な命令、会話コンテキスト、ツール、出力スキーマをカプセル化する専用の PHP クラスです。エージェントは、一度構成すれば、アプリケーション全体で必要に応じてプロンプトを送信できる、セールス コーチ、ドキュメント アナライザー、サポート ボットなどの専門アシスタントと考えてください。

make:agent Artisan コマンドを使用してエージェントを作成できます。

php artisan make:agent SalesCoach

php artisan make:agent SalesCoach --structured

生成されたエージェント クラス内で、システム プロンプト/指示、メッセージ コンテキスト、利用可能なツール、および出力スキーマ (該当する場合) を定義できます。

<?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) {}

/**
* Get the instructions that the agent should follow.
*/
public function instructions(): Stringable|string
{
return 'You are a sales coach, analyzing transcripts and providing feedback and an overall sales strength score.';
}

/**
* Get the list of messages comprising the conversation so far.
*/
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();
}

/**
* Get the tools available to the agent.
*
* @return Tool[]
*/
public function tools(): iterable
{
return [
new RetrievePreviousTranscripts,
];
}

/**
* Get the agent's structured output schema definition.
*/
public function schema(JsonSchema $schema): array
{
return [
'feedback' => $schema->string()->required(),
'score' => $schema->integer()->min(1)->max(10)->required(),
];
}
}

Prompting​

エージェントにプロンプトを送信するには、まず make メソッドまたは標準のインスタンス化を使用してインスタンスを作成し、次に prompt を呼び出します。

$response = (new SalesCoach)
->prompt('Analyze this sales transcript...');

return (string) $response;

make メソッドはコンテナからエージェントを解決し、自動依存注入を可能にします。エージェントのコンストラクターに引数を渡すこともできます。

$agent = SalesCoach::make(user: $user);

追加の引数を prompt メソッドに渡すことで、プロンプトを送信するときにデフォルトのプロバイダ、モデル、または HTTP タイムアウトをオーバーライドできます。

$response = (new SalesCoach)->prompt(
'Analyze this sales transcript...',
provider: Lab::Anthropic,
model: 'claude-sonnet-5',
timeout: 120,
);

Raw HTTP Responses​

テキスト生成エージェントから返されるすべてのレスポンスには、raw プロパティを介して、基盤となるプロバイダ API 呼び出しの生の HTTP レスポンスが公開されます。これにより、レート制限ヘッダー、リクエスト ID、その他の正確なペイロードフィールドなど、AI SDK の汎用レスポンスには含まれないプロバイダ固有の情報にアクセスできます。

$response = (new SalesCoach)->prompt('Analyze this sales transcript...');

$response->raw; // Illuminate\Http\Client\Response|null

$response->raw->header('X-RateLimit-Remaining-Requests');
$response->raw->json('id');

ツール呼び出しループでは、各ステップが自身のリクエストに対する生のレスポンスを保持します。

foreach ($response->steps as $step) {
$step->raw?->header('X-RateLimit-Remaining-Requests');
}

ストリーミングでレスポンスを返す場合、HTTP クライアントではなく AWS SDK 経由で API を呼び出す Bedrock プロバイダを使用する場合、および withRawResponse で明示的に指定していないフェイクレスポンスの場合、raw プロパティは null です。

Conversation Context​

エージェントが Conversational インターフェイスを実装している場合、該当する場合は、messages メソッドを使用して前の会話コンテキストを返すことができます。

/**
* Get the list of messages comprising the conversation so far.
*/
public function messages(): iterable
{
return $this->user->history()
->latest()
->limit(50)
->get()
->reverse()
->map(fn ($message) => new Message(
$message->role, $message->content,
))->all();
}

エージェントが Conversational インターフェースを実装していない場合は、withMessages メソッドを使って、1回の実行に会話履歴を提供できます。たとえば、アプリケーションのフロントエンドから投稿された履歴などです。

use Laravel\Ai\Messages\Message;

$response = (new SalesCoach)
->withMessages([
new Message('user', 'Analyze this sales transcript...'),
new Message('assistant', 'The rep never asked for the close.'),
])
->prompt('What should they say next time?');

Conversational インターフェースを実装するエージェントは独自に履歴を読み込むため、2つのアプローチを組み合わせると LogicException がスローされます。

Remembering Conversations​

RemembersConversations トレイトを使用する前に、vendor:publish Artisan コマンドで AI SDK のマイグレーションを公開して実行してください。このマイグレーションにより、会話を保存するために必要なデータベーステーブルが作成されます。

Laravel にエージェントの会話履歴を自動的に保存および取得させたい場合は、RemembersConversations トレイトを使用できます。このトレイトは、Conversational インターフェイスを手動で実装せずに、データベースに会話メッセージを永続化する簡単な方法を提供します。

<?php

namespace App\Ai\Agents;

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;

/**
* Get the instructions that the agent should follow.
*/
public function instructions(): string
{
return 'You are a sales coach...';
}
}

RemembersConversations トレイトを使用する場合は、エージェントクラスに messages メソッドを手動で定義しないでください。messages メソッドが存在すると、トレイトの実装より優先され、会話履歴がデータベースから読み込まれなくなります。

ユーザーに対して新しい会話を開始するには、プロンプトを送信する前に forUser メソッドを呼び出します。

$response = (new SalesCoach)->forUser($user)->prompt('Hello!');

$conversationId = $response->conversationId;

会話 ID は応答で返され、将来の参照のために保存できます。 Eloquent を使用してユーザーの会話をすべて取得したい場合は、HasConversations トレイトをユーザー モデルに追加できます。

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Ai\Concerns\HasConversations;

class User extends Authenticatable
{
use HasConversations;
}

トレイトをモデルに追加すると、conversations リレーションを介してユーザーの会話を取得してクエリできます。

$conversations = $user->conversations()
->latest('updated_at')
->paginate(20);

既存の会話を続行するには、continue メソッドを使用します。

$response = (new SalesCoach)
->continue($conversationId, as: $user)
->prompt('Tell me more about that.');

指定した ID が null の場合は新しい会話を開始し、それ以外の場合は指定した会話を継続するために continueOrStart メソッドを使用できます。

$response = (new SalesCoach)
->continueOrStart($conversationId, as: $user)
->prompt('Hello!');

RemembersConversations トレイトを使用すると、プロンプトを送信する際に過去のメッセージが自動的に読み込まれ、会話のコンテキストに含まれます。新しいメッセージ(ユーザーとアシスタントの両方)は、各やり取りの後に自動的に保存されます。各レスポンスには、保存された会話とメッセージの ID も含まれます。

$response->conversationId;
$response->userMessageId;
$response->assistantMessageId;

Conversation Participants​

ユーザーが会話の参加者になることが最も一般的ですが、会話は任意の 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.');

会話に参加する任意の Eloquent モデルに HasConversations トレイトを追加できます。これにより作成される conversations リレーションは、そのモデルのタイプと主キーにスコープされたポリモーフィックリレーションです。また、逆リレーションを通じて会話を所有する参加者にもアクセスできます。

$conversations = $team->conversations;

$participant = $conversation->participant;

アプリケーションで複数の参加者モデルの種類を使用している場合は、保存される参加者タイプがモデルクラス名に依存しないよう、Eloquent morph map の定義を検討してください。

continue メソッドと continueOrStart メソッドは、指定された参加者がその会話の所有者であるかを確認しません。会話を続行する前に、アプリケーションで会話へのアクセスを認可してください。

Inspecting Stored Conversations​

ユーザーに会話を表示する際は、メッセージ ID、タイムスタンプ、ツール呼び出しなどの詳細が必要になることがよくあります。AI SDK のテーブルを直接クエリすることなく、サービスコンテナから会話ストアを解決して保存済みのメッセージを読み取れます。

use Laravel\Ai\Contracts\ConversationStore;

$store = app(ConversationStore::class);

メッセージはカーソルを使用して新しいものから順にページネーションされ、各メッセージの ID、タイムスタンプ、使用量、メタデータ、添付ファイルを含む StoredMessage インスタンスとして返されます。

$messages = $store->paginateConversationMessages($conversationId, perPage: 25);

foreach ($messages as $message) {
$message->id;
$message->role;
$message->content;
$message->createdAt;
$message->usage;
$message->status;
}

各ターンは、ユーザーのプロンプトとアシスタントの返信で構成され、ステップのリストとして保存されます。1つのステップはプロバイダへの単一のリクエストです。そのため、モデルがツールを呼び出すターンには複数のステップが含まれます。各ツールの結果は、その結果を生成したツール呼び出しに記録されます。toolCalls、providerToolCalls、toolResults メソッドは、これらのステップを順番に平坦化するため、自分でたどる必要はありません。

$message->steps;

$message->toolCalls();
$message->providerToolCalls();
$message->toolResults();

ツール呼び出しは、実行されると result を含みます。approval_reason は含むものの result を含まないツール呼び出しは、まだ tool approval を待機中です。

status プロパティには、Laravel\Ai\Enums\MessageStatus のインスタンスが含まれます。途中で失敗したターンは、それまでに完了したステップとともに Failed として保存されるため、失敗する前に実行されたツール呼び出しは履歴に残ります。会話を続行するとき、実行結果が記録されていないツール呼び出しは、中断されたものとしてモデルに送信されます。Laravel には、そのツール呼び出しが実行されたかどうかを判断できないためです。

アプリケーションのフロントエンドから提供された ID を使って会話を続行する前に、その会話が指定された参加者のものとして保存されていることを確認してください。

abort_unless($store->conversationBelongsTo(
$conversationId, $user->getMorphClass(), $user->getKey()
), 403);

最新のターンが tool approval の承認待ちで一時停止している場合は、実行を再開せずにページを再読み込みした後、保留中のツール呼び出しを表示できます。

foreach ($store->pendingApprovalsFor($conversationId) as $approval) {
// $approval->id, $approval->tool, $approval->arguments, $approval->reason...
}

これらのメソッドは、PaginatesConversations、VerifiesConversationOwnership、ResolvesPendingApprovals の各コントラクトで定義されています。付属のデータベースストアは3つすべてを実装していますが、カスタムストアでは必要なコントラクトのみを実装できます。

Structured Output​

エージェントが構造化された出力を返すようにするには、HasStructuredOutput インターフェイスを実装します。これには、エージェントが schema メソッドを定義する必要があります。

<?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(),
];
}
}

構造化された出力を返すエージェントにプロンプトを送信すると、返された StructuredAgentResponse に配列のようにアクセスできます。

$response = (new SalesCoach)->prompt('Analyze this sales transcript...');

return $response['score'];

Nested Objects​

ネストされた構造化出力を定義するには、クロージャを指定した object メソッドを使用します。

<?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​

エージェントが構造化アイテムのリストを返す必要がある場合は、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(),
];
}

値が複数のスキーマのいずれかに一致する可能性がある場合は、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(),
];
}

Attachments​

プロンプトを送信するときに、プロンプトとともに添付ファイルを渡して、モデルが画像やドキュメントを検査できるようにすることもできます。

use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Files;

$response = (new SalesCoach)->prompt(
'Analyze the attached sales transcript...',
attachments: [
Files\Document::fromStorage('transcript.pdf'), // Attach a document from a filesystem disk...
Files\Document::fromPath('/home/laravel/transcript.md'), // Attach a document from a local path...
$request->file('transcript'), // Attach an uploaded file...
]
);

同様に、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'), // Attach an image from a filesystem disk...
Files\Image::fromPath('/home/laravel/photo.jpg'), // Attach an image from a local path...
$request->file('photo'), // Attach an uploaded file...
]
);

Streaming​

stream メソッドを呼び出すことで、エージェントの応答をストリーミングできます。返された StreamableAgentResponse をルートから返すと、ストリーミング応答 (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...
});
});

あるいは、ストリーミングされたイベントを手動で反復処理することもできます。

$stream = (new SalesCoach)->stream('Analyze this sales transcript...');

foreach ($stream as $event) {
// ...
}

レスポンスには、モデルの推論と引用したソースも含まれます。RemembersConversations トレイトを使用すると、これらはアシスタントメッセージとともに保存されます。

use Laravel\Ai\Responses\StreamedAgentResponse;

(new SalesCoach)
->stream('Analyze this sales transcript...')
->then(function (StreamedAgentResponse $response) {
$response->reasoning; // '' unless the model returned reasoning text...
$response->meta->citations;
});

prompt メソッドが返すレスポンスでは、推論も利用できます。

Stream Protocols​

デフォルトでは、ストリーミングされたレスポンスに AI SDK 独自のイベント形式を使用します。ただし、代わりにフロントエンドのストリーミングプロトコルを使用することもできます。これにより、独自のチャットインターフェースを構築するのではなく、既存のチャットインターフェースとエージェントを組み合わせられます。

ストリーミング可能な応答で usingVercelDataProtocol メソッドを呼び出すことにより、Vercel AI SDK stream protocol を使用してイベントをストリーミングできます。

use App\Ai\Agents\SalesCoach;

Route::get('/coach', function () {
return (new SalesCoach)
->stream('Analyze this sales transcript...')
->usingVercelDataProtocol();
});

アプリケーションのフロントエンドで独自のメッセージ ID を割り当てている場合は、そのメッセージ ID を渡すこともできます。

->usingVercelDataProtocol($request->string('messageId'));

別の方法として、Agent User Interaction (AG-UI) protocol を使用してストリーミングするには、usingAgentUserInteractionProtocol メソッドを利用できます。

Route::post('/coach', function (Request $request) {
return (new SalesCoach)
->forUser($request->user())
->stream($request->string('prompt'))
->usingAgentUserInteractionProtocol();
});

threadId と runId の引数はオプションで、省略すると会話 ID と呼び出し ID がデフォルト値として使用されます:

->usingAgentUserInteractionProtocol(
threadId: $request->input('threadId'),
runId: $request->input('runId'),
);

AI SDK が実装していないプロトコルを使用するには、独自の Laravel\Ai\Streaming\Protocols\StreamProtocol 実装を usingProtocol メソッドに渡します。

use App\Ai\Protocols\CustomProtocol;

return (new SalesCoach)
->stream('Analyze this sales transcript...')
->usingProtocol(new CustomProtocol);

Frontend Integration​

Vercel の useChat や CopilotKit などのライブラリで構築されたチャットインターフェイスは、メッセージ、ツール呼び出し、承認プロンプトをすでに描画するため、アプリケーションではそれらが送信するリクエストを処理するだけで済みます。各リクエストには、会話履歴、最新のユーザーメッセージ、ツールの承認レスポンスが含まれます。

Vercel::chat と AgentUserInteraction::chat メソッドは、このようなリクエストをエージェントの stream メソッドに直接渡せるオブジェクトへ変換します。

use Laravel\Ai\Vercel\Vercel;

Route::post('/chat', function (Request $request) {
$chat = Vercel::chat($request);

return (new SupportAgent)
->withMessages($chat->history())
->stream($chat)
->usingProtocol($chat->protocol());
});

リクエストに approval decisions が含まれている場合、エージェントはその決定を使用して処理を再開します。それ以外の場合、エージェントにはリクエストに含まれる最新のユーザーメッセージと添付ファイルが提示されます。protocol メソッドは、クライアントが使用したプロトコルを返します。

Conversational インターフェースを実装するエージェントは自身の履歴を読み込むため、withMessages メソッドは省略できます。

AgentUserInteraction::chat メソッドは、リクエストのスレッドIDと実行IDに加えて、AG-UI クライアントにも同じ API を提供します。

use Laravel\Ai\AgentUserInteraction\AgentUserInteraction;

$chat = AgentUserInteraction::chat($request);

$chat->threadId();
$chat->runId();

保存されたメッセージをクライアントが期待する形式に戻すこともできます。これにより、ページを再読み込みした後などに、クライアントで以前の会話を表示できます。

$messages = $conversation->messages()->oldest()->get();

return ['messages' => Vercel::toUiMessages($messages)];

AgentUserInteraction::toClientState メソッドは、保留中の承認割り込みを返すことに加えて、AG-UI クライアントに対しても同じ変換を実行します。

return AgentUserInteraction::toClientState($messages);

Broadcasting​

ストリーミング イベントは、いくつかの異なる方法でブロードキャストできます。まず、ストリーミング イベントで broadcast メソッドまたは broadcastNow メソッドを呼び出すだけです。

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 メソッドを呼び出して、エージェントの操作をキューに入れ、ストリーミング イベントが利用可能になったときにブロードキャストすることもできます。

(new SalesCoach)->broadcastOnQueue(
'Analyze this sales transcript...',
new Channel('channel-name'),
);

Skipping Oversized Events​

一部のブロードキャストプラットフォームでは、WebSocket メッセージのサイズが 10KB 前後に制限されています。大きなツール結果のようにデータ量の多いストリームイベントは、この制限を超えてブロードキャストに失敗することがあります。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 テーブルには保存されるため、ストリームの完了後にフロントエンドで完全なツールデータを読み込めます。これは、キューを使う場合の broadcastOnQueue と、同期的な broadcast / broadcastNow の両方で機能します。

Queueing​

エージェントの queue メソッドを使用すると、エージェントにプロンプトを送信し、応答をバックグラウンドで処理させることで、アプリケーションの高速性と応答性を維持できます。 then メソッドと catch メソッドは、応答が利用可能な場合、または例外が発生した場合に呼び出されるクロージャを登録するために使用できます。

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​

ツールを使用して、エージェントがプロンプトに応答する際に利用できる追加機能を提供できます。ツールは、make:tool Artisan コマンドを使用して作成できます。

php artisan make:tool RandomNumberGenerator

生成されたツールは、アプリケーションの app/Ai/Tools ディレクトリに配置されます。各ツールには、ツールを利用する必要があるときにエージェントによって呼び出される handle メソッドが含まれています。

<?php

namespace App\Ai\Tools;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;

class RandomNumberGenerator implements Tool
{
/**
* Get the description of the tool's purpose.
*/
public function description(): Stringable|string
{
return 'This tool may be used to generate cryptographically secure random numbers.';
}

/**
* Execute the tool.
*/
public function handle(Request $request): Stringable|string
{
return (string) random_int($request['min'], $request['max']);
}

/**
* Get the tool's schema definition.
*/
public function schema(JsonSchema $schema): array
{
return [
'min' => $schema->integer()->min(0)->required(),
'max' => $schema->integer()->required(),
];
}
}

ツールを定義したら、エージェントの tools メソッドからツールを返すことができます。

use App\Ai\Tools\RandomNumberGenerator;

/**
* Get the tools available to the agent.
*
* @return Tool[]
*/
public function tools(): iterable
{
return [
new RandomNumberGenerator,
];
}

Runtime Tool Overrides​

withTools メソッドを使用すると、エージェントインスタンスで宣言されているツールを置き換えられます。これは、テナントごと、または機能フラグで切り替えるツールセットに便利です。

$response = (new SupportAgent)
->withTools([new LookupOrder])
->prompt('Where is order 12345?');

クロージャを渡すこともできます。このクロージャにはエージェントが宣言したツールが渡されるため、ツールを追加したり、絞り込んだりできます。

$response = (new SupportAgent)
->withTools(fn (array $tools) => [...$tools, new LookupOrder])
->prompt('Where is order 12345?');

Validating Tool Arguments​

ツールのスキーマによってモデルが指定できる引数は制限されますが、リクエストの validate メソッドを使って受け取った引数をバリデーションできます。

public function handle(Request $request): Stringable|string
{
$validated = $request->validate([
'city' => 'required|string',
'days' => 'required|integer|max:7',
]);

return $this->forecast($validated['city'], $validated['days']);
}

バリデーションに失敗すると、バリデーションメッセージがツールの結果としてモデルに返されます。これにより、モデルは引数を修正してツールを再度呼び出せます。

Repairing Tool Calls​

RepairToolCalls 属性を使用すると、モデルが未知のローカルツールを呼び出した場合に、エージェントが復旧できるようになります。Laravel は利用可能なローカルツールの名前とともに失敗した呼び出しをモデルへ返すため、モデルは呼び出しを修正できます。

use Laravel\Ai\Attributes\RepairToolCalls;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;

#[RepairToolCalls]
class SupportAgent implements Agent, HasTools
{
use Promptable;

// ...
}

Laravel がステップの最大数を自動的に算出する場合、この属性によって修復された呼び出し用のステップが1つ追加されます。明示的に指定した MaxSteps の制限は変わりません。

SimilaritySearch ツールを使用すると、エージェントはデータベースに保存されているベクトル埋め込みを使用して、特定のクエリに類似したドキュメントを検索できます。これは、アプリケーションのデータを検索するためのアクセス権をエージェントに付与する場合の検索拡張生成 (RAG) に役立ちます。

類似性検索ツールを作成する最も簡単な方法は、ベクトル埋め込みを含む Eloquent モデルで usingModel メソッドを使用することです。

use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;

public function tools(): iterable
{
return [
SimilaritySearch::usingModel(Document::class, 'embedding'),
];
}

最初の引数は Eloquent モデル クラスで、2 番目の引数はベクトル エンベディングを含む列です。

0.0 と 1.0 の間の最小類似性しきい値とクロージャを指定して、クエリをカスタマイズすることもできます。

SimilaritySearch::usingModel(
model: Document::class,
column: 'embedding',
minSimilarity: 0.7,
limit: 10,
query: fn ($query) => $query->where('published', true),
),

さらに制御するには、検索結果を返すカスタム クロージャを含む類似性検索ツールを作成できます。

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();
}),
];
}

withDescription メソッドを使用してツールの説明をカスタマイズできます。

SimilaritySearch::usingModel(Document::class, 'embedding')
->withDescription('Search the knowledge base for relevant articles.'),

Deferred Tool Loading​

エージェントが公開するすべてのツールは、デフォルトでリクエストごとにプロバイダへ送信されます。エージェントが多数のツールを提供していると、トークンを消費し、モデルがツールを選択する精度が低下する可能性があります。OpenAI、Azure、Anthropic では、プロバイダツールの ToolSearch を使ってツール定義の読み込みを遅らせ、必要になったときだけプロバイダが読み込むようにできます。

use App\Ai\Tools\RefundOrder;
use App\Ai\Tools\SearchInvoices;
use App\Ai\Tools\Weather;
use Laravel\Ai\Providers\Tools\ToolSearch;

public function tools(): iterable
{
return [
new Weather,
new ToolSearch(tools: [
new SearchInvoices,
new RefundOrder,
]),
];
}

ラップされたツールを変更する必要はありません。プロバイダがプロンプトに関連するツールを検索して読み込むため、その後エージェントは他のツールと同じようにそれらを呼び出せます。

Anthropic を使用する場合、strategy 引数でプロバイダが遅延ツールを検索する方法を決定できます。サポートされている戦略は regex(デフォルト)と bm25 です。

new ToolSearch(tools: [new SearchInvoices], strategy: 'bm25'),

Anthropic を使用する場合、withProviderOptions メソッドを使って検索ツールにプロバイダ固有の追加オプションを渡せます。

(new ToolSearch(tools: [new SearchInvoices]))
->withProviderOptions(['cache_control' => ['type' => 'ephemeral']]),

ツール検索に対応していないプロバイダでは、遅延ツールを黙って破棄せず、例外が発生します。また、Anthropic では少なくとも1つのツールを ToolSearch ラッパーの外側に指定する必要があります。

File Storage Tools​

FileStorage ツールファクトリを使うと、エージェントに Laravel の filesystem disk へのアクセスを許可できます。all メソッドは、指定したディスク上のファイルの一覧表示、読み取り、情報の確認、URL の生成、書き込み、削除、コピー、移動をエージェントに許可するツールを返します。

use Laravel\Ai\Tools\FileStorage;

public function tools(): iterable
{
return FileStorage::all('local');
}

エージェントがファイルの確認だけを行えるようにしたい場合は、readOnly メソッドを使います。

return FileStorage::readOnly('local');

これらのメソッドは Illuminate\Support\Collection を返すため、エージェントに提供するツールをさらに絞り込めます。

use Laravel\Ai\Tools\Filesystem\DeleteFile;

return FileStorage::all('s3')
->reject(fn ($tool) => $tool instanceof DeleteFile);

MCP Tools​

アプリケーションで Laravel MCP を使用している場合、Model Context Protocol サーバーが公開するツールをエージェントに提供できます。Laravel MCP client を使用すると、リモートまたはローカルの MCP サーバーに接続し、そのツールをエージェントへ直接渡せます。

MCP toolsを使用するには、アプリケーションに Laravel MCP パッケージをインストールする必要があります。

MCP クライアントの tools メソッドはコレクションを返すため、... 演算子を使ってエージェントの tools 配列にスプレッドします。

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ツールを自動的にラップするため、エージェントは他のツールと同じように呼び出せます。また、named MCP clientも使用できます。

use Laravel\Mcp\Facades\Mcp;

public function tools(): iterable
{
return [
...Mcp::client('github')->tools(),
];
}

または、local MCP server に接続します。

use Laravel\Mcp\Client;

public function tools(): iterable
{
return [
...Client::local('php', ['artisan', 'mcp:start'])->tools(),
];
}

MCP クライアントの作成と認証について詳しくは、bearer token や OAuth についての説明を含む MCP client documentation を参照してください。

Skills​

Agent Skills は、エージェントに特定のタスクの実行方法を教えるための手順や関連ファイルをまとめたフォルダです。スキルはオープンスタンダードに準拠しているため、アプリケーションのエージェントと、アプリケーションの開発に使用するコーディングエージェントの間で同じスキルを共有できます。

各スキルは、アプリケーションの resources/skills ディレクトリ内にある SKILL.md ファイルを含むディレクトリです。リファレンス文書など、ディレクトリ内のほかのファイルもスキルに含まれます。

resources/skills/
└── refund-policy/
├── SKILL.md
└── references/EDGE-CASES.md

SKILL.md ファイルの先頭には、スキルの name と、どのような場合にスキルを使うべきかを示す description を含む YAML frontmatter を記述し、その後にスキルの指示を続けます。

---
name: refund-policy
description: Use when a customer asks for a refund or disputes a charge.
---

# Refund Policy

Customers may request a full refund within 30 days of purchase...

name を省略すると、スキルのディレクトリ名が使用されます。description のないスキルは無視されます。

エージェントがスキルを利用できるようにするには、HasSkills インターフェイスを実装し、skills メソッドからスキルのソースを返します。

use Laravel\Ai\Contracts\HasSkills;
use Laravel\Ai\Skills\Skill;

class SupportAgent implements Agent, HasSkills
{
use Promptable;

public function skills(): iterable
{
return [
resource_path('skills'),
base_path('.agents/skills'),
new Skill('house-style', 'Use when you write copy for a customer.', view('skills.house-style')),
fn () => $this->user->team->skills->map(
fn ($skill) => new Skill($skill->name, $skill->description, $skill->instructions)
),
];
}
}

ソースにはディレクトリ、Skill インスタンス、またはスキルを返すクロージャを指定できます。クロージャはエージェントがスキルを必要としたときにのみ呼び出されます。同じ名前のスキルが複数ある場合は、先に指定されたソースが優先されます。

エージェントには各スキルの名前と説明を一覧表示する LoadSkill ツールが渡されます。プロンプトで要求された場合にのみ、スキルのすべての指示と同梱されたテキストファイルが読み込まれます。バイナリファイルと 256 KB を超えるファイルは返されません。

Provider Tools​

プロバイダ ツールは、AI プロバイダによってネイティブに実装される特別なツールで、Web 検索、URL フェッチ、ファイル検索などの機能を提供します。通常のツールとは異なり、プロバイダ ツールはアプリケーションではなくプロバイダ自体によって実行されます。

プロバイダ ツールは、エージェントの tools メソッドから返すことができます。

WebSearch プロバイダ ツールを使用すると、エージェントは Web でリアルタイム情報を検索できます。これは、現在のイベント、最近のデータ、またはモデルのトレーニングのカットオフ以降に変更された可能性のあるトピックに関する質問に答えるのに役立ちます。

対応プロバイダ: Anthropic、OpenAI、Azure、Gemini、xAI、Groq、OpenRouter

use Laravel\Ai\Providers\Tools\WebSearch;

public function tools(): iterable
{
return [
new WebSearch,
];
}

Web 検索ツールを構成して、検索数を制限したり、結果を特定のドメインに制限したりすることができます。

(new WebSearch)->max(5)->allow(['laravel.com', 'php.net']),

ユーザーの場所に基づいて検索結果を絞り込むには、location メソッドを使用します。

(new WebSearch)->location(
city: 'New York',
region: 'NY',
country: 'US'
);

Groq は GPT-OSS モデルでのみウェブ検索をサポートしており、max、allow、location メソッドは無視します。

Web Fetch​

WebFetch プロバイダ ツールを使用すると、エージェントは Web ページのコンテンツをフェッチして読み取ることができます。これは、エージェントが特定の URL を分析したり、既知の Web ページから詳細情報を取得したりする必要がある場合に役立ちます。

対応プロバイダ: Anthropic、Gemini、OpenRouter

use Laravel\Ai\Providers\Tools\WebFetch;

public function tools(): iterable
{
return [
new WebFetch,
];
}

Web 取得ツールを設定して、取得数を制限したり、特定のドメインに制限したりすることができます。

(new WebFetch)->max(3)->allow(['docs.laravel.com']),

FileSearch プロバイダ ツールを使用すると、エージェントは vector stores に保存されている files を検索できます。これにより、エージェントがアップロードされたドキュメントで関連情報を検索できるようになり、検索拡張生成 (RAG) が可能になります。

対応プロバイダ: OpenAI, Azure, Gemini, xAI

use Laravel\Ai\Providers\Tools\FileSearch;

public function tools(): iterable
{
return [
new FileSearch(stores: ['store_id']),
];
}

複数のベクトル ストア ID を指定して、複数のストアを検索できます。

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'])
);

Code Execution​

CodeExecution プロバイダツールを使うと、エージェントは AI プロバイダがホストするサンドボックス内でコードを実行できます。計算の実行やデータの分析に便利です。

対応プロバイダ: Anthropic、OpenAI、Azure、Gemini、xAI、Groq

use Laravel\Ai\Providers\Tools\CodeExecution;

public function tools(): iterable
{
return [new CodeExecution];
}

OpenAI または Azure を使用する場合は、プロバイダオプションを通じてstored filesをサンドボックスで利用できるようにします。

(new CodeExecution)->withProviderOptions([
'container' => ['type' => 'auto', 'file_ids' => ['file_123']],
]);

Groqでは、GPT-OSSモデルのみがコード実行をサポートしています。

Sub-Agents​

別のエージェントの tools メソッドからエージェントを返すこともできます。エージェントがツールとして返されると、親エージェントは特定のタスクをサブエージェントに委任し、元のプロンプトに応答する際にサブエージェントの応答を使用することができます。これは、汎用エージェントが独自の命令、ツール、モデル構成、またはプロバイダ設定を備えた専門エージェントにアクセスする必要がある場合に役立ちます。

たとえば、カスタマー サポート エージェントは、払い戻し資格に関する質問を専用の払い戻しエージェントに委任できます。

<?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,
];
}
}

サブエージェントが親エージェントに公開される方法をカスタマイズするには、サブエージェントに CanActAsTool インターフェイスを実装し、ツールとして公開する名前と説明を定義します。

use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\CanActAsTool;
use Laravel\Ai\Enums\Lab;

#[Provider(Lab::Anthropic)]
class RefundsAgent implements Agent, CanActAsTool, HasTools
{
/**
* 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.';
}

// ...
}

サブエージェントが CanActAsTool を実装していない場合、Laravel はエージェントのクラスのベース名をツール名として使用し、親エージェントに明確な自己完結型タスクの説明を渡すように要求する一般的な説明を使用します。各サブエージェントの呼び出しは独立して実行され、親エージェントの会話履歴を受け取りません。

サブエージェントは現在、human approval が必要なツールをサポートしていません。サブエージェントが承認を必要とするツールを使うと、処理が一時停止します。しかし、親エージェントには通常のツール結果が返されるため、アプリケーションに承認待ちであることを通知しないまま処理を続行します。承認が必要なツールは、トップレベルエージェントに登録してください。

親エージェントがstreamingしている場合、structured outputを生成するサブエージェントを除き、そのサブエージェントもストリーミングします。ストリーミング中のサブエージェントについて、親エージェントはサブエージェントがそれまでに生成したテキストを含む予備の ToolResult イベントを出力します。ツール呼び出しの最終結果はこれらのイベントの後に続くため、イベントを手動で反復処理する場合は予備イベントをスキップできます。

use Laravel\Ai\Streaming\Events\ToolResult;

foreach ($stream as $event) {
if ($event instanceof ToolResult && $event->preliminary) {
continue;
}

// ...
}

text、usage、toolResults などのレスポンス値は、暫定的な結果を示すイベントを無視します。Vercel protocol はこれらをネイティブなストリーミングツール出力として処理するため、useChat はカスタムコードなしで進行状況を表示できます。一方、AG-UI プロトコルではアクティビティスナップショットとして報告されます。完了したレスポンスのテキスト、推論、引用、使用量には、サブエージェントのものも含まれます。

Middleware​

エージェントはミドルウェアをサポートしているため、各生成ステップがプロバイダに送信される前にインターセプトして変更できます。ミドルウェアはステップごとに1回呼び出されるため、3ステップかかる実行では3回呼び出されます。ミドルウェアは、make:agent-middleware Artisan コマンドで作成できます。

php artisan make:agent-middleware LogPrompts

生成されたミドルウェアは、アプリケーションの app/Ai/Middleware ディレクトリに配置されます。エージェントにミドルウェアを追加するには、HasMiddleware インターフェイスを実装し、ミドルウェア クラスの配列を返す middleware メソッドを定義します。

<?php

namespace App\Ai\Agents;

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;

// ...

/**
* Get the agent's middleware.
*/
public function middleware(): array
{
return [
new LogPrompts,
];
}
}

各ミドルウェアクラスでは、PendingStep と、そのステップを次のミドルウェアへ渡す Closure を受け取る handle メソッドを定義してください。

<?php

namespace App\Ai\Middleware;

use Closure;
use Illuminate\Support\Facades\Log;
use Laravel\Ai\PendingStep;

class LogPrompts
{
/**
* Handle the pending generation step.
*/
public function handle(PendingStep $step, Closure $next)
{
Log::info('Prompting agent', ['model' => $step->model]);

return $next($step);
}
}

これから送信する provider、model、instructions、messages、tools に加えて、このステップでは、すでに完了したステップ、それらの合計使用量、実行の進捗状況を取得できます。

$step->steps;
$step->usage;

$step->number;
$step->isFirstStep();
$step->isFinalStep;

withModel、withInstructions、withMessages、withTools、onlyTools、withoutTools、withToolChoice、withMaxTokens、withProviderOptions の各メソッドは、ステップのコピーを返します。たとえば、エージェントが使用した後に、コストの高いツールを削除できます。

public function handle(PendingStep $step, Closure $next)
{
if (! $step->isFirstStep()) {
$step = $step->withoutTools('SearchDocumentation');
}

return $next($step);
}

または、会話の途中部分を要約することで、長いツール呼び出しループをコンテキストウィンドウ内に保持できます。

use App\Ai\Agents\Summarizer;
use Laravel\Ai\Messages\UserMessage;

public function handle(PendingStep $step, Closure $next)
{
if (count($step->messages) > 40) {
$summary = (new Summarizer)->prompt(
collect(array_slice($step->messages, 1, -10))->map->content->implode("\n"),
)->text;

$step = $step->withMessages([
$step->messages[0],
new UserMessage("Summary of the conversation so far: {$summary}"),
...array_slice($step->messages, -10),
]);
}

return $next($step);
}

withMessages メソッドに渡したメッセージは、現在のステップで送信される内容だけを変更します。後続のステップと保存された会話では、引き続き要約されていない完全な履歴が使用されます。

モデルがステップに回答した後、ツール呼び出しが実行される前にコードを実行するには、then メソッドを使用できます。これは同期レスポンスとストリーミングレスポンスの両方で機能します。

use Laravel\Ai\Gateway\StepResponse;

public function handle(PendingStep $step, Closure $next)
{
return $next($step)->then(function (StepResponse $response) {
Log::info('Agent responded', ['text' => $response->text]);
});
}

ミドルウェアは、$next の結果、StepResponse、または StepResult::stop() を返す必要があります。キャッシュ済みの応答を返す場合など、モデルを呼び出さずにステップへ応答するには StepResponse を返してください。実行を終了するには StepResult::stop() を返します。それ以外の値を返すと、LogicException が発生します。

Stopping Runs​

エージェントの次のモデル呼び出しを行う前に実行を終了するには、Stop ミドルウェアを使用します。たとえば、ユーザーが「停止」ボタンを押したときや、実行が入力トークンの予算に達したときに終了できます。レスポンスには、完了したステップと、それらの合計使用量が保持されます。

use Illuminate\Support\Facades\Cache;
use Laravel\Ai\Contracts\HasMiddleware;
use Laravel\Ai\Middleware\Stop;
use Laravel\Ai\PendingStep;

public function middleware(): array
{
return [
// Stop when the user has pressed the "stop" button...
Stop::when(fn (PendingStep $step) => Cache::has("chat:{$this->user->id}:stopped")),

// Stop when the run has used at least 50,000 input tokens...
Stop::unless(fn (PendingStep $step) => $step->usage->inputTokens < 50_000),
];
}

各ステップの前に、when は条件が true の場合に実行を停止し、unless は条件が false の場合に実行を停止します。停止したストリーミング実行の終了理由は cancelled です。

カスタムミドルウェアは、$next の結果ではなく StepResult::stop() を返して処理を終了させることもできます。

use Laravel\Ai\Gateway\StepResult;

public function handle(PendingStep $step, Closure $next)
{
return $this->cancelled($step) ? StepResult::stop() : $next($step);
}

Anonymous Agents​

場合によっては、専用のエージェント クラスを作成せずにモデルをすばやく操作したい場合があります。 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');

Agent Configuration​

PHP 属性を使用して、エージェントのテキスト生成オプションを構成できます。次の属性が使用可能です。

  • MaxSteps: ツールの使用時にエージェントが実行できるステップの最大数。
  • MaxTokens: モデルが生成できるトークンの最大数。
  • Model: エージェントが使用するモデル。
  • Provider: エージェントで使用する AI プロバイダ(フェイルオーバー用に複数指定することもできます)。
  • Temperature: 生成に使用するサンプリング温度(0.0〜1.0)。
  • Timeout: エージェントのリクエストに対する HTTP タイムアウト(秒)。デフォルトは 60 です。
  • TopP: 生成に使用する nucleus sampling の確率(0.0〜1.0)。
  • UseCheapestModel: コストを最適化するため、プロバイダで最も安価なテキストモデルを使用します。
  • UseSmartestModel: 複雑なタスクに対応するため、プロバイダで最も高性能なテキストモデルを使用します。
<?php

namespace App\Ai\Agents;

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;

// Will use the cheapest model (e.g., Haiku)...
}

#[UseSmartestModel]
class ComplexReasoner implements Agent
{
use Promptable;

// Will use the most capable model (e.g., Opus)...
}

UseCheapestModel と UseSmartestModel が選択するモデルは、プロバイダから新しいモデルがリリースされると、Laravel AI SDK のリリースごとに変更される場合があります。モデルを切り替えると、挙動の変化、非推奨のパラメータ、コストの大幅な違いが生じる可能性があります。安定した予測可能なモデルと料金を求める場合は、Model 属性を使ってモデルを明示してください。

Provider Options​

エージェントがプロバイダ固有のオプション (OpenAI の推論量やペナルティ設定など) を渡す必要がある場合は、HasProviderOptions コントラクトを実装し、providerOptions メソッドを定義します。

<?php

namespace App\Ai\Agents;

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;

// ...

/**
* Get provider-specific generation options.
*/
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 => [],
};
}
}

providerOptions メソッドは、現在使用されているプロバイダ (Lab 列挙型または文字列) を受け取り、プロバイダごとに異なるオプションを返すことができます。各フォールバック プロバイダが独自の構成を受け取ることができるため、これは failover を使用する場合に特に便利です。

上記の Anthropic の例では、cache_control を介して prompt caching も有効になります。

image、audio、transcription、embedding、reranking の各ビルダもプロバイダオプションを受け付けます。

use Laravel\Ai\Audio;

$audio = Audio::of('I love coding with Laravel.')
->withProviderOptions(['speed' => 1.25])
->generate();

配列の代わりにクロージャを渡すこともできます。このクロージャには、現在使用されているプロバイダが渡されます。

Custom HTTP Headers​

アプリケーションの config/ai.php 設定ファイルでプロバイダに設定したヘッダーは、そのプロバイダが実行するすべてのリクエストに送信されます。AI gateway で使用するメタデータなど、リクエストごとにヘッダーを送信するには、画像、音声、文字起こし、埋め込み、リランキングの各ビルダと file uploads で利用できる withHeaders メソッドを使用します。

use Laravel\Ai\Embeddings;

$embeddings = Embeddings::for($chunks)
->withHeaders(['cf-aig-metadata' => json_encode(['team' => $team->id])])
->withProviderOptions(['dimensions' => 1024])
->generate();

ヘッダーは、現在使用しているプロバイダを受け取るクロージャとして指定することもできます。ヘッダーはリクエストボディに含まれず、embedding cache keys にも影響しません。

Prompt Caching​

多くのプロバイダは、繰り返し使用されるプロンプトのプレフィックスを自動的にキャッシュし、キャッシュされた部分には割引料金を適用します。OpenAI、Gemini、Groq、DeepSeek、xAI では設定は必要ありません。レスポンスの使用量から節約額を確認できます。

$response->usage->cacheReadInputTokens;
$response->usage->cacheWriteInputTokens;

どちらのカウントも入力トークン総数に含まれるもので、その総数については usage documentation で詳しく説明します。

anthropic プロバイダと bedrock プロバイダは、明示的に指定された場合にのみキャッシュします。CacheInstructions 属性と CacheToolDefinitions 属性は、エージェントの指示とツール定義の末尾にキャッシュブレークポイントを設定します。これにより、各会話ではそのプレフィックスを再度書き込む代わりに、キャッシュから読み取ります。

use Laravel\Ai\Attributes\CacheInstructions;
use Laravel\Ai\Attributes\CacheToolDefinitions;

#[CacheInstructions]
#[CacheToolDefinitions]
class SalesCoach implements Agent
{
use Promptable;

// ...
}

リクエストごとに指示が変わる場合、たとえば現在の日付を埋め込む場合は、CacheToolDefinitions だけを使用してください。リクエストごとに変化するプレフィックスをキャッシュすると、毎回新しいキャッシュエントリが作成されます。そのため、再利用することなくキャッシュへの書き込みコストだけが発生します。

これらの属性をサポートしないプロバイダは無視するため、エージェントは failover の使用時にも安全に宣言できます。

キャッシュされたプレフィックスは、デフォルトで5分間保持されます。属性に TTL を渡すことで、Anthropic での保持期間を1時間に指定できます。

#[CacheInstructions('1h')]
#[CacheToolDefinitions('1h')]

また、トップレベルの cache_control provider option を使って、Anthropic の自動キャッシュを有効にすることもできます。これによりリクエストの最後のブロックの後ろに1つのブレークポイントが設定されるため、会話が進むにつれてブレークポイントも移動し、各ターンでは直前までのターンをキャッシュから読み取ります。どちらの仕組みも組み合わせて使用できます。

プロバイダはツール、指示、メッセージの順にプロンプトを構築するため、指示を1時間キャッシュする場合は、ツール定義も1時間キャッシュする必要があります。異なる期間を指定すると、InvalidArgumentException がスローされます。

Human Tool Approval​

ツールの承認後に実行を再開するには、一時停止したターンの履歴を利用できる必要があります。RemembersConversations トレイトを使うエージェントなどの Conversational エージェントを使用するか、アプリケーションのフロントエンドから withMessages method を使って履歴を渡してください。どちらも行わないエージェントは、ツールの実行が一時停止したときに ApprovalNotResumableException をスローします。

機密性の高い操作や取り消せない操作を実行するツールでは、実行前に人間の承認が必要になる場合があります。ツールを承認可能にするには、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 メソッドを定義します。このメソッドは、ブール値または承認を求める理由を含む 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.'),
];
}

承認が必要なツールが呼び出されると、エージェントは実行前に一時停止します。レスポンスの保留中の承認を確認できます。そこには各ツール呼び出しの ID、ツール名、引数、承認理由が含まれます。

$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 インスタンスを渡します。判断では、呼び出しを承認または拒否したり、実行前に引数を編集したりできます。

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.'),
]));

一時停止中のターンは、一時停止した参加者ではなく、その会話と保留中のツール呼び出しによって識別されます。そのため、再開する前に、complete approval flowのようにアプリケーションで会話へのアクセスを認可するか、conversation store の conversationBelongsTo メソッドを使ってアクセス権を確認してください。

ブール値の true と false は、それぞれ承認と拒否の略記として使用できます。保留中のすべてのツール呼び出しには、判定を与える必要があります。不明なツール呼び出し ID、判定が指定されていないツール呼び出し ID、またはすでに解決済みのツール呼び出し ID があると、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 メソッドでは、ツールの承認をサポートしています。

ストリーミングおよびブロードキャスト中の一時停止は、tool_approval_request イベントで表されます。Vercel AI SDK stream protocol を使用する場合、承認リクエストと結果はプロトコル固有のツール承認パートとして発行され、Agent User Interaction protocol では割り込みとして報告されます。

いずれのプロトコルを使用するクライアントも、会話の他の内容と一緒に判断結果を送信するため、chat request をエージェントに直接渡せます。

$chat = Vercel::chat($request);

return (new FileAssistant)
->continue($conversationId, as: $request->user())
->stream($chat)
->usingProtocol($chat->protocol());

一時停止したターンを再開すると、再開後のステップがそのターンにマージされるため、各ターンは単一のアシスタントメッセージとして保存されます。レスポンスの assistantMessageId には一時停止したメッセージの ID が含まれ、そのメッセージの使用量には一時停止と再開の両方の使用量が含まれます。

キューに入れたエージェントでは、生成されたレスポンスが then コールバックに渡され、Laravel は ToolApprovalRequested イベントもディスパッチします。

Laravel は、モデルに続きを尋ねる前に、承認されたツールの実行結果を保存します。その後の生成に失敗した場合、承認処理はすでに完了しています。同じ承認決定を再度送信するのではなく、通常のテキストプロンプトで会話を続けてください。

Complete Approval Flow​

次のルートは、新しいテキストプロンプトまたはチャット画面からの承認判断を受け付ける、完全な承認フローを示しています。この例では、アプリケーションの 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::post('/chat/{conversation}', function (Request $request, Conversation $conversation) {
Gate::authorize('view', $conversation);

$validated = $request->validate([
'message' => ['nullable', 'string', 'required_without:decisions', 'prohibits:decisions'],
'decisions' => ['nullable', 'array', 'required_without:message', 'prohibits:message'],
'decisions.*.action' => ['required_with:decisions', Rule::in(['approve', 'reject'])],
'decisions.*.result' => ['nullable', 'string'],
]);

$prompt = isset($validated['decisions'])
? Decisions::from(collect($validated['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');

レスポンスのステータスが awaiting_approval の場合、チャット画面には保留中の承認を表示し、ユーザーの選択を同じエンドポイントへ送信します。このとき、各判断のキーにはツール呼び出し ID を使用します。それ以外の場合、画面からは単純な message 値を送信できます。

{
"decisions": {
"call_abc": {
"action": "approve"
},
"call_def": {
"action": "reject",
"result": "The invoice must be retained."
}
}
}

Images​

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 メソッドは画像のアスペクト比を制御するために使用できますが、quality メソッドは最終的な画像品質 (high、medium、low) についてモデルをガイドするために使用できます。 timeout メソッドを使用して、HTTP タイムアウトを秒単位で指定できます。

use Laravel\Ai\Image;

$image = Image::of('A donut sitting on the kitchen counter')
->quality('high')
->landscape()
->timeout(120)
->generate();

attachments メソッドを使用して参照画像を添付できます。

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();

プロバイダによっては、1回のリクエストで複数の画像を生成できます。OpenAI、Azure、xAI は n provider option を受け付け、レスポンスには返されたすべての画像が含まれます。

$response = Image::of('A donut sitting on the kitchen counter')
->withProviderOptions(['n' => 4])
->generate();

count($response); // 4
$response->images; // A collection of generated images...
$response->firstImage(); // The first generated image...

生成されたイメージは、アプリケーションの 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();

// ...
});

Audio​

Laravel\Ai\Audio クラスは、指定されたテキストから音声を生成するために使用できます。

use Laravel\Ai\Audio;

$audio = Audio::of('I love coding with Laravel.')->generate();

$rawContent = (string) $audio;

Laravel の Stringable クラスから利用できる toAudio メソッドを使用して、文字列からオーディオを生成することもできます。

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();

生成されたオーディオは、アプリケーションの config/filesystems.php 構成ファイルで構成されたデフォルトのディスクに簡単に保存できます。

$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();

// ...
});

Transcriptions​

Laravel\Ai\Transcription クラスは、指定された音声のトランスクリプトを生成するために使用できます。

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 メソッドを使用すると、通常の文字起こしテキストに加え、話者ごとに分割した文字起こしを応答に含めるよう指定できます。これにより、話者別の文字起こしにアクセスできます。

$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) {
// ...
});

Text Summarization​

Laravel の Stringable クラスで利用できる summarize メソッドを使って、テキストを要約できます。デフォルトでは、要約は3文以内で、設定したプロバイダが提供する最も安価なテキストモデルを使って生成されます。

use Illuminate\Support\Str;

$summary = Str::of($article)->summarize();

要約の生成に使用する文の最大数、プロバイダ、モデル、タイムアウトを指定できます。Str クラスには、このメソッドの static 版も用意されています。

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​

Laravel の Stringable クラスから利用できる新しい toEmbeddings メソッドを使用すると、任意の文字列のベクトル埋め込みを簡単に生成できます。

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');

Multimodal Embeddings​

文字列に加えて、Embeddings::for メソッドは画像、音声、ドキュメント、動画の入力にも対応しているため、テキスト以外のコンテンツの埋め込みを生成できます。Gemini は画像、音声、ドキュメント、動画の埋め込みをサポートし、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);

マルチモーダル入力では、file classes used for attachments と同じファイルクラスを使用します。これらのファイルは、ローカルパス、ファイルシステムディスク、リモート URL、または Base64 エンコードされたコンテンツから作成できます。画像、ドキュメント、動画はアップロードされたファイルからも作成でき、ドキュメントは生の文字列コンテンツから作成することもできます。

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 では、1つのリクエスト内でリモート URL のメディアと Base64 エンコードされたメディアを混在させることはできません。ローカルファイル、保存済みファイル、アップロードされたファイルは Base64 エンコードされたコンテンツとして送信されます。テキスト入力は、どちらのメディアソースとも組み合わせられます。利用可能なマルチモーダルモデルと入力については、プロバイダのドキュメントを確認してください。

Querying Embeddings​

埋め込みを生成したら、通常は後でクエリできるように、データベースの vector 列に保存します。Laravel は、pgvector 拡張機能を介した PostgreSQL のベクトル列に加え、MariaDB のベクトル列もネイティブでサポートしています。まず、マイグレーションで vector 列を定義し、次元数を指定します。

Schema::ensureVectorExtensionExists();

Schema::create('documents', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->vector('embedding', dimensions: 1536);
$table->timestamps();
});

類似性検索を高速化するためにベクトル インデックスを追加することもできます。ベクトル列で index を呼び出すと、Laravel はコサイン距離を使用して HNSW インデックスを自動的に作成します。

$table->vector('embedding', dimensions: 1536)->index();

Eloquent モデルでは、AsVector キャストを使ってベクトル列をキャストする必要があります。

use Illuminate\Database\Eloquent\Casts\AsVector;

protected function casts(): array
{
return [
'embedding' => AsVector::class,
];
}

類似するレコードをクエリするには、whereVectorSimilarTo メソッドを使用します。このメソッドは、コサイン類似度の下限値 (0.0 と 1.0 の間、1.0 は同一) によって結果をフィルターし、類似度によって結果を並べ替えます。

use App\Models\Document;

$documents = Document::query()
->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
->limit(10)
->get();

$queryEmbedding は、浮動小数点数の配列またはプレーン文字列の場合があります。文字列が指定されると、Laravel はその文字列の埋め込みを自動的に生成します。

$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 ツールのドキュメントを確認してください。

ベクトルクエリは現在、pgvector 拡張機能を使用する PostgreSQL 接続、またはネイティブのベクトルサポートを備えた MariaDB 11.7 以降で利用できます。

Caching Embeddings​

埋め込み生成をキャッシュして、同一の入力に対する冗長な API 呼び出しを回避できます。キャッシュを有効にするには、ai.caching.embeddings.cache 構成オプションを true に設定します。

'caching' => [
'embeddings' => [
'cache' => true,
'store' => env('CACHE_STORE', 'database'),
'individually' => true,
// ...
],
],

キャッシュが有効になっている場合、埋め込みは 30 日間キャッシュされます。キャッシュ キーはプロバイダ、モデル、ディメンション、および入力コンテンツに基づいており、異なる構成で新しい埋め込みが生成される一方で、同一のリクエストがキャッシュされた結果を返すことが保証されます。

デフォルトでは、各入力の埋め込みがそれぞれ固有のキーでキャッシュされます。そのため、入力のセットや順序が変わっていても、後続のリクエストで以前に処理した入力のキャッシュが使用されることがあります。入力のセット全体を1つのキーでキャッシュするには、設定オプション ai.caching.embeddings.individually を false に設定してください。

グローバル キャッシュが無効になっている場合でも、cache メソッドを使用して特定のリクエストのキャッシュを有効にすることもできます。

$response = Embeddings::for(['Napa Valley has great wine.'])
->cache()
->generate();

カスタムのキャッシュ期間を秒単位で指定できます。

$response = Embeddings::for(['Napa Valley has great wine.'])
->cache(seconds: 3600) // Cache for 1 hour
->generate();

toEmbeddings Stringable メソッドは、cache 引数も受け入れます。

// Cache with default duration...
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: true);

// Cache for a specific duration...
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: 3600);

Reranking​

リランキングを使用すると、特定のクエリとの関連性に基づいてドキュメントのリストを並べ替えることができます。これは、意味的理解を使用して検索結果を改善するのに役立ちます。

Laravel\Ai\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');

// Access the top result...
$response->first()->document; // "Laravel is a PHP web application framework."
$response->first()->score; // 0.95
$response->first()->index; // 1 (original position)

limit メソッドを使うと返される結果の数を制限できます。一方、timeout メソッドでは HTTP のタイムアウトを秒単位で指定できます。デフォルトは 30 秒です。

$response = Reranking::of($documents)
->limit(5)
->timeout(60)
->rerank('search query');

Reranking Collections​

便宜上、Laravel コレクションは rerank マクロを使用してリランキングできます。最初の引数はリランキングに使用するフィールドを指定し、2 番目の引数はクエリです。

// Rerank by a single field...
$posts = Post::all()
->rerank('body', 'Laravel tutorials');

// Rerank by multiple fields (sent as JSON)...
$reranked = $posts->rerank(['title', 'body'], 'Laravel tutorials');

// Rerank using a closure to build the document...
$reranked = $posts->rerank(
fn ($post) => $post->title.': '.$post->body,
'Laravel tutorials'
);

結果の数を制限してプロバイダを指定することもできます。

$reranked = $posts->rerank(
by: 'content',
query: 'Laravel tutorials',
limit: 10,
provider: Lab::Cohere,
timeout: 60,
);

Classification​

分類機能は現在実験的な機能であり、AI SDK の今後のマイナーリリースで API が変更される可能性があります。

Classification を使うと、指定した文字列またはデータ配列に対してあらかじめ定めた質問を行い、自由形式のテキストではなく、各質問への型付きの回答とその確率を受け取れます。これは、回答をしきい値と比較したり、テストで回答をアサーションしたりする必要があるルーティング、モデレーション、スコアリングに役立ちます。

Laravel\Ai\Classification クラスを使用してコンテンツを分類できます。各質問にはキーが割り当てられ、そのキーを使って対応する回答をレスポンスから取得できます。

use Laravel\Ai\Classification;
use Laravel\Ai\Classification\Boolean;
use Laravel\Ai\Classification\Choice;
use Laravel\Ai\Classification\Score;

$result = Classification::of($supportRequest)
->questions([
'urgent' => new Boolean('Does this request need an immediate response?', [
'true' => 'Explicitly time-sensitive',
'false' => 'No urgency expressed',
]),
'department' => new Choice('Which team should handle this request?', [
'billing' => 'Payments, invoices, and refunds',
'technical' => 'Bugs, outages, and integrations',
'sales' => 'Pricing, plans, and upgrades',
]),
'frustration' => new Score('How frustrated is the customer?', [
'Calm',
'Frustrated',
'Very angry',
]),
])
->classify();

Boolean 形式の質問は、回答が「true」である確率を返します。isTrue メソッドを使うと、その確率が指定したしきい値を満たすか判定できます。デフォルトは 0.5 です。

$result['urgent']->probability; // 0.94
$result['urgent']->isTrue(threshold: 0.8); // true

Choice 質問は、指定された選択肢のいずれかと、各選択肢の確率を返します。confidence プロパティは、すべての確率を考慮したプロバイダの確信度を示します。プロバイダが確信度を測定できない場合は null になります。

$result['department']->choice; // 'technical'
$result['department']->probabilityOf('technical'); // 0.87
$result['department']->probabilities; // ['billing' => 0.08, 'technical' => 0.87, 'sales' => 0.05]
$result['department']->confidence; // 0.82

Score の質問は、指定された順序付きレベル上の位置を返します。score プロパティは確率で重み付けされるため、2つのレベルの間の値になる場合があります。一方、level メソッドと label メソッドは、最も可能性の高いレベルを表します。

$result['frustration']->score; // 1.24, the probability-weighted level
$result['frustration']->level(); // 1, the most probable level
$result['frustration']->label(); // 'Frustrated'
$result['frustration']->normalized(); // 0.62, the score as a fraction of the highest level
$result['frustration']->probabilities; // [0.12, 0.52, 0.36]

Boolean 質問に指定する判定基準、Choice 質問に指定する選択肢の説明、Score 質問に指定するレベルは、1文では不十分な場合にそれぞれ配列にできます。Choice 質問には少なくとも2つの選択肢が必要で、Score 質問には少なくとも2つのレベルが必要です。

レスポンスは反復処理やカウントができ、配列としてアクセスできます。さらに、answer メソッドを使うと単一の回答を取得でき、collect メソッドを使うとすべての回答を collection として返します。

$result->answer('urgent');
$result->collect();

$result->usage;
$result->meta->provider;

Classifying Images​

OpenAI、OpenAI互換、TypeSafe、またはOpenRouterプロバイダを使用する場合、画像を of メソッドの第2引数として渡すことで、指定したコンテンツと併せて画像を分類できます。画像は、file classes used for attachments と同じクラスを使って作成できます。

use Laravel\Ai\Classification;
use Laravel\Ai\Classification\Boolean;
use Laravel\Ai\Files\Image;

$result = Classification::of('Inspect the product in this photo.', [
Image::fromPath($photo),
])
->question('damaged', new Boolean('Does the product have visible damage?'))
->classify(provider: 'openai');

質問内で特定の画像を指定するには、文字列キーを使って画像を添付します。SDK は各画像のキーを名前としてプロバイダに送信します。

$result = Classification::of('Inspect the products in these photos.', [
'front' => Image::fromPath($front),
'back' => Image::fromPath($back),
])
->question('damaged', new Boolean('Is the product in `front` or `back` damaged?'))
->classify();

Yes or No Decisions​

はい・いいえの判定を1つ行うには、Laravel の Stringable クラスで利用できる decide メソッドを使用できます。このメソッドは完全なレスポンスではなく、boolean を返します。「yes」と「no」が何を意味するかを説明し、回答が到達すべき確率も指定できます。デフォルトは 0.5 です。

use Illuminate\Support\Str;

if (Str::of($message)->decide('Is this spam?')) {
// ...
}

$spam = Str::of($message)->decide('Is this spam?', criteria: [
'true' => 'Unsolicited bulk mail.',
'false' => 'A genuine message from a customer.',
], threshold: 0.9);

Choosing From Collections​

選択肢のリストから項目を1つすばやく選ぶには、Laravel の Collection クラスで使用できる decide メソッドを使います。このメソッドは質問と分類するテキストを受け取り、コレクションから選択した項目を返します。

文字列と enum のコレクションはそのまま使用できます。それ以外の項目には by 引数で名前を指定してください。また、describe 引数を使ってフィールド、フィールドの配列、またはクロージャを指定し、各選択肢についてモデルに詳しい情報を提供することもできます。

$department = collect(['billing', 'technical', 'sales'])
->decide('Which team should handle this request?', $ticket->body);

$priority = collect(Priority::cases())
->decide('How urgent is this request?', $ticket->body);

$department = Department::all()->decide(
'Which department should handle this request?',
$ticket->body,
by: 'name',
describe: 'description',
);

describe 引数で定義した説明は、テキストがその選択肢に一致するかどうかをモデルが判断する基準になります。選択肢の名前だけでは曖昧な場合に便利です。describe 引数にクロージャを指定すると、そのクロージャは各項目を受け取ります。クロージャからは、その項目を説明する文字列または配列を返してください。

$priority = collect(Priority::cases())->decide(
'How urgent is this request?',
$ticket->body,
describe: fn (Priority $priority) => $priority->description(),
);

文字列をキーとする文字列のコレクションでは、キーが選択肢として使用され、値がその説明として使用されます。そして、選択されたキーが返されます。threshold を指定し、選択された選択肢の確率がその値を下回った場合は、null が返されます。

$department = collect([
'billing' => 'Payments, invoices, and refunds',
'technical' => 'Bugs, outages, and integrations',
])->decide('Which team should handle this request?', $ticket->body, threshold: 0.6) ?? 'triage';

Files​

Laravel\Ai\Files クラスまたは個々のファイル クラスは、後で会話で使用するために AI プロバイダでファイルを保存するために使用できます。これは、再アップロードせずに何度も参照したい大きなドキュメントやファイルの場合に便利です。

use Laravel\Ai\Files\Document;
use Laravel\Ai\Files\Image;

// Store a file from a local path...
$response = Document::fromPath('/home/laravel/document.pdf')->put();
$response = Image::fromPath('/home/laravel/photo.jpg')->put();

// Store a file that is stored on a filesystem disk...
$response = Document::fromStorage('document.pdf', disk: 'local')->put();
$response = Image::fromStorage('photo.jpg', disk: 'local')->put();

// Store a file that is stored on a remote URL...
$response = Document::fromUrl('https://example.com/document.pdf')->put();
$response = Image::fromUrl('https://example.com/photo.jpg')->put();

return $response->id;

未加工のコンテンツやアップロードされたファイルを保存することもできます。

use Laravel\Ai\Files;
use Laravel\Ai\Files\Document;

// Store raw content...
$stored = Document::fromString('Hello, World!', 'text/plain')->put();

// Store an uploaded file...
$stored = Document::fromUpload($request->file('document'))->put();

ファイルが保存されると、ファイルを再アップロードする代わりに、エージェント経由でテキストを生成するときにファイルを参照できます。

use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Files;

$response = (new SalesCoach)->prompt(
'Analyze the attached sales transcript...',
attachments: [
Files\Document::fromId('file-id') // Attach a stored document...
]
);

以前に保存されたファイルを取得するには、ファイル インスタンスで get メソッドを使用します。

use Laravel\Ai\Files\Document;

$file = Document::fromId('file-id')->get();

$file->id;
$file->mimeType();

プロバイダからファイルを削除するには、delete メソッドを使用します。

Document::fromId('file-id')->delete();

デフォルトでは、Files クラスは、アプリケーションの config/ai.php 構成ファイルで構成されたデフォルトの AI プロバイダを使用します。ほとんどの操作では、provider 引数を使用して別のプロバイダを指定できます。

$response = Document::fromPath(
'/home/laravel/document.pdf'
)->put(provider: Lab::Anthropic);

withProviderOptions メソッドを使用して、プロバイダごとのアップロードオプションを渡せます。たとえば、OpenAI のファイル purpose を設定できます:

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();

Using Stored Files in Conversations​

ファイルがプロバイダに保存されたら、Document クラスまたは Image クラスの fromId メソッドを使用して、エージェントの会話でそのファイルを参照できます。

use App\Ai\Agents\DocumentAnalyzer;
use Laravel\Ai\Files;
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),
],
);

同様に、格納されたイメージは、Image クラスを使用して参照できます。

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​

ベクター ストアを使用すると、検索拡張生成 (RAG) に使用できる、検索可能なファイルのコレクションを作成できます。 Laravel\Ai\Stores クラスは、ベクター ストアを作成、取得、削除するためのメソッドを提供します。

use Laravel\Ai\Stores;

// Create a new vector store...
$store = Stores::create('Knowledge Base');

// Create a store with additional options...
$store = Stores::create(
name: 'Knowledge Base',
description: 'Documentation and reference materials.',
expiresWhenIdleFor: days(30),
);

return $store->id;

既存のベクター ストアを ID で取得するには、get メソッドを使用します。

use Laravel\Ai\Stores;

$store = Stores::get('store_id');

$store->id;
$store->name;
$store->fileCounts;
$store->ready;

ベクター ストアを削除するには、Stores クラスまたはストア インスタンスで delete メソッドを使用します。

use Laravel\Ai\Stores;

// Delete by ID...
Stores::delete('store_id');

// Or delete via a store instance...
$store = Stores::get('store_id');

$store->delete();

Adding Files to Stores​

ベクター ストアを作成したら、add メソッドを使用してそれに files を追加できます。ストアに追加されたファイルは、file search provider tool を使用したセマンティック検索のために自動的にインデックス付けされます。

use Laravel\Ai\Files\Document;
use Laravel\Ai\Stores;

$store = Stores::get('store_id');

// Add a file that has already been stored with the provider...
$document = $store->add('file_id');
$document = $store->add(Document::fromId('file_id'));

// Or, store and add a file in one step...
$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;

ベクトルストアに以前保存したファイルを追加すると、通常、返されるドキュメント ID はファイルに以前割り当てられた ID と同じです。ただし、ベクトルストレージプロバイダによっては、新しい異なる「ドキュメント ID」が返される場合があります。そのため、後で参照できるよう、両方の ID を常にデータベースに保存しておくことをおすすめします。

Gemini ストアにファイルを追加すると、Laravel はインポートが完了するまで待機するため、呼び出しが返ると同時にドキュメントを検索できます。インポートに失敗した場合や5分を超えた場合は Laravel\Ai\Exceptions\AiException がスローされるため、Gemini ファイルは queued job から追加するとよいでしょう。

ファイルをストアに追加するときに、ファイルにメタデータを添付できます。このメタデータは、後で file search provider tool を使用するときに検索結果をフィルタリングするために使用できます。

$store->add(Document::fromPath('/path/to/document.pdf'), metadata: [
'author' => 'Taylor Otwell',
'department' => 'Engineering',
'year' => 2026,
]);

ストアからファイルを削除するには、remove メソッドを使用します。

$store->remove('file_id');

ベクター ストアからファイルを削除しても、プロバイダの file storage からは削除されません。ファイルをベクター ストアから削除し、ファイルストレージから完全に削除するには、deleteFile 引数を使用します。

$store->remove('file_abc123', deleteFile: true);

Usage​

すべてのレスポンスには、プロバイダが報告したトークン数を含む usage プロパティがあります。入力と出力のカウントは合計値であるため、キャッシュされたトークンや推論トークンとしてカウントされたトークンも、それぞれ該当する合計に含まれます。

$response = (new SalesCoach)->prompt('Analyze this sales transcript...');

$response->usage->inputTokens;
$response->usage->outputTokens;
$response->usage->totalTokens();

テキスト生成は Laravel\Ai\Responses\Data\TextUsage インスタンスを返し、これらの合計をさらに詳細に分類できます。プロバイダが値を報告しない場合、各値は 0 ではなく null になります。

$response->usage->cacheReadInputTokens; // Subset of the input tokens read from a prompt cache...
$response->usage->cacheWriteInputTokens; // Subset of the input tokens written to a prompt cache...
$response->usage->reasoningTokens; // Subset of the output tokens spent on reasoning...

$response->usage->uncachedInputTokens(); // Input tokens that were neither read from nor written to the cache...

キャッシュからの読み取り、キャッシュへの書き込み、キャッシュを使用しない入力は料金が異なるため、入力の合計だけを使うのではなく、これら3つを分けて料金を見積もる必要があります。

残りの機能は、それぞれに固有のカウントを含む使用量オブジェクトを返します。

機能使用量オブジェクト追加される情報
テキスト、分類TextUsageキャッシュの読み取り、キャッシュへの書き込み、推論トークン
画像ImageUsageimageInputTokens と imageOutputTokens
文字起こしTranscriptionUsageaudioSeconds。文字起こしした音声の長さ
リランキングRerankingUsagesearchUnits。トークンの代わりに一部のプロバイダが課金する単位
音声、埋め込みUsage

すべてのプロバイダがすべてのカウントを報告するわけではありません。プロバイダが報告しないカウントは null になります。

use Laravel\Ai\Image;
use Laravel\Ai\Transcription;

Image::of('A donut sitting on the kitchen counter')->generate()->usage->imageOutputTokens;

Transcription::fromPath('/home/laravel/meeting.mp3')->generate()->usage->audioSeconds;

Failover​

プロンプトを送信したり、その他のメディアを生成したりするときに、プライマリ プロバイダでサービスの中断またはレート制限が発生した場合に、バックアップ プロバイダ/モデルに自動的にフェイルオーバーするプロバイダ/モデルの配列を指定できます。

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]);

フェイルオーバーは、FailoverableException がスローされた場合にのみ発生します。たとえば、レート制限 (RateLimitedException)、過負荷または利用不可のプロバイダ (ProviderOverloadedException)、クレジット不足 (InsufficientCreditsException) などです。バリデーションエラーや不正なリクエストエラーといった通常のエラーでは、フェイルオーバーは発生しません。

[Lab::OpenAI, Lab::Anthropic] のようにプロバイダの単純なリストを渡した場合、各プロバイダはそのデフォルトモデルを使用します。フェイルオーバーチェーン内の各プロバイダに特定のモデルを指定するには、Lab enum の value をキーとして連想配列を渡します(enum のケースは PHP の配列キーとして直接使用できません)。

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',
],
);

Testing​

キューに入れた画像、音声、文字起こし、または埋め込みの生成をフェイクすると、キューに入れた生成に登録された then コールバックはフェイクしたレスポンスを受け取って呼び出されます。これにより、コールバックに含まれるロジックをテストできます。これらのコールバックを呼び出したくない場合は、Queue::fake() を使ってキューもフェイクできます。

Agents​

テスト中にエージェントの応答を偽装するには、エージェント クラスで fake メソッドを呼び出します。必要に応じて、応答の配列またはクロージャを指定できます。

use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Prompts\AgentPrompt;

// Automatically generate a fixed response for every prompt...
SalesCoach::fake();

// Provide a list of prompt responses...
SalesCoach::fake([
'First response',
'Second response',
]);

// Dynamically handle prompt responses based on the incoming prompt...
SalesCoach::fake(function (AgentPrompt $prompt) {
return 'Response for: '.$prompt->prompt;
});

構造化された出力を返すエージェントをフェイクする場合、レスポンスとして配列を指定できます。エージェントは、与えたデータを含む構造化レスポンスを返します:

SalesCoach::fake([
['score' => 87],
]);

ツールの承認を待機しているレスポンスを偽装することもできます。

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

さらに、推論を含むレスポンスを偽装できます。この偽装レスポンスは推論イベントを発行するため、ストリーミング実行でも推論が報告されます。

use Laravel\Ai\Responses\AgentResponse;

SalesCoach::fake([
AgentResponse::fakeWithReasoning('They asked about pricing.', 'Plans start at $10.'),
]);

$response = (new SalesCoach)->stream('What does it cost?');

foreach ($response as $event) {
// ...
}

$response->reasoning; // 'They asked about pricing.'

構造化出力を返すエージェントに対して Agent::fake() を呼び出し、フェイク出力を明示的に指定していない場合、Laravel はエージェントに定義された出力スキーマに一致するフェイクデータを自動的に生成します。

エージェントにプロンプトを出した後、受け取ったプロンプトについてアサーションを行うことができます。

use Laravel\Ai\Prompts\AgentPrompt;

SalesCoach::assertPrompted('Analyze this...');

SalesCoach::assertPrompted(function (AgentPrompt $prompt) {
return $prompt->contains('Analyze');
});

SalesCoach::assertPromptedTimes(3);

SalesCoach::assertNotPrompted('Missing prompt');

SalesCoach::assertNeverPrompted();

承認の継続を検証する際は、プロンプトの承認判断を確認できます。

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();
});

キューに入れられたエージェント呼び出しの場合は、キューへの登録を検証するアサーションメソッドを使用します。

use Laravel\Ai\QueuedAgentPrompt;

SalesCoach::assertQueued('Analyze this...');

SalesCoach::assertQueued(function (QueuedAgentPrompt $prompt) {
return $prompt->contains('Analyze');
});

SalesCoach::assertNotQueued('Missing prompt');

SalesCoach::assertNeverQueued();

すべてのエージェント呼び出しに対応する偽の応答があることを確認するには、preventStrayPrompts を使用できます。偽の応答が定義されていない状態でエージェントが呼び出された場合、例外がスローされます。

SalesCoach::fake()->preventStrayPrompts();

Images​

Image クラスの fake メソッドを呼び出すことで、画像生成をフェイクできます。画像生成をフェイクすると、記録された画像生成プロンプトに対してさまざまなアサーションを行えます。

use Laravel\Ai\Image;
use Laravel\Ai\Prompts\ImagePrompt;
use Laravel\Ai\Prompts\QueuedImagePrompt;

// Automatically generate a fixed response for every prompt...
Image::fake();

// Provide a list of prompt responses...
Image::fake([
base64_encode($firstImage),
base64_encode($secondImage),
]);

// Dynamically handle prompt responses based on the incoming prompt...
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();

キューに入れた画像生成を検証するには、キューへの登録を検証するアサーションメソッドを使用します。

Image::assertQueued(
fn (QueuedImagePrompt $prompt) => $prompt->contains('sunset')
);

Image::assertNotQueued('Missing prompt');

Image::assertNothingQueued();

すべてのイメージ生成に対応する偽の応答があることを確認するには、preventStrayImages を使用できます。偽の応答が定義されていない状態でイメージが生成された場合、例外がスローされます。

Image::fake()->preventStrayImages();

Audio​

Audio クラスの fake メソッドを呼び出すことで、音声生成をフェイクできます。音声生成をフェイクすると、記録された音声生成プロンプトに対してさまざまなアサーションを行えます。

use Laravel\Ai\Audio;
use Laravel\Ai\Prompts\AudioPrompt;
use Laravel\Ai\Prompts\QueuedAudioPrompt;

// Automatically generate a fixed response for every prompt...
Audio::fake();

// Provide a list of prompt responses...
Audio::fake([
base64_encode($firstAudio),
base64_encode($secondAudio),
]);

// Dynamically handle prompt responses based on the incoming prompt...
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();

キューに入れられたオーディオ生成の場合は、キューへの登録を検証するアサーションメソッドを使用します。

Audio::assertQueued(
fn (QueuedAudioPrompt $prompt) => $prompt->contains('Hello')
);

Audio::assertNotQueued('Missing prompt');

Audio::assertNothingQueued();

すべてのオーディオ生成に対応する偽の応答があることを確認するには、preventStrayAudio を使用できます。定義された偽の応答なしでオーディオが生成された場合、例外がスローされます。

Audio::fake()->preventStrayAudio();

Transcriptions​

Transcription クラスの fake メソッドを呼び出すことで、文字起こしの生成をフェイクできます。文字起こしの生成をフェイクすると、記録された文字起こし生成プロンプトに対してさまざまなアサーションを行えます。

use Laravel\Ai\Transcription;
use Laravel\Ai\Prompts\TranscriptionPrompt;
use Laravel\Ai\Prompts\QueuedTranscriptionPrompt;

// Automatically generate a fixed response for every prompt...
Transcription::fake();

// Provide a list of prompt responses...
Transcription::fake([
'First transcription text.',
'Second transcription text.',
]);

// Dynamically handle prompt responses based on the incoming prompt...
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();

キューに入れられたトランスクリプション生成の場合は、キューへの登録を検証するアサーションメソッドを使用します。

Transcription::assertQueued(
fn (QueuedTranscriptionPrompt $prompt) => $prompt->isDiarized()
);

Transcription::assertNotQueued(
fn (QueuedTranscriptionPrompt $prompt) => $prompt->language === 'fr'
);

Transcription::assertNothingQueued();

すべての転写生成に対応する偽の応答があることを確認するには、preventStrayTranscriptions を使用できます。偽の応答が定義されていない状態でトランスクリプションが生成された場合、例外がスローされます。

Transcription::fake()->preventStrayTranscriptions();

Embeddings​

Embeddings クラスの fake メソッドを呼び出すことで、埋め込みの生成をフェイクできます。埋め込みの生成をフェイクすると、記録された埋め込み生成プロンプトに対してさまざまなアサーションを行えます。

use Laravel\Ai\Embeddings;
use Laravel\Ai\Prompts\EmbeddingsPrompt;
use Laravel\Ai\Prompts\QueuedEmbeddingsPrompt;

// Automatically generate fake embeddings of the proper dimensions for every prompt...
Embeddings::fake();

// Provide a list of prompt responses...
Embeddings::fake([
[$firstEmbeddingVector],
[$secondEmbeddingVector],
]);

// Dynamically handle prompt responses based on the incoming prompt...
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();

キューに入れられた埋め込み生成の場合は、キューへの登録を検証するアサーションメソッドを使用します。

Embeddings::assertQueued(
fn (QueuedEmbeddingsPrompt $prompt) => $prompt->contains('Laravel')
);

Embeddings::assertNotQueued(
fn (QueuedEmbeddingsPrompt $prompt) => $prompt->contains('Other')
);

Embeddings::assertNothingQueued();

すべての埋め込み生成に対応する偽の応答があることを確認するには、preventStrayEmbeddings を使用できます。偽の応答が定義されていない状態で埋め込みが生成された場合、例外がスローされます。

Embeddings::fake()->preventStrayEmbeddings();

Reranking​

リランキング操作は、Reranking クラスの fake メソッドを呼び出すことで偽装できます。

use Laravel\Ai\Reranking;
use Laravel\Ai\Prompts\RerankingPrompt;
use Laravel\Ai\Responses\Data\RankedDocument;

// Automatically generate a fake reranked responses...
Reranking::fake();

// Provide custom responses...
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();

Classification​

Classification クラスの fake メソッドを呼び出すことで、分類をモックできます。カスタムレスポンスを指定しない場合、Laravel は各質問の形式に合う回答を自動的に生成します。

use Laravel\Ai\Classification;
use Laravel\Ai\Prompts\ClassificationPrompt;
use Laravel\Ai\Responses\Data\BooleanAnswer;
use Laravel\Ai\Responses\Data\ChoiceAnswer;

// Automatically generate fake answers...
Classification::fake();

// Provide answers for specific questions...
Classification::fake([
[
'urgent' => new BooleanAnswer(0.94),
'department' => new ChoiceAnswer('technical', [
'billing' => 0.08,
'technical' => 0.87,
'sales' => 0.05,
], confidence: 0.82),
],
]);

// Build answers from the prompt...
Classification::fake(fn (ClassificationPrompt $prompt) => [
'urgent' => new BooleanAnswer($prompt->contains('ASAP') ? 1.0 : 0.0),
]);

フェイクレスポンスで省略した質問にも生成された回答が返されるため、テストではアサーションの対象にする回答だけを指定すれば十分です。

分類した後は、実行された操作についてアサーションを記述できます。

Classification::assertClassified(function (ClassificationPrompt $prompt) {
return $prompt->contains('refund') && $prompt->asks('department');
});

Classification::assertNotClassified(
fn (ClassificationPrompt $prompt) => $prompt->asks('sentiment')
);

Classification::assertNothingClassified();

Files​

ファイル操作は、Files クラスの fake メソッドを呼び出すことでフェイクできます。

use Laravel\Ai\Files;

Files::fake();

ファイル操作が偽装されると、発生したアップロードと削除についてアサーションを行うことができます。

use Laravel\Ai\Contracts\Files\StorableFile;
use Laravel\Ai\Files\Document;

// Store files...
Document::fromString('Hello, Laravel!', mimeType: 'text/plain')
->as('hello.txt')
->put();

// Make assertions...
Files::assertStored(fn (StorableFile $file) =>
(string) $file === 'Hello, Laravel!' &&
$file->mimeType() === 'text/plain'
);

Files::assertNotStored(fn (StorableFile $file) =>
(string) $file === 'Hello, World!'
);

Files::assertNothingStored();

ファイルの削除をアサートするには、ファイル ID を渡すことができます。

Files::assertDeleted('file-id');
Files::assertNotDeleted('file-id');
Files::assertNothingDeleted();

Vector Stores​

Stores クラスの fake メソッドを呼び出すことで、ベクターストアの操作をフェイクできます。ストアをフェイクすると、file operations も自動的にフェイクされます。

use Laravel\Ai\Stores;

Stores::fake();

ストア操作が偽装されると、作成または削除されたストアについてアサーションを行うことができます。

use Laravel\Ai\Stores;

// Create store...
$store = Stores::create('Knowledge Base');

// Make assertions...
Stores::assertCreated('Knowledge Base');

Stores::assertCreated(fn (string $name, ?string $description) =>
$name === 'Knowledge Base'
);

Stores::assertNotCreated('Other Store');

Stores::assertNothingCreated();

ストアの削除に対してアサートするには、ストア ID を指定できます。

Stores::assertDeleted('store_id');
Stores::assertNotDeleted('other_store_id');
Stores::assertNothingDeleted();

ファイルがストアに追加またはストアから削除されたことをアサートするには、特定の Store インスタンスでアサーション メソッドを使用します。

Stores::fake();

$store = Stores::get('store_id');

// Add / remove files...
$store->add('added_id');
$store->remove('removed_id');

// Make assertions...
$store->assertAdded('added_id');
$store->assertRemoved('removed_id');

$store->assertNotAdded('other_file_id');
$store->assertNotRemoved('other_file_id');

ファイルがプロバイダの file storage に保存され、同じリクエスト内のベクター ストアに追加された場合、ファイルのプロバイダ 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!');

Events​

Laravel AI SDK は、次のようなさまざまな events をディスパッチします。

  • AddingFileToStore
  • AddingFileToStoreFailed
  • AgentFailed
  • AgentFailedOver
  • AgentPrompted
  • AgentStreamed
  • AudioFailed
  • AudioGenerated
  • ClassificationFailed
  • Classified
  • Classifying
  • CreatingStore
  • CreatingStoreFailed
  • EmbeddingsFailed
  • EmbeddingsGenerated
  • FileAddedToStore
  • FileDeleted
  • FileRemovedFromStore
  • FileStored
  • GeneratingAudio
  • GeneratingEmbeddings
  • GeneratingImage
  • GeneratingTranscription
  • ImageFailed
  • ImageGenerated
  • InvokingTool
  • PromptingAgent
  • ProviderFailedOver
  • RemovingFileFromStore
  • RemovingFileFromStoreFailed
  • Reranked
  • Reranking
  • RerankingFailed
  • StartingStep
  • StepCompleted
  • StepFailed
  • StoreCreated
  • StoreDeleted
  • StoringFile
  • StoringFileFailed
  • StreamingAgent
  • ToolApprovalRequested
  • ToolApprovalResolved
  • ToolFailed
  • ToolInvoked
  • TranscriptionFailed
  • TranscriptionGenerated

これらのイベントのいずれかをリッスンして、AI SDK の使用情報を記録または保存できます。