Tradução truncada silenciosamente quando a análise do fluxo JSON falha (sem erro gerado)

Resumo
DiscourseAi::Translation::PostLocalizer (localização de conteúdo) ocasionalmente
produz traduções truncadas sem registrar nenhum erro em nenhum lugar (log do Rails,
log do Sidekiq, filas de dead/retry do Sidekiq). O truncamento ocorre no meio de uma frase,
de forma silenciosa, e o resultado parcial é salvo como se fosse uma tradução
completa e bem-sucedida.

Observado em: Discourse core + discourse-ai 46bea6613 (2026-07-09), LLM:
Gemini 2.5 Flash via provedor Google.

Evidências de reprodução
Dois casos reais na nossa instância:

  • Uma postagem alemã de ~2090 caracteres traduzida para o espanhol produziu apenas 50 caracteres,
    cortando imediatamente antes de uma palavra entre aspas (“aufwendiger”) no texto
    de origem.
  • Uma postagem alemã de ~1830 caracteres traduzida para o inglês (83 caracteres) e eslovaco
    (97 caracteres) — ambas cortadas no exato mesmo ponto da frase,
    imediatamente antes de um shortcode de emoji (:collision:) no texto de origem.
    Todos os outros idiomas (fr, it, pt, ru, he) para a mesma postagem foram traduzidos
    completamente (1600–1950 caracteres).

Causa raiz (até onde pude rastrear)
Em lib/completions/structured_output.rb, StructuredOutput#read_buffered_property
recorre a DiscourseAi::Utils::BestEffortJsonParser.extract_key sempre que
@partial_json_tracker.broken? é verdadeiro. Esse recurso retorna tudo o que foi
analisado antes do fluxo JSON falhar — sem exceção, sem aviso, sem
indicação em nenhum lugar de que a saída está incompleta. Em
lib/translation/base_translator.rb#get_translation, esse valor é usado como está
como a tradução final e persistido via PostLocalization#save!.

Ambos os pontos de truncamento em nossos dados ocorrem imediatamente antes de um “caractere especial”
no texto de origem (uma aspa reta, um shortcode de emoji),
sugerindo que o rastreador de fluxo JSON fica confuso com certos
caracteres/sequências na saída em fluxo do modelo e marca o fluxo
como broken? prematuramente.

Impacto
O conteúdo é publicado com traduções visivelmente incompletas, às vezes sem sentido,
e não há sinal operacional (log, trabalho morto, notificação de administrador)
para detectá-lo. Só descobrimos porque um membro da comunidade
percebeu lixo no meio da frase na interface.

Correções sugeridas

  • Exibir um aviso/erro (Rails.logger + Discourse-error-log) sempre que
    StructuredOutput#broken? for verdadeiro e o recurso best-effort for usado, para que
    administradores possam detectar isso em vez de enviar silenciosamente conteúdo parcial.
  • Considere validar o comprimento traduzido em relação a um limite de sanidade relativo
    ao comprimento da origem antes de salvar um PostLocalization.

Fico feliz em fornecer pares completos de texto bruto/traduzido privativamente, se for útil para
debugar o rastreador JSON.

1 curtida