For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
メインナビゲーション
Theme

マルチエージェント

Responses API のリクエスト内でモデルがサブエージェントを起動し、それぞれの担当タスクを並列に進めます。

概要

マルチエージェントを使うと、モデルが複数のサブエージェントを起動して並列に作業を進め、それぞれの成果をまとめて最終回答を返します。コードベースの調査、ドキュメント作成、実装など、作業を分担して並列に進める利点が大きい複雑なタスクを扱うアプリケーションで特に効果的です。

マルチエージェントは、GPT-6.1 Sol とすべての GPT-5.6 モデルでベータ機能として利用できます。アプリケーションで有効にする前に、使用するモデルのページを確認してください。

マルチエージェントに適したタスク

タスクは、多くの場合、互いに独立した作業に分割できます。単一のエージェントなら順番に進める作業も、複数のエージェントなら並列に進められます。マルチエージェントでは、ルートエージェントが複数のサブエージェントに作業を割り当て、各サブエージェントが同時に作業を進めます。これには次の利点があります。

  • 並列実行。独立した調査、分析、実装を同時に進められるため、実行時間を短縮できる場合があります。
  • タスクごとに独立したコンテキスト。各サブエージェントは範囲を限定したタスクを受け取り、専用のコンテキストを維持します。無関係な作業のコンテキストが互いに干渉しにくくなり、パフォーマンスが向上します。
  • モデルによる作業の調整。ルートエージェントはサブエージェントを作成し、追加情報を送り、結果を待って最終回答をまとめます。アプリケーション側でオーケストレーションを実装する必要はありません。

マルチエージェントによるオーケストレーションが最も役立つのは、次のように、タスクを具体的かつ独立した作業に分割できる場合です。

  • 大規模なコードベースの各部分を個別に調査
  • 複数の提案、文書、仮説を比較
  • 複数の情報源を並列に調査
  • 独立したコンポーネントを実装、または独立したテストスイートを作成
  • 障害の原因として考えられる複数の可能性を並列に調査
  • 問題に対する異なるアプローチを同時に検討

サブエージェントを追加すると、トークン使用量が増える場合があります。また、順を追って一連の推論を進める必要があるタスク、共有状態への書き込みが頻繁に発生するタスク、実行時間の大半をすでに単一の遅い外部処理が占めるタスクでは、効果が限られる場合があります。

マルチエージェントに適した場合 単一のエージェントに適した場合
範囲が明確で独立したタスクに分割できる 各ステップが直前のステップに直接依存する
コンテキストを分けると各作業に集中しやすい 1 回の短い実行で完了できる小規模なタスクである
並列に調査すると実際の所要時間を短縮できる 複数のエージェントが同じ変更可能なリソースを取り合う
独立した調査結果を比較すると見落としが減る 固定された決定論的な実行グラフが必要である

クイックスタート

Python と JavaScript の例ではベータ版の Responses SDK を使用します。HTTP リクエストでは client.beta.responses を使い、responses_multi_agent=v1 を betas 引数に渡します。HTTP リクエストを直接送信する場合と WebSocket で接続する場合は、リクエストヘッダーまたは接続ヘッダーに OpenAI-Beta: responses_multi_agent=v1 を指定します。マルチエージェントのベータ期間中は項目のスキーマが変更される可能性があります。

Responses API のリクエストで multi_agent.enabled を指定してマルチエージェントを有効にします。multi_agent.enabled が true の場合、ルートエージェントはサブエージェントをツリー状に作成できます。サブエージェントはリクエストで指定したモデルと利用可能なツールを共有し、エージェント同士は起動、メッセージ送信、待機などの基本操作を通じて連携します(マルチエージェントの仕組みを参照)。ルートエージェントはサブエージェントの回答をまとめ、最終回答を返します。

サブエージェントによる pull request のレビュー
from openai import OpenAI

client = OpenAI()


def review_pull_request(diff: str) -> str:
    response = client.beta.responses.create(
        model="gpt-6.1-sol",
        input=(
            "Review the pull-request diff below with three agents: one for "
            "correctness, one for security, and one for missing tests. "
            "Reconcile duplicate or conflicting findings, then return a "
            "prioritized review with file and line references.\n\n"
            f"<diff>\n{diff}\n</diff>"
        ),
        multi_agent={
            "enabled": True,
            "max_concurrent_subagents": 3,
        },
        betas=["responses_multi_agent=v1"],
    )

    return "".join(
        part.text
        for item in response.output
        if (
            item.type == "message"
            and item.agent is not None
            and item.agent.agent_name == "/root"
            and item.phase == "final_answer"
        )
        for part in item.content
        if part.type == "output_text"
    )

max_concurrent_subagents は、エージェントツリー全体で同時にアクティブになれるサブエージェントの最大数を設定します。子、孫、さらに深い階層のサブエージェントをすべて数えますが、ルートエージェントは含みません。

API では、この設定値に固定の上限を設けていません。デフォルトは 3 で、ほとんどのワークロードに推奨されます。また、マルチエージェントの実行では、ツリーの深さや 1 回の実行で作成できるサブエージェントの総数にも固定の上限はありません。

ルートモデルがどのような場合にサブエージェントを起動するかを調整するには、開発者メッセージを追加します。このメッセージは、ルートエージェントとサブエージェントに自動で挿入される指示を補足します。

開発者メッセージの例を示します。

  • 「ユーザーがサブエージェントの使用、作業の委任、または複数のエージェントによる並列作業を明示的に求めた場合にのみ、サブエージェントを起動してください。」
  • 「マルチエージェントによる自発的な作業の委任が有効です。並列作業によって速度や品質が大きく向上する場合は、サブエージェントを使用してください。」

マルチエージェントの仕組み

Responses API は、ルートエージェントとサブエージェントのモデルに、API 側で実行するオーケストレーション用の操作と、その使用方法を指示するメッセージを提供します。ルートエージェントの名前は /root です。起動したサブエージェントには、次のような階層パスが使われます。

/root
├── /root/researcher
├── /root/reviewer
└── /root/reviewer/tester

マルチエージェントでは、サブエージェントの総数やツリーの深さに固定の上限はありません。ほとんどのタスクでは、max_concurrent_subagents にデフォルト値の 3 を使用してください。この設定は、子からさらに深い階層まで、ツリー全体で同時に実行できるサブエージェントのターン数を制限します。

マルチエージェントモードを有効にすると、Responses API はエージェント間の連携に使う 6 種類の操作を提供し、API 側で実行します。これらは multi_agent_call 項目として出力される場合があります。アプリケーション側でこれらの操作を実行したり、出力を送信したりしないでください。

操作 用途
spawn_agent サブエージェントを作成し、最初のタスクを割り当てます。
send_message 新しいターンを開始せずに、既存のエージェント宛てのメッセージをキューに追加します。
followup_task ルート以外の既存のエージェントに追加の作業を割り当て、そのターンを開始または再開します。
wait_agent 呼び出し元のエージェントのメールボックスに更新が届くまで待機します。
interrupt_agent 別のエージェントのコンテキストを保持したまま、実行中のターンを中断します。
list_agents 現在のエージェントツリー、各エージェントのステータスと last_task_message を返します。

開発者が定義したツールの呼び出しは、マルチエージェントが無効な場合と同じ方法で処理します。ツリー内のどのエージェントも function_call を出力できます。アプリケーション側で呼び出しを実行し、対応する function_call_output を送信する必要があります。

ツリー内のすべてのエージェントが、API リクエストのモデル呼び出しに設定されたツールを利用できます。

Responses API でのマルチエージェントの使用

HTTP と WebSocket のパフォーマンス比較

HTTP と WebSocket は同じマルチエージェント機能をサポートしますが、ツールを多用するワークフローや長時間実行するワークフローには WebSocket を推奨します。接続が維持されるため、アプリケーションは関数の出力が得られ次第返すことができます。これにより、処理を継続するためのオーバーヘッドが減り、エージェントの待機時間が短くなります。

HTTP では、アクティブなすべてのエージェントが作業を終えるか、クライアント側での関数呼び出しの実行を待って一時停止すると、レスポンスが完了します。アプリケーションはその後、未処理の関数呼び出しをすべて実行し、その出力を新しい Responses API リクエストで送信します。これで、一時停止していたエージェントが作業を再開できます。

WebSocket では、アプリケーションは処理中のレスポンスが完了するのを待たずに、関数の出力を取得するたびにそのレスポンスに追加できます。待機中のエージェントはすぐに作業を再開でき、他のエージェントはその間も作業を続けられます。これにより、エージェント間の調整に伴う遅延が減り、各エージェントが異なるタイミングで作業を終えたりツールを要求したりしても、余分なリクエストの往復を避けられます。

複数のホスト型ツールを呼び出すワークフロー(並列ウェブ検索など)や、関数呼び出しが少なく 1 回のリクエストで完結するワークフローには、HTTP で十分な場合があります。ほとんどのマルチエージェントワークフローでは、WebSocket を使うとレイテンシが低下し、処理全体のパフォーマンスが向上すると見込まれます。

HTTP での関数呼び出しの実行

HTTP で関数呼び出しを実行する際の、アプリケーション、Responses API のルートエージェント、3 つのサブエージェント間の処理の流れ。

WebSocket での関数呼び出しの実行

WebSocket で関数呼び出しを実行する際の、アプリケーション、Responses API のルートエージェント、3 つのサブエージェント間の処理の流れ。

HTTP

以下の例には、ベータ版の Responses API を利用できるベータ版 SDK が必要です。HTTP ストリーミングでは、client.beta.responses.create を呼び出し、responses_multi_agent=v1 を betas 引数に渡します。これにより、ベータ版の型と自動補完が有効になります。Python で型アノテーションを追加する場合は、ベータ版のレスポンス項目の型を openai.types.beta からインポートしてください。

クライアント側のコード例を示します。

HTTP ストリーミングでのツール呼び出しの処理
from __future__ import annotations

import json
import sys

from openai import OpenAI
from openai.types.beta import BetaResponseOutputItem

client = OpenAI()
ROOT = "/root"
PROPOSALS = {
    "alpha": {"estimated_weeks": 6, "risk": "medium"},
    "beta": {"estimated_weeks": 8, "risk": "low"},
}
tools = [
    {
        "type": "function",
        "name": "get_proposal",
        "description": "Return details for a proposal that the agents should compare.",
        "parameters": {
            "type": "object",
            "properties": {
                "proposal": {
                    "type": "string",
                    "enum": ["alpha", "beta"],
                }
            },
            "required": ["proposal"],
            "additionalProperties": False,
        },
        "strict": True,
    }
]
history = [
    {
        "role": "user",
        "content": (
            "Use two subagents, one for proposal alpha and one for proposal beta. "
            "Have each call get_proposal, then compare their findings."
        ),
    }
]


def agent_name(item: BetaResponseOutputItem) -> str:
    return item.agent.agent_name if item.agent else ROOT


def render_to_user(delta: str) -> None:
    print(delta, end="", flush=True)


def log_subagent_text(agent: str, delta: str) -> None:
    print(f"[{agent}] {delta}", end="", file=sys.stderr, flush=True)


def process_tool_call(name: str, arguments: str) -> str:
    if name != "get_proposal":
        raise ValueError(f"Unknown tool: {name}")
    parsed_arguments = json.loads(arguments)
    return json.dumps(PROPOSALS[parsed_arguments["proposal"]])


while True:
    output_items = []
    pending_calls = []
    item_agents: dict[int, str] = {}

    stream = client.beta.responses.create(
        model="gpt-6.1-sol",
        input=history,
        tools=tools,
        store=False,
        multi_agent={
            "enabled": True,
            "max_concurrent_subagents": 3,
        },
        stream=True,
        betas=["responses_multi_agent=v1"],
    )
    for event in stream:
        if event.type == "response.output_item.added":
            item_agents[event.output_index] = agent_name(event.item)
        elif event.type == "response.output_text.delta":
            agent = item_agents.get(event.output_index, ROOT)
            if agent == ROOT:
                render_to_user(event.delta)
            else:
                log_subagent_text(agent, event.delta)
        elif event.type == "response.output_item.done":
            output_items.append(event.item)
            if event.item.type == "function_call":
                # Handle function calls from both the root agent and subagents.
                pending_calls.append(event.item)
        elif event.type == "response.completed":
            print(f"\nUsage: {event.response.usage}", file=sys.stderr)
            break
        elif event.type in {
            "error",
            "response.failed",
            "response.incomplete",
        }:
            raise RuntimeError(event)

    history.extend(output_items)

    for call in pending_calls:
        history.append(
            {
                "type": "function_call_output",
                "call_id": call.call_id,
                "output": process_tool_call(call.name, call.arguments),
            }
        )

    if not pending_calls:
        break

1 つ以上のエージェントが開発者定義の関数を呼び出した場合は、保留中の呼び出しをすべて実行し、それぞれの出力を含む継続リクエストを作成します。

WebSocket

WebSocket モードでエージェントが開発者定義の関数を呼び出した場合は、アプリケーション側で関数を実行し、response.inject イベントでその結果を処理中のレスポンスに送信します。待機中のエージェントは、マルチエージェントのレスポンス全体が完了するのを待たずに作業を再開できます。

{
  "type": "response.inject",
  "response_id": "resp_123",
  "input": [
    {
      "type": "function_call_output",
      "call_id": "call_123",
      "output": "{\"temperature\":72}"
    }
  ]
}

有効な response.inject リクエストに対して、サーバーは次のいずれかのイベントを返します。

  • response.inject.created:入力が検証され、レスポンスへの追加が受け付けられました。
  • response.inject.failed:入力は追加されませんでした。error.code を確認してください。
{
  "type": "response.inject.created",
  "sequence_number": 42,
  "response_id": "resp_123"
}
{
  "type": "response.inject.failed",
  "sequence_number": 43,
  "response_id": "resp_123",
  "input": [
    {
      "type": "function_call_output",
      "call_id": "call_123",
      "output": "{\"temperature\":72}"
    }
  ],
  "error": {
    "code": "response_already_completed",
    "message": "Response 'resp_123' has already completed."
  }
}

リクエストが response.inject のスキーマに準拠していない場合、サーバーはステータス 400 の汎用エラーを返し、WebSocket 接続を閉じます。次のイベントを送信する前に、リクエストを修正して新しい WebSocket 接続を開いてください。

Python のベータ版 SDK では client.beta.responses.connect、TypeScript のベータ版 SDK では ResponsesWS を使って WebSocket モードを利用できます。接続ヘッダーに OpenAI-Beta: responses_multi_agent=v1 を指定してください。HTTP ストリーミングとは異なり、WebSocket コネクタはまだ betas 引数に対応していません。

response.created イベントからレスポンス ID を保存し、そのレスポンスに送信するすべての response.inject イベントに含めてください。項目の追加を要求した後は、レスポンスが完了し、すべての追加要求について response.inject.created または response.inject.failed イベントを受信するまで、WebSocket からの読み取りを続けます。

WebSocket 経由でのツール出力の追加
from __future__ import annotations

import json

from openai import OpenAI

client = OpenAI()
PROPOSALS = {
    "alpha": {"estimated_weeks": 6, "risk": "medium"},
    "beta": {"estimated_weeks": 8, "risk": "low"},
}
tools = [
    {
        "type": "function",
        "name": "get_proposal",
        "description": "Return details for a proposal that the agents should compare.",
        "parameters": {
            "type": "object",
            "properties": {
                "proposal": {
                    "type": "string",
                    "enum": ["alpha", "beta"],
                }
            },
            "required": ["proposal"],
            "additionalProperties": False,
        },
        "strict": True,
    }
]


def process_tool_call(name: str, arguments: str) -> str:
    if name != "get_proposal":
        raise ValueError(f"Unknown tool: {name}")
    parsed_arguments = json.loads(arguments)
    return json.dumps(PROPOSALS[parsed_arguments["proposal"]])


def run_multi_agent(connection):
    previous_response_id: str | None = None
    pending_input: list[dict[str, object]] = [{"role": "user", "content": input()}]

    while pending_input:
        request = {
            "type": "response.create",
            "model": "gpt-6.1-sol",
            "store": True,
            "multi_agent": {"enabled": True},
            "tools": tools,
            "input": pending_input,
        }
        if previous_response_id is not None:
            request["previous_response_id"] = previous_response_id

        connection.send(request)

        next_input: list[dict[str, object]] = []
        completed_response = None
        response_id: str | None = None
        pending_injections = 0

        for event in connection:
            event_type = event.type

            if event_type == "response.created":
                response_id = event.response.id

            elif event_type == "response.output_item.done":
                item = event.item

                if item.type == "function_call":
                    if response_id is None:
                        raise RuntimeError(
                            "Received a function call before response.created"
                        )

                    output = {
                        "type": "function_call_output",
                        "call_id": item.call_id,
                        "output": process_tool_call(item.name, item.arguments),
                    }
                    pending_injections += 1

                    connection.send(
                        {
                            "type": "response.inject",
                            "response_id": response_id,
                            "input": [output],
                        }
                    )

            elif event_type == "response.inject.created":
                pending_injections -= 1

            elif event_type == "response.inject.failed":
                pending_injections -= 1

                if event.error.code != "response_already_completed":
                    raise RuntimeError(event.error)

                next_input.extend(item.model_dump(mode="json") for item in event.input)

            elif event_type == "response.completed":
                completed_response = event.response

            elif event_type in {
                "error",
                "response.failed",
                "response.incomplete",
            }:
                raise RuntimeError(event)

            if completed_response is not None and pending_injections == 0:
                break

        if completed_response is None:
            raise RuntimeError("Connection ended before response.completed")
        if pending_injections:
            raise RuntimeError("Connection ended with unacknowledged tool results")

        if not next_input:
            return completed_response

        previous_response_id = completed_response.id
        pending_input = next_input


with client.beta.responses.connect(
    extra_headers={"OpenAI-Beta": "responses_multi_agent=v1"},
) as connection:
    response = run_multi_agent(connection)
    print(
        "".join(
            part.text
            for item in response.output
            if (
                item.type == "message"
                and item.agent is not None
                and item.agent.agent_name == "/root"
                and item.phase == "final_answer"
            )
            for part in item.content
            if part.type == "output_text"
        )
    )

response.inject イベントの送信後は、WebSocket からの読み取りを続け、返された確認イベントを次のように処理します。

  • response.inject.created:関数の出力が処理中のレスポンスに追加されました。そのレスポンスのイベントを引き続き読み取ってください。
  • response.inject.failed で response_already_completed が返った場合:関数の出力を追加する前にレスポンスが完了しました。失敗イベントで返された input を、完了したレスポンスから処理を継続する新しい response.create リクエストで送信してください。
  • response.inject.failed で response_not_found が返った場合:サーバーは response_id で指定されたレスポンスを見つけられませんでした。response.created で受け取った ID を使用しているか確認してください。

マルチエージェントの 1 回の実行が複数の Responses API リクエストにまたがることがあります。HTTP では、エージェントが開発者定義の関数を呼び出すと、アプリケーションが関数を実行し、新しい response.create 呼び出しでその出力を送信します。WebSocket では、アプリケーションが処理中のレスポンスに関数の出力を直接追加します。

マルチエージェントで追加される出力項目

マルチエージェントのレスポンスには、次の 3 種類の出力項目が含まれる場合があります。

  • multi_agent_call:spawn_agent など、API 側で実行するマルチエージェントの操作を記録します。
  • multi_agent_call_output:API 側で実行した操作の結果を格納します。
  • agent_message:あるエージェントから別のエージェントへの暗号化されたメッセージを伝達します。

call_id フィールドは、各 multi_agent_call を対応する multi_agent_call_output に関連付けます。

各項目には agent 属性も含まれます。agent_message では、agent.agent_name が受信側のエージェントを示します。メッセージの送信元と送信先を追跡するには、author と recipient を使用します。

アプリケーションが multi_agent_call を受信した場合は、関数呼び出しとして実行したり、結果を返したりしないでください。Responses API が操作を実行し、対応する multi_agent_call_output を返します。アプリケーションで再生やトレースに使用する場合は、両方の項目を保存してください。

[
  {
    "type": "multi_agent_call",
    "id": "mac_123",
    "call_id": "call_spawn_a",
    "action": "spawn_agent",
    "arguments": "{\"task_name\":\"agent_a\",\"fork_turns\":\"all\",\"message\":\"enc_...\"}",
    "agent": { "agent_name": "/root" }
  },
  {
    "type": "multi_agent_call_output",
    "id": "maco_123",
    "call_id": "call_spawn_a",
    "action": "spawn_agent",
    "output": [
      {
        "type": "output_text",
        "text": "{\"task_name\":\"/root/agent_a\"}",
        "annotations": [],
        "logprobs": []
      }
    ],
    "agent": { "agent_name": "/root" }
  },
  {
    "type": "agent_message",
    "id": "amsg_123",
    "author": "/root/agent_a",
    "recipient": "/root",
    "content": [
      {
        "type": "encrypted_content",
        "encrypted_content": "enc_..."
      }
    ],
    "agent": { "agent_name": "/root" }
  }
]

特定のエージェントに紐づく SSE イベントには、トップレベルの agent 属性が含まれます。agent_message イベントでは、agent.agent_name が受信側のエージェントを示します。response.created や response.completed などのライフサイクルイベントは、個々のエージェントではなくレスポンス全体の状態を表すため、agent 属性を含みません。

{
  "type": "response.output_item.done",
  "agent": { "agent_name": "/root" },
  "item": {
    "type": "agent_message",
    "id": "amsg_123",
    "author": "/root/agent_a",
    "recipient": "/root",
    "content": [
      {
        "type": "encrypted_content",
        "encrypted_content": "enc_..."
      }
    ],
    "agent": { "agent_name": "/root" }
  }
}

制限事項

  1. コンパクション
    1. /responses/compact エンドポイントは、マルチエージェントが有効な場合は使用できません。
    2. multi_agent.enabled を true にすると、リクエストで context_management を設定していなくても、サーバー側の自動コンパクションが有効になります。コンパクションはルートエージェントと各サブエージェントに個別に適用され、それぞれのコンテキストが保持されます。compact_threshold を上書きするには、リクエストで context_management.compact_threshold を明示的に設定します。
  2. reasoning.summary は、マルチエージェントが有効な場合は使用できません。
  3. max_tool_calls は、マルチエージェントが有効な場合は使用できません。
  4. max_concurrent_subagents のデフォルト値は 3 です。この値を推奨します。

プロンプトの指針

マルチエージェントを有効にすると、システムは以下の指示を新しい開発者メッセージとしてルートエージェントとサブエージェントに自動で追加します。これらの指示は編集も削除もできません。独自の開発者指示は、自動で追加される指示を補足する形で記述してください。

ルートエージェント

You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals.

At the start of your turn, you are the active agent.
You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents.
All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools.

You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task and trigger a turn, and `send_message` to pass a message to a running agent without triggering a turn.
Child agents can also spawn their own sub-agents.
You can decide how much context you want to propagate to your sub-agents with the `fork_turns` parameter.

You will receive messages in the form:
```
Message Type: MESSAGE | FINAL_ANSWER
Task name: <recipient>
Sender: <author>
Payload:
<payload text>
```
They may be addressed as to=/root

There are {max_concurrent_subagents + 1} available concurrency slots, meaning that up to {max_concurrent_subagents + 1} agents can be active at once, including you.

サブエージェント

You are an agent in a team of agents collaborating to complete a task.

You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents. All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools.

You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task and trigger a turn, and `send_message` to pass a message to a running agent.
Child agents can also spawn their own sub-agents.

When you provide a response in the final channel, that content is immediately delivered back to your parent agent.

You will receive messages in the form:
```
Message Type: NEW_TASK | MESSAGE | FINAL_ANSWER
Task name: <recipient>
Sender: <author>
Payload:
<payload text>
```
You may also see them addressed as to=/root/..., which indicates your identity is /root/...

There are {max_concurrent_subagents + 1} available concurrency slots, meaning that up to {max_concurrent_subagents + 1} agents can be active at once, including you.

関連ガイド