0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ローカル LLM で A2A (Agent2Agent) を試す:独立した Agent 間連携を動かす

0
Posted at

はじめに

前回は、ローカル LLM を OpenAI Agents SDK に接続し、単一の Agent を実行しました。

今回は、Agent が別の Agent に仕事を依頼する仕組みである A2A(Agent2Agent)を試します。売上分析を受け付ける Agent A と、分析を専門に行う Agent B を用意し、Agent A から Agent B へ分析を依頼します。

ただし、Agent A のコードから Agent B を直接 import したり、同じ Runner 内の Python の Tool として扱ったりはしません。Agent A と Agent B を別プロセスとして起動し、A2A プロトコルで連携させます。

ローカル LLM サーバーは、両 Agent から利用する共通の推論基盤です。Agent A / B 自身は別々のアプリケーションとして存在します。

A2A とは

A2A は、独立して動く AI Agent 同士が通信・協働するためのオープンプロトコルです。Agent を作る Framework や、利用する LLM、実行する場所が異なっていても、共通の方法で能力を確認し、仕事を依頼して結果を受け取れます。

プロトコルの仕様や公式チュートリアルは、A2A Protocol 公式サイト を参照してください。

今回試す構成

今回の例では、Agent A は Agent B を「売上データを分析できる外部 Agent」として扱います。Agent B の内部実装や Instructions は Agent A に公開しません。Agent B は Agent Card という能力説明を公開し、Agent A はそれを取得してから A2A のタスクとして依頼を送ります。

「Agent を Tool として呼び出す」と表現するとイメージしやすい一方で、A2A の Agent は単純な Tool そのものではありません。Tool は通常、決められた入力に対して処理結果を返す機能です。A2A の Agent は、Task の進捗、複数回のやり取り、長時間処理などを扱いながら、自律的に仕事を進められます。今回の記事では最小構成として、Agent A が Agent B へ1回の分析タスクを依頼し、完了結果を受け取るところまでを確認します。

完成イメージ

ターミナルを2つ開いて Agent B と Agent A を別々に起動します。

ユーザーからの依頼は Agent A が受け取ります。Agent A は分析が必要だと判断した場合、公開されている Agent B の Agent Card を取得し、A2A のタスクとして依頼を送ります。Agent B は自分の Agent として処理し、結果を A2A のレスポンスとして返します。Agent A はその結果を使ってユーザーへ回答します。

今回確認すること

  • Agent A / B を別ディレクトリ、別仮想環境、別プロセスとして起動できる
  • Agent B が A2A Server として Agent Card を公開できる
  • Agent A が Agent Card を取得して、Agent B の Skill を確認できる
  • Agent A が A2A Client として Agent B にタスクを送信できる
  • Agent B の結果を Agent A が受け取り、最終回答を返せる
  • 両 Agent が同じローカル LLM サーバーを利用できる

SDK 内の Agent 連携と何が違うのか

OpenAI Agents SDK の as_tool() や handoff を使えば、1つの Python プロセス内でも複数 Agent を連携できます。しかしその場合、Agent A は Agent B の Python オブジェクトを直接持ち、同じ SDK の実行管理下に置かれます。

今回の構成で Agent A が知るのは、Agent B の URL と、そこから取得する Agent Card だけです。Agent B は別プロセスで停止・更新・再起動でき、将来は別 PC や別言語の実装へ置き換えることもできます。

観点 SDK 内の Agent 連携 今回の A2A 連携
プロセス 同一 別々
起動 1つのプログラム Agent ごとに別ターミナル
Agent B の参照 Python オブジェクト URL と Agent Card
通信 SDK 内部の呼び出し A2A プロトコル
Agent B の実装 同じアプリに依存 Agent A から独立
LLM 共有してもよい 共有してもよい

「LLM を共用する」ことと「Agent を独立させる」ことは矛盾しません。LLM サーバーは共通インフラであり、Agent A / B のプロセス、Instructions、依存関係、実行ライフサイクルはそれぞれ独立しています。

実装する

ディレクトリ構成

最初から Agent ごとに分けます。

a2a-sample/
├─ agent-a/
│  ├─ .venv/
│  ├─ pyproject.toml
│  ├─ main.py              # Agent A の CLI と A2A Client
│  └─ a2a_client.py        # Agent Card の取得とタスク送信
│
└─ agent-b/
   ├─ .venv/
   ├─ pyproject.toml
   ├─ server.py             # A2A Server のエントリーポイント
   ├─ executor.py           # A2A の依頼を Agent B へ橋渡しする
   └─ analysis_agent.py     # OpenAI Agents SDK で定義する Agent B

両方のプロジェクトに a2a-sdk、openai-agents、openai を追加します。記事では動作を最小化するため、A2A の JSON-RPC バインディングとテキスト入出力だけを使います。

Agent B を A2A Server として公開する

Agent B は分析専門の Agent です。外部へ公開するのは Agent B の Python クラスではなく、A2A Server と Agent Card です。

Agent Card に能力を記述する

Agent Card は通常 /.well-known/agent-card.json で公開されます。Agent A はこの JSON を取得して、Agent B の名前、説明、接続先、対応する入出力形式、Skills を確認します。

今回の Agent B には、例えば次の Skill を定義します。

id: sales_analysis
name: Sales analysis
description: 売上データを集計し、売上・件数・傾向を分析する
input: text/plain
output: text/plain

Skill の説明は、Agent A が委譲判断に使える粒度で書きます。「いろいろできます」のような説明では、後から Agent が増えたときに適切な委譲先を選べません。

A2A の依頼を Agent B に渡す

Agent B 側では、A2A SDK の AgentExecutor を実装します。Executor は A2A のリクエストを受け取り、メッセージ本文を取り出して OpenAI Agents SDK の Agent B へ渡し、完了結果を A2A の artifact として返す橋渡し役です。

A2A Request
  ↓
AgentExecutor.execute()
  ↓
Runner.run(analysis_agent, input=...)
  ↓
local LLM
  ↓
A2A artifact

Agent B の Instructions は分析に限定します。completed のレコードだけを対象にし、集計値・根拠・注意点を返すようにします。ユーザー向けの会話や委譲先の選択は Agent A の責務です。

Agent A から Agent B を呼び出す

Agent A はユーザーとの窓口です。Agent B の URL(例: http://127.0.0.1:9999)を設定として持ち、開始時または委譲直前に Agent Card を取得します。

GET http://127.0.0.1:9999/.well-known/agent-card.json

取得した Agent Card に sales_analysis の Skill があることを確認したうえで、A2A Client が Agent B にメッセージを送ります。

Agent A
  ├─ ユーザーの依頼を確認
  ├─ Agent Card を取得
  ├─ Skill / 入出力形式を確認
  ├─ A2A task を作成して Agent B へ送信
  └─ 完了した task の artifact から分析結果を取得

最初の実装では、売上データの分析依頼は必ず Agent B に送るルールにします。これにより、ローカルモデルの Tool Calling の安定性に依存せず、A2A の通信・タスク・結果取得を検証できます。

次の段階で、Agent A 自身に「分析が必要なら Agent B に委譲する」という判断をさせます。このときも A2A Client を Agent A の Tool として包めばよく、Agent B が独立プロセスである点は変わりません。

実行する

事前に、共通のローカル LLM サーバーを起動しておきます。Agent A / B はそれぞれ LOCAL_LLM_BASE_URL と LOCAL_LLM_MODEL を同じ値に設定します。

Terminal 1: Agent B を起動する

cd agent-b
.venv\Scripts\Activate.ps1
$env:LOCAL_LLM_BASE_URL = "http://127.0.0.1:11434/v1"
$env:LOCAL_LLM_MODEL = "Qwen3.5-9B-Q5_K_M.gguf"
python server.py

起動後、Agent Card を取得できることを確認します。

Invoke-RestMethod http://127.0.0.1:9999/.well-known/agent-card.json

Terminal 2: Agent A を起動する

cd agent-a
.venv\Scripts\Activate.ps1
$env:AGENT_B_URL = "http://127.0.0.1:9999"
$env:AGENT_B_TIMEOUT_SECONDS = "300"
$env:LOCAL_LLM_BASE_URL = "http://127.0.0.1:11434/v1"
$env:LOCAL_LLM_MODEL = "Qwen3.5-9B-Q5_K_M.gguf"
Get-Content -Raw -Encoding utf8 request.txt | python main.py

入力には、agent-a/request.txt を使用しました。以下は動作確認用に作成した架空の売上データです。
status が completed の決済だけを対象に、日付・商品別の決済件数と売上金額を Markdown 表として返すよう依頼します。cancelled のレコードを2件混ぜ、指示どおりに除外されることも確認します。

以下は1レコード1決済の売上データです。

- `status` が `completed` のレコードだけを対象にしてください。
- 日付・商品ごとに決済件数と売上金額を集計してください。
- 分析担当者へ渡せる Markdown 表だけを返してください。

取引ID,日付,商品,金額(円),status
P001,2026-08-01,ノート,300,completed
P002,2026-08-01,ノート,300,completed
P003,2026-08-01,ボールペン,150,completed
P004,2026-08-01,マグカップ,1800,completed
P005,2026-08-02,ノート,300,completed
P006,2026-08-02,ボールペン,150,completed
P007,2026-08-02,ボールペン,150,completed
P008,2026-08-02,マグカップ,1800,completed
P009,2026-08-03,ノート,300,completed
P010,2026-08-03,ノート,300,completed
P011,2026-08-03,ボールペン,150,completed
P012,2026-08-03,マグカップ,1800,cancelled
P013,2026-08-04,ノート,300,completed
P014,2026-08-04,ボールペン,150,completed
P015,2026-08-04,ボールペン,150,completed
P016,2026-08-04,ボールペン,150,completed
P017,2026-08-05,ノート,300,completed
P018,2026-08-05,ノート,300,completed
P019,2026-08-05,ノート,300,completed
P020,2026-08-05,マグカップ,1800,completed
P021,2026-08-06,ボールペン,150,completed
P022,2026-08-06,ボールペン,150,completed
P023,2026-08-06,マグカップ,1800,completed
P024,2026-08-07,ノート,300,completed
P025,2026-08-07,ノート,300,completed
P026,2026-08-07,ボールペン,150,completed
P027,2026-08-07,マグカップ,1800,completed
P028,2026-08-08,ノート,300,cancelled
P029,2026-08-08,ボールペン,150,completed
P030,2026-08-08,ボールペン,150,completed
P031,2026-08-08,ボールペン,150,completed
P032,2026-08-08,ボールペン,150,completed
P033,2026-08-08,マグカップ,1800,completed

確認したいログの流れは次のとおりです。

Agent A: Agent Card を取得した
Agent A: sales_analysis Skill を見つけた
Agent A: Agent B に A2A task を送信した

Agent B: task を受信した
Agent B: local LLM に分析を依頼した
Agent B: artifact を返した

Agent A: artifact を受信し、最終回答を表示した

データは Agent A がそのまま A2A task の入力として渡します。Agent B は completed のレコードだけを集計して artifact を返し、Agent A はその artifact を受けてユーザーへ最終出力を返します。

実行結果

Agent A は Agent B の Agent Card を取得し、sales_analysis Skill が公開されていることを確認してから A2A task を送信しました。その後、Agent B が返した artifact を受け取れました。

[Agent A] Retrieved Agent B's Agent Card
Name        : Sales Analysis Agent
Interface   : http://127.0.0.1:9999 (JSONRPC 1.0)
Skill       : sales_analysis

[Agent A] Sending an A2A task to Agent B
[Agent A] Received Agent B's analysis artifact

[Agent A] Final answer

| 日付 | 商品 | 決済件数 | 売上金額(円) |
| --- | --- | ---: | ---: |
| 2026-08-01 | ノート | 2 | 600 |
| 2026-08-01 | ボールペン | 1 | 150 |
| 2026-08-01 | マグカップ | 1 | 1,800 |
| 2026-08-02 | ノート | 1 | 300 |
| 2026-08-02 | ボールペン | 2 | 300 |
| 2026-08-02 | マグカップ | 1 | 1,800 |
| 2026-08-03 | ノート | 2 | 600 |
| 2026-08-03 | ボールペン | 1 | 150 |
| 2026-08-04 | ノート | 1 | 300 |
| 2026-08-04 | ボールペン | 3 | 450 |
| 2026-08-05 | ノート | 3 | 900 |
| 2026-08-05 | マグカップ | 1 | 1,800 |
| 2026-08-06 | ボールペン | 2 | 300 |
| 2026-08-06 | マグカップ | 1 | 1,800 |
| 2026-08-07 | ノート | 2 | 600 |
| 2026-08-07 | ボールペン | 1 | 150 |
| 2026-08-07 | マグカップ | 1 | 1,800 |
| 2026-08-08 | ボールペン | 4 | 600 |
| 2026-08-08 | マグカップ | 1 | 1,800 |

cancelled のレコードに対応する 2026-08-03 のマグカップと、2026-08-08 のノートは、最終出力に含まれていません。

この時点では扱わないこと

  • Agent B の自動発見(URL は AGENT_B_URL で明示する)
  • 認証、署名、認可
  • ストリーミング結果や長時間タスクの再開
  • 複数の Agent Card からの委譲先選択
  • MCP 接続

これらを省くことで、今回の記事では「独立プロセスの Agent が Agent Card を公開し、A2A task で連携する」という核心を確認します。

まとめ

  • Agent A と Agent B は別ディレクトリ・別仮想環境・別プロセスで起動する
  • 共通なのはローカル LLM サーバーだけで、Agent の実装やライフサイクルは独立している
  • Agent B は A2A Server と Agent Card を公開し、Agent A は A2A Client として利用する
  • 同一 SDK 内の複数 Agent 連携ではなく、A2A による Agent 間通信を検証する

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?