Skip to content

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

Description

@283Gawin

问题描述

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:providerThe bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions