任务的生命周期¶
在 Agent2Agent (A2A) 协议中,交互范围从简单的无状态交换到复杂的、长时间运行的流程。当代理从客户端收到消息时,它可以通过两种基本方式进行响应:
- 以无状态
Message响应:此响应类型通常用于即时、自包含的交互,这些交互在不需要进一步状态管理的情况下结束。 - 启动有状态
Task:如果响应是Task,代理将通过定义的生命周期处理它,根据需要传达进度并请求输入,直到它达到中断状态(例如,input-required、auth-required)或终端状态(例如,completed、canceled、rejected、failed)。
分组相关交互¶
contextId 是一个关键标识符,它逻辑上将多个 Task 对象和独立的 Message 对象分组,为一系列交互提供连续性。
- 当客户端第一次发送消息时,代理会使用新的
contextId进行响应。如果启动了任务,它也将拥有一个taskId。 - 客户端可以发送后续消息并包含相同的
contextId,以表明它们正在同一上下文中继续先前的交互。 - 客户端可以选择将
taskId附加到后续消息中,以表明它继续该特定任务。
contextId 实现了针对共同目标或跨多个(可能并发的)任务的共享上下文会话的协作。在内部,A2A 代理(尤其是使用 LLM 的代理)使用 contextId 来管理其内部对话状态或其 LLM 上下文。
代理响应:消息或任务¶
选择以 Message 还是 Task 响应取决于交互的性质和代理的能力
- 用于琐碎交互的消息:
Message对象适用于不需要长时间运行的处理或复杂状态管理的事务性交互。代理可以使用消息在提交Task对象之前协商任务的接受或范围。 - 用于有状态交互的任务:一旦代理将传入消息的意图映射到需要长时间、可跟踪工作并支持的某个能力时,代理将以
Task对象响应。
从概念上讲,代理在不同的复杂性级别上运行
- 仅消息代理:始终使用
Message对象响应。它们通常不管理复杂状态或长时间运行的执行,并使用contextId将消息关联起来。这些代理可能直接包装 LLM 调用和简单工具。 - 任务生成代理:始终使用
Task对象响应,即使是响应,也会将其建模为已完成的任务。一旦创建了任务,代理将只返回Task对象以响应发送的消息,一旦任务完成,就不能再发送消息。这种方法避免了在Task和Message之间进行决策,但即使是简单的交互也会创建已完成的任务对象。 - 混合代理:生成
Message和Task对象。这些代理使用消息来协商代理能力和任务的工作范围,然后发送Task对象来跟踪执行和管理input-required或错误处理等状态。一旦创建了任务,代理将只返回Task对象以响应发送的消息,一旦任务完成,就不能再发送消息。混合代理使用消息来协商任务的范围,然后生成一个任务来跟踪其执行。有关混合代理的更多信息,请参阅 A2A 协议:揭秘任务与消息。
任务细化¶
客户端通常需要根据任务结果发送新请求或细化先前任务的输出。这通过使用与原始任务相同的 contextId 启动另一个交互来建模。客户端通过在 Message 对象中使用 referenceTaskIds 提供对原始任务的引用来进一步提示代理。然后代理以新的 Task 或 Message 响应。
任务不可变性¶
一旦任务达到终端状态(已完成、已取消、已拒绝或失败),它就不能重新启动。任何与该任务相关的后续交互(例如细化)都必须在相同的 contextId 内启动新任务。此原则具有以下几个优点
- 任务不可变性。 客户端可以可靠地引用任务及其相关的状态、工件和消息,从而提供输入到输出的清晰映射。这对于编排和可追溯性很有价值。
- 清晰的工作单元。 每个新请求、细化或跟进都成为一个独立的任务。这简化了簿记,允许对代理的工作进行精细跟踪,并能够将每个工件追溯到特定的工作单元。
- 更简单的实现。 这消除了代理开发人员在创建新任务还是重新启动现有任务方面的歧义。
并行跟进¶
A2A 通过使代理能够为在相同 contextId 内发送的每条跟进消息创建独立、并行的任务来支持并行工作。这允许客户端跟踪单个任务,并在先决任务完成后立即创建新的依赖任务。
例如
- 任务 1:预订飞往赫尔辛基的航班。
- 任务 2:根据任务 1,预订酒店。
- 任务 3:根据任务 1,预订雪地摩托活动。
- 任务 4:根据任务 2,为酒店预订添加水疗预订。
引用先前的工件¶
服务代理从引用的任务或 contextId 推断出相关工件。作为领域专家,服务代理最适合解决歧义或识别缺失信息。如果存在歧义,代理会通过返回 input-required 状态来要求客户端澄清。然后客户端在其响应中指定工件,可选择在 Part 元数据中填充工件引用(artifactId、taskId)。
跟踪工件变异¶
跟进或细化任务通常会导致基于旧工件创建新工件。跟踪这些变异对于确保在后续交互中仅使用工件的最新版本很重要。这可以概念化为版本历史,其中每个新工件都与其前身链接。
但是,客户端最适合管理此工件链接。客户端确定什么构成可接受的结果,并有能力接受或拒绝新版本。因此,服务代理不应负责跟踪工件变异,并且此链接不属于 A2A 协议规范。客户端应在其端维护此版本历史记录,并向用户呈现最新的可接受版本。
为了便于客户端跟踪,服务代理在生成现有工件的细化版本时应使用一致的 artifact-name。
在启动跟进或细化任务时,客户端应明确引用他们打算细化的特定工件,理想情况下是从其角度来看的“最新”版本。如果未提供工件引用,服务代理可以
- 尝试根据当前
contextId推断目标工件。 - 如果存在歧义或上下文不足,代理应返回
input-required任务状态,以请求客户端澄清。
示例跟进场景¶
以下示例说明了典型的带跟进的任务流
-
客户端向代理发送消息
-
代理响应一艘船的图像(已完成的任务)
{ "jsonrpc": "2.0", "id": "req-001", "result": { "id": "task-boat-gen-123", "contextId": "ctx-conversation-abc", "status": { "state": "completed" }, "artifacts": [ { "artifactId": "artifact-boat-v1-xyz", "name": "sailboat_image.png", "description": "A generated image of a sailboat on the ocean.", "parts": [ { "file": { "name": "sailboat_image.png", "mediaType": "image/png", "fileWithBytes": "base64_encoded_png_data_of_a_sailboat" } } ] } ] } } -
客户端要求将船涂成红色。此细化请求引用了先前的
taskId并使用了相同的contextId。 -
代理响应一个新的图像工件(新任务、相同上下文、相同工件名称):代理在相同的
contextId中创建一个新任务。新的船图像工件保留相同的名称,但具有新的artifactId。{ "jsonrpc": "2.0", "id": "req-002", "result": { "id": "task-boat-color-456", "contextId": "ctx-conversation-abc", "status": { "state": "completed" }, "artifacts": [ { "artifactId": "artifact-boat-v2-red-pqr", "name": "sailboat_image.png", "description": "A generated image of a red sailboat on the ocean.", "parts": [ { "file": { "name": "sailboat_image.png", "mediaType": "image/png", "fileWithBytes": "base64_encoded_png_data_of_a_RED_sailboat" } } ] } ] } }