Lab L2.4: Custom Skills

Assignment: Accept this lab on GitHub Classroom
You’ll get your own repository with starter code, instructions, and automatic grading.

   
Duration 60 minutes
Prerequisites Module 2.4 completed

Objectives

  • Create a custom skill using SkillBase
  • Package reusable functionality
  • Share skills between agents

Setup

Create two files:

  • weather_skill.py - The custom skill
  • lab2_4_agent.py - The agent using the skill

Part 1: Create Basic Skill (20 min)

Task

Create a weather lookup skill (simulated data).

weather_skill.py

#!/usr/bin/env python3
"""Custom weather skill."""

from typing import List, Dict, Any
from signalwire_agents.skills import SkillBase
from signalwire_agents.core.function_result import SwaigFunctionResult


class WeatherSkill(SkillBase):
    """Skill for weather lookups."""

    SKILL_NAME = "weather_lookup"
    SKILL_DESCRIPTION = "Look up weather information for cities"
    SKILL_VERSION = "1.0.0"
    REQUIRED_PACKAGES = []
    REQUIRED_ENV_VARS = []

    # Simulated weather data
    WEATHER_DATA = {
        "new york": {"temp": 45, "condition": "cloudy", "humidity": 65},
        "los angeles": {"temp": 72, "condition": "sunny", "humidity": 40},
        "chicago": {"temp": 38, "condition": "windy", "humidity": 55},
        "miami": {"temp": 82, "condition": "sunny", "humidity": 75},
        "seattle": {"temp": 52, "condition": "rainy", "humidity": 80},
    }

    def setup(self) -> bool:
        """Setup the skill - called during initialization."""
        return True

    def register_tools(self) -> None:
        """Register tools for this skill."""

        self.define_tool(
            name="get_weather",
            description="Get current weather for a city",
            parameters={
                "city": {
                    "type": "string",
                    "description": "City name"
                }
            },
            handler=self._get_weather_handler
        )

        self.define_tool(
            name="list_weather_cities",
            description="Get list of cities with weather data",
            parameters={},
            handler=self._list_cities_handler
        )

    def _get_weather_handler(self, args, raw_data):
        """Handler for get_weather tool."""
        city = args.get("city", "")
        city_lower = city.lower()
        weather = self.WEATHER_DATA.get(city_lower)

        if weather:
            return SwaigFunctionResult(
                f"Weather in {city}: {weather['temp']}°F, "
                f"{weather['condition']}, {weather['humidity']}% humidity"
            )
        return SwaigFunctionResult(f"Weather data not available for {city}")

    def _list_cities_handler(self, args, raw_data):
        """Handler for list_weather_cities tool."""
        cities = list(self.WEATHER_DATA.keys())
        return SwaigFunctionResult(
            f"Available cities: {', '.join(c.title() for c in cities)}"
        )

    def get_hints(self) -> List[str]:
        """Return speech recognition hints."""
        return ["weather", "temperature", "forecast"]

    def get_prompt_sections(self) -> List[Dict[str, Any]]:
        """Return prompt sections to add to agent."""
        return [
            {
                "title": "Weather Information",
                "body": "You can provide weather information for various cities.",
                "bullets": [
                    "Use get_weather to check conditions in a city",
                    "Use list_weather_cities to see available cities"
                ]
            }
        ]

    @classmethod
    def get_parameter_schema(cls) -> Dict[str, Dict[str, Any]]:
        """Return the parameter schema for this skill."""
        return super().get_parameter_schema()

Key Components

Component Purpose
SKILL_NAME Unique identifier used with add_skill()
SKILL_DESCRIPTION Human-readable description
setup() Initialization, return True if successful
register_tools() Define tools using self.define_tool()
get_parameter_schema() Define configuration parameters

Part 2: Use Skill in Agent (15 min)

Task

Create an agent that uses the weather skill.

lab2_4_agent.py

#!/usr/bin/env python3
"""Lab 2.4: Agent using custom skill."""

from signalwire_agents import AgentBase, register_skill
from weather_skill import WeatherSkill

# Register the custom skill with the skill registry
register_skill(WeatherSkill)


class WeatherAgent(AgentBase):
    def __init__(self):
        super().__init__(name="weather-agent")

        self.prompt_add_section(
            "Role",
            "You are a weather information assistant. "
            "Help users check weather conditions in various cities."
        )

        self.prompt_add_section(
            "Instructions",
            bullets=[
                "Use the weather skill to look up conditions",
                "If city not found, suggest available cities",
                "Provide helpful weather-related advice"
            ]
        )

        self.add_language("English", "en-US", "rime.spore")

        # Add custom weather skill by name (must match SKILL_NAME)
        self.add_skill("weather_lookup")


if __name__ == "__main__":
    agent = WeatherAgent()
    agent.run()

Important Pattern

Custom skills require two steps:

  1. Register the skill class with register_skill(WeatherSkill)
  2. Add the skill by name with self.add_skill("weather_lookup")

The name must match the SKILL_NAME defined in your skill class.


Part 3: Add Configurable Skill (25 min)

Task

Enhance the weather skill to accept configuration options.

Updated weather_skill.py with Configuration

#!/usr/bin/env python3
"""Custom weather skill with configuration."""

from typing import List, Dict, Any
from signalwire_agents.skills import SkillBase
from signalwire_agents.core.function_result import SwaigFunctionResult


class WeatherSkill(SkillBase):
    """Configurable weather skill."""

    SKILL_NAME = "weather_lookup"
    SKILL_DESCRIPTION = "Look up weather information for cities"
    SKILL_VERSION = "1.0.0"
    REQUIRED_PACKAGES = []
    REQUIRED_ENV_VARS = []

    WEATHER_DATA = {
        "new york": {"temp_f": 45, "temp_c": 7, "condition": "cloudy", "humidity": 65},
        "los angeles": {"temp_f": 72, "temp_c": 22, "condition": "sunny", "humidity": 40},
        "chicago": {"temp_f": 38, "temp_c": 3, "condition": "windy", "humidity": 55},
        "miami": {"temp_f": 82, "temp_c": 28, "condition": "sunny", "humidity": 75},
        "seattle": {"temp_f": 52, "temp_c": 11, "condition": "rainy", "humidity": 80},
    }

    def setup(self) -> bool:
        """Setup the skill with configuration."""
        # Access configuration via self.config
        self.units = self.config.get("units", "fahrenheit")
        self.include_humidity = self.config.get("include_humidity", True)
        return True

    def register_tools(self) -> None:
        """Register tools for this skill."""

        self.define_tool(
            name="get_weather",
            description="Get current weather for a city",
            parameters={
                "city": {
                    "type": "string",
                    "description": "City name"
                }
            },
            handler=self._get_weather_handler
        )

        self.define_tool(
            name="list_weather_cities",
            description="List available cities",
            parameters={},
            handler=self._list_cities_handler
        )

    def _get_weather_handler(self, args, raw_data):
        """Handler for get_weather tool."""
        city = args.get("city", "")
        city_lower = city.lower()
        weather = self.WEATHER_DATA.get(city_lower)

        if not weather:
            return SwaigFunctionResult(f"Weather data not available for {city}")

        # Format based on configuration
        if self.units == "celsius":
            temp = f"{weather['temp_c']}C"
        else:
            temp = f"{weather['temp_f']}F"

        result = f"Weather in {city}: {temp}, {weather['condition']}"

        if self.include_humidity:
            result += f", {weather['humidity']}% humidity"

        return SwaigFunctionResult(result)

    def _list_cities_handler(self, args, raw_data):
        """Handler for list_weather_cities tool."""
        cities = list(self.WEATHER_DATA.keys())
        return SwaigFunctionResult(
            f"Available cities: {', '.join(c.title() for c in cities)}"
        )

    def get_hints(self) -> List[str]:
        return ["weather", "temperature"]

    def get_prompt_sections(self) -> List[Dict[str, Any]]:
        return []

    @classmethod
    def get_parameter_schema(cls) -> Dict[str, Dict[str, Any]]:
        """Define configuration parameters for this skill."""
        schema = super().get_parameter_schema()
        schema.update({
            "units": {
                "type": "string",
                "description": "Temperature units (fahrenheit or celsius)",
                "default": "fahrenheit"
            },
            "include_humidity": {
                "type": "boolean",
                "description": "Whether to include humidity in response",
                "default": True
            }
        })
        return schema

Using with Configuration

# In your agent - pass config as second argument
self.add_skill("weather_lookup", {
    "units": "celsius",
    "include_humidity": True
})

Testing

# Test the agent with skill
swaig-test lab2_4_agent.py --dump-swml

# List tools (should show skill functions)
swaig-test lab2_4_agent.py --list-tools

# Test get_weather
swaig-test lab2_4_agent.py --exec get_weather --city "New York"

# Test list_weather_cities
swaig-test lab2_4_agent.py --exec list_weather_cities

Validation Checklist

  • WeatherSkill class inherits from SkillBase
  • SKILL_NAME and SKILL_DESCRIPTION are defined
  • setup() returns True
  • register_tools() uses self.define_tool()
  • get_parameter_schema() calls super().get_parameter_schema()
  • Agent uses register_skill() before add_skill()
  • Both tools appear in SWML output

Challenge Extension

Create a “translation” skill that:

  1. Translates simple phrases (simulated)
  2. Supports configurable source/target languages
  3. Has a tool to list supported languages

Submission

Upload your completed files:

  • weather_skill.py
  • lab2_4_agent.py

Complete Agent Code

Click to reveal complete solution

weather_skill.py:

#!/usr/bin/env python3
"""Lab 2.4: Custom Skills - Weather Skill"""

from typing import List, Dict, Any
from signalwire_agents.skills import SkillBase
from signalwire_agents.core.function_result import SwaigFunctionResult


class WeatherSkill(SkillBase):
    """Custom skill for weather lookups."""

    SKILL_NAME = "weather_lookup"
    SKILL_DESCRIPTION = "Look up weather information for cities"
    SKILL_VERSION = "1.0.0"
    REQUIRED_PACKAGES = []
    REQUIRED_ENV_VARS = []

    WEATHER_DATA = {
        "new york": {"temp": 45, "condition": "cloudy", "humidity": 65},
        "los angeles": {"temp": 72, "condition": "sunny", "humidity": 40},
        "chicago": {"temp": 38, "condition": "windy", "humidity": 55},
        "miami": {"temp": 82, "condition": "sunny", "humidity": 75},
        "seattle": {"temp": 52, "condition": "rainy", "humidity": 80},
    }

    def setup(self) -> bool:
        return True

    def register_tools(self) -> None:
        self.define_tool(
            name="get_weather",
            description="Get current weather for a city",
            parameters={
                "city": {"type": "string", "description": "City name"}
            },
            handler=self._get_weather_handler
        )

        self.define_tool(
            name="list_weather_cities",
            description="Get list of cities with weather data",
            parameters={},
            handler=self._list_cities_handler
        )

    def _get_weather_handler(self, args, raw_data):
        city = args.get("city", "")
        weather = self.WEATHER_DATA.get(city.lower())

        if weather:
            return SwaigFunctionResult(
                f"Weather in {city}: {weather['temp']}F, "
                f"{weather['condition']}, {weather['humidity']}% humidity"
            )
        return SwaigFunctionResult(f"Weather data not available for {city}")

    def _list_cities_handler(self, args, raw_data):
        cities = list(self.WEATHER_DATA.keys())
        return SwaigFunctionResult(
            f"Available cities: {', '.join(c.title() for c in cities)}"
        )

    def get_hints(self) -> List[str]:
        return ["weather", "temperature", "forecast"]

    def get_prompt_sections(self) -> List[Dict[str, Any]]:
        return [{
            "title": "Weather Information",
            "body": "You can provide weather information for various cities.",
            "bullets": [
                "Use get_weather to check conditions in a city",
                "Use list_weather_cities to see available cities"
            ]
        }]

    @classmethod
    def get_parameter_schema(cls) -> Dict[str, Dict[str, Any]]:
        return super().get_parameter_schema()

lab2_4_agent.py:

#!/usr/bin/env python3
"""Lab 2.4: Custom Skills - Agent"""

from signalwire_agents import AgentBase, register_skill
from weather_skill import WeatherSkill

register_skill(WeatherSkill)


class WeatherAgent(AgentBase):
    def __init__(self):
        super().__init__(name="weather-agent")

        self.prompt_add_section(
            "Role",
            "You are a weather information assistant. "
            "Help users check weather conditions in various cities."
        )

        self.prompt_add_section(
            "Instructions",
            bullets=[
                "Use the weather skill to look up conditions",
                "If city not found, suggest available cities",
                "Provide helpful weather-related advice"
            ]
        )

        self.add_language("English", "en-US", "rime.spore")
        self.add_skill("weather_lookup")


if __name__ == "__main__":
    agent = WeatherAgent()
    agent.run()

Next: Lab 2.5 - Contexts and Workflows


Back to top

SignalWire AI Agents Certification Program