运行时适配器与统一事件合同
Agent 服务不应把供应商 SDK 响应直接暴露给业务。适配器负责把模型响应映射成内部事件:文本增量、工具调用、工具结果、用量、完成、失败和取消。内部事件保留 run_id、sequence、provider request ID 与原始类型,才能实现去重、重放和排障。平台特有能力通过 capability 声明或显式扩展表达,不能静默丢失。
第一个可运行实验:事件归一化与序号去重
保存为 adapter.py,用 Python 3.12 运行。
python
def normalize(raw):
kind = raw['type']
table = {'text.delta': 'delta', 'response.done': 'done', 'response.error': 'error'}
if kind not in table:
return {'type': 'extension', 'raw_type': kind, 'data': raw}
return {'type': table[kind], 'sequence': raw['sequence'], 'request_id': raw['request_id'],
**({'text': raw['text']} if kind == 'text.delta' else {})}
def consume(events):
seen, out = set(), []
for event in events:
if event['sequence'] in seen:
continue
seen.add(event['sequence'])
out.append(normalize(event))
return out
raw = [
{'type': 'text.delta', 'sequence': 1, 'request_id': 'r1', 'text': 'A'},
{'type': 'text.delta', 'sequence': 1, 'request_id': 'r1', 'text': 'A'},
{'type': 'response.done', 'sequence': 2, 'request_id': 'r1'},
]
assert [e['type'] for e in consume(raw)] == ['delta', 'done']
assert consume([{'type': 'tool.delta', 'sequence': 3, 'request_id': 'r1'}])[0]['type'] == 'extension'
print('adapter contract passed')去重依据应是供应商保证的事件序号或内部组合键,不能靠文本内容比较;两个相同文字片段可能是合法输出。适配器不负责业务成功判定,完成事件还要经过结构化校验、工具状态和权限检查。
变体实验
交换事件到达顺序,要求消费者按序号检测缺口并进入恢复订阅。再加入不同 request ID 的同一序号,要求拒绝混合两次运行。
练习
- 设计
usage、tool_call和cancelled的内部事件形状。 - 区分供应商事件重放与业务重试。
- 列出 capability 矩阵中必须记录的模型、版本、工具和流式字段。