signalwire-agents 0.1.13__py3-none-any.whl → 1.0.17.dev4__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 (143) hide show
  1. signalwire_agents/__init__.py +99 -15
  2. signalwire_agents/agent_server.py +248 -60
  3. signalwire_agents/agents/bedrock.py +296 -0
  4. signalwire_agents/cli/__init__.py +9 -0
  5. signalwire_agents/cli/build_search.py +951 -41
  6. signalwire_agents/cli/config.py +80 -0
  7. signalwire_agents/cli/core/__init__.py +10 -0
  8. signalwire_agents/cli/core/agent_loader.py +470 -0
  9. signalwire_agents/cli/core/argparse_helpers.py +179 -0
  10. signalwire_agents/cli/core/dynamic_config.py +71 -0
  11. signalwire_agents/cli/core/service_loader.py +303 -0
  12. signalwire_agents/cli/dokku.py +2320 -0
  13. signalwire_agents/cli/execution/__init__.py +10 -0
  14. signalwire_agents/cli/execution/datamap_exec.py +446 -0
  15. signalwire_agents/cli/execution/webhook_exec.py +134 -0
  16. signalwire_agents/cli/init_project.py +2636 -0
  17. signalwire_agents/cli/output/__init__.py +10 -0
  18. signalwire_agents/cli/output/output_formatter.py +255 -0
  19. signalwire_agents/cli/output/swml_dump.py +186 -0
  20. signalwire_agents/cli/simulation/__init__.py +10 -0
  21. signalwire_agents/cli/simulation/data_generation.py +374 -0
  22. signalwire_agents/cli/simulation/data_overrides.py +200 -0
  23. signalwire_agents/cli/simulation/mock_env.py +282 -0
  24. signalwire_agents/cli/swaig_test_wrapper.py +52 -0
  25. signalwire_agents/cli/test_swaig.py +566 -2366
  26. signalwire_agents/cli/types.py +81 -0
  27. signalwire_agents/core/__init__.py +2 -2
  28. signalwire_agents/core/agent/__init__.py +12 -0
  29. signalwire_agents/core/agent/config/__init__.py +12 -0
  30. signalwire_agents/core/agent/deployment/__init__.py +9 -0
  31. signalwire_agents/core/agent/deployment/handlers/__init__.py +9 -0
  32. signalwire_agents/core/agent/prompt/__init__.py +14 -0
  33. signalwire_agents/core/agent/prompt/manager.py +306 -0
  34. signalwire_agents/core/agent/routing/__init__.py +9 -0
  35. signalwire_agents/core/agent/security/__init__.py +9 -0
  36. signalwire_agents/core/agent/swml/__init__.py +9 -0
  37. signalwire_agents/core/agent/tools/__init__.py +15 -0
  38. signalwire_agents/core/agent/tools/decorator.py +97 -0
  39. signalwire_agents/core/agent/tools/registry.py +210 -0
  40. signalwire_agents/core/agent_base.py +845 -2916
  41. signalwire_agents/core/auth_handler.py +233 -0
  42. signalwire_agents/core/config_loader.py +259 -0
  43. signalwire_agents/core/contexts.py +418 -0
  44. signalwire_agents/core/data_map.py +3 -15
  45. signalwire_agents/core/function_result.py +116 -44
  46. signalwire_agents/core/logging_config.py +162 -18
  47. signalwire_agents/core/mixins/__init__.py +28 -0
  48. signalwire_agents/core/mixins/ai_config_mixin.py +442 -0
  49. signalwire_agents/core/mixins/auth_mixin.py +280 -0
  50. signalwire_agents/core/mixins/prompt_mixin.py +358 -0
  51. signalwire_agents/core/mixins/serverless_mixin.py +460 -0
  52. signalwire_agents/core/mixins/skill_mixin.py +55 -0
  53. signalwire_agents/core/mixins/state_mixin.py +153 -0
  54. signalwire_agents/core/mixins/tool_mixin.py +230 -0
  55. signalwire_agents/core/mixins/web_mixin.py +1142 -0
  56. signalwire_agents/core/security_config.py +333 -0
  57. signalwire_agents/core/skill_base.py +84 -1
  58. signalwire_agents/core/skill_manager.py +62 -20
  59. signalwire_agents/core/swaig_function.py +18 -5
  60. signalwire_agents/core/swml_builder.py +207 -11
  61. signalwire_agents/core/swml_handler.py +27 -21
  62. signalwire_agents/core/swml_renderer.py +123 -312
  63. signalwire_agents/core/swml_service.py +171 -203
  64. signalwire_agents/mcp_gateway/__init__.py +29 -0
  65. signalwire_agents/mcp_gateway/gateway_service.py +564 -0
  66. signalwire_agents/mcp_gateway/mcp_manager.py +513 -0
  67. signalwire_agents/mcp_gateway/session_manager.py +218 -0
  68. signalwire_agents/prefabs/concierge.py +0 -3
  69. signalwire_agents/prefabs/faq_bot.py +0 -3
  70. signalwire_agents/prefabs/info_gatherer.py +0 -3
  71. signalwire_agents/prefabs/receptionist.py +0 -3
  72. signalwire_agents/prefabs/survey.py +0 -3
  73. signalwire_agents/schema.json +9218 -5489
  74. signalwire_agents/search/__init__.py +7 -1
  75. signalwire_agents/search/document_processor.py +490 -31
  76. signalwire_agents/search/index_builder.py +307 -37
  77. signalwire_agents/search/migration.py +418 -0
  78. signalwire_agents/search/models.py +30 -0
  79. signalwire_agents/search/pgvector_backend.py +748 -0
  80. signalwire_agents/search/query_processor.py +162 -31
  81. signalwire_agents/search/search_engine.py +916 -35
  82. signalwire_agents/search/search_service.py +376 -53
  83. signalwire_agents/skills/README.md +452 -0
  84. signalwire_agents/skills/__init__.py +14 -2
  85. signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
  86. signalwire_agents/skills/api_ninjas_trivia/__init__.py +12 -0
  87. signalwire_agents/skills/api_ninjas_trivia/skill.py +237 -0
  88. signalwire_agents/skills/datasphere/README.md +210 -0
  89. signalwire_agents/skills/datasphere/skill.py +84 -3
  90. signalwire_agents/skills/datasphere_serverless/README.md +258 -0
  91. signalwire_agents/skills/datasphere_serverless/__init__.py +9 -0
  92. signalwire_agents/skills/datasphere_serverless/skill.py +82 -1
  93. signalwire_agents/skills/datetime/README.md +132 -0
  94. signalwire_agents/skills/datetime/__init__.py +9 -0
  95. signalwire_agents/skills/datetime/skill.py +20 -7
  96. signalwire_agents/skills/joke/README.md +149 -0
  97. signalwire_agents/skills/joke/__init__.py +9 -0
  98. signalwire_agents/skills/joke/skill.py +21 -0
  99. signalwire_agents/skills/math/README.md +161 -0
  100. signalwire_agents/skills/math/__init__.py +9 -0
  101. signalwire_agents/skills/math/skill.py +18 -4
  102. signalwire_agents/skills/mcp_gateway/README.md +230 -0
  103. signalwire_agents/skills/mcp_gateway/__init__.py +10 -0
  104. signalwire_agents/skills/mcp_gateway/skill.py +421 -0
  105. signalwire_agents/skills/native_vector_search/README.md +210 -0
  106. signalwire_agents/skills/native_vector_search/__init__.py +9 -0
  107. signalwire_agents/skills/native_vector_search/skill.py +569 -101
  108. signalwire_agents/skills/play_background_file/README.md +218 -0
  109. signalwire_agents/skills/play_background_file/__init__.py +12 -0
  110. signalwire_agents/skills/play_background_file/skill.py +242 -0
  111. signalwire_agents/skills/registry.py +395 -40
  112. signalwire_agents/skills/spider/README.md +236 -0
  113. signalwire_agents/skills/spider/__init__.py +13 -0
  114. signalwire_agents/skills/spider/skill.py +598 -0
  115. signalwire_agents/skills/swml_transfer/README.md +395 -0
  116. signalwire_agents/skills/swml_transfer/__init__.py +10 -0
  117. signalwire_agents/skills/swml_transfer/skill.py +359 -0
  118. signalwire_agents/skills/weather_api/README.md +178 -0
  119. signalwire_agents/skills/weather_api/__init__.py +12 -0
  120. signalwire_agents/skills/weather_api/skill.py +191 -0
  121. signalwire_agents/skills/web_search/README.md +163 -0
  122. signalwire_agents/skills/web_search/__init__.py +9 -0
  123. signalwire_agents/skills/web_search/skill.py +586 -112
  124. signalwire_agents/skills/wikipedia_search/README.md +228 -0
  125. signalwire_agents/{core/state → skills/wikipedia_search}/__init__.py +5 -4
  126. signalwire_agents/skills/{wikipedia → wikipedia_search}/skill.py +33 -3
  127. signalwire_agents/web/__init__.py +17 -0
  128. signalwire_agents/web/web_service.py +559 -0
  129. signalwire_agents-1.0.17.dev4.data/data/share/man/man1/sw-agent-init.1 +400 -0
  130. signalwire_agents-1.0.17.dev4.data/data/share/man/man1/sw-search.1 +483 -0
  131. signalwire_agents-1.0.17.dev4.data/data/share/man/man1/swaig-test.1 +308 -0
  132. {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/METADATA +347 -215
  133. signalwire_agents-1.0.17.dev4.dist-info/RECORD +147 -0
  134. signalwire_agents-1.0.17.dev4.dist-info/entry_points.txt +6 -0
  135. signalwire_agents/core/state/file_state_manager.py +0 -219
  136. signalwire_agents/core/state/state_manager.py +0 -101
  137. signalwire_agents/skills/wikipedia/__init__.py +0 -9
  138. signalwire_agents-0.1.13.data/data/schema.json +0 -5611
  139. signalwire_agents-0.1.13.dist-info/RECORD +0 -67
  140. signalwire_agents-0.1.13.dist-info/entry_points.txt +0 -3
  141. {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/WHEEL +0 -0
  142. {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/licenses/LICENSE +0 -0
  143. {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,359 @@
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 List, Dict, Any
11
+ import re
12
+
13
+ from signalwire_agents.core.skill_base import SkillBase
14
+ from signalwire_agents.core.data_map import DataMap
15
+ from signalwire_agents.core.function_result import SwaigFunctionResult
16
+
17
+
18
+ class SWMLTransferSkill(SkillBase):
19
+ """Skill for transferring calls between agents using SWML with pattern matching"""
20
+
21
+ SKILL_NAME = "swml_transfer"
22
+ SKILL_DESCRIPTION = "Transfer calls between agents based on pattern matching"
23
+ SKILL_VERSION = "1.0.0"
24
+ REQUIRED_PACKAGES = []
25
+ REQUIRED_ENV_VARS = []
26
+
27
+ # Enable multiple instances support
28
+ SUPPORTS_MULTIPLE_INSTANCES = True
29
+
30
+ @classmethod
31
+ def get_parameter_schema(cls) -> Dict[str, Dict[str, Any]]:
32
+ """Get parameter schema for SWML Transfer skill"""
33
+ schema = super().get_parameter_schema()
34
+ schema.update({
35
+ "transfers": {
36
+ "type": "object",
37
+ "description": "Transfer configurations mapping patterns to destinations",
38
+ "required": True,
39
+ "additionalProperties": {
40
+ "type": "object",
41
+ "properties": {
42
+ "url": {
43
+ "type": "string",
44
+ "description": "SWML endpoint URL for agent transfer"
45
+ },
46
+ "address": {
47
+ "type": "string",
48
+ "description": "Phone number or SIP address for direct connect"
49
+ },
50
+ "message": {
51
+ "type": "string",
52
+ "description": "Message to say before transferring",
53
+ "default": "Transferring you now..."
54
+ },
55
+ "return_message": {
56
+ "type": "string",
57
+ "description": "Message when returning from transfer",
58
+ "default": "The transfer is complete. How else can I help you?"
59
+ },
60
+ "post_process": {
61
+ "type": "boolean",
62
+ "description": "Whether to process message with AI before saying",
63
+ "default": True
64
+ },
65
+ "final": {
66
+ "type": "boolean",
67
+ "description": "Whether transfer is permanent (true) or temporary (false)",
68
+ "default": True
69
+ },
70
+ "from_addr": {
71
+ "type": "string",
72
+ "description": "Caller ID for connect action (optional)"
73
+ }
74
+ }
75
+ }
76
+ },
77
+ "description": {
78
+ "type": "string",
79
+ "description": "Description for the transfer tool",
80
+ "default": "Transfer call based on pattern matching",
81
+ "required": False
82
+ },
83
+ "parameter_name": {
84
+ "type": "string",
85
+ "description": "Name of the parameter that accepts the transfer type",
86
+ "default": "transfer_type",
87
+ "required": False
88
+ },
89
+ "parameter_description": {
90
+ "type": "string",
91
+ "description": "Description for the transfer type parameter",
92
+ "default": "The type of transfer to perform",
93
+ "required": False
94
+ },
95
+ "default_message": {
96
+ "type": "string",
97
+ "description": "Message when no pattern matches",
98
+ "default": "Please specify a valid transfer type.",
99
+ "required": False
100
+ },
101
+ "default_post_process": {
102
+ "type": "boolean",
103
+ "description": "Whether to process default message with AI",
104
+ "default": False,
105
+ "required": False
106
+ },
107
+ "required_fields": {
108
+ "type": "object",
109
+ "description": "Additional required fields to collect before transfer",
110
+ "default": {},
111
+ "required": False,
112
+ "additionalProperties": {
113
+ "type": "string",
114
+ "description": "Field description"
115
+ }
116
+ }
117
+ })
118
+ return schema
119
+
120
+ def get_instance_key(self) -> str:
121
+ """
122
+ Get the key used to track this skill instance
123
+
124
+ For SWML transfer, we use the tool name to differentiate instances
125
+ """
126
+ tool_name = self.params.get('tool_name', 'transfer_call')
127
+ return f"{self.SKILL_NAME}_{tool_name}"
128
+
129
+ def setup(self) -> bool:
130
+ """Setup and validate skill configuration"""
131
+ # Validate required parameters
132
+ required_params = ['transfers']
133
+ missing_params = [param for param in required_params if not self.params.get(param)]
134
+ if missing_params:
135
+ self.logger.error(f"Missing required parameters: {missing_params}")
136
+ return False
137
+
138
+ # Validate transfers structure
139
+ transfers = self.params.get('transfers', {})
140
+ if not isinstance(transfers, dict):
141
+ self.logger.error("'transfers' parameter must be a dictionary")
142
+ return False
143
+
144
+ # Validate each transfer configuration
145
+ for pattern, config in transfers.items():
146
+ if not isinstance(config, dict):
147
+ self.logger.error(f"Transfer config for pattern '{pattern}' must be a dictionary")
148
+ return False
149
+
150
+ # Must have either 'url' or 'address'
151
+ if 'url' not in config and 'address' not in config:
152
+ self.logger.error(f"Transfer config for pattern '{pattern}' must include either 'url' or 'address'")
153
+ return False
154
+
155
+ if 'url' in config and 'address' in config:
156
+ self.logger.error(f"Transfer config for pattern '{pattern}' cannot have both 'url' and 'address'")
157
+ return False
158
+
159
+ # Set defaults for optional fields
160
+ config.setdefault('message', f"Transferring you now...")
161
+ config.setdefault('return_message', "The transfer is complete. How else can I help you?")
162
+ config.setdefault('post_process', True)
163
+ config.setdefault('final', True) # Default to permanent transfer
164
+ # from_addr is optional for connect action only
165
+
166
+ # Store configuration
167
+ self.tool_name = self.params.get('tool_name', 'transfer_call')
168
+ self.description = self.params.get('description', 'Transfer call based on pattern matching')
169
+ self.parameter_name = self.params.get('parameter_name', 'transfer_type')
170
+ self.parameter_description = self.params.get('parameter_description', 'The type of transfer to perform')
171
+ self.transfers = transfers
172
+ self.default_message = self.params.get('default_message', 'Please specify a valid transfer type.')
173
+ self.default_post_process = self.params.get('default_post_process', False)
174
+
175
+ # Required fields configuration
176
+ self.required_fields = self.params.get('required_fields', {})
177
+
178
+ return True
179
+
180
+ def register_tools(self) -> None:
181
+ """Register the transfer tool with pattern matching"""
182
+
183
+ # Build the DataMap tool with all patterns
184
+ transfer_tool = (DataMap(self.tool_name)
185
+ .description(self.description)
186
+ .parameter(self.parameter_name, 'string', self.parameter_description, required=True)
187
+ )
188
+
189
+ # Add required fields as parameters
190
+ for field_name, field_description in self.required_fields.items():
191
+ transfer_tool = transfer_tool.parameter(
192
+ field_name,
193
+ 'string',
194
+ field_description,
195
+ required=True
196
+ )
197
+
198
+ # Add expression for each pattern
199
+ for pattern, config in self.transfers.items():
200
+ # Create the function result with transfer
201
+ result = SwaigFunctionResult(
202
+ config['message'],
203
+ post_process=config['post_process']
204
+ )
205
+
206
+ # Add required fields to global data under call_data key
207
+ if self.required_fields:
208
+ call_data = {}
209
+ for field_name in self.required_fields.keys():
210
+ call_data[field_name] = f"${{args.{field_name}}}"
211
+ result = result.update_global_data({
212
+ "call_data": call_data
213
+ })
214
+
215
+ # Add the transfer action based on whether url or address is provided
216
+ if 'url' in config:
217
+ # Use swml_transfer for SWML endpoints
218
+ result = result.swml_transfer(
219
+ config['url'],
220
+ config['return_message'],
221
+ config.get('final', True)
222
+ )
223
+ else:
224
+ # Use connect for addresses (phone numbers, SIP, etc.)
225
+ result = result.connect(
226
+ config['address'],
227
+ config.get('final', True),
228
+ config.get('from_addr', None)
229
+ )
230
+
231
+ # Add the expression to the DataMap
232
+ transfer_tool = transfer_tool.expression(
233
+ f'${{{f"args.{self.parameter_name}"}}}',
234
+ pattern,
235
+ result
236
+ )
237
+
238
+ # Add default fallback expression
239
+ default_result = SwaigFunctionResult(
240
+ self.default_message,
241
+ post_process=self.default_post_process
242
+ )
243
+
244
+ # For fallback, still save required fields if provided
245
+ if self.required_fields:
246
+ call_data = {}
247
+ for field_name in self.required_fields.keys():
248
+ call_data[field_name] = f"${{args.{field_name}}}"
249
+ default_result = default_result.update_global_data({
250
+ "call_data": call_data
251
+ })
252
+
253
+ transfer_tool = transfer_tool.expression(
254
+ f'${{{f"args.{self.parameter_name}"}}}',
255
+ r'/.*/', # Match anything as fallback
256
+ default_result
257
+ )
258
+
259
+ # Register the tool with the agent
260
+ if hasattr(self.agent, 'register_swaig_function'):
261
+ self.agent.register_swaig_function(transfer_tool.to_swaig_function())
262
+ else:
263
+ # Fallback to define_tool if register_swaig_function is not available
264
+ self.logger.error("Agent does not have register_swaig_function method")
265
+
266
+ def get_hints(self) -> List[str]:
267
+ """Return speech recognition hints"""
268
+ hints = []
269
+
270
+ # Extract common words from patterns for hints
271
+ for pattern in self.transfers.keys():
272
+ # Remove regex delimiters and flags
273
+ clean_pattern = pattern
274
+ # Remove leading and trailing slashes
275
+ if clean_pattern.startswith('/'):
276
+ clean_pattern = clean_pattern[1:]
277
+ if clean_pattern.endswith('/'):
278
+ clean_pattern = clean_pattern[:-1]
279
+ # Remove flags after the pattern (e.g., 'i' for case-insensitive)
280
+ elif clean_pattern.endswith('/i'):
281
+ clean_pattern = clean_pattern[:-2]
282
+
283
+ # Only add if it's not a catch-all pattern
284
+ if clean_pattern and not clean_pattern.startswith('.'):
285
+ # Handle patterns with alternatives (e.g., "sales|billing")
286
+ if '|' in clean_pattern:
287
+ for part in clean_pattern.split('|'):
288
+ hints.append(part.strip().lower())
289
+ else:
290
+ hints.append(clean_pattern.lower())
291
+
292
+ # Add common transfer-related words
293
+ hints.extend(['transfer', 'connect', 'speak to', 'talk to'])
294
+
295
+ return hints
296
+
297
+ def get_prompt_sections(self) -> List[Dict[str, Any]]:
298
+ """Return prompt sections to add to agent"""
299
+ sections = []
300
+
301
+ # Build a list of transfer destinations with their patterns
302
+ if self.transfers:
303
+ transfer_bullets = []
304
+
305
+ for pattern, config in self.transfers.items():
306
+ # Extract meaningful name from pattern
307
+ # Remove regex delimiters and flags
308
+ clean_pattern = pattern
309
+ # Remove leading and trailing slashes
310
+ if clean_pattern.startswith('/'):
311
+ clean_pattern = clean_pattern[1:]
312
+ if clean_pattern.endswith('/'):
313
+ clean_pattern = clean_pattern[:-1]
314
+ # Remove flags after the pattern (e.g., 'i' for case-insensitive)
315
+ elif clean_pattern.endswith('/i'):
316
+ clean_pattern = clean_pattern[:-2]
317
+
318
+ # Only add if it's not a catch-all pattern
319
+ if clean_pattern and not clean_pattern.startswith('.'):
320
+ # Create a description for this transfer destination
321
+ if 'url' in config:
322
+ destination = config['url']
323
+ else:
324
+ destination = config['address']
325
+ transfer_desc = f'"{clean_pattern}" - transfers to {destination}'
326
+ transfer_bullets.append(transfer_desc)
327
+
328
+ # Add the Transferring section
329
+ sections.append({
330
+ "title": "Transferring",
331
+ "body": f"You can transfer calls using the {self.tool_name} function with the following destinations:",
332
+ "bullets": transfer_bullets
333
+ })
334
+
335
+ # Add usage instructions
336
+ bullets = [
337
+ f"Use the {self.tool_name} function when a transfer is needed",
338
+ f"Pass the destination type to the '{self.parameter_name}' parameter"
339
+ ]
340
+
341
+ # Add required fields instructions if configured
342
+ if self.required_fields:
343
+ bullets.append("You must provide the following information before transferring:")
344
+ for field_name, field_description in self.required_fields.items():
345
+ bullets.append(f" - {field_name}: {field_description}")
346
+ bullets.append("All required information will be saved under 'call_data' for the next agent")
347
+
348
+ bullets.extend([
349
+ f"The system will match patterns and handle the transfer automatically",
350
+ "After transfer completes, you'll regain control of the conversation"
351
+ ])
352
+
353
+ sections.append({
354
+ "title": "Transfer Instructions",
355
+ "body": f"How to use the transfer capability:",
356
+ "bullets": bullets
357
+ })
358
+
359
+ return sections
@@ -0,0 +1,178 @@
1
+ # Weather API Skill
2
+
3
+ A configurable skill for getting current weather information from WeatherAPI.com with customizable temperature units and TTS-optimized natural language responses.
4
+
5
+ ## Features
6
+
7
+ - **Configurable Temperature Units**: Choose between Fahrenheit and Celsius
8
+ - **TTS-Friendly Responses**: Natural language numbers without abbreviations or symbols
9
+ - **Real-time Weather Data**: Current conditions via WeatherAPI.com
10
+ - **DataMap Efficiency**: Serverless webhook execution, no agent processing load
11
+ - **Error Handling**: Graceful fallback for API failures or invalid locations
12
+ - **Comprehensive Weather Info**: Temperature, conditions, wind, clouds, feels-like
13
+
14
+ ## Configuration
15
+
16
+ ### Basic Structure
17
+
18
+ ```python
19
+ agent.add_skill("weather_api", {
20
+ "tool_name": "get_weather",
21
+ "api_key": "your_weatherapi_key",
22
+ "temperature_unit": "fahrenheit" # or "celsius"
23
+ })
24
+ ```
25
+
26
+ ### Parameters
27
+
28
+ - **tool_name** (string, optional): Custom name for the generated SWAIG function (default: "get_weather")
29
+ - **api_key** (string, required): Your WeatherAPI.com API key
30
+ - **temperature_unit** (string, optional): "fahrenheit" or "celsius" (default: "fahrenheit")
31
+
32
+ ## Usage Examples
33
+
34
+ ### Fahrenheit Weather (Default)
35
+
36
+ ```python
37
+ agent.add_skill("weather_api", {
38
+ "tool_name": "get_weather",
39
+ "api_key": "your_weatherapi_key"
40
+ })
41
+ ```
42
+
43
+ **Generated Tool**: `get_weather(location)`
44
+ **Temperature Format**: "seventy two degrees Fahrenheit"
45
+
46
+ ### Celsius Weather
47
+
48
+ ```python
49
+ agent.add_skill("weather_api", {
50
+ "tool_name": "get_weather_celsius",
51
+ "api_key": "your_weatherapi_key",
52
+ "temperature_unit": "celsius"
53
+ })
54
+ ```
55
+
56
+ **Generated Tool**: `get_weather_celsius(location)`
57
+ **Temperature Format**: "twenty two degrees Celsius"
58
+
59
+ ### Custom Tool Name
60
+
61
+ ```python
62
+ agent.add_skill("weather_api", {
63
+ "tool_name": "check_current_weather",
64
+ "api_key": "your_weatherapi_key",
65
+ "temperature_unit": "fahrenheit"
66
+ })
67
+ ```
68
+
69
+ **Generated Tool**: `check_current_weather(location)`
70
+
71
+ ## Generated SWAIG Function
72
+
73
+ For the Fahrenheit example above, the skill generates:
74
+
75
+ ```json
76
+ {
77
+ "function": "get_weather",
78
+ "description": "Get current weather information for any location",
79
+ "parameters": {
80
+ "type": "object",
81
+ "properties": {
82
+ "location": {
83
+ "type": "string",
84
+ "description": "The city, state, country, or location to get weather for"
85
+ }
86
+ },
87
+ "required": ["location"]
88
+ },
89
+ "data_map": {
90
+ "webhook": {
91
+ "url": "https://api.weatherapi.com/v1/current.json?key=your_api_key&q=%{enc:args.location}&aqi=no",
92
+ "method": "GET"
93
+ },
94
+ "output": {
95
+ "response": "Tell the user the current weather conditions. Express all temperatures in Fahrenheit using natural language numbers without abbreviations or symbols for clear text-to-speech pronunciation. For example, say 'seventy two degrees Fahrenheit' instead of '72F' or '72°F'. Include the condition, current temperature, wind direction and speed, cloud coverage percentage, and what the temperature feels like. Current conditions: ${current.condition.text}. Temperature: ${current.temp_f} degrees Fahrenheit. Wind: ${current.wind_dir} at ${current.wind_mph} miles per hour. Cloud coverage: ${current.cloud} percent. Feels like: ${current.feelslike_f} degrees Fahrenheit."
96
+ },
97
+ "error_keys": ["error"],
98
+ "fallback_output": {
99
+ "response": "Sorry, I cannot get weather information right now. Please try again later or check if the location name is correct."
100
+ }
101
+ }
102
+ }
103
+ ```
104
+
105
+ ## TTS Optimization
106
+
107
+ The skill is specifically designed for text-to-speech systems:
108
+
109
+ ### Natural Language Numbers
110
+ - ✅ "seventy two degrees Fahrenheit"
111
+ - ❌ "72F" or "72°F"
112
+
113
+ ### Full Pronunciation
114
+ - ✅ "twenty two degrees Celsius"
115
+ - ❌ "22C" or "22°C"
116
+
117
+ ### Complete Weather Description
118
+ - Current conditions description
119
+ - Temperature in natural language
120
+ - Wind direction and speed
121
+ - Cloud coverage percentage
122
+ - Feels-like temperature
123
+
124
+ ## Execution Flow
125
+
126
+ 1. **AI calls function**: `get_weather(location: "New York")`
127
+ 2. **DataMap webhook**: `GET https://api.weatherapi.com/v1/current.json?key=...&q=New%20York&aqi=no`
128
+ 3. **API response**: Weather data with current conditions
129
+ 4. **AI responds**: "Tell the user the current weather conditions. Express all temperatures in Fahrenheit using natural language numbers... Current conditions: Partly cloudy. Temperature: seventy two degrees Fahrenheit. Wind: Southwest at fifteen miles per hour. Cloud coverage: twenty five percent. Feels like: seventy five degrees Fahrenheit."
130
+
131
+ ## WeatherAPI.com Integration
132
+
133
+ The skill integrates with WeatherAPI.com's current weather endpoint:
134
+
135
+ - **Endpoint**: `https://api.weatherapi.com/v1/current.json`
136
+ - **Parameters**: `key`, `q` (location), `aqi=no`
137
+ - **Response**: Real-time weather data including temperature, conditions, wind, clouds
138
+ - **Location Support**: Cities, states, countries, coordinates, airports, etc.
139
+
140
+ ## Temperature Unit Support
141
+
142
+ ### Fahrenheit Configuration
143
+ ```python
144
+ "temperature_unit": "fahrenheit"
145
+ ```
146
+ - Uses `temp_f` and `feelslike_f` fields
147
+ - Displays as "degrees Fahrenheit"
148
+
149
+ ### Celsius Configuration
150
+ ```python
151
+ "temperature_unit": "celsius"
152
+ ```
153
+ - Uses `temp_c` and `feelslike_c` fields
154
+ - Displays as "degrees Celsius"
155
+
156
+ ## Error Handling
157
+
158
+ - **Invalid API Key**: Validation during skill initialization
159
+ - **Invalid Temperature Unit**: Must be "fahrenheit" or "celsius"
160
+ - **API Failures**: Graceful fallback response for network/API issues
161
+ - **Invalid Locations**: Fallback suggests checking location name
162
+
163
+ ## Benefits
164
+
165
+ - **TTS Optimized**: Natural language responses perfect for voice agents
166
+ - **Configurable**: Easy temperature unit switching without code changes
167
+ - **Efficient**: DataMap webhook execution, no agent processing load
168
+ - **Reliable**: Real-time data from established weather service
169
+ - **User Friendly**: Clear error messages and location flexibility
170
+ - **Professional**: Production-ready with comprehensive error handling
171
+
172
+ ## Implementation Notes
173
+
174
+ - Temperature unit determines which API fields to use (`temp_f`/`temp_c`, `feelslike_f`/`feelslike_c`)
175
+ - Response includes detailed TTS instructions for natural pronunciation
176
+ - URL encoding automatically applied to location parameter
177
+ - Wind speed always reported in miles per hour regardless of temperature unit
178
+ - No air quality data requested to keep responses focused
@@ -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 WeatherApiSkill
11
+
12
+ __all__ = ['WeatherApiSkill']