Discourse AI : une requête Gemini bloquée via promptFeedback.blockReason est traitée comme une réponse vide et n'est jamais traduite

Description

Lorsque l’API Gemini de Google refuse une requête avec promptFeedback.blockReason, Discourse AI traite la réponse comme une réussite mais une réponse vide. Dans le cas de la traduction par IA, cela a deux effets : le sujet ou le message ne reçoit jamais de langue détectée, et n’est donc jamais traduit, et le backfill renvoie la même requête bloquée lors des exécutions ultérieures. Rien n’indique à un administrateur qu’un blocage a eu lieu.

Observé sur un site auto-hébergé exécutant release/2026.7 (2026.7.3, commit f1caa6321287f918644fba9ff8943579fdcf027a) avec le plugin discourse-ai inclus. La requête concernait la détection de langue d’un titre de sujet, en utilisant l’agent détecteur de langue intégré sur gemini-3.5-flash-lite. Le titre était une courte phrase inoffensive en suédois. Le même titre a été traduit par gemini-3.8-flash sans blocage.

Google a répondu avec HTTP 200 et un seul événement de flux sans candidats :

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

La requête contient déjà BLOCK_NONE pour les quatre catégories de sécurité définies par Discourse AI, ce blocage n’est donc pas influencé par ces paramètres. Le journal d’audit enregistre un response_status de 200.

Cause racine

  1. DiscourseAi::Completions::Endpoints::Gemini#decode_chunk ne lit que candidates dans chaque événement de flux. promptFeedback n’est jamais inspecté, de sorte qu’une requête bloquée produit un résultat vide sans erreur.
  2. DiscourseAi::Translation::LanguageDetector#detect retourne nil lorsque la réponse ne correspond pas à son modèle d’étiquette de langue, ce qui est le cas pour une réponse vide. Le sujet ou le message conserve locale NULL.
  3. Jobs::TopicsLocaleDetectionBackfill sélectionne les sujets avec locale NULL à chaque exécution et n’a pas de limite de tentatives, donc, en lisant le code, la requête bloquée est répétée toutes les cinq minutes tant que le sujet est dans la fenêtre de backfill. Les messages sont limités à deux tentatives de détection par jour par le quota de relocalisation, et sont réessayés chaque jour.
  4. Les candidats de traduction nécessitent un locale détecté, donc l’élément n’est jamais traduit.

Le même code est présent sur main au 03/10/2026.

Correction suggérée

  1. Détecter promptFeedback.blockReason dans le point de terminaison Gemini et lever ou journaliser une erreur distincte et visible qui nomme la raison.
  2. Enregistrer l’échec sur le sujet ou le message afin que le backfill cesse de répéter une requête qui sera à nouveau bloquée, ou appliquer une limite de tentatives à la détection des sujets comme cela existe déjà pour les messages.
  3. Afficher les éléments bloqués sur la page d’administration de la traduction, afin qu’un administrateur puisse définir la langue manuellement.

Reproduire

Le blocage dépend du filtre de Google et ne peut pas être forcé avec une entrée connue. Les appels affectés peuvent être trouvés dans le journal d’audit :

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

Contournement

Définir manuellement la langue du sujet permet à la traduction de se poursuivre. Sur ce site, le modèle de traduction n’a pas bloqué le même texte.