mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(gemini): emit thoughtSignature sentinel for cross-provider tool_calls in native adapter
When Hermes fails over from a non-Gemini provider (xAI, Anthropic, etc.) to
Gemini mid-conversation, the existing assistant tool_calls in history carry
no Gemini ``extra_content.google.thought_signature`` (the originating provider
never emits one). The native adapter's ``_translate_tool_call_to_gemini``
omitted ``thoughtSignature`` entirely in that case, so Gemini 3 thinking
models rejected every replayed turn with::
HTTP 400 INVALID_ARGUMENT
Function call is missing a thought_signature in functionCall parts.
Additional data, function call default_api:<tool_name>, position N.
The Cloud Code Assist sibling adapter already handles this exact case by
emitting a sentinel ``"skip_thought_signature_validator"`` (see
``agent/gemini_cloudcode_adapter.py:106``, originally added in #11270 and
documented as matching ``opencode-gemini-auth``'s approach). This change
mirrors that fallback in the native adapter so the two paths behave
identically when replaying cross-provider history.
Verified live against ``generativelanguage.googleapis.com/v1beta`` with
``gemini-3-pro-preview``: synthetic 2-turn conversation with no real
``thoughtSignature`` returns 400 without the sentinel and 200 with it.
Test added: ``test_build_native_request_emits_sentinel_for_cross_provider_tool_call``.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f65d105cbb
commit
8d119832b4
2 changed files with 43 additions and 2 deletions
|
|
@ -270,8 +270,12 @@ def _translate_tool_call_to_gemini(tool_call: Dict[str, Any]) -> Dict[str, Any]:
|
|||
}
|
||||
}
|
||||
thought_signature = _tool_call_extra_signature(tool_call)
|
||||
if thought_signature:
|
||||
part["thoughtSignature"] = thought_signature
|
||||
# Fallback sentinel for cross-provider tool_calls (e.g. fallback from
|
||||
# xAI/Anthropic to Gemini, where the original tool_call carries no
|
||||
# Gemini thoughtSignature). Mirrors gemini_cloudcode_adapter.py:106.
|
||||
# Without this, Gemini 3 thinking models reject replayed history with
|
||||
# 400 INVALID_ARGUMENT on the missing thoughtSignature.
|
||||
part["thoughtSignature"] = thought_signature or "skip_thought_signature_validator"
|
||||
return part
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,43 @@ def test_build_native_request_preserves_thought_signature_on_tool_replay():
|
|||
assert parts[0]["thoughtSignature"] == "sig-123"
|
||||
|
||||
|
||||
def test_build_native_request_emits_sentinel_for_cross_provider_tool_call():
|
||||
"""Cross-provider tool_calls (xAI/Anthropic -> Gemini fallback) carry no
|
||||
Gemini thoughtSignature. Without a sentinel, Gemini 3 thinking models
|
||||
reject the request with 400 INVALID_ARGUMENT. The native adapter must
|
||||
emit the same ``skip_thought_signature_validator`` sentinel that the
|
||||
Cloud Code Assist adapter already uses for the same scenario.
|
||||
"""
|
||||
from agent.gemini_native_adapter import build_gemini_request
|
||||
|
||||
request = build_gemini_request(
|
||||
messages=[
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_1",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"arguments": '{"city": "Paris"}',
|
||||
},
|
||||
# No extra_content — this tool_call originated from a
|
||||
# non-Gemini provider during fallback.
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
tools=[],
|
||||
tool_choice=None,
|
||||
)
|
||||
|
||||
parts = request["contents"][0]["parts"]
|
||||
assert parts[0]["functionCall"]["name"] == "get_weather"
|
||||
assert parts[0]["thoughtSignature"] == "skip_thought_signature_validator"
|
||||
|
||||
|
||||
def test_build_native_request_uses_original_function_name_for_tool_result():
|
||||
from agent.gemini_native_adapter import build_gemini_request
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue