工具调用与对话状态
为什么需要
实时会话同时存在会修订的 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。下一步:断线恢复、超时与终态。