Skip to content

Commit 12297f2

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

File tree

3 files changed

+76
-10
lines changed

3 files changed

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

0 commit comments

Comments
 (0)