Azure OpenAI Responses API を使用したプログラムによるツール呼び出し

プログラムによるツール呼び出しにより、モデルはコードを使用して、Responses API 要求中にツールをオーケストレーションできます。 モデルは、ツールの呼び出しごとに個別のモデル ターンを必要とするのではなく、ツールを呼び出して結果を処理するプログラムを生成できます。 プログラムは、分岐や反復などの操作を実行できます。

この記事では、プログラムによるツール呼び出しが便利な場合と、関数呼び出し、並列ツール呼び出し、およびツール検索とどのように異なるかについて説明します。

可用性に関する考慮事項

プログラムによるツール呼び出しを実装する前に、Azure OpenAI モデルのデプロイと SDK バージョンでこの機能がサポートされていることを確認してください。 Responses API のサポートによって、プログラムによるツール呼び出しのサポートも確立されるとは想定しないでください。

エンドポイントと認証のセットアップについては、「Azure OpenAI 応答 API を使用する」を参照してください。

プログラムによるツール呼び出しと関数呼び出し

標準関数呼び出しでは、通常、アプリケーションは各ツール呼び出し手順に参加します。

モデル -> ツール呼び出し -> アプリケーションがツールを実行する -> モデル -> 次のツール呼び出し -> アプリケーションがツールを実行する -> モデル

プログラム的なツール呼び出しにより、モデルは複数のツール操作を調整するコードを生成できます。

モデル -> 生成されたプログラム -> ツール操作 -> プログラムの結果 -> モデル

この方法では、複数のツールを含むワークフローに必要なモデル ターンの数を減らすことができます。

これらのフローは、オーケストレーションの概念上の違いを示しています。 アプリケーションまたはサービスが実行するどの操作かは指定されていません。

例: 在庫と需要の比較

このPython例では、サンプル インベントリと需要データを返す 2 つのアプリケーション定義関数を公開しています。 このモデルでは、プログラムによるツール呼び出しを使用して結果を比較します。 アプリケーションが関数を実行し、その結果を返して、ランタイムが生成されたプログラムを再開できるようにします。

コード ブロックは 1 つのスクリプトを形成します。 表示されている順序で結合してください。

Azure OpenAI クライアント

プログラムによるツール呼び出しをサポートする Azure OpenAI デプロイと、output_schema、allowed_callers、呼び出し元の情報を含む OpenAI SDK バージョンを使用します。 Python環境にopenaiパッケージとazure-identityパッケージをインストールしてください:

pip install --upgrade openai azure-identity

Azure OpenAI 応答 API の使用に関する説明に従って、Microsoft Entra ID認証を構成します。 ID には、Azure OpenAI デプロイを呼び出すアクセス許可が必要です。

リソース名をYOUR-RESOURCE-NAMEに置き換えます。 この例の後半のmodel値は、モデル カタログ識別子ではなく、Azureデプロイ名です。

import json

from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from openai import OpenAI

# Use the Azure OpenAI v1 endpoint and Microsoft Entra ID authentication.
endpoint = "https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/"
credential = DefaultAzureCredential()
token_provider = get_bearer_token_provider(
    credential, "https://cognitiveservices.azure.com/.default"
)
openai = OpenAI(base_url=endpoint, api_key=token_provider)

アプリケーション定義関数

関数は、42 個の使用可能なユニットと、指定された SKU に対して 31 個の要求ユニットという固定サンプル データを返します。 それらの実装を、アプリケーション内の在庫データ ソースと需要データ ソースに置き換えます。

def get_inventory(sku):
    return {"sku": sku, "available_units": 42}


def get_demand(sku):
    return {"sku": sku, "requested_units": 31}


implementations = {
    "get_inventory": get_inventory,
    "get_demand": get_demand,
}

ツール定義

各関数は、入力スキーマと JSON 結果を記述する output_schema を宣言します。 allowed_callersを ["programmatic"] に設定すると、直接モデル呼び出しではなく、生成されたプログラムで関数を使用できるようになります。

tools = [
    {
        "type": "function",
        "name": "get_inventory",
        "description": "Return the SKU and available inventory units.",
        "strict": True,
        "parameters": {
            "type": "object",
            "properties": {"sku": {"type": "string"}},
            "required": ["sku"],
            "additionalProperties": False,
        },
        "output_schema": {
            "type": "object",
            "properties": {
                "sku": {"type": "string"},
                "available_units": {"type": "number"},
            },
            "required": ["sku", "available_units"],
            "additionalProperties": False,
        },
        "allowed_callers": ["programmatic"],
    }
]

demand 関数とプログラムによるツール呼び出し機能を同じリストに追加します。

tools.extend([
    {
        "type": "function",
        "name": "get_demand",
        "description": "Return the SKU and requested demand units.",
        "strict": True,
        "parameters": {
            "type": "object",
            "properties": {"sku": {"type": "string"}},
            "required": ["sku"],
            "additionalProperties": False,
        },
        "output_schema": {
            "type": "object",
            "properties": {
                "sku": {"type": "string"},
                "requested_units": {"type": "number"},
            },
            "required": ["sku", "requested_units"],
            "additionalProperties": False,
        },
        "allowed_callers": ["programmatic"],
    },
    {"type": "programmatic_tool_calling"},
])

ツールの結果と呼び出し元情報

アプリケーションは、名前によって関数呼び出しをディスパッチし、各結果をシリアル化します。 ランタイムが正しいプログラムを再開できるように、呼び出しの caller 情報を保持します。 不明なツール名と無効な引数は、捏造された結果を返すのではなく、エラーを発生させます。

def run_tool(call):
    run = implementations.get(call.name)
    if run is None:
        raise ValueError(f"Unknown tool: {call.name}")

    arguments = json.loads(call.arguments)
    if (
        not isinstance(arguments, dict)
        or set(arguments) != {"sku"}
        or not isinstance(arguments["sku"], str)
    ):
        raise ValueError(f"Invalid arguments for tool: {call.name}")

    result = run(**arguments)
    output = {
        "type": "function_call_output",
        "call_id": call.call_id,
        "output": json.dumps(result),
    }
    if call.caller is not None:
        output["caller"] = call.caller.model_dump(exclude_none=True)
    return output

応答の継続

YOUR-MODEL-DEPLOYMENTを、サポートされている Azure OpenAI デプロイの名前に置き換えます。

store=Falseでは、アプリケーションは会話を進めます。 ツールの結果を追加する前に、プログラムや推論項目を含むすべての出力項目を保持してください。 ループは最終的な回答または拒否で停止し、応答が失敗した場合、または要求の上限に達した場合にエラーが発生します。

input_items = [
    {"role": "user", "content": "Compare inventory with demand for sku_123."}
]
MAX_REQUESTS = 10
for _ in range(MAX_REQUESTS):
    response = openai.responses.create(
        model="YOUR-MODEL-DEPLOYMENT",
        store=False, input=input_items, tools=tools,
    )
    if response.status != "completed":
        raise RuntimeError(f"Response ended with status {response.status}")
    input_items.extend(
        item.model_dump(exclude_none=True) for item in response.output
    )
    calls = [item for item in response.output if item.type == "function_call"]
    if calls:
        input_items.extend(run_tool(call) for call in calls)
        continue
    if response.output_text:
        print(response.output_text)
        break
    refusals = [
        part.refusal for item in response.output if item.type == "message"
        for part in item.content if part.type == "refusal"
    ]
    if refusals:
        print("\n".join(refusals))
        break
else:
    raise RuntimeError("Programmatic tool calling exceeded the request limit")

最後の答えは、在庫が需要をカバーしていることを説明する必要があります: 利用可能な42ユニットから要求された31ユニットを引くと、11ユニット残ります。 文言は異なる場合があります。 モデルが要求を拒否した場合は、代わりに拒否応答が出力されます。

アプリケーションは在庫関数と需要関数を実行しますが、生成されたプログラムはそれらの使用を調整し、結果を処理します。

プログラムからツール呼び出しを使用する場合

タスクが…の場合は、プログラムによるツール呼び出しを検討してください。

  • 関連するツール呼び出しがいくつか必要です。
  • 条件付きロジックが含まれています。
  • ツールの結果を反復して処理する必要があります。
  • 回答を生成する前に中間データ処理を実行します。
  • そうでなければ、ツールの連携を調整するためにモデルのターンを繰り返す必要があります。

1 つまたは 2 つの独立した関数を含む単純なワークフローでは、標準関数呼び出しを実装する方が簡単な場合があります。

プログラムによるツール呼び出しと並列ツール呼び出し

並列ツールの呼び出しとプログラムによるツール呼び出しは、さまざまなオーケストレーションのニーズに対応します。

複数の独立した操作を同時に実行できる場合は、 並列ツール呼び出し を使用します。 たとえば、5 つの独立した場所の天気を取得します。

ツール操作で調整、中間計算、分岐、または反復が必要な場合は、 プログラムによるツール呼び出し を使用します。 たとえば、顧客の注文を取得し、条件に一致する注文を選択し、それらの注文の出荷情報を照会します。

ツール検索は、モデルのコンテキストに読み込まれるツール定義を制御します。

プログラムによるツール呼び出しは、利用可能になった後にモデルがツールを連携実行する方法を制御します。

ツールの探索とツール オーケストレーションの区別です。 これらの概念上の関係からは、デプロイが同じ要求で両方の機能の使用をサポートしているかどうかは判断できません。

セキュリティに関する考慮事項

生成されたプログラムを信頼できないコードとして扱います。

ツールを公開する場合:

  • 最小特権の原則に従います。
  • ツールの引数を検証します。
  • 機密性の高い操作へのアクセスを制限します。
  • モデル出力とは無関係に操作を認証および承認します。
  • シークレットや不要な機密データを記録せずに、監査と診断のためにツールの実行をログに記録する。
  • 不要なツールやデータを表示しないようにします。

モデルによって生成されたコードを認可境界として使用しないでください。