Главная / Статьи

Автоматизация разработки на Laravel с помощью ИИ-агентов: создание, тестирование, исправление ошибок, повторение

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

Издатель: REACTAPP.TOP14 мин чтения3315 слов

Источники: Build → Test → Fix → Repeat: Automating Laravel Development with AI Agents

Беговая дорожка копирования и вставки

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

Некоторые разработчики повторяют этот цикл десятки раз в день. Это называют разработкой с использованием ИИ, но на самом деле активнее всех в этом процессе не ИИ, а человек, который передает сообщения между моделью и терминалом: решает, когда нужно запускать тесты, читает ошибки, определяет, требуется ли дополнительная коррекция, и постоянно копирует и вставляет информацию туда-сюда. ИИ остается бездействующим до поступления следующей инструкции.

Лучшее решение предполагает отказ от использования человека-курьера. Небольшая система может самостоятельно выполнять цикл создания, тестирования, исправления ошибок и повторения до полного завершения работы, причем человек вмешивается только для окончательного проверки. Здесь используются 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);
        }
    }
    

    Здесь выполняется агент сборки, результат его работы сохраняется, после чего процесс автоматически переходит к следующей задаче (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."
        }
      ]
    }
    

    Затем передайте его агенту, чья единственная задача — диагностика; назовем его агентом отладки, с инструкциями, строго ограничивающими его возможности:

    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 ускоряет процесс написания кода. Такая оркестрация позволяет процессу написания кода работать самостоятельно, и именно здесь эксперименты с ИИ в области программирования становятся интересными.