Описание
Когда API Google Gemini отклоняет запрос с помощью promptFeedback.blockReason, Discourse AI обрабатывает ответ как успешный, но пустой. В контексте перевода на ИИ это приводит к двум последствиям: теме или сообщению никогда не присваивается обнаруженный язык, поэтому оно никогда не переводится, а процесс повторной обработки (backfill) отправляет тот же заблокированный запрос снова при последующих запусках. Администратору не сообщается о том, что произошла блокировка.
Проблема наблюдалась на self-hosted сайте, работающем на версии release/2026.7 (2026.7.3, коммит f1caa6321287f918644fba9ff8943579fdcf027a) с встроенным плагином discourse-ai. Запросом было определение языка заголовка темы с использованием встроенного агента детектора локали на базе gemini-3.5-flash-lite. Заголовок представлял собой короткое безобидное предложение на шведском языке. Тот же заголовок был успешно переведен моделью gemini-3.8-flash без блокировки.
Google ответил с HTTP 200 и одним потоковым событием без кандидатов:
data: {"promptFeedback": {"blockReason": "PROHIBITED_CONTENT"},"usageMetadata": {"promptTokenCount": 417,"totalTokenCount": 417,"promptTokensDetails": [{"modality": "TEXT","tokenCount": 417}],"serviceTier": "standard"},"modelVersion": "gemini-3.5-flash-lite","responseId": "..."}
Запрос уже содержит значение BLOCK_NONE для четырех категорий безопасности, которые устанавливает Discourse AI, поэтому эта блокировка не является следствием этих настроек. В журнале аудита записан response_status 200.
Корневая причина
- Метод
DiscourseAi::Completions::Endpoints::Gemini#decode_chunkчитает только полеcandidatesиз каждого потокового события. ПолеpromptFeedbackникогда не проверяется, поэтому заблокированный запрос приводит к пустому результату без ошибки. - Метод
DiscourseAi::Translation::LanguageDetector#detectвозвращаетnil, если ответ не соответствует шаблону языкового тега, что и происходит в случае пустого ответа. У темы или сообщения полеlocaleостается NULL. - Задача
Jobs::TopicsLocaleDetectionBackfillвыбирает темы сlocaleNULL при каждом запуске и не имеет ограничения на количество попыток, поэтому, судя по коду, заблокированный запрос повторяется каждые пять минут, пока тема находится в окне повторной обработки. Для сообщений действует ограничение в две попытки определения в день через квоту relocalize, и они повторяются каждый день. - Кандидаты на перевод требуют обнаруженной локали, поэтому элемент никогда не переводится.
Тот же код присутствует в ветке main по состоянию на 03.10.2026.
Предлагаемое исправление
- Обнаруживать
promptFeedback.blockReasonв эндпоинте Gemini и вызывать или записывать в журнал отдельную, видимую ошибку, указывающую на причину. - Записывать сбой для темы или сообщения, чтобы повторная обработка прекратила отправлять запрос, который будет заблокирован снова, либо применить ограничение на количество попыток для определения языка тем, как это уже сделано для сообщений.
- Отображать заблокированные элементы на административной странице перевода, чтобы администратор мог установить язык вручную.
Воспроизведение
Блокировка зависит от фильтра 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
Обходной путь
Ручная установка языка темы позволяет продолжить перевод. На этом сайте модель перевода не блокировала тот же текст.