Discourse AI: una solicitud de Gemini bloqueada por promptFeedback.blockReason se trata como respuesta vacía y nunca se traduce

Descripción

Cuando la API de Gemini de Google rechaza una solicitud con promptFeedback.blockReason, Discourse AI trata la respuesta como una respuesta exitosa pero vacía. En la traducción por IA, esto tiene dos efectos: el tema o la publicación nunca recibe un idioma detectado, por lo que nunca se traduce, y el relleno (backfill) vuelve a enviar la misma solicitud bloqueada en ejecuciones posteriores. Nada indica al administrador que se produjo un bloqueo.

Observado en un sitio autoalojado que ejecuta release/2026.7 (2026.7.3, commit f1caa6321287f918644fba9ff8943579fdcf027a) con el plugin discourse-ai integrado. La solicitud fue la detección de idioma de un título de tema, utilizando el agente detector de idioma integrado en gemini-3.5-flash-lite. El título era una oración corta e inofensiva en sueco. El mismo título fue traducido por gemini-3.8-flash sin bloqueo.

Google respondió con HTTP 200 y un único evento de flujo sin candidatos:

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 solicitud ya lleva BLOCK_NONE para las cuatro categorías de seguridad que Discourse AI establece, por lo que este bloqueo no es uno de los que influyen en esas configuraciones. El registro de auditoría registra response_status 200.

Causa raíz

  1. DiscourseAi::Completions::Endpoints::Gemini#decode_chunk solo lee candidates de cada evento de flujo. promptFeedback nunca se inspecciona, por lo que una solicitud bloqueada produce un resultado vacío sin error.
  2. DiscourseAi::Translation::LanguageDetector#detect devuelve nil cuando la respuesta no coincide con su patrón de etiqueta de idioma, que es el caso para una respuesta vacía. El tema o la publicación mantiene locale como NULL.
  3. Jobs::TopicsLocaleDetectionBackfill selecciona temas con locale NULL en cada ejecución y no tiene límite de intentos, por lo que, al leer el código, la solicitud bloqueada se repite cada cinco minutos durante el tiempo que el tema esté en la ventana de relleno. Las publicaciones están limitadas a dos intentos de detección al día por la cuota de relocalización, y se reintentan cada día.
  4. Los candidatos de traducción requieren un locale detectado, por lo que el elemento nunca se traduce.

El mismo código está presente en main a fecha del 2026-10-03.

Solución sugerida

  1. Detectar promptFeedback.blockReason en el punto de entrada de Gemini y lanzar o registrar un error distinto y visible que nombre la razón.
  2. Registrar el fallo contra el tema o la publicación para que el relleno deje de repetir una solicitud que será bloqueada nuevamente, o aplicar un límite de intentos a la detección de temas como ya existe para las publicaciones.
  3. Mostrar los elementos bloqueados en la página de administración de traducción, para que un administrador pueda establecer el idioma manualmente.

Reproducir

El bloqueo depende del filtro de Google y no se puede forzar con una entrada conocida. Las llamadas afectadas se pueden encontrar en el registro de auditoría:

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

Solución temporal

Establecer el idioma del tema manualmente permite que la traducción continúe. En este sitio, el modelo de traducción no bloqueó el mismo texto.