---
title: "Laravel AI SDK на практиці: агент, що стоїть за There There"
url: https://laravelukraine.com/blog/laravel-ai-sdk-na-praktici-agent-shho-stoyit-za-there-there
date: 2026-10-09
source: https://freek.dev/3089-laravel-ai-sdk-in-practice-the-agent-behind-there-there
---

# Laravel AI SDK на практиці: агент, що стоїть за There There

Фрік Ван дер Гертен (Freek Van der Herten) у [своєму дописі](https://freek.dev/3089-laravel-ai-sdk-in-practice-the-agent-behind-there-there) поділився досвідом використання нового [Laravel AI SDK](https://github.com/laravel/ai) у [There There](https://there-there.app) - хелпдеску, який команда Spatie створює для себе. Після двох десятиліть власної підтримки клієнтів вони вирішили більше не миритися з компромісами в інструментах і зробити той, яким хочуть користуватися самі.

## Роль AI у There There

Найцікавіша частина проєкту - AI. Його використовують не для заміни агентів підтримки, а для того, щоб вони працювали швидше. Людина читає тікет, думає й натискає кнопку, а модель підтягує релевантну історію, готує чернетку відповіді, ставить теги та закриває дублікати. Зараз There There перебуває в закритому бета-тестуванні, а заявку на ранній доступ можна подати на [there-there.app](https://there-there.app).

Весь AI-шар збудований на Laravel AI SDK.

## Клас агента

Усе починається з класу агента - у Spatie він називається `ThereThereAgent`. Він визначає, яку модель використовувати, як виглядає системний промпт, які інструменти (tools) може викликати модель і яку історію розмови відтворювати. Ось скорочена версія:

```php
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-схему, тож аргументи валідуються ще до того, як потраплять у код застосунку. Ось інструмент, що змінює статус тікета:

```php
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 можна кількома рядками:

```php
$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](https://github.com/laravel/ai), а спробувати There There - подавши заявку на ранній доступ до закритої бети на [there-there.app](https://there-there.app).
