| Duration | 2 hours |
| Day | 3 of 5 |
Learning Objectives
By the end of this module, students will be able to:
- Create custom skills using SkillBase
- Define skill parameters and configuration
- Package skills for distribution
- Register skills with agents
Topics
1. SkillBase Architecture (25 min)
Why Custom Skills?
- Reusability - Use across multiple agents
- Encapsulation - Bundle related functionality
- Distribution - Share with team or community
- Separation - Keep agent code clean
SkillBase Class
from signalwire_agents.skills import SkillBase
class MySkill(SkillBase):
# Required class attributes
SKILL_NAME = "my_skill"
SKILL_DESCRIPTION = "What this skill does"
SKILL_VERSION = "1.0.0"
def setup(self, config: dict):
"""Configure the skill with provided parameters."""
pass
def register_functions(self, agent):
"""Register functions with the agent."""
pass
def get_prompts(self) -> list:
"""Return additional prompts for the agent."""
return []
def get_hints(self) -> list:
"""Return speech recognition hints."""
return []
2. Creating a Basic Skill (30 min)
Example: Greeting Skill
from signalwire_agents.skills import SkillBase
from signalwire_agents import SwaigFunctionResult
class GreetingSkill(SkillBase):
"""Skill for personalized greetings."""
SKILL_NAME = "greeting"
SKILL_DESCRIPTION = "Provides personalized greeting functions"
SKILL_VERSION = "1.0.0"
def setup(self, config: dict):
"""Store configuration."""
self.company_name = config.get("company_name", "Our Company")
self.greeting_style = config.get("style", "formal")
def register_functions(self, agent):
"""Add greeting function to agent."""
@agent.tool(description=f"Get a greeting for {self.company_name}")
def get_greeting(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
name = args.get("name", "")
if self.greeting_style == "formal":
msg = f"Good day, {name}. Welcome to {self.company_name}."
else:
msg = f"Hey {name}! Welcome to {self.company_name}!"
return SwaigFunctionResult(msg)
def get_prompts(self) -> list:
"""Add greeting-related prompts."""
return [
{
"section": "Greeting Style",
"body": f"Use a {self.greeting_style} greeting style. "
f"You represent {self.company_name}."
}
]
Using the Skill
from signalwire_agents import AgentBase
agent = AgentBase(name="my-agent")
# Add skill with configuration
agent.add_skill("greeting", {
"company_name": "TechCorp",
"style": "casual"
})
3. Skill with Multiple Functions (25 min)
Example: Customer Lookup Skill
class CustomerSkill(SkillBase):
"""Skill for customer data operations."""
SKILL_NAME = "customer"
SKILL_DESCRIPTION = "Customer lookup and management"
SKILL_VERSION = "1.0.0"
def setup(self, config: dict):
self.api_url = config.get("api_url")
self.api_key = config.get("api_key")
# Validate required config
if not self.api_url:
raise ValueError("customer skill requires api_url")
def register_functions(self, agent):
"""Register all customer functions."""
@agent.tool(description="Look up customer by ID")
def get_customer(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
customer_id = args.get("customer_id", "")
# Call API
customer = self._fetch_customer(customer_id)
if customer:
return SwaigFunctionResult(
f"Customer {customer['name']}, account since {customer['since']}."
)
return SwaigFunctionResult("Customer not found.")
@agent.tool(description="Get customer's recent orders")
def get_customer_orders(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
customer_id = args.get("customer_id", "")
orders = self._fetch_orders(customer_id)
if orders:
return SwaigFunctionResult(
f"Found {len(orders)} orders for this customer."
)
return SwaigFunctionResult("No orders found.")
@agent.tool(description="Update customer phone number")
def update_customer_phone(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
customer_id = args.get("customer_id", "")
phone = args.get("phone", "")
success = self._update_phone(customer_id, phone)
if success:
return SwaigFunctionResult("Phone number updated.")
return SwaigFunctionResult("Could not update phone number.")
def _fetch_customer(self, customer_id: str) -> dict:
"""Internal: Fetch customer from API."""
import requests
response = requests.get(
f"{self.api_url}/customers/{customer_id}",
headers={"Authorization": f"Bearer {self.api_key}"}
)
return response.json() if response.ok else None
def _fetch_orders(self, customer_id: str) -> list:
"""Internal: Fetch orders from API."""
import requests
response = requests.get(
f"{self.api_url}/customers/{customer_id}/orders",
headers={"Authorization": f"Bearer {self.api_key}"}
)
return response.json() if response.ok else []
def _update_phone(self, customer_id: str, phone: str) -> bool:
"""Internal: Update customer phone."""
import requests
response = requests.patch(
f"{self.api_url}/customers/{customer_id}",
headers={"Authorization": f"Bearer {self.api_key}"},
json={"phone": phone}
)
return response.ok
def get_hints(self) -> list:
"""Speech recognition hints."""
return ["customer ID", "account number", "phone number"]
4. Skill Registration (20 min)
Local Registration
For skills in your project:
from signalwire_agents import AgentBase
from my_skills.customer import CustomerSkill
agent = AgentBase(name="support")
# Register the skill class
agent.register_skill_class(CustomerSkill)
# Then use it
agent.add_skill("customer", {
"api_url": "https://api.example.com",
"api_key": os.getenv("CUSTOMER_API_KEY")
})
Auto-Discovery
Skills can be discovered via entry points in setup.py:
# setup.py
setup(
name="my-skills",
entry_points={
"signalwire_agents.skills": [
"customer = my_skills.customer:CustomerSkill",
"greeting = my_skills.greeting:GreetingSkill",
]
}
)
Then use by name:
agent.add_skill("customer", {...})
5. Error Handling in Skills (20 min)
Configuration Validation
def setup(self, config: dict):
"""Validate and store configuration."""
self.api_key = config.get("api_key")
if not self.api_key:
raise ValueError(
f"{self.SKILL_NAME} skill requires 'api_key' parameter"
)
# Validate API key format
if not self.api_key.startswith("sk-"):
raise ValueError("Invalid API key format")
Function Error Handling
def register_functions(self, agent):
@agent.tool(description="Get data")
def get_data(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
id = args.get("id", "")
try:
data = self._fetch_data(id)
return SwaigFunctionResult(f"Found: {data}")
except ConnectionError:
return SwaigFunctionResult(
"I'm having trouble connecting. Please try again."
)
except Exception as e:
# Log internally, don't expose
self.logger.error(f"get_data failed: {e}")
return SwaigFunctionResult(
"Something went wrong. Let me transfer you to support."
)
Complete Example
Click to reveal complete solution
CRM Integration Skill
#!/usr/bin/env python3
"""CRM integration skill."""
import os
import logging
from signalwire_agents.skills import SkillBase
from signalwire_agents import SwaigFunctionResult
logger = logging.getLogger(__name__)
class CRMSkill(SkillBase):
"""Skill for CRM integration."""
SKILL_NAME = "crm"
SKILL_DESCRIPTION = "CRM lookup and customer management"
SKILL_VERSION = "1.0.0"
def setup(self, config: dict):
"""Configure CRM connection."""
self.crm_url = config.get("crm_url")
self.api_key = config.get("api_key")
self.timeout = config.get("timeout", 5)
if not self.crm_url or not self.api_key:
raise ValueError("CRM skill requires crm_url and api_key")
def register_functions(self, agent):
"""Register CRM functions."""
skill = self # Capture for closures
@agent.tool(
description="Look up a contact in the CRM by phone number",
fillers=["Looking up your account...", "One moment..."]
)
def lookup_contact(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
phone = args.get("phone", "")
contact = skill._api_call(f"/contacts?phone={phone}")
if contact:
return SwaigFunctionResult(
f"Found {contact['name']}, a {contact['type']} customer "
f"since {contact['created']}."
)
return SwaigFunctionResult(
"I don't see that number in our system. "
"Would you like me to create a new contact?"
)
@agent.tool(
description="Create a new contact in the CRM"
)
def create_contact(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
name = args.get("name", "")
phone = args.get("phone", "")
email = args.get("email")
data = {"name": name, "phone": phone}
if email:
data["email"] = email
result = skill._api_call("/contacts", method="POST", data=data)
if result:
return SwaigFunctionResult(
f"Created contact for {name}. Their ID is {result['id']}."
)
return SwaigFunctionResult("Could not create contact.")
@agent.tool(
description="Log a note on a contact's account"
)
def add_note(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
contact_id = args.get("contact_id", "")
note = args.get("note", "")
result = skill._api_call(
f"/contacts/{contact_id}/notes",
method="POST",
data={"note": note}
)
if result:
return SwaigFunctionResult("Note added to the account.")
return SwaigFunctionResult("Could not add note.")
def _api_call(self, endpoint: str, method: str = "GET", data: dict = None):
"""Make API call to CRM."""
import requests
url = f"{self.crm_url}{endpoint}"
headers = {"Authorization": f"Bearer {self.api_key}"}
try:
if method == "GET":
response = requests.get(url, headers=headers, timeout=self.timeout)
elif method == "POST":
response = requests.post(url, headers=headers, json=data, timeout=self.timeout)
else:
return None
if response.ok:
return response.json()
except Exception as e:
logger.error(f"CRM API error: {e}")
return None
def get_prompts(self) -> list:
"""CRM-related prompts."""
return [
{
"section": "CRM Usage",
"body": "When a customer calls, look up their phone number in the CRM. "
"Always log notes about the conversation."
}
]
def get_hints(self) -> list:
"""Speech hints for CRM terms."""
return ["CRM", "contact ID", "account number"]
Key Takeaways
- SkillBase provides structure - Consistent skill interface
- setup() for configuration - Validate and store config
- register_functions() for tools - Add functions to agent
- Prompts and hints included - Complete capability package
- Error handling is critical - Never expose internal errors
Preparation for Lab 2.4
- Think of a capability to package as a skill
- Identify configuration parameters needed
- Plan 2-3 functions for the skill
Lab Preview
In Lab 2.4, you will:
- Create a custom skill from scratch
- Add multiple functions
- Implement configuration validation
- Test the skill in an agent