fix(flows): Drop orphaned FRs instead of poisoning sessions - #6587
Conversation
When the latest history event was a function response with no matching function call, contents assembly raised ValueError before any callback could intervene, leaving the session permanently unusable. Drop the orphaned response and continue so later turns can proceed. Fixes google#6582
|
Hi @GWeale — hoping you might have a minute when free. You’ve touched This is a fix for #6582: an orphaned trailing function response used to raise during contents assembly (before any callback), which permanently poisoned the session. The change drops those unmatched FRs, logs a warning, and continues so later turns can still run. I tried to keep the blast radius small and covered it with contents-processor + full Runner tests. Happy to adjust if you’d rather surface this differently — just say the word. Thanks so much for any feedback. |
|
Hi @DeanChensj — looping you in as well for a review when you have a moment. This is the consumption-side companion to the mixed-turn session-poisoning work: orphaned trailing function responses were raising during contents assembly and leaving the session unusable. The change drops those unmatched FRs and continues. Happy to tweak anything that looks off — thanks so much for taking a look. |
Link to Issue or Description of Change
1. Link to an existing issue (if applicable):
Problem:
If a session ever persisted a function response whose matching function call was absent from the branch-filtered history,
_rearrange_events_for_latest_function_responseraisedValueErrorwhile buildingllm_request.contents.That raise happens in request preprocessing — before
before_model_callback— so no user hook can heal it. Every later turn replays the same history and re-raises, permanently poisoning the session.Solution:
When the trailing event carries function responses with no matching function call in history, drop those orphaned response event(s), log a warning, and continue rearranging / assembling contents from the surviving history. The model simply never sees a result it never asked for, and the session stays usable.
Testing Plan
Unit Tests:
Updated / new tests in
tests/unittests/flows/llm_flows/test_contents_function.py:test_orphaned_function_response_is_dropped_not_raised— former raise expectation replaced with drop behaviortest_orphaned_fr_after_valid_history_keeps_session_usable— issue contents request-processor (_rearrange_events_for_latest_function_response) raises ValueError on an orphaned function response, permanently poisoning the session — raise happens before before_model_callback, so no user hook can intercept it #6582 history shape (valid FC/FR + answer + trailing orphan)test_multiple_trailing_orphaned_frs_are_all_dropped— consecutive orphanstest_runner_continues_after_orphaned_fr_in_session— fullRunner.run_asyncpath; model is called and orphan is absent from promptpytest results (local):
Manual / repro verification:
mainwith the issue's contents-processor repro:ValueError: No function call event found for function responses ids: {'orphan-1'}hi→ FC/FR →answer), orphan omittedPre-commit:
Checklist
Additional context
Files changed:
src/google/adk/flows/llm_flows/contents.py— drop orphaned trailing FRs instead of raisingtests/unittests/flows/llm_flows/test_contents_function.py— regression + runner coverageCompanion producer-side issues (e.g. progressive SSE leaving orphaned FRs) are separate; this change hardens the consumption path so any orphaned FR no longer kills the session.
Exact commands run