Skip to content

fix: preserve pending function calls during event compaction#4743

Open
atian8179 wants to merge 1 commit intogoogle:mainfrom
atian8179:fix/compaction-preserve-pending-function-calls
Open

fix: preserve pending function calls during event compaction#4743
atian8179 wants to merge 1 commit intogoogle:mainfrom
atian8179:fix/compaction-preserve-pending-function-calls

Conversation

@atian8179
Copy link

Problem

When EventsCompactionConfig is enabled, event compaction can summarize away function call events that have not yet received a response (e.g., adk_request_confirmation pending user confirmation). When the user later responds with a FunctionResponse, the runner cannot find the matching FunctionCall event and raises ValueError.

Root Cause

_safe_token_compaction_split_index() only considers matching call-response pairs to avoid orphaning retained responses. It does not account for pending function calls — calls that exist in the event history with no corresponding response yet.

Solution

  1. Add _pending_function_call_ids() — scans all events to find function call IDs with no matching response
  2. Update _safe_token_compaction_split_index() — verify the compaction prefix does not contain any pending function calls before accepting a split point

Testing

All 28 existing compaction tests pass unchanged.

Fixes #4740

Event compaction can remove function call events that have not yet
received a response (e.g., adk_request_confirmation). When the user
later responds, the runner cannot find the matching function call
and raises ValueError.

Add _pending_function_call_ids() to identify unanswered function
calls, and update _safe_token_compaction_split_index() to ensure
the compaction split point never places pending function calls in
the compacted prefix.

Fixes google#4740
@google-cla
Copy link

google-cla bot commented Mar 6, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves an issue where event compaction could prematurely remove function calls that were still awaiting a response, leading to runtime errors. The changes ensure that all pending function calls are correctly identified and preserved during the compaction process, thereby maintaining the integrity of event history and preventing ValueError exceptions when subsequent responses arrive.

Highlights

  • New Function Added: Introduced _pending_function_call_ids to identify function call IDs that have not yet received a matching response within a list of events.
  • Compaction Logic Enhancement: Modified _safe_token_compaction_split_index to prevent the compaction of events that include pending (unanswered) function calls, ensuring they are retained until a response is received.
  • Documentation Update: Updated the docstring for _safe_token_compaction_split_index to reflect its new behavior of preserving pending function calls.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/google/adk/apps/compaction.py
    • Added _pending_function_call_ids to identify function calls without corresponding responses.
    • Updated _safe_token_compaction_split_index to incorporate checks for pending function calls, preventing their compaction.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@adk-bot adk-bot added the core [Component] This issue is related to the core interface and implementation label Mar 6, 2026
@adk-bot
Copy link
Collaborator

adk-bot commented Mar 6, 2026

Response from ADK Triaging Agent

Hello @atian8179, thank you for creating this PR!

Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). You can visit https://cla.developers.google.com/ to see your current agreements or to sign a new one.

This information will help reviewers to review your PR more efficiently. Thanks!

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses an issue where pending function calls could be compacted away, leading to errors, by introducing a new function to identify pending calls and updating the compaction logic to preserve them. However, the current implementation introduces a quadratic complexity ($O(N^2)$) in the _safe_token_compaction_split_index function, which could be exploited to cause a Denial of Service (DoS) if a session contains a large number of events. An optimization to reduce the complexity to $O(N)$ has been suggested to mitigate this performance concern for long conversation histories.

Comment on lines +332 to +338
for j in range(i):
if _event_function_call_ids(candidate_events[j]) & pending_call_ids:
pending_in_prefix = True
break
if not pending_in_prefix:
best_split = i
break
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The _safe_token_compaction_split_index function introduces a nested loop, leading to $O(N^2)$ time complexity. This can be exploited by an attacker to cause a Denial of Service (DoS) by generating a large number of events, consuming excessive CPU. To remediate this, optimize the check for pending function calls in the prefix to achieve $O(N)$ complexity, for example, by pre-calculating the index of the first event that contains a pending function call.

   # Identify pending function calls across ALL candidate events
  pending_call_ids = _pending_function_call_ids(candidate_events)

  # Pre-calculate the index of the first event containing a pending call
  first_pending_call_idx = -1
  for idx, event in enumerate(candidate_events):
    if _event_function_call_ids(event) & pending_call_ids:
      first_pending_call_idx = idx
      break

  unmatched_response_ids: set[str] = set()
  best_split = 0

  for i in range(len(candidate_events) - 1, -1, -1):
    event = candidate_events[i]
    unmatched_response_ids.update(_event_function_response_ids(event))
    call_ids = _event_function_call_ids(event)
    unmatched_response_ids -= call_ids

    # Also check: would compacting events [0..i) remove a pending call?
    if not unmatched_response_ids and i <= initial_split:
      # Verify no pending function calls exist in the compacted prefix
      if first_pending_call_idx == -1 or first_pending_call_idx >= i:
        best_split = i
        break

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Event compaction breaks adk_request_confirmation by removing the pending function call event

2 participants