如果 Skill 执行过程中需要用户确认或补充信息,如何设计 Skill 的断点续执行机制?

面试官:“如果 Skill 执行到一半需要用户确认或补充信息,怎么设计它的断点续执行机制?”

候选人:

“这是个很实际的需求,几乎任何非纯计算 Skill 都会碰到。比如预订机票时要用户确认价格,或者填报销单时缺少发票信息。我设计的核心思路是:把 Skill 执行建模成一个可序列化的状态机,当需要人机交互时,当前状态和上下文全部落盘,等用户响应后再从断点恢复,就像单机游戏存档一样。

我分四层来讲这个机制。

一、Skill 内部的暂停信号

Skill 在需要用户介入时,不能简单地阻塞线程等待,那样会耗尽线程资源。它应该主动抛出一个标准的“需要交互”信号,比如返回一个特殊状态 WAITING_USER_INPUT,或抛出一个 UserInteractionRequiredException,携带结构化的“澄清请求”:

{
  "status": "WAITING_USER_INPUT",
  "prompt": "您希望预订经济舱(¥1200)还是商务舱(¥3500)?",
  "options": ["经济舱", "商务舱"],
  "context_snapshot_id": "task_abc123_step_3"
}

这个信号里最关键的是 context_snapshot_id,它是恢复执行的钥匙。


二、Agent 编排引擎的暂停与持久化

编排引擎捕获到这个信号后,执行三步:

  1. 序列化执行现场:把当前 TaskContext(所有变量的快照)、当前执行到哪个 Skill、该 Skill 内部的“程序计数器”(比如执行到第几个步骤)全部序列化,存到高速存储(Redis 适合临时交互,数据库适合长时等待)。这个存储对象的键就是 context_snapshot_id

  2. 挂起任务并设置超时:该任务进入 SUSPENDED 状态,同时启动一个超时计时器(比如 30 分钟)。如果超时未收到用户响应,自动触发超时策略(取消任务并补偿,或发提醒)。

  3. 向用户发送澄清请求:通过消息通道(WebSocket、短信、邮件等)把 promptoptions 推送给用户,并等待用户选择。


三、恢复执行:从断点继续

当用户做出选择后,系统接收响应,携带之前的 context_snapshot_id

  1. 加载现场:从存储中反序列化 TaskContext 和 Skill 的状态机,重新构建 Skill 实例(或找到已挂起的实例)。

  2. 注入用户输入:把用户的选择(比如“商务舱”)写入上下文,然后让 Skill 从中断的那个点继续执行下一步,而不是从头开始。这就要求 Skill 内部必须是可重入的——它的每个步骤都基于上下文中的状态变量,重复执行相同步骤不会产生副作用(幂等设计)。

  3. 恢复编排:编排引擎重新接管,按 DAG 继续调度后续的 Skill。


四、Skill 内部状态机的实现方式

为了让 Skill 支持这种“暂停-恢复”,我不能把业务逻辑写成一个长长的线性方法,而是要用状态机或分段执行。

比较实用的做法有两种:

  • 基于协程 / 生成器:在支持协程的语言里,Skill 用 yield 暂停并返回澄清请求,恢复时从 yield 后继续。这在 Python 或 Kotlin 里很自然。

  • 显式状态机:把 Skill 的每个阶段定义成独立的 step() 方法,当前执行到哪个步骤记录在上下文中。每次执行时,根据上下文的 current_step 字段,直接跳转到对应步骤继续。这兼容任何语言,但代码会稍多一点。

示例状态结构:

{
  "skill_id": "book_flight",
  "current_step": "confirm_cabin",
  "steps_completed": ["search_flights", "filter_by_time"],
  "variables": {
    "departure": "北京",
    "destination": "上海",
    "date": "2026-06-25",
    "candidates": [...]
  }
}

恢复时,引擎调用 skill.resume(context),Skill 内部根据 current_step 跳到 confirm_cabin 步骤,使用上下文中已有的 candidates,向用户确认,而不会重新查航班。