fix(workflow): Keep regular-tool FRs on mixed task turns - #6586
Conversation
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
|
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 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. |
Copybara already merged google#6586 onto main. Keep main's wrapper and tests so the open PR can merge cleanly without reintroducing a divergent patch.
|
Resolved the merge conflict in PR should be conflict-free now; safe to close if Copybara is considered the merge of record. |
|
Hi @DeanChensj — Copybara already imported this in Could you re-run Actions → Copybara PR Handler → Run workflow with:
(or close it and add the |
|
Thanks, let me close this PR manually and look into why the PR does not auto-close. |
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.pybroke out ofrun_asyncimmediately after handling the task FC. That closed the generator before the pending regular-tool function-response event was read.Effects:
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:
New / updated tests:
tests/unittests/workflow/test_llm_agent_wrapper_mixed_turn.py_event_has_eager_tool_callstrue for regular+task, false for task-only_drain_pending_tool_response_eventsyields FR then stopstests/unittests/workflow/test_task_api_e2e.pytest_chat_root_mixed_regular_tool_and_task_keeps_regular_frtest_chat_root_mixed_turn_with_two_regular_tools_and_taskpytest results (local):
Manual / repro verification:
mainwith the issue's stub-LLM mixed-turn script:set_todo_listFR was missing and the tool did not execute (FAIL).FR:set_todo_list, and the tool executes.finish_taskconfirms:Pre-commit:
Checklist
Additional context
Files changed:
src/google/adk/workflow/_llm_agent_wrapper.py— drain eager-tool FRs before breaking on task delegationtests/unittests/workflow/test_task_api_e2e.py— mixed-turn e2e coveragetests/unittests/workflow/test_llm_agent_wrapper_mixed_turn.py— helper unit testsExact 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