Gemini API を本番投入してしばらくすると、必ずどこかで遭遇するのが「途中で文章が切れている」「応答が空で返ってくる」という症状です。そして candidates の finish_reason を見ると RECITATION という見慣れない値が入っています。エラーメッセージも出ず、リクエストは HTTP 200 で成功しているように見えます。これは私自身、社内向けナレッジベースを Gemini で組んでいたときに最初にハマったポイントでした。
このエラーは「セーフティフィルター」とも違い、量のチェックでも、トークン超過でもありません。Gemini が「自分の応答が著作権で保護されたコンテンツに似すぎている」と自己判定した時に発火する、独立した中断シグナルです。今回はこの RECITATION の正体と、現場で使える回避パターンを整理します。
RECITATION が発火する具体的な条件
公式ドキュメントには「学習データの一部を逐語的に再生しそうになった場合に停止する」と書かれていますが、実装上のトリガーはもう少し具体的です。私が観測した範囲では、以下のパターンで再現性が高いです。
- 既存の歌詞・詩・有名スピーチ・聖書の節などを「そのまま」または「ごく軽い改変で」生成しようとした場合
- 公開リポジトリで広く知られた OSS のコード(よくある useEffect サンプル、TensorFlow チュートリアル等)をそのまま再生成しようとした場合
- レシピ本・教科書・公式ドキュメントの定型表現を、原文に近い構造で要約させた場合
- ユーザーの入力プロンプト自体が長く、その中に著作物が含まれていてモデルがそれを反復しようとした場合
つまり「Gemini が学習データから直接コピーしているように見える」と内部判定された瞬間に、応答ストリームが切られます。SAFETY のように完全ブロックされて空応答になることもあれば、生成途中で打ち切られて 400〜800 文字くらいで止まることもあります。
まず最初に確認すべきレスポンス構造
エラーハンドリングの第一歩は、finish_reason を見落とさないコードを書くことです。多くのチュートリアルが response.text だけを読んでいますが、これだと RECITATION 発生時に「短い応答」と「正常終了」を区別できません。
# Python (google-genai SDK) - finish_reason を必ず検査するパターン
from google import genai
from google.genai import types
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
response = client.models.generate_content(
model="gemini-2.5-pro",
contents="ここにプロンプト",
config=types.GenerateContentConfig(
temperature=0.7,
max_output_tokens=2048,
),
)
# 必ず candidates と finish_reason を検査する
if not response.candidates:
raise RuntimeError("候補が空です。プロンプトブロックを確認してください。")
candidate = response.candidates[0]
finish_reason = candidate.finish_reason # "STOP" / "RECITATION" / "SAFETY" / "MAX_TOKENS" など
if finish_reason == "RECITATION":
# 著作権類似ブロック発生 — 後述のリトライ戦略へ
print("⚠️ RECITATION で中断。テキストは:", candidate.content.parts[0].text if candidate.content else "(空)")
elif finish_reason == "SAFETY":
print("⚠️ セーフティフィルターでブロック")
elif finish_reason == "STOP":
print("✅ 正常終了:", response.text)
else:
print(f"その他の中断: {finish_reason}")ポイントは finish_reason == "STOP" のときだけを「成功」とみなし、それ以外は全て個別に処理することです。response.text は RECITATION のとき例外を投げるか空文字を返すかが SDK バージョンで揺れるため、テキスト取得は candidate.content.parts[0].text 経由が安全です。
回避パターン1: プロンプトを「変換タスク」に書き換える
最も効果が高いのは、生成タスクを「自由記述」から「変換・要約・分析」にシフトすることです。Gemini は入力に対する変換タスクでは RECITATION が起きにくく、ゼロからの生成(特に「〜の歌詞を書いて」「有名な〜を再現して」)で起きやすい傾向があります。
# ❌ RECITATION を誘発しやすいプロンプト
bad_prompt = "クリスマスキャロル風の歌詞を書いてください。"
# ✅ 変換タスクとして書き直す
good_prompt = """次の要素を踏まえて、完全にオリジナルの歌詞を作成してください。
既存の楽曲・童謡・讃美歌の歌詞は一切流用せず、独自の語彙と比喩で書いてください。
テーマ: 雪の朝の家族の温もり
構造: 4行 × 4ブロック
韻: ABAB
"""「既存の歌詞を流用しない」「独自の語彙で」と明示的に指示するだけで、再生成の確率が大きく下がります。これは Gemini が「指示への忠実度」を出力選択時に重視するためです。
回避パターン2: temperature を上げて経路をずらす
RECITATION は出力分布の最尤経路(temperature=0 付近)で発生しやすい現象です。温度を上げて少しランダム性を与えると、学習データそのものの再現を避けながら同等の品質を出せることが多いです。
ただし「温度を上げれば直る」という単純な話ではありません。私の検証では、temperature=0.0〜0.3 で発生していたケースを 0.7 に変えるだけで解消したパターンが多数ありました。一方、temperature=1.0 以上は応答品質が落ちるので、0.6〜0.8 を最初に試すのが実用的です。具体的なプロンプトチューニングのコツについては、Gemini API のセーフティフィルターブロック対応ガイド も合わせて参照すると、ブロック系エラー全般の理解が深まります。
回避パターン3: リトライ + プロンプト書き換えの自動化
本番環境では、RECITATION が発生したら自動でプロンプトを書き換えてリトライする層を入れておくと安心です。ナイーブな実装だと無限ループになるので、最大試行回数を切ってフォールバックを用意します。
# Production-ready: RECITATION 検知 → プロンプト変換リトライ
def generate_with_recitation_recovery(client, model, original_prompt, max_retries=3):
"""RECITATION 発生時にプロンプトを段階的に変換してリトライする。"""
prompts = [
original_prompt,
f"{original_prompt}\n\n注: 既存の作品の文言を一切引用せず、完全にオリジナルの表現で書いてください。",
f"以下の要件を独自の言葉で達成してください。既存の歌詞・コード・文章の引用は禁止します。\n要件: {original_prompt}",
]
for attempt, prompt in enumerate(prompts[:max_retries]):
response = client.models.generate_content(
model=model,
contents=prompt,
config=types.GenerateContentConfig(
temperature=0.7 + 0.1 * attempt, # リトライごとに温度を少し上げる
max_output_tokens=2048,
),
)
if not response.candidates:
continue
finish_reason = response.candidates[0].finish_reason
if finish_reason == "STOP":
return response.text
if finish_reason != "RECITATION":
# SAFETY などはリトライしても解消しないので即座に上に返す
raise RuntimeError(f"非リトライ可能な中断: {finish_reason}")
# 3回試して全て RECITATION ならフォールバック
raise RuntimeError("RECITATION から復帰できませんでした。プロンプト設計を見直してください。")このパターンは、エラー処理全般のリトライ戦略の応用です。指数バックオフを含めた網羅的な実装は Gemini API のリトライパターン解説 にまとめてあるので、本番運用ではそちらと組み合わせて使ってください。
RECITATION と「空応答」の見分け方
現場でよく混同されるのが「empty response」と「RECITATION」です。両者は対処が全く違うので、ログ層で必ず区別しておきます。
finish_reason == "STOP"で text が空 → 正常応答だが内容が無いケース。プロンプトが曖昧、またはmax_output_tokensが極端に小さいfinish_reason == "MAX_TOKENS"→ 出力トークン上限切れ。max_output_tokensを上げるfinish_reason == "SAFETY"→ セーフティフィルター。カテゴリと閾値を調整finish_reason == "RECITATION"→ 本記事の対象。プロンプト書き換え + 温度調整
詳しい原因切り分けは Gemini API の空応答・finish_reason 対応ガイド も参考になります。エラーごとの典型的なシグナルを覚えておくと、ログを見た瞬間に対応の当たりが付きます。
設計レベルでの予防策
最後に、RECITATION を「事故」ではなく「想定内」として扱うための設計指針を3つ挙げます。
ひとつ目は、ユーザー入力に著作物が含まれる前提でバリデーションを入れること。たとえばチャットボットに「歌詞を翻訳して」と入力された場合、その翻訳出力自体が RECITATION でブロックされる可能性が高いです。あらかじめ「著作物の翻訳・要約はサポート外」と仕様で切ってしまうのが安全です。
ふたつ目は、コード生成タスクでは「テンプレートを示して埋めさせる」スタイルを採ること。フリーフォームで「Reactのフォームコンポーネントを書いて」と依頼するより、骨格となる関数シグネチャと型を渡してから埋めさせる方が、よくある OSS パターンとの完全一致を避けやすくなります。
みっつ目は、観測可能性の確保です。finish_reason を構造化ログに必ず吐き、Datadog や Cloud Logging で集計できるようにしておくと、本番投入後に RECITATION の発生率が見えます。経験則として、RECITATION 率が全リクエストの 1% を超えてきたらプロンプト設計の見直しサインです。
まずは観測ログを仕込むところから
RECITATION は「珍しいエラー」ではなく「Gemini を本番で使うなら必ず遭遇する中断シグナル」です。今日からできる最小の一歩は、既存コードの response.text の前に finish_reason をログ出力する1行を足すことです。それだけで、これまで「たまに応答が短い」と思っていた事象の正体が見え始めるはずです。