fix: strip embedded thought signature from LiteLLM tool call ids - #6743
fix: strip embedded thought signature from LiteLLM tool call ids#6743benclarkeio wants to merge 1 commit into
Conversation
LiteLLM embeds a Gemini `thought_signature` in the tool call id, separated by `__thought__`. `_message_to_generate_content_response` already lifts that signature onto `part.thought_signature`, but then assigned the raw id to `part.function_call.id`, so every consumer of `function_call_id` saw a few hundred characters of base64 appended to the real id. - Split the separator off the id before assigning it to the function call - Leave `thought_signature` extraction unchanged, so nothing is lost The round trip is unaffected: `_content_to_message_param` re-attaches the signature to the outgoing tool call from `part.thought_signature` via `provider_specific_fields` and `extra_content.google.thought_signature`, and `_extract_thought_signature_from_tool_call` reads both of those before it falls back to the id-embedded form. Both sides of the call/response pairing are generated from ADK's own stored parts, so ids stay matched. Fixes google#6742
varunbiluri
left a comment
There was a problem hiding this comment.
The ID should only be shortened when the suffix was actually accepted as the embedded thought signature. As written, every provider-issued ID containing __thought__ is truncated, even when _decode_thought_signature() rejects the suffix (or when the signature was sourced from extra_content / provider_specific_fields). Tool-call IDs are otherwise opaque, so an ID such as job__thought__not-base64 becomes job, which can break response pairing or collide with another call. Please have the extraction path return the cleaned ID only when it successfully decodes the ID-embedded form (or otherwise gate this assignment on successful decoding specifically from that fallback), and add coverage showing a non-signature ID containing the separator is preserved.
Fixes #6742
Problem
LiteLLM embeds a Gemini
thought_signatureinside the tool call id, separated by__thought__(see_THOUGHT_SIGNATURE_SEPARATOR)._message_to_generate_content_responsealready extracts that signature ontopart.thought_signature, but then assigns the raw id to the function call:So every consumer of
function_call_idgets several hundred characters of base64 glued onto the real id:It leaks into logs, into any UI that displays a tool call id, and into anything that derives a name from one.
Fix
Split the separator off before assigning the id. The signature is already preserved on the part, so nothing is lost.
Why the round trip still works
_content_to_message_paramre-attaches the signature to the outgoing tool call frompart.thought_signature, via bothprovider_specific_fieldsandextra_content.google.thought_signature._extract_thought_signature_from_tool_callchecks those two locations before falling back to the id-embedded form, which exists only for providers that drop the other channels.tool_callsentry and thetoolmessage'stool_call_id) are generated from ADK's own stored parts, so stripping consistently keeps them matched.Testing
Added
test_message_to_generate_content_response_strips_signature_from_id, asserting the id is split and the signature still lands on the part.tests/unittests/models/test_litellm.pypasses in full (387 passed). Formatted withpyinkper the repo config.Note
This replaces #6599, which was opened from the same work but carried a co-author trailer on its commit. Rebased onto current
main(the surrounding code now goes through a localfunction_callvariable) and reopened clean.