From 14f6cdbb7b15ba1de1d9e3c1a05d1cebc1dd1564 Mon Sep 17 00:00:00 2001 From: Yufeng He <40085740+he-yufeng@users.noreply.github.com> Date: Mon, 8 Jun 2026 05:31:28 +0800 Subject: [PATCH] fix: keep mldev candidate parts separate --- google/genai/models.py | 35 +++++++++++++++++++--- google/genai/tests/types/test_part_type.py | 35 ++++++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/google/genai/models.py b/google/genai/models.py index 547132546..c18a99ec3 100644 --- a/google/genai/models.py +++ b/google/genai/models.py @@ -189,6 +189,31 @@ def _Candidate_from_mldev( return to_object +def _dedupe_mldev_candidate_parts(candidates: list[dict[str, Any]]) -> None: + if len(candidates) < 2: + return + + first_parts = getv(candidates[0], ['content', 'parts']) + if not isinstance(first_parts, list): + return + + duplicate_tail = [] + for candidate in candidates[1:]: + parts = getv(candidate, ['content', 'parts']) + if not isinstance(parts, list) or not parts: + return + duplicate_tail.extend(parts) + + if ( + duplicate_tail + and len(first_parts) > len(duplicate_tail) + and first_parts[-len(duplicate_tail) :] == duplicate_tail + ): + content = getv(candidates[0], ['content']) + if isinstance(content, dict): + content['parts'] = first_parts[: -len(duplicate_tail)] + + def _CitationMetadata_from_mldev( from_object: Union[dict[str, Any], object], parent_object: Optional[dict[str, Any]] = None, @@ -1697,13 +1722,15 @@ def _GenerateContentResponse_from_mldev( ) if getv(from_object, ['candidates']) is not None: + candidates = [ + _Candidate_from_mldev(item, to_object, root_object) + for item in getv(from_object, ['candidates']) + ] + _dedupe_mldev_candidate_parts(candidates) setv( to_object, ['candidates'], - [ - _Candidate_from_mldev(item, to_object, root_object) - for item in getv(from_object, ['candidates']) - ], + candidates, ) if getv(from_object, ['modelVersion']) is not None: diff --git a/google/genai/tests/types/test_part_type.py b/google/genai/tests/types/test_part_type.py index a856b15de..c941957cf 100644 --- a/google/genai/tests/types/test_part_type.py +++ b/google/genai/tests/types/test_part_type.py @@ -15,6 +15,7 @@ import pytest +from ... import models from ... import types @@ -83,6 +84,40 @@ def test_two_candidates_text(caplog, generate_content_response): generate_content_response.candidates = None +def test_mldev_candidate_count_keeps_candidate_parts_separate(): + response_dict = models._GenerateContentResponse_from_mldev({ + 'candidates': [ + { + 'content': { + 'parts': [ + {'text': 'first'}, + {'text': 'second'}, + {'text': 'third'}, + ] + } + }, + {'content': {'parts': [{'text': 'second'}]}}, + {'content': {'parts': [{'text': 'third'}]}}, + ] + }) + + response = types.GenerateContentResponse._from_response( + response=response_dict, + kwargs={}, + ) + + assert [part.text for part in response.candidates[0].content.parts] == [ + 'first' + ] + assert [part.text for part in response.candidates[1].content.parts] == [ + 'second' + ] + assert [part.text for part in response.candidates[2].content.parts] == [ + 'third' + ] + assert response.text == 'first' + + def test_thought_signature_no_warning(caplog, generate_content_response): generate_content_response.candidates = [ types.Candidate(