Zum Inhalt springen

Neuron-Speicher- und Prüfungs-Zustandsmaschine

Dieses Muster eignet sich, wenn ein Agent den Gesprächskontext über mehrere menschliche Prüfrunden hinweg behalten soll, der gesamte Lebenszyklus aber weiterhin von Queuety orchestriert werden soll.

Queuety verwaltet die Prüfungs-Zustandsmaschine, während Neuron den Agentenspeicher für den Schreib-Thread verwaltet.

Warum der Neuron-Speicher hier hilft

Wenn ein Prüfer einen Entwurf ablehnt und Änderungen verlangt, sollte der nächste Agentenaufruf nicht den gesamten Kontext von Grund auf rekonstruieren müssen.

Neuron SQLChatHistory eignet sich dafür gut, weil der Agent im selben Gesprächs-Thread weiterarbeiten kann, während Queuety die Entscheidungen des Prüfers und die Überarbeitungsrunden steuert.

Der Neuron-Agent mit von SQL verwaltetem Verlauf

PHP
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\SQLChatHistory;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\SystemPrompt;

final class EditorAgent extends Agent
{
    public function __construct(
        private readonly string $threadId,
        private readonly \PDO $pdo,
    ) {}

    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: $_ENV['ANTHROPIC_API_KEY'],
            model: $_ENV['ANTHROPIC_MODEL'],
        );
    }

    protected function chatHistory(): ChatHistoryInterface
    {
        return new SQLChatHistory(
            thread_id: $this->threadId,
            pdo: $this->pdo,
            table: 'neuron_chat_history',
            contextWindow: 50000,
        );
    }

    public function instructions(): string
    {
        return (string) new SystemPrompt(
            background: [
                'You are an editorial agent for long-form product briefs.',
                'Apply reviewer notes without losing the original intent.',
            ],
        );
    }
}

Einmal entwerfen, mit demselben Speicher-Thread überarbeiten

PHP
namespace App\StateMachines\Actions;

use App\Neuron\EditorAgent;
use NeuronAI\Chat\Messages\UserMessage;
use Queuety\Contracts\StateAction;

final class DraftBriefAction implements StateAction
{
    public function handle(array $state, ?string $event = null, array $event_payload = [], array $payload = []): array|string
    {
        // Queuety creates actions with `new DraftBriefAction()`, so build dependencies here.
        $pdo = new \PDO($_ENV['NEURON_DB_DSN'], $_ENV['NEURON_DB_USER'], $_ENV['NEURON_DB_PASSWORD']);

        $threadId = $state['neuron_thread_id'] ?? sprintf(
            'brief:%d:editor',
            $state['brief_id'],
        );

        $prompt = "Write the first draft for brief {$state['brief_id']}.";

        if ('reject' === $event) {
            $prompt = sprintf(
                'Revise the draft using these review notes: %s',
                $event_payload['reason'] ?? 'Tighten the draft and address reviewer concerns.',
            );
        }

        $agent = new EditorAgent($threadId, $pdo);
        $message = $agent->chat(
            new UserMessage($prompt)
        )->getMessage();

        return [
            'neuron_thread_id' => $threadId,
            'draft_markdown' => $message->getContent(),
            'review_round' => (int) ($state['review_round'] ?? 0) + 1,
            '_event' => 'draft_ready',
        ];
    }
}

Die Queuety-Zustandsmaschine

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

$machine_id = Queuety::machine('brief_review_session')
    ->version('brief-review-session.v1')
    ->state('drafting')
    ->action(DraftBriefAction::class)
    ->on('draft_ready', 'awaiting_review')
    ->state('awaiting_review')
    ->on('approve', 'completed')
    ->on('reject', 'drafting')
    ->state('completed', StateMachineStatus::Completed)
    ->dispatch([
        'brief_id' => 42,
    ]);

// Later, once the draft action has finished and the machine waits in `awaiting_review`:
Queuety::machine_event(
    $machine_id,
    'reject',
    [ 'reason' => 'Tighten the introduction and add pricing context.' ]
);

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

Der erste Durchlauf wechselt zu drafting, führt die eingereihte Aktion aus und wechselt dann zu awaiting_review.

Lehnt der Prüfer den Entwurf ab, wechselt die Maschine zurück zu drafting. Da die Ereignisdaten von reject an DraftBriefAction übergeben werden, kann der nächste Entwurf die Hinweise des Prüfers verwenden, ohne den ursprünglichen Neuron-Threadverlauf zu verlieren.

Warum das sauberer ist als eine Workflow-Wiederholung

Dieses Problem dreht sich um den Lebenszyklus. Ein Entwurf wartet auf die menschliche Prüfung, ein Prüfer kann ihn freigeben oder ablehnen, bei einer Ablehnung kehrt die Sitzung zum Entwurf zurück und bei einer Freigabe wird sie abgeschlossen. Das lässt sich mit einer Zustandsmaschine direkter abbilden als mit einem linearen Workflow mit Rücksprung.

Der Workflow-Zustand eignet sich weiterhin hervorragend für Orchestrierungsdaten wie die Anzahl der Prüfrunden, Artefakt-IDs, Workflow-IDs und Weiterleitungsentscheidungen. Der Neuron-Speicher eignet sich besser für Gesprächskontext wie vorherige Prompts, iterative Überarbeitungen und Modellantworten, die Teil des Schreib-Threads bleiben sollen.

Wenn du beides verwendest, übernimmt jedes System die Aufgabe, für die es am besten geeignet ist.

Wann du stattdessen einen Workflow verwenden solltest

Bevorzuge einen Workflow, wenn der Prüfpfad klar begrenzt und auf Ausführung ausgerichtet ist, zum Beispiel:

Einen Entwurf erstellen, auf eine Freigabe warten und veröffentlichen.

Bevorzuge eine Zustandsmaschine, wenn die Sitzung dieselben Zustände mehrmals durchlaufen kann und externe Ereignisse den Lebenszyklus steuern.