Discourse AI: solicitação do Gemini bloqueada via promptFeedback.blockReason é tratada como resposta vazia e nunca traduzida

Descrição

Quando a API Gemini do Google recusa uma solicitação com promptFeedback.blockReason, o Discourse AI trata a resposta como uma resposta bem-sucedida, porém vazia. Na tradução por IA, isso tem dois efeitos: o tópico ou a publicação nunca recebe um idioma detectado, portanto nunca é traduzido, e o backfill envia a mesma solicitação bloqueada novamente em execuções posteriores. Nada indica a um administrador que um bloqueio ocorreu.

Observado em um site auto-hospedado executando release/2026.7 (2026.7.3, commit f1caa6321287f918644fba9ff8943579fdcf027a) com o plugin discourse-ai integrado. A solicitação era a detecção de idioma de um título de tópico, usando o agente de detector de idioma integrado em gemini-3.5-flash-lite. O título era uma frase curta e inofensiva em sueco. O mesmo título foi traduzido por gemini-3.8-flash sem um bloqueio.

O Google respondeu com HTTP 200 e um único evento de stream sem 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": "..."}

A solicitação já carrega BLOCK_NONE para as quatro categorias de segurança que o Discourse AI define, portanto este bloqueio não é um daqueles influenciados por essas configurações. O log de auditoria registra response_status 200.

Causa raiz

  1. DiscourseAi::Completions::Endpoints::Gemini#decode_chunk lê apenas candidates de cada evento de stream. promptFeedback nunca é inspecionado, portanto uma solicitação bloqueada produz um resultado vazio sem erro.
  2. DiscourseAi::Translation::LanguageDetector#detect retorna nil quando a resposta não corresponde ao seu padrão de tag de idioma, o que é o caso para uma resposta vazia. O tópico ou a publicação mantém locale como NULL.
  3. Jobs::TopicsLocaleDetectionBackfill seleciona tópicos com locale NULL em cada execução e não tem limite de tentativas, portanto, lendo o código, a solicitação bloqueada é repetida a cada cinco minutos por quanto tempo o tópico estiver na janela de backfill. Publicações são limitadas a duas tentativas de detecção por dia pela cota de relocalize, e são reenviadas a cada dia.
  4. Candidatos de tradução requerem um locale detectado, portanto o item nunca é traduzido.

O mesmo código está presente em main desde 2026-10-03.

Correção sugerida

  1. Detectar promptFeedback.blockReason no endpoint Gemini e levantar ou registrar um erro distinto e visível que nomeie o motivo.
  2. Registrar a falha contra o tópico ou a publicação para que o backfill pare de repetir uma solicitação que será bloqueada novamente, ou aplicar um limite de tentativas à detecção de tópico, como já existe para publicações.
  3. Exibir itens bloqueados na página de administração de tradução, para que um administrador possa definir o idioma manualmente.

Reproduzir

O bloqueio depende do filtro do Google e não pode ser forçado com uma entrada conhecida. Chamadas afetadas podem ser encontradas no log de auditoria:

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

Solução alternativa

Definir o idioma do tópico manualmente permite que a tradução prossiga. Neste site, o modelo de tradução não bloqueou o mesmo texto.