Skip to content

Commit 6bdac02

Browse files
kevinkassimocopybara-github
authored andcommitted
fix: do not consider events with state delta and no content as final response
Previously this will return true for events yielded from before_agent_callback when there are state changes. Note with this change, it will also return false for state delta only callbacks even after main response, but this is fine as long as the actual final response event has it to be true. Closes #2992 PiperOrigin-RevId: 825279439
1 parent 74a3500 commit 6bdac02

File tree

2 files changed

+42
-0
lines changed

2 files changed

+42
-0
lines changed

src/google/adk/events/event.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ def is_final_response(self) -> bool:
8989
"""
9090
if self.actions.skip_summarization or self.long_running_tool_ids:
9191
return True
92+
93+
# If we see state delta but no content, then this must be an event fired
94+
# from callbacks that does not early exit / override response,
95+
# thus the event itself cannot be final response.
96+
if self.actions.state_delta and self.content is None:
97+
return False
98+
9299
return (
93100
not self.get_function_calls()
94101
and not self.get_function_responses()

tests/unittests/agents/test_base_agent.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,41 @@ async def test_run_async_with_async_before_agent_callback_bypass_agent(
339339
assert events[0].content.parts[0].text == 'agent run is bypassed.'
340340

341341

342+
@pytest.mark.asyncio
343+
async def test_run_async_with_async_before_agent_callback_state_delta(
344+
request: pytest.FixtureRequest,
345+
):
346+
def before_agent_callback_state_delta(callback_context: CallbackContext):
347+
callback_context.state['some_field'] = 'some_value'
348+
return None
349+
350+
agent = _TestingAgent(
351+
name=f'{request.function.__name__}_test_agent',
352+
before_agent_callback=before_agent_callback_state_delta,
353+
)
354+
parent_ctx = await _create_parent_invocation_context(
355+
request.function.__name__, agent
356+
)
357+
358+
events = [e async for e in agent.run_async(parent_ctx)]
359+
360+
# The before_agent_callback yields an event with state change.
361+
# Then the agent's _run_async_impl yields another event.
362+
assert len(events) == 2
363+
364+
callback_event = events[0]
365+
# The callback event should have state delta and no content.
366+
assert callback_event.actions.state_delta is not None
367+
assert callback_event.actions.state_delta['some_field'] == 'some_value'
368+
assert callback_event.content is None
369+
# This should not be a final response.
370+
assert not callback_event.is_final_response()
371+
372+
agent_run_event = events[1]
373+
# The event from the agent run should be a final response.
374+
assert agent_run_event.is_final_response()
375+
376+
342377
class CallbackType(Enum):
343378
SYNC = 1
344379
ASYNC = 2

0 commit comments

Comments
 (0)