signalwire-agents 0.1.10__py3-none-any.whl → 0.1.11__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.
Files changed (37) hide show
  1. signalwire_agents/__init__.py +39 -4
  2. signalwire_agents/agent_server.py +46 -2
  3. signalwire_agents/cli/__init__.py +9 -0
  4. signalwire_agents/cli/test_swaig.py +2545 -0
  5. signalwire_agents/core/agent_base.py +691 -82
  6. signalwire_agents/core/contexts.py +289 -0
  7. signalwire_agents/core/data_map.py +499 -0
  8. signalwire_agents/core/function_result.py +57 -10
  9. signalwire_agents/core/skill_base.py +27 -37
  10. signalwire_agents/core/skill_manager.py +89 -23
  11. signalwire_agents/core/swaig_function.py +13 -1
  12. signalwire_agents/core/swml_handler.py +37 -13
  13. signalwire_agents/core/swml_service.py +37 -28
  14. signalwire_agents/skills/datasphere/__init__.py +12 -0
  15. signalwire_agents/skills/datasphere/skill.py +229 -0
  16. signalwire_agents/skills/datasphere_serverless/__init__.py +1 -0
  17. signalwire_agents/skills/datasphere_serverless/skill.py +156 -0
  18. signalwire_agents/skills/datetime/skill.py +9 -5
  19. signalwire_agents/skills/joke/__init__.py +1 -0
  20. signalwire_agents/skills/joke/skill.py +88 -0
  21. signalwire_agents/skills/math/skill.py +9 -6
  22. signalwire_agents/skills/registry.py +23 -4
  23. signalwire_agents/skills/web_search/skill.py +57 -21
  24. signalwire_agents/skills/wikipedia/__init__.py +9 -0
  25. signalwire_agents/skills/wikipedia/skill.py +180 -0
  26. signalwire_agents/utils/__init__.py +2 -0
  27. signalwire_agents/utils/schema_utils.py +111 -44
  28. signalwire_agents/utils/serverless.py +38 -0
  29. signalwire_agents-0.1.11.dist-info/METADATA +756 -0
  30. signalwire_agents-0.1.11.dist-info/RECORD +58 -0
  31. {signalwire_agents-0.1.10.dist-info → signalwire_agents-0.1.11.dist-info}/WHEEL +1 -1
  32. signalwire_agents-0.1.11.dist-info/entry_points.txt +2 -0
  33. signalwire_agents-0.1.10.dist-info/METADATA +0 -319
  34. signalwire_agents-0.1.10.dist-info/RECORD +0 -44
  35. {signalwire_agents-0.1.10.data → signalwire_agents-0.1.11.data}/data/schema.json +0 -0
  36. {signalwire_agents-0.1.10.dist-info → signalwire_agents-0.1.11.dist-info}/licenses/LICENSE +0 -0
  37. {signalwire_agents-0.1.10.dist-info → signalwire_agents-0.1.11.dist-info}/top_level.txt +0 -0
@@ -31,10 +31,6 @@ class SkillManager:
31
31
  Returns:
32
32
  tuple: (success, error_message) - error_message is empty string if successful
33
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
34
  # Get skill class from registry if not provided
39
35
  if skill_class is None:
40
36
  try:
@@ -50,8 +46,21 @@ class SkillManager:
50
46
  return False, error_msg
51
47
 
52
48
  try:
53
- # Create skill instance with parameters
49
+ # Create skill instance with parameters to get the instance key
54
50
  skill_instance = skill_class(self.agent, params)
51
+ instance_key = skill_instance.get_instance_key()
52
+
53
+ # Check if this instance is already loaded
54
+ if instance_key in self.loaded_skills:
55
+ # For single-instance skills, this is an error
56
+ if not skill_instance.SUPPORTS_MULTIPLE_INSTANCES:
57
+ error_msg = f"Skill '{skill_name}' is already loaded and does not support multiple instances"
58
+ self.logger.error(error_msg)
59
+ return False, error_msg
60
+ else:
61
+ # For multi-instance skills, just warn and return success
62
+ self.logger.warning(f"Skill instance '{instance_key}' is already loaded")
63
+ return True, ""
55
64
 
56
65
  # Validate environment variables with specific error details
57
66
  import os
@@ -97,9 +106,9 @@ class SkillManager:
97
106
  for section in prompt_sections:
98
107
  self.agent.prompt_add_section(**section)
99
108
 
100
- # Store loaded skill
101
- self.loaded_skills[skill_name] = skill_instance
102
- self.logger.info(f"Successfully loaded skill '{skill_name}'")
109
+ # Store loaded skill using instance key
110
+ self.loaded_skills[instance_key] = skill_instance
111
+ self.logger.info(f"Successfully loaded skill instance '{instance_key}' (skill: '{skill_name}')")
103
112
  return True, ""
104
113
 
105
114
  except Exception as e:
@@ -107,30 +116,87 @@ class SkillManager:
107
116
  self.logger.error(error_msg)
108
117
  return False, error_msg
109
118
 
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")
119
+ def unload_skill(self, skill_identifier: str) -> bool:
120
+ """
121
+ Unload a skill and cleanup
122
+
123
+ Args:
124
+ skill_identifier: Either a skill name or an instance key
125
+
126
+ Returns:
127
+ bool: True if successfully unloaded, False otherwise
128
+ """
129
+ # Try to find the skill by identifier (could be skill name or instance key)
130
+ skill_instance = None
131
+ instance_key = None
132
+
133
+ # First try as direct instance key
134
+ if skill_identifier in self.loaded_skills:
135
+ instance_key = skill_identifier
136
+ skill_instance = self.loaded_skills[skill_identifier]
137
+ else:
138
+ # Try to find by skill name (for backwards compatibility)
139
+ for key, instance in self.loaded_skills.items():
140
+ if instance.SKILL_NAME == skill_identifier:
141
+ instance_key = key
142
+ skill_instance = instance
143
+ break
144
+
145
+ if skill_instance is None:
146
+ self.logger.warning(f"Skill '{skill_identifier}' is not loaded")
114
147
  return False
115
148
 
116
149
  try:
117
- skill_instance = self.loaded_skills[skill_name]
118
150
  skill_instance.cleanup()
119
- del self.loaded_skills[skill_name]
120
- self.logger.info(f"Successfully unloaded skill '{skill_name}'")
151
+ del self.loaded_skills[instance_key]
152
+ self.logger.info(f"Successfully unloaded skill instance '{instance_key}'")
121
153
  return True
122
154
  except Exception as e:
123
- self.logger.error(f"Error unloading skill '{skill_name}': {e}")
155
+ self.logger.error(f"Error unloading skill '{skill_identifier}': {e}")
124
156
  return False
125
157
 
126
158
  def list_loaded_skills(self) -> List[str]:
127
- """List names of currently loaded skills"""
159
+ """List instance keys of currently loaded skills"""
128
160
  return list(self.loaded_skills.keys())
129
161
 
130
- def has_skill(self, skill_name: str) -> bool:
131
- """Check if skill is currently loaded"""
132
- return skill_name in self.loaded_skills
162
+ def has_skill(self, skill_identifier: str) -> bool:
163
+ """
164
+ Check if skill is currently loaded
165
+
166
+ Args:
167
+ skill_identifier: Either a skill name or an instance key
168
+
169
+ Returns:
170
+ bool: True if loaded, False otherwise
171
+ """
172
+ # First try as direct instance key
173
+ if skill_identifier in self.loaded_skills:
174
+ return True
175
+
176
+ # Try to find by skill name (for backwards compatibility)
177
+ for instance in self.loaded_skills.values():
178
+ if instance.SKILL_NAME == skill_identifier:
179
+ return True
180
+
181
+ return False
133
182
 
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)
183
+ def get_skill(self, skill_identifier: str) -> Optional[SkillBase]:
184
+ """
185
+ Get a loaded skill instance by identifier
186
+
187
+ Args:
188
+ skill_identifier: Either a skill name or an instance key
189
+
190
+ Returns:
191
+ SkillBase: The skill instance if found, None otherwise
192
+ """
193
+ # First try as direct instance key
194
+ if skill_identifier in self.loaded_skills:
195
+ return self.loaded_skills[skill_identifier]
196
+
197
+ # Try to find by skill name (for backwards compatibility)
198
+ for instance in self.loaded_skills.values():
199
+ if instance.SKILL_NAME == skill_identifier:
200
+ return instance
201
+
202
+ return None
@@ -27,7 +27,9 @@ class SWAIGFunction:
27
27
  description: str,
28
28
  parameters: Dict[str, Dict] = None,
29
29
  secure: bool = False,
30
- fillers: Optional[Dict[str, List[str]]] = None
30
+ fillers: Optional[Dict[str, List[str]]] = None,
31
+ webhook_url: Optional[str] = None,
32
+ **extra_swaig_fields
31
33
  ):
32
34
  """
33
35
  Initialize a new SWAIG function
@@ -39,6 +41,8 @@ class SWAIGFunction:
39
41
  parameters: Dictionary of parameters, keys are parameter names, values are param definitions
40
42
  secure: Whether this function requires token validation
41
43
  fillers: Optional dictionary of filler phrases by language code
44
+ webhook_url: Optional external webhook URL to use instead of local handling
45
+ **extra_swaig_fields: Additional SWAIG fields to include in function definition
42
46
  """
43
47
  self.name = name
44
48
  self.handler = handler
@@ -46,6 +50,11 @@ class SWAIGFunction:
46
50
  self.parameters = parameters or {}
47
51
  self.secure = secure
48
52
  self.fillers = fillers
53
+ self.webhook_url = webhook_url
54
+ self.extra_swaig_fields = extra_swaig_fields
55
+
56
+ # Mark as external if webhook_url is provided
57
+ self.is_external = webhook_url is not None
49
58
 
50
59
  def _ensure_parameter_structure(self) -> Dict:
51
60
  """
@@ -165,6 +174,9 @@ class SWAIGFunction:
165
174
  # Add fillers if provided
166
175
  if self.fillers and len(self.fillers) > 0:
167
176
  function_def["fillers"] = self.fillers
177
+
178
+ # Add any extra SWAIG fields
179
+ function_def.update(self.extra_swaig_fields)
168
180
 
169
181
  return function_def
170
182
 
@@ -93,17 +93,33 @@ class AIVerbHandler(SWMLVerbHandler):
93
93
  """
94
94
  errors = []
95
95
 
96
- # Check required fields
96
+ # Check that prompt is present
97
97
  if "prompt" not in config:
98
98
  errors.append("Missing required field 'prompt'")
99
+ return False, errors
99
100
 
100
- # Validate prompt structure if present
101
- if "prompt" in config:
102
- prompt = config["prompt"]
103
- if not isinstance(prompt, dict):
104
- errors.append("'prompt' must be an object")
105
- elif "text" not in prompt and "pom" not in prompt:
106
- errors.append("'prompt' must contain either 'text' or 'pom'")
101
+ prompt = config["prompt"]
102
+ if not isinstance(prompt, dict):
103
+ errors.append("'prompt' must be an object")
104
+ return False, errors
105
+
106
+ # Check that prompt contains one of: text, pom, or contexts
107
+ has_text = "text" in prompt
108
+ has_pom = "pom" in prompt
109
+ has_contexts = "contexts" in prompt
110
+
111
+ options_count = sum([has_text, has_pom, has_contexts])
112
+
113
+ if options_count == 0:
114
+ errors.append("'prompt' must contain one of: 'text', 'pom', or 'contexts'")
115
+ elif options_count > 1:
116
+ errors.append("'prompt' can only contain one of: 'text', 'pom', or 'contexts'")
117
+
118
+ # Validate contexts structure if present
119
+ if has_contexts:
120
+ contexts = prompt["contexts"]
121
+ if not isinstance(contexts, dict):
122
+ errors.append("'prompt.contexts' must be an object")
107
123
 
108
124
  # Validate SWAIG structure if present
109
125
  if "SWAIG" in config:
@@ -116,6 +132,7 @@ class AIVerbHandler(SWMLVerbHandler):
116
132
  def build_config(self,
117
133
  prompt_text: Optional[str] = None,
118
134
  prompt_pom: Optional[List[Dict[str, Any]]] = None,
135
+ contexts: Optional[Dict[str, Any]] = None,
119
136
  post_prompt: Optional[str] = None,
120
137
  post_prompt_url: Optional[str] = None,
121
138
  swaig: Optional[Dict[str, Any]] = None,
@@ -124,8 +141,9 @@ class AIVerbHandler(SWMLVerbHandler):
124
141
  Build a configuration for the AI verb
125
142
 
126
143
  Args:
127
- prompt_text: Text prompt for the AI (mutually exclusive with prompt_pom)
128
- prompt_pom: POM structure for the AI prompt (mutually exclusive with prompt_text)
144
+ prompt_text: Text prompt for the AI (mutually exclusive with prompt_pom and contexts)
145
+ prompt_pom: POM structure for the AI prompt (mutually exclusive with prompt_text and contexts)
146
+ contexts: Contexts and steps configuration (mutually exclusive with prompt_text and prompt_pom)
129
147
  post_prompt: Optional post-prompt text
130
148
  post_prompt_url: Optional URL for post-prompt processing
131
149
  swaig: Optional SWAIG configuration
@@ -136,13 +154,19 @@ class AIVerbHandler(SWMLVerbHandler):
136
154
  """
137
155
  config = {}
138
156
 
139
- # Add prompt (either text or POM)
157
+ # Add prompt (either text, POM, or contexts - mutually exclusive)
158
+ prompt_options_count = sum(x is not None for x in [prompt_text, prompt_pom, contexts])
159
+ if prompt_options_count == 0:
160
+ raise ValueError("One of prompt_text, prompt_pom, or contexts must be provided")
161
+ elif prompt_options_count > 1:
162
+ raise ValueError("prompt_text, prompt_pom, and contexts are mutually exclusive")
163
+
140
164
  if prompt_text is not None:
141
165
  config["prompt"] = {"text": prompt_text}
142
166
  elif prompt_pom is not None:
143
167
  config["prompt"] = {"pom": prompt_pom}
144
- else:
145
- raise ValueError("Either prompt_text or prompt_pom must be provided")
168
+ elif contexts is not None:
169
+ config["prompt"] = {"contexts": contexts}
146
170
 
147
171
  # Add post-prompt if provided
148
172
  if post_prompt is not None:
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env python3
1
2
  """
2
3
  Copyright (c) 2025 SignalWire
3
4
 
@@ -7,11 +8,9 @@ Licensed under the MIT License.
7
8
  See LICENSE file in the project root for full license information.
8
9
  """
9
10
 
11
+ # -*- coding: utf-8 -*-
10
12
  """
11
- SWMLService - Base class for SWML document creation and serving
12
-
13
- This class provides the foundation for creating and serving SWML documents.
14
- It handles schema validation, document creation, and web service functionality.
13
+ Base SWML Service for SignalWire Agents
15
14
  """
16
15
 
17
16
  import os
@@ -37,11 +36,11 @@ try:
37
36
  structlog.stdlib.add_logger_name,
38
37
  structlog.stdlib.add_log_level,
39
38
  structlog.stdlib.PositionalArgumentsFormatter(),
40
- structlog.processors.TimeStamper(fmt="iso"),
39
+ structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S"),
41
40
  structlog.processors.StackInfoRenderer(),
42
41
  structlog.processors.format_exc_info,
43
42
  structlog.processors.UnicodeDecoder(),
44
- structlog.processors.JSONRenderer()
43
+ structlog.dev.ConsoleRenderer()
45
44
  ],
46
45
  context_class=dict,
47
46
  logger_factory=structlog.stdlib.LoggerFactory(),
@@ -188,17 +187,21 @@ class SWMLService:
188
187
  """
189
188
  Create auto-vivified methods for all verbs at initialization time
190
189
  """
191
- print("Creating auto-vivified methods for all verbs")
190
+ self.log.debug("creating_verb_methods")
192
191
 
193
192
  # Get all verb names from the schema
193
+ if not self.schema_utils:
194
+ self.log.warning("no_schema_utils_available")
195
+ return
196
+
194
197
  verb_names = self.schema_utils.get_all_verb_names()
195
- print(f"Found {len(verb_names)} verbs in schema")
198
+ self.log.debug("found_verbs_in_schema", count=len(verb_names))
196
199
 
197
200
  # Create a method for each verb
198
201
  for verb_name in verb_names:
199
202
  # Skip verbs that already have specific methods
200
203
  if hasattr(self, verb_name):
201
- print(f"Skipping {verb_name} - already has a method")
204
+ self.log.debug("skipping_verb_has_method", verb=verb_name)
202
205
  continue
203
206
 
204
207
  # Handle sleep verb specially since it takes an integer directly
@@ -210,7 +213,7 @@ class SWMLService:
210
213
  Args:
211
214
  duration: The amount of time to sleep in milliseconds
212
215
  """
213
- print(f"Executing auto-vivified method for 'sleep'")
216
+ self.log.debug("executing_sleep_verb", duration=duration)
214
217
  # Sleep verb takes a direct integer parameter in SWML
215
218
  if duration is not None:
216
219
  return self_instance.add_verb("sleep", duration)
@@ -226,7 +229,7 @@ class SWMLService:
226
229
  # Also cache it for later
227
230
  self._verb_methods_cache[verb_name] = sleep_method
228
231
 
229
- print(f"Created special method for {verb_name}")
232
+ self.log.debug("created_special_method", verb=verb_name)
230
233
  continue
231
234
 
232
235
  # Generate the method implementation for normal verbs
@@ -235,7 +238,7 @@ class SWMLService:
235
238
  """
236
239
  Dynamically generated method for SWML verb
237
240
  """
238
- print(f"Executing auto-vivified method for '{name}'")
241
+ self.log.debug("executing_verb_method", verb=name, kwargs_count=len(kwargs))
239
242
  config = {}
240
243
  for key, value in kwargs.items():
241
244
  if value is not None:
@@ -260,7 +263,7 @@ class SWMLService:
260
263
  # Also cache it for later
261
264
  self._verb_methods_cache[verb_name] = method
262
265
 
263
- print(f"Created method for {verb_name}")
266
+ self.log.debug("created_verb_method", verb=verb_name)
264
267
 
265
268
  def __getattr__(self, name: str) -> Any:
266
269
  """
@@ -280,21 +283,26 @@ class SWMLService:
280
283
  Raises:
281
284
  AttributeError: If name is not a valid SWML verb
282
285
  """
283
- print(f"DEBUG: __getattr__ called for '{name}'")
286
+ self.log.debug("getattr_called", attribute=name)
284
287
 
285
288
  # Simple version to match our test script
286
289
  # First check if this is a valid SWML verb
290
+ if not self.schema_utils:
291
+ msg = f"'{self.__class__.__name__}' object has no attribute '{name}' (no schema available)"
292
+ self.log.debug("getattr_no_schema", attribute=name)
293
+ raise AttributeError(msg)
294
+
287
295
  verb_names = self.schema_utils.get_all_verb_names()
288
296
 
289
297
  if name in verb_names:
290
- print(f"DEBUG: '{name}' is a valid verb")
298
+ self.log.debug("getattr_valid_verb", verb=name)
291
299
 
292
300
  # Check if we already have this method in the cache
293
301
  if not hasattr(self, '_verb_methods_cache'):
294
302
  self._verb_methods_cache = {}
295
303
 
296
304
  if name in self._verb_methods_cache:
297
- print(f"DEBUG: Using cached method for '{name}'")
305
+ self.log.debug("getattr_cached_method", verb=name)
298
306
  return types.MethodType(self._verb_methods_cache[name], self)
299
307
 
300
308
  # Handle sleep verb specially since it takes an integer directly
@@ -306,7 +314,7 @@ class SWMLService:
306
314
  Args:
307
315
  duration: The amount of time to sleep in milliseconds
308
316
  """
309
- print(f"DEBUG: Executing auto-vivified method for 'sleep'")
317
+ self.log.debug("executing_sleep_method", duration=duration)
310
318
  # Sleep verb takes a direct integer parameter in SWML
311
319
  if duration is not None:
312
320
  return self_instance.add_verb("sleep", duration)
@@ -317,7 +325,7 @@ class SWMLService:
317
325
  raise TypeError("sleep() missing required argument: 'duration'")
318
326
 
319
327
  # Cache the method for future use
320
- print(f"DEBUG: Caching special method for '{name}'")
328
+ self.log.debug("caching_sleep_method", verb=name)
321
329
  self._verb_methods_cache[name] = sleep_method
322
330
 
323
331
  # Return the bound method
@@ -328,7 +336,7 @@ class SWMLService:
328
336
  """
329
337
  Dynamically generated method for SWML verb
330
338
  """
331
- print(f"DEBUG: Executing auto-vivified method for '{name}'")
339
+ self.log.debug("executing_dynamic_verb", verb=name, kwargs_count=len(kwargs))
332
340
  config = {}
333
341
  for key, value in kwargs.items():
334
342
  if value is not None:
@@ -343,7 +351,7 @@ class SWMLService:
343
351
  verb_method.__doc__ = f"Add the {name} verb to the document."
344
352
 
345
353
  # Cache the method for future use
346
- print(f"DEBUG: Caching method for '{name}'")
354
+ self.log.debug("caching_verb_method", verb=name)
347
355
  self._verb_methods_cache[name] = verb_method
348
356
 
349
357
  # Return the bound method
@@ -351,7 +359,7 @@ class SWMLService:
351
359
 
352
360
  # Not a valid verb
353
361
  msg = f"'{self.__class__.__name__}' object has no attribute '{name}'"
354
- print(f"DEBUG: {msg}")
362
+ self.log.debug("getattr_invalid_attribute", attribute=name, error=msg)
355
363
  raise AttributeError(msg)
356
364
 
357
365
  def _find_schema_path(self) -> Optional[str]:
@@ -823,7 +831,7 @@ class SWMLService:
823
831
  route_with_slash = route_path + "/"
824
832
 
825
833
  # Log the incoming path for debugging
826
- print(f"Catch-all received: '{full_path}', route: '{route_path}'")
834
+ self.log.debug("catch_all_route_hit", path=full_path, route=route_path)
827
835
 
828
836
  # Check for exact match to our route (without trailing slash)
829
837
  if full_path == route_path:
@@ -851,21 +859,21 @@ class SWMLService:
851
859
  return await self._handle_request(request, response)
852
860
 
853
861
  # Not our route or not matching our patterns
854
- print(f"No match for path: '{full_path}'")
862
+ self.log.debug("no_route_match", path=full_path)
855
863
  return {"error": "Path not found"}
856
864
 
857
- # Print all routes for debugging
858
- print(f"All routes for {self.name}:")
865
+ # Log all routes for debugging
866
+ self.log.debug("registered_routes", service=self.name)
859
867
  for route in app.routes:
860
868
  if hasattr(route, "path"):
861
- print(f" {route.path}")
869
+ self.log.debug("route_registered", path=route.path)
862
870
 
863
871
  self._app = app
864
872
 
865
873
  host = host or self.host
866
874
  port = port or self.port
867
875
 
868
- # Print the auth credentials
876
+ # Get the auth credentials
869
877
  username, password = self._basic_auth
870
878
 
871
879
  # Use correct protocol and host in displayed URL
@@ -878,12 +886,13 @@ class SWMLService:
878
886
  username=username,
879
887
  password_length=len(password))
880
888
 
889
+ # Print user-friendly startup message (keep for UX)
881
890
  print(f"Service '{self.name}' is available at:")
882
891
  print(f"URL: {protocol}://{display_host}{self.route}")
883
892
  print(f"URL with trailing slash: {protocol}://{display_host}{self.route}/")
884
893
  print(f"Basic Auth: {username}:{password}")
885
894
 
886
- # Check if SIP routing is enabled and print additional info
895
+ # Check if SIP routing is enabled and log additional info
887
896
  if self._routing_callbacks:
888
897
  print(f"Callback endpoints:")
889
898
  for path in self._routing_callbacks:
@@ -0,0 +1,12 @@
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 .skill import DataSphereSkill
11
+
12
+ __all__ = ['DataSphereSkill']