signalwire-agents 0.1.0__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 +17 -0
- signalwire_agents/agent_server.py +336 -0
- signalwire_agents/core/__init__.py +20 -0
- signalwire_agents/core/agent_base.py +2449 -0
- signalwire_agents/core/function_result.py +104 -0
- signalwire_agents/core/pom_builder.py +195 -0
- signalwire_agents/core/security/__init__.py +0 -0
- signalwire_agents/core/security/session_manager.py +170 -0
- signalwire_agents/core/state/__init__.py +8 -0
- signalwire_agents/core/state/file_state_manager.py +210 -0
- signalwire_agents/core/state/state_manager.py +92 -0
- signalwire_agents/core/swaig_function.py +163 -0
- signalwire_agents/core/swml_builder.py +205 -0
- signalwire_agents/core/swml_handler.py +218 -0
- signalwire_agents/core/swml_renderer.py +359 -0
- signalwire_agents/core/swml_service.py +1009 -0
- signalwire_agents/prefabs/__init__.py +15 -0
- signalwire_agents/prefabs/concierge.py +276 -0
- signalwire_agents/prefabs/faq_bot.py +314 -0
- signalwire_agents/prefabs/info_gatherer.py +253 -0
- signalwire_agents/prefabs/survey.py +387 -0
- signalwire_agents/schema.json +5611 -0
- signalwire_agents/utils/__init__.py +0 -0
- signalwire_agents/utils/pom_utils.py +0 -0
- signalwire_agents/utils/schema_utils.py +348 -0
- signalwire_agents/utils/token_generators.py +0 -0
- signalwire_agents/utils/validators.py +0 -0
- signalwire_agents-0.1.0.data/data/schema.json +5611 -0
- signalwire_agents-0.1.0.dist-info/METADATA +154 -0
- signalwire_agents-0.1.0.dist-info/RECORD +32 -0
- signalwire_agents-0.1.0.dist-info/WHEEL +5 -0
- signalwire_agents-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,359 @@
|
|
1
|
+
"""
|
2
|
+
SwmlRenderer for generating complete SWML documents for SignalWire AI Agents
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Dict, List, Any, Optional, Union
|
6
|
+
import json
|
7
|
+
import yaml
|
8
|
+
|
9
|
+
from signalwire_agents.core.swml_service import SWMLService
|
10
|
+
from signalwire_agents.core.swml_builder import SWMLBuilder
|
11
|
+
|
12
|
+
|
13
|
+
class SwmlRenderer:
|
14
|
+
"""
|
15
|
+
Renders SWML documents for SignalWire AI Agents with AI and SWAIG components
|
16
|
+
|
17
|
+
This class provides backward-compatible methods for rendering SWML documents
|
18
|
+
while also supporting the new SWMLService architecture. It can work either
|
19
|
+
standalone (legacy mode) or with a SWMLService instance.
|
20
|
+
"""
|
21
|
+
|
22
|
+
@staticmethod
|
23
|
+
def render_swml(
|
24
|
+
prompt: Union[str, List[Dict[str, Any]]],
|
25
|
+
post_prompt: Optional[str] = None,
|
26
|
+
post_prompt_url: Optional[str] = None,
|
27
|
+
swaig_functions: Optional[List[Dict[str, Any]]] = None,
|
28
|
+
startup_hook_url: Optional[str] = None,
|
29
|
+
hangup_hook_url: Optional[str] = None,
|
30
|
+
prompt_is_pom: bool = False,
|
31
|
+
params: Optional[Dict[str, Any]] = None,
|
32
|
+
add_answer: bool = False,
|
33
|
+
record_call: bool = False,
|
34
|
+
record_format: str = "mp4",
|
35
|
+
record_stereo: bool = True,
|
36
|
+
format: str = "json",
|
37
|
+
default_webhook_url: Optional[str] = None,
|
38
|
+
service: Optional[SWMLService] = None
|
39
|
+
) -> str:
|
40
|
+
"""
|
41
|
+
Generate a complete SWML document with AI configuration
|
42
|
+
|
43
|
+
Args:
|
44
|
+
prompt: Either a string prompt or a POM in list-of-dict format
|
45
|
+
post_prompt: Optional post-prompt text (for summary)
|
46
|
+
post_prompt_url: URL to receive the post-prompt result
|
47
|
+
swaig_functions: List of SWAIG function definitions
|
48
|
+
startup_hook_url: URL for startup hook
|
49
|
+
hangup_hook_url: URL for hangup hook
|
50
|
+
prompt_is_pom: Whether prompt is a POM object or raw text
|
51
|
+
params: Additional AI params (temperature, etc)
|
52
|
+
add_answer: Whether to auto-add the answer block after AI
|
53
|
+
record_call: Whether to add a record_call block
|
54
|
+
record_format: Format for recording the call
|
55
|
+
record_stereo: Whether to record in stereo
|
56
|
+
format: Output format, 'json' or 'yaml'
|
57
|
+
default_webhook_url: Optional default webhook URL for all SWAIG functions
|
58
|
+
service: Optional SWMLService instance to use
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
SWML document as a string
|
62
|
+
"""
|
63
|
+
# If we have a service, use it to build the document
|
64
|
+
if service:
|
65
|
+
# Create a builder for the service
|
66
|
+
builder = SWMLBuilder(service)
|
67
|
+
|
68
|
+
# Reset the document to start fresh
|
69
|
+
builder.reset()
|
70
|
+
|
71
|
+
# Add answer block if requested
|
72
|
+
if add_answer:
|
73
|
+
builder.answer()
|
74
|
+
|
75
|
+
# Add record_call if requested
|
76
|
+
if record_call:
|
77
|
+
# TODO: Add record_call to builder API
|
78
|
+
service.add_verb("record_call", {
|
79
|
+
"format": record_format,
|
80
|
+
"stereo": record_stereo
|
81
|
+
})
|
82
|
+
|
83
|
+
# Configure SWAIG object for AI verb
|
84
|
+
swaig_config = {}
|
85
|
+
functions = []
|
86
|
+
|
87
|
+
# Add startup hook if provided
|
88
|
+
if startup_hook_url:
|
89
|
+
functions.append({
|
90
|
+
"function": "startup_hook",
|
91
|
+
"description": "Called when the call starts",
|
92
|
+
"parameters": {
|
93
|
+
"type": "object",
|
94
|
+
"properties": {}
|
95
|
+
},
|
96
|
+
"web_hook_url": startup_hook_url
|
97
|
+
})
|
98
|
+
|
99
|
+
# Add hangup hook if provided
|
100
|
+
if hangup_hook_url:
|
101
|
+
functions.append({
|
102
|
+
"function": "hangup_hook",
|
103
|
+
"description": "Called when the call ends",
|
104
|
+
"parameters": {
|
105
|
+
"type": "object",
|
106
|
+
"properties": {}
|
107
|
+
},
|
108
|
+
"web_hook_url": hangup_hook_url
|
109
|
+
})
|
110
|
+
|
111
|
+
# Add regular functions if provided
|
112
|
+
if swaig_functions:
|
113
|
+
for func in swaig_functions:
|
114
|
+
# Skip special hooks as we've already added them
|
115
|
+
if func.get("function") not in ["startup_hook", "hangup_hook"]:
|
116
|
+
functions.append(func)
|
117
|
+
|
118
|
+
# Only add SWAIG if we have functions or a default URL
|
119
|
+
if functions or default_webhook_url:
|
120
|
+
swaig_config = {}
|
121
|
+
|
122
|
+
# Add defaults if we have a default webhook URL
|
123
|
+
if default_webhook_url:
|
124
|
+
swaig_config["defaults"] = {
|
125
|
+
"web_hook_url": default_webhook_url
|
126
|
+
}
|
127
|
+
|
128
|
+
# Add functions if we have any
|
129
|
+
if functions:
|
130
|
+
swaig_config["functions"] = functions
|
131
|
+
|
132
|
+
# Add AI verb with appropriate configuration
|
133
|
+
builder.ai(
|
134
|
+
prompt_text=None if prompt_is_pom else prompt,
|
135
|
+
prompt_pom=prompt if prompt_is_pom else None,
|
136
|
+
post_prompt=post_prompt,
|
137
|
+
post_prompt_url=post_prompt_url,
|
138
|
+
swaig=swaig_config if swaig_config else None,
|
139
|
+
**(params or {})
|
140
|
+
)
|
141
|
+
|
142
|
+
# Get the document as a dictionary or string based on format
|
143
|
+
if format.lower() == "yaml":
|
144
|
+
import yaml
|
145
|
+
return yaml.dump(builder.build(), sort_keys=False)
|
146
|
+
else:
|
147
|
+
return builder.render()
|
148
|
+
else:
|
149
|
+
# Legacy implementation (unchanged for backward compatibility)
|
150
|
+
# Start building the SWML document
|
151
|
+
swml = {
|
152
|
+
"version": "1.0.0",
|
153
|
+
"sections": {
|
154
|
+
"main": []
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
# Build the AI block
|
159
|
+
ai_block = {
|
160
|
+
"ai": {
|
161
|
+
"prompt": {}
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
# Set prompt based on type
|
166
|
+
if prompt_is_pom:
|
167
|
+
ai_block["ai"]["prompt"]["pom"] = prompt
|
168
|
+
else:
|
169
|
+
ai_block["ai"]["prompt"]["text"] = prompt
|
170
|
+
|
171
|
+
# Add post_prompt if provided
|
172
|
+
if post_prompt:
|
173
|
+
ai_block["ai"]["post_prompt"] = {
|
174
|
+
"text": post_prompt
|
175
|
+
}
|
176
|
+
|
177
|
+
# Add post_prompt_url if provided
|
178
|
+
if post_prompt_url:
|
179
|
+
ai_block["ai"]["post_prompt_url"] = post_prompt_url
|
180
|
+
|
181
|
+
# SWAIG is a dictionary not an array (fix from old implementation)
|
182
|
+
ai_block["ai"]["SWAIG"] = {}
|
183
|
+
|
184
|
+
# Add defaults if we have a default webhook URL
|
185
|
+
if default_webhook_url:
|
186
|
+
ai_block["ai"]["SWAIG"]["defaults"] = {
|
187
|
+
"web_hook_url": default_webhook_url
|
188
|
+
}
|
189
|
+
|
190
|
+
# Collect all functions
|
191
|
+
functions = []
|
192
|
+
|
193
|
+
# Add SWAIG hooks if provided
|
194
|
+
if startup_hook_url:
|
195
|
+
startup_hook = {
|
196
|
+
"function": "startup_hook",
|
197
|
+
"description": "Called when the call starts",
|
198
|
+
"parameters": {
|
199
|
+
"type": "object",
|
200
|
+
"properties": {}
|
201
|
+
},
|
202
|
+
"web_hook_url": startup_hook_url
|
203
|
+
}
|
204
|
+
functions.append(startup_hook)
|
205
|
+
|
206
|
+
if hangup_hook_url:
|
207
|
+
hangup_hook = {
|
208
|
+
"function": "hangup_hook",
|
209
|
+
"description": "Called when the call ends",
|
210
|
+
"parameters": {
|
211
|
+
"type": "object",
|
212
|
+
"properties": {}
|
213
|
+
},
|
214
|
+
"web_hook_url": hangup_hook_url
|
215
|
+
}
|
216
|
+
functions.append(hangup_hook)
|
217
|
+
|
218
|
+
# Add regular functions from the provided list
|
219
|
+
if swaig_functions:
|
220
|
+
for func in swaig_functions:
|
221
|
+
# Skip special hooks as we've already added them
|
222
|
+
if func.get("function") not in ["startup_hook", "hangup_hook"]:
|
223
|
+
functions.append(func)
|
224
|
+
|
225
|
+
# Add functions to SWAIG if we have any
|
226
|
+
if functions:
|
227
|
+
ai_block["ai"]["SWAIG"]["functions"] = functions
|
228
|
+
|
229
|
+
# Add AI params if provided (but not rendering settings)
|
230
|
+
if params:
|
231
|
+
# Filter out non-AI parameters that should be separate SWML methods
|
232
|
+
ai_params = {k: v for k, v in params.items()
|
233
|
+
if k not in ["auto_answer", "record_call", "record_format", "record_stereo"]}
|
234
|
+
|
235
|
+
# Only update if we have valid AI parameters
|
236
|
+
if ai_params:
|
237
|
+
ai_block["ai"]["params"] = ai_params
|
238
|
+
|
239
|
+
# Start building the SWML blocks
|
240
|
+
main_blocks = []
|
241
|
+
|
242
|
+
# Add answer block first if requested (to answer the call)
|
243
|
+
if add_answer:
|
244
|
+
main_blocks.append({"answer": {}})
|
245
|
+
|
246
|
+
# Add record_call block next if requested
|
247
|
+
if record_call:
|
248
|
+
main_blocks.append({
|
249
|
+
"record_call": {
|
250
|
+
"format": record_format,
|
251
|
+
"stereo": record_stereo # SWML expects a boolean not a string
|
252
|
+
}
|
253
|
+
})
|
254
|
+
|
255
|
+
# Add the AI block
|
256
|
+
main_blocks.append(ai_block)
|
257
|
+
|
258
|
+
# Set the main section to our ordered blocks
|
259
|
+
swml["sections"]["main"] = main_blocks
|
260
|
+
|
261
|
+
# Return in requested format
|
262
|
+
if format.lower() == "yaml":
|
263
|
+
return yaml.dump(swml, sort_keys=False)
|
264
|
+
else:
|
265
|
+
return json.dumps(swml, indent=2)
|
266
|
+
|
267
|
+
@staticmethod
|
268
|
+
def render_function_response_swml(
|
269
|
+
response_text: str,
|
270
|
+
actions: Optional[List[Dict[str, Any]]] = None,
|
271
|
+
format: str = "json",
|
272
|
+
service: Optional[SWMLService] = None
|
273
|
+
) -> str:
|
274
|
+
"""
|
275
|
+
Generate a SWML document for a function response
|
276
|
+
|
277
|
+
Args:
|
278
|
+
response_text: Text to say/display
|
279
|
+
actions: List of SWML actions to execute
|
280
|
+
format: Output format, 'json' or 'yaml'
|
281
|
+
service: Optional SWMLService instance to use
|
282
|
+
|
283
|
+
Returns:
|
284
|
+
SWML document as a string
|
285
|
+
"""
|
286
|
+
if service:
|
287
|
+
# Use the service to build the document
|
288
|
+
service.reset_document()
|
289
|
+
|
290
|
+
# Add a play block for the response if provided
|
291
|
+
if response_text:
|
292
|
+
service.add_verb("play", {
|
293
|
+
"url": f"say:{response_text}"
|
294
|
+
})
|
295
|
+
|
296
|
+
# Add any actions
|
297
|
+
if actions:
|
298
|
+
for action in actions:
|
299
|
+
if action["type"] == "play":
|
300
|
+
service.add_verb("play", {
|
301
|
+
"url": action["url"]
|
302
|
+
})
|
303
|
+
elif action["type"] == "transfer":
|
304
|
+
service.add_verb("connect", [
|
305
|
+
{"to": action["dest"]}
|
306
|
+
])
|
307
|
+
elif action["type"] == "hang_up":
|
308
|
+
service.add_verb("hangup", {})
|
309
|
+
# Additional action types could be added here
|
310
|
+
|
311
|
+
# Return in requested format
|
312
|
+
if format.lower() == "yaml":
|
313
|
+
import yaml
|
314
|
+
return yaml.dump(service.get_document(), sort_keys=False)
|
315
|
+
else:
|
316
|
+
return service.render_document()
|
317
|
+
else:
|
318
|
+
# Legacy implementation (unchanged for backward compatibility)
|
319
|
+
swml = {
|
320
|
+
"version": "1.0.0",
|
321
|
+
"sections": {
|
322
|
+
"main": []
|
323
|
+
}
|
324
|
+
}
|
325
|
+
|
326
|
+
# Add a play block for the response if provided
|
327
|
+
if response_text:
|
328
|
+
swml["sections"]["main"].append({
|
329
|
+
"play": {
|
330
|
+
"url": f"say:{response_text}"
|
331
|
+
}
|
332
|
+
})
|
333
|
+
|
334
|
+
# Add any actions
|
335
|
+
if actions:
|
336
|
+
for action in actions:
|
337
|
+
if action["type"] == "play":
|
338
|
+
swml["sections"]["main"].append({
|
339
|
+
"play": {
|
340
|
+
"url": action["url"]
|
341
|
+
}
|
342
|
+
})
|
343
|
+
elif action["type"] == "transfer":
|
344
|
+
swml["sections"]["main"].append({
|
345
|
+
"connect": [
|
346
|
+
{"to": action["dest"]}
|
347
|
+
]
|
348
|
+
})
|
349
|
+
elif action["type"] == "hang_up":
|
350
|
+
swml["sections"]["main"].append({
|
351
|
+
"hangup": {}
|
352
|
+
})
|
353
|
+
# Additional action types could be added here
|
354
|
+
|
355
|
+
# Return in requested format
|
356
|
+
if format.lower() == "yaml":
|
357
|
+
return yaml.dump(swml, sort_keys=False)
|
358
|
+
else:
|
359
|
+
return json.dumps(swml)
|