跳到主要内容

状态机

智能体通常从一个循环开始:调用模型,运行它请求的工具,然后重复。当应用还需要审批、重试、压缩、外部事件,或者停止后再恢复的能力时,这种方式就不够了。这些需求需要在执行顺序中有自己的位置,而不是在提示词里多写几条指令。

GDK 把执行表示为一个有序的步骤列表。你用操作和推理步骤组合出一个 StateMachine。要改变智能体,就改变这个组合或编写新的操作,而不是扩展状态机本身。

执行循环​

每一轮处理中,状态机会:

  1. 通过你的 SessionLoader 加载会话。
  2. 按顺序尝试各个步骤,直到有一个适用。
  3. 把该步骤的效果交给你的 EffectHandler。
  4. 带着更新后的会话重新开始,或把控制权交还给调用方。

一个步骤可以拒绝、应用效果后继续,或应用效果后让出。如果没有步骤适用,状态机就会让出。步骤不是程序计数器:下一轮从列表顶部开始,而不是从后一个步骤开始。

The state machine execution cycleLoad the session and try steps in order. Apply the first applicable step's effects, then either reload and start again or yield to the caller. If no step applies, yield directly.Load sessionSessionLoaderTry steps in orderOperations + inferenceApply returned effectsEffectHandlerContinue?Return to callerFirst applicable stepYesYieldNo step appliesReload; start at the first step

一个简单的组合如下所示。tools 和 inference 是由你的应用配置的实例,使用相同的会话和效果类型:

let machine = StateMachine::new(
vec![
Step::Operation(Arc::new(tools)),
Step::Inference(Arc::new(inference)),
],
cancel,
);

用户发来消息后,工具操作无事可做,于是运行推理。如果模型请求某个工具,这个请求就会成为对话的一部分。下一轮中,工具操作执行它,并把结果作为效果返回。再下一轮到达推理时,上下文中已经包含这个结果。当模型完成且没有更多工作时,推理步骤拒绝,状态机把控制权交还给应用。

推理之前,状态机还会向各个操作询问它们的工具定义和提示词贡献。因此,即使某个组件的 run 方法无事可做,它也能影响模型请求。

状态属于应用​

MachineSession 公开会话 ID 和对话。你的会话类型还可以保存应用数据,例如工作目录、审批状态或业务记录。效果描述要应用的更改:追加一条消息、替换压缩后的历史,或更新应用特有的状态。

状态机不规定使用哪种数据库。你针对文件、数据库或内存存储来实现加载和应用效果。Emitter 事件是另一回事:它们让客户端在工作进行时显示输出,但显示事件并不会持久化其效果。

由于决策是根据已加载的状态做出的,全新的状态机可以接着已保存的对话继续,而不必记住自己当时处于循环的哪个分支。操作必须记录对后续运行有影响的决策,而不是只把它们保存在局部变量里。Hello World 的会话存储展示了这条边界的文件存储版本。

让出与恢复​

让出意味着把控制权交还给调用方。它可能表示智能体已经完成、需要人工审批,或必须等待外部事件。它不会启动后台工作线程,也不会自动安排之后的运行。

你的应用持久化新的输入或决定,构造合适的操作,然后再次调用状态机。例如,审批操作可以在工具请求等待决定时让出,待决定保存之后再允许执行。它必须出现在任何会执行未获批请求或越过它继续推理的步骤之前。

仅靠持久化并不能让外部动作做到恰好执行一次。如果进程在付款或发送消息之后、保存结果之前停止,应用仍然需要有办法对账。工作流需要时,请保存标识符并使用幂等的外部 API。

运行、单步和停止​

用 run 让状态机加载、执行步骤并应用,直到让出或被取消:

let session = machine.run(&runtime, &session_id, &emitter).await?;

需要更精细控制的应用可以自己驱动 step 和 apply。返回的 StepResult 会标明应用了哪个步骤,以及运行是继续、已让出还是已取消。自己调用这些方法的调用方,在离开运行时还必须调用 finalize。

取消会丢弃正在运行的步骤的 future,然后让各个操作有机会保留已收到的输出,并收尾被中断的工具响应。run 会在返回前处理这部分收尾。这无法撤销外部副作用,也不能保证在异步任务之外启动的工作已经停止。

接下来,请阅读操作,了解如何把策略组合进这个循环。完整 API 见 goose-agent::machine。