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

Middle: питання на співбесіді з теми «AI SDK, MCP і Boost»

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

3 питання

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

Структурований вивід: агент описує 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: тестування агентів