You've already forked adk-python
mirror of
https://github.com/encounter/adk-python.git
synced 2026-03-30 10:57:20 -07:00
feat: Add models.py and prompt.py to adk/skills to use in skill toolset
Also redefined schemas in models.py to be pydantic. Co-authored-by: Kathy Wu <wukathy@google.com> PiperOrigin-RevId: 866270203
This commit is contained in:
committed by
Copybara-Service
parent
483c5bab94
commit
c7362100eb
@@ -0,0 +1,29 @@
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Agent Development Kit - Skills."""
|
||||
|
||||
from .models import Frontmatter
|
||||
from .models import Resources
|
||||
from .models import Script
|
||||
from .models import Skill
|
||||
from .prompt import format_skills_as_xml
|
||||
|
||||
__all__ = [
|
||||
"Frontmatter",
|
||||
"Resources",
|
||||
"Script",
|
||||
"Skill",
|
||||
"format_skills_as_xml",
|
||||
]
|
||||
@@ -0,0 +1,148 @@
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Data models for Agent Skills."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Frontmatter(BaseModel):
|
||||
"""L1 skill content: metadata parsed from SKILL.md frontmatter for skill discovery.
|
||||
|
||||
Attributes:
|
||||
name: Skill name in kebab-case (required).
|
||||
description: What the skill does and when the model should use it
|
||||
(required).
|
||||
license: License for the skill (optional).
|
||||
compatibility: Compatibility information for the skill (optional).
|
||||
allowed_tools: Tool patterns the skill requires (optional, experimental).
|
||||
metadata: Key-value pairs for client-specific properties (defaults to
|
||||
empty dict).
|
||||
"""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
license: Optional[str] = None
|
||||
compatibility: Optional[str] = None
|
||||
allowed_tools: Optional[str] = None
|
||||
metadata: dict[str, str] = {}
|
||||
|
||||
|
||||
class Script(BaseModel):
|
||||
"""Wrapper for script content."""
|
||||
|
||||
src: str
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Returns the string representation of the script content.
|
||||
|
||||
This ensures that any script type can be converted to a string, which is
|
||||
useful for including the script in prompts or saving it to the file system.
|
||||
"""
|
||||
return self.src
|
||||
|
||||
|
||||
class Resources(BaseModel):
|
||||
"""L3 skill content: additional instructions, assets, and scripts, loaded as needed.
|
||||
|
||||
Attributes:
|
||||
references: Additional markdown files with instructions, workflows, or
|
||||
guidance.
|
||||
assets: Resource materials like database schemas, API documentation,
|
||||
templates, or examples.
|
||||
scripts: Executable scripts that can be run via bash.
|
||||
"""
|
||||
|
||||
references: dict[str, str] = {}
|
||||
assets: dict[str, str] = {}
|
||||
scripts: dict[str, Script] = {}
|
||||
|
||||
def get_reference(self, reference_id: str) -> Optional[str]:
|
||||
"""Get content of a reference file.
|
||||
|
||||
Args:
|
||||
reference_id: Unique path or name of the reference file.
|
||||
|
||||
Returns:
|
||||
Reference content as string, or None if not found
|
||||
"""
|
||||
return self.references.get(reference_id)
|
||||
|
||||
def get_asset(self, asset_id: str) -> Optional[str]:
|
||||
"""Get content of an asset file.
|
||||
|
||||
Args:
|
||||
asset_id: Unique path or name of the asset file.
|
||||
|
||||
Returns:
|
||||
Asset content as string, or None if not found
|
||||
"""
|
||||
return self.assets.get(asset_id)
|
||||
|
||||
def get_script(self, script_id: str) -> Optional[Script]:
|
||||
"""Get content of a script file.
|
||||
|
||||
Args:
|
||||
script_id: Unique path or name of the script file.
|
||||
|
||||
Returns:
|
||||
Script object, or None if not found
|
||||
"""
|
||||
return self.scripts.get(script_id)
|
||||
|
||||
def list_references(self) -> list[str]:
|
||||
"""List all available reference paths."""
|
||||
return list(self.references.keys())
|
||||
|
||||
def list_assets(self) -> list[str]:
|
||||
"""List all available asset paths."""
|
||||
return list(self.assets.keys())
|
||||
|
||||
def list_scripts(self) -> list[str]:
|
||||
"""List all available script paths."""
|
||||
return list(self.scripts.keys())
|
||||
|
||||
|
||||
class Skill(BaseModel):
|
||||
"""Complete skill representation including frontmatter, instructions, and resources.
|
||||
|
||||
A skill combines:
|
||||
- L1: Frontmatter for discovery (name, description).
|
||||
- L2: Instructions from SKILL.md body, loaded when skill is triggered.
|
||||
- L3: Resources including additional instructions, assets, and scripts,
|
||||
loaded as needed.
|
||||
|
||||
Attributes:
|
||||
frontmatter: Parsed skill frontmatter from SKILL.md.
|
||||
instructions: L2 skill content: markdown instruction from SKILL.md body.
|
||||
resources: L3 skill content: additional instructions, assets, and scripts.
|
||||
"""
|
||||
|
||||
frontmatter: Frontmatter
|
||||
instructions: str
|
||||
resources: Resources = Resources()
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Convenience property to access skill name."""
|
||||
return self.frontmatter.name
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
"""Convenience property to access skill description."""
|
||||
return self.frontmatter.description
|
||||
@@ -0,0 +1,53 @@
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Module for skill prompt generation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import html
|
||||
from typing import List
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
def format_skills_as_xml(skills: List[models.Frontmatter]) -> str:
|
||||
"""Formats available skills into a standard XML string.
|
||||
|
||||
Args:
|
||||
skills: A list of skill frontmatter objects.
|
||||
|
||||
Returns:
|
||||
XML string with <available_skills> block containing each skill's
|
||||
name and description.
|
||||
"""
|
||||
|
||||
if not skills:
|
||||
return "<available_skills>\n</available_skills>"
|
||||
|
||||
lines = ["<available_skills>"]
|
||||
|
||||
for skill in skills:
|
||||
lines.append("<skill>")
|
||||
lines.append("<name>")
|
||||
lines.append(html.escape(skill.name))
|
||||
lines.append("</name>")
|
||||
lines.append("<description>")
|
||||
lines.append(html.escape(skill.description))
|
||||
lines.append("</description>")
|
||||
lines.append("</skill>")
|
||||
|
||||
lines.append("</available_skills>")
|
||||
|
||||
return "\n".join(lines)
|
||||
@@ -0,0 +1,13 @@
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
@@ -0,0 +1,70 @@
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Unit tests for skill models."""
|
||||
|
||||
from google.adk.skills import models
|
||||
import pytest
|
||||
|
||||
|
||||
def test_frontmatter():
|
||||
"""Tests Frontmatter model."""
|
||||
frontmatter = models.Frontmatter(
|
||||
name="test-skill",
|
||||
description="Test description",
|
||||
license="Apache 2.0",
|
||||
compatibility="test",
|
||||
allowed_tools="test",
|
||||
metadata={"key": "value"},
|
||||
)
|
||||
assert frontmatter.name == "test-skill"
|
||||
assert frontmatter.description == "Test description"
|
||||
assert frontmatter.license == "Apache 2.0"
|
||||
assert frontmatter.compatibility == "test"
|
||||
assert frontmatter.allowed_tools == "test"
|
||||
assert frontmatter.metadata == {"key": "value"}
|
||||
|
||||
|
||||
def test_resources():
|
||||
"""Tests Resources model."""
|
||||
resources = models.Resources(
|
||||
references={"ref1": "ref content"},
|
||||
assets={"asset1": "asset content"},
|
||||
scripts={"script1": models.Script(src="print('hello')")},
|
||||
)
|
||||
assert resources.get_reference("ref1") == "ref content"
|
||||
assert resources.get_asset("asset1") == "asset content"
|
||||
assert resources.get_script("script1").src == "print('hello')"
|
||||
assert resources.get_reference("ref2") is None
|
||||
assert resources.get_asset("asset2") is None
|
||||
assert resources.get_script("script2") is None
|
||||
assert resources.list_references() == ["ref1"]
|
||||
assert resources.list_assets() == ["asset1"]
|
||||
assert resources.list_scripts() == ["script1"]
|
||||
|
||||
|
||||
def test_skill_properties():
|
||||
"""Tests Skill model."""
|
||||
frontmatter = models.Frontmatter(
|
||||
name="my-skill", description="my description"
|
||||
)
|
||||
skill = models.Skill(frontmatter=frontmatter, instructions="do this")
|
||||
assert skill.name == "my-skill"
|
||||
assert skill.description == "my description"
|
||||
|
||||
|
||||
def test_script_to_string():
|
||||
"""Tests Script model."""
|
||||
script = models.Script(src="print('hello')")
|
||||
assert str(script) == "print('hello')"
|
||||
@@ -0,0 +1,49 @@
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Unit tests for prompt."""
|
||||
|
||||
from google.adk.skills import models
|
||||
from google.adk.skills import prompt
|
||||
import pytest
|
||||
|
||||
|
||||
class TestPrompt:
|
||||
|
||||
def test_format_skills_as_xml(self):
|
||||
skills = [
|
||||
models.Frontmatter(name="skill1", description="desc1"),
|
||||
models.Frontmatter(name="skill2", description="desc2"),
|
||||
]
|
||||
xml = prompt.format_skills_as_xml(skills)
|
||||
|
||||
assert "<name>\nskill1\n</name>" in xml
|
||||
assert "<description>\ndesc1\n</description>" in xml
|
||||
assert "<location>" not in xml
|
||||
assert "<name>\nskill2\n</name>" in xml
|
||||
assert "<description>\ndesc2\n</description>" in xml
|
||||
assert xml.startswith("<available_skills>")
|
||||
assert xml.endswith("</available_skills>")
|
||||
|
||||
def test_format_skills_as_xml_empty(self):
|
||||
xml = prompt.format_skills_as_xml([])
|
||||
assert xml == "<available_skills>\n</available_skills>"
|
||||
|
||||
def test_format_skills_as_xml_escaping(self):
|
||||
skills = [
|
||||
models.Frontmatter(name="skill&name", description="desc<ription>"),
|
||||
]
|
||||
xml = prompt.format_skills_as_xml(skills)
|
||||
assert "skill&name" in xml
|
||||
assert "desc<ription>" in xml
|
||||
Reference in New Issue
Block a user