事件契约
事件契约定义事件格式、业务语义、顺序、终态和兼容规则,以 AI 应用事件流为例。
#concept
#evergreen
#ai
#architecture
[!info] related notes
- 所属 MOC: AI Agent Application MOC, LLM Streaming MOC
- 格式与载体: Event Envelope, SSE, AI 应用流式协议
- 演进: Schema Versioning, Protocol Versioning
- 实践: LLM Streaming 协议设计, Event Reducer
事件契约
事件契约约定生产者发出的事件如何被消费者解释:不仅包含字段和类型,还包括业务含义、顺序、重复、错误、终态与兼容规则。它不限定 React、Go、Python 或三层部署。
AI 事件目录示例
这些名称沿用原笔记作为设计例子,不代表现有项目已实现同一套协议:
| 事件 | 应明确的语义 |
|---|---|
text_delta / text_done | 文本增量和该文本段完成;文本完成是否等于整个 run 完成需区分 |
tool_call_start / tool_call_delta | 是模型提出调用、参数生成,还是实际执行已开始;参数片段需累积后校验 |
tool_call_result | 执行结果与失败如何表达,用什么调用 ID 关联 |
progress / state_patch | 进度单位、补丁目标版本、重复应用与乱序处理 |
interrupt | 需要补充信息或审批;应关联具体待决操作,而不是展示后就视为获准 |
error | 可恢复错误还是运行终止,消费者接下来允许做什么 |
done | 明确的运行终态及结果,不用连接关闭代替 |
heartbeat | 连接存活信号,不证明业务仍有进展或最终成功 |
审批事件只提出请求;授权和实际执行仍由有权限的后端核验,不能由模型输出或前端按钮自行确立。
格式、身份与时间
统一包装见 event-envelope,不要在多个笔记重复维护不一致的 JSON/TypeScript 定义。
契约需说明事件名与 payload 的关联、标识符范围、时间戳单位以及 sequence 的分配者。step_id 对运行级事件可能没有意义,不能不加区分地要求每个事件都有它。
工具结果的失败不能统一渲染成“done = 成功”;结果未知或等待确认也要与明确失败区分。文本增量去重后再追加,避免重连回放重复显示。
SSE 转发边界
上游响应
→ 检查 HTTP 状态和媒体类型
→ 按 SSE 空行边界解帧,合并多行 data
→ 按事件契约验证 payload 与元数据
→ 按约定记录 / 转发
→ 消费者校验、去重并更新状态
SSE 不是“一行一个 JSON”。直接逐行调用 JSON parser 会丢失多行 data、id、retry 和注释语义。若透明代理不需要解释事件,可以按协议要求转发字节,而不自行重建事件。
不能把无效关键事件静默跳过后报告成功。解析失败、上游异常和客户端断连都应有明确处理方式;响应开始前后的错误表达也不同。转发层不应无依据地覆盖生产者时间戳或序号。
顺序、恢复与合并
- 单条 SSE/TCP 连接内字节有序;业务乱序可能来自多生产者、异步处理或跨连接重放,不能简单归因为 TCP 网络抖动。
- sequence 可用于检测重复或间隙,但不能自动修复缺失;恢复还需要事件留存、游标与重放规则。
- 心跳可帮助避免部分空闲超时,不能保证所有代理都不断线。
- 在同一条 SSE 响应内合并多个 delta 减少的是事件处理、刷新或传输开销,不是 HTTP 请求数量。合并时需保留语义、顺序与恢复边界。
验证范围
至少用对应场景覆盖多行 SSE、未知版本、未知关键事件、重复、间隙、工具失败、等待审批和无终态断线。协议文档、生产者、代理与消费者的行为应一致;静态类型检查不能证明网络事件已经符合契约。