Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/google/adk/events/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,15 @@ def is_final_response(self) -> bool:
and not self.get_function_calls()
):
return True
# A state-only event persists framework state; it is not an agent response.
# Keep empty Content events final because a model may legitimately complete
# a turn without producing any parts.
if (
self.content is None
and self.output is None
and self.actions.state_delta
):
return False
return (
not self.get_function_calls()
and not self.get_function_responses()
Expand Down
48 changes: 48 additions & 0 deletions tests/unittests/agents/test_base_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,30 @@ async def test_run_async_before_agent_callback_bypass_agent(
assert events[0].content.parts[0].text == 'agent run is bypassed.'


@pytest.mark.asyncio
async def test_run_async_before_agent_callback_state_delta_is_not_final(
request: pytest.FixtureRequest,
):
def update_state(callback_context: CallbackContext) -> None:
callback_context.state['callback_state'] = 'before'

agent = _TestingAgent(
name=f'{request.function.__name__}_test_agent',
before_agent_callback=update_state,
)
parent_ctx = await _create_parent_invocation_context(
request.function.__name__, agent
)

events = [event async for event in agent.run_async(parent_ctx)]

assert len(events) == 2
assert events[0].content is None
assert events[0].actions.state_delta == {'callback_state': 'before'}
assert events[0].is_final_response() is False
assert events[1].is_final_response() is True


@pytest.mark.asyncio
async def test_run_async_with_async_before_agent_callback_bypass_agent(
request: pytest.FixtureRequest,
Expand Down Expand Up @@ -694,6 +718,30 @@ async def test_run_async_after_agent_callback_append_reply(
)


@pytest.mark.asyncio
async def test_run_async_after_agent_callback_state_delta_is_not_final(
request: pytest.FixtureRequest,
):
def update_state(callback_context: CallbackContext) -> None:
callback_context.state['callback_state'] = 'after'

agent = _TestingAgent(
name=f'{request.function.__name__}_test_agent',
after_agent_callback=update_state,
)
parent_ctx = await _create_parent_invocation_context(
request.function.__name__, agent
)

events = [event async for event in agent.run_async(parent_ctx)]

assert len(events) == 2
assert events[0].is_final_response() is True
assert events[1].content is None
assert events[1].actions.state_delta == {'callback_state': 'after'}
assert events[1].is_final_response() is False


@pytest.mark.asyncio
async def test_run_async_with_async_after_agent_callback_append_reply(
request: pytest.FixtureRequest,
Expand Down
10 changes: 10 additions & 0 deletions tests/unittests/events/test_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ def test_is_final_response_empty_event_is_final():
assert event.is_final_response() is True


def test_is_final_response_state_delta_only_event_is_not_final():
event = _event(actions=EventActions(state_delta={'key': 'value'}))
assert event.is_final_response() is False


def test_is_final_response_empty_content_with_state_delta_is_final():
event = _event(parts=[], actions=EventActions(state_delta={'key': 'value'}))
assert event.is_final_response() is True


def test_is_final_response_with_function_call_is_not_final():
event = _event(parts=[_text_part(), _function_call_part()])
assert event.is_final_response() is False
Expand Down