Skip to content

Commit 283fa11

Browse files
speedstorm1copybara-github
authored andcommitted
fix: Drop the whole user turn from curated chat history when the model turn is invalid
fixes #3051 PiperOrigin-RevId: 998867927
1 parent a216d95 commit 283fa11

2 files changed

Lines changed: 89 additions & 2 deletions

File tree

‎google/genai/chats.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,10 @@ def _extract_curated_history(
104104
i += 1
105105
if is_valid:
106106
curated_history.extend(current_output)
107-
elif curated_history:
108-
curated_history.pop()
107+
else:
108+
# A user turn can span several consecutive user contents.
109+
while curated_history and curated_history[-1].role == "user":
110+
curated_history.pop()
109111
return curated_history
110112

111113

‎google/genai/tests/chats/test_get_history.py‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,91 @@ def test_history_with_invalid_turns():
441441
assert chat.get_history(curated=True) == curated_history
442442

443443

444+
def _multi_content_user_turn():
445+
return [
446+
types.Content(
447+
role='user',
448+
parts=[types.Part.from_text(text='what is the weather')],
449+
),
450+
types.Content(
451+
role='user',
452+
parts=[
453+
types.Part.from_function_response(
454+
name='get_weather', response={'c': 21}
455+
)
456+
],
457+
),
458+
]
459+
460+
461+
def test_history_with_multi_content_user_turn_and_invalid_model_output():
462+
"""An invalid model output drops every user content of its turn."""
463+
history = _multi_content_user_turn() + [
464+
types.Content(role='model', parts=[]),
465+
]
466+
467+
models_module = models.Models(mock_api_client)
468+
chats_module = chats.Chats(modules=models_module)
469+
chat = chats_module.create(model='gemini-2.5-flash', history=history)
470+
471+
assert chat.get_history() == history
472+
assert chat.get_history(curated=True) == []
473+
474+
475+
def test_history_with_valid_turn_then_invalid_multi_content_user_turn():
476+
"""Dropping the rejected user turn stops at the previous valid output."""
477+
valid_turn = [
478+
types.Content(role='user', parts=[types.Part.from_text(text='u1')]),
479+
types.Content(role='model', parts=[types.Part.from_text(text='m1')]),
480+
]
481+
history = (
482+
valid_turn
483+
+ _multi_content_user_turn()
484+
+ [types.Content(role='model', parts=[])]
485+
)
486+
487+
models_module = models.Models(mock_api_client)
488+
chats_module = chats.Chats(modules=models_module)
489+
chat = chats_module.create(model='gemini-2.5-flash', history=history)
490+
491+
assert chat.get_history() == history
492+
assert chat.get_history(curated=True) == valid_turn
493+
494+
495+
def test_history_with_invalid_multi_content_user_turn_then_valid_turn():
496+
"""No content of a rejected user turn attaches to the next valid turn."""
497+
valid_turn = [
498+
types.Content(role='user', parts=[types.Part.from_text(text='u2')]),
499+
types.Content(role='model', parts=[types.Part.from_text(text='m2')]),
500+
]
501+
history = (
502+
_multi_content_user_turn()
503+
+ [types.Content(role='model', parts=[])]
504+
+ valid_turn
505+
)
506+
507+
models_module = models.Models(mock_api_client)
508+
chats_module = chats.Chats(modules=models_module)
509+
chat = chats_module.create(model='gemini-2.5-flash', history=history)
510+
511+
assert chat.get_history() == history
512+
assert chat.get_history(curated=True) == valid_turn
513+
514+
515+
def test_async_chat_create_with_invalid_multi_content_user_turn():
516+
"""The async chat drops the whole rejected user turn too."""
517+
history = _multi_content_user_turn() + [
518+
types.Content(role='model', parts=[]),
519+
]
520+
521+
models_module = models.AsyncModels(mock_api_client)
522+
chats_module = chats.AsyncChats(modules=models_module)
523+
chat = chats_module.create(model='gemini-2.5-flash', history=history)
524+
525+
assert chat.get_history() == history
526+
assert chat.get_history(curated=True) == []
527+
528+
444529
def test_chat_with_empty_text_part(mock_generate_content_with_empty_text_part):
445530
models_module = models.Models(mock_api_client)
446531
chats_module = chats.Chats(modules=models_module)

0 commit comments

Comments
 (0)