> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Laravel AI SDK

> OpenAI, Anthropic, Gemini 등 여러 AI 프로바이더를 통합 인터페이스로 다룰 수 있는 Laravel AI SDK의 완전 가이드. 에이전트, 이미지 생성, 음성, 임베딩, 테스트까지 아우릅니다.

## 시작하기

[Laravel AI SDK](https://github.com/laravel/ai)는 OpenAI, Anthropic, Gemini 등의 AI 프로바이더와 상호작용하기 위한 통합적이고 표현력 있는 API를 제공합니다. AI SDK를 사용하면 도구 및 구조화된 출력을 갖춘 지능형 에이전트 구축, 이미지 생성, 음성 합성 및 음성 인식, 벡터 임베딩 생성 등 다양한 AI 기능을 일관된 Laravel다운 인터페이스로 구현할 수 있습니다.

<Info>
  Laravel AI SDK는 Laravel 13에서 추가된 공식 패키지입니다. `laravel/ai`로 제공되며, 여러 AI 프로바이더를 통합 API로 다룰 수 있습니다.
</Info>

## 프로바이더 지원 목록

| 기능         | 지원 프로바이더                                                                                                       |
| ---------- | -------------------------------------------------------------------------------------------------------------- |
| 텍스트 생성     | OpenAI, OpenAI Compatible, Anthropic, Gemini, Azure, Bedrock, Groq, xAI, DeepSeek, Mistral, Ollama, OpenRouter |
| 이미지 생성     | OpenAI, Gemini, xAI, Azure, Bedrock, OpenRouter                                                                |
| 음성 합성(TTS) | OpenAI, ElevenLabs, Gemini                                                                                     |
| 음성 인식(STT) | OpenAI, ElevenLabs, Mistral, Gemini                                                                            |
| 임베딩        | OpenAI, Gemini, Azure, Bedrock, Cohere, Mistral, Jina, VoyageAI, Ollama, OpenRouter                            |
| 리랭킹        | Cohere, Jina, VoyageAI                                                                                         |
| 파일         | OpenAI, Anthropic, Gemini, Azure                                                                               |

## 설치

<Steps>
  <Step title="패키지 설치">
    Composer로 Laravel AI SDK를 설치합니다.

    ```shell theme={null}
    composer require laravel/ai
    ```
  </Step>

  <Step title="설정 파일과 마이그레이션 공개">
    `vendor:publish` Artisan 명령어로 설정 파일과 마이그레이션을 공개합니다.

    ```shell theme={null}
    php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
    ```
  </Step>

  <Step title="마이그레이션 실행">
    데이터베이스 마이그레이션을 실행합니다. `agent_conversations`와 `agent_conversation_messages` 테이블이 생성되어 대화 이력 저장에 사용됩니다.

    ```shell theme={null}
    php artisan migrate
    ```
  </Step>
</Steps>

## 설정

### 환경 변수

사용할 AI 프로바이더의 API 키를 `.env` 파일에 설정합니다.

```ini theme={null}
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=
OPENROUTER_API_KEY=
JINA_API_KEY=
VOYAGEAI_API_KEY=
XAI_API_KEY=
```

텍스트, 이미지, 음성, 문자 변환, 임베딩에 사용되는 기본 모델은 `config/ai.php`에서도 설정할 수 있습니다.

### 커스텀 베이스 URL

프록시 서비스를 경유하는 경우, 프로바이더별로 커스텀 URL을 설정할 수 있습니다.

```php theme={null}
'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'),
    ],
],
```

커스텀 베이스 URL은 OpenAI, Anthropic, Gemini, Groq, Cohere, DeepSeek, xAI, OpenRouter에서 사용할 수 있습니다.

### OpenAI-Compatible 프로바이더

LM Studio, vLLM, Together, Fireworks, 로컬 게이트웨이 등 OpenAI 호환 API를 사용하는 경우 `openai-compatible` 드라이버로 프로바이더를 설정할 수 있습니다. `url`은 필수이며, `key`를 지정하면 Bearer 토큰으로 전송됩니다.

```php theme={null}
'providers' => [
    'local' => [
        'driver' => 'openai-compatible',
        'url' => env('LOCAL_AI_URL'),
        'key' => env('LOCAL_AI_API_KEY'),
    ],
],
```

설정 후에는 다른 프로바이더와 동일하게 프로바이더 이름으로 지정할 수 있습니다.

```php theme={null}
agent()->prompt('What is Laravel?', provider: 'local', model: 'local-model');
```

기본 텍스트 모델을 설정해 두면 매번 모델을 지정하지 않고 사용할 수 있습니다.

```php theme={null}
'local' => [
    'driver' => 'openai-compatible',
    'url' => env('LOCAL_AI_URL'),
    'key' => env('LOCAL_AI_API_KEY'),
    'models' => [
        'text' => [
            'default' => env('LOCAL_AI_MODEL'),
        ],
    ],
],
```

OpenAI-Compatible 프로바이더는 텍스트 생성, 스트리밍, 도구, 구조화된 출력, 이미지 첨부를 지원합니다. 엔드포인트에서 추가 요청 바디 필드가 필요한 경우, [프로바이더 옵션](#프로바이더-옵션)을 사용하십시오.

### Lab enum

코드 내에서 프로바이더를 참조할 때는 `Lab` enum을 사용합니다.

```php theme={null}
use Laravel\Ai\Enums\Lab;

Lab::Anthropic;
Lab::OpenAI;
Lab::Gemini;
```

***

## 에이전트

에이전트는 Laravel AI SDK의 기본 구성 요소입니다. `make:agent` 명령어로 에이전트 클래스를 생성할 수 있습니다.

```shell theme={null}
php artisan make:agent SalesCoach

# 구조화된 출력이 있는 에이전트 생성
php artisan make:agent SalesCoach --structured
```

생성된 에이전트는 `app/Ai/Agents/` 디렉터리에 배치됩니다. 다음은 주요 인터페이스를 모두 구현한 에이전트의 예시입니다.

```php theme={null}
<?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(),
        ];
    }
}
```

```mermaid theme={null}
flowchart TD
    A["사용자의 프롬프트"] --> B["Agent::prompt()"]
    B --> C["옵션 부여"]
    C -->|"Conversational 인 경우"| D["대화 이력 부여"]
    C -->|"HasTools 인 경우"| E["도구 목록 부여"]
    C -->|"HasStructuredOutput 인 경우"| F["JSON 스키마 부여"]
    D --> G["AI 프로바이더로 전송"]
    E --> G
    F --> G
    B --> G
    G --> H["응답"]
    H -->|"도구 호출"| I["도구 실행"]
    I --> G
    H -->|"완료"| J["AgentResponse"]
```

### 프롬프트

`prompt()` 메서드로 에이전트에 메시지를 보냅니다.

```php theme={null}
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');
return (string) $response;
```

`make()` 정적 메서드를 사용하면 컨테이너에서 의존성을 해결하여 인스턴스를 생성할 수 있습니다.

```php theme={null}
$agent = SalesCoach::make(user: $user);
```

프로바이더, 모델, 타임아웃은 `prompt()`의 인수로 재정의할 수 있습니다.

```php theme={null}
$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: Lab::Anthropic,
    model: 'claude-haiku-4-5-20251001',
    timeout: 120,
);
```

### 대화 컨텍스트

`Conversational` 인터페이스를 구현하고 `messages()` 메서드를 정의하면 과거 대화 이력을 AI에 전달할 수 있습니다.

`RemembersConversations` 트레이트를 사용하면 대화 이력을 데이터베이스에 자동으로 저장 및 조회할 수 있습니다.

```php theme={null}
use Laravel\Ai\Concerns\RemembersConversations;

class SalesCoach implements Agent, Conversational
{
    use Promptable, RemembersConversations;

    public function instructions(): string
    {
        return 'You are a sales coach...';
    }
}
```

`forUser()`로 대화를 시작하고, 반환된 `conversationId`를 사용해 `continue()`로 이어지는 대화를 할 수 있습니다.

```php theme={null}
$response = (new SalesCoach)->forUser($user)->prompt('Hello!');
$conversationId = $response->conversationId;

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

### 구조화된 출력

`HasStructuredOutput` 인터페이스를 구현하고 `schema()` 메서드로 JSON 스키마를 정의하면, AI의 응답을 구조화된 데이터로 받을 수 있습니다.

```php theme={null}
public function schema(JsonSchema $schema): array
{
    return ['score' => $schema->integer()->required()];
}

$response = (new SalesCoach)->prompt('Analyze this...');
return $response['score'];
```

#### 중첩된 객체

```php theme={null}
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(),
    ];
}
```

#### 객체의 배열

```php theme={null}
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(여러 스키마 중 선택)

값이 여러 스키마 중 하나에 매치되는 경우, `anyOf` 메서드를 사용합니다.

```php theme={null}
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` 인수로 문서나 이미지를 에이전트에 전달할 수 있습니다.

```php theme={null}
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'),
    ]
);
```

이미지 첨부도 동일하게 할 수 있습니다.

```php theme={null}
$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()` 메서드를 사용하면 응답을 청크 단위로 반환할 수 있습니다. 긴 응답을 실시간으로 프런트엔드에 보내는 데 적합합니다.

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

`then()` 콜백으로 스트리밍 완료 후 처리를 기술할 수 있습니다.

```php theme={null}
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...
        });
});
```

스트림을 수동으로 순회할 수도 있습니다.

```php theme={null}
$stream = (new SalesCoach)->stream('Analyze this sales transcript...');

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

#### Vercel AI SDK 프로토콜

프런트엔드에서 Vercel AI SDK를 사용하는 경우 `usingVercelDataProtocol()`을 호출합니다.

```php theme={null}
Route::get('/coach', function () {
    return (new SalesCoach)->stream('Analyze...')->usingVercelDataProtocol();
});
```

### 브로드캐스트

스트림의 이벤트를 Laravel Echo 등의 브로드캐스트 채널로 전송할 수 있습니다.

```php theme={null}
use Illuminate\Broadcasting\Channel;

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

foreach ($stream as $event) {
    $event->broadcast(new Channel('channel-name'));
}
```

`broadcastOnQueue()`를 사용하면 큐를 경유해 브로드캐스트할 수 있습니다.

```php theme={null}
(new SalesCoach)->broadcastOnQueue(
    'Analyze this sales transcript...',
    new Channel('channel-name'),
);
```

#### 거대한 이벤트 스킵

브로드캐스트 플랫폼에 따라 WebSocket 메시지를 약 10KB로 제한하는 경우가 있습니다. 큰 도구 결과 등 데이터 양이 많은 스트림 이벤트가 이 상한을 초과해 브로드캐스트에 실패할 수 있습니다. `WithoutBroadcasting` Attribute를 사용해 특정 이벤트 유형을 브로드캐스트에서 제외할 수 있습니다.

```php theme={null}
<?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`) 모두에서 동작합니다.

### 큐

`queue()` 메서드로 프롬프트를 큐에 적재해 비동기 처리할 수 있습니다.

```php theme={null}
use Laravel\Ai\Responses\AgentResponse;

Route::post('/coach', function (Request $request) {
    (new SalesCoach)
        ->queue($request->input('transcript'))
        ->then(function (AgentResponse $response) { /* ... */ })
        ->catch(function (Throwable $e) { /* ... */ });

    return back();
});
```

### 도구

도구를 사용하면 AI가 코드 내의 함수를 호출할 수 있게 됩니다. `make:tool` 명령어로 도구 클래스를 생성할 수 있습니다.

```shell theme={null}
php artisan make:tool RandomNumberGenerator
```

```php theme={null}
<?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
{
    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(),
        ];
    }
}
```

에이전트의 `tools()` 메서드로 도구를 등록합니다.

```php theme={null}
public function tools(): iterable
{
    return [new RandomNumberGenerator];
}
```

#### 유사도 검색 도구

벡터 임베딩을 사용한 유사도 검색 도구를 간단히 추가할 수 있습니다.

```php theme={null}
use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;

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

옵션을 지정할 수도 있습니다.

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

클로저로 독자적인 검색 로직을 정의할 수도 있습니다.

```php theme={null}
new SimilaritySearch(using: function (string $query) {
    return Document::query()
        ->where('user_id', $this->user->id)
        ->whereVectorSimilarTo('embedding', $query)
        ->limit(10)
        ->get();
}),
```

`withDescription()`으로 도구의 설명을 커스터마이즈할 수 있습니다.

```php theme={null}
SimilaritySearch::usingModel(Document::class, 'embedding')
    ->withDescription('Search the knowledge base for relevant articles.'),
```

### 파일 스토리지 도구

`FileStorage` 도구 팩토리를 사용하면 에이전트에 Laravel [파일시스템 디스크](/ko/filesystem)에 대한 접근을 부여할 수 있습니다. `all` 메서드는 지정한 디스크 상의 파일을 목록화, 읽기, URL 생성, 쓰기, 삭제, 복사하는 도구 세트를 반환합니다.

```php theme={null}
use Laravel\Ai\Tools\FileStorage;

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

읽기 전용 접근만 허용하려면 `readOnly` 메서드를 사용합니다.

```php theme={null}
return FileStorage::readOnly('local');
```

이 메서드들은 `Illuminate\Support\Collection`을 반환하므로 제공할 도구를 더 좁힐 수 있습니다.

```php theme={null}
use Laravel\Ai\Tools\Filesystem\DeleteFile;

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

### MCP 도구

애플리케이션에서 [Laravel MCP](/ko/mcp)를 사용하고 있다면, [Model Context Protocol](https://modelcontextprotocol.io) 서버가 공개하는 도구를 에이전트에 제공할 수 있습니다. [Laravel MCP 클라이언트](/ko/mcp#mcp-client)를 사용해 원격 또는 로컬 MCP 서버에 접속하고, 그 도구를 에이전트에 직접 전달할 수 있습니다.

<Info>
  MCP 도구를 사용하려면 애플리케이션에 [Laravel MCP](/ko/mcp) 패키지가 설치되어 있어야 합니다.
</Info>

MCP 클라이언트의 `tools` 메서드는 컬렉션을 반환하므로 `...` 스프레드 연산자로 에이전트의 `tools` 배열에 펼칩니다.

```php theme={null}
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 도구를 자동으로 래핑해 에이전트가 다른 도구와 동일하게 호출할 수 있게 합니다. [이름 있는 MCP 클라이언트](/ko/mcp#named-clients)를 사용할 수도 있습니다.

```php theme={null}
use Laravel\Mcp\Facades\Mcp;

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

또는 [로컬 MCP 서버](/ko/mcp#client-connecting)에 접속하는 것도 가능합니다.

```php theme={null}
use Laravel\Mcp\Client;

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

MCP 클라이언트의 생성과 인증(Bearer 토큰이나 OAuth 등)에 대해서는 [MCP 클라이언트 문서](/ko/mcp#mcp-client)를 참조하십시오.

### 프로바이더 도구

AI 프로바이더가 네이티브로 구현하고 있는 특별한 도구입니다.

#### Web 검색

웹 검색을 에이전트에 추가합니다. Anthropic, OpenAI, Gemini, OpenRouter에 대응하고 있습니다.

```php theme={null}
use Laravel\Ai\Providers\Tools\WebSearch;

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

옵션으로 검색 건수나 도메인 필터, 위치 정보를 지정할 수 있습니다.

```php theme={null}
(new WebSearch)->max(5)->allow(['laravel.com', 'php.net']),
(new WebSearch)->location(city: 'New York', region: 'NY', country: 'US');
```

#### Web 페치

지정한 URL의 콘텐츠를 가져오는 도구입니다. Anthropic, Gemini에 대응하고 있습니다.

```php theme={null}
use Laravel\Ai\Providers\Tools\WebFetch;

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

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

#### 파일 검색

벡터 스토어에서 문서를 검색하는 도구입니다. OpenAI, Gemini에 대응하고 있습니다.

```php theme={null}
use Laravel\Ai\Providers\Tools\FileSearch;

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

// 여러 스토어 지정
new FileSearch(stores: ['store_1', 'store_2']);

// 메타데이터로 필터링
new FileSearch(stores: ['store_id'], where: ['author' => 'Taylor Otwell', 'year' => 2026]);
```

`FileSearchQuery`를 사용한 복잡한 필터도 지정할 수 있습니다.

```php theme={null}
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'])
);
```

### 서브 에이전트

에이전트는 다른 에이전트의 `tools()` 메서드에서 반환할 수도 있습니다. 에이전트를 도구로서 등록하면, 부모 에이전트가 특정 작업을 서브 에이전트에 위임하고 그 결과를 원래 응답에 통합할 수 있습니다. 범용 에이전트가 전문화된 지시, 도구, 모델 설정, 프로바이더 설정을 가진 특화형 에이전트에 접근할 때 편리합니다.

예를 들어 고객 지원 에이전트가 환불 정책 질문을 환불 전문 에이전트에 위임하는 예시입니다.

```php theme={null}
<?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;

    public function instructions(): string
    {
        return 'You help customers with account, order, and billing questions. Delegate refund policy questions to the refunds specialist.';
    }

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

부모 에이전트에게 서브 에이전트를 보이는 방법을 커스터마이즈하려면, 서브 에이전트에 `CanActAsTool` 인터페이스를 구현하고 도구용 이름과 설명을 정의합니다.

```php theme={null}
<?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;

    public function instructions(): string
    {
        return 'You are a refunds specialist. Use order details and the refund policy to give concise eligibility guidance.';
    }

    public function name(): string
    {
        return 'refunds_specialist';
    }

    public function description(): string
    {
        return 'Determine whether an order is eligible for a refund and explain the next step.';
    }

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

`CanActAsTool`을 구현하지 않은 서브 에이전트의 경우, Laravel은 클래스 이름을 도구 이름으로 사용하고 범용적인 설명문을 자동으로 생성합니다. 각 서브 에이전트의 호출은 독립적으로 이루어지며, 부모 에이전트의 대화 이력은 이어받지 않습니다.

### 미들웨어

에이전트에 미들웨어를 추가해 프롬프트나 응답을 가로챌 수 있습니다.

```shell theme={null}
php artisan make:agent-middleware LogPrompts
```

에이전트에 `HasMiddleware` 인터페이스를 구현하고, `middleware()` 메서드로 미들웨어를 등록합니다.

```php theme={null}
<?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;

    public function middleware(): array
    {
        return [new LogPrompts];
    }
}
```

미들웨어 클래스의 구현 예시입니다.

```php theme={null}
<?php

namespace App\Ai\Middleware;

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()`을 사용하면 응답 이후의 처리도 추가할 수 있습니다.

```php theme={null}
public function handle(AgentPrompt $prompt, Closure $next)
{
    return $next($prompt)->then(function (AgentResponse $response) {
        Log::info('Agent responded', ['text' => $response->text]);
    });
}
```

### 익명 에이전트

클래스를 정의하지 않고 `agent()` 헬퍼로 익명 에이전트를 사용할 수 있습니다.

```php theme={null}
use function Laravel\Ai\{agent};

$response = agent(
    instructions: 'You are an expert at software development.',
    messages: [],
    tools: [],
)->prompt('Tell me about Laravel');
```

구조화된 출력이 있는 익명 에이전트도 만들 수 있습니다.

```php theme={null}
use Illuminate\Contracts\JsonSchema\JsonSchema;

$response = agent(
    schema: fn (JsonSchema $schema) => ['number' => $schema->integer()->required()],
)->prompt('Generate a random number less than 100');
```

### 에이전트 설정(PHP Attributes)

PHP Attribute를 사용해 에이전트의 기본 설정을 선언적으로 기술할 수 있습니다.

| Attribute             | 설명                 |
| --------------------- | ------------------ |
| `#[Provider]`         | 사용할 프로바이더 지정       |
| `#[Model]`            | 사용할 모델 지정          |
| `#[MaxSteps]`         | 도구 호출의 최대 스텝 수     |
| `#[MaxTokens]`        | 최대 토큰 수            |
| `#[Temperature]`      | 온도 파라미터(0.0\~1.0)  |
| `#[TopP]`             | 핵 샘플링 확률(0.0\~1.0) |
| `#[Timeout]`          | 타임아웃(초)            |
| `#[UseCheapestModel]` | 최저가 모델 자동 선택       |
| `#[UseSmartestModel]` | 최고 성능 모델 자동 선택     |

```php theme={null}
<?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-haiku-4-5-20251001')]
#[MaxSteps(10)]
#[MaxTokens(4096)]
#[Temperature(0.7)]
#[Timeout(120)]
#[TopP(0.9)]
class SalesCoach implements Agent
{
    use Promptable;
}
```

모델 선택 단축 Attribute도 준비되어 있습니다.

```php theme={null}
// 가장 저렴한 모델 사용
#[UseCheapestModel]
class SimpleSummarizer implements Agent
{
    use Promptable;
}

// 가장 고성능인 모델 사용
#[UseSmartestModel]
class ComplexReasoner implements Agent
{
    use Promptable;
}
```

### 프로바이더 옵션

`HasProviderOptions` 인터페이스를 구현하면 프로바이더 고유의 옵션을 전달할 수 있습니다.

```php theme={null}
<?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;

    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],
            ],
            default => [],
        };
    }
}
```

***

## 이미지 생성

`Image` 클래스로 이미지를 생성할 수 있습니다. OpenAI, Gemini, xAI 프로바이더가 지원합니다.

```php theme={null}
use Laravel\Ai\Image;

$image = Image::of('A donut sitting on the kitchen counter')->generate();
$rawContent = (string) $image;
```

품질이나 종횡비, 타임아웃을 지정할 수 있습니다.

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

참조 이미지를 첨부해 가공할 수도 있습니다.

```php theme={null}
use Laravel\Ai\Files;

$image = Image::of('Update this photo to be in the style of an impressionist painting.')
    ->attachments([Files\Image::fromStorage('photo.jpg')])
    ->landscape()
    ->generate();
```

### 이미지 저장

```php theme={null}
$path = $image->store();
$path = $image->storeAs('image.jpg');
$path = $image->storePublicly();
$path = $image->storePubliclyAs('image.jpg');
```

### 큐로 이미지 생성

```php theme={null}
use Laravel\Ai\Responses\ImageResponse;

Image::of('A donut sitting on the kitchen counter')
    ->portrait()
    ->queue()
    ->then(function (ImageResponse $image) {
        $path = $image->store();
    });
```

***

## 음성 합성(TTS)

`Audio` 클래스로 텍스트를 음성으로 변환할 수 있습니다. OpenAI, ElevenLabs 프로바이더가 지원합니다.

```php theme={null}
use Laravel\Ai\Audio;

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

음성의 성별이나 구체적인 보이스 ID, 말투의 지시도 지정할 수 있습니다.

```php theme={null}
$audio = Audio::of('I love coding with Laravel.')->female()->generate();
$audio = Audio::of('I love coding with Laravel.')->voice('voice-id-or-name')->generate();
$audio = Audio::of('I love coding with Laravel.')->female()->instructions('Said like a pirate')->generate();
```

### 음성 저장

```php theme={null}
$path = $audio->store();
$path = $audio->storeAs('audio.mp3');
$path = $audio->storePublicly();
$path = $audio->storePubliclyAs('audio.mp3');
```

### 큐로 음성 생성

```php theme={null}
use Laravel\Ai\Responses\AudioResponse;

Audio::of('I love coding with Laravel.')
    ->queue()
    ->then(function (AudioResponse $audio) {
        $path = $audio->store();
    });
```

***

## 음성 인식(STT)

`Transcription` 클래스로 오디오 파일을 텍스트로 변환할 수 있습니다. OpenAI, ElevenLabs, Mistral 프로바이더가 지원합니다.

```php theme={null}
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()`를 사용하면 화자별로 분리된 문자 변환 결과를 얻을 수 있습니다.

```php theme={null}
$transcript = Transcription::fromStorage('audio.mp3')->diarize()->generate();
```

### 큐로 문자 변환

```php theme={null}
use Laravel\Ai\Responses\TranscriptionResponse;

Transcription::fromStorage('audio.mp3')
    ->queue()
    ->then(function (TranscriptionResponse $transcript) { /* ... */ });
```

***

## 임베딩(Embeddings)

텍스트를 벡터 표현으로 변환해 유사도 검색 등에 활용할 수 있습니다.

```php theme={null}
use Illuminate\Support\Str;

// Stringable 사용 방법
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings();
```

```php theme={null}
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, ...]]
```

프로바이더, 모델, 차원 수를 지정할 수도 있습니다.

```php theme={null}
$response = Embeddings::for(['Napa Valley has great wine.'])
    ->dimensions(1536)
    ->generate(Lab::OpenAI, 'text-embedding-3-small');
```

### 벡터 검색(pgvector)

PostgreSQL과 pgvector 확장을 사용한 벡터 검색의 설정 예시입니다.

<Steps>
  <Step title="마이그레이션 생성">
    ```php theme={null}
    Schema::ensureVectorExtensionExists();

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

    // HNSW 인덱스 추가
    $table->vector('embedding', dimensions: 1536)->index();
    ```
  </Step>

  <Step title="모델 설정">
    ```php theme={null}
    protected function casts(): array
    {
        return ['embedding' => 'array'];
    }
    ```
  </Step>

  <Step title="유사도 검색 쿼리">
    ```php theme={null}
    // 임베딩 벡터로 검색
    $documents = Document::query()
        ->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
        ->limit(10)
        ->get();

    // 문자열을 전달하면 자동으로 임베딩 생성
    $documents = Document::query()
        ->whereVectorSimilarTo('embedding', 'best wineries in Napa Valley')
        ->limit(10)
        ->get();
    ```
  </Step>
</Steps>

저수준 메서드도 사용할 수 있습니다.

```php theme={null}
$documents = Document::query()
    ->select('*')
    ->selectVectorDistance('embedding', $queryEmbedding, as: 'distance')
    ->whereVectorDistanceLessThan('embedding', $queryEmbedding, maxDistance: 0.3)
    ->orderByVectorDistance('embedding', $queryEmbedding)
    ->limit(10)
    ->get();
```

### 임베딩 캐시

같은 텍스트의 임베딩 생성을 반복하지 않도록 캐시할 수 있습니다.

`config/ai.php`에서 기본 캐시 설정을 합니다.

```php theme={null}
'caching' => [
    'embeddings' => [
        'cache' => true,
        'store' => env('CACHE_STORE', 'database'),
    ],
],
```

요청별로 캐시를 제어할 수도 있습니다.

```php theme={null}
$response = Embeddings::for(['Napa Valley has great wine.'])->cache()->generate();
$response = Embeddings::for(['Napa Valley has great wine.'])->cache(seconds: 3600)->generate();

// Stringable에서도 동일
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: true);
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: 3600);
```

***

## 리랭킹

검색 결과를 쿼리와의 관련도로 리랭크(정렬)할 수 있습니다. Cohere, Jina 프로바이더가 지원합니다.

```php theme={null}
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
```

`limit()`으로 반환 건수를 좁힐 수 있습니다.

```php theme={null}
$response = Reranking::of($documents)->limit(5)->rerank('search query');
```

### 컬렉션의 리랭킹

Eloquent 컬렉션을 직접 리랭크할 수 있습니다.

```php theme={null}
// 단일 필드
$posts = Post::all()->rerank('body', 'Laravel tutorials');

// 여러 필드(JSON으로 전송)
$reranked = $posts->rerank(['title', 'body'], 'Laravel tutorials');

// 클로저로 독자적 텍스트 생성
$reranked = $posts->rerank(fn ($post) => $post->title.': '.$post->body, 'Laravel tutorials');

// 옵션 포함
$reranked = $posts->rerank(
    by: 'content',
    query: 'Laravel tutorials',
    limit: 10,
    provider: Lab::Cohere,
);
```

***

## 파일 관리

AI 프로바이더에 파일을 업로드해 나중에 참조할 수 있습니다.

```php theme={null}
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();

// URL에서
$response = Document::fromUrl('https://example.com/document.pdf')->put();
$response = Image::fromUrl('https://example.com/photo.jpg')->put();

return $response->id;
```

문자열이나 폼 업로드에서도 다룰 수 있습니다.

```php theme={null}
$stored = Document::fromString('Hello, World!', 'text/plain')->put();
$stored = Document::fromUpload($request->file('document'))->put();
```

### 저장된 파일 참조

업로드된 파일 ID로 에이전트에 첨부할 수 있습니다.

```php theme={null}
use Laravel\Ai\Files;

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

### 파일 조회·삭제

```php theme={null}
// 조회
$file = Document::fromId('file-id')->get();
$file->id;
$file->mimeType();

// 삭제
Document::fromId('file-id')->delete();
```

### 프로바이더 지정

```php theme={null}
$response = Document::fromPath('/home/laravel/document.pdf')->put(provider: Lab::Anthropic);
```

### 프로바이더 고유 옵션 지정

`withProviderOptions` 메서드로 프로바이더 고유의 업로드 옵션을 전달할 수 있습니다. 예를 들어 OpenAI의 파일 `purpose`를 설정할 수 있습니다.

```php theme={null}
use Laravel\Ai\Files\Document;

$response = Document::fromPath('/home/laravel/knowledge.txt')
    ->withProviderOptions(['purpose' => 'assistants'])
    ->put();
```

프로바이더마다 다른 옵션을 지정하는 경우는 클로저를 전달합니다.

```php theme={null}
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();
```

***

## 벡터 스토어

벡터 스토어를 사용하면 문서를 프로바이더 측에서 관리할 수 있습니다.

```php theme={null}
use Laravel\Ai\Stores;

// 생성
$store = Stores::create('Knowledge Base');
$store = Stores::create(
    name: 'Knowledge Base',
    description: 'Documentation.',
    expiresWhenIdleFor: days(30),
);
return $store->id;

// 조회
$store = Stores::get('store_id');
$store->id;
$store->name;
$store->fileCounts;
$store->ready;

// 삭제
Stores::delete('store_id');
$store->delete();
```

### 스토어에 파일 추가

```php theme={null}
$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;
```

메타데이터를 부여할 수도 있습니다.

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

### 스토어에서 파일 삭제

```php theme={null}
$store->remove('file_id');

// 파일 자체도 삭제하는 경우
$store->remove('file_abc123', deleteFile: true);
```

***

## 페일오버

여러 프로바이더를 배열로 지정하면 첫 번째 프로바이더가 실패한 경우 다음 프로바이더로 자동 폴백됩니다.

```php theme={null}
use App\Ai\Agents\SalesCoach;
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]);
```

***

## 테스트

Laravel AI SDK는 테스트용 페이크 기능을 제공하여, 실제 API를 호출하지 않고도 테스트할 수 있습니다.

### 에이전트 테스트

```php theme={null}
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;
});

// 어서션
SalesCoach::assertPrompted('Analyze this...');
SalesCoach::assertPrompted(function (AgentPrompt $prompt) {
    return $prompt->contains('Analyze');
});
SalesCoach::assertNotPrompted('Missing prompt');
SalesCoach::assertNeverPrompted();
```

큐잉의 어서션도 준비되어 있습니다.

```php theme={null}
use Laravel\Ai\QueuedAgentPrompt;

SalesCoach::assertQueued('Analyze this...');
SalesCoach::assertQueued(function (QueuedAgentPrompt $prompt) {
    return $prompt->contains('Analyze');
});
SalesCoach::assertNotQueued('Missing prompt');
SalesCoach::assertNeverQueued();
```

`preventStrayPrompts()`를 사용하면 페이크로 정의하지 않은 프롬프트가 호출된 경우 예외를 던집니다.

```php theme={null}
SalesCoach::fake()->preventStrayPrompts();
```

구조화된 출력을 반환하는 에이전트를 페이크하는 경우, 배열로 응답을 지정할 수 있습니다. 에이전트는 지정한 데이터를 포함한 구조화된 응답을 반환합니다.

```php theme={null}
SalesCoach::fake([
    ['score' => 87],
]);
```

<Info>
  구조화된 출력 에이전트에 대해 `fake()`가 페이크 데이터를 명시적으로 전달하지 않고 호출되면, Laravel은 에이전트가 정의한 스키마에 부합하는 페이크 데이터를 자동 생성합니다.
</Info>

익명 에이전트의 테스트에는 `AnonymousAgent::fake()`를 사용합니다.

```php theme={null}
use Laravel\Ai\AnonymousAgent;

AnonymousAgent::fake(['Test response']);
```

### 이미지 생성 테스트

```php theme={null}
use Laravel\Ai\Image;
use Laravel\Ai\Prompts\ImagePrompt;

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

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

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

### 음성 합성 테스트

```php theme={null}
use Laravel\Ai\Audio;
use Laravel\Ai\Prompts\AudioPrompt;

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

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

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

### 문자 변환 테스트

```php theme={null}
use Laravel\Ai\Transcription;
use Laravel\Ai\Prompts\TranscriptionPrompt;

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

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

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

### 임베딩 테스트

```php theme={null}
use Laravel\Ai\Embeddings;
use Laravel\Ai\Prompts\EmbeddingsPrompt;

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

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

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

### 리랭킹 테스트

```php theme={null}
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();
```

### 파일 테스트

```php theme={null}
use Laravel\Ai\Files;
use Laravel\Ai\Contracts\Files\StorableFile;
use Laravel\Ai\Files\Document;

Files::fake();

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

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

### 벡터 스토어 테스트

```php theme={null}
use Laravel\Ai\Stores;

Stores::fake(); // 파일 조작도 페이크됨

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

스토어에 대한 파일 조작의 어서션도 할 수 있습니다.

```php theme={null}
$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');

// 클로저로 콘텐츠를 검증
$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는 다음의 이벤트를 디스패치합니다. 이 이벤트들을 리스닝함으로써 로그 기록이나 모니터링에 활용할 수 있습니다.

<AccordionGroup>
  <Accordion title="에이전트 관련">
    * `PromptingAgent` — 프롬프트 전송 전
    * `AgentPrompted` — 프롬프트 전송 후
    * `StreamingAgent` — 스트리밍 시작 시
    * `AgentStreamed` — 스트리밍 완료 후
    * `InvokingTool` — 도구 호출 전
    * `ToolInvoked` — 도구 호출 후
  </Accordion>

  <Accordion title="이미지·음성·문자 변환 관련">
    * `GeneratingImage` — 이미지 생성 전
    * `ImageGenerated` — 이미지 생성 후
    * `GeneratingAudio` — 음성 생성 전
    * `AudioGenerated` — 음성 생성 후
    * `GeneratingTranscription` — 문자 변환 전
    * `TranscriptionGenerated` — 문자 변환 후
  </Accordion>

  <Accordion title="임베딩·리랭킹 관련">
    * `GeneratingEmbeddings` — 임베딩 생성 전
    * `EmbeddingsGenerated` — 임베딩 생성 후
    * `Reranking` — 리랭킹 전
    * `Reranked` — 리랭킹 후
  </Accordion>

  <Accordion title="파일·스토어 관련">
    * `StoringFile` — 파일 저장 전
    * `FileStored` — 파일 저장 후
    * `FileDeleted` — 파일 삭제 후
    * `CreatingStore` — 스토어 생성 전
    * `StoreCreated` — 스토어 생성 후
    * `AddingFileToStore` — 스토어에 파일 추가 전
    * `FileAddedToStore` — 스토어에 파일 추가 후
    * `RemovingFileFromStore` — 스토어에서 파일 삭제 전
    * `FileRemovedFromStore` — 스토어에서 파일 삭제 후
  </Accordion>
</AccordionGroup>


## Related topics

- [Laravel 13 신기능 정리](/ko/blog/laravel-13-new-features.md)
- [2026년 3월 Laravel 업데이트](/ko/blog/changelog/202603.md)
- [Laravel AI SDK 연동](/ko/packages/laravel-copilot-sdk/ai-sdk.md)
- [Laravel AI SDK 연동 - VOICEVOX for Laravel](/ko/packages/laravel-voicevox/ai-sdk.md)
- [Laravel AI SDK용 Amazon Bedrock 드라이버](/ko/packages/laravel-amazon-bedrock.md)
