Перейти к содержимому

Конечные автоматы

Конечные автоматы и рабочие процессы решают разные задачи.

Рабочий процесс отвечает на вопрос: «Какая задача выполняется следующей?» Конечный автомат отвечает на вопрос: «В каком состоянии находится объект и какие события допустимы в этом состоянии?» Это различие важно, когда вы создаёте не просто последовательность шагов, а долгоживущую сущность, которая со временем реагирует на внешние события, например сеанс агента, чат, цикл проверки, тикет или процесс согласования.

Быстрый пример

PHP
use Queuety\Enums\StateMachineStatus;
use Queuety\Queuety;

$machine_id = Queuety::machine( 'agent_session' )
    ->version( 'agent-session.v1' )
    ->state( 'awaiting_user' )
    ->on( 'user_message', 'planning' )
    ->state( 'planning' )
    ->action( PlanSessionAction::class )
    ->on( 'planned', 'awaiting_review' )
    ->state( 'awaiting_review' )
    ->on( 'approve', 'completed', ReviewApprovedGuard::class )
    ->on( 'reject', 'awaiting_user' )
    ->state( 'completed', StateMachineStatus::Completed )
    ->dispatch( [ 'thread_id' => 42 ] );

Queuety::machine_event(
    $machine_id,
    'user_message',
    [ 'message' => 'Find competitors for this product' ]
);

Queuety::machine_event(
    $machine_id,
    'approve',
    [ 'approved' => true, 'reviewer' => 'editor@example.com' ]
);

Этот автомат начинает в состоянии awaiting_user, переходит в planning при получении сообщения пользователя, позволяет поставленному в очередь действию отправить planned, а затем ожидает в awaiting_review, пока внешнее согласование не завершит сеанс. Важно не само по себе оформление кода. Важно, что жизненный цикл задан явно, доступен для проверки и управляется событиями.

Как работает автомат

При отправке экземпляра автомата на выполнение Queuety сохраняет имя текущего состояния, публичную полезную нагрузку состояния, версию и хеш определения, допустимые входящие события для текущего состояния и долговечную хронологию событий.

Если для текущего состояния задан action(), Queuety ставит в очередь задание при входе в состояние. Это действие может просто обновить публичное состояние и оставить автомат в ожидании следующего события либо отправить _event и при необходимости _event_payload, чтобы сразу выполнить следующий переход. Если у состояния нет действия и оно не является конечным, автомат просто ждёт внешних событий, отправленных с помощью Queuety::machine_event().

Выбор между рабочими процессами и конечными автоматами

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

Используйте конечный автомат, когда допустимое поведение зависит от текущей фазы жизненного цикла. Он лучше подходит, если события могут приходить в разном порядке, сущность может существовать долго или нужно явно проверять жизненный цикл с состояниями вроде awaiting_user, planning, running_agents, awaiting_review и completed.

Как они взаимодействуют

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

Структура конструктора

StateMachineBuilder намеренно оставлен небольшим. initial() и state() задают граф жизненного цикла. action() привязывает поставленную в очередь задачу к одному состоянию. on() объявляет, какие события могут перевести автомат в следующее состояние. on_queue(), with_priority() и max_attempts() управляют выполнением действий состояний, а version() и idempotency_key() добавляют метаданные для надёжной диспетчеризации. dispatch() создаёт экземпляр автомата.

Действия состояний также могут предоставлять необязательный метод config(), использующий те же ключи политики ресурсов, что и шаги и обработчики рабочих процессов. Это позволяет включить ресурсоёмкий переход состояния в общую группу параллелизма или задать ему больший вес затрат, если действие расходует больше бюджета, чем обычный шаг.

Более широкая модель ресурсов описана в разделе Управление ресурсами.

Контракты

Действия при входе в состояние реализуют Queuety\Contracts\StateAction:

PHP
namespace Queuety\Contracts;

interface StateAction {
    public function handle( array $state, ?string $event = null, array $event_payload = [], array $payload = [] ): array|string;
}

$payload — это структурированная полезная нагрузка из определения действия; она пуста, если действие задано обычным именем класса. Queuety создаёт действия с помощью new $class(), поэтому у класса действия должен быть конструктор без обязательных аргументов.

Действия состояний также могут определять необязательный метод config() с concurrency_group, concurrency_limit и cost_units, если поставленное в очередь действие при входе должно участвовать в допуске к выполнению рабочими процессами, бюджетах взвешенной очереди или провайдера и других политиках ресурсов.

Условия перехода реализуют Queuety\Contracts\StateGuard:

PHP
namespace Queuety\Contracts;

interface StateGuard {
    public function allows( array $state, array $event_payload, string $event, array $payload = [] ): bool;
}

API для проверки состояния

Используйте фасад для проверки выполняющихся автоматов:

PHP
$status   = Queuety::machine_status( $machine_id );
$machines = Queuety::list_machines();
$events   = Queuety::machine_timeline( $machine_id );
$trace    = Queuety::machine_trace( $machine_id );
$state    = Queuety::machine_state_at( $machine_id, $event_id );

Для конечных автоматов нет команд WP-CLI: wp queuety workflow ... предназначен только для рабочих процессов. Используйте эти API PHP, например через wp eval, чтобы проверять автоматы из командной строки.

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

Сеансы агентов

Конечные автоматы особенно полезны для систем агентов, которые больше похожи на сеансы, чем на конвейеры. Сеанс, переходящий между awaiting_user, planning, running_agents, awaiting_review и completed, обычно проще представить как явный жизненный цикл, чем как одно очень длинное определение рабочего процесса со множеством ожиданий и переходов назад.

См. также