Máquina de estados de sesión Neuron
Este patrón es útil cuando lo que estás creando se comporta más como una sesión que como una ejecución puntual.
Las máquinas de estados Queuety gestionan el ciclo de vida de la sesión, los flujos de trabajo Queuety realizan las tareas en segundo plano que se benefician de los reintentos, los modos de finalización para cada elemento y la agregación, y Neuron gestiona las llamadas al agente y la memoria de conversación.
Esta separación encaja bien con asistentes de soporte, asistentes de investigación internos, agentes editoriales que pueden pausar para una revisión humana y conversaciones de chat que pasan por las fases de planificación, espera y respuesta.
Estructura del ciclo de vida
En este ejemplo, una sesión de soporte pasa por awaiting_user, planning, waiting_on_research y responding. La máquina mantiene explícito ese ciclo de vida, mientras un flujo de trabajo en segundo plano reúne información y avisa a la sesión cuando termina.
La máquina de estados
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',
] );
Cuando un usuario envía un mensaje:
Queuety::machine_event(
$session_id,
'user_message',
[
'session_machine_id' => $session_id,
'message' => 'Can you compare our pricing to the main competitors?',
]
);
La máquina pasa a planning, pone en cola su acción e inicia el flujo de investigación.
Acción de planificación: iniciar el flujo de trabajo
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',
];
}
}
Esta acción de estado no intenta realizar la investigación. Inicia un flujo de trabajo y registra su ID en el estado de la máquina.
Paso del flujo de trabajo: avisar a la máquina cuando la investigación esté lista
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 [];
}
}
Cuando termina el flujo de trabajo, envía research_completed a la máquina e incluye el paquete sintetizado en la carga útil del evento.
Acción de respuesta: llamar a Neuron con la memoria de la sesión
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',
];
}
}
Una vez generada la respuesta, la máquina vuelve a awaiting_user con la respuesta actualizada en el estado público. La misma sesión puede aceptar entonces otro evento user_message y continuar.
Por qué esto es más claro que un único flujo de trabajo grande
Las tareas pesadas siguen teniendo forma de flujo de trabajo porque la investigación requiere planificación, ejecuciones especializadas, agregación y síntesis. La conversación de soporte de larga duración tiene forma de máquina de estados porque la sesión debe registrar si espera un mensaje del usuario, espera los resultados de la investigación o puede emitir una respuesta.
Esta separación hace que el código de ejecución sea fácil de leer y que el modelo de sesión quede explícito.