Skip to content

Commit 0a2fed5

Browse files
committed
fix(interactions/events):fixed response duplication and corrected is_final_response()
1 parent a2e43aa commit 0a2fed5

File tree

3 files changed

+74
-10
lines changed

3 files changed

+74
-10
lines changed

src/google/adk/events/event.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def is_final_response(self) -> bool:
9393
not self.get_function_calls()
9494
and not self.get_function_responses()
9595
and not self.partial
96+
and self.turn_complete
9697
and not self.has_trailing_code_execution_result()
9798
)
9899

src/google/adk/models/interactions_utils.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,16 +1005,6 @@ async def generate_content_via_interactions(
10051005
if llm_response:
10061006
yield llm_response
10071007

1008-
# Final aggregated response
1009-
if aggregated_parts:
1010-
yield LlmResponse(
1011-
content=types.Content(role='model', parts=aggregated_parts),
1012-
partial=False,
1013-
turn_complete=True,
1014-
finish_reason=types.FinishReason.STOP,
1015-
interaction_id=current_interaction_id,
1016-
)
1017-
10181008
else:
10191009
# Non-streaming mode
10201010
interaction = await api_client.aio.interactions.create(

tests/unittests/models/test_interactions_utils.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,3 +759,76 @@ def test_full_conversation(self):
759759
assert len(result) == 2
760760
assert result[0].parts[0].text == 'Great'
761761
assert result[1].parts[0].text == 'Tell me more'
762+
763+
764+
class TestResponseInteractionDeduplication:
765+
"""Tests for response interaction api deduplication."""
766+
767+
def test_is_final_response_requires_turn_complete_true(self):
768+
"""Verify is_final_response() returns False when turn_complete=False."""
769+
# Case 1: partial=False but turn_complete=False -> should be False
770+
event = Event(
771+
author='agent',
772+
content=types.Content(role='model', parts=[types.Part(text='Hello')]),
773+
partial=False,
774+
turn_complete=False, # Turn not complete
775+
)
776+
assert event.is_final_response() is False
777+
778+
# Case 2: partial=False and turn_complete=True -> should be True
779+
event_final = Event(
780+
author='agent',
781+
content=types.Content(role='model', parts=[types.Part(text='Hello')]),
782+
partial=False,
783+
turn_complete=True, # Turn complete
784+
)
785+
assert event_final.is_final_response() is True
786+
787+
def test_no_duplicate_final_response_in_streaming(self):
788+
"""Verify streaming events don't duplicate the final response."""
789+
from unittest.mock import MagicMock
790+
791+
# Simulate the streaming flow
792+
aggregated_parts = []
793+
794+
# Event 1: content.delta (streaming text)
795+
delta_event = MagicMock()
796+
delta_event.event_type = 'content.delta'
797+
delta_event.delta = MagicMock()
798+
delta_event.delta.type = 'text'
799+
delta_event.delta.text = 'Hello'
800+
801+
response1 = interactions_utils.convert_interaction_event_to_llm_response(
802+
delta_event, aggregated_parts, 'interaction_123'
803+
)
804+
assert response1.partial is True
805+
assert response1.turn_complete is False
806+
807+
# Event 2: content.stop (content complete but turn not complete)
808+
stop_event = MagicMock()
809+
stop_event.event_type = 'content.stop'
810+
811+
response2 = interactions_utils.convert_interaction_event_to_llm_response(
812+
stop_event, aggregated_parts, 'interaction_123'
813+
)
814+
assert response2.partial is False
815+
assert response2.turn_complete is False # Not final yet
816+
817+
# Event 3: interaction.status_update with completed (final response)
818+
status_event = MagicMock()
819+
status_event.event_type = 'interaction.status_update'
820+
status_event.status = 'completed'
821+
822+
response3 = interactions_utils.convert_interaction_event_to_llm_response(
823+
status_event, aggregated_parts, 'interaction_123'
824+
)
825+
assert response3.partial is False
826+
assert response3.turn_complete is True # This is the final response
827+
828+
# Verify: Only response3 should have turn_complete=True
829+
# This proves no duplication - there's only ONE final response
830+
final_responses = [
831+
r for r in [response1, response2, response3] if r.turn_complete
832+
]
833+
assert len(final_responses) == 1
834+
assert final_responses[0] == response3

0 commit comments

Comments
 (0)