最近在继续处理历史中文文章的英文覆盖翻译时,又遇到了一篇无法正常完成翻译的文章。
这一次与之前常见的 Protected Token 校验失败、网络请求异常或者 WordPress 写入失败都不同。翻译接口直接返回了 HTTP 400,并明确提示:
系统检测到输入或生成内容可能包含不安全或敏感内容,请避免输入易产生敏感内容的提示语。
文章本身是一篇普通的技术博客 SEO 实践记录,主要讨论英文站索引、标签页面以及 Search Console 数据,并没有刻意涉及敏感主题。
因此,这次问题更像是内容安全系统在特定上下文下产生了一次误拦截。
更有意思的是,最开始怀疑的内容经过两轮修改后,问题依然存在。最终真正让翻译恢复正常的,是另一组看起来更加普通的流量统计表达。
这篇文章记录完整的定位过程。
1. 历史文章批处理在第 9 篇失败
这次仍然是在 wordpress-ai-excerpt-backfill 项目中继续执行历史文章迁移。
批次运行到第 9 篇时停止:
- 中文文章 ID:15738
- 英文文章 ID:15775
- 尝试次数:3
- 最终状态:
translation_failed
错误并不是翻译后的结构校验失败,而是在调用翻译接口时直接返回:
HTTP request failed with status 400:
prompt_client_error: Bad Request (400)
后面的信息进一步说明,服务端认为输入或者生成内容可能触发了内容安全检查。

这与普通的 HTTP 500、超时或者 WordPress REST API 写入异常有明显区别。
请求甚至没有走到后面的候选内容校验阶段,因此首先需要检查原始中文文章本身。
2. 第一反应:怀疑一处网络访问相关术语
文章主要讨论的是多语言网站 SEO。
绝大部分内容都比较普通:
- 中文和英文文章数量;
- 分类和标签数量;
- Google Search Console;
noindex, follow;- Crawl Budget;
- 英文站海外流量;
- Linux、Go、Gin、Docker 等技术内容。
逐段检查以后,我首先注意到了第 5 节中的一处网络访问相关技术术语。

此前处理另一篇历史文章时,我曾经遇到过相似情况。
当时一处网络访问类表达会导致翻译接口拒绝请求,将其改写成更加中性的“网络服务”以后,文章即可继续处理。
因此,这次最开始也自然怀疑到了同一个方向。
但这里有一个原则:
不能为了通过翻译接口,直接删除原文中原本存在的信息。
所以第一次没有删除,而是将原来的缩写改成了对应的完整中文技术名称。
3. 改成完整中文名称,仍然失败
第一次修改以后,通过恢复工具检查:
python3 bin/history-migration.py recover \
--post-id 15738
程序正确识别到生产中文文章已经变化:
生产中文源: 已修改
恢复策略: restart_from_current
将执行: restart_from_current -> execute_single_candidate
原因: production Chinese title or content changed after pre-write
这正是需要的恢复路径。
因为中文源已经人工修改,不能继续使用旧的失败现场直接 resume,而应该:
- 以当前生产中文内容重新建立基线;
- 再重新执行单篇英文覆盖翻译。
随后执行:
python3 bin/history-migration.py recover \
--post-id 15738 \
--execute
结果却仍然是相同的 HTTP 400。
这说明,仅仅把英文缩写改成准确的中文名称,并没有解决问题。

这个结果开始削弱最初的判断。
如果安全系统只是在匹配某一个固定英文缩写,那么改成中文以后理论上应该已经发生变化。
于是继续进行了第二次测试。
4. 进一步改成“网络服务”,还是失败
第二次不再保留具体技术名称,而是采用此前历史文章中已经使用过的中性表达:
网络服务
然后再次执行:
python3 bin/history-migration.py recover \
--post-id 15738
确认恢复策略仍然为:
restart_from_current
再执行:
python3 bin/history-migration.py recover \
--post-id 15738 \
--execute
结果依然没有变化:
HTTP request failed with status 400:
prompt_client_error
到这里,基本可以停止继续围绕这一处网络术语排查了。
排查链路已经变成:
原技术术语
↓
完整中文名称
↓
更中性的“网络服务”
↓
三种表达全部返回相同的 HTTP 400
因此,最开始认为“网络访问相关术语就是触发原因”的判断并不成立。
至少它不是这篇文章当前唯一的触发因素。
5. 第二个方向:国家和地区流量统计
重新阅读全文以后,另一个比较集中的内容是海外流量统计。
文章中多次列出了具体国家和地区,同时还包含:
- 百分比;
- 海外流量;
- 国家;
- 地区;
- Search Console 数据。
其中有一组比较完整的国家和地区流量比例,同时相同内容还出现在截图的图片 alt 属性中。
为了继续缩小范围,我先将这一组具体数据改写成更加概括的表达:
来自多个国家和地区的海外流量已经有所增长
图片的 alt 也同步修改。

这里有一个容易忽略的地方:
WordPress 图片的 ALT 同样属于文章 HTML 内容。
如果翻译插件把 Gutenberg HTML 整体发送给模型,那么即使正文已经修改,只要 alt 仍然保存着原来的文字,相同内容依然有可能继续出现在请求中。
不过,这一次重新执行以后,HTTP 400 仍然存在。
这说明这一处完整统计也不是唯一触发条件。
6. 不再猜单个词,而是处理剩余同类表达
检查修改后的文章发现,虽然主要统计段已经改掉,但正文中仍然存在数处具体国家和地区名称,例如:
- 某两个国家的流量比例;
- 跟踪不同国家实际流量的描述;
- 总结部分对海外流量来源的描述。
因此,前一次实验实际上只能证明:
被修改的那一条统计句不是唯一触发位置。
并不能证明整个“国家和地区流量表达”方向不存在问题。
于是下一轮不再逐个猜测某一个具体词,而是把剩余同类表达统一改成更加概括的描述。
例如:
英文文章和英文分类已经开始带来更多海外流量。
以及:
跟踪不同国家和地区的实际流量情况。
总结部分则改成:
成功带来了更多海外流量。
这些调整没有改变文章原本想表达的结论,只是不再把并非文章核心的信息精确到每一个国家或地区名称。
7. 再次恢复,这一次终于成功
保存修改后的生产中文文章后,首先执行预览:
python3 bin/history-migration.py recover \
--post-id 15738
程序再次判断:
生产中文源: 已修改
恢复策略: restart_from_current
随后正式执行:
python3 bin/history-migration.py recover \
--post-id 15738 \
--execute
这一次结果终于变成:
基线重建: completed
重新执行: completed
最终状态: completed
下一步: 无

至此,中文文章 ID 15738 的历史英文重翻译完成。
8. 这次不能简单总结成“某个词是敏感词”
这次排查很容易产生一个错误结论。
最开始看到文章中的某个网络访问技术术语后,很容易认为:
就是这个词导致了 HTTP 400。
但实际验证并不支持这种说法。
连续两轮修改以后:
- 改成完整中文技术名称,失败;
- 再改成中性的“网络服务”,仍然失败。
真正发生变化的是后面将多处具体国家和地区流量表达统一概括以后,请求才恢复正常。
但即使如此,我认为仍然不能进一步断言:
某一个具体国家或地区名称就是敏感词。
因为最后一次实际上修改了多处同类内容。
我们能够确认的是:
在本次文章、本次请求以及当前模型服务环境下,一组具体国家和地区名称与流量统计相关的上下文,很可能参与触发了内容安全检查;将这些内容改成更概括的表达以后,请求恢复正常。
至于究竟是其中一个词、几个词的组合,还是模型生成英文以后形成的上下文触发了检查,目前没有必要继续为了寻找唯一触发点而反复测试。
9. 错误提示中的“输入或生成内容”也值得注意
这次错误提示还有一个很重要的细节:
系统检测到输入或生成内容可能包含不安全或敏感内容。
也就是说,不能简单假定所有 HTTP 400 都是在提交中文原文以后立即进行关键词匹配。
至少从错误信息本身来看,还存在另一种可能:
- 中文内容首先通过部分检查;
- 模型开始生成英文;
- 生成结果中的某些表达触发安全检查;
- 最终整个请求仍然以 HTTP 400 返回。
因此,仅凭一次失败,很难准确判断到底是哪一个中文词导致问题。
这也是为什么这类问题更适合通过实际修改和重新提交来验证,而不是只靠猜测。
10. 处理历史文章时,我更倾向于“等义改写”,而不是删除
这次还有一个比较重要的处理原则。
历史文章毕竟已经公开存在多年,原文中的很多术语都是当时真实技术背景的一部分。
如果因为现在的模型接口无法接受,就简单删除内容,很容易造成两个问题:
- 原文信息被改变;
- 英文版本与中文版本之间的语义开始出现明显差异。
因此,我现在更倾向于:
在不影响文章核心技术含义的前提下,对非核心的高风险表达进行等义或者概括性改写。
例如具体国家流量比例对于一篇讨论 SEO 策略的文章而言,并不是核心技术信息。
文章真正需要表达的是:
英文内容已经开始获得海外访问。
那么从具体百分比调整为“多个国家和地区的海外流量已经有所增长”,并不会破坏文章的主要结论。
这种处理方式比直接删除整段内容更加稳妥。
11. 截图可以保留排查证据,但 ALT 不必重复具体内容
这次整理排查记录时,我还决定采用一个稍微不同的写法。
正文不会反复写出所有被怀疑的具体词,而是使用:
- 网络访问相关技术术语;
- 网络服务类表达;
- 具体国家和地区名称;
- 地区流量统计;
- 中性的概括表达。
真正的原始现场则保留在截图中。
这样既能够保存排查证据,也避免为了记录一次内容安全问题,在新文章正文中再次大量复制同类表达。
同时,截图的 ALT 和图注也没有必要重新把截图中的全部文字复述一遍。
例如:
历史文章翻译返回 HTTP 400 内容安全错误
就已经足以说明图片用途。
没有必要把截图中的原始表达再次完整写入 ALT。
12. 对恢复工具本身也有一个新的优化方向
从这次状态变化来看,现有 recover 流程表现是正确的。
人工修改生产中文文章以后:
生产中文源: 已修改
恢复策略: restart_from_current
工具不会继续使用旧基线强行恢复,而是:
restart_from_current
↓
重新建立基线
↓
execute_single_candidate
这避免了把修改前后的证据混在一起。
不过这次也暴露出一个可以以后继续优化的方向。
目前这种错误最终仍然显示为:
真实错误: wordpress_api_error
但实际异常中的关键原因已经非常明确:
prompt_client_error
HTTP 400
以后如果再次遇到类似文章,可以考虑把这种错误进一步独立分类。
例如区分:
- WordPress API 普通异常;
- 网络异常;
- 内容安全类 HTTP 400;
- Protected Token 校验失败;
- 英文写入异常。
这样看到批次失败以后,就能够更快判断:
这篇文章应该检查翻译结构,还是应该直接检查原始内容。
不过目前历史文章迁移仍然在继续,这一项暂时没有必要为了单篇文章立即修改程序。
13. 总结
这次历史文章翻译失败,最终经历了一个比较典型的错误判断和逐步排除过程:
- 翻译接口返回 HTTP 400 内容安全错误;
- 首先怀疑一处网络访问相关技术术语;
- 改成完整中文名称以后仍然失败;
- 再改成更加中性的网络服务表达,依然失败;
- 转而检查具体国家和地区流量统计;
- 修改其中一组统计以后仍然失败;
- 继续将剩余同类表达统一概括;
- 重新建立当前中文源基线;
- 再次执行英文覆盖翻译;
- 最终状态恢复为
completed。
这次最大的体会并不是找到一个所谓的“敏感词列表”,而是:
内容安全错误未必由一个孤立关键词触发,也可能与整段上下文、多个词的组合甚至模型生成结果有关。
因此遇到类似 HTTP 400 时,与其不断猜某一个单词,不如先按照语义类别逐步缩小范围。
同时,也不应该为了让模型通过检查就直接删除历史文章内容。
对于并非文章核心的信息,采用更加中性、概括但仍保持原意的表达,通常是更稳妥的处理方式。
而对于真正具有技术记录价值的原始现场,则可以通过截图保留。
这样既能够让历史文章继续完成英文迁移,也尽量保持原有内容的完整性和可读性。
需要长期技术维护或远程问题排查?
我是拥有 15+ 年经验的 PHP / Go 后端工程师,长期关注已有系统维护、Bug 修复、性能优化、服务器排查、WordPress 网站维护和小功能迭代。
如果你的项目遇到以下情况,可以先从一次小问题排查开始合作:
- ✅ PHP / Laravel / Yii2 老项目无人维护
- ✅ Go / Gin 后端接口需要排查或优化
- ✅ WordPress 网站访问慢、报错或插件冲突
- ✅ Nginx / MySQL / Redis / Linux 服务器异常
- ✅ CDN / Cloudflare / DNS / HTTPS 配置问题
- ✅ 需要长期远程技术支持或兼职维护
更多介绍请查看:关于我 & 合作
微信:13980074657
邮箱:shuijingwanwq@gmail.com
Telegram:@shuijingwan
GitHub:https://github.com/shuijingwan


发表回复