---
title: "URL AI Transformer від Spatie: сторінки за списком URL через Laravel AI SDK"
url: https://laravelukraine.com/blog/url-ai-transformer-vid-spatie-storinki-za-spiskom-url-cerez-laravel-ai-sdk
date: 2026-10-27
---

# URL AI Transformer від Spatie: сторінки за списком URL через Laravel AI SDK

Spatie має пакет [`laravel-url-ai-transformer`](https://github.com/spatie/laravel-url-ai-transformer), який робить одну просту річ: завантажує сторінки за списком URL, віддає їхній текст AI-моделі з вашими інструкціями і зберігає відповідь у базі. На нього звернув увагу [Laravel News](https://laravel-news.com/laravel-url-ai-transformer), і привід є: у липні [версія 2.0.0](https://github.com/spatie/laravel-url-ai-transformer/releases/tag/2.0.0) замінила Prism на офіційний Laravel AI SDK, а 19 жовтня [2.1.0](https://github.com/spatie/laravel-url-ai-transformer/releases/tag/2.1.0) додала підтримку `laravel/ai` 1.x.

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

### Встановлення

Поточні версії потребують PHP 8.4 і Laravel 13. Laravel News пише про «Laravel 12 або 13», але, за [гайдом з оновлення](https://github.com/spatie/laravel-url-ai-transformer/blob/main/UPGRADE.md), Laravel 12 підтримує лише 2.0.0 - для нього треба зафіксувати саме цю версію.

```bash
  composer require spatie/laravel-url-ai-transformer
  php artisan vendor:publish --tag="url-ai-transformer-migrations"
  php artisan migrate
```

Конфіг публікувати не обов'язково (`--tag="url-ai-transformer-config"`), але API-ключ провайдера потрібен. За замовчуванням це OpenAI, тож у `.env` додаємо `OPENAI_API_KEY`.

### Реєстрація URL і запуск

URL і трансформери для них реєструються через клас `Transform`, зазвичай у сервіс-провайдері:

```php
  use Spatie\LaravelUrlAiTransformer\Support\Transform;
  use Spatie\LaravelUrlAiTransformer\Transformers\LdJsonTransformer;

  Transform::urls(
      'https://example.com/about',
      'https://example.com/products',
  )->usingTransformers(new LdJsonTransformer, new SummaryTransformer);
```

`urls()` приймає і closure. Він виконується лише тоді, коли запускається трансформація, тож вибірка з бази не додає запит до кожного HTTP-запиту застосунку:

```php
  Transform::urls(
      fn () => Article::published()->pluck('url')->toArray()
  )->usingTransformers(new LdJsonTransformer);
```

Так само пакет поєднується зі [`spatie/crawler`](https://github.com/spatie/crawler): у closure обходимо сайт через `Crawler::create(url('/'))->internalOnly()->foundUrls()` і перетворюємо отримані `CrawledUrl` на рядки через `array_map()`. Так трансформація охоплює всі внутрішні сторінки без ручного списку.

Запускає все Artisan-команда:

```bash
  php artisan transform-urls
  php artisan transform-urls --url="*/blog/*" --transformer="ldJson"
  php artisan transform-urls --force
  php artisan transform-urls --now
```

Команда завантажує кожен URL один раз, синхронно, і ставить у чергу окреме завдання на кожен трансформер, зареєстрований для цієї сторінки. Тому потрібен воркер черги, або `--now`, щоб виконати все одразу. `--url` і `--transformer` підтримують wildcard, тож можна перегенерувати лише частину сайту. `--force` ігнорує `shouldRun()` - метод, яким трансформер може пропустити сторінку, наприклад, якщо вже успішно обробив її менше ніж 30 днів тому.

Результат читається за URL і типом трансформера. Тип за замовчуванням - назва класу в camelCase без суфікса `Transformer`, але можна передати й сам клас:

```php
  use Spatie\LaravelUrlAiTransformer\Models\TransformationResult;

  $ldJson = TransformationResult::forUrl('https://example.com/about', 'ldJson');
```

`findForUrl()` повертає саму модель. На ній є `regenerate()` (знову в чергу) і `regenerateNow()`, а якщо трансформер упав, у полях `latest_exception_message` і `latest_exception_trace` лежить причина, і спрацьовує подія `TransformerFailed`. Щоб результати не застарівали, команду ставлять у розклад у `routes/console.php`.

### Свій трансформер

`php artisan make:transformer SummaryTransformer` створює клас в `app/Transformers`. Мінімально потрібен лише `instructions()` - це системний промпт агента, а текст сторінки пакет передає як промпт користувача:

```php
  namespace App\Transformers;

  use Spatie\LaravelUrlAiTransformer\Transformers\Transformer;
  use Stringable;

  class SummaryTransformer extends Transformer
  {
      public function instructions(): Stringable|string
      {
          return 'Summarize this webpage content in 3 concise bullet points.';
      }
  }
```

Для машиночитаних даних трансформер реалізує контракт `HasStructuredOutput` з AI SDK і описує `schema()`: відповідь перевіряється за схемою і зберігається як JSON. Вбудований `LdJsonTransformer` іде трохи хитрішим шляхом, бо JSON-LD не має фіксованої форми: схема містить одне обов'язкове рядкове поле `json`, а `resultFrom()` декодує його з `JSON_THROW_ON_ERROR`. Невалідний JSON кидає виняток, трансформація позначається як невдала, і завдання в черзі пробує ще раз.

Трансформер може взагалі обійтися без AI: перевизначте `transform()` і порахуйте результат сам, наприклад кількість слів на сторінці. Для тестів досить `Http::fake()` для сторінки і `SummaryTransformer::fake([...])` для відповіді моделі.

### Під капотом - Laravel AI SDK

Перша версія ходила до моделей через Prism. Друга зробила базовий `Transformer` агентом [Laravel AI SDK](https://github.com/laravel/ai), тож усе, що ви знаєте про агентів SDK, працює і тут. Якщо з ним ще не знайомі, почніть з нашого розбору [агента, що стоїть за There There](https://laravelukraine.com/blog/laravel-ai-sdk-na-praktici-agent-shho-stoyit-za-there-there) - це теж проєкт Spatie на тому самому SDK.

У конфігу задаються провайдер з енума `Lab` і модель: рядком або `Model::Cheapest` / `Model::Smartest`. За замовчуванням - OpenAI і найдешевша модель. Окремий трансформер перевизначає це атрибутами SDK:

```php
  use Laravel\Ai\Attributes\Model;
  use Laravel\Ai\Attributes\Provider;
  use Laravel\Ai\Attributes\Temperature;
  use Laravel\Ai\Enums\Lab;

  #[Provider(Lab::Anthropic)]
  #[Model('claude-haiku-4-5-20251001')]
  #[Temperature(0.1)]
  class FactsTransformer extends Transformer
  {
      // ...
  }
```

`#[UseSmartestModel]` змінює лише модель, лишаючи провайдера з конфігу, а методи `provider()` і `model()` дозволяють обрати їх під час виконання. Провайдери - ті, що підтримує сам SDK: документація пакета згадує OpenAI, Anthropic і Gemini.

У `composer.json` стоїть `laravel/ai: ^0.11.0|^1.2.3`, тож оновлювати SDK заради пакета не обов'язково. Якщо оновлюєтеся до 1.x, Spatie радить спершу прочитати UPGRADE.md SDK, особливо якщо у вас збережені розмови, власні `middleware` агентів, MCP або Bedrock. І ще одне: `Model::Cheapest` тепер може вказувати на іншу модель, ніж раніше, тож якщо результати мають бути стабільними, зафіксуйте назву моделі в `url-ai-transformer.ai.model`.

### Обмеження і вартість

- **6000 символів.** Перед відправкою `PrepareUrlContentAction` вирізає скрипти й стилі, знімає HTML-теги, стискає пробіли і обрізає текст до 6000 символів. Для довгих статей модель побачить лише початок. Змінити це можна в `content()` окремого трансформера або власною дією в конфігу, наприклад взявши лише вміст `<main>`.
- **Кожна пара «URL + трансформер» - окремий запит.** Сайт на 500 сторінок із двома трансформерами - це 1000 запитів до моделі на кожен прогін. Щоденний розклад разом із краулером без `shouldRun()` швидко стає помітним рахунком.
- **Завантаження сторінок не в черзі.** Повтори, backoff і обмеження частоти запитів (`RateLimited` у власному `ProcessTransformerJob`) діють на AI-запит, а сторінки команда тягне синхронно. Довгий список URL - довга команда.
- **Старі версії могли слати моделі JavaScript.** У [2.0.1](https://github.com/spatie/laravel-url-ai-transformer/releases/tag/2.0.1) виправили випадок, коли великі inline-скрипти впиралися в ліміт PCRE і до моделі доходив код замість тексту сторінки. Якщо сидите на старішій версії і бачите дивні результати, причина може бути тут.

### Кому варто спробувати

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

Почніть з `--now` на двох-трьох URL і дешевої моделі, подивіться на результат у `transformation_results` і лише потім вмикайте розклад. А згенерований JSON-LD перед виведенням у `<script type="application/ld+json">` варто вибірково перевірити: розмітка, що не відповідає вмісту сторінки, пошуковикам не допоможе, а `json_decode()` гарантує лише валідний синтаксис, а не правдивість.
