-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Capture thought signatures from Google models #2305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Capture thought signatures from Google models #2305
Conversation
# thought_signature=_base64_string_to_bytes(item.signature), | ||
# text=item.content, | ||
# ) | ||
# ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NOTE: We don't send ThinkingPart to the providers yet. If you are unsatisfied with this,
Can we enable it here, or is there a broader rule about this in place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah we can return the signatures, just not the actual thought text.
@@ -682,7 +682,7 @@ class ThinkingPart: | |||
signature: str | None = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose the alternative to base64 encoding/decoding would be do signature: str | bytes | None = None
, but I'm not sure what kind of ripple effects that would produce.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's do that
@@ -511,7 +525,7 @@ def _process_response_from_parts( | |||
for part in parts: | |||
if part.text is not None: | |||
if part.thought: | |||
items.append(ThinkingPart(content=part.text)) | |||
items.append(ThinkingPart(content=part.text, signature=_bytes_to_base64_string(part.thought_signature))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I don't think this is going to work.
The thinking signature in the example (see Appendix 1) comes right after the thinking part.
So, I think support for a signature
field would need to be added to ToolCallPart()
and TextPart()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, interesting, I had missed that detail in reading your issue originally.
For consistency with Anthropic, and to not have to modify all the other part classes with this Google-specific detail, I would prefer to store the signature on the most recent thinking part in Pydantic AI representation, and then to include it on the (next) text/tool part when sending it back to Google. Can you see if you can make that work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that we'll also need to handle the signature when streaming, which is implemented here:
yield self._parts_manager.handle_thinking_delta(vendor_part_id='thinking', content=part.text) |
Since the signature will come on a text/tool message, we'll want to check if its set there, and if so call handle_thinking_delta(vendor_part_id='thinking', signature=signature)
before we call handle_text_delta
or handle_tool_call_delta
so that the signature will be added to the most recently seen thinking part.
@@ -511,7 +525,7 @@ def _process_response_from_parts( | |||
for part in parts: | |||
if part.text is not None: | |||
if part.thought: | |||
items.append(ThinkingPart(content=part.text)) | |||
items.append(ThinkingPart(content=part.text, signature=_bytes_to_base64_string(part.thought_signature))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that we'll also need to handle the signature when streaming, which is implemented here:
yield self._parts_manager.handle_thinking_delta(vendor_part_id='thinking', content=part.text) |
Since the signature will come on a text/tool message, we'll want to check if its set there, and if so call handle_thinking_delta(vendor_part_id='thinking', signature=signature)
before we call handle_text_delta
or handle_tool_call_delta
so that the signature will be added to the most recently seen thinking part.
Overview
Capture the thought signature for Google Models.
Note: This requires casting the bytes Google provides to a base64-encoded string for compatibility with
ThoughtPart()
'ssignature
field, and then back to bytes when returning the response to Google.Closes #2293