Gemini 内容被安全策略拦截:SAFETY 报错的判断与处理

被安全策略拦截时返回体长什么样

请求整体被挡住:连候选都没有

提示词在入口就被拦下时,返回体里没有 candidates 字段,只有一段 promptFeedback

{
  "promptFeedback": {
    "blockReason": "SAFETY",
    "safetyRatings": [
      {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "HIGH"},
      {"category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE"}
    ]
  }
}

看到 blockReason 就说明请求根本没进入生成阶段,模型没有开始写任何一个字。这种情况改输出格式、改温度、换模型都不会有用,问题在提示词内容本身。

生成中途被截断:有候选但 finishReason 是 SAFETY

另一种情况是模型已经开始回答,中途触发策略停止:

{
  "candidates": [
    {
      "finishReason": "SAFETY",
      "finishMessage": "The response was blocked due to safety concerns.",
      "content": {"parts": [{"text": ""}]}
    }
  ]
}

这类返回里 parts 往往是空的,或者只有半句话。处理它的方式和入口拦截不同:要检查的是已经生成的那部分,判断是内容触发了策略,还是回答方向跑偏。

OpenAI 兼容层会把它翻译成 content_filter

走 OpenAI 兼容协议时,客户端拿到的是这一段:

{
  "choices": [
    {"index": 0, "message": {"role": "assistant", "content": null}, "finish_reason": "content_filter"}
  ]
}

contentnullfinish_reasoncontent_filter。这是判断依据:看到 content_filter,就不要再去找 key 或网络的问题了。

六种 finishReason 分别代表什么

先看这一张对照表

finishReason含义你该做什么
STOP正常结束直接使用返回内容
MAX_TOKENS输出长度到顶被截断调大输出上限或分段提问
SAFETY内容触发安全策略检查提示词措辞与意图
RECITATION疑似复述受版权保护的原文让它改用概述而不是照抄
PROHIBITED_CONTENT命中禁止类内容该内容不在可处理范围内
IMAGE_SAFETY图片生成被策略拦截检查图片描述与人物相关内容

容易被误判成安全拦截的是 MAX_TOKENS

输出写到一半没了、文字戛然而止,很多人以为是安全拦截,其实是长度上限。判断依据只有一条:看 finishReason 的原文,不要靠感觉猜。

RECITATION 也不是安全拦截

模型判断回答将大段复述受保护文本时会停止,处理办法是让对方改用概述、改写、给要点,而不是索要原文。这类问题在翻译和引文类任务里出现得多。

安全类别与阈值的工作方式

四个常见类别

返回体里的 safetyRatings 会逐条给概率值,从 NEGLIGIBLEHIGH。哪个类别是 HIGH,就把注意力放在哪一条上,这个字段是定位误判方向有价值的信息。

阈值决定拦截线在哪里

接口支持按类别调整拦截阈值,从只在概率很高时拦截,到只要有一点可能就拦截。但在聚合接入平台上,出于合规要求,通常沿用标准阈值,不开放按用户随意调低。真正有效的做法是改提示词,而不是调阈值。

触发是概率判断,不是关键词匹配

同一段文字换个说法,结果可能完全不同;反过来,看上去很平常的词放在特定上下文里也会被判定为高风险。这意味着反复重发同一段提示词,大概率得到同样的结果,只会白花调用次数。

三步确认到底是不是安全拦截

先看返回体里有没有 blockReason

blockReason 就是入口拦截,直接跳到改写提示词这一步;没有就继续往下看。

再看 finishReason 是不是 SAFETY 或 content_filter

是这两个值,说明生成阶段被拦;是 MAX_TOKENS,改输出长度限制;是 STOP 但内容为空,那要查的是解析代码有没有读错字段。

把原始响应完整打出来

import json, os
from openai import OpenAI

client = OpenAI(api_key=os.environ["XYU_API_KEY"], base_url="https://xyuapi.top/v1")
resp = client.chat.completions.create(
    model="gemini-2.5-pro",
    messages=[{"role": "user", "content": prompt}],
)
print(json.dumps(resp.model_dump(), ensure_ascii=False)[:1200])

先看清返回的原始结构,再决定改什么。跳过这一步直接改提示词,往往改了好几版都不知道自己在跟什么东西对抗。

代码里必须处理空内容

客户端崩溃常出在 content 为 null

choice = resp.choices[0]
text = choice.message.content
if not text:
    print("本次返回为空,finish_reason =", choice.finish_reason)
    text = ""          # 后面统一按空字符串处理,别让 None 继续往下传

None 一路传下去,之后会在某个 len() 或字符串拼接处炸出 TypeError,而真正的根因在几百行之前。入口处就地归一化,是省事的做法。

批量任务要单独统计被拦截的比例

跑一千条数据,如果被拦的有几十条,那是一个需要处理的业务问题;如果只有一两条,记下来跳过即可。没有统计,你只会觉得「偶尔有几条不通」,永远不知道该不该优化。

落盘保存被拦截的原文

把被拦的提示词原文、类别、finishReason 一起写进一个文件。攒够几十条之后回看,通常能发现它们有共同特征,比如都包含某些描述性词汇,或者都属于同一类业务场景。

记录格式用 JSON 行更省事,一行一条,字段固定成提示词、类别、finish_reason、时间。后续统计、检索、给同事看都方便,不需要再整理一遍。

统计要按业务线分开看。同一个提示词模板铺在十个场景里,可能只有一个场景反复被拦,那就只改那一个场景的措辞,没必要为了个别情况把整个模板改得面目全非。

被拦样本的保留时间建议不少于一个月。策略判定会随模型版本调整,上个月能过的提示词这个月未必还能过,留着历史样本才有对比依据。

提示词改写的正确做法

把意图说清楚,减少歧义

同一件事,写得含糊就容易被判高风险。做内容审核工具的场景,写成「请判断以下文本是否属于骚扰内容,输出类别与依据」,比「帮我分析这段话」清楚得多,也更容易通过。

补上业务背景与用途

开头加一句用途说明,例如「以下是客服工单文本,用于生成内部质量报告,请客观归纳问题类型」。模型拿到明确的场景与产出目标,判断会更聚焦。

去掉与任务无关的刺激性描述

任务是分类就只给分类需要的信息,不需要把细节原样复述一遍。信息够用即可,多余的形容词和细节既容易触发策略,也不提升结果质量。

该换任务形式的就换

如果某个任务反复被拦,先想一想是不是任务本身就不该交给模型做。换成让模型给审核建议、给话术模板、给风险清单,往往既合规又能拿到有价值的结果。

还有一点值得放在心里:被拦不代表内容一定有问题。审核、医疗、法律这些本来就要处理敏感表述的场景,误拦比例天然更高。把拦截率当成一个可以管理的技术指标,而不是当成故障,心态和方案都会清楚很多。

提示词改完先小批量试十条,确认通过率稳定了再放量。一次投两千条然后发现全被拦,既浪费调用次数,还得重新组织一遍数据重跑。

生图场景的额外限制

图片生成对人物相关内容限制更严

图片模型的策略比纯文本更保守,凡是涉及真实人物形象、可能被误认成真人的描述,都容易被拦,返回 IMAGE_SAFETY。画产品图、场景图、插画风格的内容基本不受影响。

提示词要描述画面而不是描述意图

写清楚主体、场景、风格、构图、光线,让模型知道要画什么。含糊、留白过多的描述,模型只能自行补全,补出来的方向未必是你想要的,也更容易触发策略。

被拦后先换描述再换模型

换成另一种画面表达方式,通常比换模型更有效。价格上 NanoBanana-Pro 是 0.27 元/次,同一段描述反复重发就是反复计费,先把描述改好再发更划算。

合规边界:这几件事不做

平台按上游策略执行,不提供例外

对涉及违法、暴力、危害他人安全等明确不允许的内容,平台不会协助处理,也不会提供任何规避手段。这是使用这类接口的前提,把它当成系统的一部分来设计业务流程。

不要用变形、拼接、编码之类的方式反复试探

这类做法既拿不到稳定结果,也会让你的调用记录变得可疑。把精力放在业务表达的清晰度上,才是正路。

用流程兜住边界

把审核、抽检、人工复核做进业务流程,让被拦截的少量样本有人工兜底路径。这样即使某条内容被拦,业务也不会中断,客户体验有保障。

人工兜底路径也要有明确的处理时限和目标。审核类场景里,模型给出的是辅助建议,判定环节交给人来做,既用上了模型的效率,也不会把边界问题甩给用户。

内部的判定标准要写下来。哪些词算骚扰、哪类表述算风险,写成可查的规则表,让不同同事的判断保持一致,也让模型的提示词有明确的依据。

稳定接入与价格参考

接口信息

主入口 https://xyuapi.top/v1,备用 https://xyuai.cc/v1,另支持 Gemini 原生 /v1beta 路径,OpenAI 兼容协议,Chatbox、Cherry Studio、NextChat、Cline 这类客户端填上地址和 key 就能用。

被拦截的请求怎么计费

按次计费的模式下,是否计入以平台用量页显示的数字为准。做法上很简单:改写提示词之后再发,不要对着同一段文字连发五次,那五次大概率是同一种结果。

常用模型价格

模型价格说明
gemini-2.5-pro0.031 元/次文本审核、分类、归纳
gemini-3-pro-preview0.05 元/次复杂判断与改写
gemini-3.1-pro-preview0.09 元/次高难度分析与复核
claude-sonnet-4-5-thinking0.09 元/次长文处理与规则梳理
NanoBanana-Pro(生图)0.27 元/次图片生成与编辑

现在的动作清单

  1. 在客户端封装里判断 finish_reason,把 SAFETYPROHIBITED_CONTENTcontent_filter 统一归成「内容未通过」一类。
  2. content 为空的处理写进代码入口,避免 None 往下传。
  3. 落盘记录被拦提示词与类别,攒够样本再统一优化。
  4. 对反复被拦的任务,改任务形式而不是改参数。

相关阅读

🚀 想要立即使用?来小鱼API体验全系列AI模型

查看全部产品

支持Gemini / Claude / GPT / Grok / DeepSeek · 国内直连 · 支付宝/微信