diff --git a/google/genai/_transformers.py b/google/genai/_transformers.py index 6739647133..1ab19cd4fa 100644 --- a/google/genai/_transformers.py +++ b/google/genai/_transformers.py @@ -1397,3 +1397,4 @@ def t_is_vertex_embed_content_model(model: str) -> bool: def t_json_schema(origin: Any) -> Any: return origin + diff --git a/google/genai/models.py b/google/genai/models.py index c597326eb5..2d5240a2dd 100644 --- a/google/genai/models.py +++ b/google/genai/models.py @@ -6251,6 +6251,9 @@ def generate_content( # The image shows a flat lay arrangement of freshly baked blueberry # scones. """ + import collections.abc + if isinstance(contents, (collections.abc.Iterator, collections.abc.Generator)): + contents = list(contents) incompatible_tools_indexes = ( _extra_utils.find_afc_incompatible_tool_indexes(config) @@ -6431,6 +6434,9 @@ def generate_content_stream( # The image shows a flat lay arrangement of freshly baked blueberry # scones. """ + import collections.abc + if isinstance(contents, (collections.abc.Iterator, collections.abc.Generator)): + contents = list(contents) incompatible_tools_indexes = ( _extra_utils.find_afc_incompatible_tool_indexes(config) @@ -8442,6 +8448,9 @@ async def generate_content( print(response.text) # J'aime les bagels. """ + import collections.abc + if isinstance(contents, (collections.abc.Iterator, collections.abc.Generator)): + contents = list(contents) # Retrieve and cache any MCP sessions if provided. incompatible_tools_indexes = ( _extra_utils.find_afc_incompatible_tool_indexes( @@ -8694,6 +8703,9 @@ async def generate_content_stream( # The image shows a flat lay arrangement of freshly baked blueberry # scones. """ + import collections.abc + if isinstance(contents, (collections.abc.Iterator, collections.abc.Generator)): + contents = list(contents) if not config: parsed_config = None elif isinstance(config, dict): diff --git a/google/genai/tests/models/test_gc_continuation_token.py b/google/genai/tests/models/test_gc_continuation_token.py index 46dcdfcc5c..f2722a0958 100644 --- a/google/genai/tests/models/test_gc_continuation_token.py +++ b/google/genai/tests/models/test_gc_continuation_token.py @@ -4141,3 +4141,25 @@ async def _hop(resp): last = chunks[-1].candidates[0] assert last.finish_reason == types.FinishReason.MAX_TOKENS assert last.continuation_token == b'tok_hop2_max_tokens' + +def test_generate_content_accepts_reversed_iterator(mock_api_client): + """reversed([...]) contents should be materialized before Pydantic validation.""" + models_module = models.Models(mock_api_client) + contents = [ + types.Content(role='user', parts=[types.Part(text='hello')]), + types.Content(role='user', parts=[types.Part(text='world')]), + ] + mock_api_client.request.return_value = { + 'candidates': [{'content': {'role': 'model', 'parts': [{'text': 'ok'}]}, 'finish_reason': 'STOP'}] + } + with mock.patch.object( + models.Models, + '_generate_content_with_continuation', + return_value=types.GenerateContentResponse( + candidates=[types.Candidate(content=types.Content(role='model', parts=[types.Part(text='ok')]))] + ), + ) as mock_gc: + models_module.generate_content(model='gemini-2.5-pro', contents=reversed(contents)) + called_contents = mock_gc.call_args.kwargs['contents'] + assert isinstance(called_contents, list), 'reversed() should be materialized to a list' + assert len(called_contents) == 2