-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Description
Bug Report: LLM responses associated with collapsed function_responses should also be removed
** Please make sure you read the contribution guide and file the issues in the right place. **
Describe the bug
When ADK collapses/removes old function_response messages from the conversation history (as part of its message history management), it does not remove the associated LLM-generated text responses that were created in response to those function responses. This results in duplicate or confusing messages being sent to the LLM, where both:
- An intermediate text response (generated before the function_response was collapsed)
- A final text response (generated after the function_response was processed)
are both present in the conversation history sent to the LLM. This causes the LLM to receive conflicting information and respond in strange or inconsistent ways.
To Reproduce
Please share a minimal code and data to reproduce your problem.
Steps to reproduce the behavior:
- Create an agent that makes function calls and receives function responses asynchronously. In this case, the sequence of events end up being:
- Content 1: User message
- Content 2: function_call
- Content 3: function_response -- intermediate while the task is running background
- Content 4: LLM Responds to the intermediate function_response.
- Content 5: function_response -- final response posted.
- Content 6: LLM responds to final function response.
-
After the function call completes and final function_response is received, observe the conversation history in the last LLM call:
- You'll see a pattern like:
Content 5: role=user, function_response (for tool X) Content 4: role=model, text="Calling X now..." (intermediate response) Content 6: role=model, text="Done! I called X and got result Y" (final response)
- You'll see a pattern like:
-
When ADK collapses the function_response (Content 3), it should also remove Content 4 (the intermediate response), but currently it only removes Content 3, leaving both Content 4 and Content 6 in the history.
-
This results in the LLM receiving:
Content 4: role=model, text="Calling X now..." Content 6: role=model, text="Done! I called X and got result Y"Which is confusing because Content 4 is now outdated/incorrect. The LLM gets confused and responds for both messages -- something like "Call X now. I coudln't get through person Y"
Expected behavior
When ADK collapses/removes old function_response messages from the conversation history, it should also remove any associated LLM-generated text responses that were created in response to those function responses.
Specifically:
- If a
function_responsemessage is removed, any immediately followingmodelrole messages with text content should also be removed, as they were generated based on the now-removed function_response context.
This ensures the conversation history sent to the LLM only contains the most recent, accurate responses.
Actual behavior
ADK removes the function_response message but leaves the associated LLM text responses, resulting in:
- Duplicate or conflicting messages in the conversation history
- Outdated intermediate responses being sent to the LLM
- Confusion for the LLM, leading to strange or inconsistent responses
Workaround
A workaround is to use before_model_callback to detect and remove consecutive model/assistant messages with text blocks:
def remove_duplicate_text_blocks(
callback_context: CallbackContext,
llm_request: LlmRequest
) -> Optional[LlmRequest]:
"""Remove consecutive model/assistant text blocks (sign of collapsed function_responses)."""
if not llm_request.contents or len(llm_request.contents) < 2:
return None
def has_text_part(content):
if not content.parts:
return False
for part in content.parts:
if hasattr(part, 'text') and part.text is not None:
return True
return False
modified_contents = []
i = 0
while i < len(llm_request.contents):
content = llm_request.contents[i]
# Check if this is a model/assistant message with text
if content.role in ("assistant", "model") and has_text_part(content):
# Check if next content is also a model/assistant message with text
if (i + 1 < len(llm_request.contents) and
llm_request.contents[i + 1].role in ("assistant", "model") and
has_text_part(llm_request.contents[i + 1])):
# Skip this content (first of the two consecutive text blocks)
i += 1
continue
modified_contents.append(content)
i += 1
# Modify the request in place
llm_request.contents = modified_contents
return NoneHowever, this is a workaround that detects the symptom (consecutive text blocks) rather than fixing the root cause (removing associated responses when function_responses are collapsed).
Desktop (please complete the following information):
- ADK version: 1.17
Model Information:
- Are you using LiteLLM: Yes
- Which model is being used:
anthropic/claude-sonnet-4-5
Additional context
This bug is particularly problematic because:
- It creates confusion for the LLM, which receives conflicting information about the state of function calls
- It leads to inconsistent or incorrect responses from the LLM
- It requires workarounds that add complexity and may not catch all edge cases
- The fix should be in ADK's message history management logic, not in user code
The recommended fix is to update ADK's function_response collapse logic to:
- Identify which LLM text responses are associated with a function_response being collapsed
- Remove those associated text responses along with the function_response
- This ensures the conversation history only contains the most recent, accurate state