fix: preserve pending function calls during event compaction#4743
fix: preserve pending function calls during event compaction#4743atian8179 wants to merge 1 commit intogoogle:mainfrom
Conversation
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
|
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. |
Summary of ChangesHello, 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 Highlights
🧠 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
Using Gemini Code AssistThe 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
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 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
|
|
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! |
There was a problem hiding this comment.
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
| 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 |
There was a problem hiding this comment.
The _safe_token_compaction_split_index function introduces a nested loop, leading to
# 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
Problem
When
EventsCompactionConfigis enabled, event compaction can summarize away function call events that have not yet received a response (e.g.,adk_request_confirmationpending user confirmation). When the user later responds with aFunctionResponse, the runner cannot find the matchingFunctionCallevent and raisesValueError.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
_pending_function_call_ids()— scans all events to find function call IDs with no matching response_safe_token_compaction_split_index()— verify the compaction prefix does not contain any pending function calls before accepting a split pointTesting
All 28 existing compaction tests pass unchanged.
Fixes #4740