Bug:Gemini Provider 重建工具消息时以 tool_call_id 充当 functionResponse.name,导致函数调用历史配对失败(400)
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 76/100
Research direction
Start in astrbot/core/provider/sources/gemini_source.py and trace how the messages list is converted into Gemini contents, especially the assistant tool_calls and role="tool" branches. Reproduce the second tool-call round with an opaque tool_call_id, then verify that functionResponse.name matches the earlier functionCall.name and the request no longer returns the reported 400.
Written by the indexing model from the issue text.
Description
问题描述
gemini_source.py 在把会话历史重建为 Gemini contents 时,对 role == "tool" 的消息使用 message.get("name", message["tool_call_id"]) 来生成 functionResponse.name。当历史中保存的 tool_call_id 是不透明 ID(如 call_xxx)而不是函数名时,重建出的请求会出现:
functionCall.name = "steal_meme_to_smile"(取自 assistant 消息的tool_calls[].function.name)functionResponse.name = "call_1073557"(直接沿用了tool_call_id)
两者不配对。任何校验 functionResponse.name 必须与前一条 functionCall.name 一致的 Gemini 兼容上游(Google 官方 API 及常见兼容网关)都会返回 400:
{"error":{"message":"... functionResponse.name \"call_1073557\" does not match functionCall.name \"steal_meme_to_smile\" ...","type":"invalid_request_error"}}
且由于这条记录会一直留在会话历史里,该会话后续所有请求都会持续失败。
复现条件
满足以下任一情况后,第二轮工具调用往返即触发:
-
会话历史先经 OpenAI 兼容 provider/网关产生(
tool_call_id为call_xxx等 opaque ID),之后切换到 Gemini provider 继续使用同一会话; -
Gemini 兼容网关在响应的
function_call中携带显式id。此时gemini_source.py中tool_call_id = part.function_call.id or part.function_call.name会把该 id 存入会话历史;而重建请求时
Part.from_function_call(name=..., args=...)并不会把 id 写回 functionCall,tool 分支又把 id 当作函数名写进 functionResponse,两侧必然不配对。
我们在线上(AstrBot + 兼容网关反代 antigravity 后端)实际遇到的即为此场景:首次工具调用成功,紧接着的下一轮请求开始全部 400。
根因
astrbot/core/provider/sources/gemini_source.py(main @ fe3d775,2026-08-29)中 tool 分支:
elif role == "tool":
func_name = message.get("name", message["tool_call_id"])
历史中 role:"tool" 消息通常只有 tool_call_id 而没有 name 字段,因此 opaque ID 被直接当成函数名使用。
修复建议
重建 contents 前先建立 tool_call_id -> function name 映射,tool 分支优先回查真实函数名:
gemini_contents: list[types.Content] = []
tool_call_names: dict[str, str] = {}
for msg in payloads["messages"]:
for tool in msg.get("tool_calls") or []:
if (
isinstance(tool, dict)
and tool.get("id")
and isinstance(tool.get("function"), dict)
and tool["function"].get("name")
):
tool_call_names[tool["id"]] = tool["function"]["name"]
for message in payloads["messages"]:
...
elif role == "tool":
func_name = (
message.get("name")
or tool_call_names.get(message["tool_call_id"])
or message["tool_call_id"]
)
该修复对存量历史同样生效(函数名可从同历史中的 assistant tool_calls 里找回)。可选的增强:重建 functionCall 时把保存的 id 一并写回(types.FunctionCall(id=...)),以兼容按 id 配对的上游。
环境信息
- AstrBot 版本:main 分支 fe3d775(2026-08-29)
- Provider:Gemini(google-genai SDK,兼容网关反代 antigravity 后端)
- 触发模型:gemini-3.6-flash-high / gemini-3.7-flash-high
- 已本地验证上述补丁可消除 400
- Dominant language
- Python
- Stars
- 40.7k
- Forks
- 2.9k
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 114
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from AstrBotDevs/AstrBot
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
AstrBotDevs/AstrBot#10033 · 3 comments ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
AstrBotDevs/AstrBot#9999 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
AstrBotDevs/AstrBot#9945 · 2 comments ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
AstrBotDevs/AstrBot#9938 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
AstrBotDevs/AstrBot#9929 · 2 comments ·
All issues in AstrBotDevs/AstrBot
Similar issues
-
documentation help wanted
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
simonw/sqlite-utils#872 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100