跳转到正文

Neuron 会话状态机

当你构建的系统更像一个会话,而不是一次性运行时,这种模式很有用。

Queuety 状态机管理会话生命周期,Queuety 工作流负责后台任务,并支持重试、for-each 完成模式和结果汇总,而 Neuron 则处理实际的代理调用和对话记忆。

这种职责划分适用于支持型 Copilot、内部研究助理、可能暂停等待人工审核的编辑代理,以及在规划、等待和响应阶段之间切换的聊天线程。

生命周期结构

在此示例中,一个支持会话依次经过 awaiting_user、planning、waiting_on_research 和 responding。状态机明确管理这个生命周期,而后台工作流负责收集研究资料,并在完成时向会话发送信号。

状态机

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',
    ] );

用户发送消息时:

PHP
Queuety::machine_event(
    $session_id,
    'user_message',
    [
        'session_machine_id' => $session_id,
        'message' => 'Can you compare our pricing to the main competitors?',
    ]
);

状态机转换到 planning,将操作加入队列,并启动研究工作流。

规划操作:调度工作流

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',
        ];
    }
}

这个状态操作不会尝试自行开展研究,而是调度一个工作流,并将工作流 ID 记录在状态机状态中。

工作流步骤:研究就绪时向状态机发送信号

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 [];
    }
}

工作流完成后,会将 research_completed 发送回状态机,并将综合整理后的资料包作为事件载荷一并传入。

响应操作:使用会话记忆调用 Neuron

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',
        ];
    }
}

生成响应后,状态机带着更新后的回复公开状态转换回 awaiting_user。同一会话随后可以接受另一个 user_message 事件并继续。

为什么这比一个大型工作流更清晰

繁重任务仍然由工作流承担,因为研究部分需要规划、专业代理运行、汇总和综合整理。长期运行的支持对话则由状态机管理,因为会话必须跟踪自己是在等待用户发言、等待研究结果,还是可以发出响应。

这种职责划分让运行时逻辑更易读,也让会话模型更加明确。