Skip to content

Commit ca83646

Browse files
authored
feat(gemini): add thought signature validation support (#2120)
1 parent 46f183f commit ca83646

File tree

4 files changed

+27
-14
lines changed

4 files changed

+27
-14
lines changed

models/gemini/manifest.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@ resource:
3434
tool:
3535
enabled: true
3636
type: plugin
37-
version: 0.6.4
37+
version: 0.7.0

models/gemini/models/llm/llm.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
"gemini-3-pro-image-preview",
5151
}
5252

53+
# https://ai.google.dev/gemini-api/docs/thought-signatures#faqs
54+
DEFAULT_THOUGHT_SIGNATURE: bytes = b"skip_thought_signature_validator"
55+
5356

5457
class GoogleLargeLanguageModel(LargeLanguageModel):
5558
is_thinking = None
@@ -506,25 +509,32 @@ def _format_message_to_gemini_content(
506509
:return: Gemini Content representation of message
507510
"""
508511

509-
def _build_text_parts(_content: str | TextPromptMessageContent) -> List[types.Part]:
512+
def _build_text_parts(
513+
_content: str | TextPromptMessageContent, *, is_assistant_tree: bool = False
514+
) -> List[types.Part]:
510515
text_parts = []
511516
if isinstance(_content, TextPromptMessageContent):
512517
_content = _content.data
513518
if message.role == PromptMessageRole.ASSISTANT:
514519
_content = re.sub(r"^<think>.*?</think>\s*", "", _content, count=1, flags=re.DOTALL)
515520
if _content:
516-
text_parts.append(types.Part.from_text(text=_content))
521+
_unverified_part = types.Part.from_text(text=_content)
522+
if is_assistant_tree:
523+
_unverified_part.thought_signature = DEFAULT_THOUGHT_SIGNATURE
524+
text_parts.append(_unverified_part)
517525
return text_parts
518526

519527
# Helper function to build parts from content
520-
def build_parts(content: str | List[PromptMessageContentUnionTypes]) -> List[types.Part]:
528+
def build_parts(
529+
content: str | List[PromptMessageContentUnionTypes], *, is_assistant_tree: bool = False
530+
) -> List[types.Part]:
521531
if isinstance(content, str):
522-
return _build_text_parts(content)
532+
return _build_text_parts(content, is_assistant_tree=is_assistant_tree)
523533

524534
parts_ = []
525535
for obj in content:
526536
if obj.type == PromptMessageContentType.TEXT:
527-
parts_.extend(_build_text_parts(obj))
537+
parts_.extend(_build_text_parts(obj, is_assistant_tree=is_assistant_tree))
528538
else:
529539
# Filter files based on type and supported formats
530540
should_upload = True
@@ -542,7 +552,10 @@ def build_parts(content: str | List[PromptMessageContentUnionTypes]) -> List[typ
542552
uri, mime_type = self._upload_file_content_to_google(
543553
obj, genai_client, file_server_url_prefix
544554
)
545-
parts_.append(types.Part.from_uri(file_uri=uri, mime_type=mime_type))
555+
_unverified_part = types.Part.from_uri(file_uri=uri, mime_type=mime_type)
556+
if is_assistant_tree:
557+
_unverified_part.thought_signature = DEFAULT_THOUGHT_SIGNATURE
558+
parts_.append(_unverified_part)
546559
else:
547560
# Log skipped files for debugging
548561
logging.debug(
@@ -560,17 +573,17 @@ def build_parts(content: str | List[PromptMessageContentUnionTypes]) -> List[typ
560573

561574
# Handle text content (remove thinking tags)
562575
if message.content:
563-
parts.extend(build_parts(message.content))
576+
parts.extend(build_parts(message.content, is_assistant_tree=True))
564577

565578
# Handle tool calls
566579
# https://ai.google.dev/gemini-api/docs/function-calling?hl=zh-cn&example=chart#how-it-works
567580
if message.tool_calls:
568581
call = message.tool_calls[0]
569-
parts.append(
570-
types.Part.from_function_call(
571-
name=call.function.name, args=json.loads(call.function.arguments)
572-
)
582+
_unsafe_part = types.Part.from_function_call(
583+
name=call.function.name, args=json.loads(call.function.arguments)
573584
)
585+
_unsafe_part.thought_signature = DEFAULT_THOUGHT_SIGNATURE
586+
parts.append(_unsafe_part)
574587

575588
return types.Content(role="model", parts=parts)
576589

models/gemini/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ requires-python = ">=3.12"
88
# uv pip compile pyproject.toml -o ./requirements.txt
99
dependencies = [
1010
"dify-plugin>=0.5.1,<0.6.0",
11-
"google-genai>=1.51.0,<2.0.0",
11+
"google-genai>=1.52.0,<2.0.0",
1212
"google-generativeai>=0.8.5",
1313
"numpy>=2.3.2",
1414
]

models/gemini/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ google-auth==2.41.1
5252
# google-generativeai
5353
google-auth-httplib2==0.2.0
5454
# via google-api-python-client
55-
google-genai==1.51.0
55+
google-genai==1.52.0
5656
# via gemini-g9cie8 (pyproject.toml)
5757
google-generativeai==0.8.5
5858
# via gemini-g9cie8 (pyproject.toml)

0 commit comments

Comments
 (0)