Discourse AI: GeminiのリクエストがpromptFeedback.blockReasonでブロックされた場合、空の応答として扱われ、翻訳されない

説明

GoogleのGemini APIが promptFeedback.blockReason を返してリクエストを拒否した場合、Discourse AIはレスポンスを成功したが空の応答として処理します。AI翻訳において、これには2つの影響があります。トピックまたは投稿に検出された言語が設定されず、翻訳が実行されないこと、およびバックフィルが後続の実行で同じブロックされたリクエストを再度送信することです。ブロックが発生したことを管理者に示すインジケーターは何もありません。

これは、バンドルされたdiscourse-aiプラグインを備えた release/2026.7 (2026.7.3, commit f1caa6321287f918644fba9ff8943579fdcf027a) を実行しているセルフホスト環境で確認されました。リクエストは gemini-3.5-flash-lite 上の組み込みロケール検出エージェントを使用してトピックタイトルの言語検出を行うものでした。タイトルはスウェーデン語の短く無害な文でした。同じタイトルは gemini-3.8-flash によってブロックされずに翻訳されました。

GoogleはHTTP 200と、候補が1つもない単一のストリームイベントで応答しました:

data: {"promptFeedback": {"blockReason": "PROHIBITED_CONTENT"},"usageMetadata": {"promptTokenCount": 417,"totalTokenCount": 417,"promptTokensDetails": [{"modality": "TEXT","tokenCount": 417}],"serviceTier": "standard"},"modelVersion": "gemini-3.5-flash-lite","responseId": "..."}

リクエストにはすでにDiscourse AIが設定する4つの安全性カテゴリに対して BLOCK_NONE が含まれているため、このブロックはそれらの設定が影響を与えるものではありません。監査ログには response_status 200 が記録されています。

根本原因

  1. DiscourseAi::Completions::Endpoints::Gemini#decode_chunk は各ストリームイベントから candidates のみを読み取ります。 promptFeedback は決して検査されないため、ブロックされたリクエストはエラーなしで空の結果を生成します。
  2. DiscourseAi::Translation::LanguageDetector#detect は、応答が言語タグのパターンに一致しない場合(空の応答の場合)に nil を返します。トピックまたは投稿の locale は NULL のままになります。
  3. Jobs::TopicsLocaleDetectionBackfill は毎回 locale が NULL のトピックを選択し、試行回数に制限がないため、コードを読む限りでは、トピックがバックフィルウィンドウ内にいる限り、ブロックされたリクエストが5分ごとに繰り返されます。投稿は再ロカライズクォータにより1日2回の検出試行に制限されており、毎日再試行されます。
  4. 翻訳候補には検出されたロケールが必要であるため、アイテムは決して翻訳されません。

同じコードは2026-10-03時点で main にも存在します。

提案される修正

  1. Geminiエンドポイントで promptFeedback.blockReason を検出し、理由を明記した個別かつ可視のエラーを発生させたり記録したりします。
  2. トピックまたは投稿に対して失敗を記録し、再びブロックされるリクエストの繰り返しをバックフィルが停止させるようにするか、投稿にすでに存在するようにトピック検出に試行回数の制限を適用します。
  3. 翻訳管理ページにブロックされたアイテムを表示し、管理者が手動で言語を設定できるようにします。

再現手順

ブロックはGoogleのフィルターに依存しており、既知の入力で強制することはできません。影響を受けた呼び出しは監査ログで見つけることができます:

SELECT id, created_at, language_model, post_id, topic_id, response_status
FROM ai_api_audit_logs
WHERE feature_name = 'translation'
  AND raw_response_payload LIKE '%blockReason%'
ORDER BY created_at

回避策

トピックの言語を手動で設定すると、翻訳を続行できます。このサイトでは、翻訳モデルは同じテキストをブロックしませんでした。