工具调用与对话状态

为什么需要

实时会话同时存在会修订的 transcript、正在播放的 response、工具副作用和长期业务状态。若都塞进一份聊天历史,用户一句打断可能删除已经生效的预约,也可能让迟到工具结果被错误接到下一轮。

怎样实现

分开事实与视图

层例子权威性
媒体/识别视图audio frame、partial/final transcript、时间戳observation,可修订、不可信
会话事件turn committed、response canceled、timeout由 runtime 按合同迁移
工具账本intent digest、idempotency key、approval、receipt副作用事实源
业务状态订单、预约、余额以目标系统查询结果为准
模型 context摘要、最近 turn、工具说明临时视图,不能覆盖上述事实

工具事件至少绑定 call_id + response_id + turn_id;写操作再绑定用户/租户、参数摘要、权限、审批版本、idempotency key 和 deadline。结果必须回到原 call_id,未知 call 或不匹配 response 直接拒绝。

生产执行账本与离线事件不是同一份 envelope

模型提出 tool.call 只是候选意图,不能携带或自行生成执行授权。真正的执行边界应把可审计 receipt 记到自己的账本,例如下列应用内部记录;它不是任一供应商的 wire event,也不是本课模拟器直接接受的 JSON:

{ // 实时轮次中一次有副作用工具调用的可恢复回执
  "kind": "tool_execution_receipt", // 明确这是控制面回执,不是要直接读给用户的自然语言回复
  "turn_id": "t-7", // 绑定触发调用的用户轮次,打断后仍可审计发生过什么
  "response_id": "r-7", // 绑定 assistant 当前响应,避免其他响应误复用结果
  "call_id": "c-2", // 单次 tool call 的稳定关联 ID,用于结果回填与去重
  "status": "committed", // 下游副作用已确认;恢复时应查 receipt 而不是重新提交
  "intent_digest": "sha256:...", // 绑定规范化工具、参数和目标,防止同 key 被调包
  "idempotency_key": "booking:t-7:c-2", // 重试时复用,避免重复预订等外部副作用
  "authorization_ref": "grant:booking-write-v4", // 指向已核验授权记录,不把 token 写入会话状态
  "receipt_ref": "opaque-receipt-id" // 外部系统回执引用;详情应按权限受控查询
}

JSONC 教学表示

行尾说明不属于真实事件;提交到严格 JSON API 前请删除 // 内容。

examples/realtime_session.py 只实现为确定性回归服务的离线关联核心。它接受的 tool.result 是下列简化事件,runtime 从已有 response_id 账本推导 turn_id:

{ // 从工具 adapter 回到会话状态机的结果事件
  "event_id": "e-42", // 唯一事件 ID,客户端重连/重放时可去重
  "type": "tool.result", // 指示 consumer 将 payload 交给工具结果处理分支
  "at_ms": 420, // 记录相对时间,便于计算工具等待对实时体验的影响
  "payload": { // 不可信业务结果与会话控制字段分区保存
    "call_id": "c-2", // 必须匹配先前已批准/已派发的工具调用
    "response_id": "r-7", // 防止迟到结果污染另一条 assistant 响应
    "ok": true, // 只表示 adapter 成功获得结果,不自动代表用户任务完成
    "result": "synthetic-available" // 教学 fixture 的业务数据;真实结果仍按来源和 schema 处理
  } // 结束工具结果 payload
}

这里的 result 不是授权决定、幂等 receipt 或目标系统事实;模拟器不能证明实际工具执行、审批、token 处理或 receipt 对账。生产 adapter 应在调用前重验授权/审批,在调用后写入上面的受保护账本,并把仅含必要关联的结果事件交给会话 runtime。

打断只取消尚未成为业务事实的生成/播放。工具可能处于 not_started、pending、committed、failed 或 unknown;对 unknown 先按 key/receipt 查询,不盲目重试,也不向用户声称已回滚。模型发起的取消请求仍由 runtime 根据工具能力和业务规则执行。

对话摘要可以压缩旧 turn,但要保留未决 call、审批、承诺、用户纠正和来源指针。partial transcript 只能驱动 UI 预览或低风险预测,产生副作用前使用 committed turn 或明确确认。

常见失败

  • 用自然语言“刚才已经取消”代替查询真实工具状态。
  • 把 transcript revision 当新 turn,重复创建工具调用。
  • call ID 只在内存中;进程重启后迟到结果无法关联。
  • 用户打断后重用 response ID,旧工具/音频混入新回答。
  • 将工具返回的恶意文本直接当系统指令或权威事实。

怎样验证

为每个写工具测试:同 key/同参数重复、同 key/异参数冲突、commit 前 crash、commit 后 receipt 前 crash、打断与结果同时到达、迟到结果、审批过期。grader 查询最终业务状态和副作用次数,不能只看模型说了什么。

实践任务

为“语音预约”定义 turn/response/call 三类 ID 和五种工具状态。写出用户在工具 pending 时说“算了”的事件序列,并分别处理服务端返回 not-started、committed 和 unknown。

依据与下一步

实时产品的工具事件形状会变化;OpenAI Realtime with tools与 Google Live tool use只用于观察具体实现,核心仍是 Tool Calling 与 恢复合同。动态页面核验于 2026-07-18。下一步:断线恢复、超时与终态。