GEMINI LABEN
MODEL — Gemini 3.5 Flashが一般提供。3.1 Proをほぼ全ベンチで上回りつつ高速に動きますAPI — Interactions APIがGAに到達。Geminiモデルとエージェントを扱う主要APIになりましたAGENTS — Managed Agentsが公開プレビュー。Googleホストの隔離Linuxサンドボックスで自律エージェントを動かせますCOST — Project Spend Capsでプロジェクト単位のGemini API月額上限を設定できますSHEETS — Gemini in Sheetsが周辺データを解析し、数式エラーをワンクリックで診断・修正しますSTUDIO — Google AI Studioが刷新され、スターターアプリのギャラリーが拡充されましたMODEL — Gemini 3.5 Flashが一般提供。3.1 Proをほぼ全ベンチで上回りつつ高速に動きますAPI — Interactions APIがGAに到達。Geminiモデルとエージェントを扱う主要APIになりましたAGENTS — Managed Agentsが公開プレビュー。Googleホストの隔離Linuxサンドボックスで自律エージェントを動かせますCOST — Project Spend Capsでプロジェクト単位のGemini API月額上限を設定できますSHEETS — Gemini in Sheetsが周辺データを解析し、数式エラーをワンクリックで診断・修正しますSTUDIO — Google AI Studioが刷新され、スターターアプリのギャラリーが拡充されました
記事一覧/API / SDK
API / SDK/2026-06-26上級

Gemini API のセーフティフィルタが正当な応答を黙って落とすとき — 全切りせず誤検知だけを救う運用メモ

本番のGemini APIで正当なプロンプトがSAFETYでブロックされる誤検知を、全カテゴリ無効化に逃げずに扱う運用メモ。入力ブロックと出力ブロックの切り分け、誤検知率の計装、カテゴリ別の段階的リカバリまでを実装で整理します。

gemini-api252safety-filterproduction94observability7error-handling8

プレミアム記事

セーフティフィルタの相談で一番多いのは「どう全部切るか」ですが、本番で困るのはたいてい逆です。全部は切れない、切りたくない、それでも正当なリクエストが時々黙って落ちる。落ちたことに気づくのも遅れる。finishReasonSAFETY で返り、response.text を読もうとした瞬間に例外が飛んで、ユーザーには空のカードだけが残ります。

私自身、個人開発の傍らで4つの技術ブログを自動投稿で回している関係で、無人で走るバッチがこれに引っかかったことがあります。題材として渡したコード断片やエラーメッセージのごく一部が DANGEROUS_CONTENT 寄りに判定され、生成が途中で止まる。人がいないので、翌朝ログを見るまで誰も気づきません。このメモは、そのとき「全カテゴリ OFF にして終わり」にせず、誤検知だけを拾って安全側に戻すために組んだ仕組みの記録です。

まず「どちらでブロックされたか」を1行で確定する

セーフティフィルタは入力(プロンプト)と出力(生成結果)の両方を見ます。この二つは原因も対処もまったく別物なのに、ログでは同じ「ブロックされた」に見えてしまうのが厄介な点です。最初にやるべきは、どちらで落ちたかを毎回機械的に記録することです。

入力でブロックされた場合は候補(candidate)が一つも生成されず、情報は prompt_feedback.block_reason に入ります。出力でブロックされた場合は候補は生成されますが、finish_reasonSAFETY になり本文が空になります。新しい google-genai SDK では、この切り分けは次のように書けます。

from google import genai
from google.genai import types
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
def classify_block(resp):
    """ブロックの発生箇所を入力/出力/なしで返す"""
    pf = getattr(resp, "prompt_feedback", None)
    if pf and getattr(pf, "block_reason", None):
        return "INPUT_BLOCKED", str(pf.block_reason)
 
    if not resp.candidates:
        # 候補ゼロかつ prompt_feedback も無いレアケース
        return "NO_CANDIDATE", "unknown"
 
    cand = resp.candidates[0]
    if cand.finish_reason == types.FinishReason.SAFETY:
        return "OUTPUT_BLOCKED", "SAFETY"
 
    return "OK", str(cand.finish_reason)

この関数を本番の生成呼び出しの直後に必ず通し、INPUT_BLOCKEDOUTPUT_BLOCKED かをそのままログのフィールドにします。これだけで、後から「入力プロンプトを直すべき案件」と「出力側の閾値を見直すべき案件」を混ぜずに数えられるようになります。経験上、この区別をしていないログは「ブロックが多い」までしか言えず、次の一手が打てません。

誤検知を「カテゴリ別の率」で見えるようにする

閾値を緩めるかどうかを勘で決めると、たいてい緩めすぎるか、怖くて何もできないかのどちらかに振れます。判断材料は、どのカテゴリが、どのくらいの確率で、どれだけブロックに寄与しているかという実測値です。

各候補とプロンプトフィードバックには safety_ratings が付き、要素ごとに categoryprobabilityNEGLIGIBLE / LOW / MEDIUM / HIGH)・blocked(真偽)が入ります。これをそのまま構造化ログに落とし、カテゴリ別に集計します。

from collections import Counter
 
def extract_ratings(resp):
    """入力側・出力側の safety_ratings を平坦化して返す"""
    rows = []
    pf = getattr(resp, "prompt_feedback", None)
    if pf and getattr(pf, "safety_ratings", None):
        for r in pf.safety_ratings:
            rows.append(("input", str(r.category), str(r.probability), bool(r.blocked)))
    for cand in (resp.candidates or []):
        for r in (cand.safety_ratings or []):
            rows.append(("output", str(r.category), str(r.probability), bool(r.blocked)))
    return rows
 
def summarize(logged_rows):
    """蓄積した rows からカテゴリ別の誤検知傾向を出す"""
    blocked = Counter()
    medium_plus = Counter()
    for _side, cat, prob, was_blocked in logged_rows:
        if was_blocked:
            blocked[cat] += 1
        if prob in ("MEDIUM", "HIGH"):
            medium_plus[cat] += 1
    return blocked, medium_plus

ここで重要なのは、blocked の件数だけでなく MEDIUM 止まり(ブロックには至らないが境界に近い)の分布も一緒に見ることです。MEDIUM が特定カテゴリに偏って積み上がっているなら、そのカテゴリは「いまは耐えているが、入力がわずかに変われば落ちる」予備軍です。本番で突然ブロックが増える事故は、たいていこの予備軍が閾値をまたいだ瞬間に起きます。率で持っておくと、事故になる前に気づけます。

なお probability はあくまで安全方針上の確からしさであって、出力内容の正しさとは別物です。ここを取り違えて「LOW だから内容も安全」と読むと判断を誤ります。フィルタが見ているのは方針適合であって事実性ではありません。

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

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
入力ブロック(prompt_feedback)と出力ブロック(finish_reason=SAFETY)を最初の1行で切り分けるログ設計
誤検知率をカテゴリ別に集計し、緩めてよい閾値を勘ではなく実測で決める計装コード
全カテゴリOFFに逃げず、原因カテゴリだけを段階的に緩めて安全側に倒すリカバリ関数
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

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

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

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

関連記事

API / SDK2026-05-23
Gemini API × Sentry でLLMエラー追跡とプロンプト失敗を観測する本番運用パイプライン
Sentry のエラートラッキングと Gemini API 固有の失敗パターンを組み合わせ、プロンプト崩壊・安全フィルタ発火・トークン超過まで本番で観測する実装パイプラインを設計します。
API / SDK2026-05-02
Gemini API を個人アプリに組み込む時の 7 つの設計判断 — エラー設計から品質モニタリングまで
Gemini API を個人アプリに本番導入した時に直面する設計判断を、私自身の運用記録から 7 つに絞ってまとめました。コストとレイテンシのトレードオフ、エラー時のフォールバック設計、品質モニタリングまで、教科書には載っていない実戦的な判断軸を共有します。
API / SDK2026-04-25
Gemini API を OpenTelemetry でトレースする本番運用ガイド — 1リクエストの内側を全部見せる
Gemini API を本番投入したあと、ログだけでは追えなくなったコスト・レイテンシ・失敗の連鎖を OpenTelemetry の分散トレーシングで丸ごと可視化する方法を、Python・Node.js の実装からセマンティック規約、Grafana/Datadog 連携まで具体的に解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →