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.
- signalwire_agents/__init__.py +99 -15
- signalwire_agents/agent_server.py +248 -60
- signalwire_agents/agents/bedrock.py +296 -0
- signalwire_agents/cli/__init__.py +9 -0
- signalwire_agents/cli/build_search.py +951 -41
- signalwire_agents/cli/config.py +80 -0
- signalwire_agents/cli/core/__init__.py +10 -0
- signalwire_agents/cli/core/agent_loader.py +470 -0
- signalwire_agents/cli/core/argparse_helpers.py +179 -0
- signalwire_agents/cli/core/dynamic_config.py +71 -0
- signalwire_agents/cli/core/service_loader.py +303 -0
- signalwire_agents/cli/dokku.py +2320 -0
- signalwire_agents/cli/execution/__init__.py +10 -0
- signalwire_agents/cli/execution/datamap_exec.py +446 -0
- signalwire_agents/cli/execution/webhook_exec.py +134 -0
- signalwire_agents/cli/init_project.py +2636 -0
- signalwire_agents/cli/output/__init__.py +10 -0
- signalwire_agents/cli/output/output_formatter.py +255 -0
- signalwire_agents/cli/output/swml_dump.py +186 -0
- signalwire_agents/cli/simulation/__init__.py +10 -0
- signalwire_agents/cli/simulation/data_generation.py +374 -0
- signalwire_agents/cli/simulation/data_overrides.py +200 -0
- signalwire_agents/cli/simulation/mock_env.py +282 -0
- signalwire_agents/cli/swaig_test_wrapper.py +52 -0
- signalwire_agents/cli/test_swaig.py +566 -2366
- signalwire_agents/cli/types.py +81 -0
- signalwire_agents/core/__init__.py +2 -2
- signalwire_agents/core/agent/__init__.py +12 -0
- signalwire_agents/core/agent/config/__init__.py +12 -0
- signalwire_agents/core/agent/deployment/__init__.py +9 -0
- signalwire_agents/core/agent/deployment/handlers/__init__.py +9 -0
- signalwire_agents/core/agent/prompt/__init__.py +14 -0
- signalwire_agents/core/agent/prompt/manager.py +306 -0
- signalwire_agents/core/agent/routing/__init__.py +9 -0
- signalwire_agents/core/agent/security/__init__.py +9 -0
- signalwire_agents/core/agent/swml/__init__.py +9 -0
- signalwire_agents/core/agent/tools/__init__.py +15 -0
- signalwire_agents/core/agent/tools/decorator.py +97 -0
- signalwire_agents/core/agent/tools/registry.py +210 -0
- signalwire_agents/core/agent_base.py +845 -2916
- signalwire_agents/core/auth_handler.py +233 -0
- signalwire_agents/core/config_loader.py +259 -0
- signalwire_agents/core/contexts.py +418 -0
- signalwire_agents/core/data_map.py +3 -15
- signalwire_agents/core/function_result.py +116 -44
- signalwire_agents/core/logging_config.py +162 -18
- signalwire_agents/core/mixins/__init__.py +28 -0
- signalwire_agents/core/mixins/ai_config_mixin.py +442 -0
- signalwire_agents/core/mixins/auth_mixin.py +280 -0
- signalwire_agents/core/mixins/prompt_mixin.py +358 -0
- signalwire_agents/core/mixins/serverless_mixin.py +460 -0
- signalwire_agents/core/mixins/skill_mixin.py +55 -0
- signalwire_agents/core/mixins/state_mixin.py +153 -0
- signalwire_agents/core/mixins/tool_mixin.py +230 -0
- signalwire_agents/core/mixins/web_mixin.py +1142 -0
- signalwire_agents/core/security_config.py +333 -0
- signalwire_agents/core/skill_base.py +84 -1
- signalwire_agents/core/skill_manager.py +62 -20
- signalwire_agents/core/swaig_function.py +18 -5
- signalwire_agents/core/swml_builder.py +207 -11
- signalwire_agents/core/swml_handler.py +27 -21
- signalwire_agents/core/swml_renderer.py +123 -312
- signalwire_agents/core/swml_service.py +171 -203
- signalwire_agents/mcp_gateway/__init__.py +29 -0
- signalwire_agents/mcp_gateway/gateway_service.py +564 -0
- signalwire_agents/mcp_gateway/mcp_manager.py +513 -0
- signalwire_agents/mcp_gateway/session_manager.py +218 -0
- signalwire_agents/prefabs/concierge.py +0 -3
- signalwire_agents/prefabs/faq_bot.py +0 -3
- signalwire_agents/prefabs/info_gatherer.py +0 -3
- signalwire_agents/prefabs/receptionist.py +0 -3
- signalwire_agents/prefabs/survey.py +0 -3
- signalwire_agents/schema.json +9218 -5489
- signalwire_agents/search/__init__.py +7 -1
- signalwire_agents/search/document_processor.py +490 -31
- signalwire_agents/search/index_builder.py +307 -37
- signalwire_agents/search/migration.py +418 -0
- signalwire_agents/search/models.py +30 -0
- signalwire_agents/search/pgvector_backend.py +748 -0
- signalwire_agents/search/query_processor.py +162 -31
- signalwire_agents/search/search_engine.py +916 -35
- signalwire_agents/search/search_service.py +376 -53
- signalwire_agents/skills/README.md +452 -0
- signalwire_agents/skills/__init__.py +14 -2
- signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
- signalwire_agents/skills/api_ninjas_trivia/__init__.py +12 -0
- signalwire_agents/skills/api_ninjas_trivia/skill.py +237 -0
- signalwire_agents/skills/datasphere/README.md +210 -0
- signalwire_agents/skills/datasphere/skill.py +84 -3
- signalwire_agents/skills/datasphere_serverless/README.md +258 -0
- signalwire_agents/skills/datasphere_serverless/__init__.py +9 -0
- signalwire_agents/skills/datasphere_serverless/skill.py +82 -1
- signalwire_agents/skills/datetime/README.md +132 -0
- signalwire_agents/skills/datetime/__init__.py +9 -0
- signalwire_agents/skills/datetime/skill.py +20 -7
- signalwire_agents/skills/joke/README.md +149 -0
- signalwire_agents/skills/joke/__init__.py +9 -0
- signalwire_agents/skills/joke/skill.py +21 -0
- signalwire_agents/skills/math/README.md +161 -0
- signalwire_agents/skills/math/__init__.py +9 -0
- signalwire_agents/skills/math/skill.py +18 -4
- signalwire_agents/skills/mcp_gateway/README.md +230 -0
- signalwire_agents/skills/mcp_gateway/__init__.py +10 -0
- signalwire_agents/skills/mcp_gateway/skill.py +421 -0
- signalwire_agents/skills/native_vector_search/README.md +210 -0
- signalwire_agents/skills/native_vector_search/__init__.py +9 -0
- signalwire_agents/skills/native_vector_search/skill.py +569 -101
- signalwire_agents/skills/play_background_file/README.md +218 -0
- signalwire_agents/skills/play_background_file/__init__.py +12 -0
- signalwire_agents/skills/play_background_file/skill.py +242 -0
- signalwire_agents/skills/registry.py +395 -40
- signalwire_agents/skills/spider/README.md +236 -0
- signalwire_agents/skills/spider/__init__.py +13 -0
- signalwire_agents/skills/spider/skill.py +598 -0
- signalwire_agents/skills/swml_transfer/README.md +395 -0
- signalwire_agents/skills/swml_transfer/__init__.py +10 -0
- signalwire_agents/skills/swml_transfer/skill.py +359 -0
- signalwire_agents/skills/weather_api/README.md +178 -0
- signalwire_agents/skills/weather_api/__init__.py +12 -0
- signalwire_agents/skills/weather_api/skill.py +191 -0
- signalwire_agents/skills/web_search/README.md +163 -0
- signalwire_agents/skills/web_search/__init__.py +9 -0
- signalwire_agents/skills/web_search/skill.py +586 -112
- signalwire_agents/skills/wikipedia_search/README.md +228 -0
- signalwire_agents/{core/state → skills/wikipedia_search}/__init__.py +5 -4
- signalwire_agents/skills/{wikipedia → wikipedia_search}/skill.py +33 -3
- signalwire_agents/web/__init__.py +17 -0
- signalwire_agents/web/web_service.py +559 -0
- signalwire_agents-1.0.17.dev4.data/data/share/man/man1/sw-agent-init.1 +400 -0
- signalwire_agents-1.0.17.dev4.data/data/share/man/man1/sw-search.1 +483 -0
- signalwire_agents-1.0.17.dev4.data/data/share/man/man1/swaig-test.1 +308 -0
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/METADATA +347 -215
- signalwire_agents-1.0.17.dev4.dist-info/RECORD +147 -0
- signalwire_agents-1.0.17.dev4.dist-info/entry_points.txt +6 -0
- signalwire_agents/core/state/file_state_manager.py +0 -219
- signalwire_agents/core/state/state_manager.py +0 -101
- signalwire_agents/skills/wikipedia/__init__.py +0 -9
- signalwire_agents-0.1.13.data/data/schema.json +0 -5611
- signalwire_agents-0.1.13.dist-info/RECORD +0 -67
- signalwire_agents-0.1.13.dist-info/entry_points.txt +0 -3
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/licenses/LICENSE +0 -0
- {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']
|