Skip to content

事件生命周期与流式提交边界 ​

流式接口把一次响应拆成许多事件,但业务只需要少数稳定状态:进行中、可提交、失败、取消和已过期。展示层可以消费增量文本,提交层必须等待终态和校验完成。连接关闭、收到最后一段文字、收到完成事件是三个不同事实。

第一个可运行实验:终态、取消与迟到事件 ​

使用 Python 3.12 保存 lifecycle.py,运行 python lifecycle.py。实验故意把供应商事件先归一化成内部事件;真实适配器还应保留原始事件名和 provider request ID。

python
from dataclasses import dataclass

@dataclass
class State:
    generation: int
    status: str = 'running'
    text: str = ''

def reduce(state, event):
    if event.get('generation') != state.generation:
        return state
    if state.status != 'running':
        raise ValueError('event after terminal')
    kind = event['type']
    if kind == 'delta':
        state.text += event['text']
    elif kind == 'complete':
        state.status = 'completed'
    elif kind == 'cancel':
        state.status = 'cancelled'
    elif kind == 'failed':
        state.status = 'failed'
    else:
        raise ValueError('unknown event')
    return state

s = State(generation=7)
reduce(s, {'generation': 7, 'type': 'delta', 'text': 'hi'})
reduce(s, {'generation': 7, 'type': 'complete'})
assert (s.status, s.text) == ('completed', 'hi')
assert reduce(s, {'generation': 6, 'type': 'delta', 'text': 'old'}).text == 'hi'
try:
    reduce(s, {'generation': 7, 'type': 'delta', 'text': 'late'})
except ValueError:
    print('terminal lifecycle passed')
else:
    raise AssertionError('late event accepted')

generation 解决的是页面或任务已经切换后的迟到事件,不是幂等键。一次模型调用可以被取消后重试,副作用仍需在工具层另外去重。取消前已经执行的工具也不能被 UI 状态覆盖,应查询工具执行状态或进入补偿流程。

变体实验 ​

把 complete 改成连接关闭信号,观察代码无法区分“正常完成”和“传输中断”。再让 failed 后收到一个带新 generation 的事件:它应该被忽略,因为它属于新的请求;如果复用旧 generation,则应被拒绝。

练习 ​

  1. 增加 usage 事件,只更新统计字段,不能把响应标记为完成。
  2. 增加 tool_call 和 tool_result 状态,要求工具结果必须带匹配的 call ID。
  3. 设计一个 committable(state) 函数:只有 completed 且结构化校验通过时返回真。

用心记录每一步成长