feat(fix): Check all content parts for emptiness in _contains_empty_content

PiperOrigin-RevId: 845954226
This commit is contained in:
Google Team Member
2025-12-17 16:02:42 -08:00
committed by Copybara-Service
parent 589f15cb27
commit f35d129b4c
2 changed files with 89 additions and 9 deletions
+15 -9
View File
@@ -219,13 +219,26 @@ def _rearrange_events_for_latest_function_response(
return result_events
def _is_part_invisible(p: types.Part) -> bool:
"""A part is considered invisble if it's a thought, or has no visible content."""
return getattr(p, 'thought', False) or not (
p.text
or p.inline_data
or p.file_data
or p.function_call
or p.function_response
)
def _contains_empty_content(event: Event) -> bool:
"""Check if an event should be skipped due to missing or empty content.
This can happen to the events that only changed session state.
When both content and transcriptions are empty, the event will be considered
as empty. The content is considered empty if none of its parts contain text,
inline data, file data, function call, or function response.
inline data, file data, function call, or function response. Parts with
only thoughts are also considered empty.
Args:
event: The event to check.
@@ -240,14 +253,7 @@ def _contains_empty_content(event: Event) -> bool:
not event.content
or not event.content.role
or not event.content.parts
or all(
not p.text
and not p.inline_data
and not p.file_data
and not p.function_call
and not p.function_response
for p in [event.content.parts[0]]
)
or all(_is_part_invisible(p) for p in event.content.parts)
) and (not event.output_transcription and not event.input_transcription)
@@ -16,6 +16,7 @@ from google.adk.agents.llm_agent import Agent
from google.adk.events.event import Event
from google.adk.events.event_actions import EventActions
from google.adk.flows.llm_flows import contents
from google.adk.flows.llm_flows.contents import request_processor
from google.adk.flows.llm_flows.functions import REQUEST_CONFIRMATION_FUNCTION_CALL_NAME
from google.adk.flows.llm_flows.functions import REQUEST_EUC_FUNCTION_CALL_NAME
from google.adk.models.llm_request import LlmRequest
@@ -433,6 +434,58 @@ async def test_rewind_events_are_filtered_out():
]
@pytest.mark.asyncio
async def test_other_agent_empty_content():
"""Test that other agent messages with only thoughts or empty content are filtered out."""
agent = Agent(model="gemini-2.5-flash", name="current_agent")
llm_request = LlmRequest(model="gemini-2.5-flash")
invocation_context = await testing_utils.create_invocation_context(
agent=agent
)
# Add events: user message, other agents with empty content, user message
events = [
Event(
invocation_id="inv1",
author="user",
content=types.UserContent("Hello"),
),
# Other agent with only thoughts
Event(
invocation_id="inv2",
author="other_agent1",
content=types.ModelContent([
types.Part(text="This is a private thought", thought=True),
types.Part(text="Another private thought", thought=True),
]),
),
# Other agent with empty text and thoughts
Event(
invocation_id="inv3",
author="other_agent2",
content=types.ModelContent([
types.Part(text="", thought=False),
types.Part(text="Secret thought", thought=True),
]),
),
Event(
invocation_id="inv4",
author="user",
content=types.UserContent("World"),
),
]
invocation_context.session.events = events
# Process the request
async for _ in request_processor.run_async(invocation_context, llm_request):
pass
# Verify empty content events are completely filtered out
assert llm_request.contents == [
types.UserContent("Hello"),
types.UserContent("World"),
]
@pytest.mark.asyncio
async def test_events_with_empty_content_are_skipped():
"""Test that events with empty content (state-only changes) are skipped."""
@@ -471,6 +524,14 @@ async def test_events_with_empty_content_are_skipped():
author="user",
content=types.Content(parts=[types.Part(text="")], role="model"),
),
# Event with content that has multiple empty text parts
Event(
invocation_id="inv6_2",
author="user",
content=types.Content(
parts=[types.Part(text=""), types.Part(text="")], role="model"
),
),
# Event with content that has only inline data part
Event(
invocation_id="inv7",
@@ -502,6 +563,15 @@ async def test_events_with_empty_content_are_skipped():
role="user",
),
),
# Event with mixed empty and non-empty text parts
Event(
invocation_id="inv9",
author="user",
content=types.Content(
parts=[types.Part(text=""), types.Part(text="Mixed content")],
role="user",
),
),
]
invocation_context.session.events = events
@@ -534,4 +604,8 @@ async def test_events_with_empty_content_are_skipped():
],
role="user",
),
types.Content(
parts=[types.Part(text=""), types.Part(text="Mixed content")],
role="user",
),
]