From af84b1b8c0278f4c3a2fa208ebf1efbad54953e1 Mon Sep 17 00:00:00 2001 From: Yohei Nishikubo Date: Wed, 25 Mar 2026 09:40:21 +0900 Subject: [PATCH] fix(Gemini): update ToolCallRequest and OpenAICompatProvider to handle thought signatures in extra_content --- nanobot/providers/base.py | 16 +++++++++++++++- nanobot/providers/openai_compat_provider.py | 7 +++++++ tests/agent/test_gemini_thought_signature.py | 2 +- tests/providers/test_litellm_kwargs.py | 4 ++-- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/nanobot/providers/base.py b/nanobot/providers/base.py index 046458dec..1fd610b91 100644 --- a/nanobot/providers/base.py +++ b/nanobot/providers/base.py @@ -30,7 +30,21 @@ class ToolCallRequest: }, } if self.provider_specific_fields: - tool_call["provider_specific_fields"] = self.provider_specific_fields + # Gemini OpenAI compatibility expects thought signatures in extra_content.google. + if "thought_signature" in self.provider_specific_fields: + tool_call["extra_content"] = { + "google": { + "thought_signature": self.provider_specific_fields["thought_signature"], + } + } + other_fields = { + k: v for k, v in self.provider_specific_fields.items() + if k != "thought_signature" + } + if other_fields: + tool_call["provider_specific_fields"] = other_fields + else: + tool_call["provider_specific_fields"] = self.provider_specific_fields if self.function_provider_specific_fields: tool_call["function"]["provider_specific_fields"] = self.function_provider_specific_fields return tool_call diff --git a/nanobot/providers/openai_compat_provider.py b/nanobot/providers/openai_compat_provider.py index 866e05ef8..1157e176d 100644 --- a/nanobot/providers/openai_compat_provider.py +++ b/nanobot/providers/openai_compat_provider.py @@ -43,6 +43,13 @@ def _coerce_dict(value: Any) -> dict[str, Any] | None: def _extract_tool_call_fields(tc: Any) -> tuple[dict[str, Any] | None, dict[str, Any] | None]: """Extract provider-specific metadata from a tool call object.""" provider_specific_fields = _coerce_dict(_get_attr_or_item(tc, "provider_specific_fields")) + extra_content = _coerce_dict(_get_attr_or_item(tc, "extra_content")) + google_content = _coerce_dict(_get_attr_or_item(extra_content, "google")) if extra_content else None + if google_content: + provider_specific_fields = { + **(provider_specific_fields or {}), + **google_content, + } function = _get_attr_or_item(tc, "function") function_provider_specific_fields = _coerce_dict( _get_attr_or_item(function, "provider_specific_fields") diff --git a/tests/agent/test_gemini_thought_signature.py b/tests/agent/test_gemini_thought_signature.py index 35739602a..f4b279b65 100644 --- a/tests/agent/test_gemini_thought_signature.py +++ b/tests/agent/test_gemini_thought_signature.py @@ -14,6 +14,6 @@ def test_tool_call_request_serializes_provider_fields() -> None: message = tool_call.to_openai_tool_call() - assert message["provider_specific_fields"] == {"thought_signature": "signed-token"} + assert message["extra_content"] == {"google": {"thought_signature": "signed-token"}} assert message["function"]["provider_specific_fields"] == {"inner": "value"} assert message["function"]["arguments"] == '{"path": "todo.md"}' diff --git a/tests/providers/test_litellm_kwargs.py b/tests/providers/test_litellm_kwargs.py index 4d1572075..e912a7bfd 100644 --- a/tests/providers/test_litellm_kwargs.py +++ b/tests/providers/test_litellm_kwargs.py @@ -40,7 +40,7 @@ def _fake_tool_call_response() -> SimpleNamespace: id="call_123", index=0, function=function, - provider_specific_fields={"thought_signature": "signed-token"}, + extra_content={"google": {"thought_signature": "signed-token"}}, ) message = SimpleNamespace( content=None, @@ -160,7 +160,7 @@ async def test_openai_compat_preserves_provider_specific_fields_on_tool_calls() assert tool_call.function_provider_specific_fields == {"inner": "value"} serialized = tool_call.to_openai_tool_call() - assert serialized["provider_specific_fields"] == {"thought_signature": "signed-token"} + assert serialized["extra_content"] == {"google": {"thought_signature": "signed-token"}} assert serialized["function"]["provider_specific_fields"] == {"inner": "value"}