描述
当 Google 的 Gemini API 以 promptFeedback.blockReason 拒绝请求时,Discourse AI 会将该响应视为成功但内容为空的回复。在 AI 翻译功能中,这会产生两个影响:主题或帖子永远不会被检测到语言,因此永远不会被翻译;并且回填任务(backfill)会在后续运行中再次发送相同的被阻止请求。没有任何提示告知管理员发生了阻止。
此问题在运行 release/2026.7(2026.7.3,提交 f1caa6321287f918644fba9ff8943579fdcf027a)并附带 discourse-ai 插件的自托管站点上观察到。该请求是对主题标题进行语言检测,使用的是内置的本地语言检测代理(locale detector agent),模型为 gemini-3.5-flash-lite。标题是一句简短、无害的瑞典语句子。同样的标题在使用 gemini-3.8-flash 翻译时未被阻止。
Google 返回了 HTTP 200 以及一个没有候选项(candidates)的单个流事件:
data: {"promptFeedback": {"blockReason": "PROHIBITED_CONTENT"},"usageMetadata": {"promptTokenCount": 417,"totalTokenCount": 417,"promptTokensDetails": [{"modality": "TEXT","tokenCount": 417}],"serviceTier": "standard"},"modelVersion": "gemini-3.5-flash-lite","responseId": "..."}
该请求已经针对 Discourse AI 设置的四个安全类别携带了 BLOCK_NONE,因此此阻止并非由这些设置引起。审计日志记录 response_status 为 200。
根本原因
DiscourseAi::Completions::Endpoints::Gemini#decode_chunk仅从每个流事件中读取candidates。promptFeedback从未被检查,因此被阻止的请求会产生空结果且没有错误。- 当回复不匹配其语言标签模式时(空回复即属此情况),
DiscourseAi::Translation::LanguageDetector#detect返回nil。主题或帖子的locale保持为 NULL。 Jobs::TopicsLocaleDetectionBackfill在每次运行时选择locale为 NULL 的主题,且没有尝试次数限制,因此根据代码逻辑,只要主题处于回填窗口内,被阻止的请求就会每五分钟重复一次。帖子受重新本地化配额的限制,每天最多进行两次检测尝试,并每天重试。- 翻译候选项需要检测到语言环境(locale),因此该项目永远不会被翻译。
截至 2026-10-03,main 分支中存在相同的代码。
建议修复
- 在 Gemini 端点中检测
promptFeedback.blockReason,并引发或记录一个明确的、可见的错误,其中指明原因。 - 针对主题或帖子记录失败情况,以便回填停止重复发送将被再次阻止的请求,或者像帖子那样为主题检测应用尝试次数限制。
- 在翻译管理页面上显示被阻止的项目,以便管理员可以手动设置语言。
复现
该阻止取决于 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
变通方案
手动设置主题的语言可以让翻译继续进行。在该站点上,翻译模型并未阻止相同的文本。