From 663b28b16aaf6589b1c18ebabc745246a8d52dc2 Mon Sep 17 00:00:00 2001 From: Luyang Wang Date: Sat, 27 Jun 2026 11:54:00 -0400 Subject: [PATCH] fix(skills): prevent empty responses after load_skill The skill system instruction does not tell the model what to do after a load_skill call returns. Some models (notably Gemini) treat the load_skill tool call as the entire turn and stop with no visible output, producing empty responses. This is most acute for tool-heavy skills, whose next correct action after load_skill is to call more tools rather than reply. Add rule 7 to _build_skill_system_instruction: load_skill only retrieves instructions and does NOT complete the turn; the model must continue in the same turn (calling whatever tools the skill requires) and never end with an empty response right after loading a skill. The rule uses the {prefix} substitution like the other rules, so both the default and prefixed system instructions carry it. Verified in a production environment: appending this guidance dropped the empty-response rate for Gemini after skill loading substantially. Adds tests asserting rule 7 is present in both the default and prefixed system instruction. --- src/google/adk/tools/skill_toolset.py | 5 +++++ tests/unittests/tools/test_skill_toolset.py | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/google/adk/tools/skill_toolset.py b/src/google/adk/tools/skill_toolset.py index 4b4fd069d72..c09b2dcdda9 100644 --- a/src/google/adk/tools/skill_toolset.py +++ b/src/google/adk/tools/skill_toolset.py @@ -97,6 +97,11 @@ def _build_skill_system_instruction(prefix: str | None = None) -> str: f"6. If `{p}run_skill_script` returns an error (for example " f"`SCRIPT_NOT_FOUND`), do not retry the same script or guess a " "different script path. Report the error to the user and stop.\n" + f"7. Loading a skill only retrieves its instructions; it does NOT " + f"complete your turn. After a `{p}load_skill` call returns, continue " + "in the SAME turn: call whatever tools the skill's steps require " + "(search, data retrieval, render), then write your reply. Never end " + "your turn with an empty response right after loading a skill.\n" ) diff --git a/tests/unittests/tools/test_skill_toolset.py b/tests/unittests/tools/test_skill_toolset.py index e9386bb063a..450dbe3f307 100644 --- a/tests/unittests/tools/test_skill_toolset.py +++ b/tests/unittests/tools/test_skill_toolset.py @@ -1758,6 +1758,24 @@ def test_system_instruction_references_run_skill_script(): ) +def test_system_instruction_marks_load_skill_as_non_terminal(): + """Rule 7 must tell the model load_skill does not complete the turn. + + Without it, some models (notably Gemini) treat the load_skill tool call as + the entire turn and stop with no visible output, producing empty responses. + """ + instruction = skill_toolset.DEFAULT_SKILL_SYSTEM_INSTRUCTION + assert "does NOT complete your turn" in instruction + assert "empty response" in instruction + + +def test_prefixed_system_instruction_includes_continue_after_load_rule(): + """The prefixed builder variant must also carry rule 7 (with the prefix).""" + instruction = skill_toolset._build_skill_system_instruction(prefix="my") + assert "does NOT complete your turn" in instruction + assert "my_load_skill" in instruction + + # ── Finding 2: empty files are mounted (not silently dropped) ──