Laravel AI SDK на практиці: агент, що стоїть за There There
Фрік Ван дер Гертен (Freek Van der Herten) у своєму дописі поділився досвідом використання нового Laravel AI SDK у There There - хелпдеску, який команда Spatie створює для себе. Після двох десятиліть власної підтримки клієнтів вони вирішили більше не миритися з компромісами в інструментах і зробити той, яким хочуть користуватися самі.
Роль AI у There There
Найцікавіша частина проєкту - AI. Його використовують не для заміни агентів підтримки, а для того, щоб вони працювали швидше. Людина читає тікет, думає й натискає кнопку, а модель підтягує релевантну історію, готує чернетку відповіді, ставить теги та закриває дублікати. Зараз There There перебуває в закритому бета-тестуванні, а заявку на ранній доступ можна подати на there-there.app.
Весь AI-шар збудований на Laravel AI SDK.
Клас агента
Усе починається з класу агента - у Spatie він називається ThereThereAgent. Він визначає, яку модель використовувати, як виглядає системний промпт, які інструменти (tools) може викликати модель і яку історію розмови відтворювати. Ось скорочена версія:
use Laravel\Ai\Attributes\MaxSteps;
use Laravel\Ai\Attributes\Temperature;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;
#[Temperature(0.3)]
#[MaxSteps(10)]
class ThereThereAgent implements Agent, Conversational, HasTools
{
use Promptable;
public function __construct(
protected Workspace $workspace,
protected User $user,
protected ?Ticket $contextTicket = null,
protected array $conversationHistory = [],
) {}
public function model(): string
{
return config('ai-pricing.default.name');
}
public function instructions(): string
{
return view('prompts.ai.there-there-agent-system', [
'workspace' => $this->workspace,
'user' => $this->user,
'contextTicket' => $this->contextTicket,
])->render();
}
public function tools(): iterable
{
return [
new ChangeTicketStatusTool($this->workspace),
new AssignTicketTool($this->workspace),
new AddNoteToTicketTool($this->workspace),
new ManageTicketTagsTool($this->workspace),
];
}
}
Тут цікаві кілька речей:
- Атрибути
#[Temperature]і#[MaxSteps]декларативні - не потрібно передавати значення через параметри конструктора чи конфігураційний файл. - Метод
instructions()рендерить Blade-представлення, тож системний промпт - це звичайний шаблон з include та partial. - Метод
tools()повертає iterable, тому інструменти можна вмикати й вимикати залежно від контексту.
Інструменти (tools)
Інструменти - це класи, які модель може вирішити викликати. Вони мають типізовану JSON-схему, тож аргументи валідуються ще до того, як потраплять у код застосунку. Ось інструмент, що змінює статус тікета:
class ChangeTicketStatusTool implements Tool
{
use FindsTicketsInWorkspace;
public function __construct(protected Workspace $workspace) {}
public function description(): string
{
return 'Change the status of a ticket. Use this when the agent asks to close, reopen, or mark a ticket as spam.';
}
public function schema(JsonSchema $schema): array
{
return [
'ticket_subject' => $schema->string()->description('The subject, title, or ULID of the ticket.')->required(),
'contact_email' => $schema->string()->description('The contact email on the ticket.')->required(),
'new_status' => $schema->string()->description('The new status: open, closed, or spam.')->required(),
];
}
public function handle(Request $request): string
{
$status = TicketStatus::tryFrom($request['new_status'] ?? '');
if (! $status) {
return 'Invalid status. Must be one of: open, closed, spam.';
}
$ticket = $this->findTicket($request['ticket_subject'], $request['contact_email']);
if (! $ticket) {
return 'Could not find a matching ticket.';
}
app(ChangeTicketStatusAction::class)->execute($ticket, $status);
return json_encode([
'result' => 'success',
'ticket' => $ticket->displayTitle(),
'new_status' => $status->label(),
]);
}
}
Кожен метод має чітку роль:
description()- те, що модель читає, вирішуючи, чи викликати цей інструмент.schema()- типізована JSON-схема, якій має відповідати модель.handle()- виконує інструмент і повертає рядок, що потрапляє назад у контекст моделі. У разі успіху повертається JSON, щоб у моделі були структуровані дані для фінальної відповіді.
Важливо, що всередині інструмента немає складної логіки. Реальна робота делегована ChangeTicketStatusAction - звичайній domain-дії. Інструмент лише виступає AI-адаптером над кодом, який уже існував.
Виклик агента
Викликати агента з action можна кількома рядками:
$agent = new ThereThereAgent(
workspace: $workspace,
user: $user,
contextTicket: $agentChat->contextTicket,
conversationHistory: $agentChat->getConversationHistoryExcludingLatest(),
);
return $this->streamingService->stream($agent, $messageForAi, $agentChat);
Агента передають у streaming-сервіс, який перетворює потік відповіді SDK на події, зрозумілі фронтенду. Про це автор обіцяє окремий допис.
Підсумок
За словами автора, Laravel AI SDK ідеально підійшов для їхнього сценарію: абстракції агента й інструментів прості, промпт на основі Blade приємно редагувати, а типізована схема означає, що модель рідко плутає аргументи. Вони лише торкнулися поверхні можливостей SDK.
Детальніше про SDK можна дізнатися в репозиторії Laravel AI SDK, а спробувати There There - подавши заявку на ранній доступ до закритої бети на there-there.app.
Читати в документації
Читайте також
Порядок авторизації та прив’язки моделей у Laravel: чому замість 403 приходить 404
Чому користувач без прав отримує 404 замість 403 на маршрутах із route model binding, як це пов’язано з пріоритетом middleware SubstituteBindings та Authorize і як це виправити за допомогою PermissionMiddleware.
Вакансії за темою
Product Lead (Strategy + Full Stack)
Product Lead з відповідальністю за розробку та лідерство. Гібридна роль: 60% управління продуктом (дорожна карта, пріоритизація, аналітика) та 40% full stack розробки на PHP Laravel. Потрібен досвід розробника (5+ років), знання бекенду, баз даних, здатність приймати продуктові рішення та керувати командою. Основний стек: PHP 8.2+, Laravel 12, MySQL, Redis, Alpine.js, Livewire.
Senior/ Lead PHP Engineer (with AI Skills) (#5828)
Розробка та підтримка backend-сервісів і API на PHP та Laravel з повним циклом володіння фічами від дизайну до моніторингу. Ключове завдання - інтеграція агентних AI-workflow (LLM-агенти, tool calling) у продукт і внутрішні інструменти, а також менторство інших інженерів. Вимоги: 6+ років PHP, 4+ роки з Laravel, досвід GenAI, знання MySQL/PostgreSQL, Docker та хмарних платформ, робота за часовим поясом EDT.
Senior / Lead PHP Engineer with AI skills
Senior/Lead PHP Engineer з досвідом у Laravel та GenAI. Займатиметесь розробкою backend-сервісів і API, інтеграцією агентних AI-систем (LLM, tool-calling, autonomous workflows), оптимізацією продуктивності й надійності. Потребує 6+ років досвіду PHP, 4+ років Laravel, hands-on роботи з LLM API та агентами. Основний стек: PHP, Laravel, MySQL/PostgreSQL, Docker, AWS/GCP. Керівництво та ментор інших інженерів.
Пакети за темою
Laravel Blade Javascript
Blade-директива для експорту змінних у JavaScript.