signalwire-agents 0.1.23__py3-none-any.whl → 0.1.25__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 +1 -1
- signalwire_agents/agent_server.py +2 -1
- signalwire_agents/cli/config.py +61 -0
- signalwire_agents/cli/core/__init__.py +1 -0
- signalwire_agents/cli/core/agent_loader.py +254 -0
- signalwire_agents/cli/core/argparse_helpers.py +164 -0
- signalwire_agents/cli/core/dynamic_config.py +62 -0
- signalwire_agents/cli/execution/__init__.py +1 -0
- signalwire_agents/cli/execution/datamap_exec.py +437 -0
- signalwire_agents/cli/execution/webhook_exec.py +125 -0
- signalwire_agents/cli/output/__init__.py +1 -0
- signalwire_agents/cli/output/output_formatter.py +132 -0
- signalwire_agents/cli/output/swml_dump.py +177 -0
- signalwire_agents/cli/simulation/__init__.py +1 -0
- signalwire_agents/cli/simulation/data_generation.py +365 -0
- signalwire_agents/cli/simulation/data_overrides.py +187 -0
- signalwire_agents/cli/simulation/mock_env.py +271 -0
- signalwire_agents/cli/test_swaig.py +522 -2539
- signalwire_agents/cli/types.py +72 -0
- signalwire_agents/core/agent/__init__.py +1 -3
- signalwire_agents/core/agent/config/__init__.py +1 -3
- signalwire_agents/core/agent/prompt/manager.py +25 -7
- signalwire_agents/core/agent/tools/decorator.py +2 -0
- signalwire_agents/core/agent/tools/registry.py +8 -0
- signalwire_agents/core/agent_base.py +492 -3053
- signalwire_agents/core/function_result.py +31 -42
- signalwire_agents/core/mixins/__init__.py +28 -0
- signalwire_agents/core/mixins/ai_config_mixin.py +373 -0
- signalwire_agents/core/mixins/auth_mixin.py +287 -0
- signalwire_agents/core/mixins/prompt_mixin.py +345 -0
- signalwire_agents/core/mixins/serverless_mixin.py +368 -0
- signalwire_agents/core/mixins/skill_mixin.py +55 -0
- signalwire_agents/core/mixins/state_mixin.py +219 -0
- signalwire_agents/core/mixins/tool_mixin.py +295 -0
- signalwire_agents/core/mixins/web_mixin.py +1130 -0
- signalwire_agents/core/skill_manager.py +3 -1
- signalwire_agents/core/swaig_function.py +10 -1
- signalwire_agents/core/swml_service.py +140 -58
- signalwire_agents/skills/README.md +452 -0
- signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
- signalwire_agents/skills/datasphere/README.md +210 -0
- signalwire_agents/skills/datasphere_serverless/README.md +258 -0
- signalwire_agents/skills/datetime/README.md +132 -0
- signalwire_agents/skills/joke/README.md +149 -0
- signalwire_agents/skills/math/README.md +161 -0
- signalwire_agents/skills/native_vector_search/skill.py +33 -13
- signalwire_agents/skills/play_background_file/README.md +218 -0
- signalwire_agents/skills/spider/README.md +236 -0
- signalwire_agents/skills/spider/__init__.py +4 -0
- signalwire_agents/skills/spider/skill.py +479 -0
- signalwire_agents/skills/swml_transfer/README.md +395 -0
- signalwire_agents/skills/swml_transfer/__init__.py +1 -0
- signalwire_agents/skills/swml_transfer/skill.py +257 -0
- signalwire_agents/skills/weather_api/README.md +178 -0
- signalwire_agents/skills/web_search/README.md +163 -0
- signalwire_agents/skills/wikipedia_search/README.md +228 -0
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/METADATA +47 -2
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/RECORD +62 -22
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/entry_points.txt +1 -1
- signalwire_agents/core/agent/config/ephemeral.py +0 -176
- signalwire_agents-0.1.23.data/data/schema.json +0 -5611
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,395 @@
|
|
1
|
+
# SWML Transfer Skill
|
2
|
+
|
3
|
+
Transfer calls between agents using SWML with pattern matching. This skill enables agents to transfer calls to other agents based on user input patterns, with full control over transfer messages and behaviors.
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
The SWML Transfer skill provides a flexible way to implement call transfers between agents. It uses pattern matching to determine where to transfer calls based on user input, making it ideal for routing scenarios like transferring to sales, support, or other specialized agents.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- **Pattern-based routing** - Use regex patterns to match user input
|
12
|
+
- **Multiple instances** - Load the skill multiple times with different configurations
|
13
|
+
- **Customizable messages** - Configure pre-transfer and post-transfer messages
|
14
|
+
- **Post-processing control** - Enable/disable post-processing per transfer
|
15
|
+
- **Dynamic URL support** - Transfer to any URL endpoint
|
16
|
+
- **Fallback handling** - Default behavior when no pattern matches
|
17
|
+
- **Automatic prompt sections** - Adds "Transferring" section listing all configured destinations
|
18
|
+
- **Required fields collection** - Collect any required data before transfer
|
19
|
+
|
20
|
+
## Requirements
|
21
|
+
|
22
|
+
- No additional Python packages required
|
23
|
+
- No environment variables required
|
24
|
+
|
25
|
+
## Configuration
|
26
|
+
|
27
|
+
### Required Parameters
|
28
|
+
|
29
|
+
- `transfers`: Dictionary mapping regex patterns to transfer configurations
|
30
|
+
|
31
|
+
### Optional Parameters
|
32
|
+
|
33
|
+
- `tool_name` (default: "transfer_call"): Name of the transfer function
|
34
|
+
- `description` (default: "Transfer call based on pattern matching"): Tool description
|
35
|
+
- `parameter_name` (default: "transfer_type"): Name of the parameter for the transfer function
|
36
|
+
- `parameter_description` (default: "The type of transfer to perform"): Parameter description
|
37
|
+
- `default_message` (default: "Please specify a valid transfer type."): Message when no pattern matches
|
38
|
+
- `default_post_process` (default: False): Post-processing flag for default case
|
39
|
+
- `required_fields` (default: {}): Object mapping field names to descriptions for data collection
|
40
|
+
|
41
|
+
### Transfer Configuration
|
42
|
+
|
43
|
+
Each entry in the `transfers` dictionary should have:
|
44
|
+
- **Pattern (key)**: Regex pattern to match (e.g., "/sales/i" for case-insensitive)
|
45
|
+
- **Configuration (value)**: Dictionary with:
|
46
|
+
- **One of these required**:
|
47
|
+
- `url`: Transfer to SWML endpoint (uses swml_transfer action)
|
48
|
+
- `address`: Transfer to phone/SIP address (uses connect action)
|
49
|
+
- `message` (optional): Pre-transfer message
|
50
|
+
- `return_message` (optional): Post-transfer message (only used when `final` is False)
|
51
|
+
- `post_process` (optional): Boolean for post-processing (default: True)
|
52
|
+
- `final` (optional): Boolean for permanent transfer (default: True)
|
53
|
+
- `True`: Permanent transfer - call exits agent completely (default)
|
54
|
+
- `False`: Temporary transfer - control returns after transfer completes
|
55
|
+
- `from_addr` (optional): Caller ID override for connect action (only with `address`)
|
56
|
+
|
57
|
+
## Usage
|
58
|
+
|
59
|
+
### Basic Usage
|
60
|
+
|
61
|
+
```python
|
62
|
+
from signalwire_agents import AgentBase
|
63
|
+
|
64
|
+
class MyAgent(AgentBase):
|
65
|
+
def __init__(self):
|
66
|
+
super().__init__(name="Routing Agent", route="/router")
|
67
|
+
|
68
|
+
# Add transfer capability
|
69
|
+
self.add_skill("swml_transfer", {
|
70
|
+
"tool_name": "transfer_to_department",
|
71
|
+
"transfers": {
|
72
|
+
"/sales/i": {
|
73
|
+
"url": "https://example.com/sales",
|
74
|
+
"message": "I'll connect you with our sales team.",
|
75
|
+
"return_message": "Thank you for speaking with sales."
|
76
|
+
},
|
77
|
+
"/support/i": {
|
78
|
+
"url": "https://example.com/support",
|
79
|
+
"message": "Let me transfer you to technical support.",
|
80
|
+
"return_message": "I hope support was able to help!"
|
81
|
+
}
|
82
|
+
}
|
83
|
+
})
|
84
|
+
|
85
|
+
agent = MyAgent()
|
86
|
+
agent.serve()
|
87
|
+
```
|
88
|
+
|
89
|
+
### Advanced Configuration
|
90
|
+
|
91
|
+
```python
|
92
|
+
# Multiple transfer types with custom messages
|
93
|
+
self.add_skill("swml_transfer", {
|
94
|
+
"tool_name": "route_call",
|
95
|
+
"description": "Route calls to appropriate departments",
|
96
|
+
"parameter_name": "department",
|
97
|
+
"parameter_description": "Which department to transfer to",
|
98
|
+
"transfers": {
|
99
|
+
"/sales|billing|pricing/i": {
|
100
|
+
"url": "https://api.company.com/sales-agent",
|
101
|
+
"message": "I'll connect you with our sales team for pricing and billing questions.",
|
102
|
+
"return_message": "Thank you for contacting our sales department.",
|
103
|
+
"post_process": True,
|
104
|
+
"final": False # Temporary transfer - returns control
|
105
|
+
},
|
106
|
+
"/support|technical|help/i": {
|
107
|
+
"url": "https://api.company.com/support-agent",
|
108
|
+
"message": "Let me transfer you to our technical support team.",
|
109
|
+
"return_message": "I hope we were able to resolve your technical issue.",
|
110
|
+
"post_process": True,
|
111
|
+
"final": False # Temporary transfer - returns control
|
112
|
+
},
|
113
|
+
"/manager|supervisor/i": {
|
114
|
+
"url": "https://api.company.com/manager-agent",
|
115
|
+
"message": "I understand you'd like to speak with a manager. One moment please.",
|
116
|
+
"return_message": "Thank you for your patience.",
|
117
|
+
"post_process": False,
|
118
|
+
"final": False # Temporary transfer - returns control
|
119
|
+
},
|
120
|
+
"/disconnect|goodbye/i": {
|
121
|
+
"url": "https://api.company.com/closing-agent",
|
122
|
+
"message": "Thank you for calling. Goodbye!",
|
123
|
+
"post_process": True,
|
124
|
+
"final": True # Permanent transfer - exits this agent completely
|
125
|
+
}
|
126
|
+
},
|
127
|
+
"default_message": "I can transfer you to sales, support, or a manager. Which would you prefer?",
|
128
|
+
"default_post_process": False
|
129
|
+
})
|
130
|
+
```
|
131
|
+
|
132
|
+
### Permanent vs Temporary Transfers
|
133
|
+
|
134
|
+
```python
|
135
|
+
# Permanent transfer example - call exits agent completely
|
136
|
+
self.add_skill("swml_transfer", {
|
137
|
+
"tool_name": "permanent_transfer",
|
138
|
+
"transfers": {
|
139
|
+
"/billing/i": {
|
140
|
+
"url": "https://example.com/billing-specialist",
|
141
|
+
"message": "I'll transfer you to our billing specialist. Goodbye!",
|
142
|
+
"final": True # Call won't return to this agent
|
143
|
+
}
|
144
|
+
}
|
145
|
+
})
|
146
|
+
|
147
|
+
# Temporary transfer example - call returns after transfer
|
148
|
+
self.add_skill("swml_transfer", {
|
149
|
+
"tool_name": "temporary_transfer",
|
150
|
+
"transfers": {
|
151
|
+
"/specialist/i": {
|
152
|
+
"url": "https://example.com/specialist",
|
153
|
+
"message": "Let me connect you with a specialist.",
|
154
|
+
"return_message": "Welcome back! Is there anything else I can help with?",
|
155
|
+
"final": False # Call returns to this agent after transfer
|
156
|
+
}
|
157
|
+
}
|
158
|
+
})
|
159
|
+
```
|
160
|
+
|
161
|
+
### Phone Number and SIP Address Transfers
|
162
|
+
|
163
|
+
```python
|
164
|
+
# Transfer to phone numbers and SIP addresses using 'address'
|
165
|
+
self.add_skill("swml_transfer", {
|
166
|
+
"tool_name": "direct_transfer",
|
167
|
+
"transfers": {
|
168
|
+
"/emergency/i": {
|
169
|
+
"address": "+15551234567", # Phone number
|
170
|
+
"message": "Transferring you to our emergency line.",
|
171
|
+
"final": True
|
172
|
+
},
|
173
|
+
"/voicemail/i": {
|
174
|
+
"address": "sip:voicemail@company.com", # SIP address
|
175
|
+
"message": "Transferring to voicemail.",
|
176
|
+
"from_addr": "+15559876543", # Optional caller ID
|
177
|
+
"final": True
|
178
|
+
},
|
179
|
+
"/callback/i": {
|
180
|
+
"address": "+15551111111",
|
181
|
+
"message": "I'll connect you for a callback.",
|
182
|
+
"return_message": "The callback is complete. Anything else?",
|
183
|
+
"final": False # Temporary transfer
|
184
|
+
}
|
185
|
+
}
|
186
|
+
})
|
187
|
+
```
|
188
|
+
|
189
|
+
### Mixed Transfer Types
|
190
|
+
|
191
|
+
```python
|
192
|
+
# Mix SWML endpoints and direct addresses in one skill
|
193
|
+
self.add_skill("swml_transfer", {
|
194
|
+
"tool_name": "transfer",
|
195
|
+
"transfers": {
|
196
|
+
# SWML agent transfers
|
197
|
+
"/sales|billing/i": {
|
198
|
+
"url": "https://api.company.com/sales-agent",
|
199
|
+
"message": "Transferring to our sales team.",
|
200
|
+
"final": False
|
201
|
+
},
|
202
|
+
"/support/i": {
|
203
|
+
"url": "https://api.company.com/support-agent",
|
204
|
+
"message": "Transferring to technical support.",
|
205
|
+
"final": False
|
206
|
+
},
|
207
|
+
# Direct phone transfers
|
208
|
+
"/operator/i": {
|
209
|
+
"address": "+18005551234",
|
210
|
+
"message": "Connecting you with an operator.",
|
211
|
+
"final": True
|
212
|
+
},
|
213
|
+
# SIP transfers
|
214
|
+
"/conference/i": {
|
215
|
+
"address": "sip:conference@company.com",
|
216
|
+
"message": "Joining you to the conference.",
|
217
|
+
"from_addr": "+15559876543",
|
218
|
+
"final": True
|
219
|
+
}
|
220
|
+
}
|
221
|
+
})
|
222
|
+
```
|
223
|
+
|
224
|
+
### Multiple Instances
|
225
|
+
|
226
|
+
You can load the skill multiple times for different transfer scenarios:
|
227
|
+
|
228
|
+
```python
|
229
|
+
# General department transfers
|
230
|
+
self.add_skill("swml_transfer", {
|
231
|
+
"tool_name": "transfer_to_department",
|
232
|
+
"transfers": {
|
233
|
+
"/sales/i": {"url": "https://example.com/sales"},
|
234
|
+
"/support/i": {"url": "https://example.com/support"}
|
235
|
+
}
|
236
|
+
})
|
237
|
+
|
238
|
+
# Language-specific transfers
|
239
|
+
self.add_skill("swml_transfer", {
|
240
|
+
"tool_name": "transfer_to_language",
|
241
|
+
"parameter_name": "language",
|
242
|
+
"transfers": {
|
243
|
+
"/spanish|español/i": {
|
244
|
+
"url": "https://example.com/es-agent",
|
245
|
+
"message": "Le transferiré a un agente que habla español."
|
246
|
+
},
|
247
|
+
"/french|français/i": {
|
248
|
+
"url": "https://example.com/fr-agent",
|
249
|
+
"message": "Je vais vous transférer à un agent francophone."
|
250
|
+
}
|
251
|
+
}
|
252
|
+
})
|
253
|
+
|
254
|
+
# With required fields collection
|
255
|
+
self.add_skill("swml_transfer", {
|
256
|
+
"tool_name": "transfer_with_data",
|
257
|
+
"required_fields": {
|
258
|
+
"summary": "Summary of the conversation",
|
259
|
+
"customer_name": "Customer's full name",
|
260
|
+
"issue_type": "Type of issue (technical/billing/general)"
|
261
|
+
},
|
262
|
+
"transfers": {
|
263
|
+
"/sales/i": {
|
264
|
+
"url": "https://example.com/sales",
|
265
|
+
"message": "I'll transfer you to sales with your information."
|
266
|
+
},
|
267
|
+
"/support/i": {
|
268
|
+
"url": "https://example.com/support",
|
269
|
+
"message": "I'll transfer you to support with your information."
|
270
|
+
}
|
271
|
+
}
|
272
|
+
})
|
273
|
+
```
|
274
|
+
|
275
|
+
## Generated Functions
|
276
|
+
|
277
|
+
The skill generates a single SWAIG function with the configured name:
|
278
|
+
|
279
|
+
### Function: `transfer_call` (or custom `tool_name`)
|
280
|
+
|
281
|
+
**Parameters:**
|
282
|
+
- `transfer_type` (or custom `parameter_name`): String parameter that will be matched against configured patterns
|
283
|
+
- Additional parameters defined in `required_fields` configuration
|
284
|
+
|
285
|
+
**Behavior:**
|
286
|
+
1. Takes the input parameter value
|
287
|
+
2. Matches it against configured regex patterns in order
|
288
|
+
3. If `required_fields` are configured, saves all field values to `global_data.call_data`
|
289
|
+
4. If a match is found, transfers to the corresponding URL with configured messages
|
290
|
+
5. If no match is found, returns the default message (data still saved if provided)
|
291
|
+
|
292
|
+
## Prompt Sections
|
293
|
+
|
294
|
+
The skill automatically adds prompt sections to help the AI understand available transfer destinations:
|
295
|
+
|
296
|
+
### Transferring Section
|
297
|
+
Lists all configured transfer destinations extracted from the patterns. For example:
|
298
|
+
```
|
299
|
+
## Transferring
|
300
|
+
You can transfer calls using the transfer_to_department function with the following destinations:
|
301
|
+
- "sales" - transfers to https://example.com/sales
|
302
|
+
- "support" - transfers to https://example.com/support
|
303
|
+
```
|
304
|
+
|
305
|
+
### Transfer Instructions Section
|
306
|
+
Provides usage instructions for the transfer capability:
|
307
|
+
```
|
308
|
+
## Transfer Instructions
|
309
|
+
How to use the transfer capability:
|
310
|
+
- Use the transfer_to_department function when a transfer is needed
|
311
|
+
- Pass the destination type to the 'department' parameter
|
312
|
+
- The system will match patterns and handle the transfer automatically
|
313
|
+
- After transfer completes, you'll regain control of the conversation
|
314
|
+
```
|
315
|
+
|
316
|
+
## Example Conversations
|
317
|
+
|
318
|
+
### Example 1: Department Transfer
|
319
|
+
```
|
320
|
+
User: "I need help with my order"
|
321
|
+
Agent: "I can help you with that. Would you like to speak with sales or support?"
|
322
|
+
User: "I think I need support"
|
323
|
+
Agent: [Uses transfer_to_department("support")]
|
324
|
+
System: "Let me transfer you to our technical support team."
|
325
|
+
[Transfer occurs]
|
326
|
+
System: "I hope we were able to resolve your technical issue."
|
327
|
+
```
|
328
|
+
|
329
|
+
### Example 2: Multi-pattern Match
|
330
|
+
```
|
331
|
+
User: "I want to talk to someone about pricing"
|
332
|
+
Agent: [Uses route_call("pricing")]
|
333
|
+
System: "I'll connect you with our sales team for pricing and billing questions."
|
334
|
+
[Transfer to sales agent]
|
335
|
+
```
|
336
|
+
|
337
|
+
### Example 3: No Match
|
338
|
+
```
|
339
|
+
User: "Transfer me to the CEO"
|
340
|
+
Agent: [Uses transfer_to_department("CEO")]
|
341
|
+
System: "I can transfer you to sales, support, or a manager. Which would you prefer?"
|
342
|
+
```
|
343
|
+
|
344
|
+
### Example 4: Transfer with Required Fields
|
345
|
+
```
|
346
|
+
User: "I need to speak with support about my PC that won't boot"
|
347
|
+
Agent: "I'll transfer you to support. Let me collect some information first."
|
348
|
+
Agent: [Uses transfer_with_data("support", "Customer experiencing boot failure with their PC. Initial troubleshooting not yet performed.", "John Smith", "technical")]
|
349
|
+
System: "I'll transfer you to support with your information."
|
350
|
+
[Transfer occurs with data saved to global_data.call_data]
|
351
|
+
Support Agent: [Can access the data via ${global_data.call_data.summary}, ${global_data.call_data.customer_name}, etc.]
|
352
|
+
```
|
353
|
+
|
354
|
+
## Troubleshooting
|
355
|
+
|
356
|
+
### Transfer Not Working
|
357
|
+
- Verify the URL is accessible and returns valid SWML
|
358
|
+
- Check that the pattern syntax is correct (regex format)
|
359
|
+
- Enable debug logging to see pattern matching results
|
360
|
+
|
361
|
+
### Pattern Not Matching
|
362
|
+
- Remember to use case-insensitive flag `/i` if needed
|
363
|
+
- Test patterns with online regex tools
|
364
|
+
- Use pipe `|` for multiple options: `/sales|billing/i`
|
365
|
+
|
366
|
+
### Authentication Issues
|
367
|
+
- Ensure URLs include authentication if required
|
368
|
+
- Use `agent.get_full_url(include_auth=True)` for building URLs in dynamic configs
|
369
|
+
|
370
|
+
## Best Practices
|
371
|
+
|
372
|
+
1. **Use Clear Patterns**: Make patterns specific enough to avoid false matches
|
373
|
+
2. **Provide Context**: Use descriptive messages so users know what's happening
|
374
|
+
3. **Handle Failures**: Always include a sensible default message
|
375
|
+
4. **Test Patterns**: Verify regex patterns match expected inputs
|
376
|
+
5. **Group Related Transfers**: Use pipe `|` to group similar departments/options
|
377
|
+
|
378
|
+
## Dynamic URL Configuration
|
379
|
+
|
380
|
+
For agents that need to build URLs dynamically (e.g., with proxy detection), implement the skill loading in a dynamic configuration callback:
|
381
|
+
|
382
|
+
```python
|
383
|
+
def configure_transfers(self, query_params, body_params, headers, agent):
|
384
|
+
# Build URLs with proper proxy detection
|
385
|
+
base_url = self.get_full_url(include_auth=True).rstrip('/')
|
386
|
+
|
387
|
+
agent.add_skill("swml_transfer", {
|
388
|
+
"transfers": {
|
389
|
+
"/sales/i": {
|
390
|
+
"url": f"{base_url}/sales",
|
391
|
+
"message": "Transferring to sales..."
|
392
|
+
}
|
393
|
+
}
|
394
|
+
})
|
395
|
+
```
|
@@ -0,0 +1 @@
|
|
1
|
+
"""SWML Transfer Skill for SignalWire Agents"""
|
@@ -0,0 +1,257 @@
|
|
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
|
+
def get_instance_key(self) -> str:
|
31
|
+
"""
|
32
|
+
Get the key used to track this skill instance
|
33
|
+
|
34
|
+
For SWML transfer, we use the tool name to differentiate instances
|
35
|
+
"""
|
36
|
+
tool_name = self.params.get('tool_name', 'transfer_call')
|
37
|
+
return f"{self.SKILL_NAME}_{tool_name}"
|
38
|
+
|
39
|
+
def setup(self) -> bool:
|
40
|
+
"""Setup and validate skill configuration"""
|
41
|
+
# Validate required parameters
|
42
|
+
required_params = ['transfers']
|
43
|
+
missing_params = [param for param in required_params if not self.params.get(param)]
|
44
|
+
if missing_params:
|
45
|
+
self.logger.error(f"Missing required parameters: {missing_params}")
|
46
|
+
return False
|
47
|
+
|
48
|
+
# Validate transfers structure
|
49
|
+
transfers = self.params.get('transfers', {})
|
50
|
+
if not isinstance(transfers, dict):
|
51
|
+
self.logger.error("'transfers' parameter must be a dictionary")
|
52
|
+
return False
|
53
|
+
|
54
|
+
# Validate each transfer configuration
|
55
|
+
for pattern, config in transfers.items():
|
56
|
+
if not isinstance(config, dict):
|
57
|
+
self.logger.error(f"Transfer config for pattern '{pattern}' must be a dictionary")
|
58
|
+
return False
|
59
|
+
|
60
|
+
# Must have either 'url' or 'address'
|
61
|
+
if 'url' not in config and 'address' not in config:
|
62
|
+
self.logger.error(f"Transfer config for pattern '{pattern}' must include either 'url' or 'address'")
|
63
|
+
return False
|
64
|
+
|
65
|
+
if 'url' in config and 'address' in config:
|
66
|
+
self.logger.error(f"Transfer config for pattern '{pattern}' cannot have both 'url' and 'address'")
|
67
|
+
return False
|
68
|
+
|
69
|
+
# Set defaults for optional fields
|
70
|
+
config.setdefault('message', f"Transferring you now...")
|
71
|
+
config.setdefault('return_message', "The transfer is complete. How else can I help you?")
|
72
|
+
config.setdefault('post_process', True)
|
73
|
+
config.setdefault('final', True) # Default to permanent transfer
|
74
|
+
# from_addr is optional for connect action only
|
75
|
+
|
76
|
+
# Store configuration
|
77
|
+
self.tool_name = self.params.get('tool_name', 'transfer_call')
|
78
|
+
self.description = self.params.get('description', 'Transfer call based on pattern matching')
|
79
|
+
self.parameter_name = self.params.get('parameter_name', 'transfer_type')
|
80
|
+
self.parameter_description = self.params.get('parameter_description', 'The type of transfer to perform')
|
81
|
+
self.transfers = transfers
|
82
|
+
self.default_message = self.params.get('default_message', 'Please specify a valid transfer type.')
|
83
|
+
self.default_post_process = self.params.get('default_post_process', False)
|
84
|
+
|
85
|
+
# Required fields configuration
|
86
|
+
self.required_fields = self.params.get('required_fields', {})
|
87
|
+
|
88
|
+
return True
|
89
|
+
|
90
|
+
def register_tools(self) -> None:
|
91
|
+
"""Register the transfer tool with pattern matching"""
|
92
|
+
|
93
|
+
# Build the DataMap tool with all patterns
|
94
|
+
transfer_tool = (DataMap(self.tool_name)
|
95
|
+
.description(self.description)
|
96
|
+
.parameter(self.parameter_name, 'string', self.parameter_description, required=True)
|
97
|
+
)
|
98
|
+
|
99
|
+
# Add required fields as parameters
|
100
|
+
for field_name, field_description in self.required_fields.items():
|
101
|
+
transfer_tool = transfer_tool.parameter(
|
102
|
+
field_name,
|
103
|
+
'string',
|
104
|
+
field_description,
|
105
|
+
required=True
|
106
|
+
)
|
107
|
+
|
108
|
+
# Add expression for each pattern
|
109
|
+
for pattern, config in self.transfers.items():
|
110
|
+
# Create the function result with transfer
|
111
|
+
result = SwaigFunctionResult(
|
112
|
+
config['message'],
|
113
|
+
post_process=config['post_process']
|
114
|
+
)
|
115
|
+
|
116
|
+
# Add required fields to global data under call_data key
|
117
|
+
if self.required_fields:
|
118
|
+
call_data = {}
|
119
|
+
for field_name in self.required_fields.keys():
|
120
|
+
call_data[field_name] = f"${{args.{field_name}}}"
|
121
|
+
result = result.update_global_data({
|
122
|
+
"call_data": call_data
|
123
|
+
})
|
124
|
+
|
125
|
+
# Add the transfer action based on whether url or address is provided
|
126
|
+
if 'url' in config:
|
127
|
+
# Use swml_transfer for SWML endpoints
|
128
|
+
result = result.swml_transfer(
|
129
|
+
config['url'],
|
130
|
+
config['return_message'],
|
131
|
+
config.get('final', True)
|
132
|
+
)
|
133
|
+
else:
|
134
|
+
# Use connect for addresses (phone numbers, SIP, etc.)
|
135
|
+
result = result.connect(
|
136
|
+
config['address'],
|
137
|
+
config.get('final', True),
|
138
|
+
config.get('from_addr', None)
|
139
|
+
)
|
140
|
+
|
141
|
+
# Add the expression to the DataMap
|
142
|
+
transfer_tool = transfer_tool.expression(
|
143
|
+
f'${{{f"args.{self.parameter_name}"}}}',
|
144
|
+
pattern,
|
145
|
+
result
|
146
|
+
)
|
147
|
+
|
148
|
+
# Add default fallback expression
|
149
|
+
default_result = SwaigFunctionResult(
|
150
|
+
self.default_message,
|
151
|
+
post_process=self.default_post_process
|
152
|
+
)
|
153
|
+
|
154
|
+
# For fallback, still save required fields if provided
|
155
|
+
if self.required_fields:
|
156
|
+
call_data = {}
|
157
|
+
for field_name in self.required_fields.keys():
|
158
|
+
call_data[field_name] = f"${{args.{field_name}}}"
|
159
|
+
default_result = default_result.update_global_data({
|
160
|
+
"call_data": call_data
|
161
|
+
})
|
162
|
+
|
163
|
+
transfer_tool = transfer_tool.expression(
|
164
|
+
f'${{{f"args.{self.parameter_name}"}}}',
|
165
|
+
r'/.*/', # Match anything as fallback
|
166
|
+
default_result
|
167
|
+
)
|
168
|
+
|
169
|
+
# Register the tool with the agent
|
170
|
+
if hasattr(self.agent, 'register_swaig_function'):
|
171
|
+
self.agent.register_swaig_function(transfer_tool.to_swaig_function())
|
172
|
+
else:
|
173
|
+
# Fallback to define_tool if register_swaig_function is not available
|
174
|
+
self.logger.error("Agent does not have register_swaig_function method")
|
175
|
+
|
176
|
+
def get_hints(self) -> List[str]:
|
177
|
+
"""Return speech recognition hints"""
|
178
|
+
hints = []
|
179
|
+
|
180
|
+
# Extract common words from patterns for hints
|
181
|
+
for pattern in self.transfers.keys():
|
182
|
+
# Remove regex delimiters and flags
|
183
|
+
clean_pattern = pattern.strip('/')
|
184
|
+
if clean_pattern.endswith('i'):
|
185
|
+
clean_pattern = clean_pattern[:-1]
|
186
|
+
|
187
|
+
# Only add if it's not a catch-all pattern
|
188
|
+
if clean_pattern and not clean_pattern.startswith('.'):
|
189
|
+
# Handle patterns with alternatives (e.g., "sales|billing")
|
190
|
+
if '|' in clean_pattern:
|
191
|
+
for part in clean_pattern.split('|'):
|
192
|
+
hints.append(part.strip().lower())
|
193
|
+
else:
|
194
|
+
hints.append(clean_pattern.lower())
|
195
|
+
|
196
|
+
# Add common transfer-related words
|
197
|
+
hints.extend(['transfer', 'connect', 'speak to', 'talk to'])
|
198
|
+
|
199
|
+
return hints
|
200
|
+
|
201
|
+
def get_prompt_sections(self) -> List[Dict[str, Any]]:
|
202
|
+
"""Return prompt sections to add to agent"""
|
203
|
+
sections = []
|
204
|
+
|
205
|
+
# Build a list of transfer destinations with their patterns
|
206
|
+
if self.transfers:
|
207
|
+
transfer_bullets = []
|
208
|
+
|
209
|
+
for pattern, config in self.transfers.items():
|
210
|
+
# Extract meaningful name from pattern
|
211
|
+
# Remove regex delimiters and flags
|
212
|
+
clean_pattern = pattern.strip('/')
|
213
|
+
if clean_pattern.endswith('i'):
|
214
|
+
clean_pattern = clean_pattern[:-1]
|
215
|
+
|
216
|
+
# Only add if it's not a catch-all pattern
|
217
|
+
if clean_pattern and not clean_pattern.startswith('.'):
|
218
|
+
# Create a description for this transfer destination
|
219
|
+
if 'url' in config:
|
220
|
+
destination = config['url']
|
221
|
+
else:
|
222
|
+
destination = config['address']
|
223
|
+
transfer_desc = f'"{clean_pattern}" - transfers to {destination}'
|
224
|
+
transfer_bullets.append(transfer_desc)
|
225
|
+
|
226
|
+
# Add the Transferring section
|
227
|
+
sections.append({
|
228
|
+
"title": "Transferring",
|
229
|
+
"body": f"You can transfer calls using the {self.tool_name} function with the following destinations:",
|
230
|
+
"bullets": transfer_bullets
|
231
|
+
})
|
232
|
+
|
233
|
+
# Add usage instructions
|
234
|
+
bullets = [
|
235
|
+
f"Use the {self.tool_name} function when a transfer is needed",
|
236
|
+
f"Pass the destination type to the '{self.parameter_name}' parameter"
|
237
|
+
]
|
238
|
+
|
239
|
+
# Add required fields instructions if configured
|
240
|
+
if self.required_fields:
|
241
|
+
bullets.append("You must provide the following information before transferring:")
|
242
|
+
for field_name, field_description in self.required_fields.items():
|
243
|
+
bullets.append(f" - {field_name}: {field_description}")
|
244
|
+
bullets.append("All required information will be saved under 'call_data' for the next agent")
|
245
|
+
|
246
|
+
bullets.extend([
|
247
|
+
f"The system will match patterns and handle the transfer automatically",
|
248
|
+
"After transfer completes, you'll regain control of the conversation"
|
249
|
+
])
|
250
|
+
|
251
|
+
sections.append({
|
252
|
+
"title": "Transfer Instructions",
|
253
|
+
"body": f"How to use the transfer capability:",
|
254
|
+
"bullets": bullets
|
255
|
+
})
|
256
|
+
|
257
|
+
return sections
|