메인 콘텐츠로 건너뛰기

시작하기

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

프로바이더 지원 목록

기능지원 프로바이더
텍스트 생성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

설치

1

패키지 설치

Composer로 Laravel AI SDK를 설치합니다.
composer require laravel/ai
2

설정 파일과 마이그레이션 공개

vendor:publish Artisan 명령어로 설정 파일과 마이그레이션을 공개합니다.
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
3

마이그레이션 실행

데이터베이스 마이그레이션을 실행합니다. agent_conversationsagent_conversation_messages 테이블이 생성되어 대화 이력 저장에 사용됩니다.
php artisan migrate

설정

환경 변수

사용할 AI 프로바이더의 API 키를 .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=
OPENROUTER_API_KEY=
JINA_API_KEY=
VOYAGEAI_API_KEY=
XAI_API_KEY=
텍스트, 이미지, 음성, 문자 변환, 임베딩에 사용되는 기본 모델은 config/ai.php에서도 설정할 수 있습니다.

커스텀 베이스 URL

프록시 서비스를 경유하는 경우, 프로바이더별로 커스텀 URL을 설정할 수 있습니다.
'providers' => [
    'openai' => [
        'driver' => 'openai',
        'key' => env('OPENAI_API_KEY'),
        'url' => env('OPENAI_URL'),
    ],
    'anthropic' => [
        'driver' => 'anthropic',
        'key' => env('ANTHROPIC_API_KEY'),
        'url' => env('ANTHROPIC_BASE_URL'),
    ],
],
커스텀 베이스 URL은 OpenAI, Anthropic, Gemini, Groq, Cohere, DeepSeek, xAI, OpenRouter에서 사용할 수 있습니다.

OpenAI-Compatible 프로바이더

LM Studio, vLLM, Together, Fireworks, 로컬 게이트웨이 등 OpenAI 호환 API를 사용하는 경우 openai-compatible 드라이버로 프로바이더를 설정할 수 있습니다. url은 필수이며, key를 지정하면 Bearer 토큰으로 전송됩니다.
'providers' => [
    'local' => [
        'driver' => 'openai-compatible',
        'url' => env('LOCAL_AI_URL'),
        'key' => env('LOCAL_AI_API_KEY'),
    ],
],
설정 후에는 다른 프로바이더와 동일하게 프로바이더 이름으로 지정할 수 있습니다.
agent()->prompt('What is Laravel?', provider: 'local', model: 'local-model');
기본 텍스트 모델을 설정해 두면 매번 모델을 지정하지 않고 사용할 수 있습니다.
'local' => [
    'driver' => 'openai-compatible',
    'url' => env('LOCAL_AI_URL'),
    'key' => env('LOCAL_AI_API_KEY'),
    'models' => [
        'text' => [
            'default' => env('LOCAL_AI_MODEL'),
        ],
    ],
],
OpenAI-Compatible 프로바이더는 텍스트 생성, 스트리밍, 도구, 구조화된 출력, 이미지 첨부를 지원합니다. 엔드포인트에서 추가 요청 바디 필드가 필요한 경우, 프로바이더 옵션을 사용하십시오.

Lab enum

코드 내에서 프로바이더를 참조할 때는 Lab enum을 사용합니다.
use Laravel\Ai\Enums\Lab;

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

에이전트

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

# 구조화된 출력이 있는 에이전트 생성
php artisan make:agent SalesCoach --structured
생성된 에이전트는 app/Ai/Agents/ 디렉터리에 배치됩니다. 다음은 주요 인터페이스를 모두 구현한 에이전트의 예시입니다.
<?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(),
        ];
    }
}

프롬프트

prompt() 메서드로 에이전트에 메시지를 보냅니다.
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');
return (string) $response;
make() 정적 메서드를 사용하면 컨테이너에서 의존성을 해결하여 인스턴스를 생성할 수 있습니다.
$agent = SalesCoach::make(user: $user);
프로바이더, 모델, 타임아웃은 prompt()의 인수로 재정의할 수 있습니다.
$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: Lab::Anthropic,
    model: 'claude-haiku-4-5-20251001',
    timeout: 120,
);

대화 컨텍스트

Conversational 인터페이스를 구현하고 messages() 메서드를 정의하면 과거 대화 이력을 AI에 전달할 수 있습니다. RemembersConversations 트레이트를 사용하면 대화 이력을 데이터베이스에 자동으로 저장 및 조회할 수 있습니다.
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()로 이어지는 대화를 할 수 있습니다.
$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의 응답을 구조화된 데이터로 받을 수 있습니다.
public function schema(JsonSchema $schema): array
{
    return ['score' => $schema->integer()->required()];
}

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

중첩된 객체

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

객체의 배열

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 메서드를 사용합니다.
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 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'),
    ]
);
이미지 첨부도 동일하게 할 수 있습니다.
$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() 메서드를 사용하면 응답을 청크 단위로 반환할 수 있습니다. 긴 응답을 실시간으로 프런트엔드에 보내는 데 적합합니다.
Route::get('/coach', function () {
    return (new SalesCoach)->stream('Analyze this sales transcript...');
});
then() 콜백으로 스트리밍 완료 후 처리를 기술할 수 있습니다.
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) {
    // ...
}

Vercel AI SDK 프로토콜

프런트엔드에서 Vercel AI SDK를 사용하는 경우 usingVercelDataProtocol()을 호출합니다.
Route::get('/coach', function () {
    return (new SalesCoach)->stream('Analyze...')->usingVercelDataProtocol();
});

브로드캐스트

스트림의 이벤트를 Laravel Echo 등의 브로드캐스트 채널로 전송할 수 있습니다.
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'),
);

거대한 이벤트 스킵

브로드캐스트 플랫폼에 따라 WebSocket 메시지를 약 10KB로 제한하는 경우가 있습니다. 큰 도구 결과 등 데이터 양이 많은 스트림 이벤트가 이 상한을 초과해 브로드캐스트에 실패할 수 있습니다. WithoutBroadcasting Attribute를 사용해 특정 이벤트 유형을 브로드캐스트에서 제외할 수 있습니다.
<?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() 메서드로 프롬프트를 큐에 적재해 비동기 처리할 수 있습니다.
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 명령어로 도구 클래스를 생성할 수 있습니다.
php artisan make:tool RandomNumberGenerator
<?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() 메서드로 도구를 등록합니다.
public function tools(): iterable
{
    return [new RandomNumberGenerator];
}

유사도 검색 도구

벡터 임베딩을 사용한 유사도 검색 도구를 간단히 추가할 수 있습니다.
use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;

public function tools(): iterable
{
    return [
        SimilaritySearch::usingModel(Document::class, 'embedding'),
    ];
}
옵션을 지정할 수도 있습니다.
SimilaritySearch::usingModel(
    model: Document::class,
    column: 'embedding',
    minSimilarity: 0.7,
    limit: 10,
    query: fn ($query) => $query->where('published', true),
),
클로저로 독자적인 검색 로직을 정의할 수도 있습니다.
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.'),

파일 스토리지 도구

FileStorage 도구 팩토리를 사용하면 에이전트에 Laravel 파일시스템 디스크에 대한 접근을 부여할 수 있습니다. 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 도구

애플리케이션에서 Laravel MCP를 사용하고 있다면, Model Context Protocol 서버가 공개하는 도구를 에이전트에 제공할 수 있습니다. Laravel MCP 클라이언트를 사용해 원격 또는 로컬 MCP 서버에 접속하고, 그 도구를 에이전트에 직접 전달할 수 있습니다.
MCP 도구를 사용하려면 애플리케이션에 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 도구를 자동으로 래핑해 에이전트가 다른 도구와 동일하게 호출할 수 있게 합니다. 이름 있는 MCP 클라이언트를 사용할 수도 있습니다.
use Laravel\Mcp\Facades\Mcp;

public function tools(): iterable
{
    return [
        ...Mcp::client('github')->tools(),
    ];
}
또는 로컬 MCP 서버에 접속하는 것도 가능합니다.
use Laravel\Mcp\Client;

public function tools(): iterable
{
    return [
        ...Client::local('php', ['artisan', 'mcp:start'])->tools(),
    ];
}
MCP 클라이언트의 생성과 인증(Bearer 토큰이나 OAuth 등)에 대해서는 MCP 클라이언트 문서를 참조하십시오.

프로바이더 도구

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

Web 검색

웹 검색을 에이전트에 추가합니다. Anthropic, OpenAI, Gemini, OpenRouter에 대응하고 있습니다.
use Laravel\Ai\Providers\Tools\WebSearch;

public function tools(): iterable
{
    return [new WebSearch];
}
옵션으로 검색 건수나 도메인 필터, 위치 정보를 지정할 수 있습니다.
(new WebSearch)->max(5)->allow(['laravel.com', 'php.net']),
(new WebSearch)->location(city: 'New York', region: 'NY', country: 'US');

Web 페치

지정한 URL의 콘텐츠를 가져오는 도구입니다. Anthropic, Gemini에 대응하고 있습니다.
use Laravel\Ai\Providers\Tools\WebFetch;

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

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

파일 검색

벡터 스토어에서 문서를 검색하는 도구입니다. OpenAI, Gemini에 대응하고 있습니다.
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를 사용한 복잡한 필터도 지정할 수 있습니다.
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

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

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은 클래스 이름을 도구 이름으로 사용하고 범용적인 설명문을 자동으로 생성합니다. 각 서브 에이전트의 호출은 독립적으로 이루어지며, 부모 에이전트의 대화 이력은 이어받지 않습니다.

미들웨어

에이전트에 미들웨어를 추가해 프롬프트나 응답을 가로챌 수 있습니다.
php artisan make:agent-middleware LogPrompts
에이전트에 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;

    public function middleware(): array
    {
        return [new LogPrompts];
    }
}
미들웨어 클래스의 구현 예시입니다.
<?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()을 사용하면 응답 이후의 처리도 추가할 수 있습니다.
public function handle(AgentPrompt $prompt, Closure $next)
{
    return $next($prompt)->then(function (AgentResponse $response) {
        Log::info('Agent responded', ['text' => $response->text]);
    });
}

익명 에이전트

클래스를 정의하지 않고 agent() 헬퍼로 익명 에이전트를 사용할 수 있습니다.
use function Laravel\Ai\{agent};

$response = agent(
    instructions: 'You are an expert at software development.',
    messages: [],
    tools: [],
)->prompt('Tell me about Laravel');
구조화된 출력이 있는 익명 에이전트도 만들 수 있습니다.
use Illuminate\Contracts\JsonSchema\JsonSchema;

$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

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도 준비되어 있습니다.
// 가장 저렴한 모델 사용
#[UseCheapestModel]
class SimpleSummarizer implements Agent
{
    use Promptable;
}

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

프로바이더 옵션

HasProviderOptions 인터페이스를 구현하면 프로바이더 고유의 옵션을 전달할 수 있습니다.
<?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 프로바이더가 지원합니다.
use Laravel\Ai\Image;

$image = Image::of('A donut sitting on the kitchen counter')->generate();
$rawContent = (string) $image;
품질이나 종횡비, 타임아웃을 지정할 수 있습니다.
$image = Image::of('A donut sitting on the kitchen counter')
    ->quality('high')
    ->landscape()
    ->timeout(120)
    ->generate();
참조 이미지를 첨부해 가공할 수도 있습니다.
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();

이미지 저장

$path = $image->store();
$path = $image->storeAs('image.jpg');
$path = $image->storePublicly();
$path = $image->storePubliclyAs('image.jpg');

큐로 이미지 생성

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 프로바이더가 지원합니다.
use Laravel\Ai\Audio;

$audio = Audio::of('I love coding with Laravel.')->generate();
$rawContent = (string) $audio;
음성의 성별이나 구체적인 보이스 ID, 말투의 지시도 지정할 수 있습니다.
$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();

음성 저장

$path = $audio->store();
$path = $audio->storeAs('audio.mp3');
$path = $audio->storePublicly();
$path = $audio->storePubliclyAs('audio.mp3');

큐로 음성 생성

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 프로바이더가 지원합니다.
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\Responses\TranscriptionResponse;

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

임베딩(Embeddings)

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

// Stringable 사용 방법
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings();
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');

벡터 검색(pgvector)

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

마이그레이션 생성

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

모델 설정

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

유사도 검색 쿼리

// 임베딩 벡터로 검색
$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();
저수준 메서드도 사용할 수 있습니다.
$documents = Document::query()
    ->select('*')
    ->selectVectorDistance('embedding', $queryEmbedding, as: 'distance')
    ->whereVectorDistanceLessThan('embedding', $queryEmbedding, maxDistance: 0.3)
    ->orderByVectorDistance('embedding', $queryEmbedding)
    ->limit(10)
    ->get();

임베딩 캐시

같은 텍스트의 임베딩 생성을 반복하지 않도록 캐시할 수 있습니다. config/ai.php에서 기본 캐시 설정을 합니다.
'caching' => [
    'embeddings' => [
        'cache' => true,
        'store' => env('CACHE_STORE', 'database'),
    ],
],
요청별로 캐시를 제어할 수도 있습니다.
$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 프로바이더가 지원합니다.
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()으로 반환 건수를 좁힐 수 있습니다.
$response = Reranking::of($documents)->limit(5)->rerank('search query');

컬렉션의 리랭킹

Eloquent 컬렉션을 직접 리랭크할 수 있습니다.
// 단일 필드
$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 프로바이더에 파일을 업로드해 나중에 참조할 수 있습니다.
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;
문자열이나 폼 업로드에서도 다룰 수 있습니다.
$stored = Document::fromString('Hello, World!', 'text/plain')->put();
$stored = Document::fromUpload($request->file('document'))->put();

저장된 파일 참조

업로드된 파일 ID로 에이전트에 첨부할 수 있습니다.
use Laravel\Ai\Files;

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

파일 조회·삭제

// 조회
$file = Document::fromId('file-id')->get();
$file->id;
$file->mimeType();

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

프로바이더 지정

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

벡터 스토어

벡터 스토어를 사용하면 문서를 프로바이더 측에서 관리할 수 있습니다.
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();

스토어에 파일 추가

$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;
메타데이터를 부여할 수도 있습니다.
$store->add(
    Document::fromPath('/path/to/document.pdf'),
    metadata: [
        'author' => 'Taylor Otwell',
        'department' => 'Engineering',
        'year' => 2026,
    ]
);

스토어에서 파일 삭제

$store->remove('file_id');

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

페일오버

여러 프로바이더를 배열로 지정하면 첫 번째 프로바이더가 실패한 경우 다음 프로바이더로 자동 폴백됩니다.
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를 호출하지 않고도 테스트할 수 있습니다.

에이전트 테스트

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();
큐잉의 어서션도 준비되어 있습니다.
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();
구조화된 출력을 반환하는 에이전트를 페이크하는 경우, 배열로 응답을 지정할 수 있습니다. 에이전트는 지정한 데이터를 포함한 구조화된 응답을 반환합니다.
SalesCoach::fake([
    ['score' => 87],
]);
구조화된 출력 에이전트에 대해 fake()가 페이크 데이터를 명시적으로 전달하지 않고 호출되면, Laravel은 에이전트가 정의한 스키마에 부합하는 페이크 데이터를 자동 생성합니다.
익명 에이전트의 테스트에는 AnonymousAgent::fake()를 사용합니다.
use Laravel\Ai\AnonymousAgent;

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

이미지 생성 테스트

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

음성 합성 테스트

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

문자 변환 테스트

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

임베딩 테스트

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

리랭킹 테스트

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

파일 테스트

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

벡터 스토어 테스트

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();
스토어에 대한 파일 조작의 어서션도 할 수 있습니다.
$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는 다음의 이벤트를 디스패치합니다. 이 이벤트들을 리스닝함으로써 로그 기록이나 모니터링에 활용할 수 있습니다.
  • PromptingAgent — 프롬프트 전송 전
  • AgentPrompted — 프롬프트 전송 후
  • StreamingAgent — 스트리밍 시작 시
  • AgentStreamed — 스트리밍 완료 후
  • InvokingTool — 도구 호출 전
  • ToolInvoked — 도구 호출 후
  • GeneratingImage — 이미지 생성 전
  • ImageGenerated — 이미지 생성 후
  • GeneratingAudio — 음성 생성 전
  • AudioGenerated — 음성 생성 후
  • GeneratingTranscription — 문자 변환 전
  • TranscriptionGenerated — 문자 변환 후
  • GeneratingEmbeddings — 임베딩 생성 전
  • EmbeddingsGenerated — 임베딩 생성 후
  • Reranking — 리랭킹 전
  • Reranked — 리랭킹 후
  • StoringFile — 파일 저장 전
  • FileStored — 파일 저장 후
  • FileDeleted — 파일 삭제 후
  • CreatingStore — 스토어 생성 전
  • StoreCreated — 스토어 생성 후
  • AddingFileToStore — 스토어에 파일 추가 전
  • FileAddedToStore — 스토어에 파일 추가 후
  • RemovingFileFromStore — 스토어에서 파일 삭제 전
  • FileRemovedFromStore — 스토어에서 파일 삭제 후
마지막 수정일 2026년 7월 13일