代理编排
当你希望代理系统能够自适应执行任务,同时保留持久化状态、重试、检查能力和明确的等待语义时,Queuety就很适合。
对于大多数智能体流程,你不需要可在运行时编辑的 DAG 引擎。实际上,大多数系统都可以用一小组持久化编排操作来建模。for_each() 在单个工作流运行中扩展工作。start_agents() 和 wait_for_agents() 将运行时发现的工作交给独立的顶层工作流。wait_for_decision() 和 wait_for_input() 暂停以等待人工控制。wait_for_workflow()、wait_for_workflows() 和 wait_for_agent_group() 让一个顶层运行等待另一个运行,或等待已启动的一组工作流达到指定数量。
一个实用的思维模型
分层思考智能体编排最容易理解。规划器决定有哪些工作。Queuety 将这些工作转化为持久化任务或工作流。专业执行运行产出结果并存储制品。监督阶段汇总这些结果,并决定下一步做什么。在任何时候,人工都可以在运行继续之前批准、拒绝或纠正它。
重要区别在于,智能体决定要做什么工作,而 Queuety 仍决定如何持久化、重试、恢复和汇总这些工作。当编排对象是长期会话而不是有界运行时,可搭配状态机使用。
模式 1:规划器/执行器与独立的智能体运行
当规划步骤在运行时发现任务,且每个任务都应成为独立的顶层工作流时,使用 start_agents()。
use Queuety\Enums\WaitMode;
use Queuety\Queuety;
$agent_run = Queuety::workflow( 'agent_run' )
->then( FetchSourcesStep::class )
->then( DraftFindingStep::class );
Queuety::workflow( 'brief_research' )
->version( 'brief-research.v1' )
->idempotency_key( 'brief:42:research' )
->max_transitions( 20 )
->then( PlanResearchTasksStep::class )
->start_agents( 'agent_tasks', $agent_run, group_key: 'researchers' )
->wait_for_agent_group( 'researchers', WaitMode::Quorum, 2, 'agent_results' )
->then( SynthesizeBriefStep::class )
->dispatch( [ 'brief_id' => 42 ] );
在此模式中,PlanResearchTasksStep 将运行时发现的项目写入 $state['agent_tasks'],start_agents() 为每项任务分派一个持久化工作流,而 wait_for_agent_group() 会暂停父工作流,直到满足所选完成条件。随后,SynthesizeBriefStep 会在 $state['agent_results'] 下接收已完成的子工作流状态。
这是规划器/执行器系统最简洁的起点,因为每次智能体运行都可以单独检查和重试。
模式 2:运行继续前进行人工审核
当工作流应停下来等待明确的“是”或“否”决定时,使用 wait_for_decision();当操作员需要将结构化反馈发送回运行中时,使用 wait_for_input()。
use Queuety\Queuety;
$workflow_id = Queuety::workflow( 'brief_review' )
->then( DraftBriefStep::class )
->wait_for_decision( result_key: 'review' )
->then( HandleReviewDecisionStep::class )
->wait_for_input( result_key: 'revision_notes' )
->then( ApplyRevisionNotesStep::class )
->dispatch( [ 'brief_id' => 42 ] );
如果审核者批准:
Queuety::approve_workflow(
$workflow_id,
[
'reviewer' => 'editor@example.com',
'notes' => 'Looks good, publish after legal check',
],
'approved'
);
如果审核者拒绝:
Queuety::reject_workflow(
$workflow_id,
[
'reviewer' => 'editor@example.com',
'reason' => 'Needs citations and a stronger conclusion',
],
'rejected'
);
在 wait_for_decision() 之后,工作流会收到一个结构化负载,其中包含最终结果、恢复运行的信号,以及随附的任何审核者数据。这样就能实现真正的人工参与检查点,同时无需在运行等待期间保持 PHP 进程处于打开状态。
模式 3:一个顶层工作流等待另一个工作流
有时,最简洁的建模方式不是创建一个庞大的工作流。一个工作流可以完成研究,另一个工作流则可以等待它完成后再开始下一阶段。
use Queuety\Enums\WaitMode;
use Queuety\Queuety;
$research_id = Queuety::workflow( 'research_brief' )
->then( PlanResearchTasksStep::class )
->start_agents( 'agent_tasks', $agent_run )
->wait_for_agents( mode: WaitMode::All, result_key: 'agent_results' )
->then( SynthesizeBriefStep::class )
->dispatch( [ 'brief_id' => 42 ] );
Queuety::workflow( 'publish_brief' )
->wait_for_workflow( $research_id, 'research' )
->wait_for_decision( result_key: 'editor_review' )
->then( PublishBriefStep::class )
->dispatch( [ 'brief_id' => 42 ] );
这样就在研究阶段和发布阶段之间建立了持久化边界。每次运行都有自己的运维生命周期,但第二个工作流仍会在依赖项完成后自动恢复。
选择合适的基本操作
当工作属于单个工作流运行,并且分支实际上只是在共享状态集合上并行执行步骤时,for_each() 是合适的工具。
当每个发现的任务都应成为独立的持久化顶层运行,并且你希望分别检查、重试或导出这些运行时,start_agents() 更合适。
当一个顶层工作流依赖另一个顶层工作流,并且你希望在这些编排层之间实现持久化异步协调时,wait_for_workflow() 和 wait_for_workflows() 最合适。
当工作流暂停期间需要人工批准、拒绝或引导运行,且不占用工作进程容量时,应在相应位置使用 wait_for_decision() 和 wait_for_input()。
建议的状态约定
状态键保持可预测,智能体系统就更容易调试。实际上,agent_tasks 适合存放规划器输出,agent_workflow_ids 适合存放已启动的顶层运行,agent_results 适合存放已完成的子工作流状态,而 review 或 decision 适合存放批准或拒绝负载。草稿、引文或研究清单等较大的输出通常应存放在制品存储中,而不是主工作流的状态集合里。
智能体运行的建议护栏
智能体工作流本质上具有动态性,因此比几乎任何其他用例都更需要护栏。
Queuety::workflow( 'agent_research' )
->version( 'agent-research.v2' )
->idempotency_key( 'brief:42:agent-research' )
->max_transitions( 20 )
->max_for_each_items( 12 )
->max_state_bytes( 32768 );
这些控制措施回答了生产环境中最实际的问题:哪个定义版本生成了某次运行、重复的外部请求是否会导致重复分派、规划器是否会创建过多分支,以及工作流状态是否会无限增长。对于大多数智能体系统,应先设置这些护栏,再调整工作进程数量或队列拓扑。