Senior: питання на співбесіді з теми «AI SDK, MCP і Boost»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
3 питання
Відповідь моделі може генеруватися десятки секунд, а з інструментами - ще довше. Синхронний 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 - стримінг утримує воркер так само; рахуйте кількість воркерів під одночасні потоки.
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.
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]для класифікації й витягання даних, потужна - лише там, де потрібно міркування; - кешування: однакові запити (ембединги того самого тексту, резюме того самого документа) - зберегти результат, а не платити знову; кешування промптів у провайдера для довгого незмінного системного промпту;
- сповіщення про аномалії: різке зростання витрат за годину - сигнал про зловживання чи цикл.
Для персональних даних: не відправляти провайдеру більше, ніж потрібно для задачі, і знати, чи зберігає він дані й чи використовує їх для навчання - це питання договору й налаштувань облікового запису провайдера.