Gemini API 坑我一下午:它明明干活了,为啥就是不说话?

搞 AI 的兄弟们肯定都遇到过奇葩 bug,但今天 Gemini API 给我上的这一课,属实是让我有点懵。

事情是这样的,我调 generate_content,代码跑得好好的,HTTP 状态码也是 200,没报错。但我拿到的 response 打印出来一看,人傻了:

# ...省略一堆没用的...
candidates=[Candidate(
  content=Content(
    role='model'
  ),
  # ...
)] 
# ...
usage_metadata=GenerateContentResponseUsageMetadata(
  candidates_token_count=40,
  prompt_token_count=713,
  total_token_count=753
)
# ...

看到了吗?candidates 里的 content 是个空壳子,说好的 text 呢?被谁吃了?

我第一反应是 SDK 的问题?还是网络抽风了?正准备 F12 打开网络面板从头查一遍的时候,我突然瞟到下面那个 usage_metadata

candidates_token_count=40

等会儿…… 这不对啊!

这个字段的意思是,模型为了生成回复,已经实打实地计算了 40 个 token

这就好比你点了个外卖,骑手 App 显示他已经取餐了,还跑了三公里,结果你开门一看,门口啥也没有,连个包装袋都看不见。钱还照扣了!

这说明什么?说明 模型确实生成了内容,但在它到我手上之前,半路没了。

谁是“半路截胡”的程咬金?

能干这事儿的,十有八九就是 Google 内置的那个“安全内容保镖”——Safety Filter

我脑补了一下整个流程:

  1. 我的请求(713 个 token)发过去。
  2. Gemini 模型:“收到,老铁!马上给你整活儿。” 吭哧吭哧生成了 40 个 token 的内容。
  3. 内容准备出门,被门口的“安全保镖”一把拦住:“站住!你这段话我瞅着有点危险/不和谐/像抄的,不许出去!”
  4. “保镖”把内容没收了,然后 API 给我返回一个“一切正常”的空壳子,但账单(usage_metadata)还是诚实地记下了 Gemini 的劳动成果。

这可太坑了!不给东西还不告诉我是为啥。

行,怎么盘它?

既然猜到了是安全设置搞鬼,那就好办了。API 里肯定有线索。

第一招:直接看“口供”

别自己瞎猜,API 响应里其实有俩字段,就是专门干这个的:finish_reasonsafety_ratings

赶紧翻出完整的 candidate 对象,把这俩兄弟打印出来:

# response 是你拿到的 API 响应对象
if not response.parts:
    candidate = response.candidates[0]
    
    # 看它是为啥“收工”的
    print(f"收工原因: {candidate.finish_reason}")
    
    # 再看看“安全员”的打分报告
    print("安全报告:")
    for rating in candidate.safety_ratings:
        print(f"  - 类别: {rating.category}, 危险等级: {rating.probability}")

果然,finish_reason 直接给我显示了一个大大的 SAFETY。破案了。

第二招:解决问题

既然找到原因了,就有两种路子走:

1. 换个姿势再问一次(推荐)

这是最稳妥的办法。看看你的 Prompt,是不是有些词有点“擦边”或者容易被误解。换个说法,委婉一点,通常就能绕开这个“保镖”。大部分时候,我们都是被误伤的。

2. 直接“贿赂”保镖,让他放行(慎用!)

如果你确定你的场景没问题,就是不想让这个“保镖”瞎管,可以在初始化模型的时候给他塞点“好处”,让他睁一只眼闭一只眼。

from google.generativeai.types import HarmCategory, HarmBlockThreshold

# 直接告诉保镖:今天不上班,都别拦!
safety_settings = {
    HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_NONE,
    HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_NONE,
    HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_NONE,
    HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_NONE,
}

model = genai.GenerativeModel(
    model_name="gemini-2.5-flash-lite",
    safety_settings=safety_settings  # 把这个配置传进去
)

# 再试试,这次应该畅通无阻了
response = model.generate_content("你刚才那个被拦掉的提示词...")
print(response.text)

BLOCK_NONE 基本上就是把安全检查关了。注意,这有风险,万一模型真说出点啥不该说的,责任得你自己扛。所以用之前掂量掂量。

总结一下

所以下次再碰到 Gemini API 返回空消息,先别急着砸键盘骂 bug。

先看 usage_metadata 里的 candidates_token_count 是不是大于 0。 如果是,直接去查 candidates[0]finish_reason

十有八九,就是那个尽职尽责(有时有点愣)的“安全保镖”在给你一个惊喜。