signalwire-agents 0.1.8__tar.gz → 0.1.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {signalwire_agents-0.1.8/signalwire_agents.egg-info → signalwire_agents-0.1.10}/PKG-INFO +52 -1
  2. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/README.md +49 -0
  3. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/pyproject.toml +4 -2
  4. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/__init__.py +4 -1
  5. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/core/agent_base.py +40 -0
  6. signalwire_agents-0.1.10/signalwire_agents/core/skill_base.py +127 -0
  7. signalwire_agents-0.1.10/signalwire_agents/core/skill_manager.py +136 -0
  8. signalwire_agents-0.1.10/signalwire_agents/skills/__init__.py +14 -0
  9. signalwire_agents-0.1.10/signalwire_agents/skills/datetime/__init__.py +1 -0
  10. signalwire_agents-0.1.10/signalwire_agents/skills/datetime/skill.py +109 -0
  11. signalwire_agents-0.1.10/signalwire_agents/skills/math/__init__.py +1 -0
  12. signalwire_agents-0.1.10/signalwire_agents/skills/math/skill.py +88 -0
  13. signalwire_agents-0.1.10/signalwire_agents/skills/registry.py +98 -0
  14. signalwire_agents-0.1.10/signalwire_agents/skills/web_search/__init__.py +1 -0
  15. signalwire_agents-0.1.10/signalwire_agents/skills/web_search/skill.py +229 -0
  16. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10/signalwire_agents.egg-info}/PKG-INFO +52 -1
  17. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents.egg-info/SOURCES.txt +10 -0
  18. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents.egg-info/requires.txt +2 -0
  19. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/LICENSE +0 -0
  20. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/schema.json +0 -0
  21. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/setup.cfg +0 -0
  22. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/setup.py +0 -0
  23. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/agent_server.py +0 -0
  24. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/core/__init__.py +0 -0
  25. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/core/function_result.py +0 -0
  26. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/core/pom_builder.py +0 -0
  27. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/core/security/__init__.py +0 -0
  28. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/core/security/session_manager.py +0 -0
  29. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/core/state/__init__.py +0 -0
  30. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/core/state/file_state_manager.py +0 -0
  31. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/core/state/state_manager.py +0 -0
  32. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/core/swaig_function.py +0 -0
  33. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/core/swml_builder.py +0 -0
  34. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/core/swml_handler.py +0 -0
  35. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/core/swml_renderer.py +0 -0
  36. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/core/swml_service.py +0 -0
  37. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/prefabs/__init__.py +0 -0
  38. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/prefabs/concierge.py +0 -0
  39. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/prefabs/faq_bot.py +0 -0
  40. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/prefabs/info_gatherer.py +0 -0
  41. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/prefabs/receptionist.py +0 -0
  42. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/prefabs/survey.py +0 -0
  43. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/schema.json +0 -0
  44. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/utils/__init__.py +0 -0
  45. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/utils/pom_utils.py +0 -0
  46. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/utils/schema_utils.py +0 -0
  47. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/utils/token_generators.py +0 -0
  48. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents/utils/validators.py +0 -0
  49. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents.egg-info/dependency_links.txt +0 -0
  50. {signalwire_agents-0.1.8 → signalwire_agents-0.1.10}/signalwire_agents.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: signalwire_agents
3
- Version: 0.1.8
3
+ Version: 0.1.10
4
4
  Summary: SignalWire AI Agents SDK
5
5
  Author-email: SignalWire Team <info@signalwire.com>
6
6
  Project-URL: Homepage, https://github.com/signalwire/signalwire-ai-agents
@@ -24,6 +24,8 @@ Requires-Dist: setuptools==66.1.1
24
24
  Requires-Dist: signalwire_pom==2.7.1
25
25
  Requires-Dist: structlog==25.3.0
26
26
  Requires-Dist: uvicorn==0.34.2
27
+ Requires-Dist: beautifulsoup4==4.12.3
28
+ Requires-Dist: pytz==2023.3
27
29
  Dynamic: license-file
28
30
 
29
31
  # SignalWire AI Agent SDK
@@ -42,6 +44,55 @@ A Python SDK for creating, hosting, and securing SignalWire AI agents as microse
42
44
  - **State Management**: Persistent conversation state with automatic tracking
43
45
  - **Prefab Archetypes**: Ready-to-use agent types for common scenarios
44
46
  - **Multi-Agent Support**: Host multiple agents on a single server
47
+ - **Modular Skills System**: Add capabilities to agents with simple one-liner calls
48
+
49
+ ## Skills System
50
+
51
+ The SignalWire Agents SDK includes a powerful modular skills system that allows you to add complex capabilities to your agents with simple one-liner calls:
52
+
53
+ ```python
54
+ from signalwire_agents import AgentBase
55
+
56
+ # Create an agent
57
+ agent = AgentBase("My Assistant", route="/assistant")
58
+
59
+ # Add skills with one-liners
60
+ agent.add_skill("web_search") # Web search capability
61
+ agent.add_skill("datetime") # Current date/time info
62
+ agent.add_skill("math") # Mathematical calculations
63
+
64
+ # Configure skills with parameters
65
+ agent.add_skill("web_search", {
66
+ "num_results": 3, # Get 3 search results
67
+ "delay": 0.5 # Small delay between requests
68
+ })
69
+
70
+ # Advanced: Customize SWAIG function properties
71
+ agent.add_skill("math", {
72
+ "swaig_fields": {
73
+ "secure": False, # Override security settings
74
+ "fillers": {"en-US": ["Calculating..."]} # Custom filler phrases
75
+ }
76
+ })
77
+
78
+ agent.serve()
79
+ ```
80
+
81
+ ### Available Built-in Skills
82
+
83
+ - **web_search**: Google Custom Search API integration with web scraping
84
+ - **datetime**: Current date and time with timezone support
85
+ - **math**: Safe mathematical expression evaluation
86
+
87
+ ### Benefits
88
+
89
+ - **One-liner integration**: `agent.add_skill("skill_name")`
90
+ - **Configurable parameters**: `agent.add_skill("skill_name", {"param": "value"})`
91
+ - **Automatic discovery**: Skills are automatically found from the skills directory
92
+ - **Dependency validation**: Clear error messages for missing requirements
93
+ - **Modular architecture**: Skills are self-contained and reusable
94
+
95
+ For detailed documentation, see [Skills System README](docs/SKILLS_SYSTEM_README.md).
45
96
 
46
97
  ## Installation
47
98
 
@@ -14,6 +14,55 @@ A Python SDK for creating, hosting, and securing SignalWire AI agents as microse
14
14
  - **State Management**: Persistent conversation state with automatic tracking
15
15
  - **Prefab Archetypes**: Ready-to-use agent types for common scenarios
16
16
  - **Multi-Agent Support**: Host multiple agents on a single server
17
+ - **Modular Skills System**: Add capabilities to agents with simple one-liner calls
18
+
19
+ ## Skills System
20
+
21
+ The SignalWire Agents SDK includes a powerful modular skills system that allows you to add complex capabilities to your agents with simple one-liner calls:
22
+
23
+ ```python
24
+ from signalwire_agents import AgentBase
25
+
26
+ # Create an agent
27
+ agent = AgentBase("My Assistant", route="/assistant")
28
+
29
+ # Add skills with one-liners
30
+ agent.add_skill("web_search") # Web search capability
31
+ agent.add_skill("datetime") # Current date/time info
32
+ agent.add_skill("math") # Mathematical calculations
33
+
34
+ # Configure skills with parameters
35
+ agent.add_skill("web_search", {
36
+ "num_results": 3, # Get 3 search results
37
+ "delay": 0.5 # Small delay between requests
38
+ })
39
+
40
+ # Advanced: Customize SWAIG function properties
41
+ agent.add_skill("math", {
42
+ "swaig_fields": {
43
+ "secure": False, # Override security settings
44
+ "fillers": {"en-US": ["Calculating..."]} # Custom filler phrases
45
+ }
46
+ })
47
+
48
+ agent.serve()
49
+ ```
50
+
51
+ ### Available Built-in Skills
52
+
53
+ - **web_search**: Google Custom Search API integration with web scraping
54
+ - **datetime**: Current date and time with timezone support
55
+ - **math**: Safe mathematical expression evaluation
56
+
57
+ ### Benefits
58
+
59
+ - **One-liner integration**: `agent.add_skill("skill_name")`
60
+ - **Configurable parameters**: `agent.add_skill("skill_name", {"param": "value"})`
61
+ - **Automatic discovery**: Skills are automatically found from the skills directory
62
+ - **Dependency validation**: Clear error messages for missing requirements
63
+ - **Modular architecture**: Skills are self-contained and reusable
64
+
65
+ For detailed documentation, see [Skills System README](docs/SKILLS_SYSTEM_README.md).
17
66
 
18
67
  ## Installation
19
68
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "signalwire_agents"
7
- version = "0.1.8"
7
+ version = "0.1.10"
8
8
  description = "SignalWire AI Agents SDK"
9
9
  authors = [
10
10
  {name = "SignalWire Team", email = "info@signalwire.com"}
@@ -31,13 +31,15 @@ dependencies = [
31
31
  "signalwire_pom==2.7.1",
32
32
  "structlog==25.3.0",
33
33
  "uvicorn==0.34.2",
34
+ "beautifulsoup4==4.12.3",
35
+ "pytz==2023.3",
34
36
  ]
35
37
 
36
38
  [project.urls]
37
39
  Homepage = "https://github.com/signalwire/signalwire-ai-agents"
38
40
 
39
41
  [tool.setuptools]
40
- packages = ["signalwire_agents", "signalwire_agents.prefabs", "signalwire_agents.utils", "signalwire_agents.core", "signalwire_agents.core.state", "signalwire_agents.core.security"]
42
+ packages = ["signalwire_agents", "signalwire_agents.prefabs", "signalwire_agents.utils", "signalwire_agents.core", "signalwire_agents.core.state", "signalwire_agents.core.security", "signalwire_agents.skills", "signalwire_agents.skills.web_search", "signalwire_agents.skills.datetime", "signalwire_agents.skills.math"]
41
43
  include-package-data = true
42
44
 
43
45
  [tool.setuptools.package-data]
@@ -14,7 +14,7 @@ SignalWire AI Agents SDK
14
14
  A package for building AI agents using SignalWire's AI and SWML capabilities.
15
15
  """
16
16
 
17
- __version__ = "0.1.8"
17
+ __version__ = "0.1.10"
18
18
 
19
19
  # Import core classes for easier access
20
20
  from signalwire_agents.core.agent_base import AgentBase
@@ -23,4 +23,7 @@ from signalwire_agents.core.swml_service import SWMLService
23
23
  from signalwire_agents.core.swml_builder import SWMLBuilder
24
24
  from signalwire_agents.core.state import StateManager, FileStateManager
25
25
 
26
+ # Import skills to trigger discovery
27
+ import signalwire_agents.skills
28
+
26
29
  __all__ = ["AgentBase", "AgentServer", "SWMLService", "SWMLBuilder", "StateManager", "FileStateManager"]
@@ -75,6 +75,7 @@ from signalwire_agents.core.security.session_manager import SessionManager
75
75
  from signalwire_agents.core.state import StateManager, FileStateManager
76
76
  from signalwire_agents.core.swml_service import SWMLService
77
77
  from signalwire_agents.core.swml_handler import AIVerbHandler
78
+ from signalwire_agents.core.skill_manager import SkillManager
78
79
 
79
80
  # Create a logger
80
81
  logger = structlog.get_logger("agent_base")
@@ -404,6 +405,9 @@ class AgentBase(SWMLService):
404
405
 
405
406
  # Dynamic configuration callback
406
407
  self._dynamic_config_callback = None
408
+
409
+ # Initialize skill manager
410
+ self.skill_manager = SkillManager(self)
407
411
 
408
412
  def _process_prompt_sections(self):
409
413
  """
@@ -2792,3 +2796,39 @@ class AgentBase(SWMLService):
2792
2796
  self.log.info("proxy_url_manually_set", proxy_url_base=self._proxy_url_base)
2793
2797
 
2794
2798
  return self
2799
+
2800
+ # ----------------------------------------------------------------------
2801
+ # Skill Management Methods
2802
+ # ----------------------------------------------------------------------
2803
+
2804
+ def add_skill(self, skill_name: str, params: Optional[Dict[str, Any]] = None) -> 'AgentBase':
2805
+ """
2806
+ Add a skill to this agent
2807
+
2808
+ Args:
2809
+ skill_name: Name of the skill to add
2810
+ params: Optional parameters to pass to the skill for configuration
2811
+
2812
+ Returns:
2813
+ Self for method chaining
2814
+
2815
+ Raises:
2816
+ ValueError: If skill not found or failed to load with detailed error message
2817
+ """
2818
+ success, error_message = self.skill_manager.load_skill(skill_name, params=params)
2819
+ if not success:
2820
+ raise ValueError(f"Failed to load skill '{skill_name}': {error_message}")
2821
+ return self
2822
+
2823
+ def remove_skill(self, skill_name: str) -> 'AgentBase':
2824
+ """Remove a skill from this agent"""
2825
+ self.skill_manager.unload_skill(skill_name)
2826
+ return self
2827
+
2828
+ def list_skills(self) -> List[str]:
2829
+ """List currently loaded skills"""
2830
+ return self.skill_manager.list_loaded_skills()
2831
+
2832
+ def has_skill(self, skill_name: str) -> bool:
2833
+ """Check if skill is loaded"""
2834
+ return self.skill_manager.has_skill(skill_name)
@@ -0,0 +1,127 @@
1
+ """
2
+ Copyright (c) 2025 SignalWire
3
+
4
+ This file is part of the SignalWire AI Agents SDK.
5
+
6
+ Licensed under the MIT License.
7
+ See LICENSE file in the project root for full license information.
8
+ """
9
+
10
+ from abc import ABC, abstractmethod
11
+ from typing import List, Dict, Any, TYPE_CHECKING, Optional
12
+ import logging
13
+
14
+ if TYPE_CHECKING:
15
+ from signalwire_agents.core.agent_base import AgentBase
16
+
17
+ class SkillBase(ABC):
18
+ """Abstract base class for all agent skills"""
19
+
20
+ # Subclasses must define these
21
+ SKILL_NAME: str = None # Required: unique identifier
22
+ SKILL_DESCRIPTION: str = None # Required: human-readable description
23
+ SKILL_VERSION: str = "1.0.0" # Semantic version
24
+ REQUIRED_PACKAGES: List[str] = [] # Python packages needed
25
+ REQUIRED_ENV_VARS: List[str] = [] # Environment variables needed
26
+
27
+ def __init__(self, agent: 'AgentBase', params: Optional[Dict[str, Any]] = None):
28
+ if self.SKILL_NAME is None:
29
+ raise ValueError(f"{self.__class__.__name__} must define SKILL_NAME")
30
+ if self.SKILL_DESCRIPTION is None:
31
+ raise ValueError(f"{self.__class__.__name__} must define SKILL_DESCRIPTION")
32
+
33
+ self.agent = agent
34
+ self.params = params or {}
35
+ self.logger = logging.getLogger(f"skill.{self.SKILL_NAME}")
36
+
37
+ # Extract swaig_fields from params for merging into tool definitions
38
+ self.swaig_fields = self.params.pop('swaig_fields', {})
39
+
40
+ @abstractmethod
41
+ def setup(self) -> bool:
42
+ """
43
+ Setup the skill (validate env vars, initialize APIs, etc.)
44
+ Returns True if setup successful, False otherwise
45
+ """
46
+ pass
47
+
48
+ @abstractmethod
49
+ def register_tools(self) -> None:
50
+ """Register SWAIG tools with the agent"""
51
+ pass
52
+
53
+ def define_tool_with_swaig_fields(
54
+ self,
55
+ name: str,
56
+ description: str,
57
+ parameters: Dict[str, Any],
58
+ handler,
59
+ **additional_kwargs
60
+ ):
61
+ """
62
+ Helper method to define a tool with swaig_fields merged in
63
+
64
+ Args:
65
+ name: Function name
66
+ description: Function description
67
+ parameters: Function parameters schema
68
+ handler: Function handler
69
+ **additional_kwargs: Additional keyword arguments for define_tool
70
+
71
+ This method automatically merges the swaig_fields from skill params
72
+ into the tool definition, allowing the skill loader to customize
73
+ SWAIG function properties.
74
+ """
75
+ # Start with the additional kwargs passed to this method
76
+ tool_kwargs = additional_kwargs.copy()
77
+
78
+ # Merge in the swaig_fields from params (swaig_fields take precedence)
79
+ tool_kwargs.update(self.swaig_fields)
80
+
81
+ # Call the agent's define_tool with all parameters
82
+ self.agent.define_tool(
83
+ name=name,
84
+ description=description,
85
+ parameters=parameters,
86
+ handler=handler,
87
+ **tool_kwargs
88
+ )
89
+
90
+ def get_hints(self) -> List[str]:
91
+ """Return speech recognition hints for this skill"""
92
+ return []
93
+
94
+ def get_global_data(self) -> Dict[str, Any]:
95
+ """Return data to add to agent's global context"""
96
+ return {}
97
+
98
+ def get_prompt_sections(self) -> List[Dict[str, Any]]:
99
+ """Return prompt sections to add to agent"""
100
+ return []
101
+
102
+ def cleanup(self) -> None:
103
+ """Cleanup when skill is removed or agent shuts down"""
104
+ pass
105
+
106
+ def validate_env_vars(self) -> bool:
107
+ """Check if all required environment variables are set"""
108
+ import os
109
+ missing = [var for var in self.REQUIRED_ENV_VARS if not os.getenv(var)]
110
+ if missing:
111
+ self.logger.error(f"Missing required environment variables: {missing}")
112
+ return False
113
+ return True
114
+
115
+ def validate_packages(self) -> bool:
116
+ """Check if all required packages are available"""
117
+ import importlib
118
+ missing = []
119
+ for package in self.REQUIRED_PACKAGES:
120
+ try:
121
+ importlib.import_module(package)
122
+ except ImportError:
123
+ missing.append(package)
124
+ if missing:
125
+ self.logger.error(f"Missing required packages: {missing}")
126
+ return False
127
+ return True
@@ -0,0 +1,136 @@
1
+ """
2
+ Copyright (c) 2025 SignalWire
3
+
4
+ This file is part of the SignalWire AI Agents SDK.
5
+
6
+ Licensed under the MIT License.
7
+ See LICENSE file in the project root for full license information.
8
+ """
9
+
10
+ from typing import Dict, List, Type, Any, Optional
11
+ import logging
12
+ from signalwire_agents.core.skill_base import SkillBase
13
+
14
+ class SkillManager:
15
+ """Manages loading and lifecycle of agent skills"""
16
+
17
+ def __init__(self, agent):
18
+ self.agent = agent
19
+ self.loaded_skills: Dict[str, SkillBase] = {}
20
+ self.logger = logging.getLogger("skill_manager")
21
+
22
+ def load_skill(self, skill_name: str, skill_class: Type[SkillBase] = None, params: Optional[Dict[str, Any]] = None) -> tuple[bool, str]:
23
+ """
24
+ Load and setup a skill by name
25
+
26
+ Args:
27
+ skill_name: Name of the skill to load
28
+ skill_class: Optional skill class (if not provided, will try to find it)
29
+ params: Optional parameters to pass to the skill
30
+
31
+ Returns:
32
+ tuple: (success, error_message) - error_message is empty string if successful
33
+ """
34
+ if skill_name in self.loaded_skills:
35
+ self.logger.warning(f"Skill '{skill_name}' is already loaded")
36
+ return True, ""
37
+
38
+ # Get skill class from registry if not provided
39
+ if skill_class is None:
40
+ try:
41
+ from signalwire_agents.skills.registry import skill_registry
42
+ skill_class = skill_registry.get_skill_class(skill_name)
43
+ if skill_class is None:
44
+ error_msg = f"Skill '{skill_name}' not found in registry"
45
+ self.logger.error(error_msg)
46
+ return False, error_msg
47
+ except ImportError:
48
+ error_msg = f"Skills registry not available. Cannot load skill '{skill_name}'"
49
+ self.logger.error(error_msg)
50
+ return False, error_msg
51
+
52
+ try:
53
+ # Create skill instance with parameters
54
+ skill_instance = skill_class(self.agent, params)
55
+
56
+ # Validate environment variables with specific error details
57
+ import os
58
+ missing_env_vars = [var for var in skill_instance.REQUIRED_ENV_VARS if not os.getenv(var)]
59
+ if missing_env_vars:
60
+ error_msg = f"Missing required environment variables: {missing_env_vars}"
61
+ self.logger.error(error_msg)
62
+ return False, error_msg
63
+
64
+ # Validate packages with specific error details
65
+ import importlib
66
+ missing_packages = []
67
+ for package in skill_instance.REQUIRED_PACKAGES:
68
+ try:
69
+ importlib.import_module(package)
70
+ except ImportError:
71
+ missing_packages.append(package)
72
+ if missing_packages:
73
+ error_msg = f"Missing required packages: {missing_packages}"
74
+ self.logger.error(error_msg)
75
+ return False, error_msg
76
+
77
+ # Setup the skill
78
+ if not skill_instance.setup():
79
+ error_msg = f"Failed to setup skill '{skill_name}'"
80
+ self.logger.error(error_msg)
81
+ return False, error_msg
82
+
83
+ # Register tools with agent
84
+ skill_instance.register_tools()
85
+
86
+ # Add hints and global data to agent
87
+ hints = skill_instance.get_hints()
88
+ if hints:
89
+ self.agent.add_hints(hints)
90
+
91
+ global_data = skill_instance.get_global_data()
92
+ if global_data:
93
+ self.agent.update_global_data(global_data)
94
+
95
+ # Add prompt sections
96
+ prompt_sections = skill_instance.get_prompt_sections()
97
+ for section in prompt_sections:
98
+ self.agent.prompt_add_section(**section)
99
+
100
+ # Store loaded skill
101
+ self.loaded_skills[skill_name] = skill_instance
102
+ self.logger.info(f"Successfully loaded skill '{skill_name}'")
103
+ return True, ""
104
+
105
+ except Exception as e:
106
+ error_msg = f"Error loading skill '{skill_name}': {e}"
107
+ self.logger.error(error_msg)
108
+ return False, error_msg
109
+
110
+ def unload_skill(self, skill_name: str) -> bool:
111
+ """Unload a skill and cleanup"""
112
+ if skill_name not in self.loaded_skills:
113
+ self.logger.warning(f"Skill '{skill_name}' is not loaded")
114
+ return False
115
+
116
+ try:
117
+ skill_instance = self.loaded_skills[skill_name]
118
+ skill_instance.cleanup()
119
+ del self.loaded_skills[skill_name]
120
+ self.logger.info(f"Successfully unloaded skill '{skill_name}'")
121
+ return True
122
+ except Exception as e:
123
+ self.logger.error(f"Error unloading skill '{skill_name}': {e}")
124
+ return False
125
+
126
+ def list_loaded_skills(self) -> List[str]:
127
+ """List names of currently loaded skills"""
128
+ return list(self.loaded_skills.keys())
129
+
130
+ def has_skill(self, skill_name: str) -> bool:
131
+ """Check if skill is currently loaded"""
132
+ return skill_name in self.loaded_skills
133
+
134
+ def get_skill(self, skill_name: str) -> Optional[SkillBase]:
135
+ """Get a loaded skill instance by name"""
136
+ return self.loaded_skills.get(skill_name)
@@ -0,0 +1,14 @@
1
+ """
2
+ SignalWire Agent Skills Package
3
+
4
+ This package contains built-in skills for SignalWire agents.
5
+ Skills are automatically discovered from subdirectories.
6
+ """
7
+
8
+ # Import the registry to make it available
9
+ from .registry import skill_registry
10
+
11
+ # Trigger skill discovery on import
12
+ skill_registry.discover_skills()
13
+
14
+ __all__ = ["skill_registry"]
@@ -0,0 +1 @@
1
+ """DateTime Skill for SignalWire Agents"""
@@ -0,0 +1,109 @@
1
+ """
2
+ Copyright (c) 2025 SignalWire
3
+
4
+ This file is part of the SignalWire AI Agents SDK.
5
+
6
+ Licensed under the MIT License.
7
+ See LICENSE file in the project root for full license information.
8
+ """
9
+
10
+ from datetime import datetime, timezone
11
+ import pytz
12
+ from typing import List, Dict, Any
13
+
14
+ from signalwire_agents.core.skill_base import SkillBase
15
+ from signalwire_agents.core.function_result import SwaigFunctionResult
16
+
17
+ class DateTimeSkill(SkillBase):
18
+ """Provides current date, time, and timezone information"""
19
+
20
+ SKILL_NAME = "datetime"
21
+ SKILL_DESCRIPTION = "Get current date, time, and timezone information"
22
+ SKILL_VERSION = "1.0.0"
23
+ REQUIRED_PACKAGES = ["pytz"]
24
+ REQUIRED_ENV_VARS = []
25
+
26
+ def setup(self) -> bool:
27
+ """Setup the datetime skill"""
28
+ return self.validate_packages()
29
+
30
+ def register_tools(self) -> None:
31
+ """Register datetime tools with the agent"""
32
+
33
+ self.define_tool_with_swaig_fields(
34
+ name="get_current_time",
35
+ description="Get the current time, optionally in a specific timezone",
36
+ parameters={
37
+ "timezone": {
38
+ "type": "string",
39
+ "description": "Timezone name (e.g., 'America/New_York', 'Europe/London'). Defaults to UTC."
40
+ }
41
+ },
42
+ handler=self._get_time_handler
43
+ )
44
+
45
+ self.define_tool_with_swaig_fields(
46
+ name="get_current_date",
47
+ description="Get the current date",
48
+ parameters={
49
+ "timezone": {
50
+ "type": "string",
51
+ "description": "Timezone name for the date. Defaults to UTC."
52
+ }
53
+ },
54
+ handler=self._get_date_handler
55
+ )
56
+
57
+ def _get_time_handler(self, args, raw_data):
58
+ """Handler for get_current_time tool"""
59
+ timezone_name = args.get("timezone", "UTC")
60
+
61
+ try:
62
+ if timezone_name.upper() == "UTC":
63
+ tz = timezone.utc
64
+ else:
65
+ tz = pytz.timezone(timezone_name)
66
+
67
+ now = datetime.now(tz)
68
+ time_str = now.strftime("%I:%M:%S %p %Z")
69
+
70
+ return SwaigFunctionResult(f"The current time is {time_str}")
71
+
72
+ except Exception as e:
73
+ return SwaigFunctionResult(f"Error getting time: {str(e)}")
74
+
75
+ def _get_date_handler(self, args, raw_data):
76
+ """Handler for get_current_date tool"""
77
+ timezone_name = args.get("timezone", "UTC")
78
+
79
+ try:
80
+ if timezone_name.upper() == "UTC":
81
+ tz = timezone.utc
82
+ else:
83
+ tz = pytz.timezone(timezone_name)
84
+
85
+ now = datetime.now(tz)
86
+ date_str = now.strftime("%A, %B %d, %Y")
87
+
88
+ return SwaigFunctionResult(f"Today's date is {date_str}")
89
+
90
+ except Exception as e:
91
+ return SwaigFunctionResult(f"Error getting date: {str(e)}")
92
+
93
+ def get_hints(self) -> List[str]:
94
+ """Return speech recognition hints"""
95
+ return ["time", "date", "today", "now", "current", "timezone"]
96
+
97
+ def get_prompt_sections(self) -> List[Dict[str, Any]]:
98
+ """Return prompt sections to add to agent"""
99
+ return [
100
+ {
101
+ "title": "Date and Time Information",
102
+ "body": "You can provide current date and time information.",
103
+ "bullets": [
104
+ "Use get_current_time to tell users what time it is",
105
+ "Use get_current_date to tell users today's date",
106
+ "Both tools support different timezones"
107
+ ]
108
+ }
109
+ ]
@@ -0,0 +1 @@
1
+ """Math Skill for SignalWire Agents"""
@@ -0,0 +1,88 @@
1
+ """
2
+ Copyright (c) 2025 SignalWire
3
+
4
+ This file is part of the SignalWire AI Agents SDK.
5
+
6
+ Licensed under the MIT License.
7
+ See LICENSE file in the project root for full license information.
8
+ """
9
+
10
+ import re
11
+ from typing import List, Dict, Any
12
+
13
+ from signalwire_agents.core.skill_base import SkillBase
14
+ from signalwire_agents.core.function_result import SwaigFunctionResult
15
+
16
+ class MathSkill(SkillBase):
17
+ """Provides basic mathematical calculation capabilities"""
18
+
19
+ SKILL_NAME = "math"
20
+ SKILL_DESCRIPTION = "Perform basic mathematical calculations"
21
+ SKILL_VERSION = "1.0.0"
22
+ REQUIRED_PACKAGES = []
23
+ REQUIRED_ENV_VARS = []
24
+
25
+ def setup(self) -> bool:
26
+ """Setup the math skill"""
27
+ return True
28
+
29
+ def register_tools(self) -> None:
30
+ """Register math tools with the agent"""
31
+
32
+ self.define_tool_with_swaig_fields(
33
+ name="calculate",
34
+ description="Perform a mathematical calculation with basic operations (+, -, *, /, %, **)",
35
+ parameters={
36
+ "expression": {
37
+ "type": "string",
38
+ "description": "Mathematical expression to evaluate (e.g., '2 + 3 * 4', '(10 + 5) / 3')"
39
+ }
40
+ },
41
+ handler=self._calculate_handler
42
+ )
43
+
44
+ def _calculate_handler(self, args, raw_data):
45
+ """Handler for calculate tool"""
46
+ expression = args.get("expression", "").strip()
47
+
48
+ if not expression:
49
+ return SwaigFunctionResult("Please provide a mathematical expression to calculate.")
50
+
51
+ # Security: only allow safe mathematical operations
52
+ safe_chars = re.compile(r'^[0-9+\-*/().\s%**]+$')
53
+ if not safe_chars.match(expression):
54
+ return SwaigFunctionResult(
55
+ "Invalid expression. Only numbers and basic math operators (+, -, *, /, %, **, parentheses) are allowed."
56
+ )
57
+
58
+ try:
59
+ # Evaluate the expression safely
60
+ result = eval(expression, {"__builtins__": {}}, {})
61
+
62
+ return SwaigFunctionResult(f"{expression} = {result}")
63
+
64
+ except ZeroDivisionError:
65
+ return SwaigFunctionResult("Error: Division by zero is not allowed.")
66
+ except Exception as e:
67
+ return SwaigFunctionResult(f"Error calculating '{expression}': Invalid expression")
68
+
69
+ def get_hints(self) -> List[str]:
70
+ """Return speech recognition hints"""
71
+ return [
72
+ "calculate", "math", "plus", "minus", "times", "multiply",
73
+ "divide", "equals", "percent", "power", "squared"
74
+ ]
75
+
76
+ def get_prompt_sections(self) -> List[Dict[str, Any]]:
77
+ """Return prompt sections to add to agent"""
78
+ return [
79
+ {
80
+ "title": "Mathematical Calculations",
81
+ "body": "You can perform mathematical calculations for users.",
82
+ "bullets": [
83
+ "Use the calculate tool for any math expressions",
84
+ "Supports basic operations: +, -, *, /, %, ** (power)",
85
+ "Can handle parentheses for complex expressions"
86
+ ]
87
+ }
88
+ ]
@@ -0,0 +1,98 @@
1
+ """
2
+ Copyright (c) 2025 SignalWire
3
+
4
+ This file is part of the SignalWire AI Agents SDK.
5
+
6
+ Licensed under the MIT License.
7
+ See LICENSE file in the project root for full license information.
8
+ """
9
+
10
+ import os
11
+ import importlib
12
+ import importlib.util
13
+ import inspect
14
+ from typing import Dict, List, Type, Optional
15
+ from pathlib import Path
16
+ import logging
17
+
18
+ from signalwire_agents.core.skill_base import SkillBase
19
+
20
+ class SkillRegistry:
21
+ """Global registry for discovering and managing skills"""
22
+
23
+ def __init__(self):
24
+ self._skills: Dict[str, Type[SkillBase]] = {}
25
+ self.logger = logging.getLogger("skill_registry")
26
+ self._discovered = False
27
+
28
+ def discover_skills(self) -> None:
29
+ """Discover skills from the skills directory"""
30
+ if self._discovered:
31
+ return
32
+
33
+ # Get the skills directory path
34
+ skills_dir = Path(__file__).parent
35
+
36
+ # Scan for skill directories
37
+ for item in skills_dir.iterdir():
38
+ if item.is_dir() and not item.name.startswith('__'):
39
+ self._load_skill_from_directory(item)
40
+
41
+ self._discovered = True
42
+ self.logger.info(f"Discovered {len(self._skills)} skills")
43
+
44
+ def _load_skill_from_directory(self, skill_dir: Path) -> None:
45
+ """Load a skill from a directory"""
46
+ skill_file = skill_dir / "skill.py"
47
+ if not skill_file.exists():
48
+ return
49
+
50
+ try:
51
+ # Import the skill module
52
+ module_name = f"signalwire_agents.skills.{skill_dir.name}.skill"
53
+ spec = importlib.util.spec_from_file_location(module_name, skill_file)
54
+ module = importlib.util.module_from_spec(spec)
55
+ spec.loader.exec_module(module)
56
+
57
+ # Find SkillBase subclasses in the module
58
+ for name, obj in inspect.getmembers(module):
59
+ if (inspect.isclass(obj) and
60
+ issubclass(obj, SkillBase) and
61
+ obj != SkillBase and
62
+ obj.SKILL_NAME is not None):
63
+
64
+ self.register_skill(obj)
65
+
66
+ except Exception as e:
67
+ self.logger.error(f"Failed to load skill from {skill_dir}: {e}")
68
+
69
+ def register_skill(self, skill_class: Type[SkillBase]) -> None:
70
+ """Register a skill class"""
71
+ if skill_class.SKILL_NAME in self._skills:
72
+ self.logger.warning(f"Skill '{skill_class.SKILL_NAME}' already registered")
73
+ return
74
+
75
+ self._skills[skill_class.SKILL_NAME] = skill_class
76
+ self.logger.debug(f"Registered skill '{skill_class.SKILL_NAME}'")
77
+
78
+ def get_skill_class(self, skill_name: str) -> Optional[Type[SkillBase]]:
79
+ """Get skill class by name"""
80
+ self.discover_skills() # Ensure skills are discovered
81
+ return self._skills.get(skill_name)
82
+
83
+ def list_skills(self) -> List[Dict[str, str]]:
84
+ """List all registered skills with metadata"""
85
+ self.discover_skills()
86
+ return [
87
+ {
88
+ "name": skill_class.SKILL_NAME,
89
+ "description": skill_class.SKILL_DESCRIPTION,
90
+ "version": skill_class.SKILL_VERSION,
91
+ "required_packages": skill_class.REQUIRED_PACKAGES,
92
+ "required_env_vars": skill_class.REQUIRED_ENV_VARS
93
+ }
94
+ for skill_class in self._skills.values()
95
+ ]
96
+
97
+ # Global registry instance
98
+ skill_registry = SkillRegistry()
@@ -0,0 +1 @@
1
+ """Web Search Skill for SignalWire Agents"""
@@ -0,0 +1,229 @@
1
+ """
2
+ Copyright (c) 2025 SignalWire
3
+
4
+ This file is part of the SignalWire AI Agents SDK.
5
+
6
+ Licensed under the MIT License.
7
+ See LICENSE file in the project root for full license information.
8
+ """
9
+
10
+ import os
11
+ import requests
12
+ import time
13
+ from urllib.parse import urljoin, urlparse
14
+ from bs4 import BeautifulSoup
15
+ import json
16
+ from typing import Optional, List, Dict, Any
17
+
18
+ from signalwire_agents.core.skill_base import SkillBase
19
+ from signalwire_agents.core.function_result import SwaigFunctionResult
20
+
21
+ class GoogleSearchScraper:
22
+ """Google Search and Web Scraping functionality"""
23
+
24
+ def __init__(self, api_key: str, search_engine_id: str):
25
+ self.api_key = api_key
26
+ self.search_engine_id = search_engine_id
27
+ self.session = requests.Session()
28
+ self.session.headers.update({
29
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
30
+ })
31
+
32
+ def search_google(self, query: str, num_results: int = 5) -> list:
33
+ """Search Google using Custom Search JSON API"""
34
+ url = "https://www.googleapis.com/customsearch/v1"
35
+
36
+ params = {
37
+ 'key': self.api_key,
38
+ 'cx': self.search_engine_id,
39
+ 'q': query,
40
+ 'num': min(num_results, 10)
41
+ }
42
+
43
+ try:
44
+ response = self.session.get(url, params=params)
45
+ response.raise_for_status()
46
+ data = response.json()
47
+
48
+ if 'items' not in data:
49
+ return []
50
+
51
+ results = []
52
+ for item in data['items'][:num_results]:
53
+ results.append({
54
+ 'title': item.get('title', ''),
55
+ 'url': item.get('link', ''),
56
+ 'snippet': item.get('snippet', '')
57
+ })
58
+
59
+ return results
60
+
61
+ except Exception as e:
62
+ return []
63
+
64
+ def extract_text_from_url(self, url: str, timeout: int = 10) -> str:
65
+ """Scrape a URL and extract readable text content"""
66
+ try:
67
+ response = self.session.get(url, timeout=timeout)
68
+ response.raise_for_status()
69
+
70
+ soup = BeautifulSoup(response.content, 'html.parser')
71
+
72
+ # Remove unwanted elements
73
+ for script in soup(["script", "style", "nav", "footer", "header", "aside"]):
74
+ script.decompose()
75
+
76
+ text = soup.get_text()
77
+
78
+ # Clean up the text
79
+ lines = (line.strip() for line in text.splitlines())
80
+ chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
81
+ text = ' '.join(chunk for chunk in chunks if chunk)
82
+
83
+ # Limit text length
84
+ if len(text) > 2000:
85
+ text = text[:2000] + "... [Content truncated]"
86
+
87
+ return text
88
+
89
+ except Exception as e:
90
+ return ""
91
+
92
+ def search_and_scrape(self, query: str, num_results: int = 3, delay: float = 0.5) -> str:
93
+ """Main function: search Google and scrape the resulting pages"""
94
+ search_results = self.search_google(query, num_results)
95
+
96
+ if not search_results:
97
+ return f"No search results found for query: {query}"
98
+
99
+ all_text = []
100
+
101
+ for i, result in enumerate(search_results, 1):
102
+ text_content = f"=== RESULT {i} ===\n"
103
+ text_content += f"Title: {result['title']}\n"
104
+ text_content += f"URL: {result['url']}\n"
105
+ text_content += f"Snippet: {result['snippet']}\n"
106
+ text_content += f"Content:\n"
107
+
108
+ page_text = self.extract_text_from_url(result['url'])
109
+
110
+ if page_text:
111
+ text_content += page_text
112
+ else:
113
+ text_content += "Failed to extract content from this page."
114
+
115
+ text_content += f"\n{'='*50}\n\n"
116
+ all_text.append(text_content)
117
+
118
+ if i < len(search_results):
119
+ time.sleep(delay)
120
+
121
+ return '\n'.join(all_text)
122
+
123
+
124
+ class WebSearchSkill(SkillBase):
125
+ """Web search capability using Google Custom Search API"""
126
+
127
+ SKILL_NAME = "web_search"
128
+ SKILL_DESCRIPTION = "Search the web for information using Google Custom Search API"
129
+ SKILL_VERSION = "1.0.0"
130
+ REQUIRED_PACKAGES = ["bs4", "requests"]
131
+ REQUIRED_ENV_VARS = ["GOOGLE_SEARCH_API_KEY", "GOOGLE_SEARCH_ENGINE_ID"]
132
+
133
+ def setup(self) -> bool:
134
+ """Setup the web search skill"""
135
+ if not self.validate_env_vars() or not self.validate_packages():
136
+ return False
137
+
138
+ # Set default parameters
139
+ self.default_num_results = self.params.get('num_results', 1)
140
+ self.default_delay = self.params.get('delay', 0)
141
+
142
+ # Initialize the search scraper
143
+ self.search_scraper = GoogleSearchScraper(
144
+ api_key=os.getenv('GOOGLE_SEARCH_API_KEY'),
145
+ search_engine_id=os.getenv('GOOGLE_SEARCH_ENGINE_ID')
146
+ )
147
+
148
+ return True
149
+
150
+ def register_tools(self) -> None:
151
+ """Register web search tool with the agent"""
152
+ self.define_tool_with_swaig_fields(
153
+ name="web_search",
154
+ description="Search the web for information on any topic and return detailed results with content from multiple sources",
155
+ parameters={
156
+ "query": {
157
+ "type": "string",
158
+ "description": "The search query - what you want to find information about"
159
+ }
160
+ },
161
+ handler=self._web_search_handler
162
+ )
163
+
164
+ def _web_search_handler(self, args, raw_data):
165
+ """Handler for web search tool"""
166
+ query = args.get("query", "").strip()
167
+
168
+ if not query:
169
+ return SwaigFunctionResult(
170
+ "Please provide a search query. What would you like me to search for?"
171
+ )
172
+
173
+ # Use the configured number of results (no longer a parameter)
174
+ num_results = self.default_num_results
175
+
176
+ self.logger.info(f"Web search requested: '{query}' ({num_results} results)")
177
+
178
+ # Perform the search
179
+ try:
180
+ search_results = self.search_scraper.search_and_scrape(
181
+ query=query,
182
+ num_results=num_results,
183
+ delay=self.default_delay
184
+ )
185
+
186
+ if not search_results or "No search results found" in search_results:
187
+ return SwaigFunctionResult(
188
+ f"I couldn't find any results for '{query}'. "
189
+ "This might be due to a very specific query or temporary issues. "
190
+ "Try rephrasing your search or asking about a different topic."
191
+ )
192
+
193
+ response = f"I found {num_results} results for '{query}':\n\n{search_results}"
194
+ return SwaigFunctionResult(response)
195
+
196
+ except Exception as e:
197
+ self.logger.error(f"Error performing web search: {e}")
198
+ return SwaigFunctionResult(
199
+ "Sorry, I encountered an error while searching. Please try again later."
200
+ )
201
+
202
+ def get_hints(self) -> List[str]:
203
+ """Return speech recognition hints"""
204
+ return [
205
+ "Google", "search", "internet", "web", "information",
206
+ "find", "look up", "research", "query", "results"
207
+ ]
208
+
209
+ def get_global_data(self) -> Dict[str, Any]:
210
+ """Return global data for agent context"""
211
+ return {
212
+ "web_search_enabled": True,
213
+ "search_provider": "Google Custom Search"
214
+ }
215
+
216
+ def get_prompt_sections(self) -> List[Dict[str, Any]]:
217
+ """Return prompt sections to add to agent"""
218
+ return [
219
+ {
220
+ "title": "Web Search Capability",
221
+ "body": "You can search the internet for current, accurate information on any topic.",
222
+ "bullets": [
223
+ "Use the web_search tool when users ask for information you need to look up",
224
+ "Search for news, current events, product information, or any current data",
225
+ "Summarize search results in a clear, helpful way",
226
+ "Include relevant URLs so users can read more if interested"
227
+ ]
228
+ }
229
+ ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: signalwire_agents
3
- Version: 0.1.8
3
+ Version: 0.1.10
4
4
  Summary: SignalWire AI Agents SDK
5
5
  Author-email: SignalWire Team <info@signalwire.com>
6
6
  Project-URL: Homepage, https://github.com/signalwire/signalwire-ai-agents
@@ -24,6 +24,8 @@ Requires-Dist: setuptools==66.1.1
24
24
  Requires-Dist: signalwire_pom==2.7.1
25
25
  Requires-Dist: structlog==25.3.0
26
26
  Requires-Dist: uvicorn==0.34.2
27
+ Requires-Dist: beautifulsoup4==4.12.3
28
+ Requires-Dist: pytz==2023.3
27
29
  Dynamic: license-file
28
30
 
29
31
  # SignalWire AI Agent SDK
@@ -42,6 +44,55 @@ A Python SDK for creating, hosting, and securing SignalWire AI agents as microse
42
44
  - **State Management**: Persistent conversation state with automatic tracking
43
45
  - **Prefab Archetypes**: Ready-to-use agent types for common scenarios
44
46
  - **Multi-Agent Support**: Host multiple agents on a single server
47
+ - **Modular Skills System**: Add capabilities to agents with simple one-liner calls
48
+
49
+ ## Skills System
50
+
51
+ The SignalWire Agents SDK includes a powerful modular skills system that allows you to add complex capabilities to your agents with simple one-liner calls:
52
+
53
+ ```python
54
+ from signalwire_agents import AgentBase
55
+
56
+ # Create an agent
57
+ agent = AgentBase("My Assistant", route="/assistant")
58
+
59
+ # Add skills with one-liners
60
+ agent.add_skill("web_search") # Web search capability
61
+ agent.add_skill("datetime") # Current date/time info
62
+ agent.add_skill("math") # Mathematical calculations
63
+
64
+ # Configure skills with parameters
65
+ agent.add_skill("web_search", {
66
+ "num_results": 3, # Get 3 search results
67
+ "delay": 0.5 # Small delay between requests
68
+ })
69
+
70
+ # Advanced: Customize SWAIG function properties
71
+ agent.add_skill("math", {
72
+ "swaig_fields": {
73
+ "secure": False, # Override security settings
74
+ "fillers": {"en-US": ["Calculating..."]} # Custom filler phrases
75
+ }
76
+ })
77
+
78
+ agent.serve()
79
+ ```
80
+
81
+ ### Available Built-in Skills
82
+
83
+ - **web_search**: Google Custom Search API integration with web scraping
84
+ - **datetime**: Current date and time with timezone support
85
+ - **math**: Safe mathematical expression evaluation
86
+
87
+ ### Benefits
88
+
89
+ - **One-liner integration**: `agent.add_skill("skill_name")`
90
+ - **Configurable parameters**: `agent.add_skill("skill_name", {"param": "value"})`
91
+ - **Automatic discovery**: Skills are automatically found from the skills directory
92
+ - **Dependency validation**: Clear error messages for missing requirements
93
+ - **Modular architecture**: Skills are self-contained and reusable
94
+
95
+ For detailed documentation, see [Skills System README](docs/SKILLS_SYSTEM_README.md).
45
96
 
46
97
  ## Installation
47
98
 
@@ -15,6 +15,8 @@ signalwire_agents/core/__init__.py
15
15
  signalwire_agents/core/agent_base.py
16
16
  signalwire_agents/core/function_result.py
17
17
  signalwire_agents/core/pom_builder.py
18
+ signalwire_agents/core/skill_base.py
19
+ signalwire_agents/core/skill_manager.py
18
20
  signalwire_agents/core/swaig_function.py
19
21
  signalwire_agents/core/swml_builder.py
20
22
  signalwire_agents/core/swml_handler.py
@@ -31,6 +33,14 @@ signalwire_agents/prefabs/faq_bot.py
31
33
  signalwire_agents/prefabs/info_gatherer.py
32
34
  signalwire_agents/prefabs/receptionist.py
33
35
  signalwire_agents/prefabs/survey.py
36
+ signalwire_agents/skills/__init__.py
37
+ signalwire_agents/skills/registry.py
38
+ signalwire_agents/skills/datetime/__init__.py
39
+ signalwire_agents/skills/datetime/skill.py
40
+ signalwire_agents/skills/math/__init__.py
41
+ signalwire_agents/skills/math/skill.py
42
+ signalwire_agents/skills/web_search/__init__.py
43
+ signalwire_agents/skills/web_search/skill.py
34
44
  signalwire_agents/utils/__init__.py
35
45
  signalwire_agents/utils/pom_utils.py
36
46
  signalwire_agents/utils/schema_utils.py
@@ -6,3 +6,5 @@ setuptools==66.1.1
6
6
  signalwire_pom==2.7.1
7
7
  structlog==25.3.0
8
8
  uvicorn==0.34.2
9
+ beautifulsoup4==4.12.3
10
+ pytz==2023.3