GeminiのAPI関数呼び出しと構造化出力の注意点

GeminiのAPI関数呼び出しと構造化出力の注意点

こんにちは。Osukeラーニング、運営者の「osuke」です。

GeminiのAPIで関数呼び出しと構造化出力を使いたいけれど、どちらもJSONが出てくるので違いが分かりにくい。Pythonのサンプルをつないだら動きそうでも、外部サービスを誤って操作しないか心配。そんな場面で、先に整理したいのが「回答の形」と「処理を実行する責任」です。

この記事では、JSON Schemaによる出力形式の指定、Function Callingの役割、引数の検証、エラー時の再試行を、小さな在庫照会の例で説明します。2026年10月8日に確認したGoogle公式資料とXの公開投稿をもとにした調査記事です。私自身の本番運用体験ではなく、コードも実サービスに接続して成功を確かめたものではありません。

記事のポイント

  • 構造化出力と関数呼び出しを目的から選べる
  • JSONの形式チェックと内容チェックを分けられる
  • 読み取り専用の関数から検証を始められる
  • 結果不明の処理を二重実行しない設計が分かる

GeminiのAPI関数呼び出しと構造化出力

まずは機能の違いと責任の分担です。設定の名前を覚える前に、最終的に何を受け取り、誰が何を変更するのかを決めると、必要な仕組みを絞れます。

まずは回答と実行を分ける

決まった形の回答が欲しいなら構造化出力、外部の処理につなぎたいなら関数呼び出し、という分け方から始めると理解しやすいです。たとえば問い合わせ文から商品コードを取り出し、画面の入力欄へ表示するだけなら、必要なのは整理されたデータです。一方、在庫管理システムへ問い合わせて現在の残数を調べるなら、データを受け取るだけでは終わりません。アプリ側が別の処理を動かす段階があります。

架空の例として「A100はまだありますか」という問い合わせを考えます。構造化出力で商品コードA100を抽出しても、それは在庫を調べたことにはなりません。モデルが返した数字を、そのまま倉庫の実在庫と受け取るのは避けたいですね。在庫照会の関数を用意し、許された商品だけを調べ、照会結果を回答へ反映させるところまで設計して、初めて外部データを使った案内になります。

この二つは競合する選択肢ではなく、工程によって組み合わせる考え方もできます。ただし、最初から一つのリクエストに複数の仕組みを詰め込む必要はありません。問い合わせを整理する工程と在庫を調べる工程を分ければ、間違った商品コードを取り出したのか、照会先が失敗したのかを切り分けやすくなります。同時利用の対応条件はモデルやAPIごとに確認し、記事の例では一度に全部を自動化しない方針にしています。

最初に書くのは設定値ではなく、完了条件です。

  • 抽出の完了:商品コードが確認待ちの画面に表示された
  • 照会の完了:許可されたデータ源から結果を受け取った
  • 案内の完了:照会日時と結果を確認して回答した

画面にも「抽出済み」「照会中」「確認待ち」を分けて表示すると、途中の結果を完成した回答と誤認しにくくなります。

JSONは正解を保証しない

構造化出力を使う意味は、後続のプログラムが扱う形式を決めやすくすることです。たとえば商品コードの項目が文字列か、数量が整数か、といった約束を置けます。ただし、形式が合っていることと、値が事実に合っていることは別です。実在しない商品コードでも文字列としては成立しますし、問い合わせに書かれていない数量でも、整数を入れれば見かけの形式は整ってしまいます。

この違いは、住所を書き込むフォームに似ています。郵便番号の桁数を確認できても、その住所に商品を届けてよいかまでは判定できません。記事の在庫照会なら、文字列として受け取ったA100が商品台帳に存在するか、今の利用者にその在庫を見せてよいかを別に確かめます。スキーマを通過したデータを「検証済みの業務データ」と呼んでしまうと、この確認工程が抜けやすくなるので注意したいところです。

Googleの構造化出力の公式ガイドも、形式と意味の正確さを分けて値を検証するよう説明しています。独自の運用としては、分からない値を無理に埋めさせず、未記載のまま確認へ回す設計がおすすめです。たとえば商品名だけで候補が二つある場合は、どちらかを推測して照会せず、候補を表示して人が選ぶようにします。自動化率よりも、何が未確定かを残せることを優先しましょう。

確認を三段階に分けます。

  • 形式:期待する項目と型になっているか
  • 内容:元の問い合わせや商品台帳と一致するか
  • 権限:その利用者が参照してよい情報か

前の段階に通っても、次の段階を省略する理由にはなりません。たとえば他店舗の在庫が正しい数字でも、その店舗のデータを閲覧できない利用者には返さない、という確認が別途必要です。

関数の実行責任はアプリ側

関数呼び出しでは、利用可能な関数の名前や引数をモデルへ伝えます。モデルが必要だと判断すると、関数名と引数の呼び出し情報を返し、アプリ側がそれを処理します。モデルが関数名を返しただけで、外部の在庫システムが自動的に操作されたわけではありません。公式ガイドの流れも、関数の宣言、モデルの提案、アプリの実行、結果の返却という段階を分けています。

ここで重要なのは、実行してよい関数をあらかじめ限定することです。たとえば在庫を読むlookup_stockだけを許可しているなら、モデルが別の名前を返しても動かさない設計にします。受け取った文字列をそのままコードとして評価したり、コマンドとして実行したりする必要はありません。用意した関数の対応表から選び、引数を確かめ、ログを残して呼び出すという、普通のアプリ開発の境界を維持するのが基本です。

さらに、在庫の照会と注文確定は別の権限として考えましょう。照会が成功したからといって、そのまま購入や発送まで進めてよいわけではありません。最初の試作では変更を伴う関数を登録せず、読み取り専用にしておくと検証範囲を絞れます。後から書き込み処理を追加する場合も、対象、変更内容、利用者の承認、重複実行防止をそれぞれ確認する入口を設けます。「AIが選んだ」という理由を、実行許可の代わりにしないことが大切です。

関数呼び出しを受け取った後の順番

  1. 許可した関数名か確認する
  2. 引数の型と値を検証する
  3. 利用者の参照権限を確認する
  4. 実行して結果を対応する呼び出しへ返す
  5. 失敗や未確定を成功として表示しない

便利な自動実行ラッパーを使う場合も、どの段階で検証するかを確認し、アプリの責任が消えると考えないようにします。

Xの事例から読み取れること

Xの公開投稿は、実装者がどこに関心を持ち、何に戸惑っているかを考える材料になります。2026年10月3日のSunil Kumarさん(@sunildhiman90)の投稿では、Kotlin向けSDKと関数呼び出しなどが紹介されていました。ここから記事で取り上げたいのは、言語ごとの実装環境をそろえる必要性です。投稿だけで現在の対応機能を確定するのではなく、自分が使うPythonやKotlinの公式サンプルを選び、別言語の設定を混ぜないようにします。

一方、3月4日の御草さん(@PrismPhi)の投稿には、Geminiの回答にJSONが出たという報告がありました。ただし、関数呼び出しとの関係は投稿者自身も推測として書いており、APIの種類や再現手順は分かりません。これは「APIにその不具合がある」という根拠にはできませんね。読み手の画面へ内部向けのデータを表示することと、裏側の処理が完了することは違う、という説明のきっかけとして扱うのが妥当です。

9月27日のArky Yangさん(@arkyyang)の投稿は、ツール処理の返事が失われたときの二重実行を論じる研究紹介でした。投稿中の実験数値はこの記事では採用していません。ただ、結果不明の処理をすぐ繰り返さないという設計課題は、後述の確認項目に入れています。今回の調査では、現行Gemini APIで再現できる具体的な失敗手順までは取得できませんでした。少数の投稿を一般的な発生率や製品の優劣に広げず、公式仕様と独自の対策を分けて読むのが大事です。

今回のX調査の扱い

  • SDK紹介:利用場面を知る材料
  • JSON表示の報告:アプリとAPIを混同しない注意材料
  • 二重実行の議論:運用上の確認項目を考える材料

いずれも、osuke本人の開発体験や実測結果ではありません。

どちらを選ぶかの判断表

迷ったら、モデルの回答を受け取った後に何をしたいかを書き出しましょう。問い合わせの分類結果を一覧に並べたいのか、最新の在庫を調べたいのか、注文を確定したいのかで必要な仕組みが違います。次の表は機能の優劣ではなく、この記事の例での設計判断です。「AIらしい機能を増やす」ことを目的にせず、普通の入力フォームや決まった処理で済む部分は、そのまま残してよいと思います。

やりたいこと最初の選択別に必要な確認
問い合わせを分類する構造化出力分類と原文の一致
商品コードを抽出する構造化出力台帳への存在確認
必要に応じ在庫を照会する関数呼び出し権限と照会結果
必ず同じ照会を行う通常のプログラムも検討入力検証と障害処理
注文や送信を確定する別の承認付き処理意図、対象、二重実行防止

たとえば画面で利用者が商品コードを選び、毎回同じ在庫APIを呼ぶだけなら、モデルに関数を選ばせる必要は薄いかもしれません。自然文から適切な処理を選びたいときに関数呼び出しを検討すれば十分です。この判断を先にしておくと、モデルの呼び出し回数や調査すべき失敗パターンを減らせます。単純な作業は単純な仕組みに任せるという考え方は、モデルが変わっても使いやすい基準ですね。

反対に、自由文の問い合わせを複数の業務へ振り分けたいなら、モデルによる判断が役立つ場面があります。その場合も、最初は在庫照会だけに絞り、うまく振り分けられなかった入力を集めてから機能を増やしましょう。APIそのものの準備や使い始め方を先に整理したい方は、Gemini APIの基本を扱う記事も確認してください。この記事では課金や接続設定を繰り返すより、受け取ったデータをどう扱うかに焦点を置きます。

選択基準は「整えて受け取る」「必要な処理を選ばせる」「決まった処理を直接動かす」の三つです。

GeminiのAPI関数呼び出しと構造化出力の実装

ここからは、小さな確認用の実装と運用の考え方です。以下は学習用の架空例であり、実サービスへのAPI通信や注文処理を実行した結果ではありません。

小さなスキーマから始める

最初の構造化出力は、商品コード一つだけで試すと問題を切り分けやすくなります。問い合わせに明記されているときだけ取り出し、分からなければnullにする、という約束です。最初から担当者、配送先、希望日時、予算まで一度に抽出すると、どの項目の設計が原因なのか分かりにくくなります。出力を保存する場所や、その値を見て次に何をするかを決めてから、必要な項目を一つずつ増やす方が検証しやすいですね。

次は確認日時点の公式資料にあるInteractions APIの形式を使った説明例です。使用するSDKがこのAPIに対応していることを確認し、GEMINI_MODELには構造化出力に対応する利用可能なモデル名を設定します。認証情報はコードへ書かず、SDKの正規の設定方法で渡してください。この例はAPIへ送信して動作確認したものではなく、古い記事のgenerate_content向け引数と混ぜないために、呼び出し方を一つに限定して示しています。

import json
import os
from google import genai

schema = {
    "type": "object",
    "properties": {
        "item_code": {"type": ["string", "null"]}
    },
    "required": ["item_code"],
    "additionalProperties": False
}
client = genai.Client()
result = client.interactions.create(
    model=os.environ["GEMINI_MODEL"],
    input="商品コードを抽出。明記がなければnull。問い合わせ: A100はありますか",
    response_format={
        "type": "text",
        "mime_type": "application/json",
        "schema": schema
    }
)
data = json.loads(result.output_text)
# この後に型、台帳、利用者の権限を検証する

ここでjson.loadsが成功しても、その後の検証は省略しません。また、応答が空の場合やAPI自体のエラーなど、例では省略した失敗処理が必要です。公式ガイドではJSON Schemaのすべてではなく一部に対応するため、手元の複雑なスキーマを無条件に持ち込まないようにしましょう。キーの設定から確認したい場合は、Gemini APIキーの扱いを整理した記事が入口になります。画面のスクリーンショットやログにキーを含めない点も大切です。

このコードは最小の説明例です。エラー処理、タイムアウト、権限管理を備えた本番用の完成品ではありません。

読み取り専用の関数で試す

関数呼び出しを試すなら、最初は外部サービスを書き換えない関数を使いましょう。ここでは、アプリがモデルから受け取る関数名と引数を、架空の在庫表へつなぐ境界だけを示します。APIへの関数登録や結果返却のコードではありません。モデルの応答を取り出した後の処理を、ネットワークなしで検証できる形に分ける例です。期待する名前以外は拒否し、引数も商品コード一つだけに限定します。

STOCK = {"A100": 3}

def dispatch(name, args):
    if name != "lookup_stock":
        raise ValueError("許可されていない関数")
    if not isinstance(args, dict) or set(args) != {"item_code"}:
        raise ValueError("引数の項目が不正")
    code = args["item_code"]
    if type(code) is not str:
        raise ValueError("商品コードは文字列が必要")
    if code not in STOCK:
        return {"status": "needs_review"}
    return {"status": "ok", "item_code": code, "stock": STOCK[code]}

# ローカルの架空データだけを使う確認例
assert dispatch("lookup_stock", {"item_code": "A100"})["stock"] == 3
assert dispatch("lookup_stock", {"item_code": "UNKNOWN"}) == {
    "status": "needs_review"
}

この例では、知らない商品コードを在庫ゼロに変換していません。未知の商品と、台帳に存在する商品の売り切れは意味が違うからです。商品名が曖昧なら人に確認し、在庫表が読めなければ照会失敗にします。さらに本番では、ログイン中の利用者が対象の商品や倉庫を参照できるかの確認が必要です。モデルが返す利用者IDを信用するのではなく、アプリの認証済み情報で判定するようにしてください。

実際のAPIとの接続では、関数呼び出しが返らない場合、複数返る場合、同じ処理が再び求められる場合の扱いも決めます。公式のInteractions APIでは呼び出しに対応するIDを使って結果を返す流れなので、別の呼び出しの結果と取り違えないことが重要です。このローカル例に通信を加えるときは、実行回数に上限を置き、結果を失ったときの扱いを先に決めましょう。最初から無制限に処理を繰り返すループにはしません。

例の在庫3個は架空の固定値です。実店舗の在庫や、Geminiが取得した実データではありません。

エラーと再試行を分ける

エラーを見たらすぐ再試行、という一つのルールでは扱いきれません。入力の形式が違うエラーなら、同じ入力を送り直しても改善しないでしょう。一時的な通信の失敗なら待ってから再試行できる場合がありますが、何回まで行うのか、いつ人へ戻すのかを決めておく必要があります。そして書き込み処理では、返事が来ないことと、実行されていないことは同じではありません。ここが読み取り専用の練習と本番の自動操作の大きな違いです。

たとえば将来、問い合わせへのメール送信を追加したとします。送信先のサービスではメールを送れたのに、アプリが結果を受け取る直前に通信が切れることを考えると、同じ依頼をそのまま再送するのは危険です。この記事では、こうした状態を「結果不明」として分ける設計を提案します。処理の受付IDや実行履歴を確認し、二重実行を防ぐ仕組みがない場合は人に判断を戻します。JSONの再生成だけでは解決しない問題ですね。

重複防止のキーを受け付けるサービスなら、一つの意図に対して同じキーを保つ方式を検討します。ただし、そのサービスがどこまで重複を防ぐか、有効期間はあるか、同じキーで違う内容を送るとどうなるかを確認しなければなりません。モデルが毎回新しいキーを作るだけでは、同じ依頼として認識されない可能性があります。これはGemini固有の保証ではなく、接続先の契約とアプリ側の設計で決める内容です。読み取りと書き込みを同じ自動再試行設定にしないようにしましょう。

失敗時の分岐を先に作ります。

  • 入力が不正:修正依頼へ戻す
  • 読み取りの一時失敗:上限付き再試行を検討する
  • 書き込みの結果不明:履歴確認または人の判断へ戻す
  • 回数上限に到達:未完了と明示して停止する

公開前の確認リスト

正常な問い合わせ一つで成功しても、公開の判断には足りません。入力が欠けた場合や、関係のない文章が来た場合を用意し、処理を止められることまで確認したいですね。架空の商品A100で在庫を読めることに加えて、商品コードなし、未知の商品、文字列ではない値、余計な項目、許可していない関数名を渡すケースを試します。入力を受け付けないときに、利用者へ何を案内するかも確認対象です。

とくに「どの商品か分からない」と「在庫がない」は区別します。前者をゼロ個として回答すると、本当は購入できる商品を案内から落としてしまうかもしれません。同様に、権限がない状態を通信障害として隠すと、再試行ばかり増える原因になります。正しい答えを返す試験だけでなく、分からないと伝える試験、許可しない試験、途中で止まる試験を用意するのが、この記事でおすすめする確認の仕方です。

残すログも最初に決めておきましょう。処理ID、使用モデル、スキーマの版、関数名、開始と終了、結果の状態が追えると、変更後に問題が起きたとき比較しやすくなります。一方、APIキーや問い合わせ中の個人情報を無条件に保存するのは避けます。調査に必要な項目だけに絞り、閲覧できる人と保存期間を決めてください。モデルやSDKを更新したときは、同じ確認用の入力を再度通して、以前の成功をそのまま現在の保証にしないことも大事です。

公開前に確認する項目

  • 欠損や曖昧な入力を勝手に補わない
  • 許可しない関数と引数を拒否できる
  • 参照権限をアプリ側で判定できる
  • 空の応答や通信失敗を処理できる
  • 実行回数と再試行回数に上限がある
  • 結果不明を成功と報告しない
  • ログに秘密情報を残さない

GeminiのAPI関数呼び出しと構造化出力の要点

GeminiのAPIで構造化出力を使うときは、受け取りたいデータの形を決めるところから始めます。関数呼び出しを使うときは、選ばれた処理をアプリ側が検証して実行するところまで考えます。どちらもJSONが登場しますが、回答を整えること、内容を確かめること、外部処理を許可することは別の工程です。この三つを分ければ、機能名やサンプルの違いに振り回されにくくなると思います。

最初の一歩としておすすめなのは、問い合わせから商品コード一つを取り出し、人が確認できる画面へ表示するところまで作ることです。次に読み取り専用の架空データへつなぎ、分からない入力では停止できるか確認します。最後に必要な範囲だけ実データへ接続します。注文、送信、削除などの変更を伴う処理は、照会の延長として気軽に追加せず、承認と重複防止を備えた別の工程として扱いましょう。

この記事のコードは学習用の境界を示すもので、本番運用の完成を保証するものではありません。また、Xで読めた投稿は着眼点の参考であり、現在のAPIの成功率や障害の発生率を示していません。実装時には公式資料で利用するモデルとAPI、SDKの組み合わせを確かめてください。Gemini全体の使い分けから見直したい場合は、Geminiの使い方を整理した全体ガイドへ戻ると、アプリで足りる作業とAPIで作る作業を分けやすくなります。

今日やることは、小さな一つで十分です。

  1. 欲しい出力を一項目に絞る
  2. 分からない場合の値と停止条件を決める
  3. 正常・欠損・不正の三種類を確かめる
  4. 読み取り専用の処理から接続する

動く例を増やす前に、安全に止まれる例を一つ作っておきましょう。