diff --git a/src/google/adk/tools/skill_toolset.py b/src/google/adk/tools/skill_toolset.py index a6796d8c49a..e805d8eceb7 100644 --- a/src/google/adk/tools/skill_toolset.py +++ b/src/google/adk/tools/skill_toolset.py @@ -1394,6 +1394,8 @@ def clone_with_updated_skills( skills_folder=self._skills_folder, script_timeout=self._script_timeout, additional_tools=additional_tools, + tool_name_prefix=self.tool_name_prefix, + tool_filter=self.tool_filter, ) async def process_llm_request( diff --git a/tests/unittests/tools/test_skill_toolset.py b/tests/unittests/tools/test_skill_toolset.py index e8069620084..2ef0891ff52 100644 --- a/tests/unittests/tools/test_skill_toolset.py +++ b/tests/unittests/tools/test_skill_toolset.py @@ -218,6 +218,100 @@ def test_clone_with_updated_skills(mock_skill1, mock_skill2): assert "my_tool" in new_toolset._provided_tools_by_name +@pytest.mark.asyncio +async def test_clone_preserves_tool_name_prefix_in_tool_names( + mock_skill1, mock_skill2 +): + """The clone prefixes its tool names exactly like the source toolset.""" + toolset = skill_toolset.SkillToolset( + [mock_skill1], tool_name_prefix="my_prefix" + ) + + clone = toolset.clone_with_updated_skills([mock_skill2]) + + tools = await clone.get_tools_with_prefix() + assert [t.name for t in tools] == [ + "my_prefix_list_skills", + "my_prefix_load_skill", + "my_prefix_load_skill_resource", + "my_prefix_run_skill_script", + ] + + +@pytest.mark.asyncio +async def test_clone_preserves_tool_name_prefix_in_system_instruction( + mock_skill1, mock_skill2, tool_context_instance, mock_registry +): + """The clone's system instruction names the prefixed tools.""" + toolset = skill_toolset.SkillToolset( + [mock_skill1], + registry=mock_registry, + tool_name_prefix="my_prefix", + ) + + clone = toolset.clone_with_updated_skills([mock_skill2]) + + # Remove ListSkillsTool so the skills XML instruction is emitted. + clone._tools = [ + t for t in clone._tools if not isinstance(t, skill_toolset.ListSkillsTool) + ] + + llm_req = mock.create_autospec(llm_request_model.LlmRequest, instance=True) + + await clone.process_llm_request( + tool_context=tool_context_instance, llm_request=llm_req + ) + + llm_req.append_instructions.assert_called_once() + args, _ = llm_req.append_instructions.call_args + instructions = args[0] + assert len(instructions) == 3 + assert "`my_prefix_load_skill`" in instructions[0] + assert "`my_prefix_load_skill_resource`" in instructions[0] + assert "`my_prefix_run_skill_script`" in instructions[0] + assert "my_prefix_search_skills" in instructions[2] + + +@pytest.mark.asyncio +async def test_clone_preserves_list_tool_filter(mock_skill1, mock_skill2): + """The clone exposes only the tools named in the source list filter.""" + toolset = skill_toolset.SkillToolset( + [mock_skill1], tool_filter=["list_skills", "load_skill"] + ) + + clone = toolset.clone_with_updated_skills([mock_skill2]) + + tool_names = [t.name for t in await clone.get_tools()] + assert tool_names == ["list_skills", "load_skill"] + assert "load_skill_resource" not in tool_names + assert "run_skill_script" not in tool_names + + +@pytest.mark.asyncio +async def test_clone_preserves_predicate_tool_filter(mock_skill1, mock_skill2): + """The clone applies the same predicate filter object as the source.""" + toolset = skill_toolset.SkillToolset( + [mock_skill1], tool_filter=lambda tool, ctx=None: "resource" in tool.name + ) + + clone = toolset.clone_with_updated_skills([mock_skill2]) + + assert clone.tool_filter is toolset.tool_filter + assert [t.name for t in await clone.get_tools()] == ["load_skill_resource"] + + +def test_clone_without_prefix_or_filter_keeps_them_none( + mock_skill1, mock_skill2 +): + """A source toolset with neither option clones with neither option.""" + toolset = skill_toolset.SkillToolset([mock_skill1]) + + clone = toolset.clone_with_updated_skills([mock_skill2]) + + assert clone.tool_name_prefix is None + assert clone.tool_filter is None + + def test_init_accepts_environment(mock_skill1): """SkillToolset stores the provided environment.""" mock_env = mock.create_autospec(BaseEnvironment, instance=True)