🚀

A2A を基礎から学ぶ (3) さくらのAI Engine で複数Agentを管理するPlanner Agent を作る

に公開

https://zenn.dev/sakura_internet/articles/e29d64a9d211a4
https://zenn.dev/sakura_internet/articles/0f77e58048f906

今日は前回に引き続きA2Aを見ていきます。

今までのサンプルではA2AによるAgent間の情報の受け渡しにフォーカスをあてていました。このためAgentは2つしか存在しておらず、AgentAがコードの中で明示的にAgentBを呼び出す、という形をとっていました。

実際の利用状況はもっと複雑で複数のAgentが登録されており、必要な時に必要なAgentが呼び出される、という形態をとります。
この判断はプログラムで行うこともできますが、当然LLM側にあらかじめ利用可能なAgentの情報を渡しておいて、LLMに判断させるということができます。

Planner Agent と Worker Agent

Planner AgentというのはA2Aの規格で定められているものではありません。一般的な実装パターンとして複数のAgentが存在している場合、それらAgentをどのように利用すべきか?という流れを入力されたプロンプトに応じて動的に判断を行い、各Agentの呼び出しとタスク完了後のレスポンス入手、最終回答の生成までをつかさどります。

┌─────────────────────────────────────────────────────┐
│  AgentA (Planner)                                   │
│  さくらAI Engineで「どのAgentに聞くべきか」を判断    │
└─────────────────────────────────────────────────────┘
           │                                  │        
           ↓                                  ↓        
    ┌──────────────┐                   ┌──────────────┐
    │   AgentB     │                   │   AgentC     │
    │  質問応答    │                   │  現在時刻     │
    │ (さくらAI)   │                   │ (ローカル)    │
    └──────────────┘                   └──────────────┘

質問に応じでAgentBを呼び出すか、AgentCを呼び出すかを判断し、呼び出したのちはその実行を見守り(ポーリングし)ながらレスポンスを最終的な結果として出力します。

またPlannerから呼び出され実際の処理を行うAgentをWorkerと呼びます。これもPlannerと同様にA2Aで定義されているものではありませんが、実装上の概念としてよくつかわれる呼び方です。

似たような呼称として、Orchestrator Agent、Coordinator Agent、Manager Agent、Dispatcher Agent、Router Agent 等がありますがおおよそ同じものを指しています。

さっそくやってみる

前回までのサンプルをもとにまずはagentA.js,agentC.js,package.jsonを以下に置き換えます。

agentA.js
// agentA.js - A2A Planner Agent
// ========================================================
// さくらAI Engineを使って適切なWorkerを選択するPlannerエージェント
// Function Callingは使用せず、LLMの判断で振り分け
// ========================================================

import fetch from 'node-fetch';

// Worker Agentの設定
const WORKERS = {
  AGENT_B: {
    url: 'http://localhost:3001',
    name: 'AgentB'
  },
  AGENT_C: {
    url: 'http://localhost:3002',
    name: 'AgentC'
  }
};

// さくらAI Engineの設定
const SAKURA_API_URL = 'https://api.ai.sakura.ad.jp/v1/chat/completions';
const SAKURA_API_KEY = 'xxf3e60b-7c0a-4685-b5eb-148fbe845cc5:mFhWGnNfWmfvy4psmZgWKQ4EkrzEy5MQxu9vuyV5';
const SAKURA_MODEL = 'gpt-oss-120b';

// ========================================================
// ユーティリティ: JSON整形表示
// ========================================================
function displayJson(label, data) {
  console.log(`\n${label}`);
  console.log('─'.repeat(60));
  console.log(JSON.stringify(data, null, 2));
  console.log('─'.repeat(60));
}

// ========================================================
// Agent Card の取得
// ========================================================
async function getAgentCard(agentUrl, agentName) {
  try {
    const response = await fetch(`${agentUrl}/.well-known/agent.json`);
    const card = await response.json();
    console.log(`📄 ${agentName} のカード情報を取得: ${card.name}`);
    return card;
  } catch (error) {
    console.error(`${agentName} のAgent Card取得に失敗:`, error.message);
    return null;
  }
}

// ========================================================
// さくらAI Engineを呼び出す関数
// ========================================================
async function callSakuraAI(userMessage, systemPrompt) {
  try {
    const response = await fetch(SAKURA_API_URL, {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Authorization': `Bearer ${SAKURA_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        model: SAKURA_MODEL,
        messages: [
          { role: "system", content: systemPrompt },
          { role: "user", content: userMessage }
        ],
        temperature: 0.3,  // 判断の一貫性のため低めに
        max_tokens: 100,
        stream: false
      })
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`Sakura AI API error: ${response.status} - ${errorText}`);
    }

    const data = await response.json();
    return data.choices[0].message.content.trim();
    
  } catch (error) {
    console.error('❌ さくらAI Engine呼び出しエラー:', error.message);
    throw error;
  }
}

// ========================================================
// Planner: 適切なAgentを選択
// ========================================================
async function selectAgent(question, agentCards) {
  console.log('\n🧠 Planner: どのAgentに依頼すべきか判断中...\n');
  
  // Agent Cardの情報をプロンプトに含める
  const agentDescriptions = Object.entries(agentCards)
    .map(([key, card]) => {
      if (!card) return null;
      const skills = card.skills.map(s => `${s.name}: ${s.description}`).join(', ');
      return `- ${key}: ${card.name} - ${card.description} [スキル: ${skills}]`;
    })
    .filter(Boolean)
    .join('\n');
  
  const systemPrompt = `あなたはタスクを適切なAgentに振り分けるPlannerです。
以下のAgentが利用可能です:

${agentDescriptions}

ユーザーの質問を見て、どのAgentに依頼すべきか判断してください。
回答は必ず「AGENT_B」または「AGENT_C」のどちらか一方のみを返してください。
それ以外の文字は含めないでください。

判断基準:
- 時刻、日時、今何時などの質問 → AGENT_C
- それ以外の一般的な質問、知識に関する質問 → AGENT_B`;

  const userMessage = `質問: ${question}

どのAgentに依頼すべきですか?「AGENT_B」または「AGENT_C」のみで答えてください。`;

  // 📤 Plannerのリクエスト内容を表示
  displayJson('📤 Planner → さくらAI Engine (Agent選択):', {
    system: systemPrompt.substring(0, 200) + '...',
    user: userMessage
  });

  const decision = await callSakuraAI(userMessage, systemPrompt);
  
  // 📥 Plannerのレスポンス内容を表示
  displayJson('📥 さくらAI Engine → Planner (判断結果):', {
    decision: decision
  });
  
  // 判断結果をパース(AGENT_B または AGENT_C を抽出)
  if (decision.includes('AGENT_C')) {
    console.log('🎯 Planner判断: AGENT_C (時刻Agent) を選択\n');
    return 'AGENT_C';
  } else {
    console.log('🎯 Planner判断: AGENT_B (質問応答Agent) を選択\n');
    return 'AGENT_B';
  }
}

// ========================================================
// タスクの作成
// ========================================================
async function createTask(agentUrl, agentName, instruction) {
  try {
    const requestBody = {
      jsonrpc: "2.0",
      method: "tasks.create",
      params: { instruction }
    };
    
    displayJson(`📤 Planner → ${agentName} (タスク作成):`, requestBody);
    
    const response = await fetch(`${agentUrl}/tasks`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(requestBody)
    });
    
    const data = await response.json();
    
    displayJson(`📥 ${agentName} → Planner (タスク作成応答):`, data);
    
    console.log(`\n✓ タスクを作成しました [${data.result.task_id}]`);
    return data.result.task_id;
  } catch (error) {
    console.error('❌ タスク作成に失敗:', error.message);
    throw error;
  }
}

// ========================================================
// タスク状態のポーリング
// ========================================================
async function pollTaskStatus(agentUrl, agentName, taskId, maxAttempts = 30) {
  console.log('\n⏳ タスクの完了を待機中...\n');
  
  for (let i = 0; i < maxAttempts; i++) {
    try {
      const response = await fetch(`${agentUrl}/tasks/${taskId}`);
      const data = await response.json();
      
      const status = data.result.status;
      const statusEmoji = {
        'submitted': '📋',
        'working': '⚙️',
        'completed': '✅',
        'failed': '❌'
      };
      
      console.log(`   ${statusEmoji[status] || '❓'} [${i + 1}/${maxAttempts}] 状態: ${status}`);
      
      if (status === 'completed') {
        console.log(`\n✓ ${agentName}: タスク完了を確認`);
        displayJson(`📥 ${agentName} → Planner (最終結果):`, data);
        return data.result;
      }
      
      if (status === 'failed') {
        throw new Error('タスクが失敗しました: ' + (data.result.result?.error || '不明なエラー'));
      }
      
      await new Promise(resolve => setTimeout(resolve, 1000));
      
    } catch (error) {
      console.error('❌ タスク状態取得に失敗:', error.message);
      throw error;
    }
  }
  
  throw new Error('タスク完了のタイムアウト');
}

// ========================================================
// メイン処理
// ========================================================
async function main() {
  console.log('\n' + '='.repeat(60));
  console.log('🚀 A2A Planner Agent デモ');
  console.log('   さくらAI Engineで適切なWorkerを選択');
  console.log('='.repeat(60));
  
  try {
    // 1. すべてのWorker AgentのCardを取得
    console.log('\n📋 Worker Agentの情報を収集中...\n');
    
    const agentCards = {};
    for (const [key, worker] of Object.entries(WORKERS)) {
      agentCards[key] = await getAgentCard(worker.url, worker.name);
    }
    
    // 利用可能なAgentを表示
    console.log('\n🤖 利用可能なWorker Agent:');
    for (const [key, card] of Object.entries(agentCards)) {
      if (card) {
        console.log(`   - ${key}: ${card.name} - ${card.description}`);
      }
    }
    
    // 2. テスト質問を準備
    const questions = [
      "今何時ですか?",
      "A2Aプロトコルとは何ですか?簡潔に説明してください。",
      "現在の日時を教えてください",
      // "JavaScriptとは何ですか?",
    ];
    
    // 3. 各質問を処理
    for (const question of questions) {
      console.log('\n' + '='.repeat(60));
      console.log(`📝 ユーザーの質問: "${question}"`);
      console.log('='.repeat(60));
      
      // Plannerが適切なAgentを選択
      const selectedAgent = await selectAgent(question, agentCards);
      const worker = WORKERS[selectedAgent];
      
      // 選択されたAgentにタスクを依頼
      const taskId = await createTask(worker.url, worker.name, question);
      
      // 完了を待つ
      const result = await pollTaskStatus(worker.url, worker.name, taskId);
      
      // 結果を表示
      console.log('\n' + '='.repeat(60));
      console.log(`🎯 ${worker.name} からの回答:`);
      console.log('='.repeat(60));
      console.log(result.result.answer);
      
      if (result.result.model) {
        console.log(`\n📊 モデル: ${result.result.model}`);
      }
      if (result.result.raw) {
        console.log(`📊 詳細: ${JSON.stringify(result.result.raw)}`);
      }
      console.log('='.repeat(60));
      
      // 次の質問の前に少し待機
      if (questions.indexOf(question) < questions.length - 1) {
        console.log('\n⏳ 次の質問まで2秒待機...\n');
        await new Promise(resolve => setTimeout(resolve, 2000));
      }
    }
    
    console.log('\n✅ すべての質問が完了しました!\n');
    
  } catch (error) {
    console.error('\n❌ エラーが発生しました:', error.message);
    console.error(error.stack);
    process.exit(1);
  }
}

// 実行
main();
agentC.js
// agentC.js - A2A Server (Time Worker Agent)
// ========================================================
// 現在時刻を返すシンプルなWorkerエージェント
// ========================================================

import express from 'express';
import { v4 as uuidv4 } from 'uuid';

const app = express();
app.use(express.json());

const PORT = 3002;

// タスクの状態を保存(メモリ内)
const tasks = new Map();

// ========================================================
// Agent Card - エージェントのメタデータ
// ========================================================
const agentCard = {
  name: "TimeAgent",
  version: "1.0.0",
  description: "現在時刻を返すエージェント。時間に関する質問に答えます。",
  capabilities: ["text"],
  skills: [
    {
      name: "getCurrentTime",
      description: "現在の日時を返します。「今何時?」「現在時刻は?」などの質問に対応します。"
    }
  ],
  endpoint: `http://localhost:${PORT}`,
  provider: "Local",
  model: "none"
};

// ========================================================
// GET /.well-known/agent.json - エージェントカードの公開
// ========================================================
app.get('/.well-known/agent.json', (req, res) => {
  res.json(agentCard);
});

// ========================================================
// POST /tasks - タスクの作成
// ========================================================
app.post('/tasks', (req, res) => {
  // JSON-RPC形式のparamsから取得
  const { instruction } = req.body.params || req.body;
  
  const taskId = uuidv4();
  const task = {
    id: taskId,
    status: "submitted",
    instruction,
    createdAt: new Date().toISOString(),
    result: null
  };
  
  tasks.set(taskId, task);
  
  console.log(`\n📥 Agent C: タスク受信 [${taskId}]`);
  console.log(`   指示: "${instruction || '(指示なし)'}"`);
  
  // タスクを非同期で処理
  setTimeout(() => {
    processTask(taskId);
  }, 100);
  
  res.status(201).json({
    jsonrpc: "2.0",
    id: taskId,
    result: {
      task_id: taskId,
      status: "submitted"
    }
  });
});

// ========================================================
// GET /tasks/:taskId - タスク状態の取得
// ========================================================
app.get('/tasks/:taskId', (req, res) => {
  const { taskId } = req.params;
  const task = tasks.get(taskId);
  
  if (!task) {
    return res.status(404).json({
      jsonrpc: "2.0",
      error: {
        code: -32001,
        message: "Task not found"
      }
    });
  }
  
  res.json({
    jsonrpc: "2.0",
    id: taskId,
    result: {
      task_id: task.id,
      status: task.status,
      result: task.result
    }
  });
});

// ========================================================
// タスク処理(現在時刻を返す)
// ========================================================
function processTask(taskId) {
  const task = tasks.get(taskId);
  if (!task) return;
  
  try {
    // 状態を "working" に更新
    task.status = "working";
    console.log(`⚙️  Agent C: タスク処理中 [${taskId}]`);
    
    // 現在時刻を取得
    const now = new Date();
    
    // 日本時間でフォーマット
    const options = {
      timeZone: 'Asia/Tokyo',
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      weekday: 'long'
    };
    
    const formattedTime = now.toLocaleString('ja-JP', options);
    
    // 状態を "completed" に更新
    task.status = "completed";
    task.result = {
      answer: `現在の日時は ${formattedTime} です。`,
      raw: {
        iso: now.toISOString(),
        timestamp: now.getTime(),
        timezone: 'Asia/Tokyo'
      },
      timestamp: now.toISOString()
    };
    
    console.log(`✅ Agent C: タスク完了 [${taskId}]`);
    console.log(`   回答: "${task.result.answer}"`);
    
  } catch (error) {
    // エラーが発生した場合
    task.status = "failed";
    task.result = {
      error: error.message,
      timestamp: new Date().toISOString()
    };
    
    console.error(`❌ Agent C: タスク失敗 [${taskId}]`, error.message);
  }
}

// ========================================================
// サーバー起動
// ========================================================
app.listen(PORT, () => {
  console.log(`\n${'='.repeat(60)}`);
  console.log(`🕐 Agent C (Time Worker) が起動しました`);
  console.log(`${'='.repeat(60)}`);
  console.log(`📍 エンドポイント: http://localhost:${PORT}`);
  console.log(`📄 Agent Card: http://localhost:${PORT}/.well-known/agent.json`);
  console.log(`🔧 機能: 現在時刻を返す`);
  console.log(`${'='.repeat(60)}\n`);
});
package.json
{
  "name": "a2a-simple-demo",
  "version": "2.0.0",
  "description": "A2Aプロトコル Planner + Worker エージェント連携デモ",
  "type": "module",
  "scripts": {
    "agent-a": "node agentA.js",
    "agent-b": "node agentB.js",
    "agent-c": "node agentC.js"
  },
  "keywords": [
    "a2a",
    "agent",
    "planner",
    "sakura",
    "ai"
  ],
  "dependencies": {
    "express": "^4.18.2",
    "node-fetch": "^3.3.2",
    "uuid": "^9.0.1"
  }
}

npm installを実行して以下のコマンドでそれぞれAgentを起動します。それぞれ異なるターミナルで実行してください。

npm run agent-b
npm run agent-c
npm run agent-a
npm run agent-a

> a2a-simple-demo@2.0.0 agent-a
> node agentA.js


============================================================
🚀 A2A Planner Agent デモ
   さくらAI Engineで適切なWorkerを選択
============================================================

📋 Worker Agentの情報を収集中...

📄 AgentB のカード情報を取得: SakuraAIAgent
📄 AgentC のカード情報を取得: TimeAgent

🤖 利用可能なWorker Agent:
   - AGENT_B: SakuraAIAgent - さくらのAI Engineを使って質問に答えるエージェント
   - AGENT_C: TimeAgent - 現在時刻を返すエージェント。時間に関する質問に答えます。

============================================================
📝 ユーザーの質問: "今何時ですか?"
============================================================

🧠 Planner: どのAgentに依頼すべきか判断中...


📤 Planner → さくらAI Engine (Agent選択):
────────────────────────────────────────────────────────────
{
  "system": "あなたはタスクを適切なAgentに振り分けるPlannerです。\n以下のAgentが利用可能です:\n\n- AGENT_B: SakuraAIAgent - さくらのAI Engine を使って質問に答えるエージェント [スキル: answerQuestion: LLMを使って質問に答えます, generateText: 指示に基づいてテキストを生成します]\n- AGENT_C: TimeAgent ...",
  "user": "質問: 今何時ですか?\n\nどのAgentに依頼すべきですか?「AGENT_B」または「AGENT_C」のみで答えてください。"
}
────────────────────────────────────────────────────────────

📥 さくらAI Engine → Planner (判断結果):
────────────────────────────────────────────────────────────
{
  "decision": "AGENT_C"
}
────────────────────────────────────────────────────────────
🎯 Planner判断: AGENT_C (時刻Agent) を選択


📤 Planner → AgentC (タスク作成):
────────────────────────────────────────────────────────────
{
  "jsonrpc": "2.0",
  "method": "tasks.create",
  "params": {
    "instruction": "今何時ですか?"
  }
}
────────────────────────────────────────────────────────────

📥 AgentC → Planner (タスク作成応答):
────────────────────────────────────────────────────────────
{
  "jsonrpc": "2.0",
  "id": "79cd2946-44cf-44ee-be7e-052f0ca562d5",
  "result": {
    "task_id": "79cd2946-44cf-44ee-be7e-052f0ca562d5",
    "status": "submitted"
  }
}
────────────────────────────────────────────────────────────

✓ タスクを作成しました [79cd2946-44cf-44ee-be7e-052f0ca562d5]

⏳ タスクの完了を待機中...

   📋 [1/30] 状態: submitted
   ✅ [2/30] 状態: completed

✓ AgentC: タスク完了を確認

📥 AgentC → Planner (最終結果):
────────────────────────────────────────────────────────────
{
  "jsonrpc": "2.0",
  "id": "79cd2946-44cf-44ee-be7e-052f0ca562d5",
  "result": {
    "task_id": "79cd2946-44cf-44ee-be7e-052f0ca562d5",
    "status": "completed",
    "result": {
      "answer": "現在の日時は 2025/11/21金曜日 13:34:08 です。",
      "raw": {
        "iso": "2025-11-21T04:34:08.832Z",
        "timestamp": 1763699648832,
        "timezone": "Asia/Tokyo"
      },
      "timestamp": "2025-11-21T04:34:08.832Z"
    }
  }
}
────────────────────────────────────────────────────────────

============================================================
🎯 AgentC からの回答:
============================================================
現在の日時は 2025/11/21金曜日 13:34:08 です。
📊 詳細: {"iso":"2025-11-21T04:34:08.832Z","timestamp":1763699648832,"timezone":"Asia/Tokyo"}
============================================================

⏳ 次の質問まで2秒待機...


============================================================
📝 ユーザーの質問: "A2Aプロトコルとは何ですか?簡潔に説明してください。"
============================================================

🧠 Planner: どのAgentに依頼すべきか判断中...


📤 Planner → さくらAI Engine (Agent選択):
────────────────────────────────────────────────────────────
{
  "system": "あなたはタスクを適切なAgentに振り分けるPlannerです。\n以下のAgentが利用可能です:\n\n- AGENT_B: SakuraAIAgent - さくらのAI Engine を使って質問に答えるエージェント [スキル: answerQuestion: LLMを使って質問に答えます, generateText: 指示に基づいてテキストを生成します]\n- AGENT_C: TimeAgent ...",
  "user": "質問: A2Aプロトコルとは何ですか?簡潔に説明してください。\n\nどのAgentに依頼すべきですか?「AGENT_B」または「AGENT_C」のみで答えてください。"
}
────────────────────────────────────────────────────────────

📥 さくらAI Engine → Planner (判断結果):
────────────────────────────────────────────────────────────
{
  "decision": "AGENT_B"
}
────────────────────────────────────────────────────────────
🎯 Planner判断: AGENT_B (質問応答Agent) を選択


📤 Planner → AgentB (タスク作成):
────────────────────────────────────────────────────────────
{
  "jsonrpc": "2.0",
  "method": "tasks.create",
  "params": {
    "instruction": "A2Aプロトコルとは何ですか?簡潔に説明してください。"
  }
}
────────────────────────────────────────────────────────────

📥 AgentB → Planner (タスク作成応答):
────────────────────────────────────────────────────────────
{
  "jsonrpc": "2.0",
  "id": "82fd5e52-885d-437f-a6b1-23fbbdc6d0b9",
  "result": {
    "task_id": "82fd5e52-885d-437f-a6b1-23fbbdc6d0b9",
    "status": "submitted"
  }
}
────────────────────────────────────────────────────────────

✓ タスクを作成しました [82fd5e52-885d-437f-a6b1-23fbbdc6d0b9]

⏳ タスクの完了を待機中...

   📋 [1/30] 状態: submitted
   ⚙️ [2/30] 状態: working
   ⚙️ [3/30] 状態: working
   ⚙️ [4/30] 状態: working
   ✅ [5/30] 状態: completed

✓ AgentB: タスク完了を確認

📥 AgentB → Planner (最終結果):
────────────────────────────────────────────────────────────
{
  "jsonrpc": "2.0",
  "id": "82fd5e52-885d-437f-a6b1-23fbbdc6d0b9",
  "result": {
    "task_id": "82fd5e52-885d-437f-a6b1-23fbbdc6d0b9",
    "status": "completed",
    "result": {
      "answer": "**A2A(Application‑to‑Application)プロトコル**は、**アプリケーション同士が直接データや機能をやり取りするための通信規約**です 。主な特徴は次のとおりです。\n\n| 特徴 | 内容 |\n|------|------|\n| **目的** | 異なるシステム・サービス間で、業務ロジックやデータをシームレスに連携させる |\n| **通信形態** | HTTP/HTTPS、WebSocket、gRPC、MQTT など、既存のトランスポート層上で実装されることが多い |\n| **データ形式** | JSON、XML、Protocol Buffers など、軽量で可読性のあるフォーマットが利用される |\n| **認証・認可** | OAuth、JWT、APIキーなどを組み合わせて安全性を確保 |\n| **利用例** | - マイクロサービス間の API 呼び出し<br>- IoT デバイスとクラウドアプリのデータ送受信<br>- 業務システム間のリアルタイム連携 |\n| **メリット** | - 開発者が直接 API を呼び出せるため実装がシンプル<br>- スケーラビリティと柔軟性が高い<br>- 標準化されたプロトコルで他システムとの互換性が保たれる |\n| **デメリット** | - ネットワーク遅延や障害に対する耐性を設計で考慮する必要がある<br>- セキュリティ対策を怠ると情報漏洩リスクがある |\n\n要するに、A2Aプロトコルは「**アプリケーション同士が相互に機能やデータをやり取りするための標準化された通信手段**",
      "model": "gpt-oss-120b",
      "usage": {
        "prompt_tokens": 124,
        "total_tokens": 624,
        "completion_tokens": 500,
        "prompt_tokens_details": null
      },
      "timestamp": "2025-11-21T04:34:16.340Z"
    }
  }
}
────────────────────────────────────────────────────────────

============================================================
🎯 AgentB からの回答:
============================================================
**A2A(Application‑to‑Application)プロトコル**は、**アプリケーション同士が直接データや機能をやり取りするための通信規約**です。主な特徴は次のとおりです。

| 特徴 | 内容 |
|------|------|
| **目的** | 異なるシステム・サービス間で、業務ロジックやデータをシームレスに連携させる |
| **通信形態** | HTTP/HTTPS、WebSocket、gRPC、MQTT など、既存のトランスポート層上で実装されることが多い |
| **データ形式** | JSON、XML、Protocol Buffers など、軽量で可読性のあるフォーマットが利用される |
| **認証・認可** | OAuth、JWT、APIキーなどを組み合わせて安全性を確保 |
| **利用例** | - マイクロサービス間の API 呼び出し<br>- IoT デバイスとクラウドアプリのデータ送受信<br>- 業務システム間のリアルタイム連携 |     
| **メリット** | - 開発者が直接 API を呼び出せるため実装がシンプル<br>- スケーラビリティと柔軟性が高い<br>- 標準化されたプロトコルで他システム との互換性が保たれる |
| **デメリット** | - ネットワーク遅延や障害に対する耐性を設計で考慮する必要がある<br>- セキュリティ対策を怠ると情報漏洩リスクがある |

要するに、A2Aプロトコルは「**アプリケーション同士が相互に機能やデータをやり取りするための標準化された通信手段**

📊 モデル: gpt-oss-120b
============================================================

⏳ 次の質問まで2秒待機...


============================================================
📝 ユーザーの質問: "現在の日時を教えてください"
============================================================

🧠 Planner: どのAgentに依頼すべきか判断中...


📤 Planner → さくらAI Engine (Agent選択):
────────────────────────────────────────────────────────────
{
  "system": "あなたはタスクを適切なAgentに振り分けるPlannerです。\n以下のAgentが利用可能です:\n\n- AGENT_B: SakuraAIAgent - さくらのAI Engine を使って質問に答えるエージェント [スキル: answerQuestion: LLMを使って質問に答えます, generateText: 指示に基づいてテキストを生成します]\n- AGENT_C: TimeAgent ...",
  "user": "質問: 現在の日時を教えてください\n\nどのAgentに依頼すべきですか?「AGENT_B」または「AGENT_C」のみで答えてください。"
}
────────────────────────────────────────────────────────────

📥 さくらAI Engine → Planner (判断結果):
────────────────────────────────────────────────────────────
{
  "decision": "AGENT_C"
}
────────────────────────────────────────────────────────────
🎯 Planner判断: AGENT_C (時刻Agent) を選択


📤 Planner → AgentC (タスク作成):
────────────────────────────────────────────────────────────
{
  "jsonrpc": "2.0",
  "method": "tasks.create",
  "params": {
    "instruction": "現在の日時を教えてください"
  }
}
────────────────────────────────────────────────────────────

📥 AgentC → Planner (タスク作成応答):
────────────────────────────────────────────────────────────
{
  "jsonrpc": "2.0",
  "id": "bdfb047f-5a70-4374-9468-1b771955a6b8",
  "result": {
    "task_id": "bdfb047f-5a70-4374-9468-1b771955a6b8",
    "status": "submitted"
  }
}
────────────────────────────────────────────────────────────

✓ タスクを作成しました [bdfb047f-5a70-4374-9468-1b771955a6b8]

⏳ タスクの完了を待機中...

   📋 [1/30] 状態: submitted
   ✅ [2/30] 状態: completed

✓ AgentC: タスク完了を確認

📥 AgentC → Planner (最終結果):
────────────────────────────────────────────────────────────
{
  "jsonrpc": "2.0",
  "id": "bdfb047f-5a70-4374-9468-1b771955a6b8",
  "result": {
    "task_id": "bdfb047f-5a70-4374-9468-1b771955a6b8",
    "status": "completed",
    "result": {
      "answer": "現在の日時は 2025/11/21金曜日 13:34:19 です。",
      "raw": {
        "iso": "2025-11-21T04:34:19.971Z",
        "timestamp": 1763699659971,
        "timezone": "Asia/Tokyo"
      },
      "timestamp": "2025-11-21T04:34:19.971Z"
    }
  }
}
────────────────────────────────────────────────────────────

============================================================
🎯 AgentC からの回答:
============================================================
現在の日時は 2025/11/21金曜日 13:34:19 です。
📊 詳細: {"iso":"2025-11-21T04:34:19.971Z","timestamp":1763699659971,"timezone":"Asia/Tokyo"}
============================================================

✅ すべての質問が完了しました!

実行解説

まずPlannerは以下の通り2つのWorkerを認識します。

📋 Worker Agentの情報を収集中...

📄 AgentB のカード情報を取得: SakuraAIAgent
📄 AgentC のカード情報を取得: TimeAgent

🤖 利用可能なWorker Agent:
   - AGENT_B: SakuraAIAgent - さくらのAI Engineを使って質問に答えるエージェント
   - AGENT_C: TimeAgent - 現在時刻を返すエージェント。時間に関する質問に答えます。

その後入力されたプロンプトに応じてAI Engine側にAgentの選択を依頼しています。

📤 Planner → さくらAI Engine (Agent選択):
────────────────────────────────────────────────────────────
{
  "system": "あなたはタスクを適切なAgentに振り分けるPlannerです。\n以下のAgentが利用可能です:\n\n- AGENT_B: SakuraAIAgent - さくらのAI Engine を使って質問に答えるエージェント [スキル: answerQuestion: LLMを使って質問に答えます, generateText: 指示に基づいてテキストを生成します]\n- AGENT_C: TimeAgent ...",
  "user": "質問: 今何時ですか?\n\nどのAgentに依頼すべきですか?「AGENT_B」または「AGENT_C」のみで答えてください。"
}
────────────────────────────────────────────────────────────

📥 さくらAI Engine → Planner (判断結果):
────────────────────────────────────────────────────────────
{
  "decision": "AGENT_C"
}
────────────────────────────────────────────────────────────

あとは前回と同じようにAgentの実行状態をPlannerがポーリングしながら見守り、レスポンスを出力させます。

さくらインターネット株式会社

Discussion