Skip to content

Commit 21be6ad

Browse files
cornellgitcopybara-github
authored andcommitted
feat: Make skill instruction optimizable and can adapt to user tasks
PiperOrigin-RevId: 869971535
1 parent 3fbc27f commit 21be6ad

File tree

5 files changed

+61
-55
lines changed

5 files changed

+61
-55
lines changed

contributing/samples/skills_agent/agent.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from google.adk import Agent
2020
from google.adk.skills import load_skill_from_dir
2121
from google.adk.skills import models
22+
from google.adk.skills.prompt import DEFAULT_SKILL_SYSTEM_INSTRUCTION
2223
from google.adk.tools import skill_toolset
2324

2425
greeting_skill = models.Skill(
@@ -52,9 +53,7 @@
5253
model="gemini-2.5-flash",
5354
name="skill_user_agent",
5455
description="An agent that can use specialized skills.",
55-
instruction=(
56-
"You are a helpful assistant that can leverage skills to perform tasks."
57-
),
56+
instruction=DEFAULT_SKILL_SYSTEM_INSTRUCTION,
5857
tools=[
5958
my_skill_toolset,
6059
],

src/google/adk/skills/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
from .models import Resources
1919
from .models import Script
2020
from .models import Skill
21+
from .prompt import DEFAULT_SKILL_SYSTEM_INSTRUCTION
2122
from .utils import load_skill_from_dir
2223

2324
__all__ = [
2425
"Frontmatter",
2526
"Resources",
2627
"Script",
2728
"Skill",
29+
"DEFAULT_SKILL_SYSTEM_INSTRUCTION",
2830
"load_skill_from_dir",
2931
]

src/google/adk/skills/prompt.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@
2121

2222
from . import models
2323

24+
DEFAULT_SKILL_SYSTEM_INSTRUCTION = """You can use specialized 'skills' to help you with complex tasks. You MUST use the skill tools to interact with these skills.
25+
26+
Skills are folders of instructions and resources that extend your capabilities for specialized tasks. Each skill folder contains:
27+
- **SKILL.md** (required): The main instruction file with skill metadata and detailed markdown instructions.
28+
- **references/** (Optional): Additional documentation or examples for skill usage.
29+
- **assets/** (Optional): Templates, scripts or other resources used by the skill.
30+
31+
This is very important:
32+
33+
1. Use the `list_skills` tool to discover available skills.
34+
2. If a skill seems relevant to the current user query, you MUST use the `load_skill` tool with `name="<SKILL_NAME>"` to read its full instructions before proceeding.
35+
3. Once you have read the instructions, follow them exactly as documented before replying to the user. For example, If the instruction lists multiple steps, please make sure you complete all of them in order.
36+
4. The `load_skill_resource` tool is for viewing files within a skill's directory (e.g., `references/*`, `assets/*`). Do NOT use other tools to access these files.
37+
"""
38+
2439

2540
def format_skills_as_xml(skills: List[models.Frontmatter]) -> str:
2641
"""Formats available skills into a standard XML string.

src/google/adk/tools/skill_toolset.py

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,43 @@
2323
from ..agents.readonly_context import ReadonlyContext
2424
from ..features import experimental
2525
from ..features import FeatureName
26-
from ..models.llm_request import LlmRequest
2726
from ..skills import models
2827
from ..skills import prompt
2928
from .base_tool import BaseTool
3029
from .base_toolset import BaseToolset
3130
from .tool_context import ToolContext
3231

3332

33+
@experimental(FeatureName.SKILL_TOOLSET)
34+
class ListSkillsTool(BaseTool):
35+
"""Tool to list all available skills."""
36+
37+
def __init__(self, toolset: "SkillToolset"):
38+
super().__init__(
39+
name="list_skills",
40+
description=(
41+
"Lists all available skills with their names and descriptions."
42+
),
43+
)
44+
self._toolset = toolset
45+
46+
def _get_declaration(self) -> types.FunctionDeclaration | None:
47+
return types.FunctionDeclaration(
48+
name=self.name,
49+
description=self.description,
50+
parameters_json_schema={
51+
"type": "object",
52+
"properties": {},
53+
},
54+
)
55+
56+
async def run_async(
57+
self, *, args: dict[str, Any], tool_context: ToolContext
58+
) -> Any:
59+
skill_frontmatters = self._toolset._list_skills()
60+
return prompt.format_skills_as_xml(skill_frontmatters)
61+
62+
3463
@experimental(FeatureName.SKILL_TOOLSET)
3564
class LoadSkillTool(BaseTool):
3665
"""Tool to load a skill's instructions."""
@@ -179,6 +208,7 @@ def __init__(self, skills: list[models.Skill]):
179208
super().__init__()
180209
self._skills = {skill.name: skill for skill in skills}
181210
self._tools = [
211+
ListSkillsTool(self),
182212
LoadSkillTool(self),
183213
LoadSkillResourceTool(self),
184214
]
@@ -196,33 +226,3 @@ def _get_skill(self, name: str) -> models.Skill | None:
196226
def _list_skills(self) -> list[models.Frontmatter]:
197227
"""Lists the frontmatter of all available skills."""
198228
return [s.frontmatter for s in self._skills.values()]
199-
200-
async def process_llm_request(
201-
self,
202-
*,
203-
tool_context: ToolContext,
204-
llm_request: LlmRequest,
205-
) -> None:
206-
"""Adds available skills to the system instruction."""
207-
208-
skill_frontmatters = self._list_skills()
209-
210-
# Append the skill instruction into the system instruction
211-
skills_xml = prompt.format_skills_as_xml(skill_frontmatters)
212-
skill_si = f"""
213-
You can use specialized 'skills' to help you with complex tasks. Each skill has a name and a description listed below:
214-
{skills_xml}
215-
216-
Skills are folders of instructions and resources that extend your capabilities for specialized tasks. Each skill folder contains:
217-
- **SKILL.md** (required): The main instruction file with skill metadata and detailed markdown instructions.
218-
- **references/** (Optional): Additional documentation or examples for skill usage.
219-
- **assets/** (Optional): Templates, scripts or other resources used by the skill.
220-
221-
This is very important:
222-
223-
1. If a skill seems relevant to the current user query, you MUST use the `load_skill` tool with `name="<SKILL_NAME>"` to read its full instructions before proceeding.
224-
2. Once you have read the instructions, follow them exactly as documented before replying to the user. For example, If the instruction lists multiple steps, please make sure you complete all of them in order.
225-
3. The `load_skill_resource` tool is for viewing files within a skill's directory (e.g., `references/*`, `assets/*`). Do NOT use other tools to access these files.
226-
"""
227-
228-
llm_request.append_instructions([skill_si])

tests/unittests/tools/test_skill_toolset.py

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
from unittest import mock
1616

17-
from google.adk.models import llm_request
1817
from google.adk.skills import models
1918
from google.adk.tools import skill_toolset
2019
from google.adk.tools import tool_context
@@ -124,32 +123,23 @@ def test_list_skills(mock_skill1, mock_skill2):
124123
async def test_get_tools(mock_skill1, mock_skill2):
125124
toolset = skill_toolset.SkillToolset([mock_skill1, mock_skill2])
126125
tools = await toolset.get_tools()
127-
assert len(tools) == 2
128-
assert isinstance(tools[0], skill_toolset.LoadSkillTool)
129-
assert isinstance(tools[1], skill_toolset.LoadSkillResourceTool)
126+
assert len(tools) == 3
127+
assert isinstance(tools[0], skill_toolset.ListSkillsTool)
128+
assert isinstance(tools[1], skill_toolset.LoadSkillTool)
129+
assert isinstance(tools[2], skill_toolset.LoadSkillResourceTool)
130130

131131

132132
@pytest.mark.asyncio
133-
async def test_process_llm_request(
133+
@pytest.mark.asyncio
134+
async def test_list_skills_tool(
134135
mock_skill1, mock_skill2, tool_context_instance
135136
):
136137
toolset = skill_toolset.SkillToolset([mock_skill1, mock_skill2])
137-
mock_llm_request = llm_request.LlmRequest()
138-
mock_llm_request.config.system_instruction = "existing instruction"
139-
await toolset.process_llm_request(
140-
tool_context=tool_context_instance, llm_request=mock_llm_request
141-
)
142-
assert "<available_skills>" in mock_llm_request.config.system_instruction
143-
assert (
144-
"You can use specialized 'skills'"
145-
in mock_llm_request.config.system_instruction
146-
)
147-
assert (
148-
"skills are folders" in mock_llm_request.config.system_instruction.lower()
149-
)
150-
assert mock_llm_request.config.system_instruction.startswith(
151-
"existing instruction"
152-
)
138+
tool = skill_toolset.ListSkillsTool(toolset)
139+
result = await tool.run_async(args={}, tool_context=tool_context_instance)
140+
assert "<available_skills>" in result
141+
assert "skill1" in result
142+
assert "skill2" in result
153143

154144

155145
@pytest.mark.asyncio

0 commit comments

Comments
 (0)