Головна / Статті

Автоматизація розробки Laravel за допомогою AI-агентів: створення, тестування, виправлення, повторення

Використовуйте Laravel Boost та OpenCode CLI для запуску циклу збірки, тестування та виправлення помилок із черговими завданнями, оркестратором, обмеженнями на повторні спроби та людським переглядом перед тим, як щось буде випущено.

Видавець: REACTAPP.TOP14 хв читання3315 слів

Джерела: Build → Test → Fix → Repeat: Automating Laravel Development with AI Agents

Бігова доріжка копіювання-вставки

Підрахуйте, як часто типовий день із AI-асистентом для програмування проходить саме так. Напишіть запит. Копіюйте результат у редактор. Запустіть php artisan test. Побачите помилку. Копіюйте текст помилки, вставте його назад у AI, скажіть «будь ласка, виправте це» та почніть все знову з кроку копіювання.

Деякі розробники повторюють цей цикл десятки разів на день. Це називають розробкою з використанням AI, проте найбільш активною особою у цьому процесі є не AI, а людина, яка передає повідомлення між моделлю та терміналом: вирішує, коли потрібно запускати тести, читає помилки, оцінює, чи потрібна ще одна корекція, та постійно копіює все туди-сюди. AI залишається без діяльності до моменту отримання наступної інструкції.

Краща схема передбачає відсутність людини-кур’єра. Невелика система може самостійно виконувати цикл створення, тестування, виправлення та повторення до моменту повного завершення роботи, а людина втручається лише для фінального перегляду. Тут використовуються Laravel Boost та OpenCode CLI — два інструменти, які підходять для сучасної екосистеми Laravel. Пояснення наводяться повільно навмисно, щоб той, хто ніколи не користувався жодним із цих інструментів, міг їх зрозуміти.

Словник

Деякі терміни зустрічаються неодноразово:

  • Агент: ШІ, якому надано чітко визначене завдання та певний набір інструментів, що можна порівняти з працівником із письмово описаними обов’язками.
  • MCP (Model Context Protocol): з’єднувач, через який ШІ може ставити запитання безпосередньо до вашої системи — чи то база даних, чи журнали — тож він не змушений здогадуватися на основі того, що ви випадково ввели.
  • CLI (інтерфейс командного рядка): програмне забезпечення для керування шляхом введення команд у терміналі замість навігації по графічному інтерфейсу.
  • Завдання та черга: спосіб, яким Laravel обробляє завдання у фоновому режимі, по одному елементу за раз, щоб жоден користувач не змушений був чекати без дії.
  • Оркестратор: пристрій керування потоком, який визначає послідовність виконання завдань, відстежує, хто на черзі, та знає, де продовжити після збою.
  • Питання вже не стосується „чи може ШІ писати код“

    Усі, хто використовував Claude Code, Cursor чи подібні інструменти з Laravel, вже знають, що ШІ може створювати міграції, моделі, правила та тести. Залишається проблема ручного процесу роботи з цими елементами:

    Developer → AI → Developer → Test → Developer → AI → Developer → Test → ...
    

    Кожна стрілка означає людину, яка вручну переміщує контекст з одного кроку на інший, вирішуючи, що настав час для тестування або що помилка потрібно повернути до ШІ.

    Порівняйте з автоматизованою версією:

    Developer → Orchestrator → Build → Test → (failed? → Fix → Test again) → Review → Done
    

    Різниця здається незначною, але вона має велике значення. Тепер існує оркеструвальник — шар, який знає порядок виконання завдань, коли потрібно спробувати знову та коли припинити, без необхідності постійного контролю кожні тридцять секунд.

    Laravel також рухається в цьому напрямку. Laravel Boost був створений для того, щоб штучні інтелектуальні агенти отримували реальний контекст про Laravel-додаток замість того, щоб вгадувати на основі загальних шаблонів.

    Два основні інструменти

    Laravel Boost: перекладач між ШІ та додатком

    Штучний інтелект може добре розбиратися в Laravel загалом, але все одно залишатися практично безпорадним у вашому конкретному проекті. Він не знає, яку версію Laravel ви використовуєте, як насправді структурована ваша база даних, чи використовуєте ви Livewire чи Inertia, або які правила найменування прийняті вашою командою.

    Boost — це пакет Composer, який допомагає ШІ ознайомитися з реальним станом проекту. Він працює через MCP, тож ШІ може безпосередньо запитувати додаток: переглядати схему бази даних, дивитися насичені маршрути, бачити останні помилки, виконувати команди Artisan та шукати інформацію в документації Laravel, яка відповідає версіям пакетів, що фактично встановлені.

    Для встановлення потрібно дві команди:

    composer require laravel/boost --dev
    
    php artisan boost:install
    

    Другий варіант є інтерактивним. Він запитує, який IDE чи інструмент ШІ ви використовуєте, та встановлює відповідні ресурси.

    Варто чітко зазначити, що Boost перекладає контекст, але не приймає рішень. Він не скаже «запустіть тести зараз» чи «прискорте вирішення проблем у разі збою». Його єдиною функцією є запобігання тому, щоб агенти не були необізнані щодо реального стану додатку.

    OpenCode: де виконує роботу агент

    Якщо Boost перекладає контекст, то OpenCode — це робоча зона: CLI, створена спеціально для програмування за допомогою ШІ. На відміну від звичайного чат-бота, він дозволяє визначити кілька агентів із різними обов’язками — наприклад, одного, який пише код, та іншого, який аналізує результати тестів; кожен з них має власні інструкції та обмеження.

    OpenCode також може під’єднуватися до серверів MCP, зокрема до Boost. Після під’єднання все, що пропонує Boost, стає доступним для будь-якого агента, який працює всередині OpenCode.

    Практичне застосування: підключення Boost до OpenCode

    Спочатку спробуйте виконувати дії вручну у терміналі, перш ніж щось автоматизувати.

    1. Вказати OpenCode на Boost

    Після встановлення Boost OpenCode має дізнатися, де знаходиться його сервер MCP. Ця інформація зберігається у файлі opencode.json у корені проекту:

    {
      "$schema": "https://opencode.ai/config.json",
      "mcp": {
        "laravel-boost": {
          "type": "local",
          "command": ["php", "artisan", "boost:mcp"],
          "enabled": true
        }
      }
    }
    

    Точна команда може відрізнятися залежно від версії Boost, тому слідуйте тому, що генерує php artisan boost:install, замість того щоб сліпо копіювати цей текст. Це одна з частин, які найчастіше змінюються між версіями.

    Переконайтеся, що OpenCode може бачити сервер:

    opencode mcp list
    

    Якщо laravel-boost відображається як підключений, агенти мають доступ до всього, що надає Boost.

    2. Створіть агентів із окремими обов’язками

    Замість одного агента для всіх завдань розділіть обов’язки. Створіть файл .opencode/agent/builder.md у проекті:

    ---
    description: Build Agent for new features
    mode: primary
    tools:
      write: true
      edit: true
    ---
    You are the Build Agent.
    Use Laravel Boost tools to understand the application structure
    before writing any code. Never assume the database structure -
    always check the schema first. Write tests for every new behavior.
    At the end of your work, return ONLY JSON in this format:
    {"status": "success", "files_changed": [...], "summary": "..."}
    

    Рядок tools.write: true дозволяє цьому агенту змінювати файли. Наступний агент отримає write: false, що зробить його лише для читання та нездатним щось змінювати, а причина цього буде пояснена незабаром.

    Перелічте зареєстрованих агентів:

    opencode agent list
    

    3. Спочатку запустіть його вручну

    opencode run --agent builder "Add a task assignment feature to TaskFlow according to the requirements in TASK.md"
    

    Деякі примітки щодо цієї команди:

    • --agent визначає, який агент буде виконувати завдання — будівельник, тестувальник чи щось інше, визначене раніше.
    • Додавання --format json змушує результат бути у вигляді сирих даних, а не форматом, придатним для людей. Саме цей формат буде використовуватися пізніше у PHP-функціях.
    • --session та --continue дозволяють агенту пам’ятати попередні розмови замість того, щоб починати все спочатку кожного разу.

    Спробуйте виконати її вручну кілька разів, щоб зрозуміти принцип роботи, перш ніж автоматизувати процес.

    Конкретний приклад: розподіл завдань у TaskFlow

    Один простий приклад допомагає краще зрозуміти суть. Уявіть простий додаток для керування завданнями під назвою TaskFlow, який потребує наступної функціональності: користувачі можуть передавати завдання іншому користувачеві, але лише в межах проєкту, до якого є доступ обом. Розбивка вимог:

    1. A task has an assignee column (belongs to a User).
    2. A user can only assign tasks within a project they're a member of.
    3. A user must not assign or access tasks from other projects.
    4. All of the above must be covered by automated tests.
    

    Це поширена вимога, саме тому це є гарною демонстрацією. У подібних випадках часто потрібно два-три спроби, перш ніж питання про те, хто може отримати доступ до чого, що технічно називається авторизацією, стане бездоганним. Саме тут і знаходить своє застосування автоматизований цикл.

    Чому процес потребує журналу записів

    Як тільки робота виконується на фоні, ніхто не може покладатися на пам’ять щодо запитання „Яка ж це була помилка?“. Усе має бути задокументовано, а тут це означає використання таблиці бази даних як журналу прогресу:

    Schema::create('ai_tasks', function (Blueprint $table) {
        $table->id();
        $table->string('type');
        $table->string('status')->default('pending');
        $table->text('prompt');
        $table->json('result')->nullable();
        $table->unsignedTinyInteger('iteration')->default(0);
        $table->timestamps();
    });
    

    Модель:

    class AiTask extends Model
    {
        protected $fillable = [
            'type', 'status', 'prompt', 'result', 'iteration',
        ];
    
        protected function casts(): array
        {
            return ['result' => 'array'];
        }
    }
    

    Стовпець status має вирішальне значення. Він проходить через наступні етапи:

    pending → building → testing → (fixing → testing)* → reviewing → completed / failed
    

    Чому не просто прапорець done? Тому що оркестратор має знати, на якій стадії знаходиться робота, а не лише те, чи вона завершена. Відстеження стадій також дозволяє продовжити виконання після збою працівника та відображати поточний стан роботи.

    Інтеграція OpenCode у PHP

    Люди часто пропускають цей крок: не розкидуйте команди OpenCode по багатьох файлах. Якщо інструмент згодом буде замінений, не потрібно редагувати десятки місць. Створіть одну точку входу — інтерфейс:

    interface AgentRunner
    {
        public function run(string $agent, string $prompt): array;
    }
    

    Потім справжня реалізація, яка викликає OpenCode з PHP:

    class OpenCodeAgentRunner implements AgentRunner
    {
        public function run(string $agent, string $prompt): array
        {
            $result = Process::timeout(600)->run([
                'opencode', 'run',
                '--agent', $agent,
                '--format', 'json',
                $prompt,
            ]);
    
            if (! $result->successful()) {
                return [
                    'status' => 'error',
                    'error' => $result->errorOutput(),
                ];
            }
    
            return json_decode($result->output(), true) ?? [
                'status' => 'error',
                'error' => 'Agent output was not valid JSON',
            ];
        }
    }
    

    Зв’яжіть його з постачальником послуг:

    $this->app->bind(AgentRunner::class, OpenCodeAgentRunner::class);
    

    За допомогою цього паттерну кожна робота спілкується лише з AgentRunner та не переймається тим, чи знаходиться за нею OpenCode чи щось інше.

    Одна робота на етап

    Для тих, хто новий у Laravel Jobs: робота — це завдання, яке виконується у фоновому режимі через чергу, тож ніхто не чекає, поки воно завершиться.

    Робота зі створенням

    class BuildFeature implements ShouldQueue
    {
        public function __construct(protected AiTask $task) {}
    
        public function handle(AgentRunner $agent): void
        {
            $result = $agent->run(
                agent: 'builder',
                prompt: $this->task->prompt,
            );
    
            $this->task->update([
                'status' => 'testing',
                'result' => $result,
            ]);
    
            RunTests::dispatch($this->task);
        }
    }
    

    Тут виконується Build Agent, його результат зберігається, і процес безпосередньо переходить до наступної роботи (RunTests) без необхідності вручну натискати кнопки.

    Робота з тестуванням

    Цю роботу навмисно зроблено простою – їй не потрібне розуміння коду. Вона запускає набір тестів та читає їхні результати.

    class RunTests implements ShouldQueue
    {
        public function __construct(protected AiTask $task) {}
    
        public function handle(): void
        {
            $result = Process::timeout(300)->run('php artisan test --compact');
    
            if ($result->successful()) {
                $this->task->update(['status' => 'reviewing']);
                ReviewFeature::dispatch($this->task);
                return;
            }
    
            $this->task->update(['status' => 'failed']);
            AnalyzeFailure::dispatch($this->task, $result->output());
        }
    }
    

    Процес є простим:

    Test
     ├── passed → move to Review
     └── failed → move to Analyze
    

    Не передавайте первинні помилки агенту

    Цей етап є найлегшим для пропуску, але має найбільший вплив на якість. Якщо просто повідомити агенту, що тест провалився, та попросити виправити це, цього буде замало. Він мусить починати все з нуля, перечитувати кожен файл та здогадуватися, що пішло не так.

    Спочатку структуруйте інформацію про помилку:

    {
      "status": "failed",
      "failed_tests": [
        {
          "name": "user_cannot_assign_task_from_other_project",
          "error": "Expected response status code [403] but received 200."
        }
      ]
    }
    

    Потім передайте його агенту, чиєю єдиною функцією є діагностика — назвімо його Debug Agent, — з інструкціями, які суворо обмежують те, що він може робити:

    You are the Debug Agent.
    
    Analyze the failed tests below. DO NOT modify any files.
    Inspect: relevant models, policies, migrations, and tests.
    Return JSON with:
    - root_cause
    - affected_files
    - recommended_fix
    - risk_of_regression
    

    Чому розділяти діагностику та усунення проблеми? Щоб створити проміжок між розумінням проблеми та виконанням ремонту. Якщо агенту просто кажуть усунути проблеми, сліди того, чому змінився певний файл, зникають, і остаточну перевірку стає набагато складніше здійснити.

    Для TaskFlow результат роботи агента для відлову помилок, ймовірно, буде виглядати так:

    {
      "status": "analyzed",
      "root_cause": "TaskPolicy does not verify project membership before allowing assignment",
      "affected_files": ["app/Policies/TaskPolicy.php"],
      "recommended_fix": "Add a project membership check inside the assign() method",
      "next_action": "fix"
    }
    

    Це було б правдоподібне висновок, адже це одна з найпоширеніших проблем з авторизацією: політика, яка перевіряє лише те, чи увійшов користувач, а не чи він дійсно належить до проекту.

    Виправте, потім знову протестуйте, ніколи не обмежуйтесь лише виправленням

    class FixFeature implements ShouldQueue
    {
        public function __construct(protected AiTask $task, protected array $analysis) {}
    
        public function handle(AgentRunner $agent): void
        {
            $result = $agent->run(
                agent: 'fixer',
                prompt: json_encode($this->analysis),
            );
    
            $this->task->increment('iteration');
            $this->task->update([
                'status' => 'testing',
                'result' => $result,
            ]);
    
            RunTests::dispatch($this->task);
        }
    }
    

    Зверніть увагу, що після виправлення статус повертається до testing, а не переходить у completed. Принцип є чітким: агент не має права оголошувати про завершення роботи лише через зміну файлу. Єдиним достовірним доказом коректності є тестовий набір, який справді було запущено знову, а не власні твердження агента.

    Оркестратор як керувальник трафіку

    Спочатку здається привабливим послідовне виконання завдань у лінійному порядку:

    Bus::chain([
        new BuildFeature($task),
        new RunTests($task),
        new ReviewFeature($task),
    ])->dispatch();
    

    Але це лише лінійна структура. Як тільки з’являється гілка, наприклад для повернення та виправлення помилки після невдачного тесту, лінійний ланцюг виявляється недостатнім. Тут потрібен щось на кшталт диспетчера руху, який щоразу, коли його запрошують, перевіряє поточний стан та обирає наступні дії:

    class AgentOrchestrator
    {
        public function next(AiTask $task): void
        {
            match ($task->status) {
                'pending'   => BuildFeature::dispatch($task),
                'testing'   => RunTests::dispatch($task),
                'failed'    => AnalyzeFailure::dispatch($task),
                'fixing'    => RunTests::dispatch($task),
                'reviewing' => ReviewFeature::dispatch($task),
                'completed', 'stopped' => null,
                default => throw new LogicException("Unknown status: {$task->status}"),
            };
        }
    }
    

    Зміна підходу є значною. Оркеструвальник більше не запитує, яке наступне завдання. Він запитує, який перехід є дійсним з урахуванням поточного статусу. Це полегшує виправлення помилок, і можна додати новий етап, наприклад перевірку безпеки, не порушуючи загального потоку.

    Обмежте кількість спроб

    Без обмежень може розгортатися сценарій на кшталт цього:

    Fix → Test fails → Fix → Test fails → Fix → Test fails → ...
    

    Це марнує час, а кожен виклик ШІ також має певну вартість. Рішенням є встановлення верхньої межі:

    class RunTests implements ShouldQueue
    {
        protected const MAX_ITERATIONS = 5;
    
        public function handle(): void
        {
            if ($this->task->iteration >= self::MAX_ITERATIONS) {
                $this->task->update(['status' => 'needs_human_review']);
                return;
            }
            // ... run tests as usual
        }
    }
    

    Якщо п’ять спроб виправлення недостатні, система зупиняється та позначає завдання для людини, замість того щоб продовжувати спроби без кінця. Це не обов’язково означає, що ШІ погано справляється з цим завданням. Іноді вимоги є неоднозначними, або для прийняття рішення справді потрібна людина.

    Позитивні тести не означають завершення функції

    Легко забути про це, коли система, яка працює самостійно, здається захоплюючою. Припустимо, агент пише щось на кшталт:

    public function assign(User $user, Task $task): bool
    {
        return $user->isAdmin();
    }
    

    Усі тести можуть пройти, оскільки вони охоплюють лише сценарії, які хтось уявив на той момент. У справжній вимозі було зазначено, що користувач має бути членом проекту, а не те, що він має бути адміністратором. Логіка є неправильною, хоча всі тести залишаються у статусі «зелений».

    Ось чому цей процес є небезпечним:

    Build → Tests pass → Deploy immediately
    

    Більш безпечний варіант виглядає так:

    Build → Test → (if failed: Debug → Fix → Test again) → Human review → Deploy
    

    Етап перевірки все одно має включати людину, принаймні для всього, що стосується прав доступу. Для змін, чутливих до безпеки, як ця, доцільно вимагати людської перевірки перед об’єднанням, незалежно від того, наскільки ефективним здається автоматизований процес.

    Паралельні агенти та конфлікти файлів

    Ідея одночасної роботи агента безпеки та агента досліджень призводить до проблем. Якщо два агенти можуть записувати один і той самий файл приблизно в один час, виникає ситуація змагання, що означає, що два процеси перезаписують роботу один одного.

    Agent A → writes Task.php
    Agent B → reads Task.php (nearly at the same time)
    Agent A → writes Task.php again
    

    Основне правило полягає у розподілі обов’язків між тими, хто може писати, і тими, хто може лише читати:

    Research Agent   → read-only
    Review Agent     → read-only
    Security Agent   → read-only
    Build Agent      → write access
    Fix Agent        → write access
    

    Це також пояснює рядок tools.write: true у попередньому файлі builder.md. Він контролює, які агенти можуть змінювати файли, а які лише переглядати їх.

    Протистояйте супер-агенту

    Найбільшою спокусою, коли це починає працювати, є ідея про єдиного агента, який би планував, створював, тестував, виправляв помилки, перевіряв та розгортав все. Це зазвичай призводить до негативних наслідків. Чим більше відповідальності несе один агент, тим більше інформації йому потрібно обробляти, тим більше інструментів у нього є, і тим вища ймовірність прийняття неправильного рішення. Краще розділяти завдання за принципом відповідальності:

    Planner   → designs the approach
    Builder   → writes the new feature's code
    Tester    → runs the test suite
    Debugger  → diagnoses failures (read-only)
    Fixer     → executes the fix
    Reviewer  → final quality check (read-only)
    

    Це той самий принцип, що й уникнення одного величезного класу, який керує всім у додатку. Також не варто створювати одного агента, який робить усе.

    Мінімальний перелік перевірок перед реальним використанням

    Для експериментів у реальному проекті ці елементи мають бути присутні з першого дня, а не додаватися пізніше:

    • жорсткий ліміт на кількість спроб, щоб цикл виправлень не міг тривати вічно
    • часовий ліміт на кожну команду, яку виконує агент
    • обмеження на доступ до файлів, що відповідають ролі агента — лише для читання або для запису
    • тести, які мають існувати та пройти, перш ніж статус може перейти до перевірки
    • схвалення людини перед тим, як щось потрапить у продакшн
    • логування кожного рішення, яке приймає агент, а не лише кінцевого результату
    • захист секретних файлів, таких як .env, щоб агент ненавмисно їх не прочитав

    Останній елемент часто недооцінюється. Як тільки агент може виконувати команди в системі за допомогою описаних вище викликів Process::run(), він може фактично виконувати майже все на цьому сервері. Обмеження доступу є вимогою з самого початку, а не опцією.

    Фактична роль Boost

    Ось повна картина:

    Developer (us)
        │
        ▼
    Orchestrator (traffic controller, running on Laravel Queue)
        │
        ├── Build Agent
        ├── Test Agent
        ├── Debug Agent
        └── Fix Agent
              │
              ▼
          OpenCode (where the agent does its work)
              │
              ▼
        Laravel Boost (context translator, via MCP)
              │
              ▼
        Our Laravel application
    

    Boost ніколи не вирішує самостійно запустити тести чи агента для виправлення при збої. Він виконує одну важливу функцію: переконується, що кожен агент у проекті розуміє справжній стан додатку та не припускається висновків на основі загальних шаблонів. Питання про те, хто діє, у який момент та коли все зупиняється, залишаються за оркестратором, створеним на Laravel Queue.

    Питання, яке потрібно поставити зараз

    Кілька років тому цікавим було питання про те, чи може ШІ писати правильний код на Laravel. Він це чітко може та постійно вдосконалюється. Сьогодні більш актуальним є питання про те, чи може ШІ створити власний цикл зворотного зв’язку — створення, тестування, збій, розуміння причини, виправлення та повторне тестування — поки справа справді не буде вирішена, без участі людини як ручного посередника на кожному етапі.

    Сценарій TaskFlow свідчить про те, що відповідь — так, за умови, що не буде забуто трьох речей. Прогрес має чітке облікування, спроби повторення — жорсткий ліміт, а позитивні тести мають вважатися одним сигналом, а не кінцевою метою.

    Boost допомагає агентам зрозуміти контекст застосунку. OpenCode надає їм місце для роботи. Laravel Queue стає основою, яка організовує всі процеси. А розробники можуть піднятися на вищий рівень — від ручного оператора на кожному етапі до перевіряючого кінцевого результату.

    Vibe coding прискорює процес написання коду. Така оркестрація дозволяє процесу кодування працювати самостійно, і саме тут експерименти з ШІ у кодуванні стають цікавими.