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

Function Calling

指示に従い、プロンプトに応答するために利用できる新たな機能やデータへのアクセスをモデルに提供します。

Function Callingツール呼び出しとも呼ばれます)は、OpenAI モデルが外部システムと連携し、学習データに含まれないデータにアクセスするための強力で柔軟な方法です。このガイドでは、アプリケーションが提供するデータやアクションにモデルを接続する方法を説明します。JSON スキーマで定義する関数ツールと、自由形式のテキストを入出力するカスタムツールの使い方を紹介します。

Agents API のセッションでは、関数を使って関数を登録し、セッションのアクションリクエストを処理します。このガイドの例では、Responses API と Chat Completions との連携方法を示します。

アプリケーションに多数の関数や大きなスキーマがある場合は、Function Calling とツール検索を組み合わせることで、使用頻度の低いツールの読み込みを遅らせ、モデルが必要とするときにだけ読み込めます。tool_search に対応しているのは gpt-5.4 以降のモデルのみです。

GPT-6 Astra でツールを呼び出すには、Responses API が必要です。 Chat Completions の例では、互換性を確保するために GPT-5.6 を使用しています。既存の連携を更新するには、 移行 ガイドを参照してください。

仕組み

まず、ツール呼び出しに関する主な用語を確認しましょう。用語を理解したうえで、実践的な例を使ってツール呼び出しの方法を説明します。

ツール呼び出しの流れ

ツール呼び出しは、OpenAI API を介してアプリケーションとモデルの間で行われる、複数のステップからなるやり取りです。大きく分けると、次の 5 つのステップで構成されます。

  1. 呼び出し可能なツールを含めてモデルにリクエストを送信
  2. モデルからツール呼び出しを受信
  3. ツール呼び出しの入力を使ってアプリケーション側でコードを実行
  4. ツールの出力を含めてモデルに 2 回目のリクエストを送信
  5. モデルから最終応答、または追加のツール呼び出しを受信

Function Calling の手順を示す図

Responses を使うと、アプリケーションはタスクに必要な回数だけツールを呼び出し、この流れを継続できます。このループに伴う定型的なオーケストレーションをまとめて扱えるフレームワークが必要な場合は、Responses API と Agents SDK の比較を参照してください。

関数ツールの例

星座ごとの今日の運勢を取得する get_horoscope 関数を使って、ツール呼び出しの一連の流れを見てみましょう。

ツール呼び出しの完全な例
from openai import OpenAI
import json

client = OpenAI()

# 1. Define a list of callable tools for the model
tools = [
    {
        "type": "function",
        "name": "get_horoscope",
        "description": "Get today's horoscope for an astrological sign.",
        "parameters": {
            "type": "object",
            "properties": {
                "sign": {
                    "type": "string",
                    "description": "An astrological sign like Taurus or Aquarius",
                },
            },
            "required": ["sign"],
        },
    },
]


def get_horoscope(sign):
    return f"{sign}: Next Tuesday you will befriend a baby otter."


# Create a running input list we will add to over time
input_list = [{"role": "user", "content": "What is my horoscope? I am an Aquarius."}]

# 2. Prompt the model with tools defined
response = client.responses.create(
    model="gpt-6-astra",
    tools=tools,
    input=input_list,
)

# Save function call outputs for subsequent requests
input_list += response.output

for item in response.output:
    if item.type == "function_call":
        if item.name == "get_horoscope":
            # 3. Execute the function logic for get_horoscope
            sign = json.loads(item.arguments)["sign"]
            horoscope = get_horoscope(sign)

            # 4. Provide function call results to the model
            input_list.append(
                {
                    "type": "function_call_output",
                    "call_id": item.call_id,
                    "output": horoscope,
                }
            )

print("Final input:")
print(input_list)

response = client.responses.create(
    model="gpt-6-astra",
    instructions="Respond only with a horoscope generated by a tool.",
    tools=tools,
    input=input_list,
)

# 5. The model should be able to give a response!
print("Final output:")
print(response.model_dump_json(indent=2))
print("\n" + response.output_text)

GPT-5 や o4-mini などのリーズニングモデルでは、ツール呼び出しを含むモデルの応答で返された推論項目もすべて、ツール呼び出しの出力とともに送り返す必要があります。

関数の定義

関数は通常、各 API リクエストの tools パラメーターで宣言します。ツール検索を使うと、アプリケーションはやり取りの途中で、読み込みを遅らせていた関数を読み込むこともできます。どちらの場合も、呼び出し可能な関数は同じスキーマ構造を使用します。関数の定義には次のプロパティがあります。

フィールド説明
type常に function を指定します。
name関数名(例:get_weather
description関数を使用するタイミングと方法の詳細
parameters関数の入力引数を定義するJSON スキーマ
strict関数呼び出しに厳格モードを適用するかどうか

以下は、get_weather 関数の定義例です。

{
  "type": "function",
  "name": "get_weather",
  "description": "Retrieves current weather for the given location.",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "City and country e.g. Bogotá, Colombia"
      },
      "units": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "Units the temperature will be returned in."
      }
    },
    "required": ["location", "units"],
    "additionalProperties": false
  },
  "strict": true
}

parametersJSON スキーマで定義するため、プロパティの型、列挙型、説明、ネストしたオブジェクト、再帰的なオブジェクトなど、豊富な機能を活用できます。

名前空間の定義

名前空間を使って、関連するツールを crmbillingshipping などのドメインごとにグループ化します。名前空間は類似するツールの整理に役立ちます。特に、CRM 用の検索ツールとサポートチケットシステム用の検索ツールのように、対象システムや目的の異なるツールをモデルが選び分ける必要がある場合に便利です。

{
  "type": "namespace",
  "name": "crm",
  "description": "CRM tools for customer lookup and order management.",
  "tools": [
    {
      "type": "function",
      "name": "get_customer_profile",
      "description": "Fetch a customer profile by customer ID.",
      "parameters": {
        "type": "object",
        "properties": {
          "customer_id": { "type": "string" }
        },
        "required": ["customer_id"],
        "additionalProperties": false
      }
    },
    {
      "type": "function",
      "name": "list_open_orders",
      "description": "List open orders for a customer ID.",
      "defer_loading": true,
      "parameters": {
        "type": "object",
        "properties": {
          "customer_id": { "type": "string" }
        },
        "required": ["customer_id"],
        "additionalProperties": false
      }
    }
  ]
}

多数のツールで構成されるエコシステムへのアクセスをモデルに提供する場合は、tool_search を使って、一部またはすべてのツールの読み込みを遅らせることができます。tool_search ツールを使うと、モデルは関連するツールを検索し、モデルのコンテキストに追加してから使用できます。対応しているのは gpt-5.4 以降のモデルのみです。詳しくは、ツール検索ガイドを参照してください。

関数定義のベストプラクティス

  1. 関数名、パラメーターの説明、指示は、明確かつ詳細に記述します。

    • 関数と各パラメーターの目的を明示し 、パラメーターの形式や出力が何を表すかも説明します。
    • 各関数を使う場合と使わない場合を、システムプロンプトに記述します。 基本的には、何をすべきかをモデルに 具体的に 伝えます。
    • 特に繰り返し発生する失敗を改善するために、例やエッジケースを含めます。(注: 例を追加すると、リーズニングモデルの性能が低下する場合があります。)
    • 遅延読み込みするツールでは、詳細な指示を関数の説明に記載し、名前空間の説明は簡潔にします。 名前空間はモデルが読み込むツールを選ぶ際に役立ち、関数の説明は読み込んだツールを正しく使う際に役立ちます。
  2. ソフトウェアエンジニアリングのベストプラクティスを適用します。

    • 関数の動作を予測しやすく、直感的に使える設計にします。(驚き最小の原則
    • 列挙型を使い 、オブジェクトの構造を工夫して、不正な状態を防ぎます。たとえば、toggle_light(on: bool, off: bool) では不正な呼び出しができてしまいます。
    • インターンにも使えるかを確かめます。 モデルに与えた情報だけで、インターンなどの人間がその関数を正しく使えるでしょうか。(使えない場合、どのような質問をされますか。その答えをプロンプトに追加します。)
  3. 可能な処理はコードで実行し、モデルの負担を減らします。

    • すでにわかっている引数の値をモデルに指定させないようにします。 たとえば、前のメニューですでに order_id を取得している場合は、order_id パラメーターを含めないようにします。代わりに、パラメーターなしの submit_refund() を定義し、コード内で order_id を渡します。
    • 常に連続して呼び出す関数は、1 つにまとめます。 たとえば、query_location() の後に必ず mark_location() を呼び出す場合は、マークを付ける処理をクエリ関数に組み込むだけで済みます。
  4. 精度を高めるために、最初から利用できる関数の数を少なくします。

    • 関数の数を変えて、性能を評価します
    • あくまで目安ですが、各ターンの開始時に利用できる関数は 20 個未満に抑えることを目指します
    • すべてのツールを最初から公開するのではなく、ツール検索を使用して 、ツール群のうち規模が大きい部分や使用頻度の低い部分を遅延読み込みします。
  5. OpenAI のリソースを活用します。

    • Playground関数スキーマを生成し、改善を重ねます
    • 関数の数が多い場合や難しいタスクでは、Function Calling の精度を高めるためにファインチューニングを検討します 。(Cookbook

トークン使用量

内部では、モデルが学習した構文で関数がシステムメッセージに挿入されます。そのため、呼び出し可能な関数の定義はモデルのコンテキスト上限に算入され、入力トークンとして課金されます。トークン上限に達する場合は、最初に読み込む関数の数を制限する、可能な範囲で説明を短くする、またはツール検索を使って必要なときだけツールを遅延読み込みすることをお勧めします。

ツールの仕様に多数の関数を定義している場合は、ファインチューニングを使ってトークン使用量を減らすことも可能です。

関数呼び出しの処理

モデルが関数を呼び出したら、その関数を実行して結果を返す必要があります。モデルの応答に含まれる呼び出しの数は、0 件、1 件、または複数件となるため、複数の呼び出しがあることを想定しておくのがベストプラクティスです。

応答の output 配列には、type の値が function_call の要素が含まれます。各要素には、call_id(後で関数の結果を送信する際に使用)、name、JSON エンコードされた arguments があります。

複数の関数呼び出しを含む応答例
[
    {
        "id": "fc_12345xyz",
        "call_id": "call_12345xyz",
        "type": "function_call",
        "name": "get_weather",
        "arguments": "{\"location\":\"Paris, France\"}"
    },
    {
        "id": "fc_67890abc",
        "call_id": "call_67890abc",
        "type": "function_call",
        "name": "get_weather",
        "arguments": "{\"location\":\"Bogotá, Colombia\"}"
    },
    {
        "id": "fc_99999def",
        "call_id": "call_99999def",
        "type": "function_call",
        "name": "send_email",
        "arguments": "{\"to\":\"bob@email.com\",\"body\":\"Hi bob\"}"
    }
]

ツール検索を使用している場合、function_call の前に tool_search_calltool_search_output の項目が含まれることもあります。関数が読み込まれたら、ここで示す方法と同様に関数呼び出しを処理します。

関数呼び出しの実行と結果の追加
input_messages += response.output

for tool_call in response.output:
    if tool_call.type != "function_call":
        continue

    name = tool_call.name
    args = json.loads(tool_call.arguments)

    result = call_function(name, args)
    input_messages.append(
        {
            "type": "function_call_output",
            "call_id": tool_call.call_id,
            "output": json.dumps(result),
        }
    )

上の例では、各呼び出しを適切な関数に振り分けるために、仮の call_function を使用しています。実装例を次に示します。

関数呼び出しの実行と結果の追加
def call_function(name, args):
    if name == "get_weather":
        return get_weather(**args)
    if name == "send_email":
        return send_email(**args)
    raise ValueError(f"Unknown function: {name}")

結果の形式

function_call_output メッセージで渡す結果は、通常は文字列にします。形式は JSON、エラーコード、プレーンテキストなど、自由に選べます。モデルはその文字列を必要に応じて解釈します。

画像やファイルを返す関数では、文字列の代わりに画像またはファイルのオブジェクトの配列を渡せます。

関数に戻り値がない場合(例:send_email)は、"success" など、成功または失敗を示す文字列を返します。

応答への結果の反映

input に結果を追加してからモデルに送り返すと、最終応答を取得できます。

モデルへの結果の返送
response = client.responses.create(
    model="gpt-6-astra",
    input=input_messages,
    tools=responses_tools,
)

print(response.output_text)
最終応答
"It's about 15°C in Paris, 18°C in Bogotá, and I've sent that email to Bob."

追加の設定

ツールの選択

デフォルトでは、ツールを使うタイミングと数をモデルが判断します。tool_choice パラメーターで特定の動作を強制できます。

  1. 自動:デフォルト)関数を呼び出さないか、1 つ以上の関数を呼び出します。tool_choice: "auto"
  2. 必須: 1 つ以上の関数を呼び出します。 tool_choice: "required"
  3. 特定の関数を強制: 指定した関数を必ず 1 回だけ呼び出します。 tool_choice: {"type": "function", "name": "get_weather"}
  4. 許可するツール: モデルが呼び出せるツールを、 利用可能なツールの一部に制限します。

allowed_tools の使用場面

モデルへのリクエストで使えるツールを一部に制限しつつ、 渡すツールのリストは変更せずにプロンプトキャッシュによるコスト削減効果を最大限に得たい場合は、allowed_tools リストの設定が役立ちます。

"tool_choice": {
    "type": "allowed_tools",
    "mode": "auto",
    "tools": [
        { "type": "function", "name": "get_weather" },
        { "type": "function", "name": "search_docs" }
    ]
  }
}

tool_choice"none" に設定すると、関数を渡さなかった場合と同じ動作にすることもできます。

ツール検索を使う場合も、tool_choice はそのターンで現在呼び出せるツールに適用されます。これは、ツールの一部を読み込んだ後、モデルが使えるツールをその範囲に制限したい場合に特に役立ちます。

並列 Function Calling

GPT-5 以降の対応モデルでは、 組み込みツールが利用可能な場合でも、関数を並列に呼び出せます。 ただし、組み込みツールを関数の並列呼び出しのバッチに含めることはできません。

モデルは、1 ターンで複数の関数を呼び出すことがあります。parallel_tool_callsfalse に設定すると、これを防ぎ、ツールの呼び出しを必ず 0 回または 1 回に制限できます。

注: 現在、ファインチューニング済みのモデルが 1 ターンで複数の関数を呼び出した場合、それらの呼び出しでは厳格モードが無効になります。

gpt-4.1-nano-2025-04-14 に関する注意: この gpt-4.1-nano のスナップショットでは、ツールの並列呼び出しが有効な場合、同じツールに対する呼び出しが複数含まれることがあります。このスナップショットを使う際は、この機能を無効にすることをおすすめします。

厳格モード

stricttrue に設定すると、関数呼び出しがベストエフォートではなく、関数スキーマに確実に準拠するようになります。厳格モードは常に有効にすることをお勧めします。

厳格モードは内部で構造化出力機能を使うため、次の要件を満たす必要があります。

  1. parameters 内の各オブジェクトで、additionalPropertiesfalse に設定する必要があります。
  2. properties 内のすべてのフィールドを required に指定する必要があります。

type の選択肢に null を追加すると、省略可能なフィールドを表現できます(以下の例を参照)。

strict: true を指定して送信したスキーマが上記の要件を満たしていない場合、 リクエストは拒否され、不足している制約の詳細が返されます。 strict を省略した場合のデフォルトの動作は API によって異なります。Responses へのリクエストでは、 可能であればスキーマを厳格モードに適合する形に正規化します。 スキーマを厳格モードに適合させられない場合は、 厳格モードを使わないベストエフォートの Function Calling にフォールバックします。 フォールバックが発生すると、レスポンス内のツールに strict: false が表示されます。Chat Completions へのリクエストでは、引き続きデフォルトで厳格モードが無効です。 Responses で厳格モードを使わず、ベストエフォートの Function Calling を維持するには、 strict: false を明示的に設定してください。

{
    "type": "function",
    "name": "get_weather",
    "description": "Retrieves current weather for the given location.",
    "strict": true,
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "City and country e.g. Bogotá, Colombia"
            },
            "units": {
                "type": ["string", "null"],
                "enum": ["celsius", "fahrenheit"],
                "description": "Units the temperature will be returned in."
            }
        },
        "required": ["location", "units"],
        "additionalProperties": false
    }
}

Playground で生成されたすべてのスキーマでは、 厳格モードが有効になっています。

厳格モードを有効にすることをお勧めしますが、いくつかの制限があります。

  1. JSON スキーマの一部の機能はサポートされていません(サポートされているスキーマを参照)。

ファインチューニング済みのモデルには、さらに次の制限があります。

  1. スキーマは最初のリクエスト時に追加の処理を受け、その後キャッシュされます。リクエストごとにスキーマが異なると、レイテンシーが増加する場合があります。
  2. スキーマはパフォーマンス向上のためにキャッシュされ、ゼロデータ保持の対象にはなりません。

ストリーミング

ストリーミングを使用すると、モデルが引数を生成している間に、呼び出す関数を表示して進捗を伝えられます。引数自体をリアルタイムで表示することもできます。

関数呼び出しのストリーミングは、通常のレスポンスのストリーミングとよく似ています。streamtrue に設定すると、さまざまな event オブジェクトを受け取れます。

関数呼び出しのストリーミング
from openai import OpenAI

client = OpenAI()

tools = [
    {
        "type": "function",
        "name": "get_weather",
        "description": "Get current temperature for a given location.",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "City and country e.g. Bogotá, Colombia",
                }
            },
            "required": ["location"],
            "additionalProperties": False,
        },
    }
]

stream = client.responses.create(
    model="gpt-6-astra",
    input=[{"role": "user", "content": "What's the weather like in Paris today?"}],
    tools=tools,
    stream=True,
)

for event in stream:
    print(event)
出力イベント
{"type":"response.output_item.added","response_id":"resp_1234xyz","output_index":0,"item":{"type":"function_call","id":"fc_1234xyz","call_id":"call_1234xyz","name":"get_weather","arguments":""}}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"{\""}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"location"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"\":\""}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"Paris"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":","}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":" France"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"\"}"}
{"type":"response.function_call_arguments.done","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"arguments":"{\"location\":\"Paris, France\"}"}
{"type":"response.output_item.done","response_id":"resp_1234xyz","output_index":0,"item":{"type":"function_call","id":"fc_1234xyz","call_id":"call_1234xyz","name":"get_weather","arguments":"{\"location\":\"Paris, France\"}"}}

ただし、チャンクを集約する先は、単一の content 文字列ではなく、エンコードされた arguments JSON オブジェクトです。

モデルが 1 つ以上の関数を呼び出すと、関数呼び出しごとに response.output_item.added 型のイベントが発行されます。このイベントには、次のフィールドが含まれます。

フィールド説明
response_id関数呼び出しが属するレスポンスの ID
output_indexレスポンス内の出力項目のインデックスです。レスポンス内の個々の関数呼び出しを表します。
itemnameargumentsid フィールドを含む、進行中の関数呼び出し項目

続いて、arguments フィールドの delta を含む response.function_call_arguments.delta 型のイベントを順次受け取ります。これらのイベントには、次のフィールドが含まれます。

フィールド説明
response_id関数呼び出しが属するレスポンスの ID
item_id差分が属する関数呼び出し項目の ID
output_indexレスポンス内の出力項目のインデックスです。レスポンス内の個々の関数呼び出しを表します。
deltaarguments フィールドの差分です。

以下のコードスニペットは、複数の delta を集約して最終的な tool_call オブジェクトにまとめる方法を示しています。

tool_call の差分の蓄積
final_tool_calls = {}

for event in stream:
    if event.type == "response.output_item.added":
        final_tool_calls[event.output_index] = event.item
    elif event.type == "response.function_call_arguments.delta":
        index = event.output_index

        if final_tool_calls[index]:
            final_tool_calls[index].arguments += event.delta
蓄積した final_tool_calls[0]
{
    "type": "function_call",
    "id": "fc_1234xyz",
    "call_id": "call_2345abc",
    "name": "get_weather",
    "arguments": "{\"location\":\"Paris, France\"}"
}

モデルが関数の呼び出しを完了すると、response.function_call_arguments.done 型のイベントが発行されます。このイベントには、次のフィールドを含む関数呼び出し全体が格納されます。

フィールド説明
response_id関数呼び出しが属するレスポンスの ID
output_indexレスポンス内の出力項目のインデックスです。レスポンス内の個々の関数呼び出しを表します。
itemnameargumentsid フィールドを含む関数呼び出し項目です。

カスタムツール

カスタムツールは、JSON スキーマに基づく関数ツールとほぼ同じように動作します。ただし、ツールが必要とする入力をモデルに明示的に指定する代わりに、モデルは任意の文字列を入力としてツールに渡せます。これは、レスポンスを不必要に JSON でラップするのを避けたり、レスポンスにカスタム文法を適用したりする場合に役立ちます。カスタム文法については後述します。

次のコードサンプルは、Python コードを含む文字列をレスポンスとして受け取るカスタムツールの作成方法を示しています。

カスタムツール呼び出しの例
from openai import OpenAI

client = OpenAI()

response = client.responses.create(
    model="gpt-6-astra",
    input="Use the code_exec tool to print hello world to the console.",
    tools=[
        {
            "type": "custom",
            "name": "code_exec",
            "description": "Executes arbitrary Python code.",
        }
    ],
)
print(response.output)

これまでと同様に、output 配列にはモデルが生成したツール呼び出しが含まれます。ただし今回は、ツール呼び出しの入力がプレーンテキストで渡されます。

[
  {
    "id": "rs_6890e972fa7c819ca8bc561526b989170694874912ae0ea6",
    "type": "reasoning",
    "content": [],
    "summary": []
  },
  {
    "id": "ctc_6890e975e86c819c9338825b3e1994810694874912ae0ea6",
    "type": "custom_tool_call",
    "status": "completed",
    "call_id": "call_aGiFQkRWSWAIsMQ19fKqxUgb",
    "input": "print(\"hello world\")",
    "name": "code_exec"
  }
]

文脈自由文法

文脈自由文法(CFG)は、特定の形式に従った有効なテキストを生成する方法を定義するルールの集合です。カスタムツールでは、CFG を指定して、モデルがそのツールに渡すテキスト入力を制約できます。

カスタムツールの設定時に、grammar パラメーターを使って独自の CFG を指定できます。現在、文法の定義には larkregex の 2 種類の CFG 構文をサポートしています。

Lark CFG

Lark の文脈自由文法の例
from openai import OpenAI

client = OpenAI()

grammar = """
start: expr
expr: term (SP ADD SP term)* -> add
| term
term: factor (SP MUL SP factor)* -> mul
| factor
factor: INT
SP: " "
ADD: "+"
MUL: "*"
%import common.INT
"""

response = client.responses.create(
    model="gpt-6-astra",
    input="Use the math_exp tool to add four plus four.",
    tools=[
        {
            "type": "custom",
            "name": "math_exp",
            "description": "Creates valid mathematical expressions",
            "format": {
                "type": "grammar",
                "syntax": "lark",
                "definition": grammar,
            },
        }
    ],
)
print(response.output)

これにより、ツールの出力は定義した Lark CFG に従うはずです。

[
  {
    "id": "rs_6890ed2b6374819dbbff5353e6664ef103f4db9848be4829",
    "type": "reasoning",
    "content": [],
    "summary": []
  },
  {
    "id": "ctc_6890ed2f32e8819daa62bef772b8c15503f4db9848be4829",
    "type": "custom_tool_call",
    "status": "completed",
    "call_id": "call_pmlLjmvG33KJdyVdC4MVdk5N",
    "input": "4 + 4",
    "name": "math_exp"
  }
]

文法は、Lark の派生構文を使用して指定します。モデルのサンプリングは LLGuidance を使用して制約されます。Lark の次の機能はサポートされていません。

  • 字句解析器の正規表現における先読み・後読み
  • 字句解析器の正規表現における非貪欲修飾子(*?+???
  • 終端記号の優先度
  • テンプレート
  • インポート(組み込みの %import common を除く)
  • %declare

カスタム文法を試すには、Lark IDE の使用をおすすめします。

文法の複雑さの抑制

文法には、ツールに必要なルールとパターンだけを含めます。文法が複雑すぎると OpenAI API がエラーを返す場合があるため、使いたい文法が対応しているかどうかを API で使用する前に確認してください。

Lark 文法を適切に仕上げるのは難しい場合があります。単純な文法ほど安定して動作しますが、複雑な文法では、モデルが学習時の分布から逸脱しないように、文法の定義そのものやプロンプト、ツールの説明を繰り返し調整する必要があります。

正しいパターンと誤ったパターン

正しい例(長さに上限のある単一の終端記号):

start: SENTENCE
SENTENCE: /[A-Za-z, ]*(the hero|a dragon|an old man|the princess)[A-Za-z, ]*(fought|saved|found|lost)[A-Za-z, ]*(a treasure|the kingdom|a secret|his way)[A-Za-z, ]*\./

次のように、複数のルールや終端記号に分割しないでください。この方法では、ルールを使って自由形式のテキストを終端記号ごとに分割しようとしています。レキサーは自由形式のテキスト部分に貪欲にマッチするため、意図どおりに制御できなくなります:

start: sentence
sentence: /[A-Za-z, ]+/ subject /[A-Za-z, ]+/ verb /[A-Za-z, ]+/ object /[A-Za-z, ]+/

小文字で定義したルールは、入力から終端記号を切り出す方法には影響しません。影響するのは終端記号の定義だけです。「アンカー間の自由形式のテキスト」が必要な場合は、全体を単一の正規表現の終端記号にまとめてください。これにより、レキサーは意図した構造に一度だけマッチします。

終端記号とルールの違い

Lark では、レクサーのトークンに終端記号(慣例では UPPERCASE)、パーサーの生成規則にルール(慣例では lowercase)を使います。サポートされる構文の範囲内で予期しない動作を避けるには、文法を明示的に記述して不要な複雑さを避け、終端記号とルールの役割を明確に分けるのが最も実用的です。

終端記号で使われる正規表現の構文は、Python の re モジュールの構文ではなく、Rust の regex クレートの構文です。

基本的な考え方とベストプラクティス

レキサーはパーサーより先に実行されます

CFG のルールのロジックが適用される前に、レキサーが終端記号にマッチします(貪欲マッチで、最長の一致が優先されます)。終端記号を複数のルールに分割してマッチの仕方を制御しようとしても、レキサーはそれらのルールには従いません。従うのは終端記号の正規表現だけです。

自由形式の範囲からテキストを切り出す場合は、単一の終端記号を使います

任意のテキストに埋め込まれたパターン(たとえば、アンカー間に「あらゆる内容」を含む自然言語)を認識する必要がある場合は、単一の終端記号として表現します。自由形式のテキストを表す終端記号とパーサーのルールを交互に組み合わせないでください。レクサーは貪欲にマッチするため、意図した境界を守らず、モデルが学習時の分布から逸脱する可能性が非常に高くなります。

個別のトークンを組み合わせるにはルールを使います

ルールは、明確に区切られた終端記号(数値、キーワード、句読点)を組み合わせて、より大きな構造を作る場合に適しています。2 つの終端記号の「間にある内容」を制約する用途には適していません。

終端記号は役割を絞り、範囲に上限を設け、自己完結させます

明示的な文字クラスと、上限のある量指定子を使います(あらゆる箇所で上限のない * を使うのではなく、{0,10} などを使います)。「ピリオドまでの任意のテキスト」が必要な場合は、マッチする範囲が際限なく広がらないよう、/.+\./ よりも /[^.\n]{0,10}*\./ のような表現を使います。

ルールは正規表現の内部動作の制御ではなく、トークンの組み合わせに使います

適切なルールの使用例:

start: expr
NUMBER: /[0-9]+/
PLUS: "+"
MINUS: "-"
expr: term (("+"|"-") term)*
term: NUMBER

空白文字を明示的に扱います

上限のない %ignore ディレクティブに頼らないでください。上限のない無視ディレクティブを使うと、文法が複雑になりすぎたり、モデルが分布外の出力を生成したりする可能性があります。空白文字を許容するすべての箇所に、明示的な終端記号を組み込む方法を推奨します。

トラブルシューティング

  • 文法が複雑すぎるために API が拒否する場合は、ルールと終端記号を簡略化し、上限のない %ignore を削除してください。
  • 想定外のトークンでカスタムツールが呼び出される場合は、終端記号のマッチ範囲が重複していないか確認し、レキサーの貪欲マッチの動作を調べてください。
  • モデルが「分布外」の出力を生成する場合(構文的には有効でも意味的には誤った、過度に長い出力や繰り返しの多い出力として現れます):
    • 文法の制約を厳しくしてください。
    • プロンプトとツールの説明を繰り返し改善してください。プロンプトにはフューショットの例を追加し、ツールの説明では文法を説明して、それに従って推論し出力するようモデルに指示します。
    • 推論強度を上げて試してください(たとえば、medium から high に変更します)。

正規表現 CFG

正規表現による文脈自由文法の例
from openai import OpenAI

client = OpenAI()

grammar = r"^(?P<month>January|February|March|April|May|June|July|August|September|October|November|December)\s+(?P<day>\d{1,2})(?:st|nd|rd|th)?\s+(?P<year>\d{4})\s+at\s+(?P<hour>0?[1-9]|1[0-2])(?P<ampm>AM|PM)$"

response = client.responses.create(
    model="gpt-6-astra",
    input="Use the timestamp tool to save a timestamp for August 7th 2025 at 10AM.",
    tools=[
        {
            "type": "custom",
            "name": "timestamp",
            "description": "Saves a timestamp in date + time in 24-hr format.",
            "format": {
                "type": "grammar",
                "syntax": "regex",
                "definition": grammar,
            },
        }
    ],
)
print(response.output)

この場合、ツールの出力は、定義した正規表現 CFG に準拠するはずです:

[
  {
    "id": "rs_6894f7a3dd4c81a1823a723a00bfa8710d7962f622d1c260",
    "type": "reasoning",
    "content": [],
    "summary": []
  },
  {
    "id": "ctc_6894f7ad7fb881a1bffa1f377393b1a40d7962f622d1c260",
    "type": "custom_tool_call",
    "status": "completed",
    "call_id": "call_8m4XCnYvEmFlzHgDHbaOCFlK",
    "input": "August 7th 2025 at 10AM",
    "name": "timestamp"
  }
]

Lark 構文の場合と同様、正規表現には Python の re モジュールの構文ではなく、Rust の regex クレートの構文を使います。

正規表現の一部の機能はサポートされていません:

  • 先読み・後読み
  • 最短一致の修飾子(*?+???

基本的な考え方とベストプラクティス

パターンは 1 行で記述する必要があります

入力内の改行にマッチさせるには、エスケープシーケンス \n を使ってください。パターンを複数行にわたって記述できる verbose/extended モードは使わないでください。

正規表現はパターン文字列をそのまま指定します

パターンを // で囲まないでください。