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.
Files changed (64) hide show
  1. signalwire_agents/__init__.py +1 -1
  2. signalwire_agents/agent_server.py +2 -1
  3. signalwire_agents/cli/config.py +61 -0
  4. signalwire_agents/cli/core/__init__.py +1 -0
  5. signalwire_agents/cli/core/agent_loader.py +254 -0
  6. signalwire_agents/cli/core/argparse_helpers.py +164 -0
  7. signalwire_agents/cli/core/dynamic_config.py +62 -0
  8. signalwire_agents/cli/execution/__init__.py +1 -0
  9. signalwire_agents/cli/execution/datamap_exec.py +437 -0
  10. signalwire_agents/cli/execution/webhook_exec.py +125 -0
  11. signalwire_agents/cli/output/__init__.py +1 -0
  12. signalwire_agents/cli/output/output_formatter.py +132 -0
  13. signalwire_agents/cli/output/swml_dump.py +177 -0
  14. signalwire_agents/cli/simulation/__init__.py +1 -0
  15. signalwire_agents/cli/simulation/data_generation.py +365 -0
  16. signalwire_agents/cli/simulation/data_overrides.py +187 -0
  17. signalwire_agents/cli/simulation/mock_env.py +271 -0
  18. signalwire_agents/cli/test_swaig.py +522 -2539
  19. signalwire_agents/cli/types.py +72 -0
  20. signalwire_agents/core/agent/__init__.py +1 -3
  21. signalwire_agents/core/agent/config/__init__.py +1 -3
  22. signalwire_agents/core/agent/prompt/manager.py +25 -7
  23. signalwire_agents/core/agent/tools/decorator.py +2 -0
  24. signalwire_agents/core/agent/tools/registry.py +8 -0
  25. signalwire_agents/core/agent_base.py +492 -3053
  26. signalwire_agents/core/function_result.py +31 -42
  27. signalwire_agents/core/mixins/__init__.py +28 -0
  28. signalwire_agents/core/mixins/ai_config_mixin.py +373 -0
  29. signalwire_agents/core/mixins/auth_mixin.py +287 -0
  30. signalwire_agents/core/mixins/prompt_mixin.py +345 -0
  31. signalwire_agents/core/mixins/serverless_mixin.py +368 -0
  32. signalwire_agents/core/mixins/skill_mixin.py +55 -0
  33. signalwire_agents/core/mixins/state_mixin.py +219 -0
  34. signalwire_agents/core/mixins/tool_mixin.py +295 -0
  35. signalwire_agents/core/mixins/web_mixin.py +1130 -0
  36. signalwire_agents/core/skill_manager.py +3 -1
  37. signalwire_agents/core/swaig_function.py +10 -1
  38. signalwire_agents/core/swml_service.py +140 -58
  39. signalwire_agents/skills/README.md +452 -0
  40. signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
  41. signalwire_agents/skills/datasphere/README.md +210 -0
  42. signalwire_agents/skills/datasphere_serverless/README.md +258 -0
  43. signalwire_agents/skills/datetime/README.md +132 -0
  44. signalwire_agents/skills/joke/README.md +149 -0
  45. signalwire_agents/skills/math/README.md +161 -0
  46. signalwire_agents/skills/native_vector_search/skill.py +33 -13
  47. signalwire_agents/skills/play_background_file/README.md +218 -0
  48. signalwire_agents/skills/spider/README.md +236 -0
  49. signalwire_agents/skills/spider/__init__.py +4 -0
  50. signalwire_agents/skills/spider/skill.py +479 -0
  51. signalwire_agents/skills/swml_transfer/README.md +395 -0
  52. signalwire_agents/skills/swml_transfer/__init__.py +1 -0
  53. signalwire_agents/skills/swml_transfer/skill.py +257 -0
  54. signalwire_agents/skills/weather_api/README.md +178 -0
  55. signalwire_agents/skills/web_search/README.md +163 -0
  56. signalwire_agents/skills/wikipedia_search/README.md +228 -0
  57. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/METADATA +47 -2
  58. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/RECORD +62 -22
  59. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/entry_points.txt +1 -1
  60. signalwire_agents/core/agent/config/ephemeral.py +0 -176
  61. signalwire_agents-0.1.23.data/data/schema.json +0 -5611
  62. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/WHEEL +0 -0
  63. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/licenses/LICENSE +0 -0
  64. {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