Bug:Gemini Provider 重建工具消息时以 tool_call_id 充当 functionResponse.name,导致函数调用历史配对失败(400)

Open Beginner friendly
#9,876 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
python
Domain
ai

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

area:provider

问题描述

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"}}

且由于这条记录会一直留在会话历史里,该会话后续所有请求都会持续失败。

复现条件

满足以下任一情况后,第二轮工具调用往返即触发:

  1. 会话历史先经 OpenAI 兼容 provider/网关产生(tool_call_idcall_xxx 等 opaque ID),之后切换到 Gemini provider 继续使用同一会话;

  2. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from AstrBotDevs/AstrBot

All issues in AstrBotDevs/AstrBot

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.