3838 VideoUrl ,
3939)
4040from ..profiles import ModelProfileSpec
41+ from ..profiles .google import GoogleModelProfile
4142from ..providers import Provider , infer_provider
4243from ..settings import ModelSettings
4344from ..tools import ToolDefinition
9394 ) from _import_error
9495
9596LatestGoogleModelNames = Literal [
97+ 'gemini-flash-latest' ,
98+ 'gemini-flash-lite-latest' ,
9699 'gemini-2.0-flash' ,
97100 'gemini-2.0-flash-lite' ,
98101 'gemini-2.5-flash' ,
99102 'gemini-2.5-flash-preview-09-2025' ,
100- 'gemini-flash-latest ' ,
103+ 'gemini-2.5- flash-image ' ,
101104 'gemini-2.5-flash-lite' ,
102105 'gemini-2.5-flash-lite-preview-09-2025' ,
103- 'gemini-flash-lite-latest' ,
104106 'gemini-2.5-pro' ,
107+ 'gemini-3-pro-preview' ,
105108]
106109"""Latest Gemini models."""
107110
@@ -228,12 +231,17 @@ def system(self) -> str:
228231 def prepare_request (
229232 self , model_settings : ModelSettings | None , model_request_parameters : ModelRequestParameters
230233 ) -> tuple [ModelSettings | None , ModelRequestParameters ]:
234+ supports_native_output_with_builtin_tools = GoogleModelProfile .from_profile (
235+ self .profile
236+ ).google_supports_native_output_with_builtin_tools
231237 if model_request_parameters .builtin_tools and model_request_parameters .output_tools :
232238 if model_request_parameters .output_mode == 'auto' :
233- model_request_parameters = replace (model_request_parameters , output_mode = 'prompted' )
239+ output_mode = 'native' if supports_native_output_with_builtin_tools else 'prompted'
240+ model_request_parameters = replace (model_request_parameters , output_mode = output_mode )
234241 else :
242+ output_mode = 'NativeOutput' if supports_native_output_with_builtin_tools else 'PromptedOutput'
235243 raise UserError (
236- 'Google does not support output tools and built-in tools at the same time. Use `output_type=PromptedOutput (...)` instead.'
244+ f 'Google does not support output tools and built-in tools at the same time. Use `output_type={ output_mode } (...)` instead.'
237245 )
238246 return super ().prepare_request (model_settings , model_request_parameters )
239247
@@ -418,9 +426,9 @@ async def _build_content_and_config(
418426 response_mime_type = None
419427 response_schema = None
420428 if model_request_parameters .output_mode == 'native' :
421- if tools :
429+ if model_request_parameters . function_tools :
422430 raise UserError (
423- 'Google does not support `NativeOutput` and tools at the same time. Use `output_type=ToolOutput(...)` instead.'
431+ 'Google does not support `NativeOutput` and function tools at the same time. Use `output_type=ToolOutput(...)` instead.'
424432 )
425433 response_mime_type = 'application/json'
426434 output_object = model_request_parameters .output_object
@@ -685,23 +693,25 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]:
685693
686694 for part in parts :
687695 if part .thought_signature :
696+ # Per https://ai.google.dev/gemini-api/docs/function-calling?example=meeting#thought-signatures:
697+ # - Always send the thought_signature back to the model inside its original Part.
698+ # - Don't merge a Part containing a signature with one that does not. This breaks the positional context of the thought.
699+ # - Don't combine two Parts that both contain signatures, as the signature strings cannot be merged.
700+
688701 signature = base64 .b64encode (part .thought_signature ).decode ('utf-8' )
702+ # Attach signature to most recent thinking part, if there was one
689703 yield self ._parts_manager .handle_thinking_delta (
690- vendor_part_id = 'thinking' ,
704+ vendor_part_id = None ,
691705 signature = signature ,
692706 provider_name = self .provider_name ,
693707 )
694708
695709 if part .text is not None :
696710 if len (part .text ) > 0 :
697711 if part .thought :
698- yield self ._parts_manager .handle_thinking_delta (
699- vendor_part_id = 'thinking' , content = part .text
700- )
712+ yield self ._parts_manager .handle_thinking_delta (vendor_part_id = None , content = part .text )
701713 else :
702- maybe_event = self ._parts_manager .handle_text_delta (
703- vendor_part_id = 'content' , content = part .text
704- )
714+ maybe_event = self ._parts_manager .handle_text_delta (vendor_part_id = None , content = part .text )
705715 if maybe_event is not None : # pragma: no branch
706716 yield maybe_event
707717 elif part .function_call :
@@ -760,6 +770,7 @@ def timestamp(self) -> datetime:
760770def _content_model_response (m : ModelResponse , provider_name : str ) -> ContentDict : # noqa: C901
761771 parts : list [PartDict ] = []
762772 thought_signature : bytes | None = None
773+ function_call_requires_signature : bool = True
763774 for item in m .parts :
764775 part : PartDict = {}
765776 if thought_signature :
@@ -769,6 +780,15 @@ def _content_model_response(m: ModelResponse, provider_name: str) -> ContentDict
769780 if isinstance (item , ToolCallPart ):
770781 function_call = FunctionCallDict (name = item .tool_name , args = item .args_as_dict (), id = item .tool_call_id )
771782 part ['function_call' ] = function_call
783+ if function_call_requires_signature and not part .get ('thought_signature' ):
784+ # Per https://ai.google.dev/gemini-api/docs/gemini-3?thinking=high#migrating_from_other_models:
785+ # > If you are transferring a conversation trace from another model (e.g., Gemini 2.5) or injecting
786+ # > a custom function call that was not generated by Gemini 3, you will not have a valid signature.
787+ # > To bypass strict validation in these specific scenarios, populate the field with this specific
788+ # > dummy string: "thoughtSignature": "context_engineering_is_the_way_to_go"
789+ part ['thought_signature' ] = b'context_engineering_is_the_way_to_go'
790+ # Only the first function call requires a signature
791+ function_call_requires_signature = False
772792 elif isinstance (item , TextPart ):
773793 part ['text' ] = item .content
774794 elif isinstance (item , ThinkingPart ):
@@ -881,7 +901,7 @@ def _function_declaration_from_tool(tool: ToolDefinition) -> FunctionDeclaration
881901 f = FunctionDeclarationDict (
882902 name = tool .name ,
883903 description = tool .description or '' ,
884- parameters = json_schema , # type: ignore
904+ parameters_json_schema = json_schema ,
885905 )
886906 return f
887907
0 commit comments