消息、任务、产物与流式恢复
三个 ID 分别管什么
message ID 标识单条消息,task ID 标识一次可跟踪工作,context ID 关联相关交互。复用 context 不等于复用 task;终态任务不能通过再发一句话任意重启。简单 Agent 可以直接返回 Message,复杂任务返回 Task 与后续状态,消费者不能假定每个响应都有任务。
第一章服务按顺序产生 task、working、artifact、completed。artifact 是交付数据,status message 是进度说明;把“正在处理”当作最终产物会造成用户拿到空结果。completed 表示协议任务完成,不保证业务内容正确,还需要业务校验。
观察真实流式顺序
将第一章客户端 ClientConfig(streaming=False) 改为 ClientConfig(streaming=True),再次运行。观察中间状态和最终产物;不要断言网络包数量或精确时间,只检查最终任务完成且内容对应本次输入。服务过快时多项更新可能紧邻到达,不能据此认定没有流式语义。
当连接断开时,先保存已经知道的 task ID,再按 SDK 的 Get Task 能力查询最终快照。订阅与重连是否能重放全部中间事件由版本与服务保证,不能默认持久消息队列语义。推送通知只是唤醒信号,接收方仍需鉴权并查询任务,不能把未经验证的 webhook 内容直接写业务结果。
离线检查状态归并
下面是宿主应用的简化状态规约,不代替完整 A2A 状态机。Python 3.12 保存 state.py:
terminal={'completed','failed','canceled','rejected'}
def advance(current,event):
if current in terminal:raise ValueError('terminal task cannot restart')
if event not in {'working','input-required'}|terminal:raise ValueError('unknown state')
return event
state='submitted'
state=advance(state,'working')
state=advance(state,'input-required')
state=advance(state,'working')
state=advance(state,'completed')
assert state=='completed'
try:advance(state,'working')
except ValueError:pass
else:raise AssertionError('restarted terminal task')
print('state boundary passed')input-required 表示等待补充信息,不是失败;客户端应保持 task/context 身份再补输入。取消请求可能与完成竞争:发送取消不是已取消,实际状态可能仍为 completed。第一章短任务明确不支持取消,不能把该例子当作取消实现。
多模态产物应带媒体类型、数据或受控文件引用,并标明访问期限。artifact 分块更新需按 artifact ID 与追加语义合并;不能每收到一段就覆盖全部文件,也不能跨任务共用一个文本缓冲区。大文件通过受控引用传递,下载仍要检查权限与 URL 范围。
练习
状态走 working→failed 后,再收到 working,应拒绝;working→input-required→working 应允许。运行两个断言,解释为什么网络重放不应复活终态。
先记录自己的输出与解释,再核对本章答案。