Agent2Agent 入門 (9) - タスクの寿命
「Agent2Agent」の「タスクの寿命」についてまとめました。
前回
1. はじめに
エージェントにメッセージが送信されると、エージェントは以下のいずれかで応答できます。
・状態のないメッセージ。
・状態のあるタスクと、0個以上のTaskStatusUpdateEventまたはTaskArtifactUpdateEvent。
応答がメッセージの場合、インタラクションは完了します。一方、タスクは中断状態 (input-required or auth-required) または終了状態 (completed, cancelled, rejected or failed) になるまで更新し続けます。
2. コンテキスト
contextId は、多数のタスクと独立したメッセージを論理的に構成します。エージェント実装者は、contextId を利用して、これらのインタラクションに基づいて llm コンテキストを管理できます。
メッセージが初めて送信されると、エージェントは contextId を返信します。エージェントがタスクで応答した場合は、taskId も付与されます。クライアントは、後続のメッセージを送信する際に同じ contextID を添付することで、同じコンテキスト内で前回のインタラクションを継続していることをエージェントに通知できます。また、クライアントはオプションで taskID を添付することで、メッセージが特定のタスクの継続であることを示すことができます。
contextId により、目標達成に向けたコラボレーションや、複数のタスク間で 1 つのセッションを共有することが可能になります。
3. エージェント: メッセージまたはタスク
メッセージは、長時間の処理や連携を必要としない、些細なやり取りに使用できます。エージェントはメッセージを使用してタスクの受諾を交渉できます。エージェントは、受信メッセージの意図をサポート対象の機能にマッピングすると、タスクで応答できます。
したがって、概念的には、エージェントには3つのレベルがあります。
(1) 常にメッセージのみで応答
複雑な状態管理や長時間実行は行わず、contextIDを使用してメッセージを結び付けます。エージェントは、LLM呼び出しとシンプルなツールを直接ラップする可能性が高いです。
(2) タスクを生成し、追跡可能なより重要な作業を行い、長期間にわたって実行
(3) メッセージとタスクを生成
メッセージを使用して、エージェントの機能とタスクの作業範囲をネゴシエートします。その後、タスクを送信して実行を追跡し、追加の入力が必要、エラー処理など、タスクの状態について連携します。
エージェントは、常にタスク オブジェクトで返信し、単純な応答を完了状態のタスクとしてモデル化することを選択できます。
4. タスクの改良とフォローアップ
4-1. タスクの改良とフォローアップ
クライアントは、タスクの結果に基づいて新たな質問でフォローアップしたり、タスクの結果を改良したりしたい場合があります。これは、元のタスクと同じコンテキストIDを使用して別のインタラクションを開始することでモデル化できます。クライアントは、MessageオブジェクトのreferenceTaskIdsを使用して元のタスクへの参照を提供することで、エージェントにさらにヒントを与えることができます。エージェントは、新しいタスクまたはメッセージを作成します。
タスクが終了状態 (completed, cancelled, rejected or failed) に達すると、再開することはできません。これにはいくつかの利点があります。
・タスクの不変性
クライアントは、タスクとその関連状態、成果物、メッセージを確実に参照できます。これにより、入力と出力の明確なマッピングが実現します。クライアントプランナーノードをタスク実行にマッピングするのに役立ちます。
・明確な作業単位
すべての新規リクエスト、リファインメント、またはフォローアップは個別のタスクとなり、記帳を簡素化し、エージェントの作業をきめ細かく追跡できます。
・各成果物は、ユニットタスクにトレースできます。
・この作業単位は、親エージェントやエージェントオプティマイザーなどの他のシステムから、よりきめ細かく参照できます。リスタート可能なタスクでは、後続のリファインメントがすべてまとめて処理されるため、何らかのメッセージインデックス範囲に頼る必要が生じます。
・より容易な実装
エージェント開発者は、ターミナル状態のタスクを参照するリクエストに対しては、常に新しいタスクを作成するというシンプルなルールに従います。
4-2. 並行フォローアップ
エージェントが同じコンテキストID内で送信されたフォローアップメッセージごとに、個別の並行タスクを作成することで、並行作業がサポートされます。これにより、クライアントは個々のサブタスクを追跡し、前提条件となるタスクが完了するとすぐに新しい依存タスクを作成できます。
・例
Task 1: Book a flight to Helsinki.
(After Task 1 finishes)
Task 2: Based on Task 1, book a hotel.
Task 3: Based on Task 1, book a snowmobile activity.
(After Task 2 finishes, while Task 3 is still in progress)
Task 4: Based on Task 2, add a spa reservation to the hotel booking.4-3. 以前のアーティファクトの参照
エージェントは、参照先のタスクまたはコンテキストIDから関連するアーティファクトを推論する役割を担います。サービングエージェントは、曖昧さを解決したり、不足している情報を特定したりするのに最適です。
曖昧さがある場合 (例 : リクエストに複数のアーティファクトが適合する可能性がある場合)、エージェントは入力必須状態を返してクライアントに説明を求めます。クライアントはレスポンスでアーティファクトを指定できます。クライアントはオプションで、パートメタデータにアーティファクト参照 {artifactId, taskId} を入力できます。これにより、後続タスクの入力と以前に生成されたアーティファクトを関連付けることができます。
このアプローチにより、クライアントの実装をシンプルにすることができます。
4-4. アーティファクトの変更の追跡
フォローアップやリファインメントによって、古いアーティファクトが変更され、新しいアーティファクトが生成される場合があります。この関連性を把握し、アーティファクトのすべての変更を追跡することで、将来のコンテキストでは最新のコピーのみが使用されるようにすることが望ましいでしょう。これは、最新のアーティファクトを先頭とするリンクリストのようなものです。
しかし、クライアントは最も適しており、結果として何を考慮するかを実際に決定します。そして、実際には変更を拒否することもできます。したがって、サービングエージェントはこの関連性を所有すべきではなく、A2Aプロトコル仕様の一部である必要はありません。サービングエージェントは、元のアーティファクトに基づいてリファインメントを生成する際に、同じアーティファクト名を維持する必要があります。
フォローアップまたはリファインメントのタスクでは、クライアントは「最新の」アーティファクト、つまりリファインメントの対象となるアーティファクトを参照するのが最適です。アーティファクトの参照が明示的に指定されていない場合、サービングエージェントは次の操作を実行できます。
コンテキストを使用して最新のアーティファクトを特定する。または、あいまいさやコンテキストがサポートされていない場合は、エージェントは「入力必須」を使用できます。
4-5. フォローアップの例
・クライアントがエージェントにメッセージを送信
{
"jsonrpc": "2.0",
"id": "req-001",
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [
{
"kind": "text",
"text": "Generate an image of a sailboat on the ocean."
}
],
"messageId": "msg-user-001"
}
}
}・エージェントがボートの画像で応答
{
"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": [
{
"kind": "file",
"file": {
"name": "sailboat_image.png",
"mimeType": "image/png",
"bytes": "<base64_encoded_png_data_of_a_sailboat>"
}
}
]
}
],
"kind": "task"
}
}・クライアントがボートを赤く塗ることを要求
以前のタスクIDを参照し、同じコンテキストIDを使用します。
{
"jsonrpc": "2.0",
"id": "req-002",
"method": "message/send",
"params": {
"message": {
"role": "user",
"messageId": "msg-user-002",
"contextId": "ctx-conversation-abc", // Same contextId
"referenceTaskIds": ["task-boat-gen-123"] // Optional: Referencing the previous task
"parts": [
{
"kind": "text",
"text": "That's great! Can you make the sailboat red?"
// Optional: In case the agent asked for actual relevant artifact.
// Client could provide the artifact data in parts.
// Also it could add metadata to the part to
// reference the specific artifact.
// "metadata": {
// "referenceArtifacts: [
// {
// "artifactId": "artifact-boat-v1-xyz",
// "taskId": "task-boat-gen-123"
// }
// ]
// }
}
],
}
}
}・エージェントが新しいイメージアーティファクトで応答
・同じコンテキストIDで新しいタスクを作成します。
・ボートのイメージアーティファクトは同じ名前ですが、アーティファクトIDが新しいです。
{
"jsonrpc": "2.0",
"id": "req-002",
"result": {
"id": "task-boat-color-456", // New task ID
"contextId": "ctx-conversation-abc", // Same contextId
"status": {
"state": "completed",
},
"artifacts": [
{
"artifactId": "artifact-boat-v2-red-pqr", // New artifactId
"name": "sailboat_image.png", // Same name as the original artifact
"description": "A generated image of a red sailboat on the ocean.",
"parts": [
{
"kind": "file",
"file": {
"name": "sailboat_image.png",
"mimeType": "image/png",
"bytes": "<base64_encoded_png_data_of_a_RED_sailboat>"
}
}
]
}
],
"kind": "task"
}
}