Gemini 的 recitation 和 spii 是什么错:内容被拦了怎么办

2026-08-25

数据截至 2026-08,价格与限额以各官网为准。本文只讲计费与接入机制,不列具体价格数字。

Gemini API 的错误参考里,recitationspii 不属于”请求写错了”,也不属于”服务出问题了”,它们属于第三族:生成被阻止码。这一族的官方处置只有一句话——修改输入后重试。这句话的重点不在”重试”,在”修改输入”:原样重试、加个指数退避再原样重试,官方都没有把它列为这一族的处置方式。所以工程上最该做的一件事,是在错误分流的第一步就把这一族从通用重试队列里摘出去,单独走一条”人工/上层逻辑介入改提示”的路径。另一件事是:如果你开了流式,这类错误不会以 HTTP 状态码的形式出现,只判状态码的客户端会整族漏掉。

先分清你手上这个 code 属于哪一族

Gemini API 的错误响应统一返回一个 error 对象,里面有两个字段:code 是机器可读的 snake_case 字符串,message 是给人看的描述。你的错误处理逻辑应该判 code,日志里应该把 message 原样留下来——这两个字段是分工的,别只记一个。

按「错在哪个环节」把官方错误参考里的码归一归,能归出三族,这个分法比按 HTTP 状态码分更有用:

  • 标准请求级错误码:这一族逐条对应 HTTP 状态码,比如 invalid_requestparameter_unknown 对应 400、authentication 对应 401、permission_denied 对应 403、not_foundmodel_not_found 对应 404、rate_limit_exceededquota_exceeded 对应 429、cancelled 对应 499、api_error 对应 500、service_unavailable 对应 503。这一族的共同点是:错在请求本身或服务本身,模型压根没开始正常产出内容。
  • 生成被阻止码:也就是本文要讲的这一族,请求本身是合法的,模型这次生成被政策、安全或内容限制拦下了。
  • 生成结构错误码:模型确实产出了,但产出物的结构有问题,比如 malformed_function_call(函数调用无法解析)、unexpected_tool_call(调用了请求中并未声明的工具)、too_many_tool_calls(工具调用数超上限)、missing_thought_signature(响应缺少必需的 thought 签名)。这一族出现在做 Agent 和 function calling 的场景里,处置思路和政策拦截完全不同,细节可以看Gemini 工具调用报 malformed_function_call 怎么办

值得注意的是,逐行标了 HTTP 状态码的那张表,覆盖的是标准请求级这一族。生成被阻止这一族,能确认的只有码名和释义——官方文档里没有找到这些码与 HTTP 状态码的对应说明,所以分流逻辑别指望靠状态码把它们摘出来,只能判 code 字符串。另外官方补了一句兜底说明:没有被列出的错误码,API 会以标准 HTTP 状态文本的 snake_case 形式返回。也就是说,你遇到一个没在文档里的 code,多半就是状态文本转写来的,不必到处找它的专属语义。

官方列出的生成被阻止码,逐个是什么意思

这一族官方列出的码名如下(以官方错误参考文档为准,码名可能随文档更新变动):

safetyrecitationlanguageprohibited_contentspiiblocklistimage_safetyimage_prohibited_contentimage_recitationimage_othercontent_blocked

其中官方给了中文释义的有这几个:

  • safety:有害内容
  • recitation:版权或引述限制
  • language:不支持的语言
  • spii:敏感个人身份信息
  • blocklist:封锁名单禁用词
  • content_blocked:不明政策原因

prohibited_content 官方在这份错误参考里只给了码名,没有另附释义;四个 image_ 开头的码同样只有码名。所以这几个的确切判定口径,官方文档里没有找到进一步说明——别按字面猜,日志里把 message 留全,那才是官方这次给你的具体理由。

从这份名单本身能读出一个结构:safetyprohibited_contentrecitation 三个码各自有一个 image_ 前缀的镜像版本,此外还多出一个 image_other。也就是说,图像方向的拦截被单独编了一套码,而且比文本方向多了一个兜底位。工程上的直接含义是:如果你的应用同时会走文本和图像生成,错误分流的映射表不能只写 safety,得把 image_safety 一并写上,否则图像路径上的拦截会掉进你的 default 分支。

content_blocked 是这一族的另一个兜底位,官方释义就是”不明政策原因”。这个码存在本身传达了一个信息:官方保留了不告诉你具体拦截维度的余地。所以指望靠 code 把每次拦截归因到某条具体规则,这条路走不通。

官方对这一族只给了一条统一处置

标准请求级那张表是逐行给处置建议的:rate_limit_exceeded 要等待加指数退避重试,quota_exceeded 要等配额重置或申请提额,authentication 要验证密钥,model_not_found 要验证模型名或回退到其他模型——每个码的动作都不一样。

到了生成被阻止这一族,官方给的是一条统一处置:修改输入后重试。没有逐码的差异化建议,recitationspii 也不例外。

这句话怎么落到代码上,值得拆开说:

第一,重试的前提是输入变了。 官方在这一族给的动作是”修改输入后重试”,不是”等待后重试”。你那套给 429 和 503 用的指数退避逻辑,退避的是时间,输入一个字没动。用它去重试一个 recitation,重试多少次都是在提交同一份被拦下的输入。

第二,“修改输入”的主语通常不是重试器。 退避重试可以完全自动化,改输入不行——要么由上层业务逻辑决定改成什么(换措辞、去掉某段引文、把用户上传的原文换成摘要),要么退回给用户。所以这一族在架构上应该从重试中间件里穿出去,变成一个业务层要处理的结果分支,而不是一个”网络抖动”式的瞬时故障。

第三,别把它计入故障率。 政策拦截和服务不可用混在同一个错误计数器里,会让你的健康度指标失真:service_unavailable 涨说明上游有事,prohibited_content 涨说明你的输入分布变了,两件事该触发的动作完全不同。

recitation 和 spii:官方给了释义,没给判定细节

这两个码在中文资料里几乎没人写,也正因为如此,最容易被脑补。这里把能确认的和不能确认的分开摆。

能确认的recitation 官方释义是”版权或引述限制”,spii 官方释义是”敏感个人身份信息”(SPII)。两者都归在生成被阻止族,处置和族内其他码一致,都是修改输入后重试。

不能确认的:官方文档里没有找到 recitation 的触发条件、判定阈值、涉及哪些语料范围的说明;也没有找到 spii 被判定为敏感个人身份信息的字段清单或识别口径。什么样的引用长度会触发、哪些证件号或联系方式算在内,这些官方在这份错误参考里都没有说。

所以务实的做法是反过来推:既然官方给的动作是改输入,那么排查时先把这次请求的输入完整拉出来,对着码名给的维度看——遇到 recitation 就看提示词或上下文里是不是塞了大段原文引述;遇到 spii 就看输入里是不是带了真实的个人身份信息。这是按官方释义指的方向去自查,不是官方给出的判定规则,别把自查结论写成”触发条件”贴给团队。

顺带一提,blocklist 的官方释义是”封锁名单禁用词”,从字面看它指向的是一份名单;至于这份名单由谁维护、命中判定怎么做,官方在这份错误参考里同样没有说明。它的官方处置和族内其他码一致,也是修改输入后重试。

流式请求下这类错误不走 HTTP 状态码

这是整篇里工程后果最大的一条,而且它对生成被阻止族尤其要命。

官方明确写了标准请求和流式请求的错误传递方式不同:

  • 标准(非流式)请求:设置 HTTP 状态码,同时在 JSON body 里返回 error 对象;
  • 流式请求(SSE,stream: true):通过 SSE 流发送一个 event_type"error" 的事件,error 字段的结构和非流式时相同。

直接后果就是:只判 HTTP 状态码的客户端,会漏掉流式过程中的错误。对生成被阻止这一族来说,漏掉的表现往往不是报错,而是”内容莫名其妙少了一截”或者”这次什么都没生成”——因为你的代码从头到尾没看见那个 error 事件。

所以流式分支里要多做一件事:在读流的循环里判 event_type,遇到 "error" 就按同一套 code 分流逻辑走,和非流式共用一份码表。这块的完整讲法在Gemini 流式请求的错误不走 HTTP 状态码

客户端该怎么改:一张三分支的码表

把上面几节合起来,落地就是一张分流表,三个分支:

  1. 可自动重试分支:标准请求级里官方明确写了”等待 + 指数退避重试”的是两个码——rate_limit_exceededservice_unavailableapi_error 常被顺手塞进同一支,但要注意官方给它的处置写法不一样,是”重试;持续则联系支持”——里面没有退避这层意思,倒是多了一句联系支持,所以真撞上连续 api_error 时,光靠加长退避窗口是接不住的。注意 429 这一格里的两个码语义不同,quota_exceeded 退避多少次都没用,这个坑单独讲在Gemini 的两种 429 完全不是一回事,通用侧的处理思路可以对照API 429 错误的通用处理
  2. 改输入分支:整个生成被阻止族,十一个码全部进这一支,包含四个 image_ 前缀的。分支里做的事是回传给上层,附上 message,不做自动重试。
  3. 改代码分支invalid_requestparameter_unknown 这类请求写错的,以及生成结构错误族的 malformed_function_call 等,这些靠重试和改提示都解决不了,得改调用代码或工具声明。

这张表要在流式和非流式两条路径上共用,否则你会在流式那条路上重新长出一套判断逻辑,然后两边慢慢跑偏。

最后:三个容易栽的坑

一是把政策拦截当瞬时故障重试,浪费配额还压根不解决问题——官方给这一族的动作是改输入,不是改重试节奏。二是码表只写文本侧的 safetyprohibited_content,忘了 image_ 那四个,图像路径上的拦截全掉进 default。三是流式分支只判 HTTP 状态码,把整族错误静默吞掉,最后表现成”输出偶尔不完整”这种极难定位的现象。

还有一个心态上的坑:想给 recitationspii 找到一份精确的触发规则清单。官方在错误参考里只给了这两个码的释义,没有给判定细节,遇到时把输入按释义指的方向自查一遍,比在网上找”触发条件”靠谱得多。所有码名与释义以官方错误参考文档的当前版本为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。