Skip to content

LLM responses associated with collapsed function_responses should also be removed #3642

@srochiramani

Description

@srochiramani

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. **

Contribution guide.

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:

  1. An intermediate text response (generated before the function_response was collapsed)
  2. 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:

  1. 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.
  1. 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)
      
  2. 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.

  3. 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_response message is removed, any immediately following model role 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 None

However, 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:

  1. It creates confusion for the LLM, which receives conflicting information about the state of function calls
  2. It leads to inconsistent or incorrect responses from the LLM
  3. It requires workarounds that add complexity and may not catch all edge cases
  4. 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:

  1. Identify which LLM text responses are associated with a function_response being collapsed
  2. Remove those associated text responses along with the function_response
  3. This ensures the conversation history only contains the most recent, accurate state

Metadata

Metadata

Assignees

Labels

core[Component] This issue is related to the core interface and implementation

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions