signalwire-agents 0.1.34__py3-none-any.whl → 0.1.36__py3-none-any.whl
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.
- signalwire_agents/__init__.py +66 -2
- signalwire_agents/core/agent_base.py +15 -0
- signalwire_agents/core/skill_base.py +64 -1
- signalwire_agents/skills/datetime/skill.py +16 -1
- signalwire_agents/skills/math/skill.py +16 -1
- signalwire_agents/skills/registry.py +312 -9
- signalwire_agents/skills/web_search/skill.py +60 -1
- {signalwire_agents-0.1.34.dist-info → signalwire_agents-0.1.36.dist-info}/METADATA +11 -7
- {signalwire_agents-0.1.34.dist-info → signalwire_agents-0.1.36.dist-info}/RECORD +13 -13
- {signalwire_agents-0.1.34.dist-info → signalwire_agents-0.1.36.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.34.dist-info → signalwire_agents-0.1.36.dist-info}/entry_points.txt +0 -0
- {signalwire_agents-0.1.34.dist-info → signalwire_agents-0.1.36.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.34.dist-info → signalwire_agents-0.1.36.dist-info}/top_level.txt +0 -0
signalwire_agents/__init__.py
CHANGED
@@ -18,7 +18,7 @@ A package for building AI agents using SignalWire's AI and SWML capabilities.
|
|
18
18
|
from .core.logging_config import configure_logging
|
19
19
|
configure_logging()
|
20
20
|
|
21
|
-
__version__ = "0.1.
|
21
|
+
__version__ = "0.1.36"
|
22
22
|
|
23
23
|
# Import core classes for easier access
|
24
24
|
from .core.agent_base import AgentBase
|
@@ -62,6 +62,67 @@ def list_skills(*args, **kwargs):
|
|
62
62
|
except ImportError:
|
63
63
|
raise NotImplementedError("CLI helpers not available")
|
64
64
|
|
65
|
+
def list_skills_with_params():
|
66
|
+
"""
|
67
|
+
Get complete schema for all available skills including parameter metadata
|
68
|
+
|
69
|
+
This function returns a comprehensive schema for all available skills,
|
70
|
+
including their metadata and parameter definitions. This is useful for
|
71
|
+
GUI configuration tools, API documentation, or programmatic skill discovery.
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
Dict[str, Dict[str, Any]]: Complete skill schema where keys are skill names
|
75
|
+
|
76
|
+
Example:
|
77
|
+
>>> schema = list_skills_with_params()
|
78
|
+
>>> print(schema['web_search']['parameters']['api_key'])
|
79
|
+
{
|
80
|
+
'type': 'string',
|
81
|
+
'description': 'Google Custom Search API key',
|
82
|
+
'required': True,
|
83
|
+
'hidden': True,
|
84
|
+
'env_var': 'GOOGLE_SEARCH_API_KEY'
|
85
|
+
}
|
86
|
+
"""
|
87
|
+
from signalwire_agents.skills.registry import skill_registry
|
88
|
+
return skill_registry.get_all_skills_schema()
|
89
|
+
|
90
|
+
def register_skill(skill_class):
|
91
|
+
"""
|
92
|
+
Register a custom skill class
|
93
|
+
|
94
|
+
This allows third-party code to register skill classes directly without
|
95
|
+
requiring them to be in a specific directory structure.
|
96
|
+
|
97
|
+
Args:
|
98
|
+
skill_class: A class that inherits from SkillBase
|
99
|
+
|
100
|
+
Example:
|
101
|
+
>>> from my_custom_skills import MyWeatherSkill
|
102
|
+
>>> register_skill(MyWeatherSkill)
|
103
|
+
>>> # Now you can use it in agents:
|
104
|
+
>>> agent.add_skill('my_weather')
|
105
|
+
"""
|
106
|
+
from signalwire_agents.skills.registry import skill_registry
|
107
|
+
return skill_registry.register_skill(skill_class)
|
108
|
+
|
109
|
+
def add_skill_directory(path):
|
110
|
+
"""
|
111
|
+
Add a directory to search for skills
|
112
|
+
|
113
|
+
This allows third-party skill collections to be registered by path.
|
114
|
+
Skills in these directories should follow the same structure as built-in skills.
|
115
|
+
|
116
|
+
Args:
|
117
|
+
path: Path to directory containing skill subdirectories
|
118
|
+
|
119
|
+
Example:
|
120
|
+
>>> add_skill_directory('/opt/custom_skills')
|
121
|
+
>>> # Now agent.add_skill('my_custom_skill') will search in this directory
|
122
|
+
"""
|
123
|
+
from signalwire_agents.skills.registry import skill_registry
|
124
|
+
return skill_registry.add_skill_directory(path)
|
125
|
+
|
65
126
|
__all__ = [
|
66
127
|
"AgentBase",
|
67
128
|
"AgentServer",
|
@@ -78,5 +139,8 @@ __all__ = [
|
|
78
139
|
"create_simple_context",
|
79
140
|
"start_agent",
|
80
141
|
"run_agent",
|
81
|
-
"list_skills"
|
142
|
+
"list_skills",
|
143
|
+
"list_skills_with_params",
|
144
|
+
"register_skill",
|
145
|
+
"add_skill_directory"
|
82
146
|
]
|
@@ -692,6 +692,13 @@ class AgentBase(
|
|
692
692
|
# Add answer verb with auto-answer enabled
|
693
693
|
agent_to_use.add_verb("answer", {})
|
694
694
|
|
695
|
+
# Add recording if enabled
|
696
|
+
if agent_to_use._record_call:
|
697
|
+
agent_to_use.add_verb("record_call", {
|
698
|
+
"format": agent_to_use._record_format,
|
699
|
+
"stereo": agent_to_use._record_stereo
|
700
|
+
})
|
701
|
+
|
695
702
|
# Use the AI verb handler to build and validate the AI verb config
|
696
703
|
ai_config = {}
|
697
704
|
|
@@ -811,6 +818,14 @@ class AgentBase(
|
|
811
818
|
# Clear and rebuild the document with the modified AI config
|
812
819
|
agent_to_use.reset_document()
|
813
820
|
agent_to_use.add_verb("answer", {})
|
821
|
+
|
822
|
+
# Add recording if enabled
|
823
|
+
if agent_to_use._record_call:
|
824
|
+
agent_to_use.add_verb("record_call", {
|
825
|
+
"format": agent_to_use._record_format,
|
826
|
+
"stereo": agent_to_use._record_stereo
|
827
|
+
})
|
828
|
+
|
814
829
|
agent_to_use.add_verb("ai", ai_config)
|
815
830
|
|
816
831
|
# Return the rendered document as a string
|
@@ -114,4 +114,67 @@ class SkillBase(ABC):
|
|
114
114
|
|
115
115
|
# For multi-instance skills, create key from skill name + tool name
|
116
116
|
tool_name = self.params.get('tool_name', self.SKILL_NAME)
|
117
|
-
return f"{self.SKILL_NAME}_{tool_name}"
|
117
|
+
return f"{self.SKILL_NAME}_{tool_name}"
|
118
|
+
|
119
|
+
@classmethod
|
120
|
+
def get_parameter_schema(cls) -> Dict[str, Dict[str, Any]]:
|
121
|
+
"""
|
122
|
+
Get the parameter schema for this skill
|
123
|
+
|
124
|
+
This method returns metadata about all parameters the skill accepts,
|
125
|
+
including their types, descriptions, default values, and whether they
|
126
|
+
are required or should be hidden (e.g., API keys).
|
127
|
+
|
128
|
+
The base implementation provides common parameters available to all skills.
|
129
|
+
Subclasses should override this method and merge their specific parameters
|
130
|
+
with the base schema.
|
131
|
+
|
132
|
+
Returns:
|
133
|
+
Dict[str, Dict[str, Any]]: Parameter schema where keys are parameter names
|
134
|
+
and values are dictionaries containing:
|
135
|
+
- type: Parameter type ("string", "integer", "number", "boolean", "object", "array")
|
136
|
+
- description: Human-readable description
|
137
|
+
- default: Default value if not provided (optional)
|
138
|
+
- required: Whether the parameter is required (default: False)
|
139
|
+
- hidden: Whether to hide this field in UIs (for secrets/keys)
|
140
|
+
- env_var: Environment variable that can provide this value (optional)
|
141
|
+
- enum: List of allowed values (optional)
|
142
|
+
- min/max: Minimum/maximum values for numeric types (optional)
|
143
|
+
|
144
|
+
Example:
|
145
|
+
{
|
146
|
+
"tool_name": {
|
147
|
+
"type": "string",
|
148
|
+
"description": "Name for the tool when using multiple instances",
|
149
|
+
"default": "my_skill",
|
150
|
+
"required": False
|
151
|
+
},
|
152
|
+
"api_key": {
|
153
|
+
"type": "string",
|
154
|
+
"description": "API key for the service",
|
155
|
+
"required": True,
|
156
|
+
"hidden": True,
|
157
|
+
"env_var": "MY_API_KEY"
|
158
|
+
}
|
159
|
+
}
|
160
|
+
"""
|
161
|
+
schema = {}
|
162
|
+
|
163
|
+
# Add swaig_fields parameter (available to all skills)
|
164
|
+
schema["swaig_fields"] = {
|
165
|
+
"type": "object",
|
166
|
+
"description": "Additional SWAIG function metadata to merge into tool definitions",
|
167
|
+
"default": {},
|
168
|
+
"required": False
|
169
|
+
}
|
170
|
+
|
171
|
+
# Add tool_name for multi-instance skills
|
172
|
+
if cls.SUPPORTS_MULTIPLE_INSTANCES:
|
173
|
+
schema["tool_name"] = {
|
174
|
+
"type": "string",
|
175
|
+
"description": "Custom name for this skill instance (for multiple instances)",
|
176
|
+
"default": cls.SKILL_NAME,
|
177
|
+
"required": False
|
178
|
+
}
|
179
|
+
|
180
|
+
return schema
|
@@ -110,4 +110,19 @@ class DateTimeSkill(SkillBase):
|
|
110
110
|
"Both tools support different timezones"
|
111
111
|
]
|
112
112
|
}
|
113
|
-
]
|
113
|
+
]
|
114
|
+
|
115
|
+
@classmethod
|
116
|
+
def get_parameter_schema(cls) -> Dict[str, Dict[str, Any]]:
|
117
|
+
"""
|
118
|
+
Get the parameter schema for the datetime skill
|
119
|
+
|
120
|
+
The datetime skill has no custom parameters - it inherits only
|
121
|
+
the base parameters from SkillBase.
|
122
|
+
"""
|
123
|
+
# Get base schema from parent
|
124
|
+
schema = super().get_parameter_schema()
|
125
|
+
|
126
|
+
# No additional parameters for datetime skill
|
127
|
+
|
128
|
+
return schema
|
@@ -88,4 +88,19 @@ class MathSkill(SkillBase):
|
|
88
88
|
"Can handle parentheses for complex expressions"
|
89
89
|
]
|
90
90
|
}
|
91
|
-
]
|
91
|
+
]
|
92
|
+
|
93
|
+
@classmethod
|
94
|
+
def get_parameter_schema(cls) -> Dict[str, Dict[str, Any]]:
|
95
|
+
"""
|
96
|
+
Get the parameter schema for the math skill
|
97
|
+
|
98
|
+
The math skill has no custom parameters - it inherits only
|
99
|
+
the base parameters from SkillBase.
|
100
|
+
"""
|
101
|
+
# Get base schema from parent
|
102
|
+
schema = super().get_parameter_schema()
|
103
|
+
|
104
|
+
# No additional parameters for math skill
|
105
|
+
|
106
|
+
return schema
|
@@ -12,7 +12,7 @@ import importlib
|
|
12
12
|
import importlib.util
|
13
13
|
import inspect
|
14
14
|
import sys
|
15
|
-
from typing import Dict, List, Type, Optional
|
15
|
+
from typing import Dict, List, Type, Optional, Any
|
16
16
|
from pathlib import Path
|
17
17
|
|
18
18
|
from signalwire_agents.core.skill_base import SkillBase
|
@@ -23,28 +23,61 @@ class SkillRegistry:
|
|
23
23
|
|
24
24
|
def __init__(self):
|
25
25
|
self._skills: Dict[str, Type[SkillBase]] = {}
|
26
|
+
self._external_paths: List[Path] = [] # Additional paths to search for skills
|
27
|
+
self._entry_points_loaded = False
|
26
28
|
self.logger = get_logger("skill_registry")
|
27
|
-
# Remove _discovered flag since we're not doing discovery anymore
|
28
29
|
|
29
30
|
def _load_skill_on_demand(self, skill_name: str) -> Optional[Type[SkillBase]]:
|
30
31
|
"""Load a skill on-demand by name"""
|
31
32
|
if skill_name in self._skills:
|
32
33
|
return self._skills[skill_name]
|
33
|
-
|
34
|
-
#
|
34
|
+
|
35
|
+
# First, ensure entry points are loaded
|
36
|
+
self._load_entry_points()
|
37
|
+
|
38
|
+
# Check if skill was loaded from entry points
|
39
|
+
if skill_name in self._skills:
|
40
|
+
return self._skills[skill_name]
|
41
|
+
|
42
|
+
# Search in built-in skills directory
|
35
43
|
skills_dir = Path(__file__).parent
|
36
|
-
|
44
|
+
skill_class = self._load_skill_from_path(skill_name, skills_dir)
|
45
|
+
if skill_class:
|
46
|
+
return skill_class
|
47
|
+
|
48
|
+
# Search in external paths
|
49
|
+
for external_path in self._external_paths:
|
50
|
+
skill_class = self._load_skill_from_path(skill_name, external_path)
|
51
|
+
if skill_class:
|
52
|
+
return skill_class
|
53
|
+
|
54
|
+
# Search in environment variable paths
|
55
|
+
env_paths = os.environ.get('SIGNALWIRE_SKILL_PATHS', '').split(':')
|
56
|
+
for path_str in env_paths:
|
57
|
+
if path_str:
|
58
|
+
skill_class = self._load_skill_from_path(skill_name, Path(path_str))
|
59
|
+
if skill_class:
|
60
|
+
return skill_class
|
61
|
+
|
62
|
+
self.logger.debug(f"Skill '{skill_name}' not found in any registered paths")
|
63
|
+
return None
|
64
|
+
|
65
|
+
def _load_skill_from_path(self, skill_name: str, base_path: Path) -> Optional[Type[SkillBase]]:
|
66
|
+
"""Try to load a skill from a specific base path"""
|
67
|
+
skill_dir = base_path / skill_name
|
37
68
|
skill_file = skill_dir / "skill.py"
|
38
69
|
|
39
70
|
if not skill_file.exists():
|
40
|
-
self.logger.debug(f"Skill '{skill_name}' not found at {skill_file}")
|
41
71
|
return None
|
42
72
|
|
43
73
|
try:
|
44
|
-
#
|
45
|
-
module_name = f"
|
74
|
+
# Create unique module name to avoid conflicts
|
75
|
+
module_name = f"signalwire_agents_external.{base_path.name}.{skill_name}.skill"
|
46
76
|
spec = importlib.util.spec_from_file_location(module_name, skill_file)
|
47
77
|
module = importlib.util.module_from_spec(spec)
|
78
|
+
|
79
|
+
# Add to sys.modules to handle relative imports
|
80
|
+
sys.modules[module_name] = module
|
48
81
|
spec.loader.exec_module(module)
|
49
82
|
|
50
83
|
# Find SkillBase subclasses in the module
|
@@ -52,6 +85,7 @@ class SkillRegistry:
|
|
52
85
|
if (inspect.isclass(obj) and
|
53
86
|
issubclass(obj, SkillBase) and
|
54
87
|
obj != SkillBase and
|
88
|
+
hasattr(obj, 'SKILL_NAME') and
|
55
89
|
obj.SKILL_NAME == skill_name): # Match exact skill name
|
56
90
|
|
57
91
|
self.register_skill(obj)
|
@@ -75,7 +109,25 @@ class SkillRegistry:
|
|
75
109
|
pass
|
76
110
|
|
77
111
|
def register_skill(self, skill_class: Type[SkillBase]) -> None:
|
78
|
-
"""
|
112
|
+
"""
|
113
|
+
Register a skill class directly
|
114
|
+
|
115
|
+
This allows third-party code to register skill classes without
|
116
|
+
requiring them to be in a specific directory structure.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
skill_class: A class that inherits from SkillBase
|
120
|
+
|
121
|
+
Example:
|
122
|
+
from my_custom_skills import MyWeatherSkill
|
123
|
+
skill_registry.register_skill(MyWeatherSkill)
|
124
|
+
"""
|
125
|
+
if not issubclass(skill_class, SkillBase):
|
126
|
+
raise ValueError(f"{skill_class} must inherit from SkillBase")
|
127
|
+
|
128
|
+
if not hasattr(skill_class, 'SKILL_NAME') or skill_class.SKILL_NAME is None:
|
129
|
+
raise ValueError(f"{skill_class} must define SKILL_NAME")
|
130
|
+
|
79
131
|
if skill_class.SKILL_NAME in self._skills:
|
80
132
|
self.logger.warning(f"Skill '{skill_class.SKILL_NAME}' already registered")
|
81
133
|
return
|
@@ -115,6 +167,257 @@ class SkillRegistry:
|
|
115
167
|
})
|
116
168
|
|
117
169
|
return available_skills
|
170
|
+
|
171
|
+
def get_all_skills_schema(self) -> Dict[str, Dict[str, Any]]:
|
172
|
+
"""
|
173
|
+
Get complete schema for all available skills including parameter metadata
|
174
|
+
|
175
|
+
This method scans all available skills and returns a comprehensive schema
|
176
|
+
that includes skill metadata and parameter definitions suitable for GUI
|
177
|
+
configuration or API documentation.
|
178
|
+
|
179
|
+
Returns:
|
180
|
+
Dict[str, Dict[str, Any]]: Complete skill schema where keys are skill names
|
181
|
+
and values contain:
|
182
|
+
- name: Skill name
|
183
|
+
- description: Skill description
|
184
|
+
- version: Skill version
|
185
|
+
- supports_multiple_instances: Whether multiple instances are allowed
|
186
|
+
- required_packages: List of required Python packages
|
187
|
+
- required_env_vars: List of required environment variables
|
188
|
+
- parameters: Parameter schema from get_parameter_schema()
|
189
|
+
- source: Where the skill was loaded from ('built-in', 'external', 'entry_point', 'registered')
|
190
|
+
|
191
|
+
Example:
|
192
|
+
{
|
193
|
+
"web_search": {
|
194
|
+
"name": "web_search",
|
195
|
+
"description": "Search the web for information",
|
196
|
+
"version": "1.0.0",
|
197
|
+
"supports_multiple_instances": True,
|
198
|
+
"required_packages": ["bs4", "requests"],
|
199
|
+
"required_env_vars": [],
|
200
|
+
"parameters": {
|
201
|
+
"api_key": {
|
202
|
+
"type": "string",
|
203
|
+
"description": "Google API key",
|
204
|
+
"required": True,
|
205
|
+
"hidden": True,
|
206
|
+
"env_var": "GOOGLE_SEARCH_API_KEY"
|
207
|
+
},
|
208
|
+
...
|
209
|
+
},
|
210
|
+
"source": "built-in"
|
211
|
+
}
|
212
|
+
}
|
213
|
+
"""
|
214
|
+
skills_schema = {}
|
215
|
+
|
216
|
+
# Load entry points first
|
217
|
+
self._load_entry_points()
|
218
|
+
|
219
|
+
# Helper function to add skill to schema
|
220
|
+
def add_skill_to_schema(skill_class, source):
|
221
|
+
try:
|
222
|
+
# Get parameter schema
|
223
|
+
try:
|
224
|
+
parameter_schema = skill_class.get_parameter_schema()
|
225
|
+
except AttributeError:
|
226
|
+
# Skill doesn't implement get_parameter_schema yet
|
227
|
+
parameter_schema = {}
|
228
|
+
|
229
|
+
skills_schema[skill_class.SKILL_NAME] = {
|
230
|
+
"name": skill_class.SKILL_NAME,
|
231
|
+
"description": skill_class.SKILL_DESCRIPTION,
|
232
|
+
"version": getattr(skill_class, 'SKILL_VERSION', '1.0.0'),
|
233
|
+
"supports_multiple_instances": getattr(skill_class, 'SUPPORTS_MULTIPLE_INSTANCES', False),
|
234
|
+
"required_packages": getattr(skill_class, 'REQUIRED_PACKAGES', []),
|
235
|
+
"required_env_vars": getattr(skill_class, 'REQUIRED_ENV_VARS', []),
|
236
|
+
"parameters": parameter_schema,
|
237
|
+
"source": source
|
238
|
+
}
|
239
|
+
except Exception as e:
|
240
|
+
self.logger.error(f"Failed to get schema for skill '{skill_class.SKILL_NAME}': {e}")
|
241
|
+
|
242
|
+
# Add already registered skills first (includes entry points)
|
243
|
+
for skill_name, skill_class in self._skills.items():
|
244
|
+
add_skill_to_schema(skill_class, 'registered')
|
245
|
+
|
246
|
+
# Scan built-in skills directory
|
247
|
+
skills_dir = Path(__file__).parent
|
248
|
+
for item in skills_dir.iterdir():
|
249
|
+
if item.is_dir() and not item.name.startswith('__'):
|
250
|
+
skill_file = item / "skill.py"
|
251
|
+
if skill_file.exists() and item.name not in skills_schema:
|
252
|
+
try:
|
253
|
+
skill_class = self._load_skill_on_demand(item.name)
|
254
|
+
if skill_class:
|
255
|
+
add_skill_to_schema(skill_class, 'built-in')
|
256
|
+
except Exception as e:
|
257
|
+
self.logger.error(f"Failed to load skill '{item.name}': {e}")
|
258
|
+
|
259
|
+
# Scan external directories
|
260
|
+
for external_path in self._external_paths:
|
261
|
+
if external_path.exists():
|
262
|
+
for item in external_path.iterdir():
|
263
|
+
if item.is_dir() and not item.name.startswith('__'):
|
264
|
+
skill_file = item / "skill.py"
|
265
|
+
if skill_file.exists() and item.name not in skills_schema:
|
266
|
+
try:
|
267
|
+
skill_class = self._load_skill_on_demand(item.name)
|
268
|
+
if skill_class:
|
269
|
+
add_skill_to_schema(skill_class, 'external')
|
270
|
+
except Exception as e:
|
271
|
+
self.logger.error(f"Failed to load skill '{item.name}': {e}")
|
272
|
+
|
273
|
+
# Scan environment variable paths
|
274
|
+
env_paths = os.environ.get('SIGNALWIRE_SKILL_PATHS', '').split(':')
|
275
|
+
for path_str in env_paths:
|
276
|
+
if path_str:
|
277
|
+
env_path = Path(path_str)
|
278
|
+
if env_path.exists():
|
279
|
+
for item in env_path.iterdir():
|
280
|
+
if item.is_dir() and not item.name.startswith('__'):
|
281
|
+
skill_file = item / "skill.py"
|
282
|
+
if skill_file.exists() and item.name not in skills_schema:
|
283
|
+
try:
|
284
|
+
skill_class = self._load_skill_on_demand(item.name)
|
285
|
+
if skill_class:
|
286
|
+
add_skill_to_schema(skill_class, 'external')
|
287
|
+
except Exception as e:
|
288
|
+
self.logger.error(f"Failed to load skill '{item.name}': {e}")
|
289
|
+
|
290
|
+
return skills_schema
|
291
|
+
|
292
|
+
def add_skill_directory(self, path: str) -> None:
|
293
|
+
"""
|
294
|
+
Add a directory to search for skills
|
295
|
+
|
296
|
+
This allows third-party skill collections to be registered by path.
|
297
|
+
Skills in these directories should follow the same structure as built-in skills:
|
298
|
+
- Each skill in its own subdirectory
|
299
|
+
- skill.py file containing the skill class
|
300
|
+
|
301
|
+
Args:
|
302
|
+
path: Path to directory containing skill subdirectories
|
303
|
+
|
304
|
+
Example:
|
305
|
+
skill_registry.add_skill_directory('/opt/custom_skills')
|
306
|
+
# Now agent.add_skill('my_custom_skill') will search in this directory
|
307
|
+
"""
|
308
|
+
skill_path = Path(path)
|
309
|
+
if not skill_path.exists():
|
310
|
+
raise ValueError(f"Skill directory does not exist: {path}")
|
311
|
+
if not skill_path.is_dir():
|
312
|
+
raise ValueError(f"Path is not a directory: {path}")
|
313
|
+
|
314
|
+
if skill_path not in self._external_paths:
|
315
|
+
self._external_paths.append(skill_path)
|
316
|
+
self.logger.info(f"Added external skill directory: {path}")
|
317
|
+
|
318
|
+
def _load_entry_points(self) -> None:
|
319
|
+
"""
|
320
|
+
Load skills from Python entry points
|
321
|
+
|
322
|
+
This allows installed packages to register skills via setup.py:
|
323
|
+
|
324
|
+
entry_points={
|
325
|
+
'signalwire_agents.skills': [
|
326
|
+
'weather = my_package.skills:WeatherSkill',
|
327
|
+
'stock = my_package.skills:StockSkill',
|
328
|
+
]
|
329
|
+
}
|
330
|
+
"""
|
331
|
+
if self._entry_points_loaded:
|
332
|
+
return
|
333
|
+
|
334
|
+
self._entry_points_loaded = True
|
335
|
+
|
336
|
+
try:
|
337
|
+
import pkg_resources
|
338
|
+
|
339
|
+
for entry_point in pkg_resources.iter_entry_points('signalwire_agents.skills'):
|
340
|
+
try:
|
341
|
+
skill_class = entry_point.load()
|
342
|
+
if issubclass(skill_class, SkillBase):
|
343
|
+
self.register_skill(skill_class)
|
344
|
+
self.logger.info(f"Loaded skill '{skill_class.SKILL_NAME}' from entry point '{entry_point.name}'")
|
345
|
+
else:
|
346
|
+
self.logger.warning(f"Entry point '{entry_point.name}' does not provide a SkillBase subclass")
|
347
|
+
except Exception as e:
|
348
|
+
self.logger.error(f"Failed to load skill from entry point '{entry_point.name}': {e}")
|
349
|
+
|
350
|
+
except ImportError:
|
351
|
+
# pkg_resources not available, try importlib.metadata (Python 3.8+)
|
352
|
+
try:
|
353
|
+
from importlib import metadata
|
354
|
+
|
355
|
+
entry_points = metadata.entry_points()
|
356
|
+
if hasattr(entry_points, 'select'):
|
357
|
+
# Python 3.10+
|
358
|
+
skill_entries = entry_points.select(group='signalwire_agents.skills')
|
359
|
+
else:
|
360
|
+
# Python 3.8-3.9
|
361
|
+
skill_entries = entry_points.get('signalwire_agents.skills', [])
|
362
|
+
|
363
|
+
for entry_point in skill_entries:
|
364
|
+
try:
|
365
|
+
skill_class = entry_point.load()
|
366
|
+
if issubclass(skill_class, SkillBase):
|
367
|
+
self.register_skill(skill_class)
|
368
|
+
self.logger.info(f"Loaded skill '{skill_class.SKILL_NAME}' from entry point '{entry_point.name}'")
|
369
|
+
else:
|
370
|
+
self.logger.warning(f"Entry point '{entry_point.name}' does not provide a SkillBase subclass")
|
371
|
+
except Exception as e:
|
372
|
+
self.logger.error(f"Failed to load skill from entry point '{entry_point.name}': {e}")
|
373
|
+
|
374
|
+
except ImportError:
|
375
|
+
# Neither pkg_resources nor importlib.metadata available
|
376
|
+
self.logger.debug("Entry point loading not available - install setuptools or use Python 3.8+")
|
377
|
+
|
378
|
+
def list_all_skill_sources(self) -> Dict[str, List[str]]:
|
379
|
+
"""
|
380
|
+
List all skill sources and the skills available from each
|
381
|
+
|
382
|
+
Returns a dictionary mapping source types to lists of skill names:
|
383
|
+
{
|
384
|
+
'built-in': ['datetime', 'math', ...],
|
385
|
+
'external_paths': ['custom_skill1', ...],
|
386
|
+
'entry_points': ['weather', ...],
|
387
|
+
'registered': ['my_skill', ...]
|
388
|
+
}
|
389
|
+
"""
|
390
|
+
sources = {
|
391
|
+
'built-in': [],
|
392
|
+
'external_paths': [],
|
393
|
+
'entry_points': [],
|
394
|
+
'registered': []
|
395
|
+
}
|
396
|
+
|
397
|
+
# Built-in skills
|
398
|
+
skills_dir = Path(__file__).parent
|
399
|
+
for item in skills_dir.iterdir():
|
400
|
+
if item.is_dir() and not item.name.startswith('__'):
|
401
|
+
skill_file = item / "skill.py"
|
402
|
+
if skill_file.exists():
|
403
|
+
sources['built-in'].append(item.name)
|
404
|
+
|
405
|
+
# External path skills
|
406
|
+
for external_path in self._external_paths:
|
407
|
+
if external_path.exists():
|
408
|
+
for item in external_path.iterdir():
|
409
|
+
if item.is_dir() and not item.name.startswith('__'):
|
410
|
+
skill_file = item / "skill.py"
|
411
|
+
if skill_file.exists():
|
412
|
+
sources['external_paths'].append(item.name)
|
413
|
+
|
414
|
+
# Already registered skills
|
415
|
+
for skill_name in self._skills:
|
416
|
+
# Determine source of registered skill
|
417
|
+
if skill_name not in sources['built-in']:
|
418
|
+
sources['registered'].append(skill_name)
|
419
|
+
|
420
|
+
return sources
|
118
421
|
|
119
422
|
# Global registry instance
|
120
423
|
skill_registry = SkillRegistry()
|
@@ -262,4 +262,63 @@ class WebSearchSkill(SkillBase):
|
|
262
262
|
"Include relevant URLs so users can read more if interested"
|
263
263
|
]
|
264
264
|
}
|
265
|
-
]
|
265
|
+
]
|
266
|
+
|
267
|
+
@classmethod
|
268
|
+
def get_parameter_schema(cls) -> Dict[str, Dict[str, Any]]:
|
269
|
+
"""
|
270
|
+
Get the parameter schema for the web search skill
|
271
|
+
|
272
|
+
Returns all configurable parameters for web search including
|
273
|
+
API credentials, search settings, and response customization.
|
274
|
+
"""
|
275
|
+
# Get base schema from parent
|
276
|
+
schema = super().get_parameter_schema()
|
277
|
+
|
278
|
+
# Add web search specific parameters
|
279
|
+
schema.update({
|
280
|
+
"api_key": {
|
281
|
+
"type": "string",
|
282
|
+
"description": "Google Custom Search API key",
|
283
|
+
"required": True,
|
284
|
+
"hidden": True, # Mark as hidden since it's a secret
|
285
|
+
"env_var": "GOOGLE_SEARCH_API_KEY"
|
286
|
+
},
|
287
|
+
"search_engine_id": {
|
288
|
+
"type": "string",
|
289
|
+
"description": "Google Custom Search Engine ID",
|
290
|
+
"required": True,
|
291
|
+
"hidden": True, # Also a secret
|
292
|
+
"env_var": "GOOGLE_SEARCH_ENGINE_ID"
|
293
|
+
},
|
294
|
+
"num_results": {
|
295
|
+
"type": "integer",
|
296
|
+
"description": "Default number of search results to return",
|
297
|
+
"default": 1,
|
298
|
+
"required": False,
|
299
|
+
"min": 1,
|
300
|
+
"max": 10
|
301
|
+
},
|
302
|
+
"delay": {
|
303
|
+
"type": "number",
|
304
|
+
"description": "Delay between scraping pages in seconds",
|
305
|
+
"default": 0,
|
306
|
+
"required": False,
|
307
|
+
"min": 0
|
308
|
+
},
|
309
|
+
"max_content_length": {
|
310
|
+
"type": "integer",
|
311
|
+
"description": "Maximum content length per scraped page (characters)",
|
312
|
+
"default": 2000,
|
313
|
+
"required": False,
|
314
|
+
"min": 100
|
315
|
+
},
|
316
|
+
"no_results_message": {
|
317
|
+
"type": "string",
|
318
|
+
"description": "Message to show when no results are found. Use {query} as placeholder.",
|
319
|
+
"default": "I couldn't find any results for '{query}'. This might be due to a very specific query or temporary issues. Try rephrasing your search or asking about a different topic.",
|
320
|
+
"required": False
|
321
|
+
}
|
322
|
+
})
|
323
|
+
|
324
|
+
return schema
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: signalwire_agents
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.36
|
4
4
|
Summary: SignalWire AI Agents SDK
|
5
5
|
Author-email: SignalWire Team <info@signalwire.com>
|
6
6
|
License: MIT
|
@@ -74,7 +74,9 @@ Dynamic: license-file
|
|
74
74
|
|
75
75
|
<!-- Header -->
|
76
76
|
<div align="center">
|
77
|
-
<
|
77
|
+
<a href="https://signalwire.com" target="_blank">
|
78
|
+
<img src="https://github.com/user-attachments/assets/0c8ed3b9-8c50-4dc6-9cc4-cc6cd137fd50" width="500" />
|
79
|
+
</a>
|
78
80
|
|
79
81
|
# Agents SDK
|
80
82
|
|
@@ -92,15 +94,17 @@ Dynamic: license-file
|
|
92
94
|
|
93
95
|
<!-- Badges -->
|
94
96
|
<div align="center">
|
95
|
-
<img src="https://img.shields.io/badge/Discord%20Community-5865F2" alt="Discord"
|
96
|
-
<img src="https://img.shields.io/badge/MIT-License-blue" alt="MIT License"
|
97
|
-
<img src="https://img.shields.io/badge/GitHub-%23121011.svg?logo=github&logoColor=white&" alt="GitHub"
|
98
|
-
<img src="https://img.shields.io/github/stars/signalwire/signalwire-agents" alt="GitHub Stars"
|
97
|
+
<a href="https://discord.com/invite/F2WNYTNjuF" target="_blank"><img src="https://img.shields.io/badge/Discord%20Community-5865F2" alt="Discord" /></a>
|
98
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/MIT-License-blue" alt="MIT License" /></a>
|
99
|
+
<a href="https://github.com/signalwire" target="_blank"><img src="https://img.shields.io/badge/GitHub-%23121011.svg?logo=github&logoColor=white&" alt="GitHub" /></a>
|
100
|
+
<a href="https://github.com/signalwire/docs" target="_blank"><img src="https://img.shields.io/github/stars/signalwire/signalwire-agents" alt="GitHub Stars" /></a>
|
99
101
|
</div>
|
100
102
|
|
101
103
|
<br/>
|
102
104
|
|
103
|
-
<
|
105
|
+
<a href="https://signalwire.com/signup" target="_blank">
|
106
|
+
<img src="https://github.com/user-attachments/assets/c2510c86-ae03-42a9-be06-ab9bcea948e1" alt="Sign Up" height="65"/>
|
107
|
+
</a>
|
104
108
|
|
105
109
|
</div>
|
106
110
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
signalwire_agents/__init__.py,sha256=
|
1
|
+
signalwire_agents/__init__.py,sha256=sAcZavFeYtFBdxVecqGemex6Ob5by-dEbwvtEgL85RI,4845
|
2
2
|
signalwire_agents/agent_server.py,sha256=x9HyWia8D3r6KMqY-Q4DtNVivfJWLTx8B-KzUI8okuA,26880
|
3
3
|
signalwire_agents/schema.json,sha256=6-7ccbt39iM1CO36dOfvupRPfd0gnQ0XoAdyo-EFyjo,238042
|
4
4
|
signalwire_agents/cli/__init__.py,sha256=XbxAQFaCIdGXIXJiriVBWoFPOJsC401u21588nO4TG8,388
|
@@ -23,7 +23,7 @@ signalwire_agents/cli/simulation/data_generation.py,sha256=pxa9aJ6XkI0O8yAIGvBTU
|
|
23
23
|
signalwire_agents/cli/simulation/data_overrides.py,sha256=3_3pT6j-q2gRufPX2bZ1BrmY7u1IdloLooKAJil33vI,6319
|
24
24
|
signalwire_agents/cli/simulation/mock_env.py,sha256=fvaR_xdLMm8AbpNUbTJOFG9THcti3Zds-0QNDbKMaYk,10249
|
25
25
|
signalwire_agents/core/__init__.py,sha256=xjPq8DmUnWYUG28sd17n430VWPmMH9oZ9W14gYwG96g,806
|
26
|
-
signalwire_agents/core/agent_base.py,sha256=
|
26
|
+
signalwire_agents/core/agent_base.py,sha256=CMMtYlCsMEL-5caoXfjGYMIiRtm8HE67v95Y8ODm6sY,44506
|
27
27
|
signalwire_agents/core/auth_handler.py,sha256=jXrof9WZ1W9qqlQT9WElcmSRafL2kG7207x5SqWN9MU,8481
|
28
28
|
signalwire_agents/core/config_loader.py,sha256=rStVRRUaeMGrMc44ocr0diMQQARZhbKqwMqQ6kqUNos,8722
|
29
29
|
signalwire_agents/core/contexts.py,sha256=g9FgOGMfGCUWlm57YZcv7CvOf-Ub9FdKZIOMu14ADfE,24428
|
@@ -32,7 +32,7 @@ signalwire_agents/core/function_result.py,sha256=MZZUoAHN8AXry-OBNA6Od5l-bKvMM2r
|
|
32
32
|
signalwire_agents/core/logging_config.py,sha256=x4d_RAjBjVpJOFA2vXnPP2dNr13BZHz091J5rGpC77Y,13142
|
33
33
|
signalwire_agents/core/pom_builder.py,sha256=ywuiIfP8BeLBPo_G4X1teZlG6zTCMkW71CZnmyoDTAQ,6636
|
34
34
|
signalwire_agents/core/security_config.py,sha256=iAnAzKEJQiXL6mMpDaYm3Sjkxwm4x2N9HD6DeWSI8yI,12536
|
35
|
-
signalwire_agents/core/skill_base.py,sha256=
|
35
|
+
signalwire_agents/core/skill_base.py,sha256=1b_4ht_T1BVnfzHYqoILb3idrrPYMs5-G-adHo2IVss,6903
|
36
36
|
signalwire_agents/core/skill_manager.py,sha256=9E2mPcn8-97t4OAnHCSdUEOW0nyr_g4zkWe9OQrU48g,7938
|
37
37
|
signalwire_agents/core/swaig_function.py,sha256=VFIYQWltvYMrUfN_lfda-YzuQ2PqF2vhb-PegAliwCc,7076
|
38
38
|
signalwire_agents/core/swml_builder.py,sha256=tJBFDAVTENEfjGLp2h9_AKOYt5O9FrSYLI-nZZVwM1E,15604
|
@@ -76,7 +76,7 @@ signalwire_agents/search/search_engine.py,sha256=KFF33aPsBdwb2N1aTJmAA0xo3KPANoX
|
|
76
76
|
signalwire_agents/search/search_service.py,sha256=zidaQb2aRusAODU8C6Nw65ZlTy3DjJ2-bMJavhMl7vk,15608
|
77
77
|
signalwire_agents/skills/README.md,sha256=sM1_08IsKdRDCzYHPLzppJbaK5MvRelsVL6Kd9A9Ubo,12193
|
78
78
|
signalwire_agents/skills/__init__.py,sha256=9AMEcyk2tDaGiUjwVIson_tVWxV4oU_2NnGGNTbHuyQ,533
|
79
|
-
signalwire_agents/skills/registry.py,sha256=
|
79
|
+
signalwire_agents/skills/registry.py,sha256=nV8chVLEDZwZO8p_szAizKdirBy_uj9GkyepJfumCW4,18486
|
80
80
|
signalwire_agents/skills/api_ninjas_trivia/README.md,sha256=SoyS7VFh3eVIiVnQ5gfTfs0a_gAlLwnmT2W2FrbNU0A,6762
|
81
81
|
signalwire_agents/skills/api_ninjas_trivia/__init__.py,sha256=zN305bBQkzlJyUNsPUMPt3gDJbvc-Iigkdh0rBou_RE,267
|
82
82
|
signalwire_agents/skills/api_ninjas_trivia/skill.py,sha256=phioqvXivdoklg7tHE998Ac5S6-0Q90X8OmEjSIGprM,7397
|
@@ -88,13 +88,13 @@ signalwire_agents/skills/datasphere_serverless/__init__.py,sha256=jpMNDcGiXsVbSC
|
|
88
88
|
signalwire_agents/skills/datasphere_serverless/skill.py,sha256=zgEoTY8Jm6YKJBzM1kn3j7tf492K-NiKG7DbE3GReeE,6787
|
89
89
|
signalwire_agents/skills/datetime/README.md,sha256=95SzVz-Pcm9MPqZ4D3sSYKMwdpsDNwwCpWFRK027-Pc,4534
|
90
90
|
signalwire_agents/skills/datetime/__init__.py,sha256=Irajm2sUhmQVFgais-J-q-3d58tNnJ4nbLmnphr90nI,234
|
91
|
-
signalwire_agents/skills/datetime/skill.py,sha256=
|
91
|
+
signalwire_agents/skills/datetime/skill.py,sha256=mA-dxBhZOIbMygBQ3Z4jmFCH-zsD8HnjcWC4asp6Gl0,4370
|
92
92
|
signalwire_agents/skills/joke/README.md,sha256=xUa2_0Pk9neli-UJxI4BPt3Fb1_5Xa3m8RuDlrkfBao,3594
|
93
93
|
signalwire_agents/skills/joke/__init__.py,sha256=8Rc5_nj30bdga2n9H9JSI2WzMn40pjApd-y-tk5WIkI,244
|
94
94
|
signalwire_agents/skills/joke/skill.py,sha256=AFaf6fMy0sxUPJHvcnf3CWMuPqpJP4ODscUexMadEcU,3381
|
95
95
|
signalwire_agents/skills/math/README.md,sha256=Nrv7PxkFPSxdnAN6856Fp1CfvsUwdncpRFFDERxmMe0,5335
|
96
96
|
signalwire_agents/skills/math/__init__.py,sha256=F7emZqBpAAkqJZxA3RNTzRSAXE5e2xu8PtFOPHebfKo,230
|
97
|
-
signalwire_agents/skills/math/skill.py,sha256
|
97
|
+
signalwire_agents/skills/math/skill.py,sha256=-h0DRX_nFkeSzLfaiOKv0zHScdXiQuz4rkLShanKwmc,3800
|
98
98
|
signalwire_agents/skills/mcp_gateway/README.md,sha256=t-71TTWlEvjgWLTcT3v4kMw9zlrKXTAC_sCjb1haNew,5826
|
99
99
|
signalwire_agents/skills/mcp_gateway/__init__.py,sha256=zLgOa7s0sIQphTNJjvasIAW7llxAApez7moC_e1tzP0,236
|
100
100
|
signalwire_agents/skills/mcp_gateway/skill.py,sha256=iS5ET9jYKnwuYsuFd-aqUJfLUhvX--RG2FfCq8BZ340,14050
|
@@ -114,7 +114,7 @@ signalwire_agents/skills/weather_api/__init__.py,sha256=WCS--GFBX8straIZPuGAmTDZ
|
|
114
114
|
signalwire_agents/skills/weather_api/skill.py,sha256=-0gFgTVau2Pxljq9_fmNDnzgUI0JBXtdOCIAdHc8fLs,6217
|
115
115
|
signalwire_agents/skills/web_search/README.md,sha256=Y95cxEScMzhmslUJF8u_Nh15FbEBuus4P-E8_kk2an0,5438
|
116
116
|
signalwire_agents/skills/web_search/__init__.py,sha256=kv4CzmF1lldRZcL_HivieslP7gtTFvxcfprKG4n6b-Q,236
|
117
|
-
signalwire_agents/skills/web_search/skill.py,sha256=
|
117
|
+
signalwire_agents/skills/web_search/skill.py,sha256=EGu6ff9aAb2W323_XDCcVDW1wbAKTZYK8HQOT__iqtE,12660
|
118
118
|
signalwire_agents/skills/wikipedia_search/README.md,sha256=KFIQ8XhqrTG8NRs72dIbjJacy2DlYEXLtxgy23gyRi4,7585
|
119
119
|
signalwire_agents/skills/wikipedia_search/__init__.py,sha256=yJ6iYTSyJC96mwwUsI_FneFhDBcLYD4xEerBKlWLTb8,375
|
120
120
|
signalwire_agents/skills/wikipedia_search/skill.py,sha256=8HhV8E4yDMe-p3qCxgEYFrL5hNvnJ6KbQzYumpgI7-k,7068
|
@@ -123,9 +123,9 @@ signalwire_agents/utils/pom_utils.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663YjFX_Pu
|
|
123
123
|
signalwire_agents/utils/schema_utils.py,sha256=i4okv_O9bUApwT_jJf4Yoij3bLCrGrW3DC-vzSy2RuY,16392
|
124
124
|
signalwire_agents/utils/token_generators.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663YjFX_PumJ35Xds,191
|
125
125
|
signalwire_agents/utils/validators.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663YjFX_PumJ35Xds,191
|
126
|
-
signalwire_agents-0.1.
|
127
|
-
signalwire_agents-0.1.
|
128
|
-
signalwire_agents-0.1.
|
129
|
-
signalwire_agents-0.1.
|
130
|
-
signalwire_agents-0.1.
|
131
|
-
signalwire_agents-0.1.
|
126
|
+
signalwire_agents-0.1.36.dist-info/licenses/LICENSE,sha256=NYvAsB-rTcSvG9cqHt9EUHAWLiA9YzM4Qfz-mPdvDR0,1067
|
127
|
+
signalwire_agents-0.1.36.dist-info/METADATA,sha256=YiUTiZ5uwMRgTDFLbm7MBBE1NX4u-BLL7l6X2DmTmEA,38377
|
128
|
+
signalwire_agents-0.1.36.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
129
|
+
signalwire_agents-0.1.36.dist-info/entry_points.txt,sha256=ZDT65zfTO_YyDzi_hwQbCxIhrUfu_t8RpNXMMXlUPWI,144
|
130
|
+
signalwire_agents-0.1.36.dist-info/top_level.txt,sha256=kDGS6ZYv84K9P5Kyg9_S8P_pbUXoHkso0On_DB5bbWc,18
|
131
|
+
signalwire_agents-0.1.36.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|