Structured OutputsとFunction Callingの違い|Pythonで使い分け

Structured OutputsとFunction Callingの違いを解説するアイキャッチ LLM

OpenAI APIでJSONを扱おうとすると、Structured OutputsとFunction Callingのどちらを使うべきか迷うことがあります。どちらもJSON Schemaを使えますが、目的は同じではありません。

結論から言うと、使い分けは次のとおりです。

  • モデルの最終回答を決まったJSON形式で受け取りたい → Structured Outputs
  • モデルにアプリ側の関数やAPIを選ばせたい → Function Calling
  • 外部データを取得し、その結果を決まった形式で返したい → 両方を組み合わせる

前回の「Function Calling入門|Pythonで複数ツールを呼ぶ」では、モデルが複数ツールを選び、Python側が実行する流れを解説しました。今回はその続編として、Structured Outputsとの違いをPythonコードで比較します。

スポンサーリンク

Structured OutputsとFunction Callingの違い

まずは全体像を図で確認します。

Structured OutputsとFunction Callingの使い分け

両者の違いを表にまとめると、次のようになります。

比較項目 Structured Outputs Function Calling
主な目的 最終回答の形式を固定する 使用するツールと引数を決める
モデルが返すもの Schemaに従ったJSON ツール名と引数
関数の実行 行わない アプリ側が行う
主な用途 抽出、分類、UI用データ 検索、DB参照、API操作
Pythonでの代表的な方法 responses.parse() responses.create(tools=...)
strictの役割 出力Schemaへの準拠 関数引数Schemaへの準拠

OpenAI公式のStructured Outputsガイドでも、システム内のツールやデータへ接続する場合はFunction Calling、ユーザーへ返す回答を構造化する場合はStructured Outputsという整理が示されています。

スポンサーリンク

Structured Outputsとは

Structured Outputsは、モデルの回答を指定したJSON Schemaへ合わせる仕組みです。

たとえば、問い合わせ文から次の情報を取り出したいとします。

  • 問い合わせカテゴリ
  • 優先度
  • 内容の要約
  • 人間による確認が必要か

通常の文章生成では、キー名が変わったり、余計な説明文が付いたりする可能性があります。Structured Outputsを使うと、後続のPython処理が期待する形式へ固定できます。

PythonでStructured Outputsを使う

Python SDKでは、Pydanticモデルをtext_formatへ渡す方法が分かりやすいです。

from enum import Enum

from openai import OpenAI
from pydantic import BaseModel, Field

client = OpenAI()

class Priority(str, Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"

class SupportTicket(BaseModel):
    category: str = Field(description="問い合わせの分類")
    priority: Priority
    summary: str = Field(description="問い合わせ内容の短い要約")
    needs_human: bool

response = client.responses.parse(
    model="gpt-5.6-terra",
    instructions=(
        "問い合わせを分類してください。本文にない事実は補わず、"
        "返金や削除などの判断が必要ならneeds_humanをtrueにしてください。"
    ),
    input="届いたキーボードが破損していました。返金方法を教えてください。",
    text_format=SupportTicket,
)

ticket = response.output_parsed
if ticket is None:
    raise RuntimeError("問い合わせを構造化できませんでした")

print(ticket.model_dump_json(indent=2))

出力は次のようなPythonオブジェクトとして扱えます。

{
  "category": "返品・返金",
  "priority": "high",
  "summary": "破損したキーボードの返金方法に関する問い合わせ",
  "needs_human": true
}

Pydanticを使うことで、Schemaを手書きする量を減らし、取得後の型も明確にできます。APIの応答をそのまま信用せず、output_parsedが得られたかを確認してから後続処理へ渡します。

Structured Outputsが向いている用途

次のような処理はStructured Outputs向きです。

  • メールや問い合わせの分類
  • 論文からタイトル、著者、キーワードを抽出
  • 商品説明から属性を抽出
  • 画面表示用のカードデータを作る
  • 文章からタスク一覧を生成
  • 判定結果をDBへ保存する前の整形

共通点は、モデルに外部機能を実行させる必要がなく、回答形式だけを固定したいことです。

Function Callingとは

Function Callingは、モデルへ利用可能なツールを伝え、必要なツール名と引数を選ばせる仕組みです。

たとえば「注文ORD-1001はいつ届きますか?」という質問に答えるには、モデルの知識ではなく、現在の注文データを確認する必要があります。そこでget_order_statusツールを定義します。

ORDER_TOOLS = [
    {
        "type": "function",
        "name": "get_order_status",
        "description": "注文IDから現在の配送状況と配送予定日を取得する。",
        "parameters": {
            "type": "object",
            "properties": {
                "order_id": {
                    "type": "string",
                    "description": "ORD-1001のような注文ID",
                }
            },
            "required": ["order_id"],
            "additionalProperties": False,
        },
        "strict": True,
    }
]

Responses APIへツール定義と質問を送ります。

response = client.responses.create(
    model="gpt-5.6-terra",
    instructions=(
        "注文状況は必ずツールで確認してください。"
        "確認できない内容を推測で補わないでください。"
    ),
    tools=ORDER_TOOLS,
    input="注文ORD-1001はいつ届きますか?",
)

モデルは注文状況を直接取得しません。必要だと判断すると、get_order_statusという関数名とorder_idfunction_callとして返します。

Python側は次の処理を担当します。

  1. ツール名が許可リストにあるか確認する
  2. 引数のJSONを解析する
  3. 注文IDなどの値を検証する
  4. 実際の関数やAPIを呼び出す
  5. 結果をfunction_call_outputとしてモデルへ返す

Function Callingは、モデルがPython関数を直接実行する機能ではありません。アプリ側が実行を管理するため、購入、削除、公開、外部送信などには別途承認処理を入れられます。

詳しい実行ループとcall_idの扱いは、前回のFunction Calling入門で解説しています。

同じJSON Schemaでも目的が違う

Structured OutputsとFunction Callingは、どちらもJSON Schemaを利用します。そのため見た目が似ていますが、Schemaが制約する対象が異なります。

Structured Outputsの場合

Schemaが制約するのは、モデルがユーザーへ返す最終回答です。

ユーザー入力
  ↓
モデル
  ↓
指定SchemaのJSON

Function Callingの場合

Schemaが制約するのは、アプリ側の関数へ渡す引数です。

ユーザー入力
  ↓
モデルがツールを選択
  ↓
Schemaに従った関数引数
  ↓
アプリ側が関数を実行

「JSONが欲しいからStructured Outputs」とだけ考えるのではなく、そのJSONが最終回答なのか、関数への入力なのかで判断します。

strict modeの共通ルール

Function Callingでは、ツール定義へstrict: trueを設定すると、関数引数がSchemaへ準拠しやすくなります。OpenAI公式のFunction Callingガイドでは、strict modeを有効にすることが推奨されています。

Strict modeでは、主に次の点へ注意します。

  • オブジェクトにadditionalProperties: falseを設定する
  • propertiesに定義したフィールドをrequiredへ含める
  • 省略可能な値は型にnullを含めて表現する
  • 対応しているJSON Schemaの範囲を確認する

たとえば配送予定日が未定の場合、フィールド自体を省略するのではなく、stringまたはnullとして定義します。

{
  "delivery_date": {
    "type": ["string", "null"],
    "description": "配送予定日。未定の場合はnull"
  }
}

Schemaに従うことと、内容が事実として正しいことは別問題です。priorityが必ず文字列で返っても、その優先度判定が業務ルールに合っているとは限りません。重要な判定はPython側でも検証してください。

JSON Modeとの違い

JSON Modeは、有効なJSONを返すことを目的とする機能です。一方、Structured Outputsは有効なJSONであることに加えて、指定したSchemaへの準拠を目指します。

機能 有効なJSON 指定Schemaへの準拠
JSON Mode 保証しない
Structured Outputs

新しい実装で利用モデルが対応している場合は、キーの存在や型まで制御できるStructured Outputsを優先すると、独自の再試行処理を減らせます。ただし、利用するモデルとSDKが対応しているかは実装時に公式ドキュメントで確認してください。

両方を組み合わせる例

実際のアプリでは、Function CallingとStructured Outputsを組み合わせることがあります。

たとえば注文サポート画面なら、次の流れです。

  1. Function Callingで注文APIを呼ぶ
  2. Python側が注文状況を取得する
  3. モデルへツール結果を返す
  4. Structured Outputsで画面表示用のJSONを生成する
  5. アプリがステータスカードとして表示する

最初のFunction Callingは「外部データを取得する役割」、最後のStructured Outputsは「UIへ渡す形式を固定する役割」です。

質問
  ↓
Function Calling
  ↓
注文APIの結果
  ↓
Structured Outputs
  ↓
画面表示用JSON

このように役割を分けると、ツール実行と画面表示の責務が混ざりません。

迷ったときの選び方

次の順番で考えると判断しやすくなります。

質問1:外部の情報や機能が必要か

必要ならFunction Callingを検討します。

  • データベースを検索する
  • 在庫や天気を取得する
  • 社内APIを呼ぶ
  • ファイルを作成する
  • メール送信や予約を行う

質問2:モデルの最終回答を機械処理するか

機械処理するならStructured Outputsを検討します。

  • JSONをDBへ保存する
  • UIの部品へ割り当てる
  • 別の処理へ渡す
  • 分類結果で分岐する

質問3:外部機能と固定形式の両方が必要か

両方必要なら併用します。Function Callingでデータを集め、Structured Outputsで最終結果を整えます。

実装時によくある勘違い

Structured Outputsなら内容も正しいとは限らない

保証されるのは主に形式です。入力文にない情報を補ったり、分類を誤ったりする可能性は残ります。金額、権限、医療、法務などの重要な内容は別の検証が必要です。

Function Callingが関数を実行してくれるわけではない

モデルが返すのは関数名と引数です。実行、認証、タイムアウト、再試行、ログ、承認はアプリ側で実装します。

Schemaだけで業務ルールを表現しようとする

文字列形式や必須項目はSchemaで制約できますが、「このユーザーがこの注文を閲覧できるか」といった権限確認はPython側で行います。

Schemaを変更して既存処理を壊す

フィールド名やenumを変更すると、保存処理やUIが動かなくなることがあります。SchemaもAPI仕様としてバージョン管理し、変更時はテストしてください。

失敗時の分岐を用意していない

拒否、出力不足、ツール障害、未知のIDなどを正常系とは分けて扱います。失敗した値を空文字で埋めると、成功したように見えるため危険です。

テストで確認する項目

最低限、次のケースを用意します。

  • 通常の入力で期待した型になる
  • 必須情報がない入力を処理できる
  • enumの境界値を処理できる
  • 長文や曖昧な文章でも壊れない
  • モデルがツールを呼ばない場合を処理できる
  • 未知のツール名を拒否できる
  • 引数のJSONが不正でも終了しない
  • 外部APIのタイムアウトを処理できる
  • 承認が必要な操作を自動実行しない

実運用前のチェックリスト

  • ☐ 最終回答の固定か、外部機能の利用かを先に決めた
  • ☐ Structured Outputsの取得後にoutput_parsedを確認した
  • ☐ Function Callingのツールを許可リストで限定した
  • strictとJSON Schemaの要件を確認した
  • ☐ Schemaとは別に値や権限をPython側で検証した
  • ☐ 拒否、出力不足、未知ID、API障害をテストした
  • ☐ 外部送信、購入、削除、公開の前に承認を入れた
  • ☐ Schema変更をバージョン管理した

よくある質問

Structured OutputsだけでAPIを呼べますか?

呼べません。Structured Outputsは回答形式を固定するための仕組みです。外部APIやPython関数を利用する場合はFunction Callingなどと組み合わせ、実行はアプリ側で行います。

Function Callingでもstrictを使えますか?

使えます。関数ツールへstrict: trueを設定し、引数Schemaを厳密にします。additionalProperties: falseと必須フィールドの定義を忘れないようにします。

Pydanticは必須ですか?

必須ではありません。JSON Schemaを直接定義することもできます。ただしPythonでは、Pydanticを使うとSchema定義と取得後の型をまとめて管理しやすくなります。

どちらを先に覚えるべきですか?

文章の抽出や分類を作りたいならStructured Outputs、外部データを取得するAIエージェントを作りたいならFunction Callingから始めるとよいでしょう。

まとめ

Structured OutputsとFunction Callingは、JSON Schemaを使う点は似ていますが役割が異なります。

  • Structured Outputs:モデルの最終回答の形式を固定する
  • Function Calling:モデルに利用するツールと引数を選ばせる
  • 併用:Function Callingで情報を取得し、Structured Outputsで表示形式を固定する

迷ったら、「モデルに何かを実行させたいのか」「最終回答を決まった形で受け取りたいのか」を分けて考えてください。

次は、Function Callingで起きやすい不正JSON、未知のツール、タイムアウト、再試行を安全に処理する実装を解説します。

参考資料

コメント

タイトルとURLをコピーしました