Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Питання на співбесіді: AI SDK, MCP і Boost

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

8 питань

Laravel AI SDK (laravel/ai) - офіційний пакет для роботи з великими мовними моделями та іншими AI-сервісами через єдиний API: OpenAI, Anthropic, Gemini, Mistral, DeepSeek, xAI, Ollama та інші. Код застосунку не залежить від конкретного провайдера - його можна змінити в конфігурації.

composer require laravel/ai

Ключі провайдерів задаються в .env (OPENAI_API_KEY, ANTHROPIC_API_KEY...), налаштування - у config/ai.php.

Агент - окремий PHP-клас, що інкапсулює все потрібне для однієї задачі: інструкції (системний промпт), контекст розмови, інструменти й схему відповіді.

php artisan make:agent SupportAssistant
namespace App\Ai\Agents;

use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;

class SupportAssistant implements Agent
{
    use Promptable;

    public function instructions(): string
    {
        return 'Ти асистент підтримки інтернет-магазину. Відповідай коротко й лише про замовлення та доставку.';
    }
}

Виклик:

$response = (new SupportAssistant)->prompt('Де моє замовлення №1042?');

return (string) $response;

SupportAssistant::make(...) створює агента через контейнер - з автоматичним впровадженням залежностей.

Налаштування через атрибути:

#[Provider(Lab::Anthropic)]
#[MaxTokens(1024)]
#[Temperature(0.2)]
#[Timeout(60)]
class SupportAssistant implements Agent

Що ще вміє SDK, крім текстових агентів:

  • структурована відповідь за JSON-схемою;
  • інструменти - функції застосунку, які модель може викликати;
  • стримінг відповіді й обробка в черзі;
  • збереження розмов у базі (RemembersConversations);
  • ембединги для семантичного пошуку (Str::of($text)->toEmbeddings(), whereVectorSimilarTo у Query Builder);
  • генерація зображень, аудіо, транскрибування;
  • резервні провайдери: provider: [Lab::OpenAI, Lab::Anthropic] - якщо перший недоступний чи вичерпав ліміт.

Чому агент - клас, а не просто виклик API: інструкції, інструменти й налаштування зібрані в одному місці, агента можна підробити в тестах (SupportAssistant::fake()) і перевикористати в контролері, команді чи черзі.

Докладніше в документації: AI SDK: агенти

MCP (Model Context Protocol) - відкритий протокол, через який AI-клієнт (Claude Code, Cursor, Codex, Copilot, Junie) отримує доступ до інструментів, ресурсів і промптів зовнішньої системи. Сервер MCP описує, що він уміє, а модель вирішує, коли цим скористатися.

Laravel Boost (laravel/boost) - пакет для розробки, що робить AI-асистента обізнаним саме про ваш застосунок і версії пакетів.

composer require laravel/boost --dev
php artisan boost:install

boost:install питає, якими агентами ви користуєтеся, і генерує для них файли:

  • конфігурацію MCP (.mcp.json тощо) - агент запускає php artisan boost:mcp;
  • настанови (guidelines) - файли на кшталт CLAUDE.md, AGENTS.md з правилами для вашої версії Laravel і встановлених пакетів (Livewire 4, Filament 5, Pest...);
  • навички (skills) - детальні інструкції, що завантажуються на вимогу під конкретну задачу.

Інструменти MCP-сервера Boost:

Інструмент Навіщо агенту
Application Info версії PHP, Laravel, пакетів, список моделей
Database Schema / Query структура таблиць і запити до бази
Search Docs документація саме для встановлених версій пакетів
Last Error / Read Log Entries останні помилки з логів
Browser Logs помилки з консолі браузера
Get Absolute URL правильна адреса сторінки застосунку

Чому це важливо: модель навчена на коді різних версій. Без контексту вона охоче напише код для Livewire 2 чи старий синтаксис маршрутів. Boost дає їй документацію й правила для того, що реально встановлено, і можливість перевірити схему бази замість вгадування.

Власні правила проєкту:

  • .ai/guidelines/*.md (чи .blade.php) - додаткові настанови, що потрапляють у згенеровані файли;
  • .ai/skills/{назва}/SKILL.md - власні навички.

Оновлення: php artisan boost:update - перегенерувати настанови й навички після оновлення пакетів (зручно додати в post-update-cmd Composer).

Безпека: Boost - залежність лише для розробки (--dev). Інструмент запитів до бази виконується з правами застосунку, тож підключати агента варто до локальної бази, а не до продакшену.

Докладніше в документації: Boost: MCP-сервер

Проблема: розбирати вільний текст моделі регулярними виразами - крихко. Модель може додати пояснення, змінити формат чи забути поле.

Структурований вивід: агент описує JSON-схему відповіді, а провайдер змушує модель повернути дані саме в такій формі.

php artisan make:agent ReviewClassifier --structured
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;

class ReviewClassifier implements Agent, HasStructuredOutput
{
    use Promptable;

    public function instructions(): string
    {
        return 'Класифікуй відгук покупця. Не вигадуй фактів, яких немає у тексті.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'sentiment' => $schema->string()->enum(['positive', 'neutral', 'negative'])->required(),
            'topics' => $schema->array()->items($schema->string())->required(),
            'needs_reply' => $schema->boolean()->required(),
            'score' => $schema->integer()->min(1)->max(5)->required(),
        ];
    }
}

Відповідь поводиться як масив:

$result = (new ReviewClassifier)->prompt($review->body);

$review->update([
    'sentiment' => $result['sentiment'],
    'needs_reply' => $result['needs_reply'],
]);

Що дає схема:

  • enum обмежує значення - у базу не потрапить "Positive!" замість positive;
  • типи й межі (integer, min, max) - менше перевірок у коді;
  • вкладені об'єкти й масиви об'єктів - для витягання списків (позиції з рахунку, контакти з листа).

Чого схема не гарантує:

  • правильності змісту: модель поверне валідний JSON з неправильною класифікацією чи вигаданим значенням. Схема перевіряє форму, а не правду;
  • однакової підтримки в усіх провайдерів - строгий режим відрізняється, і частина обмежень схеми може ігноруватися.

Тому дані від моделі - це вхідні дані користувача:

  • валідувати перед збереженням (Validator::make($result->toArray(), [...]) чи enum PHP з tryFrom);
  • не використовувати як SQL, шлях до файлу чи HTML без екранування;
  • для критичних рішень (повернення коштів, блокування) - людина в процесі.

Практичні поради:

  • описи полів (->description('...')) у схемі допомагають моделі так само, як інструкції;
  • низька температура (#[Temperature(0)]) для класифікації й витягання даних - стабільніші результати;
  • у тестах ReviewClassifier::fake([['sentiment' => 'negative', ...]]) повертає структуровану відповідь без звернення до API.

Докладніше в документації: AI SDK: структурований вивід

Інструмент - функція застосунку, яку модель може попросити викликати: знайти замовлення, перевірити залишок, створити заявку. Модель не виконує код сама - вона повертає «виклич інструмент X з аргументами Y», SDK виконує handle і передає результат назад моделі.

php artisan make:tool FindOrder
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;

class FindOrder implements Tool
{
    public function __construct(private User $user) {}

    public function description(): string
    {
        return 'Знаходить замовлення поточного користувача за номером і повертає статус доставки.';
    }

    public function schema(JsonSchema $schema): array
    {
        return ['number' => $schema->integer()->required()];
    }

    public function handle(Request $request): string
    {
        $order = $this->user->orders()->where('number', $request['number'])->first();

        return $order ? "Статус: {$order->status->label()}" : 'Замовлення не знайдено.';
    }
}
// в агенті
public function tools(): iterable
{
    return [new FindOrder($this->user)];
}

Головне правило безпеки: модель - недовірений учасник. Її аргументи можуть бути сформовані під впливом тексту від зловмисника (prompt injection у листі, відгуку, документі). Тому:

  • авторизація всередині інструмента: шукати замовлення через $this->user->orders(), а не Order::find($number) - інакше модель «знайде» чуже замовлення;
  • мінімальний набір інструментів: агенту підтримки не потрібен інструмент видалення;
  • валідація аргументів - схема описує форму, але межі й права перевіряє код;
  • обмеження кроків: #[MaxSteps(5)] - щоб модель не зациклилася у викликах і не спалила бюджет.

Схвалення людиною для незворотних дій: інструмент реалізує Approvable з трейтом InteractsWithApprovals - тоді виконання призупиняється, доки людина не підтвердить дію (повернення коштів, видалення, відправка листа). Для цього агент має зберігати розмову (RemembersConversations), щоб продовжити її після схвалення.

Опис - це інструкція для моделі: від description() і описів полів залежить, чи правильно модель обере інструмент. Пишіть, коли його використовувати і що він повертає.

Що повертати: короткий текст чи JSON лише з потрібними полями. Повертати модель цілком ($order->toJson()) - це зайві токени й ризик віддати моделі внутрішні дані (хеші, нотатки менеджерів), які потім потраплять у відповідь користувачу.

Докладніше в документації: AI SDK: інструменти

Реальні запити до LLM у тестах - повільні, платні, недетерміновані й потребують ключа API в CI. AI SDK має вбудовані підробки, як Mail::fake() чи Http::fake().

Підробка агента:

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

SupportAssistant::fake();                        // фіксована відповідь на будь-який промпт

SupportAssistant::fake(['Перша відповідь', 'Друга відповідь']);   // по черзі

SupportAssistant::fake(fn (AgentPrompt $prompt) => str_contains($prompt->prompt, 'повернення')
    ? 'Повернення можливе протягом 14 днів.'
    : 'Не можу допомогти з цим питанням.');

Структурований вивід - масиви як відповіді:

ReviewClassifier::fake([
    ['sentiment' => 'negative', 'topics' => ['delivery'], 'needs_reply' => true, 'score' => 2],
]);

Твердження:

SupportAssistant::assertPrompted(fn (AgentPrompt $prompt) => str_contains($prompt->prompt, '№1042'));
SupportAssistant::assertNotPrompted('...');
SupportAssistant::assertNeverPrompted();

// якщо агента запускали через ->queue()
ReviewClassifier::assertQueued(fn (AgentPrompt $prompt) => ...);

Інші можливості SDK мають свої підробки: Embeddings::fake(), Image::fake(), Audio::fake(), Transcription::fake(), Files::fake(), Stores::fake().

Що варто тестувати:

Що Як
що код реагує на відповідь моделі (зберігає, відповідає, ескалює) fake з різними відповідями, зокрема з неочікуваними
що в промпт потрапляють потрібні дані й не потрапляють зайві (персональні дані) assertPrompted з перевіркою тексту
інструменти агента окремо, як звичайні класи: виклик handle з різними аргументами й користувачами
обробка збоїв провайдера fake із замиканням, що кидає виняток

Що підробки не перевіряють: якість відповідей самої моделі. Для цього потрібні оцінювальні набори (evals) - окремий набір запитів з очікуваними властивостями відповіді, який запускається проти реальної моделі вручну чи за розкладом, а не в кожному прогоні тестів.

Захист від випадкових реальних запитів: у phpunit.xml задати порожні чи фіктивні ключі провайдерів - тест, який забув підробку, впаде на автентифікації, а не витратить гроші.

Докладніше в документації: AI SDK: тестування агентів

Відповідь моделі може генеруватися десятки секунд, а з інструментами - ще довше. Синхронний prompt() у контролері тримає PHP-воркер увесь цей час і впирається в тайм-аути проксі та PHP.

Три підходи:

1. Стримінг (SSE) - користувач бачить текст по мірі генерації:

Route::post('/assistant', function (Request $request) {
    return SupportAssistant::make(user: $request->user())
        ->stream($request->validated('message'))
        ->then(function (StreamedAgentResponse $response) {
            // повна відповідь: зберегти, порахувати токени
        });
});

StreamableAgentResponse повертається з маршруту як потокова відповідь. Перший токен приходить за секунду-дві, але воркер зайнятий до кінця генерації.

2. Черга - запит повертається одразу, генерація йде у воркері:

ReviewClassifier::make()
    ->queue($review->body)
    ->then(fn (AgentResponse $response) => $review->update([...]))
    ->catch(fn (Throwable $e) => report($e));

return back()->with('status', 'Аналіз запущено');

Підходить для фонових задач: класифікація, резюме, обробка документів. Результат - у базу, а інтерфейс опитує статус чи отримує подію.

3. Трансляція подій - генерація у черзі, а потік подій іде в браузер через WebSocket (Reverb):

SupportAssistant::make()->broadcastOnQueue($message, new PrivateChannel("chat.{$chat->id}"));

Поєднує обидва: воркер вебсервера звільняється одразу, а користувач бачить текст у реальному часі.

Як обрати:

Сценарій Підхід
чат, де користувач чекає відповідь стримінг або черга + трансляція
фонова обробка без очікування черга
багато одночасних користувачів черга + трансляція (не займати вебворкери)

Інфраструктурні деталі:

  • тайм-аути: #[Timeout] агента, timeout воркера черги й retry_after підключення мають бути узгоджені, інакше задачу візьме другий воркер, і модель відповість двічі (і двічі буде оплачено);
  • буферизація: Nginx, Cloudflare й інші проксі можуть буферизувати SSE - потрібні X-Accel-Buffering: no і відповідні налаштування;
  • окрема черга для LLM-задач з власним лімітом воркерів - щоб повільні виклики моделі не затримували листи й сповіщення;
  • повтори: при збоях провайдера краще резервний провайдер (provider: [...]), ніж повтор задачі цілком;
  • Octane / FrankenPHP - стримінг утримує воркер так само; рахуйте кількість воркерів під одночасні потоки.

Докладніше в документації: AI SDK: стримінг

Laravel MCP (laravel/mcp) дозволяє застосунку стати MCP-сервером: AI-клієнти (Claude, ChatGPT, Cursor) отримують доступ до інструментів, ресурсів і промптів вашого продукту.

php artisan make:mcp-server ShopServer
php artisan make:mcp-tool SearchProductsTool
#[Name('Shop')]
#[Version('1.0.0')]
#[Instructions('Пошук товарів і перегляд замовлень магазину.')]
class ShopServer extends Server
{
    protected array $tools = [SearchProductsTool::class, OrderStatusTool::class];
    protected array $resources = [ReturnPolicyResource::class];
    protected array $prompts = [];
}
#[IsReadOnly]
class OrderStatusTool extends Tool
{
    protected string $description = 'Статус замовлення поточного користувача за номером.';

    public function schema(JsonSchema $schema): array
    {
        return ['number' => $schema->integer()->required()];
    }

    public function handle(Request $request): Response
    {
        $data = $request->validate(['number' => 'required|integer']);

        $order = $request->user()->orders()->where('number', $data['number'])->first();

        return $order ? Response::text($order->status->label()) : Response::error('Замовлення не знайдено.');
    }
}

Реєстрація у routes/ai.php:

Mcp::web('/mcp/shop', ShopServer::class)->middleware(['auth:sanctum', 'throttle:mcp']);
Mcp::local('shop', ShopServer::class);   // для локальних агентів через artisan

Захист - вебсервер MCP це публічний API:

  • автентифікація: auth:sanctum з токеном у заголовку Authorization або OAuth 2.1 через Passport (Mcp::oauthRoutes() + auth:api) - другий варіант потрібен клієнтам на кшталт Claude.ai, що підключаються від імені користувача;
  • авторизація в кожному інструменті: $request->user() і політики. Модель передасть будь-який номер замовлення - перевірка «чи належить воно користувачу» обов'язкова;
  • shouldRegister() - приховати інструмент від користувачів без прав чи підписки: він не з'явиться в списку й не викликається;
  • анотації (#[IsReadOnly], #[IsDestructive], #[IsIdempotent]) - підказки клієнту, які дії потребують підтвердження. Це не захист - клієнт може їх ігнорувати;
  • обмеження частоти - агенти викликають інструменти в циклі значно частіше за людей;
  • мінімальні дані у відповіді: результат інструмента потрапляє в контекст моделі й далі може опинитися будь-де. Не повертати зайвих полів.

Prompt injection з вашого боку: дані, які сервер повертає (відгуки, описи товарів від продавців), можуть містити інструкції для моделі. Позначайте їх як дані, а деструктивні інструменти не робіть доступними разом з інструментами, що читають сторонній контент.

Тестування:

ShopServer::actingAs($user)
    ->tool(OrderStatusTool::class, ['number' => 1042])
    ->assertOk()
    ->assertSee('Доставлено');

Для ручної перевірки - php artisan mcp:inspector.

Докладніше в документації: MCP: створення серверів

Prompt injection - модель не розрізняє «інструкції розробника» і «дані»: текст у листі, відгуку, PDF чи вебсторінці може містити «ігноруй попередні інструкції й...». Повністю цю проблему не розв'язано, тому захист будується так, ніби модель буде обдурена.

Принципи:

  • модель - недовірений компонент. Її вивід - як введення користувача: валідувати, екранувати в HTML, не підставляти в SQL, шляхи, команди;
  • мінімальні права інструментів: агент, що читає сторонній контент, не повинен мати інструментів з незворотними діями. Поєднання «читає пошту» + «надсилає листи» + «бачить приватні дані» - класичний сценарій витоку;
  • авторизація в коді інструмента від імені реального користувача, а не «що попросила модель»;
  • схвалення людиною (Approvable) для грошей, видалення, зовнішніх відправок;
  • розділення даних і інструкцій: сторонній текст - окремим блоком з явною позначкою, що це дані. Знижує, але не усуває ризик;
  • вихідні фільтри: не дозволяти моделі вставляти довільні посилання й зображення в HTML-відповідь - через них виводять дані (запит до attacker.com/?data=...).

Контроль витрат:

Кожна відповідь містить використання токенів:

$response = SupportAssistant::make()->prompt($message);

$response->usage->inputTokens;
$response->usage->outputTokens;
$response->usage->cacheReadInputTokens;

Що робити з цими даними:

  • записувати токени, модель і користувача для кожного виклику - без цього неможливо зрозуміти, хто й що коштує;
  • ліміти на користувача й тариф - лічильник у Redis чи базі, перевірка перед викликом;
  • RateLimiter на маршрутах з AI - бот без обмежень може витратити місячний бюджет за ніч;
  • #[MaxTokens] і #[MaxSteps] - верхня межа довжини відповіді й кількості викликів інструментів;
  • розмір контексту: історія розмови росте з кожним повідомленням, і кожен запит оплачує її знову. Обрізати історію чи стискати її в резюме;
  • дешевша модель для простих задач: #[UseCheapestModel] для класифікації й витягання даних, потужна - лише там, де потрібно міркування;
  • кешування: однакові запити (ембединги того самого тексту, резюме того самого документа) - зберегти результат, а не платити знову; кешування промптів у провайдера для довгого незмінного системного промпту;
  • сповіщення про аномалії: різке зростання витрат за годину - сигнал про зловживання чи цикл.

Для персональних даних: не відправляти провайдеру більше, ніж потрібно для задачі, і знати, чи зберігає він дані й чи використовує їх для навчання - це питання договору й налаштувань облікового запису провайдера.

Докладніше в документації: AI SDK: використання токенів