fix: Fixes thought handling in contents.py and refactors its unit tests

Before this change, other agent's reply with thought will still be inserted in the outgoing LlmRequest due to the wrong `else` statement for calling all other type of part.

This commit also refactors test_contents.py to be behavior-oriented tests, instead of implementation-oriented, and add more test cases to cover expected scenarios.

The tests are divided into the following files with different focus:

- test_contents.py: covers the basic logic of event filter;
- test_contents_branch.py: covers the behavior related to branch, which takes effect when ParallelAgent is used.
- test_contents_other_agent.py: covers the retelling behavior to include other agents' reply as context for the current agent.
- test_contents_function.py: covers the function_call/function_response rearrangement logic mainly for `LongRunningFunctionTool`.

PiperOrigin-RevId: 802759821
This commit is contained in:
Wei Sun (Jack)
2025-09-03 16:42:20 -07:00
committed by Copybara-Service
parent fe8b37b0d3
commit a30851ee16
8 changed files with 1625 additions and 549 deletions
+2 -2
View File
@@ -63,8 +63,8 @@ from ..a2a.logs.log_utils import build_a2a_request_log
from ..a2a.logs.log_utils import build_a2a_response_log
from ..agents.invocation_context import InvocationContext
from ..events.event import Event
from ..flows.llm_flows.contents import _convert_foreign_event
from ..flows.llm_flows.contents import _is_other_agent_reply
from ..flows.llm_flows.contents import _present_other_agent_message
from ..flows.llm_flows.functions import find_matching_function_call
from .base_agent import BaseAgent
@@ -338,7 +338,7 @@ class RemoteA2aAgent(BaseAgent):
context_id = None
for event in reversed(ctx.session.events):
if _is_other_agent_reply(self.name, event):
event = _convert_foreign_event(event)
event = _present_other_agent_message(event)
elif event.author == self.name:
# stop on content generated by current a2a agent given it should already
# be in remote session
+46 -27
View File
@@ -16,7 +16,6 @@ from __future__ import annotations
import copy
from typing import AsyncGenerator
from typing import Generator
from typing import Optional
from google.genai import types
@@ -203,6 +202,25 @@ def _rearrange_events_for_latest_function_response(
return result_events
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 evnets that only changed session state.
Args:
event: The event to check.
Returns:
True if the event should be skipped, False otherwise.
"""
return (
not event.content
or not event.content.role
or not event.content.parts
or event.content.parts[0].text == ''
)
def _get_contents(
current_branch: Optional[str], events: list[Event], agent_name: str = ''
) -> list[types.Content]:
@@ -222,16 +240,7 @@ def _get_contents(
# Parse the events, leaving the contents and the function calls and
# responses from the current agent.
for event in events:
if (
not event.content
or not event.content.role
or not event.content.parts
or event.content.parts[0].text == ''
):
# Skip events without content, or generated neither by user nor by model
# or has empty text.
# E.g. events purely for mutating session states.
if _contains_empty_content(event):
continue
if not _is_event_belongs_to_branch(current_branch, event):
# Skip events not belong to current branch.
@@ -242,11 +251,12 @@ def _get_contents(
if _is_request_confirmation_event(event):
# Skip request confirmation events.
continue
filtered_events.append(
_convert_foreign_event(event)
if _is_other_agent_reply(agent_name, event)
else event
)
if _is_other_agent_reply(agent_name, event):
if converted_event := _present_other_agent_message(event):
filtered_events.append(converted_event)
else:
filtered_events.append(event)
# Rearrange events for proper function call/response pairing
result_events = _rearrange_events_for_latest_function_response(
@@ -305,19 +315,18 @@ def _is_other_agent_reply(current_agent_name: str, event: Event) -> bool:
)
def _convert_foreign_event(event: Event) -> Event:
"""Converts an event authored by another agent as a user-content event.
def _present_other_agent_message(event: Event) -> Optional[Event]:
"""Presents another agent's message as user context for the current agent.
This is to provide another agent's output as context to the current agent, so
that current agent can continue to respond, such as summarizing previous
agent's reply, etc.
Reformats the event with role='user' and adds '[agent_name] said:' prefix
to provide context without confusion about authorship.
Args:
event: The event to convert.
event: The event from another agent to present as context.
Returns:
The converted event.
Event reformatted as user-role context with agent attribution, or None
if no meaningful content remains after filtering.
"""
if not event.content or not event.content.parts:
return event
@@ -326,8 +335,10 @@ def _convert_foreign_event(event: Event) -> Event:
content.role = 'user'
content.parts = [types.Part(text='For context:')]
for part in event.content.parts:
# Exclude thoughts from the context.
if part.text and not part.thought:
if part.thought:
# Exclude thoughts from the context.
continue
elif part.text:
content.parts.append(
types.Part(text=f'[{event.author}] said: {part.text}')
)
@@ -354,6 +365,10 @@ def _convert_foreign_event(event: Event) -> Event:
else:
content.parts.append(part)
# If no meaningful parts were added (only "For context:" remains), return None
if len(content.parts) == 1:
return None
return Event(
timestamp=event.timestamp,
author='user',
@@ -429,7 +444,11 @@ def _merge_function_response_events(
def _is_event_belongs_to_branch(
invocation_branch: Optional[str], event: Event
) -> bool:
"""Event belongs to a branch, when event.branch is prefix of the invocation branch."""
"""Check if an event belongs to the current branch.
This is for event context segration between agents. E.g. agent A shouldn't
see output of agent B.
"""
if not invocation_branch or not event.branch:
return True
return invocation_branch.startswith(event.branch)
+9 -1
View File
@@ -65,7 +65,15 @@ def populate_client_function_call_id(model_response_event: Event) -> None:
function_call.id = generate_client_function_call_id()
def remove_client_function_call_id(content: types.Content) -> None:
def remove_client_function_call_id(content: Optional[types.Content]) -> None:
"""Removes ADK-generated function call IDs from content before sending to LLM.
Strips client-side function call/response IDs that start with 'adk-' prefix
to avoid sending internal tracking IDs to the model.
Args:
content: Content containing function calls/responses to clean.
"""
if content and content.parts:
for part in content.parts:
if (
@@ -515,7 +515,7 @@ class TestRemoteA2aAgentMessageHandling:
self.mock_session.events = [mock_event]
with patch(
"google.adk.agents.remote_a2a_agent._convert_foreign_event"
"google.adk.agents.remote_a2a_agent._present_other_agent_message"
) as mock_convert:
mock_convert.return_value = mock_event
@@ -937,7 +937,7 @@ class TestRemoteA2aAgentIntegration:
# Mock dependencies
with patch(
"google.adk.agents.remote_a2a_agent._convert_foreign_event"
"google.adk.agents.remote_a2a_agent._present_other_agent_message"
) as mock_convert:
mock_convert.return_value = mock_event
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,288 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for branch filtering in contents module.
Branch format: agent_1.agent_2.agent_3 (parent.child.grandchild)
Child agents can see parent agents' events, but not sibling agents' events.
"""
from google.adk.agents.llm_agent import Agent
from google.adk.events.event import Event
from google.adk.flows.llm_flows.contents import request_processor
from google.adk.models.llm_request import LlmRequest
from google.genai import types
import pytest
from ... import testing_utils
@pytest.mark.asyncio
async def test_branch_filtering_child_sees_parent():
"""Test that child agents can see parent agents' events."""
agent = Agent(model="gemini-2.5-flash", name="child_agent")
llm_request = LlmRequest(model="gemini-2.5-flash")
invocation_context = await testing_utils.create_invocation_context(
agent=agent
)
# Set current branch as child of "parent_agent"
invocation_context.branch = "parent_agent.child_agent"
# Add events from parent and child levels
events = [
Event(
invocation_id="inv1",
author="user",
content=types.UserContent("User message"),
),
Event(
invocation_id="inv2",
author="parent_agent",
content=types.ModelContent("Parent agent response"),
branch="parent_agent", # Parent branch - should be included
),
Event(
invocation_id="inv3",
author="child_agent",
content=types.ModelContent("Child agent response"),
branch="parent_agent.child_agent", # Current branch - should be included
),
]
invocation_context.session.events = events
# Process the request
async for _ in request_processor.run_async(invocation_context, llm_request):
pass
# Verify child can see user message and parent events, but not sibling events
assert len(llm_request.contents) == 3
assert llm_request.contents[0] == types.UserContent("User message")
assert llm_request.contents[1].role == "user"
assert llm_request.contents[1].parts == [
types.Part(text="For context:"),
types.Part(text="[parent_agent] said: Parent agent response"),
]
assert llm_request.contents[2] == types.ModelContent("Child agent response")
@pytest.mark.asyncio
async def test_branch_filtering_excludes_sibling_agents():
"""Test that sibling agents cannot see each other's events."""
agent = Agent(model="gemini-2.5-flash", name="child_agent1")
llm_request = LlmRequest(model="gemini-2.5-flash")
invocation_context = await testing_utils.create_invocation_context(
agent=agent
)
# Set current branch as first child
invocation_context.branch = "parent_agent.child_agent1"
# Add events from parent, current child, and sibling child
events = [
Event(
invocation_id="inv1",
author="user",
content=types.UserContent("User message"),
),
Event(
invocation_id="inv2",
author="parent_agent",
content=types.ModelContent("Parent response"),
branch="parent_agent", # Parent - should be included
),
Event(
invocation_id="inv3",
author="child_agent1",
content=types.ModelContent("Child1 response"),
branch="parent_agent.child_agent1", # Current - should be included
),
Event(
invocation_id="inv4",
author="child_agent2",
content=types.ModelContent("Sibling response"),
branch="parent_agent.child_agent2", # Sibling - should be excluded
),
]
invocation_context.session.events = events
# Process the request
async for _ in request_processor.run_async(invocation_context, llm_request):
pass
# Verify sibling events are excluded, but parent and current agent events included
assert len(llm_request.contents) == 3
assert llm_request.contents[0] == types.UserContent("User message")
assert llm_request.contents[1].role == "user"
assert llm_request.contents[1].parts == [
types.Part(text="For context:"),
types.Part(text="[parent_agent] said: Parent response"),
]
assert llm_request.contents[2] == types.ModelContent("Child1 response")
@pytest.mark.asyncio
async def test_branch_filtering_no_branch_allows_all():
"""Test that events are included when no branches are set."""
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
)
# No current branch set (None)
invocation_context.branch = None
# Add events with and without branches
events = [
Event(
invocation_id="inv1",
author="user",
content=types.UserContent("No branch message"),
branch=None,
),
Event(
invocation_id="inv2",
author="agent1",
content=types.ModelContent("Agent with branch"),
branch="agent1",
),
Event(
invocation_id="inv3",
author="user",
content=types.UserContent("Another no branch"),
branch=None,
),
]
invocation_context.session.events = events
# Process the request
async for _ in request_processor.run_async(invocation_context, llm_request):
pass
# Verify all events are included when no current branch
assert len(llm_request.contents) == 3
assert llm_request.contents[0] == types.UserContent("No branch message")
assert llm_request.contents[1].role == "user"
assert llm_request.contents[1].parts == [
types.Part(text="For context:"),
types.Part(text="[agent1] said: Agent with branch"),
]
assert llm_request.contents[2] == types.UserContent("Another no branch")
@pytest.mark.asyncio
async def test_branch_filtering_grandchild_sees_grandparent():
"""Test that deeply nested child agents can see all ancestor events."""
agent = Agent(model="gemini-2.5-flash", name="grandchild_agent")
llm_request = LlmRequest(model="gemini-2.5-flash")
invocation_context = await testing_utils.create_invocation_context(
agent=agent
)
# Set deeply nested branch: grandparent.parent.grandchild
invocation_context.branch = "grandparent_agent.parent_agent.grandchild_agent"
# Add events from all levels of hierarchy
events = [
Event(
invocation_id="inv1",
author="grandparent_agent",
content=types.ModelContent("Grandparent response"),
branch="grandparent_agent",
),
Event(
invocation_id="inv2",
author="parent_agent",
content=types.ModelContent("Parent response"),
branch="grandparent_agent.parent_agent",
),
Event(
invocation_id="inv3",
author="grandchild_agent",
content=types.ModelContent("Grandchild response"),
branch="grandparent_agent.parent_agent.grandchild_agent",
),
Event(
invocation_id="inv4",
author="sibling_agent",
content=types.ModelContent("Sibling response"),
branch="grandparent_agent.parent_agent.sibling_agent",
),
]
invocation_context.session.events = events
# Process the request
async for _ in request_processor.run_async(invocation_context, llm_request):
pass
# Verify only ancestors and current level are included
assert len(llm_request.contents) == 3
assert llm_request.contents[0].role == "user"
assert llm_request.contents[0].parts == [
types.Part(text="For context:"),
types.Part(text="[grandparent_agent] said: Grandparent response"),
]
assert llm_request.contents[1].role == "user"
assert llm_request.contents[1].parts == [
types.Part(text="For context:"),
types.Part(text="[parent_agent] said: Parent response"),
]
assert llm_request.contents[2] == types.ModelContent("Grandchild response")
@pytest.mark.asyncio
async def test_branch_filtering_parent_cannot_see_child():
"""Test that parent agents cannot see child agents' events."""
agent = Agent(model="gemini-2.5-flash", name="parent_agent")
llm_request = LlmRequest(model="gemini-2.5-flash")
invocation_context = await testing_utils.create_invocation_context(
agent=agent
)
# Set current branch as parent
invocation_context.branch = "parent_agent"
# Add events from parent and its children
events = [
Event(
invocation_id="inv1",
author="user",
content=types.UserContent("User message"),
),
Event(
invocation_id="inv2",
author="parent_agent",
content=types.ModelContent("Parent response"),
branch="parent_agent",
),
Event(
invocation_id="inv3",
author="child_agent",
content=types.ModelContent("Child response"),
branch="parent_agent.child_agent",
),
Event(
invocation_id="inv4",
author="grandchild_agent",
content=types.ModelContent("Grandchild response"),
branch="parent_agent.child_agent.grandchild_agent",
),
]
invocation_context.session.events = events
# Process the request
async for _ in request_processor.run_async(invocation_context, llm_request):
pass
# Verify parent cannot see child or grandchild events
assert llm_request.contents == [
types.UserContent("User message"),
types.ModelContent("Parent response"),
]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,388 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Behavioral tests for other agent message processing in contents module."""
from google.adk.agents.llm_agent import Agent
from google.adk.events.event import Event
from google.adk.flows.llm_flows.contents import request_processor
from google.adk.models.llm_request import LlmRequest
from google.genai import types
import pytest
from ... import testing_utils
@pytest.mark.asyncio
async def test_other_agent_message_appears_as_user_context():
"""Test that messages from other agents appear as user context."""
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 event from another agent
other_agent_event = Event(
invocation_id="test_inv",
author="other_agent",
content=types.ModelContent("Hello from other agent"),
)
invocation_context.session.events = [other_agent_event]
# Process the request
async for _ in request_processor.run_async(invocation_context, llm_request):
pass
# Verify the other agent's message is presented as user context
assert llm_request.contents[0].role == "user"
assert llm_request.contents[0].parts == [
types.Part(text="For context:"),
types.Part(text="[other_agent] said: Hello from other agent"),
]
@pytest.mark.asyncio
async def test_other_agent_thoughts_are_excluded():
"""Test that thoughts from other agents are excluded from context."""
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 event from other agent with both regular text and thoughts
other_agent_event = Event(
invocation_id="test_inv",
author="other_agent",
content=types.ModelContent([
types.Part(text="Public message", thought=False),
types.Part(text="Private thought", thought=True),
types.Part(text="Another public message"),
]),
)
invocation_context.session.events = [other_agent_event]
# Process the request
async for _ in request_processor.run_async(invocation_context, llm_request):
pass
# Verify only non-thought parts are included (thoughts excluded)
assert llm_request.contents[0].role == "user"
assert llm_request.contents[0].parts == [
types.Part(text="For context:"),
types.Part(text="[other_agent] said: Public message"),
types.Part(text="[other_agent] said: Another public message"),
]
@pytest.mark.asyncio
async def test_other_agent_function_calls():
"""Test that function calls from other agents are preserved in context."""
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 event from other agent with function call
function_call = types.FunctionCall(
id="func_123", name="search_tool", args={"query": "test query"}
)
other_agent_event = Event(
invocation_id="test_inv",
author="other_agent",
content=types.ModelContent([types.Part(function_call=function_call)]),
)
invocation_context.session.events = [other_agent_event]
# Process the request
async for _ in request_processor.run_async(invocation_context, llm_request):
pass
# Verify function call is presented as context
assert llm_request.contents[0].role == "user"
assert llm_request.contents[0].parts == [
types.Part(text="For context:"),
types.Part(
text="""\
[other_agent] called tool `search_tool` with parameters: {'query': 'test query'}"""
),
]
@pytest.mark.asyncio
async def test_other_agent_function_responses():
"""Test that function responses from other agents are properly formatted."""
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 event from other agent with function response
function_response = types.FunctionResponse(
id="func_123",
name="search_tool",
response={"results": ["item1", "item2"]},
)
other_agent_event = Event(
invocation_id="test_inv",
author="other_agent",
content=types.Content(
role="user", parts=[types.Part(function_response=function_response)]
),
)
invocation_context.session.events = [other_agent_event]
# Process the request
async for _ in request_processor.run_async(invocation_context, llm_request):
pass
# Verify function response is presented as context
assert llm_request.contents[0].role == "user"
assert llm_request.contents[0].parts == [
types.Part(text="For context:"),
types.Part(
text=(
"[other_agent] `search_tool` tool returned result: {'results':"
" ['item1', 'item2']}"
)
),
]
@pytest.mark.asyncio
async def test_other_agent_function_call_response():
"""Test function call and response sequence from other agents."""
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 function call event from other agent
function_call = types.FunctionCall(
id="func_123", name="calc_tool", args={"query": "6x7"}
)
call_event = Event(
invocation_id="test_inv1",
author="other_agent",
content=types.ModelContent([
types.Part(text="Let me calculate this"),
types.Part(function_call=function_call),
]),
)
# Add function response event
function_response = types.FunctionResponse(
id="func_123", name="calc_tool", response={"result": 42}
)
response_event = Event(
invocation_id="test_inv2",
author="other_agent",
content=types.UserContent(
parts=[types.Part(function_response=function_response)]
),
)
invocation_context.session.events = [call_event, response_event]
# Process the request
async for _ in request_processor.run_async(invocation_context, llm_request):
pass
# Verify function call and response are properly formatted
assert len(llm_request.contents) == 2
# Function call from other agent
assert llm_request.contents[0].role == "user"
assert llm_request.contents[0].parts == [
types.Part(text="For context:"),
types.Part(text="[other_agent] said: Let me calculate this"),
types.Part(
text=(
"[other_agent] called tool `calc_tool` with parameters: {'query':"
" '6x7'}"
)
),
]
# Function response from other agent
assert llm_request.contents[1].role == "user"
assert llm_request.contents[1].parts == [
types.Part(text="For context:"),
types.Part(
text="[other_agent] `calc_tool` tool returned result: {'result': 42}"
),
]
@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_multiple_agents_in_conversation():
"""Test handling multiple agents in a conversation flow."""
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
)
# Create a multi-agent conversation
events = [
Event(
invocation_id="inv1",
author="user",
content=types.UserContent("Hello everyone"),
),
Event(
invocation_id="inv2",
author="agent1",
content=types.ModelContent("Hi from agent1"),
),
Event(
invocation_id="inv3",
author="agent2",
content=types.ModelContent("Hi from agent2"),
),
]
invocation_context.session.events = events
# Process the request
async for _ in request_processor.run_async(invocation_context, llm_request):
pass
# Verify all messages are properly processed
assert len(llm_request.contents) == 3
# User message should remain as user
assert llm_request.contents[0] == types.UserContent("Hello everyone")
# Other agents' messages should be converted to user context
assert llm_request.contents[1].role == "user"
assert llm_request.contents[1].parts == [
types.Part(text="For context:"),
types.Part(text="[agent1] said: Hi from agent1"),
]
assert llm_request.contents[2].role == "user"
assert llm_request.contents[2].parts == [
types.Part(text="For context:"),
types.Part(text="[agent2] said: Hi from agent2"),
]
@pytest.mark.asyncio
async def test_current_agent_messages_not_converted():
"""Test that the current agent's own messages are not converted."""
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 from both current agent and other agent
events = [
Event(
invocation_id="inv1",
author="current_agent",
content=types.ModelContent("My own message"),
),
Event(
invocation_id="inv2",
author="other_agent",
content=types.ModelContent("Other agent message"),
),
]
invocation_context.session.events = events
# Process the request
async for _ in request_processor.run_async(invocation_context, llm_request):
pass
# Verify current agent's message stays as model role
# and other agent's message is converted to user context
assert len(llm_request.contents) == 2
assert llm_request.contents[0] == types.ModelContent("My own message")
assert llm_request.contents[1].role == "user"
assert llm_request.contents[1].parts == [
types.Part(text="For context:"),
types.Part(text="[other_agent] said: Other agent message"),
]
@pytest.mark.asyncio
async def test_user_messages_preserved():
"""Test that user messages are preserved as-is."""
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 user message
user_event = Event(
invocation_id="inv1",
author="user",
content=types.UserContent("User message"),
)
invocation_context.session.events = [user_event]
# Process the request
async for _ in request_processor.run_async(invocation_context, llm_request):
pass
# Verify user message is preserved exactly
assert len(llm_request.contents) == 1
assert llm_request.contents[0] == types.UserContent("User message")