signalwire-agents 0.1.6__py3-none-any.whl → 1.0.7__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 +130 -4
- signalwire_agents/agent_server.py +438 -32
- signalwire_agents/agents/bedrock.py +296 -0
- signalwire_agents/cli/__init__.py +18 -0
- signalwire_agents/cli/build_search.py +1367 -0
- 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/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 +1225 -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 +809 -0
- 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 +959 -2166
- signalwire_agents/core/auth_handler.py +233 -0
- signalwire_agents/core/config_loader.py +259 -0
- signalwire_agents/core/contexts.py +707 -0
- signalwire_agents/core/data_map.py +487 -0
- signalwire_agents/core/function_result.py +1150 -1
- signalwire_agents/core/logging_config.py +376 -0
- 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 +287 -0
- signalwire_agents/core/mixins/prompt_mixin.py +358 -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 +153 -0
- signalwire_agents/core/mixins/tool_mixin.py +230 -0
- signalwire_agents/core/mixins/web_mixin.py +1134 -0
- signalwire_agents/core/security/session_manager.py +174 -86
- signalwire_agents/core/security_config.py +333 -0
- signalwire_agents/core/skill_base.py +200 -0
- signalwire_agents/core/skill_manager.py +244 -0
- signalwire_agents/core/swaig_function.py +33 -9
- signalwire_agents/core/swml_builder.py +212 -12
- signalwire_agents/core/swml_handler.py +43 -13
- signalwire_agents/core/swml_renderer.py +123 -297
- signalwire_agents/core/swml_service.py +277 -260
- signalwire_agents/prefabs/concierge.py +6 -2
- signalwire_agents/prefabs/info_gatherer.py +149 -33
- signalwire_agents/prefabs/receptionist.py +14 -22
- signalwire_agents/prefabs/survey.py +6 -2
- signalwire_agents/schema.json +9218 -5489
- signalwire_agents/search/__init__.py +137 -0
- signalwire_agents/search/document_processor.py +1223 -0
- signalwire_agents/search/index_builder.py +804 -0
- signalwire_agents/search/migration.py +418 -0
- signalwire_agents/search/models.py +30 -0
- signalwire_agents/search/pgvector_backend.py +752 -0
- signalwire_agents/search/query_processor.py +502 -0
- signalwire_agents/search/search_engine.py +1264 -0
- signalwire_agents/search/search_service.py +574 -0
- signalwire_agents/skills/README.md +452 -0
- signalwire_agents/skills/__init__.py +23 -0
- 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/__init__.py +12 -0
- signalwire_agents/skills/datasphere/skill.py +310 -0
- signalwire_agents/skills/datasphere_serverless/README.md +258 -0
- signalwire_agents/skills/datasphere_serverless/__init__.py +10 -0
- signalwire_agents/skills/datasphere_serverless/skill.py +237 -0
- signalwire_agents/skills/datetime/README.md +132 -0
- signalwire_agents/skills/datetime/__init__.py +10 -0
- signalwire_agents/skills/datetime/skill.py +126 -0
- signalwire_agents/skills/joke/README.md +149 -0
- signalwire_agents/skills/joke/__init__.py +10 -0
- signalwire_agents/skills/joke/skill.py +109 -0
- signalwire_agents/skills/math/README.md +161 -0
- signalwire_agents/skills/math/__init__.py +10 -0
- signalwire_agents/skills/math/skill.py +105 -0
- 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 +10 -0
- signalwire_agents/skills/native_vector_search/skill.py +820 -0
- 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 +459 -0
- 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 +10 -0
- signalwire_agents/skills/web_search/skill.py +739 -0
- signalwire_agents/skills/wikipedia_search/README.md +228 -0
- signalwire_agents/{core/state → skills/wikipedia_search}/__init__.py +5 -4
- signalwire_agents/skills/wikipedia_search/skill.py +210 -0
- signalwire_agents/utils/__init__.py +14 -0
- signalwire_agents/utils/schema_utils.py +111 -44
- signalwire_agents/web/__init__.py +17 -0
- signalwire_agents/web/web_service.py +559 -0
- signalwire_agents-1.0.7.data/data/share/man/man1/sw-agent-init.1 +307 -0
- signalwire_agents-1.0.7.data/data/share/man/man1/sw-search.1 +483 -0
- signalwire_agents-1.0.7.data/data/share/man/man1/swaig-test.1 +308 -0
- signalwire_agents-1.0.7.dist-info/METADATA +992 -0
- signalwire_agents-1.0.7.dist-info/RECORD +142 -0
- {signalwire_agents-0.1.6.dist-info → signalwire_agents-1.0.7.dist-info}/WHEEL +1 -1
- signalwire_agents-1.0.7.dist-info/entry_points.txt +4 -0
- signalwire_agents/core/state/file_state_manager.py +0 -219
- signalwire_agents/core/state/state_manager.py +0 -101
- signalwire_agents-0.1.6.data/data/schema.json +0 -5611
- signalwire_agents-0.1.6.dist-info/METADATA +0 -199
- signalwire_agents-0.1.6.dist-info/RECORD +0 -34
- {signalwire_agents-0.1.6.dist-info → signalwire_agents-1.0.7.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.6.dist-info → signalwire_agents-1.0.7.dist-info}/top_level.txt +0 -0
|
@@ -19,6 +19,19 @@ class SwaigFunctionResult:
|
|
|
19
19
|
Wrapper around SWAIG function responses that handles proper formatting
|
|
20
20
|
of response text and actions.
|
|
21
21
|
|
|
22
|
+
The result object has three main components:
|
|
23
|
+
1. response: Text the AI should say back to the user
|
|
24
|
+
2. action: List of structured actions to execute
|
|
25
|
+
3. post_process: Whether to let AI take another turn before executing actions
|
|
26
|
+
|
|
27
|
+
Post-processing behavior:
|
|
28
|
+
- post_process=False (default): Execute actions immediately after AI response
|
|
29
|
+
- post_process=True: Let AI respond to user one more time, then execute actions
|
|
30
|
+
|
|
31
|
+
This is useful for confirmation workflows like:
|
|
32
|
+
"I'll transfer you to sales. Do you have any other questions first?"
|
|
33
|
+
(AI can handle follow-up, then execute the transfer)
|
|
34
|
+
|
|
22
35
|
Example:
|
|
23
36
|
return SwaigFunctionResult("Found your order")
|
|
24
37
|
|
|
@@ -42,16 +55,31 @@ class SwaigFunctionResult:
|
|
|
42
55
|
{"play": {"url": "music.mp3"}}
|
|
43
56
|
])
|
|
44
57
|
)
|
|
58
|
+
|
|
59
|
+
# With post-processing enabled
|
|
60
|
+
return (
|
|
61
|
+
SwaigFunctionResult("Let me transfer you to billing", post_process=True)
|
|
62
|
+
.connect("+15551234567", final=True)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Using the connect helper
|
|
66
|
+
return (
|
|
67
|
+
SwaigFunctionResult("I'll transfer you to our sales team now")
|
|
68
|
+
.connect("sales@company.com", final=False, from_addr="+15559876543")
|
|
69
|
+
)
|
|
45
70
|
"""
|
|
46
|
-
def __init__(self, response: Optional[str] = None):
|
|
71
|
+
def __init__(self, response: Optional[str] = None, post_process: bool = False):
|
|
47
72
|
"""
|
|
48
73
|
Initialize a new SWAIG function result
|
|
49
74
|
|
|
50
75
|
Args:
|
|
51
76
|
response: Optional natural language response to include
|
|
77
|
+
post_process: Whether to let AI take another turn before executing actions.
|
|
78
|
+
Defaults to False (execute actions immediately after response).
|
|
52
79
|
"""
|
|
53
80
|
self.response = response or ""
|
|
54
81
|
self.action: List[Dict[str, Any]] = []
|
|
82
|
+
self.post_process = post_process
|
|
55
83
|
|
|
56
84
|
def set_response(self, response: str) -> 'SwaigFunctionResult':
|
|
57
85
|
"""
|
|
@@ -66,6 +94,23 @@ class SwaigFunctionResult:
|
|
|
66
94
|
self.response = response
|
|
67
95
|
return self
|
|
68
96
|
|
|
97
|
+
def set_post_process(self, post_process: bool) -> 'SwaigFunctionResult':
|
|
98
|
+
"""
|
|
99
|
+
Set whether to enable post-processing for this result.
|
|
100
|
+
|
|
101
|
+
Post-processing allows the AI to take one more turn with the user
|
|
102
|
+
before executing any actions. This is useful for confirmation workflows.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
post_process: True to let AI respond once more before executing actions,
|
|
106
|
+
False to execute actions immediately after the response.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Self for method chaining
|
|
110
|
+
"""
|
|
111
|
+
self.post_process = post_process
|
|
112
|
+
return self
|
|
113
|
+
|
|
69
114
|
def add_action(self, name: str, data: Any) -> 'SwaigFunctionResult':
|
|
70
115
|
"""
|
|
71
116
|
Add a structured action to the response
|
|
@@ -93,6 +138,1102 @@ class SwaigFunctionResult:
|
|
|
93
138
|
self.action.extend(actions)
|
|
94
139
|
return self
|
|
95
140
|
|
|
141
|
+
def connect(self, destination: str, final: bool = True, from_addr: Optional[str] = None) -> 'SwaigFunctionResult':
|
|
142
|
+
"""
|
|
143
|
+
Add a connect action to transfer/connect the call to another destination.
|
|
144
|
+
|
|
145
|
+
This is a convenience method that abstracts the SWML connect verb, so users
|
|
146
|
+
don't need to manually construct SWML documents.
|
|
147
|
+
|
|
148
|
+
Transfer behavior:
|
|
149
|
+
- final=True: Permanent transfer - call exits the agent completely,
|
|
150
|
+
SWML replaces the agent and call continues there
|
|
151
|
+
- final=False: Temporary transfer - if far end hangs up, call returns
|
|
152
|
+
to the agent to continue the conversation
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
destination: Where to connect the call (phone number, SIP address, etc.)
|
|
156
|
+
final: Whether this is a permanent transfer (True) or temporary (False).
|
|
157
|
+
Defaults to True for permanent transfers.
|
|
158
|
+
from_addr: Optional caller ID override (phone number or SIP address).
|
|
159
|
+
If None, uses the current call's from address.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Self for method chaining
|
|
163
|
+
|
|
164
|
+
Example:
|
|
165
|
+
# Permanent transfer to a phone number
|
|
166
|
+
result.connect("+15551234567", final=True)
|
|
167
|
+
|
|
168
|
+
# Temporary transfer to SIP address with custom caller ID
|
|
169
|
+
result.connect("support@company.com", final=False, from_addr="+15559876543")
|
|
170
|
+
"""
|
|
171
|
+
# Build the connect verb parameters
|
|
172
|
+
connect_params = {"to": destination}
|
|
173
|
+
if from_addr is not None:
|
|
174
|
+
connect_params["from"] = from_addr
|
|
175
|
+
|
|
176
|
+
# Create the SWML action
|
|
177
|
+
swml_action = {
|
|
178
|
+
"SWML": {
|
|
179
|
+
"sections": {
|
|
180
|
+
"main": [{"connect": connect_params}]
|
|
181
|
+
},
|
|
182
|
+
"version": "1.0.0"
|
|
183
|
+
},
|
|
184
|
+
"transfer": str(final).lower() # Convert boolean to "true"/"false" string
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# Add to actions list
|
|
188
|
+
self.action.append(swml_action)
|
|
189
|
+
return self
|
|
190
|
+
|
|
191
|
+
def swml_transfer(self, dest: str, ai_response: str, final: bool = True) -> 'SwaigFunctionResult':
|
|
192
|
+
"""
|
|
193
|
+
Add a SWML transfer action with AI response setup for when transfer completes.
|
|
194
|
+
|
|
195
|
+
This is a virtual helper that generates SWML to transfer the call to another
|
|
196
|
+
destination and sets up an AI response for when the transfer completes and
|
|
197
|
+
control returns to the agent.
|
|
198
|
+
|
|
199
|
+
For transfers, you typically want to enable post-processing so the AI speaks
|
|
200
|
+
the response first before executing the transfer.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
dest: Destination URL for the transfer (SWML endpoint, SIP address, etc.)
|
|
204
|
+
ai_response: Message the AI should say when transfer completes and control returns
|
|
205
|
+
final: Whether this is a permanent transfer (True) or temporary (False).
|
|
206
|
+
Defaults to True for permanent transfers (same as connect method).
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Self for method chaining
|
|
210
|
+
|
|
211
|
+
Example:
|
|
212
|
+
# Permanent transfer (default)
|
|
213
|
+
result = (
|
|
214
|
+
SwaigFunctionResult("I'm transferring you to support", post_process=True)
|
|
215
|
+
.swml_transfer(
|
|
216
|
+
"https://support.example.com/swml",
|
|
217
|
+
"Goodbye!" # Won't be used since final=True by default
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Temporary transfer with return
|
|
222
|
+
result.swml_transfer(
|
|
223
|
+
dest,
|
|
224
|
+
"The support call is complete. How else can I help?",
|
|
225
|
+
final=False
|
|
226
|
+
)
|
|
227
|
+
"""
|
|
228
|
+
# Create the SWML action structure directly
|
|
229
|
+
swml_action = {
|
|
230
|
+
"SWML": {
|
|
231
|
+
"version": "1.0.0",
|
|
232
|
+
"sections": {
|
|
233
|
+
"main": [
|
|
234
|
+
{"set": {"ai_response": ai_response}},
|
|
235
|
+
{"transfer": {"dest": dest}}
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
"transfer": str(final).lower() # Convert boolean to "true"/"false" string
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
# Add to actions list directly
|
|
243
|
+
self.action.append(swml_action)
|
|
244
|
+
|
|
245
|
+
return self
|
|
246
|
+
|
|
247
|
+
def update_global_data(self, data: Dict[str, Any]) -> 'SwaigFunctionResult':
|
|
248
|
+
"""
|
|
249
|
+
Update global agent data variables.
|
|
250
|
+
|
|
251
|
+
This is a convenience method that abstracts the set_global_data action.
|
|
252
|
+
Global data persists across the entire agent session and is available
|
|
253
|
+
in prompt variables and can be accessed by all functions.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
data: Dictionary of key-value pairs to set/update in global data
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
self for method chaining
|
|
260
|
+
"""
|
|
261
|
+
return self.add_action("set_global_data", data)
|
|
262
|
+
|
|
263
|
+
def swml_user_event(self, event_data: Dict[str, Any]) -> 'SwaigFunctionResult':
|
|
264
|
+
"""
|
|
265
|
+
Send a user event through SWML to update the client UI.
|
|
266
|
+
|
|
267
|
+
This is a convenience method for sending user events to connected clients,
|
|
268
|
+
commonly used for real-time UI updates in interactive applications.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
event_data: Dictionary containing the event type and any associated data
|
|
272
|
+
Example: {"type": "cards_dealt", "player_hand": [...], "score": 21}
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Self for method chaining
|
|
276
|
+
|
|
277
|
+
Example:
|
|
278
|
+
result = (
|
|
279
|
+
SwaigFunctionResult("You have blackjack!")
|
|
280
|
+
.swml_user_event({
|
|
281
|
+
"type": "cards_dealt",
|
|
282
|
+
"player_hand": player_cards,
|
|
283
|
+
"dealer_hand": dealer_cards,
|
|
284
|
+
"player_score": 21
|
|
285
|
+
})
|
|
286
|
+
)
|
|
287
|
+
"""
|
|
288
|
+
swml_action = {
|
|
289
|
+
"sections": {
|
|
290
|
+
"main": [{
|
|
291
|
+
"user_event": {
|
|
292
|
+
"event": event_data
|
|
293
|
+
}
|
|
294
|
+
}]
|
|
295
|
+
},
|
|
296
|
+
"version": "1.0.0"
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return self.add_action("SWML", swml_action)
|
|
300
|
+
|
|
301
|
+
def swml_change_step(self, step_name: str) -> 'SwaigFunctionResult':
|
|
302
|
+
"""
|
|
303
|
+
Change the conversation step in the AI agent's workflow.
|
|
304
|
+
|
|
305
|
+
This is a convenience method for transitioning between conversation steps,
|
|
306
|
+
allowing dynamic workflow control based on user interactions or game state.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
step_name: Name of the step to transition to (e.g., "betting", "playing", "hand_complete")
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
Self for method chaining
|
|
313
|
+
|
|
314
|
+
Example:
|
|
315
|
+
result = (
|
|
316
|
+
SwaigFunctionResult("Starting a new hand")
|
|
317
|
+
.swml_change_step("betting")
|
|
318
|
+
.swml_user_event({"type": "game_reset", "chips": 1000})
|
|
319
|
+
)
|
|
320
|
+
"""
|
|
321
|
+
return self.add_action("change_step", step_name)
|
|
322
|
+
|
|
323
|
+
def swml_change_context(self, context_name: str) -> 'SwaigFunctionResult':
|
|
324
|
+
"""
|
|
325
|
+
Change the conversation context in the AI agent's workflow.
|
|
326
|
+
|
|
327
|
+
This is a convenience method for switching between different conversation contexts,
|
|
328
|
+
useful for agents that handle multiple distinct workflows or service modes.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
context_name: Name of the context to transition to (e.g., "support", "sales", "technical")
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Self for method chaining
|
|
335
|
+
|
|
336
|
+
Example:
|
|
337
|
+
result = (
|
|
338
|
+
SwaigFunctionResult("Transferring you to technical support")
|
|
339
|
+
.swml_change_context("technical_support")
|
|
340
|
+
)
|
|
341
|
+
"""
|
|
342
|
+
return self.add_action("change_context", context_name)
|
|
343
|
+
|
|
344
|
+
def execute_swml(self, swml_content, transfer: bool = False) -> 'SwaigFunctionResult':
|
|
345
|
+
"""
|
|
346
|
+
Execute SWML content with optional transfer behavior.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
swml_content: Can be:
|
|
350
|
+
- String: Raw SWML JSON text
|
|
351
|
+
- Dict: SWML data structure
|
|
352
|
+
- SWML object: SignalWire SWML SDK object with .to_dict() method
|
|
353
|
+
transfer: Boolean - whether call should exit agent after execution
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
self for method chaining
|
|
357
|
+
"""
|
|
358
|
+
# Detect input type and normalize to appropriate format
|
|
359
|
+
if isinstance(swml_content, str):
|
|
360
|
+
# Raw SWML string - use as-is
|
|
361
|
+
swml_data = swml_content
|
|
362
|
+
elif hasattr(swml_content, 'to_dict'):
|
|
363
|
+
# SWML SDK object - convert to dict
|
|
364
|
+
swml_data = swml_content.to_dict()
|
|
365
|
+
elif isinstance(swml_content, dict):
|
|
366
|
+
# Dict - use directly
|
|
367
|
+
swml_data = swml_content
|
|
368
|
+
else:
|
|
369
|
+
raise TypeError("swml_content must be string, dict, or SWML object")
|
|
370
|
+
|
|
371
|
+
action = swml_data
|
|
372
|
+
if transfer:
|
|
373
|
+
action["transfer"] = "true"
|
|
374
|
+
|
|
375
|
+
return self.add_action("SWML", action)
|
|
376
|
+
|
|
377
|
+
def hangup(self) -> 'SwaigFunctionResult':
|
|
378
|
+
"""
|
|
379
|
+
Terminate the call.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
self for method chaining
|
|
383
|
+
"""
|
|
384
|
+
return self.add_action("hangup", True)
|
|
385
|
+
|
|
386
|
+
def hold(self, timeout: int = 300) -> 'SwaigFunctionResult':
|
|
387
|
+
"""
|
|
388
|
+
Put the call on hold with optional timeout.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
timeout: Timeout in seconds (max 900, default 300)
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
self for method chaining
|
|
395
|
+
"""
|
|
396
|
+
# Clamp timeout to valid range
|
|
397
|
+
timeout = max(0, min(timeout, 900))
|
|
398
|
+
return self.add_action("hold", timeout)
|
|
399
|
+
|
|
400
|
+
def wait_for_user(self, enabled: Optional[bool] = None, timeout: Optional[int] = None, answer_first: bool = False) -> 'SwaigFunctionResult':
|
|
401
|
+
"""
|
|
402
|
+
Control how agent waits for user input.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
enabled: Boolean to enable/disable waiting
|
|
406
|
+
timeout: Number of seconds to wait
|
|
407
|
+
answer_first: Special "answer_first" mode
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
self for method chaining
|
|
411
|
+
"""
|
|
412
|
+
if answer_first:
|
|
413
|
+
wait_value = "answer_first"
|
|
414
|
+
elif timeout is not None:
|
|
415
|
+
wait_value = timeout
|
|
416
|
+
elif enabled is not None:
|
|
417
|
+
wait_value = enabled
|
|
418
|
+
else:
|
|
419
|
+
wait_value = True
|
|
420
|
+
|
|
421
|
+
return self.add_action("wait_for_user", wait_value)
|
|
422
|
+
|
|
423
|
+
def stop(self) -> 'SwaigFunctionResult':
|
|
424
|
+
"""
|
|
425
|
+
Stop the agent execution.
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
self for method chaining
|
|
429
|
+
"""
|
|
430
|
+
return self.add_action("stop", True)
|
|
431
|
+
|
|
432
|
+
def say(self, text: str) -> 'SwaigFunctionResult':
|
|
433
|
+
"""
|
|
434
|
+
Make the agent speak specific text.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
text: Text for agent to speak
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
self for method chaining
|
|
441
|
+
"""
|
|
442
|
+
return self.add_action("say", text)
|
|
443
|
+
|
|
444
|
+
def play_background_file(self, filename: str, wait: bool = False) -> 'SwaigFunctionResult':
|
|
445
|
+
"""
|
|
446
|
+
Play audio or video file in background.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
filename: Audio/video filename/path
|
|
450
|
+
wait: Whether to suppress attention-getting behavior during playback
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
self for method chaining
|
|
454
|
+
"""
|
|
455
|
+
if wait:
|
|
456
|
+
return self.add_action("playback_bg", {"file": filename, "wait": True})
|
|
457
|
+
else:
|
|
458
|
+
return self.add_action("playback_bg", filename)
|
|
459
|
+
|
|
460
|
+
def stop_background_file(self) -> 'SwaigFunctionResult':
|
|
461
|
+
"""
|
|
462
|
+
Stop currently playing background file.
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
self for method chaining
|
|
466
|
+
"""
|
|
467
|
+
return self.add_action("stop_playback_bg", True)
|
|
468
|
+
|
|
469
|
+
def set_end_of_speech_timeout(self, milliseconds: int) -> 'SwaigFunctionResult':
|
|
470
|
+
"""
|
|
471
|
+
Adjust end of speech timeout - milliseconds of silence after speaking
|
|
472
|
+
has been detected to finalize speech recognition.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
milliseconds: Timeout in milliseconds
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
self for method chaining
|
|
479
|
+
"""
|
|
480
|
+
return self.add_action("end_of_speech_timeout", milliseconds)
|
|
481
|
+
|
|
482
|
+
def set_speech_event_timeout(self, milliseconds: int) -> 'SwaigFunctionResult':
|
|
483
|
+
"""
|
|
484
|
+
Adjust speech event timeout - milliseconds since last speech detection
|
|
485
|
+
event to finalize recognition. Works better in noisy environments.
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
milliseconds: Timeout in milliseconds
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
self for method chaining
|
|
492
|
+
"""
|
|
493
|
+
return self.add_action("speech_event_timeout", milliseconds)
|
|
494
|
+
|
|
495
|
+
def remove_global_data(self, keys: Union[str, List[str]]) -> 'SwaigFunctionResult':
|
|
496
|
+
"""
|
|
497
|
+
Remove global agent data variables.
|
|
498
|
+
|
|
499
|
+
Args:
|
|
500
|
+
keys: Single key string or list of keys to remove
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
self for method chaining
|
|
504
|
+
"""
|
|
505
|
+
return self.add_action("unset_global_data", keys)
|
|
506
|
+
|
|
507
|
+
def set_metadata(self, data: Dict[str, Any]) -> 'SwaigFunctionResult':
|
|
508
|
+
"""
|
|
509
|
+
Set metadata scoped to current function's meta_data_token.
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
data: Dictionary of key-value pairs for metadata
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
self for method chaining
|
|
516
|
+
"""
|
|
517
|
+
return self.add_action("set_meta_data", data)
|
|
518
|
+
|
|
519
|
+
def remove_metadata(self, keys: Union[str, List[str]]) -> 'SwaigFunctionResult':
|
|
520
|
+
"""
|
|
521
|
+
Remove metadata from current function's meta_data_token scope.
|
|
522
|
+
|
|
523
|
+
Args:
|
|
524
|
+
keys: Single key string or list of keys to remove
|
|
525
|
+
|
|
526
|
+
Returns:
|
|
527
|
+
self for method chaining
|
|
528
|
+
"""
|
|
529
|
+
return self.add_action("unset_meta_data", keys)
|
|
530
|
+
|
|
531
|
+
def toggle_functions(self, function_toggles: List[Dict[str, Any]]) -> 'SwaigFunctionResult':
|
|
532
|
+
"""
|
|
533
|
+
Enable/disable specific SWAIG functions.
|
|
534
|
+
|
|
535
|
+
Args:
|
|
536
|
+
function_toggles: List of dicts with 'function' and 'active' keys
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
self for method chaining
|
|
540
|
+
"""
|
|
541
|
+
return self.add_action("toggle_functions", function_toggles)
|
|
542
|
+
|
|
543
|
+
def enable_functions_on_timeout(self, enabled: bool = True) -> 'SwaigFunctionResult':
|
|
544
|
+
"""
|
|
545
|
+
Enable function calls on speaker timeout.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
enabled: Whether to enable functions on timeout
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
self for method chaining
|
|
552
|
+
"""
|
|
553
|
+
return self.add_action("functions_on_speaker_timeout", enabled)
|
|
554
|
+
|
|
555
|
+
def enable_extensive_data(self, enabled: bool = True) -> 'SwaigFunctionResult':
|
|
556
|
+
"""
|
|
557
|
+
Send full data to LLM for this turn only, then use smaller replacement
|
|
558
|
+
in subsequent turns.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
enabled: Whether to send extensive data this turn only
|
|
562
|
+
|
|
563
|
+
Returns:
|
|
564
|
+
self for method chaining
|
|
565
|
+
"""
|
|
566
|
+
return self.add_action("extensive_data", enabled)
|
|
567
|
+
|
|
568
|
+
def update_settings(self, settings: Dict[str, Any]) -> 'SwaigFunctionResult':
|
|
569
|
+
"""
|
|
570
|
+
Update agent runtime settings.
|
|
571
|
+
|
|
572
|
+
Supported settings:
|
|
573
|
+
- frequency-penalty: Float (-2.0 to 2.0)
|
|
574
|
+
- presence-penalty: Float (-2.0 to 2.0)
|
|
575
|
+
- max-tokens: Integer (0 to 4096)
|
|
576
|
+
- top-p: Float (0.0 to 1.0)
|
|
577
|
+
- confidence: Float (0.0 to 1.0)
|
|
578
|
+
- barge-confidence: Float (0.0 to 1.0)
|
|
579
|
+
- temperature: Float (0.0 to 2.0, clamped to 1.5)
|
|
580
|
+
|
|
581
|
+
Args:
|
|
582
|
+
settings: Dictionary of settings to update
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
self for method chaining
|
|
586
|
+
"""
|
|
587
|
+
return self.add_action("settings", settings)
|
|
588
|
+
|
|
589
|
+
def switch_context(self, system_prompt: Optional[str] = None, user_prompt: Optional[str] = None,
|
|
590
|
+
consolidate: bool = False, full_reset: bool = False) -> 'SwaigFunctionResult':
|
|
591
|
+
"""
|
|
592
|
+
Change agent context/prompt during conversation.
|
|
593
|
+
|
|
594
|
+
Args:
|
|
595
|
+
system_prompt: New system prompt
|
|
596
|
+
user_prompt: User message to add
|
|
597
|
+
consolidate: Whether to summarize existing conversation
|
|
598
|
+
full_reset: Whether to do complete context reset
|
|
599
|
+
|
|
600
|
+
Returns:
|
|
601
|
+
self for method chaining
|
|
602
|
+
"""
|
|
603
|
+
if system_prompt and not user_prompt and not consolidate and not full_reset:
|
|
604
|
+
# Simple string context switch
|
|
605
|
+
return self.add_action("context_switch", system_prompt)
|
|
606
|
+
else:
|
|
607
|
+
# Advanced object context switch
|
|
608
|
+
context_data = {}
|
|
609
|
+
if system_prompt:
|
|
610
|
+
context_data["system_prompt"] = system_prompt
|
|
611
|
+
if user_prompt:
|
|
612
|
+
context_data["user_prompt"] = user_prompt
|
|
613
|
+
if consolidate:
|
|
614
|
+
context_data["consolidate"] = True
|
|
615
|
+
if full_reset:
|
|
616
|
+
context_data["full_reset"] = True
|
|
617
|
+
return self.add_action("context_switch", context_data)
|
|
618
|
+
|
|
619
|
+
def simulate_user_input(self, text: str) -> 'SwaigFunctionResult':
|
|
620
|
+
"""
|
|
621
|
+
Queue simulated user input.
|
|
622
|
+
|
|
623
|
+
Args:
|
|
624
|
+
text: Text to simulate as user input
|
|
625
|
+
|
|
626
|
+
Returns:
|
|
627
|
+
self for method chaining
|
|
628
|
+
"""
|
|
629
|
+
return self.add_action("user_input", text)
|
|
630
|
+
|
|
631
|
+
def send_sms(self, to_number: str, from_number: str, body: Optional[str] = None,
|
|
632
|
+
media: Optional[List[str]] = None, tags: Optional[List[str]] = None,
|
|
633
|
+
region: Optional[str] = None) -> 'SwaigFunctionResult':
|
|
634
|
+
"""
|
|
635
|
+
Send a text message to a PSTN phone number using SWML.
|
|
636
|
+
|
|
637
|
+
This is a virtual helper that generates SWML to send SMS messages.
|
|
638
|
+
Either body or media (or both) must be provided.
|
|
639
|
+
|
|
640
|
+
Args:
|
|
641
|
+
to_number: Phone number in E.164 format to send to
|
|
642
|
+
from_number: Phone number in E.164 format to send from
|
|
643
|
+
body: Body text of the message (optional if media provided)
|
|
644
|
+
media: Array of URLs to send in the message (optional if body provided)
|
|
645
|
+
tags: Array of tags to associate with the message for UI searching
|
|
646
|
+
region: Region to originate the message from
|
|
647
|
+
|
|
648
|
+
Returns:
|
|
649
|
+
self for method chaining
|
|
650
|
+
|
|
651
|
+
Raises:
|
|
652
|
+
ValueError: If neither body nor media is provided
|
|
653
|
+
"""
|
|
654
|
+
# Validate that at least body or media is provided
|
|
655
|
+
if not body and not media:
|
|
656
|
+
raise ValueError("Either body or media must be provided")
|
|
657
|
+
|
|
658
|
+
# Build the send_sms parameters
|
|
659
|
+
sms_params = {
|
|
660
|
+
"to_number": to_number,
|
|
661
|
+
"from_number": from_number
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
# Add optional parameters
|
|
665
|
+
if body:
|
|
666
|
+
sms_params["body"] = body
|
|
667
|
+
if media:
|
|
668
|
+
sms_params["media"] = media
|
|
669
|
+
if tags:
|
|
670
|
+
sms_params["tags"] = tags
|
|
671
|
+
if region:
|
|
672
|
+
sms_params["region"] = region
|
|
673
|
+
|
|
674
|
+
# Generate SWML document
|
|
675
|
+
swml_doc = {
|
|
676
|
+
"version": "1.0.0",
|
|
677
|
+
"sections": {
|
|
678
|
+
"main": [
|
|
679
|
+
{"send_sms": sms_params}
|
|
680
|
+
]
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
# Use execute_swml to add the action
|
|
685
|
+
return self.execute_swml(swml_doc)
|
|
686
|
+
|
|
687
|
+
def pay(self, payment_connector_url: str, input_method: str = "dtmf",
|
|
688
|
+
status_url: Optional[str] = None, payment_method: str = "credit-card",
|
|
689
|
+
timeout: int = 5, max_attempts: int = 1, security_code: bool = True,
|
|
690
|
+
postal_code: Union[bool, str] = True, min_postal_code_length: int = 0,
|
|
691
|
+
token_type: str = "reusable", charge_amount: Optional[str] = None,
|
|
692
|
+
currency: str = "usd", language: str = "en-US", voice: str = "woman",
|
|
693
|
+
description: Optional[str] = None, valid_card_types: str = "visa mastercard amex",
|
|
694
|
+
parameters: Optional[List[Dict[str, str]]] = None,
|
|
695
|
+
prompts: Optional[List[Dict[str, Any]]] = None,
|
|
696
|
+
ai_response: Optional[str] = "The payment status is ${pay_result}, do not mention anything else about collecting payment if successful.") -> 'SwaigFunctionResult':
|
|
697
|
+
"""
|
|
698
|
+
Process payment using SWML pay action.
|
|
699
|
+
|
|
700
|
+
This is a virtual helper that generates SWML for payment processing.
|
|
701
|
+
|
|
702
|
+
Args:
|
|
703
|
+
payment_connector_url: URL to make payment requests to (required)
|
|
704
|
+
input_method: Method to collect payment details ("dtmf" or "voice")
|
|
705
|
+
status_url: URL for status change notifications
|
|
706
|
+
payment_method: Payment method ("credit-card" currently supported)
|
|
707
|
+
timeout: Seconds to wait for next digit (default: 5)
|
|
708
|
+
max_attempts: Number of retry attempts (default: 1)
|
|
709
|
+
security_code: Whether to prompt for security code (default: True)
|
|
710
|
+
postal_code: Whether to prompt for postal code, or actual postcode
|
|
711
|
+
min_postal_code_length: Minimum postal code digits (default: 0)
|
|
712
|
+
token_type: Payment type ("one-time" or "reusable", default: "reusable")
|
|
713
|
+
charge_amount: Amount to charge as decimal string
|
|
714
|
+
currency: Currency code (default: "usd")
|
|
715
|
+
language: Language for prompts (default: "en-US")
|
|
716
|
+
voice: TTS voice to use (default: "woman")
|
|
717
|
+
description: Custom payment description
|
|
718
|
+
valid_card_types: Space-separated card types (default: "visa mastercard amex")
|
|
719
|
+
parameters: Array of name/value pairs for payment connector
|
|
720
|
+
prompts: Array of custom prompt configurations
|
|
721
|
+
|
|
722
|
+
Returns:
|
|
723
|
+
self for method chaining
|
|
724
|
+
"""
|
|
725
|
+
# Build the pay parameters
|
|
726
|
+
pay_params = {
|
|
727
|
+
"payment_connector_url": payment_connector_url,
|
|
728
|
+
"input": input_method,
|
|
729
|
+
"payment_method": payment_method,
|
|
730
|
+
"timeout": str(timeout),
|
|
731
|
+
"max_attempts": str(max_attempts),
|
|
732
|
+
"security_code": str(security_code).lower(),
|
|
733
|
+
"min_postal_code_length": str(min_postal_code_length),
|
|
734
|
+
"token_type": token_type,
|
|
735
|
+
"currency": currency,
|
|
736
|
+
"language": language,
|
|
737
|
+
"voice": voice,
|
|
738
|
+
"valid_card_types": valid_card_types
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
# Handle postal_code (can be boolean or string)
|
|
742
|
+
if isinstance(postal_code, bool):
|
|
743
|
+
pay_params["postal_code"] = str(postal_code).lower()
|
|
744
|
+
else:
|
|
745
|
+
pay_params["postal_code"] = postal_code
|
|
746
|
+
|
|
747
|
+
# Add optional parameters
|
|
748
|
+
if status_url:
|
|
749
|
+
pay_params["status_url"] = status_url
|
|
750
|
+
if charge_amount:
|
|
751
|
+
pay_params["charge_amount"] = charge_amount
|
|
752
|
+
if description:
|
|
753
|
+
pay_params["description"] = description
|
|
754
|
+
if parameters:
|
|
755
|
+
pay_params["parameters"] = parameters
|
|
756
|
+
if prompts:
|
|
757
|
+
pay_params["prompts"] = prompts
|
|
758
|
+
|
|
759
|
+
# Generate SWML document
|
|
760
|
+
swml_doc = {
|
|
761
|
+
"version": "1.0.0",
|
|
762
|
+
"sections": {
|
|
763
|
+
"main": [
|
|
764
|
+
{"set": {"ai_response": ai_response}},
|
|
765
|
+
{"pay": pay_params}
|
|
766
|
+
]
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
# Use execute_swml to add the action
|
|
771
|
+
return self.execute_swml(swml_doc)
|
|
772
|
+
|
|
773
|
+
def record_call(self, control_id: Optional[str] = None, stereo: bool = False,
|
|
774
|
+
format: str = "wav", direction: str = "both",
|
|
775
|
+
terminators: Optional[str] = None, beep: bool = False,
|
|
776
|
+
input_sensitivity: float = 44.0, initial_timeout: float = 0.0,
|
|
777
|
+
end_silence_timeout: float = 0.0, max_length: Optional[float] = None,
|
|
778
|
+
status_url: Optional[str] = None) -> 'SwaigFunctionResult':
|
|
779
|
+
"""
|
|
780
|
+
Start background call recording using SWML.
|
|
781
|
+
|
|
782
|
+
This is a virtual helper that generates SWML to start recording the call
|
|
783
|
+
in the background. Unlike foreground recording, the script continues
|
|
784
|
+
executing while recording happens in the background.
|
|
785
|
+
|
|
786
|
+
Args:
|
|
787
|
+
control_id: Identifier for this recording (for use with stop_record_call)
|
|
788
|
+
stereo: Record in stereo (default: False)
|
|
789
|
+
format: Recording format - "wav" or "mp3" (default: "wav")
|
|
790
|
+
direction: Audio direction - "speak", "listen", or "both" (default: "both")
|
|
791
|
+
terminators: Digits that stop recording when pressed
|
|
792
|
+
beep: Play beep before recording (default: False)
|
|
793
|
+
input_sensitivity: Input sensitivity for recording (default: 44.0)
|
|
794
|
+
initial_timeout: Time in seconds to wait for speech start (default: 0.0)
|
|
795
|
+
end_silence_timeout: Time in seconds to wait in silence before ending (default: 0.0)
|
|
796
|
+
max_length: Maximum recording length in seconds
|
|
797
|
+
status_url: URL to send recording status events to
|
|
798
|
+
|
|
799
|
+
Returns:
|
|
800
|
+
self for method chaining
|
|
801
|
+
"""
|
|
802
|
+
# Validate format parameter
|
|
803
|
+
if format not in ["wav", "mp3"]:
|
|
804
|
+
raise ValueError("format must be 'wav' or 'mp3'")
|
|
805
|
+
|
|
806
|
+
# Validate direction parameter
|
|
807
|
+
if direction not in ["speak", "listen", "both"]:
|
|
808
|
+
raise ValueError("direction must be 'speak', 'listen', or 'both'")
|
|
809
|
+
|
|
810
|
+
# Build the record_call parameters
|
|
811
|
+
record_params = {
|
|
812
|
+
"stereo": stereo,
|
|
813
|
+
"format": format,
|
|
814
|
+
"direction": direction,
|
|
815
|
+
"beep": beep,
|
|
816
|
+
"input_sensitivity": input_sensitivity,
|
|
817
|
+
"initial_timeout": initial_timeout,
|
|
818
|
+
"end_silence_timeout": end_silence_timeout
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
# Add optional parameters
|
|
822
|
+
if control_id:
|
|
823
|
+
record_params["control_id"] = control_id
|
|
824
|
+
if terminators:
|
|
825
|
+
record_params["terminators"] = terminators
|
|
826
|
+
if max_length:
|
|
827
|
+
record_params["max_length"] = max_length
|
|
828
|
+
if status_url:
|
|
829
|
+
record_params["status_url"] = status_url
|
|
830
|
+
|
|
831
|
+
# Generate SWML document
|
|
832
|
+
swml_doc = {
|
|
833
|
+
"version": "1.0.0",
|
|
834
|
+
"sections": {
|
|
835
|
+
"main": [
|
|
836
|
+
{"record_call": record_params}
|
|
837
|
+
]
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
# Use execute_swml to add the action
|
|
842
|
+
return self.execute_swml(swml_doc)
|
|
843
|
+
|
|
844
|
+
def stop_record_call(self, control_id: Optional[str] = None) -> 'SwaigFunctionResult':
|
|
845
|
+
"""
|
|
846
|
+
Stop an active background call recording using SWML.
|
|
847
|
+
|
|
848
|
+
This is a virtual helper that generates SWML to stop a recording that
|
|
849
|
+
was started with record_call().
|
|
850
|
+
|
|
851
|
+
Args:
|
|
852
|
+
control_id: Identifier for the recording to stop. If not provided,
|
|
853
|
+
the most recent recording will be stopped.
|
|
854
|
+
|
|
855
|
+
Returns:
|
|
856
|
+
self for method chaining
|
|
857
|
+
"""
|
|
858
|
+
# Build the stop_record_call parameters
|
|
859
|
+
stop_params = {}
|
|
860
|
+
if control_id:
|
|
861
|
+
stop_params["control_id"] = control_id
|
|
862
|
+
|
|
863
|
+
# Generate SWML document
|
|
864
|
+
swml_doc = {
|
|
865
|
+
"version": "1.0.0",
|
|
866
|
+
"sections": {
|
|
867
|
+
"main": [
|
|
868
|
+
{"stop_record_call": stop_params}
|
|
869
|
+
]
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
# Use execute_swml to add the action
|
|
874
|
+
return self.execute_swml(swml_doc)
|
|
875
|
+
|
|
876
|
+
def join_room(self, name: str) -> 'SwaigFunctionResult':
|
|
877
|
+
"""
|
|
878
|
+
Join a RELAY room using SWML.
|
|
879
|
+
|
|
880
|
+
This is a virtual helper that generates SWML to join a RELAY room,
|
|
881
|
+
which enables multi-party communication and collaboration.
|
|
882
|
+
|
|
883
|
+
Args:
|
|
884
|
+
name: The name of the room to join (required)
|
|
885
|
+
|
|
886
|
+
Returns:
|
|
887
|
+
self for method chaining
|
|
888
|
+
"""
|
|
889
|
+
# Build the join_room parameters
|
|
890
|
+
join_params = {"name": name}
|
|
891
|
+
|
|
892
|
+
# Generate SWML document
|
|
893
|
+
swml_doc = {
|
|
894
|
+
"version": "1.0.0",
|
|
895
|
+
"sections": {
|
|
896
|
+
"main": [
|
|
897
|
+
{"join_room": join_params}
|
|
898
|
+
]
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
# Use execute_swml to add the action
|
|
903
|
+
return self.execute_swml(swml_doc)
|
|
904
|
+
|
|
905
|
+
def sip_refer(self, to_uri: str) -> 'SwaigFunctionResult':
|
|
906
|
+
"""
|
|
907
|
+
Send SIP REFER to a SIP call using SWML.
|
|
908
|
+
|
|
909
|
+
This is a virtual helper that generates SWML to send a SIP REFER
|
|
910
|
+
message, which is used for call transfer in SIP environments.
|
|
911
|
+
|
|
912
|
+
Args:
|
|
913
|
+
to_uri: The SIP URI to send the REFER to (required)
|
|
914
|
+
|
|
915
|
+
Returns:
|
|
916
|
+
self for method chaining
|
|
917
|
+
"""
|
|
918
|
+
# Build the sip_refer parameters
|
|
919
|
+
refer_params = {"to_uri": to_uri}
|
|
920
|
+
|
|
921
|
+
# Generate SWML document
|
|
922
|
+
swml_doc = {
|
|
923
|
+
"version": "1.0.0",
|
|
924
|
+
"sections": {
|
|
925
|
+
"main": [
|
|
926
|
+
{"sip_refer": refer_params}
|
|
927
|
+
]
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
# Use execute_swml to add the action
|
|
932
|
+
return self.execute_swml(swml_doc)
|
|
933
|
+
|
|
934
|
+
def join_conference(self, name: str, muted: bool = False, beep: str = "true",
|
|
935
|
+
start_on_enter: bool = True, end_on_exit: bool = False,
|
|
936
|
+
wait_url: Optional[str] = None, max_participants: int = 250,
|
|
937
|
+
record: str = "do-not-record", region: Optional[str] = None,
|
|
938
|
+
trim: str = "trim-silence", coach: Optional[str] = None,
|
|
939
|
+
status_callback_event: Optional[str] = None,
|
|
940
|
+
status_callback: Optional[str] = None,
|
|
941
|
+
status_callback_method: str = "POST",
|
|
942
|
+
recording_status_callback: Optional[str] = None,
|
|
943
|
+
recording_status_callback_method: str = "POST",
|
|
944
|
+
recording_status_callback_event: str = "completed",
|
|
945
|
+
result: Optional[Any] = None) -> 'SwaigFunctionResult':
|
|
946
|
+
"""
|
|
947
|
+
Join an ad-hoc audio conference with RELAY and CXML calls using SWML.
|
|
948
|
+
|
|
949
|
+
This is a virtual helper that generates SWML to join audio conferences
|
|
950
|
+
with extensive configuration options for call management and recording.
|
|
951
|
+
|
|
952
|
+
Args:
|
|
953
|
+
name: Name of conference (required)
|
|
954
|
+
muted: Whether to join muted (default: False)
|
|
955
|
+
beep: Beep configuration - "true", "false", "onEnter", "onExit" (default: "true")
|
|
956
|
+
start_on_enter: Whether conference starts when this participant enters (default: True)
|
|
957
|
+
end_on_exit: Whether conference ends when this participant exits (default: False)
|
|
958
|
+
wait_url: SWML URL for hold music (default: None for default hold music)
|
|
959
|
+
max_participants: Maximum participants <= 250 (default: 250)
|
|
960
|
+
record: Recording mode - "do-not-record", "record-from-start" (default: "do-not-record")
|
|
961
|
+
region: Conference region (default: None)
|
|
962
|
+
trim: Trim silence - "trim-silence", "do-not-trim" (default: "trim-silence")
|
|
963
|
+
coach: SWML Call ID or CXML CallSid for coaching (default: None)
|
|
964
|
+
status_callback_event: Events to report - "start end join leave mute hold modify speaker announcement" (default: None)
|
|
965
|
+
status_callback: URL for status callbacks (default: None)
|
|
966
|
+
status_callback_method: HTTP method - "GET", "POST" (default: "POST")
|
|
967
|
+
recording_status_callback: URL for recording status callbacks (default: None)
|
|
968
|
+
recording_status_callback_method: HTTP method - "GET", "POST" (default: "POST")
|
|
969
|
+
recording_status_callback_event: Recording events - "in-progress completed absent" (default: "completed")
|
|
970
|
+
result: Switch on return_value when object {} or cond when array [] (default: None)
|
|
971
|
+
|
|
972
|
+
Returns:
|
|
973
|
+
self for method chaining
|
|
974
|
+
|
|
975
|
+
Raises:
|
|
976
|
+
ValueError: If beep value is invalid or max_participants exceeds 250
|
|
977
|
+
"""
|
|
978
|
+
# Validate beep parameter
|
|
979
|
+
valid_beep_values = ["true", "false", "onEnter", "onExit"]
|
|
980
|
+
if beep not in valid_beep_values:
|
|
981
|
+
raise ValueError(f"beep must be one of {valid_beep_values}")
|
|
982
|
+
|
|
983
|
+
# Validate max_participants
|
|
984
|
+
if max_participants <= 0 or max_participants > 250:
|
|
985
|
+
raise ValueError("max_participants must be a positive integer <= 250")
|
|
986
|
+
|
|
987
|
+
# Validate record parameter
|
|
988
|
+
valid_record_values = ["do-not-record", "record-from-start"]
|
|
989
|
+
if record not in valid_record_values:
|
|
990
|
+
raise ValueError(f"record must be one of {valid_record_values}")
|
|
991
|
+
|
|
992
|
+
# Validate trim parameter
|
|
993
|
+
valid_trim_values = ["trim-silence", "do-not-trim"]
|
|
994
|
+
if trim not in valid_trim_values:
|
|
995
|
+
raise ValueError(f"trim must be one of {valid_trim_values}")
|
|
996
|
+
|
|
997
|
+
# Validate status_callback_method
|
|
998
|
+
valid_methods = ["GET", "POST"]
|
|
999
|
+
if status_callback_method not in valid_methods:
|
|
1000
|
+
raise ValueError(f"status_callback_method must be one of {valid_methods}")
|
|
1001
|
+
if recording_status_callback_method not in valid_methods:
|
|
1002
|
+
raise ValueError(f"recording_status_callback_method must be one of {valid_methods}")
|
|
1003
|
+
|
|
1004
|
+
# Build the join_conference parameters - start with required parameter
|
|
1005
|
+
if isinstance(name, str) and not name.strip():
|
|
1006
|
+
raise ValueError("name cannot be empty")
|
|
1007
|
+
|
|
1008
|
+
# For simple case, can just be the conference name
|
|
1009
|
+
if (not muted and beep == "true" and start_on_enter and not end_on_exit and
|
|
1010
|
+
wait_url is None and max_participants == 250 and record == "do-not-record" and
|
|
1011
|
+
region is None and trim == "trim-silence" and coach is None and
|
|
1012
|
+
status_callback_event is None and status_callback is None and
|
|
1013
|
+
status_callback_method == "POST" and recording_status_callback is None and
|
|
1014
|
+
recording_status_callback_method == "POST" and recording_status_callback_event == "completed" and
|
|
1015
|
+
result is None):
|
|
1016
|
+
# Simple form - just the conference name
|
|
1017
|
+
join_params = name
|
|
1018
|
+
else:
|
|
1019
|
+
# Full object form with parameters
|
|
1020
|
+
join_params = {"name": name}
|
|
1021
|
+
|
|
1022
|
+
# Add non-default parameters
|
|
1023
|
+
if muted:
|
|
1024
|
+
join_params["muted"] = muted
|
|
1025
|
+
if beep != "true":
|
|
1026
|
+
join_params["beep"] = beep
|
|
1027
|
+
if not start_on_enter:
|
|
1028
|
+
join_params["start_on_enter"] = start_on_enter
|
|
1029
|
+
if end_on_exit:
|
|
1030
|
+
join_params["end_on_exit"] = end_on_exit
|
|
1031
|
+
if wait_url:
|
|
1032
|
+
join_params["wait_url"] = wait_url
|
|
1033
|
+
if max_participants != 250:
|
|
1034
|
+
join_params["max_participants"] = max_participants
|
|
1035
|
+
if record != "do-not-record":
|
|
1036
|
+
join_params["record"] = record
|
|
1037
|
+
if region:
|
|
1038
|
+
join_params["region"] = region
|
|
1039
|
+
if trim != "trim-silence":
|
|
1040
|
+
join_params["trim"] = trim
|
|
1041
|
+
if coach:
|
|
1042
|
+
join_params["coach"] = coach
|
|
1043
|
+
if status_callback_event:
|
|
1044
|
+
join_params["status_callback_event"] = status_callback_event
|
|
1045
|
+
if status_callback:
|
|
1046
|
+
join_params["status_callback"] = status_callback
|
|
1047
|
+
if status_callback_method != "POST":
|
|
1048
|
+
join_params["status_callback_method"] = status_callback_method
|
|
1049
|
+
if recording_status_callback:
|
|
1050
|
+
join_params["recording_status_callback"] = recording_status_callback
|
|
1051
|
+
if recording_status_callback_method != "POST":
|
|
1052
|
+
join_params["recording_status_callback_method"] = recording_status_callback_method
|
|
1053
|
+
if recording_status_callback_event != "completed":
|
|
1054
|
+
join_params["recording_status_callback_event"] = recording_status_callback_event
|
|
1055
|
+
if result is not None:
|
|
1056
|
+
join_params["result"] = result
|
|
1057
|
+
|
|
1058
|
+
# Generate SWML document
|
|
1059
|
+
swml_doc = {
|
|
1060
|
+
"version": "1.0.0",
|
|
1061
|
+
"sections": {
|
|
1062
|
+
"main": [
|
|
1063
|
+
{"join_conference": join_params}
|
|
1064
|
+
]
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
# Use execute_swml to add the action
|
|
1069
|
+
return self.execute_swml(swml_doc)
|
|
1070
|
+
|
|
1071
|
+
def tap(self, uri: str, control_id: Optional[str] = None, direction: str = "both",
|
|
1072
|
+
codec: str = "PCMU", rtp_ptime: int = 20,
|
|
1073
|
+
status_url: Optional[str] = None) -> 'SwaigFunctionResult':
|
|
1074
|
+
"""
|
|
1075
|
+
Start background call tap using SWML.
|
|
1076
|
+
|
|
1077
|
+
This is a virtual helper that generates SWML to start background call tapping.
|
|
1078
|
+
Media is streamed over Websocket or RTP to customer controlled URI.
|
|
1079
|
+
|
|
1080
|
+
Args:
|
|
1081
|
+
uri: Destination of tap media stream (required)
|
|
1082
|
+
Formats: rtp://IP:port, ws://example.com, or wss://example.com
|
|
1083
|
+
control_id: Identifier for this tap to use with stop_tap (optional)
|
|
1084
|
+
Default is generated and stored in tap_control_id variable
|
|
1085
|
+
direction: Direction of audio to tap (default: "both")
|
|
1086
|
+
"speak" = what party says
|
|
1087
|
+
"hear" = what party hears
|
|
1088
|
+
"both" = what party hears and says
|
|
1089
|
+
codec: Codec for tap media stream - "PCMU" or "PCMA" (default: "PCMU")
|
|
1090
|
+
rtp_ptime: Packetization time in milliseconds for RTP (default: 20)
|
|
1091
|
+
status_url: URL for status change requests (optional)
|
|
1092
|
+
|
|
1093
|
+
Returns:
|
|
1094
|
+
self for method chaining
|
|
1095
|
+
|
|
1096
|
+
Raises:
|
|
1097
|
+
ValueError: If direction or codec values are invalid
|
|
1098
|
+
"""
|
|
1099
|
+
# Validate direction parameter
|
|
1100
|
+
valid_directions = ["speak", "hear", "both"]
|
|
1101
|
+
if direction not in valid_directions:
|
|
1102
|
+
raise ValueError(f"direction must be one of {valid_directions}")
|
|
1103
|
+
|
|
1104
|
+
# Validate codec parameter
|
|
1105
|
+
valid_codecs = ["PCMU", "PCMA"]
|
|
1106
|
+
if codec not in valid_codecs:
|
|
1107
|
+
raise ValueError(f"codec must be one of {valid_codecs}")
|
|
1108
|
+
|
|
1109
|
+
# Validate rtp_ptime
|
|
1110
|
+
if rtp_ptime <= 0:
|
|
1111
|
+
raise ValueError("rtp_ptime must be a positive integer")
|
|
1112
|
+
|
|
1113
|
+
# Build the tap parameters
|
|
1114
|
+
tap_params = {"uri": uri}
|
|
1115
|
+
|
|
1116
|
+
# Add optional parameters if they differ from defaults
|
|
1117
|
+
if control_id:
|
|
1118
|
+
tap_params["control_id"] = control_id
|
|
1119
|
+
if direction != "both":
|
|
1120
|
+
tap_params["direction"] = direction
|
|
1121
|
+
if codec != "PCMU":
|
|
1122
|
+
tap_params["codec"] = codec
|
|
1123
|
+
if rtp_ptime != 20:
|
|
1124
|
+
tap_params["rtp_ptime"] = rtp_ptime
|
|
1125
|
+
if status_url:
|
|
1126
|
+
tap_params["status_url"] = status_url
|
|
1127
|
+
|
|
1128
|
+
# Generate SWML document
|
|
1129
|
+
swml_doc = {
|
|
1130
|
+
"version": "1.0.0",
|
|
1131
|
+
"sections": {
|
|
1132
|
+
"main": [
|
|
1133
|
+
{"tap": tap_params}
|
|
1134
|
+
]
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
# Use execute_swml to add the action
|
|
1139
|
+
return self.execute_swml(swml_doc)
|
|
1140
|
+
|
|
1141
|
+
def stop_tap(self, control_id: Optional[str] = None) -> 'SwaigFunctionResult':
|
|
1142
|
+
"""
|
|
1143
|
+
Stop an active tap stream using SWML.
|
|
1144
|
+
|
|
1145
|
+
This is a virtual helper that generates SWML to stop a tap stream
|
|
1146
|
+
that was started with tap().
|
|
1147
|
+
|
|
1148
|
+
Args:
|
|
1149
|
+
control_id: ID of the tap to stop (optional)
|
|
1150
|
+
If not set, the last tap started will be stopped
|
|
1151
|
+
|
|
1152
|
+
Returns:
|
|
1153
|
+
self for method chaining
|
|
1154
|
+
"""
|
|
1155
|
+
# Build the stop_tap parameters
|
|
1156
|
+
if control_id:
|
|
1157
|
+
stop_params = {"control_id": control_id}
|
|
1158
|
+
else:
|
|
1159
|
+
# For simple case with no control_id, use empty object
|
|
1160
|
+
stop_params = {}
|
|
1161
|
+
|
|
1162
|
+
# Generate SWML document
|
|
1163
|
+
swml_doc = {
|
|
1164
|
+
"version": "1.0.0",
|
|
1165
|
+
"sections": {
|
|
1166
|
+
"main": [
|
|
1167
|
+
{"stop_tap": stop_params}
|
|
1168
|
+
]
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
# Use execute_swml to add the action
|
|
1173
|
+
return self.execute_swml(swml_doc)
|
|
1174
|
+
|
|
1175
|
+
@staticmethod
|
|
1176
|
+
def create_payment_prompt(for_situation: str, actions: List[Dict[str, str]],
|
|
1177
|
+
card_type: Optional[str] = None,
|
|
1178
|
+
error_type: Optional[str] = None) -> Dict[str, Any]:
|
|
1179
|
+
"""
|
|
1180
|
+
Create a payment prompt structure for use with pay() method.
|
|
1181
|
+
|
|
1182
|
+
Args:
|
|
1183
|
+
for_situation: Situation to use prompt for (e.g., "payment-card-number")
|
|
1184
|
+
actions: List of actions with 'type' and 'phrase' keys
|
|
1185
|
+
card_type: Space-separated card types for this prompt
|
|
1186
|
+
error_type: Space-separated error types for this prompt
|
|
1187
|
+
|
|
1188
|
+
Returns:
|
|
1189
|
+
Dictionary representing the prompt structure
|
|
1190
|
+
"""
|
|
1191
|
+
prompt = {
|
|
1192
|
+
"for": for_situation,
|
|
1193
|
+
"actions": actions
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
if card_type:
|
|
1197
|
+
prompt["card_type"] = card_type
|
|
1198
|
+
if error_type:
|
|
1199
|
+
prompt["error_type"] = error_type
|
|
1200
|
+
|
|
1201
|
+
return prompt
|
|
1202
|
+
|
|
1203
|
+
@staticmethod
|
|
1204
|
+
def create_payment_action(action_type: str, phrase: str) -> Dict[str, str]:
|
|
1205
|
+
"""
|
|
1206
|
+
Create a payment action for use in payment prompts.
|
|
1207
|
+
|
|
1208
|
+
Args:
|
|
1209
|
+
action_type: "Say" for text-to-speech or "Play" for audio file
|
|
1210
|
+
phrase: Sentence to say or URL to play
|
|
1211
|
+
|
|
1212
|
+
Returns:
|
|
1213
|
+
Dictionary representing the action
|
|
1214
|
+
"""
|
|
1215
|
+
return {
|
|
1216
|
+
"type": action_type,
|
|
1217
|
+
"phrase": phrase
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
@staticmethod
|
|
1221
|
+
def create_payment_parameter(name: str, value: str) -> Dict[str, str]:
|
|
1222
|
+
"""
|
|
1223
|
+
Create a payment parameter for use with pay() method.
|
|
1224
|
+
|
|
1225
|
+
Args:
|
|
1226
|
+
name: Parameter name
|
|
1227
|
+
value: Parameter value
|
|
1228
|
+
|
|
1229
|
+
Returns:
|
|
1230
|
+
Dictionary representing the parameter
|
|
1231
|
+
"""
|
|
1232
|
+
return {
|
|
1233
|
+
"name": name,
|
|
1234
|
+
"value": value
|
|
1235
|
+
}
|
|
1236
|
+
|
|
96
1237
|
def to_dict(self) -> Dict[str, Any]:
|
|
97
1238
|
"""
|
|
98
1239
|
Convert to the JSON structure expected by SWAIG
|
|
@@ -101,6 +1242,9 @@ class SwaigFunctionResult:
|
|
|
101
1242
|
- 'response': Text to be spoken by the AI
|
|
102
1243
|
- 'action': Array of action objects
|
|
103
1244
|
|
|
1245
|
+
Optional:
|
|
1246
|
+
- 'post_process': Boolean controlling when actions execute
|
|
1247
|
+
|
|
104
1248
|
Returns:
|
|
105
1249
|
Dictionary in SWAIG function response format
|
|
106
1250
|
"""
|
|
@@ -115,6 +1259,11 @@ class SwaigFunctionResult:
|
|
|
115
1259
|
if self.action:
|
|
116
1260
|
result["action"] = self.action
|
|
117
1261
|
|
|
1262
|
+
# Add post_process if enabled and we have actions
|
|
1263
|
+
# (post_process only matters when there are actions to execute)
|
|
1264
|
+
if self.post_process and self.action:
|
|
1265
|
+
result["post_process"] = True
|
|
1266
|
+
|
|
118
1267
|
# Ensure we have at least one of response or action
|
|
119
1268
|
if not result:
|
|
120
1269
|
# Default response if neither is present
|