GEMINI LABEN
NANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定ですNANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定です
記事一覧/API / SDK
API / SDK/2026-05-21中級

responseSchema の enum 指定なのに違う文字列が返る — Gemini API で起きる原因と回避策

Gemini API の responseSchema で enum を指定したのに、許可値以外が返ってくる現象の原因と、私が壁紙アプリの自動分類で実際に使っている回避策を紹介します。

Gemini API191responseSchema5enum構造化出力12トラブルシューティング30

壁紙アプリのユーザー投稿画像を「風景/人物/抽象/その他」の4分類に振り分けようとしていたとき、responseSchemaenum: ["landscape", "portrait", "abstract", "other"] と書いたのに、レスポンスに "natural""scenery" が混ざる現象に遭遇しました。AdMob 収益を支える壁紙アプリでは、分類ミスがそのまま検索体験の劣化につながります。最初は仕様変更を疑って何度もリトライしたものの、原因はもう少し地味なところにありました。

ここでは同じ症状に当たった方が短時間で根本対処に進めるよう、再現条件・原因・3パターンの回避策を順に整理していきます。

症状の最小再現

次のような Python コードで、本来は4値しか返ってこないはずの enum フィールドに、想定外の値が混ざります。

from google import genai
from pydantic import BaseModel
from typing import Literal
 
class Classification(BaseModel):
    label: Literal["landscape", "portrait", "abstract", "other"]
    confidence: float
 
client = genai.Client()
response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents=[
        "Classify this wallpaper image.",
        image_part,
    ],
    config={
        "response_mime_type": "application/json",
        "response_schema": Classification,
    },
)
 
result = Classification.model_validate_json(response.text)
# 期待: label は4値のいずれか
# 実際: 稀に "natural", "scenic", "city" などが返り pydantic.ValidationError

Pydantic で Literal を使っているにも関わらず、Gemini が schema を完全に守ってくれないため、ValidationError で例外が飛びます。私の手元では 5,000 件中 12 件程度の混入で、リトライしても同じ画像で同じ誤った enum が再現したケースもありました。

なぜ enum が無視されるのか

公式ドキュメントには propertyOrderingenum のサポートが明記されていますが、内部的にはあくまで「制約付きデコーディングのヒント」として扱われる場面があります。とくに以下の3条件が重なると、enum 外の値が滲み出やすくなります。

  1. 画像や PDF などのマルチモーダル入力で、入力トークン数が長くなる
  2. enum 値が「自然言語として近接した語」を含む(例: landscapescenic
  3. プロンプトに enum 候補そのものが明示されていない

裏側のロジック自体は非公開なので断言は避けますが、私が gemini-2.5-flashgemini-2.5-pro で同じ画像セットを比較したかぎり、プロンプト本文に許可値を埋め込むだけで誤分類率は 1/3 から 1/5 まで減りました。schema は「強い指示」ではなく「整形のためのヒント」と捉え、プロンプト側で意味的な制約をかける方が安定します。これは OpenAI の response_format や Anthropic の Tool Use とも共通する設計感覚で、構造化出力を持つ各社の API はいずれも「プロンプトと schema の二段構え」を取ったほうが堅牢になります。

ちなみに gemini-2.5-flashgemini-2.5-pro の挙動を比較すると、私のデータでは pro の方が enum 順守率は高いものの、画像 100 枚あたりのコストは約 5 倍に膨らみます。AdMob 収益と相談しながら、コストが許容できる範囲で flash のリトライ戦略を組むのが現実的でした。

対処1: プロンプトに許可値を明示する

これがいちばん効きました。schema に書くだけでなく、プロンプト本文で許可値を箇条書きにします。

ALLOWED = ["landscape", "portrait", "abstract", "other"]
 
prompt = f"""\
Classify the wallpaper image into exactly one of these labels:
- landscape (natural scenery, mountains, sea, sky)
- portrait (a person or animal as the main subject)
- abstract (pattern, gradient, geometric art)
- other (anything that does not fit the above)
 
Return JSON. The "label" field MUST be exactly one of: {ALLOWED}.
Do NOT invent new labels.
"""
 
response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents=[prompt, image_part],
    config={
        "response_mime_type": "application/json",
        "response_schema": Classification,
    },
)

「許可値の意味的な定義」と「逸脱禁止の明示」をプロンプト本体に置くだけで、私のテストセットでは混入率がほぼゼロになりました。schema と二重に書くのは冗長に見えますが、現時点ではいちばん再現性のある安定策です。

対処2: 受け取り側でバリデーション+1回だけリトライ

それでも稀に外れることがあるので、受け取った enum 値を検証し、不一致のときだけ「自己訂正プロンプト」を1回投げます。無限リトライにすると API コストが跳ねるため、必ず上限を設けます。

def classify_with_retry(image_part, max_retry: int = 1) -> Classification:
    last_text = ""
    for attempt in range(max_retry + 1):
        instruction = prompt if attempt == 0 else (
            f"Your previous label '{last_text}' was not in {ALLOWED}. "
            f"Re-classify using ONLY these four labels."
        )
        response = client.models.generate_content(
            model="gemini-2.5-flash",
            contents=[instruction, image_part],
            config={
                "response_mime_type": "application/json",
                "response_schema": Classification,
            },
        )
        last_text = response.text
        try:
            result = Classification.model_validate_json(last_text)
            if result.label in ALLOWED:
                return result
        except Exception:
            pass
 
    # フォールバック: other 扱い(パイプラインを止めない)
    return Classification(label="other", confidence=0.0)

1回のリトライで救えるケースが大半で、最後まで弾けなかった場合は other に倒すことで分類パイプライン全体は止まりません。2014年から個人開発で運営している壁紙アプリでは、こうした「失敗してもデータが流れ続ける」設計が大切でした。

対処3: SDK バージョンによる挙動差を疑う

google-genai v0.6 以前と v1.x 以降で、response_schema の解釈が変わったタイミングがありました。Pydantic v2 のモデルを直接渡せるようになったのは比較的最近のバージョンで、それ以前は dict 形式の JSON Schema を渡す必要があります。

pip show google-genai | grep Version
# 古いバージョンなら更新
pip install --upgrade google-genai

私が運用している累計 5,000万ダウンロード規模のアプリ群でも、CI が古いバージョンに固定されたままで enum 順序の指示が効かないものがありました。「同じコードでも環境によって混入率が違う」ときは、SDK バージョン差を最初に疑うのが近道です。

運用上の優先順位

実運用では以下の順で組み合わせています。

  • まず対処1(プロンプト明示)を必ず入れる
  • 対処2(自己訂正リトライ1回)でセーフティネットを敷く
  • 障害切り分けの際は対処3(SDK バージョン確認)を最初に見る

3つのうちどれが効くかは、データセットと運用フェーズで変わります。初期は対処1で十分でも、配信規模が増えてエッジケースが見えてくる頃には対処2のリトライ網が必要になり、SDK 更新のタイミングで対処3を見直すという順に進みました。

このパターンに揃えてから、壁紙アプリのカテゴリ振り分けに対する人手チェックは月1回ほどに減り、AdMob の検索エクスペリエンスも安定しました。enum 違反が混ざること自体は完全には防げないという前提で「監視 → リトライ → フォールバック」の三層を敷いておくと、Gemini のモデル更新があっても運用は止まらず続けられます。

ドリフトを早期検出するロギング

混入は完全にゼロにはできないため、運用側で「ドリフト率」を可視化しておきます。私の壁紙アプリでは BigQuery に enum 出力をそのまま投げ、許可値外の出現割合を週次でモニタリングしています。

def log_classification(label: str, allowed: list[str]):
    is_drift = label not in allowed
    # Cloud Logging / BigQuery / Sentry のいずれでも
    print({
        "event": "enum_classification",
        "label": label,
        "is_drift": is_drift,
    })

ドリフト率が突然上がったときは、

  • モデルバージョンが変わった(gemini-2.5-flash → 新スナップショット)
  • 入力分布が変わった(新しいシリーズの壁紙を投入した)
  • SDK を更新した

のどれかが原因のことが多く、原因切り分けが30分以内で済むようになりました。

可視化を入れる前は「なんとなく分類精度が落ちた気がする」を毎週手動で確認しており、判断に丸1日かかっていたこともあります。週次グラフに置き換えてからは、ドリフト発生時の対応がプロンプト調整 → リトライ → モデル切り替えの順で機械的に進められるようになりました。

似た症状で勘違いしやすいケース

このエラーは他のスキーマ違反と症状が似ていて、初見で原因を取り違えがちです。私自身、以下と混同して時間を溶かしました。

  • response_mime_type 未指定 → JSON ではなく Markdown コードフェンス付きで返ってくる
  • ネストした oneOf を使った → 子フィールドだけ常に第一候補が返る
  • propertyOrdering 未指定の長文 JSON → フィールド順序が安定せずパースが揺れる

まず response.text を生のまま print して、JSON か Markdown か、enum 違反か順序違反かを切り分けてからにすると、対処の選択ミスが減ります。

responseSchema は便利ですが、現状は「整形のヒント」と割り切ってプロンプト側にも責任を持たせるのが堅実です。同じ症状で行き詰まっている方の手元で、最短ルートで解消の助けになれば嬉しく思います。

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API / SDK2026-06-19
日英2版を1回の構造化出力でまとめて生成し、用語の対訳ブレを止める運用設計
日本語版と英語版を別々に生成すると用語の訳が記事ごとにブレます。Gemini 3.5 Flash の構造化出力で日英をペアにして1コールで生成し、用語集をピン留めしてブレを機械検出する運用を、実測値とともにまとめます。
API / SDK2026-07-13
structured output の任意フィールドが null・空文字・キー欠落で三様にぶれる — 正規化ゲートで一つの表現に畳む
Gemini の structured output で任意フィールドが null・空文字・キー欠落の三態にぶれ、下流が壊れる問題を、responseSchema の nullable 宣言と出力後の正規化ゲートで一つの表現に畳む実装として整理します。個人開発の夜間バッチでの実測つきです。
API / SDK2026-06-27
Gemini の構造化出力でJSONのフィールド順がブレる — propertyOrdering で固定する実装メモ
responseSchema で型を縛っているのに、出力JSONのキーの並びが呼び出しごとに変わる。スナップショットテストが理由なく赤くなる原因はこれでした。propertyOrdering でフィールド順を固定し、few-shot の順序も揃え、差分ノイズを消すまでをコード付きでまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →