Skip to content

fix(workflow): Keep regular-tool FRs on mixed task turns - #6586

Open
a2105z wants to merge 2 commits into
google:mainfrom
a2105z:fix/mixed-turn-regular-tool-fr-drop
Open

fix(workflow): Keep regular-tool FRs on mixed task turns#6586
a2105z wants to merge 2 commits into
google:mainfrom
a2105z:fix/mixed-turn-regular-tool-fr-drop

Conversation

@a2105z

@a2105z a2105z commented Aug 4, 2026

Copy link
Copy Markdown

Link to Issue or Description of Change

1. Link to an existing issue (if applicable):

Problem:
When a chat-mode coordinator emits both a regular tool function call and a task-delegation function call in the same model turn, _llm_agent_wrapper.py broke out of run_async immediately after handling the task FC. That closed the generator before the pending regular-tool function-response event was read.

Effects:

  1. The regular tool never executed (side effects silently lost).
  2. Session history retained unbalanced FC/FR pairs, so later Gemini requests failed with:
    400 INVALID_ARGUMENT: Please ensure that the number of function response parts is equal to the number of function call parts.

Solution:
Before breaking on task FCs, if the turn also contains eager (non-deferred / non-long-running) tool calls, drain pending non-model events from the current LLM step so regular-tool FRs are yielded and persisted. Then dispatch task FCs and synthesize task FRs as before, and re-enter run_async.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

New / updated tests:

  • tests/unittests/workflow/test_llm_agent_wrapper_mixed_turn.py
    • _event_has_eager_tool_calls true for regular+task, false for task-only
    • _drain_pending_tool_response_events yields FR then stops
    • drain stops immediately on a model-role event
  • tests/unittests/workflow/test_task_api_e2e.py
    • test_chat_root_mixed_regular_tool_and_task_keeps_regular_fr
    • test_chat_root_mixed_turn_with_two_regular_tools_and_task

pytest results (local):

# Focused new + task API suite
pytest tests/unittests/workflow/test_llm_agent_wrapper_mixed_turn.py \
       tests/unittests/workflow/test_task_api_e2e.py -v
# 14 passed, 1 xfailed

# Broader regression suite
pytest tests/unittests/workflow/ \
       tests/unittests/agents/test_llm_agent_interruptions.py \
       tests/unittests/agents/test_llm_agent_single_turn_subagents.py -q
# 694 passed, 11 skipped, 12 xfailed

Manual / repro verification:

Pre-commit:

pre-commit run --files \
  src/google/adk/workflow/_llm_agent_wrapper.py \
  tests/unittests/workflow/test_task_api_e2e.py \
  tests/unittests/workflow/test_llm_agent_wrapper_mixed_turn.py
# all hooks passed

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

Additional context

Files changed:

  • src/google/adk/workflow/_llm_agent_wrapper.py — drain eager-tool FRs before breaking on task delegation
  • tests/unittests/workflow/test_task_api_e2e.py — mixed-turn e2e coverage
  • tests/unittests/workflow/test_llm_agent_wrapper_mixed_turn.py — helper unit tests

Exact commands run

pytest tests/unittests/workflow/test_llm_agent_wrapper_mixed_turn.py \
       tests/unittests/workflow/test_task_api_e2e.py -v
# 14 passed, 1 xfailed

pytest tests/unittests/workflow/ \
       tests/unittests/agents/test_llm_agent_interruptions.py \
       tests/unittests/agents/test_llm_agent_single_turn_subagents.py -q
# 694 passed, 11 skipped, 12 xfailed

pre-commit run --files \
  src/google/adk/workflow/_llm_agent_wrapper.py \
  tests/unittests/workflow/test_task_api_e2e.py \
  tests/unittests/workflow/test_llm_agent_wrapper_mixed_turn.py
# all hooks passed

When a chat coordinator emits a regular tool call and a task-delegation
call in the same model turn, the wrapper broke out of run_async before
draining the regular-tool function response. That left unbalanced FC/FR
history and caused Gemini to reject later turns.

Fixes google#6581
@adk-bot adk-bot added the core [Component] This issue is related to the core interface and implementation label Aug 4, 2026
@a2105z

a2105z commented Aug 4, 2026

Copy link
Copy Markdown
Author

Hi @DeanChensj — thanks for taking a look at this (and for all the prior work on the chat/task wrapper; I leaned on that area while debugging).

I put up a fix for #6581: on mixed turns (regular tool FC + task FC in the same model response), the wrapper was breaking out of run_async before the regular-tool FR was drained, which dropped the tool side-effect and left unbalanced FC/FR history for later Gemini turns.

Happy to adjust the approach if you'd prefer a different drain/boundary condition — just let me know what you'd like changed. Appreciate any feedback when you have a moment.

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.

chat-mode wrapper (_llm_agent_wrapper.py) silently drops regular-tool FR when model emits regular FC + task FC in same turn → session poisoning

3 participants