Neuronのプランナー/エグゼキューター
この例ではオーケストレーションにQueuetyを使い、実際のエージェント実行にはNeuronを使います。
このパターンはシンプルです。Queuetyのプランナーステップが実行する作業を決定し、start_agents()がタスクを独立したトップレベルワークフローに変換します。各子ワークフローでNeuronエージェントを実行し、wait_for_agents()が結果を親ワークフローに集約します。
この分担がうまく機能する理由
Neuronは、コード変更を最小限に抑えてプロバイダーを切り替え、専門エージェント用のプロンプト、ツール、メモリをまとめるのに適しています。Queuetyは、耐久性のあるfor-eachや完了モード、再試行、再開、実行の調査や人によるレビューのための画面を提供するのに適しています。
手順1:プロバイダーを切り替えられるNeuronエージェントを作成する
namespace App\Neuron;
use NeuronAI\Agent\Agent;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\Providers\Gemini\Gemini;
use NeuronAI\Providers\OpenAI\Responses\OpenAIResponses;
use NeuronAI\SystemPrompt;
final class ResearchAgent extends Agent
{
public function __construct(
private readonly string $providerName = 'openai',
) {}
protected function provider(): AIProviderInterface
{
return match ($this->providerName) {
'anthropic' => new Anthropic(
key: $_ENV['ANTHROPIC_API_KEY'],
model: $_ENV['ANTHROPIC_MODEL'],
),
'gemini' => new Gemini(
key: $_ENV['GEMINI_API_KEY'],
model: $_ENV['GEMINI_MODEL'],
),
default => new OpenAIResponses(
key: $_ENV['OPENAI_API_KEY'],
model: $_ENV['OPENAI_MODEL'],
),
};
}
public function instructions(): string
{
return (string) new SystemPrompt(
background: [
'You are a research agent.',
'Return concise, source-aware summaries for the assigned topic.',
],
);
}
}
ここで便利なのは、ワークフロー状態でproviderを渡すことで、実行時にプロバイダーを選べる点です。
手順2:Queuetyのステップ内でNeuronエージェントを実行する
namespace App\Workflow\Steps;
use App\Neuron\ResearchAgent;
use NeuronAI\Chat\Messages\UserMessage;
use Queuety\Step;
final class ResearchTopicStep implements Step
{
public function handle(array $state): array
{
$agent = new ResearchAgent(
providerName: $state['provider'] ?? 'openai',
);
$message = $agent->chat(
new UserMessage(sprintf(
"Research the topic '%s' for brief %d. Return a compact bullet summary.",
$state['topic'],
$state['brief_id'],
))
)->getMessage();
return [
'summary' => $message->getContent(),
];
}
public function config(): array
{
return [];
}
}
ここが重要な統合ポイントです。NeuronはLLMの呼び出しを処理しますが、ステップの境界と永続化されたワークフロー状態はQueuetyが引き続き管理します。
手順3:プランナーからエージェントの実行を開始する
use Queuety\Enums\WaitMode;
use Queuety\Queuety;
$agent_workflow = Queuety::workflow('research_agent_run')
->then(ResearchTopicStep::class);
Queuety::workflow('brief_research')
->version('brief-research.v1')
->then(PlanResearchTasksStep::class) // writes $state['agent_tasks']
->start_agents('agent_tasks', $agent_workflow)
->wait_for_agents(mode: WaitMode::All, result_key: 'agent_results')
->then(SynthesizeBriefStep::class)
->dispatch([
'brief_id' => 42,
'provider' => 'anthropic',
]);
PlanResearchTasksStepが次を返す場合:
[
'agent_tasks' => [
['topic' => 'pricing'],
['topic' => 'customer reviews'],
['topic' => 'recent launches'],
],
]
Queuetyはタスクごとにトップレベルワークフローを1つ作成し、すべての完了を待ってから、完了した子ワークフローの状態を$state['agent_results']の下に渡します。
ループ内でNeuronを呼び出すだけでは不十分な理由
それも可能ですが、オーケストレーションの利点が失われます。
各エージェントの実行状況を個別に調査できなくなり、一部でクラッシュするとバッチ全体が再起動される可能性があります。また、後続段階のための明確な完了地点もなくなります。
このパターンではエージェントコードをシンプルに保ちながら、耐久性のあるオーケストレーションをQueuetyに任せられます。
プランナーが長期間続くエージェントセッションの一部である場合は、このワークフローパターンを実行レイヤーに使い、外側のライフサイクルをステートマシンに管理させます。