Neuron Машина состояний сеанса
Этот подход полезен, когда создаваемая вами система больше похожа на сеанс, чем на однократный запуск.
Машины состояний Queuety управляют жизненным циклом сеанса, рабочие процессы Queuety выполняют фоновые задачи, для которых полезны повторы, режимы завершения циклов for-each и агрегация, а Neuron отвечает за вызовы агента и память диалога.
Такое разделение подходит для помощников службы поддержки, внутренних исследовательских помощников, редакционных агентов, работу которых могут приостановить для проверки человеком, и цепочек чата, переходящих между этапами планирования, ожидания и ответа.
Структура жизненного цикла
В этом примере сеанс поддержки проходит состояния awaiting_user, planning, waiting_on_research и responding. Машина явно управляет жизненным циклом, а фоновый рабочий процесс собирает материалы и сообщает сеансу о завершении.
Машина состояний
use Queuety\Queuety;
$session_id = Queuety::machine( 'support_session' )
->version( 'support-session.v1' )
->state( 'awaiting_user' )
->on( 'user_message', 'planning' )
->state( 'planning' )
->action( DispatchResearchWorkflowAction::class )
->on( 'research_dispatched', 'waiting_on_research' )
->state( 'waiting_on_research' )
->on( 'research_completed', 'responding' )
->state( 'responding' )
->action( RespondWithNeuronAction::class )
->on( 'response_ready', 'awaiting_user' )
->dispatch( [
'thread_id' => 'support:customer-42',
] );
Когда пользователь отправляет сообщение:
Queuety::machine_event(
$session_id,
'user_message',
[
'session_machine_id' => $session_id,
'message' => 'Can you compare our pricing to the main competitors?',
]
);
Машина переходит в состояние planning, ставит действие в очередь и запускает исследовательский рабочий процесс.
Действие планирования: запустить рабочий процесс
namespace App\StateMachines\Actions;
use Queuety\Contracts\StateAction;
use Queuety\Enums\WaitMode;
use Queuety\Queuety;
final class DispatchResearchWorkflowAction implements StateAction
{
public function handle( array $state, ?string $event = null, array $event_payload = [], array $payload = [] ): array|string
{
$message = trim( (string) ( $event_payload['message'] ?? '' ) );
$agent_workflow = Queuety::workflow( 'support_research_agent' )
->then( ResearchTopicStep::class );
$research_workflow = Queuety::workflow( 'support_research_packet' )
->then( PlanResearchTasksStep::class )
->start_agents( 'agent_tasks', $agent_workflow )
->wait_for_agents( mode: WaitMode::All, result_key: 'agent_results' )
->then( SynthesizeResearchPacketStep::class )
->then( NotifySessionResearchReadyStep::class );
$workflow_id = $research_workflow->dispatch( [
'machine_id' => $state['session_machine_id'],
'thread_id' => $state['thread_id'],
'customer_prompt' => $message,
] );
return [
'latest_user_message' => $message,
'research_workflow_id' => $workflow_id,
'_event' => 'research_dispatched',
];
}
}
Это действие состояния не пытается само провести исследование. Оно запускает рабочий процесс и сохраняет его ID в состоянии машины.
Шаг рабочего процесса: сообщить машине о готовности результатов исследования
namespace App\Workflow\Steps;
use Queuety\Queuety;
use Queuety\Step;
final class NotifySessionResearchReadyStep implements Step
{
public function handle( array $state ): array
{
Queuety::machine_event(
$state['machine_id'],
'research_completed',
[
'research_packet' => $state['research_packet'],
]
);
return [];
}
public function config(): array
{
return [];
}
}
После завершения рабочий процесс снова передает research_completed машине и включает подготовленный сводный пакет в данные события.
Действие ответа: вызвать Neuron с памятью сеанса
namespace App\StateMachines\Actions;
use App\Neuron\SupportAgent;
use NeuronAI\Chat\Messages\UserMessage;
use Queuety\Contracts\StateAction;
final class RespondWithNeuronAction implements StateAction
{
public function handle( array $state, ?string $event = null, array $event_payload = [], array $payload = [] ): array|string
{
// Queuety creates actions with `new RespondWithNeuronAction()`, so build dependencies here.
$pdo = new \PDO( $_ENV['NEURON_DB_DSN'], $_ENV['NEURON_DB_USER'], $_ENV['NEURON_DB_PASSWORD'] );
$agent = new SupportAgent(
threadId: $state['thread_id'],
pdo: $pdo,
);
$message = $agent->chat(
new UserMessage(
sprintf(
"Answer the user message '%s' using this research packet:\n\n%s",
$state['latest_user_message'],
json_encode( $state['research_packet'] ?? [], JSON_PRETTY_PRINT )
)
)
)->getMessage();
return [
'assistant_reply' => $message->getContent(),
'_event' => 'response_ready',
];
}
}
После подготовки ответа машина возвращается в awaiting_user, добавляя обновленный ответ в общедоступное состояние. Затем тот же сеанс может принять следующее событие user_message и продолжить работу.
Почему такой подход лучше одного большого рабочего процесса
Основная работа по-прежнему выполняется в рамках рабочего процесса: исследовательская часть требует планирования, запусков специализированных агентов, агрегации и синтеза. А длительный диалог со службой поддержки устроен как машина состояний, поскольку сеансу нужно отслеживать, ожидает ли он сообщения пользователя, результатов исследования или готов к отправке ответа.
Такое разделение делает среду выполнения понятнее, а модель сеанса — явной.