Third-Party Skills Integration Guide

This guide explains how to create and integrate third-party skills with the SignalWire AI Agents SDK. The SDK supports multiple methods for loading external skills, making it easy to extend agent capabilities without modifying the core SDK.

Overview

Third-party skills can be integrated using four different methods:

  1. Direct Registration - Register skill classes programmatically
  2. Directory Registration - Add directories containing skill collections
  3. Python Entry Points - Install skills as Python packages
  4. Environment Variables - Configure skill paths via environment

All third-party skills are discovered and indexed the same way as built-in skills, appearing in list_skills_with_params() output with their parameter schemas.

Creating a Third-Party Skill

Third-party skills follow the same structure as built-in skills. Here's a minimal example:

# my_weather_skill/skill.py
from signalwire_agents.core.skill_base import SkillBase
from signalwire_agents.core.function_result import SwaigFunctionResult
from typing import Dict, Any, List

class WeatherSkill(SkillBase):
    """Custom weather information skill"""
    
    SKILL_NAME = "weather"
    SKILL_DESCRIPTION = "Get weather information for any location"
    SKILL_VERSION = "1.0.0"
    REQUIRED_PACKAGES = ["requests"]
    REQUIRED_ENV_VARS = []
    
    @classmethod
    def get_parameter_schema(cls) -> Dict[str, Dict[str, Any]]:
        """Define configuration parameters"""
        schema = super().get_parameter_schema()
        
        schema.update({
            "api_key": {
                "type": "string",
                "description": "Weather API key",
                "required": True,
                "hidden": True,
                "env_var": "WEATHER_API_KEY"
            },
            "units": {
                "type": "string",
                "description": "Temperature units",
                "default": "celsius",
                "required": False,
                "enum": ["celsius", "fahrenheit", "kelvin"]
            },
            "cache_timeout": {
                "type": "integer",
                "description": "Cache timeout in seconds",
                "default": 300,
                "required": False,
                "min": 0,
                "max": 3600
            }
        })
        
        return schema
    
    def setup(self) -> bool:
        """Initialize the skill"""
        if not self.validate_packages():
            return False
            
        self.api_key = self.params.get('api_key')
        if not self.api_key:
            self.logger.error("Weather API key is required")
            return False
            
        self.units = self.params.get('units', 'celsius')
        self.cache_timeout = self.params.get('cache_timeout', 300)
        
        return True
    
    def register_tools(self) -> None:
        """Register weather tools with the agent"""
        self.define_tool(
            name="get_weather",
            description="Get current weather for a location",
            parameters={
                "location": {
                    "type": "string",
                    "description": "City name or coordinates"
                }
            },
            handler=self._get_weather_handler
        )
    
    def _get_weather_handler(self, args, raw_data):
        """Handle weather requests"""
        location = args.get('location', '').strip()
        
        if not location:
            return SwaigFunctionResult("Please provide a location")
        
        # Implementation would call weather API here
        # This is just an example
        return SwaigFunctionResult(
            f"The weather in {location} is sunny and 22°{self.units[0].upper()}"
        )

Integration Methods

Method 1: Direct Registration

Register individual skill classes programmatically:

from signalwire_agents import AgentBase, register_skill
from my_weather_skill import WeatherSkill

# Register the skill globally
register_skill(WeatherSkill)

# Now use it in any agent
class MyAgent(AgentBase):
    def __init__(self):
        super().__init__(name="my-agent")
        
        # Add the registered skill
        self.add_skill("weather", {
            "api_key": "your-api-key",
            "units": "fahrenheit"
        })

Method 2: Directory Registration

Register directories containing multiple skills:

from signalwire_agents import add_skill_directory

# Add a directory of custom skills
add_skill_directory('/opt/custom_skills')

# Directory structure should be:
# /opt/custom_skills/
#   weather/
#     skill.py      # Contains WeatherSkill class
#   stock_market/
#     skill.py      # Contains StockMarketSkill class
#   translation/
#     skill.py      # Contains TranslationSkill class

# Now use any skill from the directory
agent.add_skill("weather", {"api_key": "..."})
agent.add_skill("stock_market", {"api_key": "..."})

Method 3: Python Entry Points

Create installable skill packages using setuptools entry points:

# setup.py for your skill package
from setuptools import setup, find_packages

setup(
    name="my-signalwire-skills",
    version="1.0.0",
    packages=find_packages(),
    install_requires=[
        "signalwire-agents",
        "requests",
    ],
    entry_points={
        'signalwire_agents.skills': [
            'weather = my_skills.weather:WeatherSkill',
            'stock = my_skills.stock:StockMarketSkill',
            'translate = my_skills.translate:TranslationSkill',
        ]
    }
)

After installation, skills are automatically available:

pip install my-signalwire-skills
# Skills are automatically discovered
agent.add_skill("weather", {"api_key": "..."})

Method 4: Environment Variable

Set the SIGNALWIRE_SKILL_PATHS environment variable:

# Single directory
export SIGNALWIRE_SKILL_PATHS=/opt/my_skills

# Multiple directories (colon-separated)
export SIGNALWIRE_SKILL_PATHS=/opt/my_skills:/home/user/custom_skills

Skills in these directories are automatically discovered:

# No registration needed - skills are found automatically
agent.add_skill("weather", {"api_key": "..."})

Directory Structure

Skills loaded from directories must follow this structure:

my_skills_directory/
├── weather/                 # Skill directory (matches SKILL_NAME)
│   ├── skill.py            # Required: Contains skill class
│   ├── __init__.py         # Optional: Makes it a package
│   └── README.md           # Optional: Documentation
├── translation/
│   ├── skill.py
│   └── resources/          # Optional: Additional files
│       └── languages.json
└── stock_market/
    └── skill.py

Skill Discovery and Schema

Third-party skills are fully integrated with the SDK's discovery system:

from signalwire_agents import list_skills_with_params

# Get all skills including third-party ones
all_skills = list_skills_with_params()

# Third-party skills include source information
print(all_skills['weather'])
# Output:
{
    "name": "weather",
    "description": "Get weather information for any location",
    "version": "1.0.0",
    "supports_multiple_instances": False,
    "required_packages": ["requests"],
    "required_env_vars": [],
    "parameters": {
        "api_key": {
            "type": "string",
            "description": "Weather API key",
            "required": True,
            "hidden": True,
            "env_var": "WEATHER_API_KEY"
        },
        "units": {
            "type": "string",
            "description": "Temperature units",
            "default": "celsius",
            "required": False,
            "enum": ["celsius", "fahrenheit", "kelvin"]
        }
    },
    "source": "external"  # Shows it's a third-party skill
}

Best Practices

1. Skill Naming

2. Parameter Design

3. Error Handling

def setup(self) -> bool:
    """Proper setup with error handling"""
    # Validate packages
    if not self.validate_packages():
        return False
    
    # Validate required parameters
    if not self.params.get('api_key'):
        self.logger.error("API key is required")
        return False
    
    # Test connectivity
    try:
        self._test_api_connection()
    except Exception as e:
        self.logger.error(f"Failed to connect to API: {e}")
        return False
    
    return True

4. Documentation

Include a README.md in your skill directory:

# Weather Skill

Provides weather information for any location.

## Configuration

- `api_key` (required): Your weather API key
- `units` (optional): Temperature units (celsius, fahrenheit, kelvin)
- `cache_timeout` (optional): Cache timeout in seconds

## Usage

```python
agent.add_skill("weather", {
    "api_key": "your-api-key",
    "units": "fahrenheit"
})

## Advanced Features

### Multiple Instances

Support multiple instances of your skill:

```python
class WeatherSkill(SkillBase):
    SKILL_NAME = "weather"
    SUPPORTS_MULTIPLE_INSTANCES = True  # Enable multiple instances
    
    def get_instance_key(self) -> str:
        """Create unique key for this instance"""
        service = self.params.get('service', 'default')
        return f"{self.SKILL_NAME}_{service}"

Usage:

# Add multiple weather services
agent.add_skill("weather", {
    "tool_name": "openweather",
    "service": "openweathermap",
    "api_key": "key1"
})

agent.add_skill("weather", {
    "tool_name": "weatherapi", 
    "service": "weatherapi",
    "api_key": "key2"
})

Dynamic Tool Names

Customize tool names for better agent prompts:

def register_tools(self) -> None:
    tool_name = self.params.get('tool_name', 'get_weather')
    
    self.define_tool(
        name=tool_name,
        description=f"Get weather using {self.params.get('service', 'default')}",
        parameters={...},
        handler=self._weather_handler
    )

Skill Dependencies

Load skills that depend on other skills:

def setup(self) -> bool:
    # Check if required skill is available
    if not self.agent.skill_manager.has_skill("translation"):
        self.logger.error("This skill requires the translation skill")
        return False
    
    return True

Testing Third-Party Skills

Test your skills before distribution:

# test_my_skill.py
import unittest
from signalwire_agents import AgentBase
from my_weather_skill import WeatherSkill

class TestWeatherSkill(unittest.TestCase):
    def setUp(self):
        self.agent = AgentBase(name="test-agent")
        
    def test_skill_registration(self):
        # Test direct registration
        from signalwire_agents import register_skill
        register_skill(WeatherSkill)
        
        # Test adding skill
        success, error = self.agent.add_skill("weather", {
            "api_key": "test-key"
        })
        self.assertTrue(success)
        
    def test_parameter_schema(self):
        schema = WeatherSkill.get_parameter_schema()
        self.assertIn("api_key", schema)
        self.assertTrue(schema["api_key"]["required"])
        self.assertTrue(schema["api_key"]["hidden"])

Troubleshooting

Skill Not Found

If your skill isn't being discovered:

  1. Check the skill directory structure
  2. Verify SKILL_NAME matches the directory name
  3. Ensure skill.py exists and contains a valid skill class
  4. Check logs for loading errors

Import Errors

For skills with relative imports:

# Use absolute imports in skill.py
from my_skills.weather.utils import parse_temperature

# Or handle import errors gracefully
try:
    from .utils import parse_temperature
except ImportError:
    from utils import parse_temperature

Environment Variables

Debug environment variable loading:

import os
print(f"Skill paths: {os.environ.get('SIGNALWIRE_SKILL_PATHS', 'Not set')}")

from signalwire_agents.skills.registry import skill_registry
sources = skill_registry.list_all_skill_sources()
print(f"External skills: {sources['external_paths']}")

Example: Complete Third-Party Skill Package

Here's a complete example of a distributable skill package:

my-signalwire-skills/
├── setup.py
├── README.md
├── requirements.txt
├── my_signalwire_skills/
│   ├── __init__.py
│   ├── weather/
│   │   ├── __init__.py
│   │   ├── skill.py
│   │   └── utils.py
│   └── translation/
│       ├── __init__.py
│       └── skill.py
└── tests/
    ├── __init__.py
    ├── test_weather.py
    └── test_translation.py
# setup.py
from setuptools import setup, find_packages

setup(
    name="my-signalwire-skills",
    version="1.0.0",
    author="Your Name",
    description="Custom skills for SignalWire AI Agents",
    packages=find_packages(),
    install_requires=[
        "signalwire-agents>=1.0.12",
        "requests>=2.25.0",
    ],
    entry_points={
        'signalwire_agents.skills': [
            'weather = my_signalwire_skills.weather.skill:WeatherSkill',
            'translate = my_signalwire_skills.translation.skill:TranslationSkill',
        ]
    },
    python_requires='>=3.7',
)

Install and use:

pip install git+https://github.com/yourname/my-signalwire-skills.git
from signalwire_agents import AgentBase

agent = AgentBase(name="my-agent")
agent.add_skill("weather", {"api_key": "..."})
agent.add_skill("translate", {"api_key": "..."})
agent.run()