现场是这样的。用 function calling 查天气,把工具执行结果按 OpenAI 的 tool 消息塞进 contents,gemini-1.5-flash 返回 400,原文 functionResponse.name is required。

中间走过弯路,把「回填的 role 要用 user,用 model 会被当成新回复」当成主因,改完现象没变。

真正麻烦的地方在于,几个原因的现象几乎一样。结果块要带 functionResponse 结构,不能只给纯文本;name 必须是函数名,不是调用 id;多个函数并行调用时结果要各自成块,不能合并成一段。

第一件事是看上一轮返回里的 functionCall 名字

python -c "import json;d=json.load(open('resp.json'));print([p['functionCall']['name'] for p in d['candidates'][0]['content']['parts']])"

然后按函数名回填结果块重发

curl -sS "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=$GEMINI_API_KEY" -H 'content-type: application/json' -d @followup.json

等前面都确认过,再确认历史里没有残留空 parts

python -c "import json;d=json.load(open('followup.json'));print([len(c['parts']) for c in d['contents']])"

改之前我把相关日志按时间截了一段存着,事后对时间线用得上。

中途换过一次思路,把范围缩到最小的那一层,反而快了。

恢复的判定其实很直接。续轮返回 200 且 candidates 里给出自然语言回答,历史中每个 functionCall 都有同名 functionResponse 对应。

容易忽略的是,OpenAI 用调用 id 对应,Gemini 用函数名,两套语义别互抄,名字写错比缺字段更难查。线上能稳定复现的问题都不算难,难的是只在大流量下才出现的那种。

作者 9823189