Saltar al contenido

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

PHP
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:

PHP
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

PHP
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

PHP
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

PHP
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.