You've already forked adk-python
mirror of
https://github.com/encounter/adk-python.git
synced 2026-03-30 10:57:20 -07:00
feat(fix): Check all content parts for emptiness in _contains_empty_content
PiperOrigin-RevId: 845954226
This commit is contained in:
committed by
Copybara-Service
parent
589f15cb27
commit
f35d129b4c
@@ -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",
|
||||
),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user