signalwire-agents 0.1.9__py3-none-any.whl → 0.1.11__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 (37) hide show
  1. signalwire_agents/__init__.py +39 -4
  2. signalwire_agents/agent_server.py +46 -2
  3. signalwire_agents/cli/__init__.py +9 -0
  4. signalwire_agents/cli/test_swaig.py +2545 -0
  5. signalwire_agents/core/agent_base.py +691 -82
  6. signalwire_agents/core/contexts.py +289 -0
  7. signalwire_agents/core/data_map.py +499 -0
  8. signalwire_agents/core/function_result.py +57 -10
  9. signalwire_agents/core/skill_base.py +31 -1
  10. signalwire_agents/core/skill_manager.py +89 -23
  11. signalwire_agents/core/swaig_function.py +13 -1
  12. signalwire_agents/core/swml_handler.py +37 -13
  13. signalwire_agents/core/swml_service.py +37 -28
  14. signalwire_agents/skills/datasphere/__init__.py +12 -0
  15. signalwire_agents/skills/datasphere/skill.py +229 -0
  16. signalwire_agents/skills/datasphere_serverless/__init__.py +1 -0
  17. signalwire_agents/skills/datasphere_serverless/skill.py +156 -0
  18. signalwire_agents/skills/datetime/skill.py +7 -3
  19. signalwire_agents/skills/joke/__init__.py +1 -0
  20. signalwire_agents/skills/joke/skill.py +88 -0
  21. signalwire_agents/skills/math/skill.py +8 -5
  22. signalwire_agents/skills/registry.py +23 -4
  23. signalwire_agents/skills/web_search/skill.py +58 -33
  24. signalwire_agents/skills/wikipedia/__init__.py +9 -0
  25. signalwire_agents/skills/wikipedia/skill.py +180 -0
  26. signalwire_agents/utils/__init__.py +2 -0
  27. signalwire_agents/utils/schema_utils.py +111 -44
  28. signalwire_agents/utils/serverless.py +38 -0
  29. signalwire_agents-0.1.11.dist-info/METADATA +756 -0
  30. signalwire_agents-0.1.11.dist-info/RECORD +58 -0
  31. {signalwire_agents-0.1.9.dist-info → signalwire_agents-0.1.11.dist-info}/WHEEL +1 -1
  32. signalwire_agents-0.1.11.dist-info/entry_points.txt +2 -0
  33. signalwire_agents-0.1.9.dist-info/METADATA +0 -311
  34. signalwire_agents-0.1.9.dist-info/RECORD +0 -44
  35. {signalwire_agents-0.1.9.data → signalwire_agents-0.1.11.data}/data/schema.json +0 -0
  36. {signalwire_agents-0.1.9.dist-info → signalwire_agents-0.1.11.dist-info}/licenses/LICENSE +0 -0
  37. {signalwire_agents-0.1.9.dist-info → signalwire_agents-0.1.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,289 @@
1
+ """
2
+ Contexts and Steps System for SignalWire Agents
3
+
4
+ This module provides an alternative to traditional POM-based prompts by allowing
5
+ agents to be defined as structured contexts with sequential steps. Each step
6
+ contains its own prompt, completion criteria, and function restrictions.
7
+ """
8
+
9
+ from typing import Dict, List, Optional, Union, Any
10
+
11
+
12
+ class Step:
13
+ """Represents a single step within a context"""
14
+
15
+ def __init__(self, name: str):
16
+ self.name = name
17
+ self._text: Optional[str] = None
18
+ self._step_criteria: Optional[str] = None
19
+ self._functions: Optional[Union[str, List[str]]] = None
20
+ self._valid_steps: Optional[List[str]] = None
21
+
22
+ # POM-style sections for rich prompts
23
+ self._sections: List[Dict[str, Any]] = []
24
+
25
+ def set_text(self, text: str) -> 'Step':
26
+ """
27
+ Set the step's prompt text directly
28
+
29
+ Args:
30
+ text: The prompt text for this step
31
+
32
+ Returns:
33
+ Self for method chaining
34
+ """
35
+ if self._sections:
36
+ raise ValueError("Cannot use set_text() when POM sections have been added. Use one approach or the other.")
37
+ self._text = text
38
+ return self
39
+
40
+ def add_section(self, title: str, body: str) -> 'Step':
41
+ """
42
+ Add a POM section to the step
43
+
44
+ Args:
45
+ title: Section title
46
+ body: Section body text
47
+
48
+ Returns:
49
+ Self for method chaining
50
+ """
51
+ if self._text is not None:
52
+ raise ValueError("Cannot add POM sections when set_text() has been used. Use one approach or the other.")
53
+ self._sections.append({"title": title, "body": body})
54
+ return self
55
+
56
+ def add_bullets(self, title: str, bullets: List[str]) -> 'Step':
57
+ """
58
+ Add a POM section with bullet points
59
+
60
+ Args:
61
+ title: Section title
62
+ bullets: List of bullet points
63
+
64
+ Returns:
65
+ Self for method chaining
66
+ """
67
+ if self._text is not None:
68
+ raise ValueError("Cannot add POM sections when set_text() has been used. Use one approach or the other.")
69
+ self._sections.append({"title": title, "bullets": bullets})
70
+ return self
71
+
72
+ def set_step_criteria(self, criteria: str) -> 'Step':
73
+ """
74
+ Set the criteria for determining when this step is complete
75
+
76
+ Args:
77
+ criteria: Description of step completion criteria
78
+
79
+ Returns:
80
+ Self for method chaining
81
+ """
82
+ self._step_criteria = criteria
83
+ return self
84
+
85
+ def set_functions(self, functions: Union[str, List[str]]) -> 'Step':
86
+ """
87
+ Set which functions are available in this step
88
+
89
+ Args:
90
+ functions: "none" to disable all functions, or list of function names
91
+
92
+ Returns:
93
+ Self for method chaining
94
+ """
95
+ self._functions = functions
96
+ return self
97
+
98
+ def set_valid_steps(self, steps: List[str]) -> 'Step':
99
+ """
100
+ Set which steps can be navigated to from this step
101
+
102
+ Args:
103
+ steps: List of valid step names (include "next" for sequential flow)
104
+
105
+ Returns:
106
+ Self for method chaining
107
+ """
108
+ self._valid_steps = steps
109
+ return self
110
+
111
+ def _render_text(self) -> str:
112
+ """Render the step's prompt text"""
113
+ if self._text is not None:
114
+ return self._text
115
+
116
+ if not self._sections:
117
+ raise ValueError(f"Step '{self.name}' has no text or POM sections defined")
118
+
119
+ # Convert POM sections to markdown
120
+ markdown_parts = []
121
+ for section in self._sections:
122
+ if "bullets" in section:
123
+ markdown_parts.append(f"## {section['title']}")
124
+ for bullet in section["bullets"]:
125
+ markdown_parts.append(f"- {bullet}")
126
+ else:
127
+ markdown_parts.append(f"## {section['title']}")
128
+ markdown_parts.append(section["body"])
129
+ markdown_parts.append("") # Add spacing
130
+
131
+ return "\n".join(markdown_parts).strip()
132
+
133
+ def to_dict(self) -> Dict[str, Any]:
134
+ """Convert step to dictionary for SWML generation"""
135
+ step_dict = {
136
+ "text": self._render_text()
137
+ }
138
+
139
+ if self._step_criteria:
140
+ step_dict["step_criteria"] = self._step_criteria
141
+
142
+ if self._functions is not None:
143
+ step_dict["functions"] = self._functions
144
+
145
+ if self._valid_steps is not None:
146
+ step_dict["valid_steps"] = self._valid_steps
147
+
148
+ return step_dict
149
+
150
+
151
+ class Context:
152
+ """Represents a single context containing multiple steps"""
153
+
154
+ def __init__(self, name: str):
155
+ self.name = name
156
+ self._steps: Dict[str, Step] = {}
157
+ self._step_order: List[str] = []
158
+ self._valid_contexts: Optional[List[str]] = None
159
+
160
+ def add_step(self, name: str) -> Step:
161
+ """
162
+ Add a new step to this context
163
+
164
+ Args:
165
+ name: Step name
166
+
167
+ Returns:
168
+ Step object for method chaining
169
+ """
170
+ if name in self._steps:
171
+ raise ValueError(f"Step '{name}' already exists in context '{self.name}'")
172
+
173
+ step = Step(name)
174
+ self._steps[name] = step
175
+ self._step_order.append(name)
176
+ return step
177
+
178
+ def set_valid_contexts(self, contexts: List[str]) -> 'Context':
179
+ """
180
+ Set which contexts can be navigated to from this context
181
+
182
+ Args:
183
+ contexts: List of valid context names
184
+
185
+ Returns:
186
+ Self for method chaining
187
+ """
188
+ self._valid_contexts = contexts
189
+ return self
190
+
191
+ def to_dict(self) -> Dict[str, Any]:
192
+ """Convert context to dictionary for SWML generation"""
193
+ if not self._steps:
194
+ raise ValueError(f"Context '{self.name}' has no steps defined")
195
+
196
+ context_dict = {
197
+ "steps": [self._steps[name].to_dict() for name in self._step_order]
198
+ }
199
+
200
+ if self._valid_contexts is not None:
201
+ context_dict["valid_contexts"] = self._valid_contexts
202
+
203
+ return context_dict
204
+
205
+
206
+ class ContextBuilder:
207
+ """Main builder class for creating contexts and steps"""
208
+
209
+ def __init__(self, agent):
210
+ self._agent = agent
211
+ self._contexts: Dict[str, Context] = {}
212
+ self._context_order: List[str] = []
213
+
214
+ def add_context(self, name: str) -> Context:
215
+ """
216
+ Add a new context
217
+
218
+ Args:
219
+ name: Context name
220
+
221
+ Returns:
222
+ Context object for method chaining
223
+ """
224
+ if name in self._contexts:
225
+ raise ValueError(f"Context '{name}' already exists")
226
+
227
+ context = Context(name)
228
+ self._contexts[name] = context
229
+ self._context_order.append(name)
230
+ return context
231
+
232
+ def validate(self) -> None:
233
+ """Validate the contexts configuration"""
234
+ if not self._contexts:
235
+ raise ValueError("At least one context must be defined")
236
+
237
+ # If only one context, it must be named "default"
238
+ if len(self._contexts) == 1:
239
+ context_name = list(self._contexts.keys())[0]
240
+ if context_name != "default":
241
+ raise ValueError("When using a single context, it must be named 'default'")
242
+
243
+ # Validate each context has at least one step
244
+ for context_name, context in self._contexts.items():
245
+ if not context._steps:
246
+ raise ValueError(f"Context '{context_name}' must have at least one step")
247
+
248
+ # Validate step references in valid_steps
249
+ for context_name, context in self._contexts.items():
250
+ for step_name, step in context._steps.items():
251
+ if step._valid_steps:
252
+ for valid_step in step._valid_steps:
253
+ if valid_step != "next" and valid_step not in context._steps:
254
+ raise ValueError(
255
+ f"Step '{step_name}' in context '{context_name}' "
256
+ f"references unknown step '{valid_step}'"
257
+ )
258
+
259
+ # Validate context references in valid_contexts
260
+ for context_name, context in self._contexts.items():
261
+ if context._valid_contexts:
262
+ for valid_context in context._valid_contexts:
263
+ if valid_context not in self._contexts:
264
+ raise ValueError(
265
+ f"Context '{context_name}' references unknown context '{valid_context}'"
266
+ )
267
+
268
+ def to_dict(self) -> Dict[str, Any]:
269
+ """Convert all contexts to dictionary for SWML generation"""
270
+ self.validate()
271
+
272
+ return {
273
+ context_name: context.to_dict()
274
+ for context_name in self._context_order
275
+ for context_name, context in [(context_name, self._contexts[context_name])]
276
+ }
277
+
278
+
279
+ def create_simple_context(name: str = "default") -> Context:
280
+ """
281
+ Helper function to create a simple single context
282
+
283
+ Args:
284
+ name: Context name (defaults to "default")
285
+
286
+ Returns:
287
+ Context object for method chaining
288
+ """
289
+ return Context(name)