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。
我脑补了一下整个流程:
- 我的请求(713 个 token)发过去。
- Gemini 模型:“收到,老铁!马上给你整活儿。” 吭哧吭哧生成了 40 个 token 的内容。
- 内容准备出门,被门口的“安全保镖”一把拦住:“站住!你这段话我瞅着有点危险/不和谐/像抄的,不许出去!”
- “保镖”把内容没收了,然后 API 给我返回一个“一切正常”的空壳子,但账单(
usage_metadata)还是诚实地记下了 Gemini 的劳动成果。
这可太坑了!不给东西还不告诉我是为啥。
行,怎么盘它?
既然猜到了是安全设置搞鬼,那就好办了。API 里肯定有线索。
第一招:直接看“口供”
别自己瞎猜,API 响应里其实有俩字段,就是专门干这个的:finish_reason 和 safety_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。
十有八九,就是那个尽职尽责(有时有点愣)的“安全保镖”在给你一个惊喜。