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
|
@@ -0,0 +1,707 @@
|
|
|
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
|
+
"""
|
|
11
|
+
Contexts and Steps System for SignalWire Agents
|
|
12
|
+
|
|
13
|
+
This module provides an alternative to traditional POM-based prompts by allowing
|
|
14
|
+
agents to be defined as structured contexts with sequential steps. Each step
|
|
15
|
+
contains its own prompt, completion criteria, and function restrictions.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Dict, List, Optional, Union, Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Step:
|
|
22
|
+
"""Represents a single step within a context"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, name: str):
|
|
25
|
+
self.name = name
|
|
26
|
+
self._text: Optional[str] = None
|
|
27
|
+
self._step_criteria: Optional[str] = None
|
|
28
|
+
self._functions: Optional[Union[str, List[str]]] = None
|
|
29
|
+
self._valid_steps: Optional[List[str]] = None
|
|
30
|
+
self._valid_contexts: Optional[List[str]] = None
|
|
31
|
+
|
|
32
|
+
# POM-style sections for rich prompts
|
|
33
|
+
self._sections: List[Dict[str, Any]] = []
|
|
34
|
+
|
|
35
|
+
# Reset object for context switching from steps
|
|
36
|
+
self._reset_system_prompt: Optional[str] = None
|
|
37
|
+
self._reset_user_prompt: Optional[str] = None
|
|
38
|
+
self._reset_consolidate: bool = False
|
|
39
|
+
self._reset_full_reset: bool = False
|
|
40
|
+
|
|
41
|
+
def set_text(self, text: str) -> 'Step':
|
|
42
|
+
"""
|
|
43
|
+
Set the step's prompt text directly
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
text: The prompt text for this step
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Self for method chaining
|
|
50
|
+
"""
|
|
51
|
+
if self._sections:
|
|
52
|
+
raise ValueError("Cannot use set_text() when POM sections have been added. Use one approach or the other.")
|
|
53
|
+
self._text = text
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
def add_section(self, title: str, body: str) -> 'Step':
|
|
57
|
+
"""
|
|
58
|
+
Add a POM section to the step
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
title: Section title
|
|
62
|
+
body: Section body text
|
|
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, "body": body})
|
|
70
|
+
return self
|
|
71
|
+
|
|
72
|
+
def add_bullets(self, title: str, bullets: List[str]) -> 'Step':
|
|
73
|
+
"""
|
|
74
|
+
Add a POM section with bullet points
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
title: Section title
|
|
78
|
+
bullets: List of bullet points
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Self for method chaining
|
|
82
|
+
"""
|
|
83
|
+
if self._text is not None:
|
|
84
|
+
raise ValueError("Cannot add POM sections when set_text() has been used. Use one approach or the other.")
|
|
85
|
+
self._sections.append({"title": title, "bullets": bullets})
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
def set_step_criteria(self, criteria: str) -> 'Step':
|
|
89
|
+
"""
|
|
90
|
+
Set the criteria for determining when this step is complete
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
criteria: Description of step completion criteria
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Self for method chaining
|
|
97
|
+
"""
|
|
98
|
+
self._step_criteria = criteria
|
|
99
|
+
return self
|
|
100
|
+
|
|
101
|
+
def set_functions(self, functions: Union[str, List[str]]) -> 'Step':
|
|
102
|
+
"""
|
|
103
|
+
Set which functions are available in this step
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
functions: "none" to disable all functions, or list of function names
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Self for method chaining
|
|
110
|
+
"""
|
|
111
|
+
self._functions = functions
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
def set_valid_steps(self, steps: List[str]) -> 'Step':
|
|
115
|
+
"""
|
|
116
|
+
Set which steps can be navigated to from this step
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
steps: List of valid step names (include "next" for sequential flow)
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Self for method chaining
|
|
123
|
+
"""
|
|
124
|
+
self._valid_steps = steps
|
|
125
|
+
return self
|
|
126
|
+
|
|
127
|
+
def set_valid_contexts(self, contexts: List[str]) -> 'Step':
|
|
128
|
+
"""
|
|
129
|
+
Set which contexts can be navigated to from this step
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
contexts: List of valid context names
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Self for method chaining
|
|
136
|
+
"""
|
|
137
|
+
self._valid_contexts = contexts
|
|
138
|
+
return self
|
|
139
|
+
|
|
140
|
+
def set_reset_system_prompt(self, system_prompt: str) -> 'Step':
|
|
141
|
+
"""
|
|
142
|
+
Set system prompt for context switching when this step navigates to a context
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
system_prompt: New system prompt for context switching
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Self for method chaining
|
|
149
|
+
"""
|
|
150
|
+
self._reset_system_prompt = system_prompt
|
|
151
|
+
return self
|
|
152
|
+
|
|
153
|
+
def set_reset_user_prompt(self, user_prompt: str) -> 'Step':
|
|
154
|
+
"""
|
|
155
|
+
Set user prompt for context switching when this step navigates to a context
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
user_prompt: User message to inject for context switching
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Self for method chaining
|
|
162
|
+
"""
|
|
163
|
+
self._reset_user_prompt = user_prompt
|
|
164
|
+
return self
|
|
165
|
+
|
|
166
|
+
def set_reset_consolidate(self, consolidate: bool) -> 'Step':
|
|
167
|
+
"""
|
|
168
|
+
Set whether to consolidate conversation when this step switches contexts
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
consolidate: Whether to consolidate previous conversation
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Self for method chaining
|
|
175
|
+
"""
|
|
176
|
+
self._reset_consolidate = consolidate
|
|
177
|
+
return self
|
|
178
|
+
|
|
179
|
+
def set_reset_full_reset(self, full_reset: bool) -> 'Step':
|
|
180
|
+
"""
|
|
181
|
+
Set whether to do full reset when this step switches contexts
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
full_reset: Whether to completely rewrite system prompt vs inject
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Self for method chaining
|
|
188
|
+
"""
|
|
189
|
+
self._reset_full_reset = full_reset
|
|
190
|
+
return self
|
|
191
|
+
|
|
192
|
+
def _render_text(self) -> str:
|
|
193
|
+
"""Render the step's prompt text"""
|
|
194
|
+
if self._text is not None:
|
|
195
|
+
return self._text
|
|
196
|
+
|
|
197
|
+
if not self._sections:
|
|
198
|
+
raise ValueError(f"Step '{self.name}' has no text or POM sections defined")
|
|
199
|
+
|
|
200
|
+
# Convert POM sections to markdown
|
|
201
|
+
markdown_parts = []
|
|
202
|
+
for section in self._sections:
|
|
203
|
+
if "bullets" in section:
|
|
204
|
+
markdown_parts.append(f"## {section['title']}")
|
|
205
|
+
for bullet in section["bullets"]:
|
|
206
|
+
markdown_parts.append(f"- {bullet}")
|
|
207
|
+
else:
|
|
208
|
+
markdown_parts.append(f"## {section['title']}")
|
|
209
|
+
markdown_parts.append(section["body"])
|
|
210
|
+
markdown_parts.append("") # Add spacing
|
|
211
|
+
|
|
212
|
+
return "\n".join(markdown_parts).strip()
|
|
213
|
+
|
|
214
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
215
|
+
"""Convert step to dictionary for SWML generation"""
|
|
216
|
+
step_dict = {
|
|
217
|
+
"name": self.name,
|
|
218
|
+
"text": self._render_text()
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if self._step_criteria:
|
|
222
|
+
step_dict["step_criteria"] = self._step_criteria
|
|
223
|
+
|
|
224
|
+
if self._functions is not None:
|
|
225
|
+
step_dict["functions"] = self._functions
|
|
226
|
+
|
|
227
|
+
if self._valid_steps is not None:
|
|
228
|
+
step_dict["valid_steps"] = self._valid_steps
|
|
229
|
+
|
|
230
|
+
if self._valid_contexts is not None:
|
|
231
|
+
step_dict["valid_contexts"] = self._valid_contexts
|
|
232
|
+
|
|
233
|
+
# Add reset object if any reset parameters are set
|
|
234
|
+
reset_obj = {}
|
|
235
|
+
if self._reset_system_prompt is not None:
|
|
236
|
+
reset_obj["system_prompt"] = self._reset_system_prompt
|
|
237
|
+
if self._reset_user_prompt is not None:
|
|
238
|
+
reset_obj["user_prompt"] = self._reset_user_prompt
|
|
239
|
+
if self._reset_consolidate:
|
|
240
|
+
reset_obj["consolidate"] = self._reset_consolidate
|
|
241
|
+
if self._reset_full_reset:
|
|
242
|
+
reset_obj["full_reset"] = self._reset_full_reset
|
|
243
|
+
|
|
244
|
+
if reset_obj:
|
|
245
|
+
step_dict["reset"] = reset_obj
|
|
246
|
+
|
|
247
|
+
return step_dict
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class Context:
|
|
251
|
+
"""Represents a single context containing multiple steps"""
|
|
252
|
+
|
|
253
|
+
def __init__(self, name: str):
|
|
254
|
+
self.name = name
|
|
255
|
+
self._steps: Dict[str, Step] = {}
|
|
256
|
+
self._step_order: List[str] = []
|
|
257
|
+
self._valid_contexts: Optional[List[str]] = None
|
|
258
|
+
|
|
259
|
+
# Context entry parameters
|
|
260
|
+
self._post_prompt: Optional[str] = None
|
|
261
|
+
self._system_prompt: Optional[str] = None
|
|
262
|
+
self._system_prompt_sections: List[Dict[str, Any]] = [] # For POM-style system prompts
|
|
263
|
+
self._consolidate: bool = False
|
|
264
|
+
self._full_reset: bool = False
|
|
265
|
+
self._user_prompt: Optional[str] = None
|
|
266
|
+
self._isolated: bool = False
|
|
267
|
+
|
|
268
|
+
# Context prompt (separate from system_prompt)
|
|
269
|
+
self._prompt_text: Optional[str] = None
|
|
270
|
+
self._prompt_sections: List[Dict[str, Any]] = []
|
|
271
|
+
|
|
272
|
+
# Context fillers
|
|
273
|
+
self._enter_fillers: Optional[Dict[str, List[str]]] = None
|
|
274
|
+
self._exit_fillers: Optional[Dict[str, List[str]]] = None
|
|
275
|
+
|
|
276
|
+
def add_step(self, name: str) -> Step:
|
|
277
|
+
"""
|
|
278
|
+
Add a new step to this context
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
name: Step name
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Step object for method chaining
|
|
285
|
+
"""
|
|
286
|
+
if name in self._steps:
|
|
287
|
+
raise ValueError(f"Step '{name}' already exists in context '{self.name}'")
|
|
288
|
+
|
|
289
|
+
step = Step(name)
|
|
290
|
+
self._steps[name] = step
|
|
291
|
+
self._step_order.append(name)
|
|
292
|
+
return step
|
|
293
|
+
|
|
294
|
+
def set_valid_contexts(self, contexts: List[str]) -> 'Context':
|
|
295
|
+
"""
|
|
296
|
+
Set which contexts can be navigated to from this context
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
contexts: List of valid context names
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Self for method chaining
|
|
303
|
+
"""
|
|
304
|
+
self._valid_contexts = contexts
|
|
305
|
+
return self
|
|
306
|
+
|
|
307
|
+
def set_post_prompt(self, post_prompt: str) -> 'Context':
|
|
308
|
+
"""
|
|
309
|
+
Set post prompt override for this context
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
post_prompt: Post prompt text to use when this context is active
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Self for method chaining
|
|
316
|
+
"""
|
|
317
|
+
self._post_prompt = post_prompt
|
|
318
|
+
return self
|
|
319
|
+
|
|
320
|
+
def set_system_prompt(self, system_prompt: str) -> 'Context':
|
|
321
|
+
"""
|
|
322
|
+
Set system prompt for context switching (triggers context reset)
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
system_prompt: New system prompt for when this context is entered
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Self for method chaining
|
|
329
|
+
"""
|
|
330
|
+
if self._system_prompt_sections:
|
|
331
|
+
raise ValueError("Cannot use set_system_prompt() when POM sections have been added for system prompt. Use one approach or the other.")
|
|
332
|
+
self._system_prompt = system_prompt
|
|
333
|
+
return self
|
|
334
|
+
|
|
335
|
+
def set_consolidate(self, consolidate: bool) -> 'Context':
|
|
336
|
+
"""
|
|
337
|
+
Set whether to consolidate conversation history when entering this context
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
consolidate: Whether to consolidate previous conversation
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
Self for method chaining
|
|
344
|
+
"""
|
|
345
|
+
self._consolidate = consolidate
|
|
346
|
+
return self
|
|
347
|
+
|
|
348
|
+
def set_full_reset(self, full_reset: bool) -> 'Context':
|
|
349
|
+
"""
|
|
350
|
+
Set whether to do full reset when entering this context
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
full_reset: Whether to completely rewrite system prompt vs inject
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
Self for method chaining
|
|
357
|
+
"""
|
|
358
|
+
self._full_reset = full_reset
|
|
359
|
+
return self
|
|
360
|
+
|
|
361
|
+
def set_user_prompt(self, user_prompt: str) -> 'Context':
|
|
362
|
+
"""
|
|
363
|
+
Set user prompt to inject when entering this context
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
user_prompt: User message to inject for context
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
Self for method chaining
|
|
370
|
+
"""
|
|
371
|
+
self._user_prompt = user_prompt
|
|
372
|
+
return self
|
|
373
|
+
|
|
374
|
+
def set_isolated(self, isolated: bool) -> 'Context':
|
|
375
|
+
"""
|
|
376
|
+
Set whether to truncate conversation history when entering this context
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
isolated: Whether to truncate conversation on context switch
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
Self for method chaining
|
|
383
|
+
"""
|
|
384
|
+
self._isolated = isolated
|
|
385
|
+
return self
|
|
386
|
+
|
|
387
|
+
def add_system_section(self, title: str, body: str) -> 'Context':
|
|
388
|
+
"""
|
|
389
|
+
Add a POM section to the system prompt
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
title: Section title
|
|
393
|
+
body: Section body text
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
Self for method chaining
|
|
397
|
+
"""
|
|
398
|
+
if self._system_prompt is not None:
|
|
399
|
+
raise ValueError("Cannot add POM sections for system prompt when set_system_prompt() has been used. Use one approach or the other.")
|
|
400
|
+
self._system_prompt_sections.append({"title": title, "body": body})
|
|
401
|
+
return self
|
|
402
|
+
|
|
403
|
+
def add_system_bullets(self, title: str, bullets: List[str]) -> 'Context':
|
|
404
|
+
"""
|
|
405
|
+
Add a POM section with bullet points to the system prompt
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
title: Section title
|
|
409
|
+
bullets: List of bullet points
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
Self for method chaining
|
|
413
|
+
"""
|
|
414
|
+
if self._system_prompt is not None:
|
|
415
|
+
raise ValueError("Cannot add POM sections for system prompt when set_system_prompt() has been used. Use one approach or the other.")
|
|
416
|
+
self._system_prompt_sections.append({"title": title, "bullets": bullets})
|
|
417
|
+
return self
|
|
418
|
+
|
|
419
|
+
def set_prompt(self, prompt: str) -> 'Context':
|
|
420
|
+
"""
|
|
421
|
+
Set the context's prompt text directly
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
prompt: The prompt text for this context
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Self for method chaining
|
|
428
|
+
"""
|
|
429
|
+
if self._prompt_sections:
|
|
430
|
+
raise ValueError("Cannot use set_prompt() when POM sections have been added. Use one approach or the other.")
|
|
431
|
+
self._prompt_text = prompt
|
|
432
|
+
return self
|
|
433
|
+
|
|
434
|
+
def add_section(self, title: str, body: str) -> 'Context':
|
|
435
|
+
"""
|
|
436
|
+
Add a POM section to the context prompt
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
title: Section title
|
|
440
|
+
body: Section body text
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
Self for method chaining
|
|
444
|
+
"""
|
|
445
|
+
if self._prompt_text is not None:
|
|
446
|
+
raise ValueError("Cannot add POM sections when set_prompt() has been used. Use one approach or the other.")
|
|
447
|
+
self._prompt_sections.append({"title": title, "body": body})
|
|
448
|
+
return self
|
|
449
|
+
|
|
450
|
+
def add_bullets(self, title: str, bullets: List[str]) -> 'Context':
|
|
451
|
+
"""
|
|
452
|
+
Add a POM section with bullet points to the context prompt
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
title: Section title
|
|
456
|
+
bullets: List of bullet points
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
Self for method chaining
|
|
460
|
+
"""
|
|
461
|
+
if self._prompt_text is not None:
|
|
462
|
+
raise ValueError("Cannot add POM sections when set_prompt() has been used. Use one approach or the other.")
|
|
463
|
+
self._prompt_sections.append({"title": title, "bullets": bullets})
|
|
464
|
+
return self
|
|
465
|
+
|
|
466
|
+
def set_enter_fillers(self, enter_fillers: Dict[str, List[str]]) -> 'Context':
|
|
467
|
+
"""
|
|
468
|
+
Set fillers that the AI says when entering this context
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
enter_fillers: Dictionary mapping language codes (or "default") to lists of filler phrases
|
|
472
|
+
Example: {"en-US": ["Welcome...", "Hello..."], "default": ["Entering..."]}
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
Self for method chaining
|
|
476
|
+
"""
|
|
477
|
+
if enter_fillers and isinstance(enter_fillers, dict):
|
|
478
|
+
self._enter_fillers = enter_fillers
|
|
479
|
+
return self
|
|
480
|
+
|
|
481
|
+
def set_exit_fillers(self, exit_fillers: Dict[str, List[str]]) -> 'Context':
|
|
482
|
+
"""
|
|
483
|
+
Set fillers that the AI says when exiting this context
|
|
484
|
+
|
|
485
|
+
Args:
|
|
486
|
+
exit_fillers: Dictionary mapping language codes (or "default") to lists of filler phrases
|
|
487
|
+
Example: {"en-US": ["Goodbye...", "Thank you..."], "default": ["Exiting..."]}
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
Self for method chaining
|
|
491
|
+
"""
|
|
492
|
+
if exit_fillers and isinstance(exit_fillers, dict):
|
|
493
|
+
self._exit_fillers = exit_fillers
|
|
494
|
+
return self
|
|
495
|
+
|
|
496
|
+
def add_enter_filler(self, language_code: str, fillers: List[str]) -> 'Context':
|
|
497
|
+
"""
|
|
498
|
+
Add enter fillers for a specific language
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
language_code: Language code (e.g., "en-US", "es") or "default" for catch-all
|
|
502
|
+
fillers: List of filler phrases for entering this context
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
Self for method chaining
|
|
506
|
+
"""
|
|
507
|
+
if language_code and fillers and isinstance(fillers, list):
|
|
508
|
+
if self._enter_fillers is None:
|
|
509
|
+
self._enter_fillers = {}
|
|
510
|
+
self._enter_fillers[language_code] = fillers
|
|
511
|
+
return self
|
|
512
|
+
|
|
513
|
+
def add_exit_filler(self, language_code: str, fillers: List[str]) -> 'Context':
|
|
514
|
+
"""
|
|
515
|
+
Add exit fillers for a specific language
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
language_code: Language code (e.g., "en-US", "es") or "default" for catch-all
|
|
519
|
+
fillers: List of filler phrases for exiting this context
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
Self for method chaining
|
|
523
|
+
"""
|
|
524
|
+
if language_code and fillers and isinstance(fillers, list):
|
|
525
|
+
if self._exit_fillers is None:
|
|
526
|
+
self._exit_fillers = {}
|
|
527
|
+
self._exit_fillers[language_code] = fillers
|
|
528
|
+
return self
|
|
529
|
+
|
|
530
|
+
def _render_prompt(self) -> Optional[str]:
|
|
531
|
+
"""Render the context's prompt text"""
|
|
532
|
+
if self._prompt_text is not None:
|
|
533
|
+
return self._prompt_text
|
|
534
|
+
|
|
535
|
+
if not self._prompt_sections:
|
|
536
|
+
return None
|
|
537
|
+
|
|
538
|
+
# Convert POM sections to markdown
|
|
539
|
+
markdown_parts = []
|
|
540
|
+
for section in self._prompt_sections:
|
|
541
|
+
if "bullets" in section:
|
|
542
|
+
markdown_parts.append(f"## {section['title']}")
|
|
543
|
+
for bullet in section["bullets"]:
|
|
544
|
+
markdown_parts.append(f"- {bullet}")
|
|
545
|
+
else:
|
|
546
|
+
markdown_parts.append(f"## {section['title']}")
|
|
547
|
+
markdown_parts.append(section["body"])
|
|
548
|
+
markdown_parts.append("") # Add spacing
|
|
549
|
+
|
|
550
|
+
return "\n".join(markdown_parts).strip()
|
|
551
|
+
|
|
552
|
+
def _render_system_prompt(self) -> Optional[str]:
|
|
553
|
+
"""Render the system prompt text"""
|
|
554
|
+
if self._system_prompt is not None:
|
|
555
|
+
return self._system_prompt
|
|
556
|
+
|
|
557
|
+
if not self._system_prompt_sections:
|
|
558
|
+
return None
|
|
559
|
+
|
|
560
|
+
# Convert POM sections to markdown
|
|
561
|
+
markdown_parts = []
|
|
562
|
+
for section in self._system_prompt_sections:
|
|
563
|
+
if "bullets" in section:
|
|
564
|
+
markdown_parts.append(f"## {section['title']}")
|
|
565
|
+
for bullet in section["bullets"]:
|
|
566
|
+
markdown_parts.append(f"- {bullet}")
|
|
567
|
+
else:
|
|
568
|
+
markdown_parts.append(f"## {section['title']}")
|
|
569
|
+
markdown_parts.append(section["body"])
|
|
570
|
+
markdown_parts.append("") # Add spacing
|
|
571
|
+
|
|
572
|
+
return "\n".join(markdown_parts).strip()
|
|
573
|
+
|
|
574
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
575
|
+
"""Convert context to dictionary for SWML generation"""
|
|
576
|
+
if not self._steps:
|
|
577
|
+
raise ValueError(f"Context '{self.name}' has no steps defined")
|
|
578
|
+
|
|
579
|
+
context_dict = {
|
|
580
|
+
"steps": [self._steps[name].to_dict() for name in self._step_order]
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if self._valid_contexts is not None:
|
|
584
|
+
context_dict["valid_contexts"] = self._valid_contexts
|
|
585
|
+
|
|
586
|
+
# Add context entry parameters
|
|
587
|
+
if self._post_prompt is not None:
|
|
588
|
+
context_dict["post_prompt"] = self._post_prompt
|
|
589
|
+
|
|
590
|
+
rendered_system_prompt = self._render_system_prompt()
|
|
591
|
+
if rendered_system_prompt is not None:
|
|
592
|
+
context_dict["system_prompt"] = rendered_system_prompt
|
|
593
|
+
|
|
594
|
+
if self._consolidate:
|
|
595
|
+
context_dict["consolidate"] = self._consolidate
|
|
596
|
+
|
|
597
|
+
if self._full_reset:
|
|
598
|
+
context_dict["full_reset"] = self._full_reset
|
|
599
|
+
|
|
600
|
+
if self._user_prompt is not None:
|
|
601
|
+
context_dict["user_prompt"] = self._user_prompt
|
|
602
|
+
|
|
603
|
+
if self._isolated:
|
|
604
|
+
context_dict["isolated"] = self._isolated
|
|
605
|
+
|
|
606
|
+
# Add context prompt - use POM structure if sections exist, otherwise use string
|
|
607
|
+
if self._prompt_sections:
|
|
608
|
+
# Use structured POM format
|
|
609
|
+
context_dict["pom"] = self._prompt_sections
|
|
610
|
+
elif self._prompt_text is not None:
|
|
611
|
+
# Use string format
|
|
612
|
+
context_dict["prompt"] = self._prompt_text
|
|
613
|
+
|
|
614
|
+
# Add enter and exit fillers if defined
|
|
615
|
+
if self._enter_fillers is not None:
|
|
616
|
+
context_dict["enter_fillers"] = self._enter_fillers
|
|
617
|
+
|
|
618
|
+
if self._exit_fillers is not None:
|
|
619
|
+
context_dict["exit_fillers"] = self._exit_fillers
|
|
620
|
+
|
|
621
|
+
return context_dict
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
class ContextBuilder:
|
|
625
|
+
"""Main builder class for creating contexts and steps"""
|
|
626
|
+
|
|
627
|
+
def __init__(self, agent):
|
|
628
|
+
self._agent = agent
|
|
629
|
+
self._contexts: Dict[str, Context] = {}
|
|
630
|
+
self._context_order: List[str] = []
|
|
631
|
+
|
|
632
|
+
def add_context(self, name: str) -> Context:
|
|
633
|
+
"""
|
|
634
|
+
Add a new context
|
|
635
|
+
|
|
636
|
+
Args:
|
|
637
|
+
name: Context name
|
|
638
|
+
|
|
639
|
+
Returns:
|
|
640
|
+
Context object for method chaining
|
|
641
|
+
"""
|
|
642
|
+
if name in self._contexts:
|
|
643
|
+
raise ValueError(f"Context '{name}' already exists")
|
|
644
|
+
|
|
645
|
+
context = Context(name)
|
|
646
|
+
self._contexts[name] = context
|
|
647
|
+
self._context_order.append(name)
|
|
648
|
+
return context
|
|
649
|
+
|
|
650
|
+
def validate(self) -> None:
|
|
651
|
+
"""Validate the contexts configuration"""
|
|
652
|
+
if not self._contexts:
|
|
653
|
+
raise ValueError("At least one context must be defined")
|
|
654
|
+
|
|
655
|
+
# If only one context, it must be named "default"
|
|
656
|
+
if len(self._contexts) == 1:
|
|
657
|
+
context_name = list(self._contexts.keys())[0]
|
|
658
|
+
if context_name != "default":
|
|
659
|
+
raise ValueError("When using a single context, it must be named 'default'")
|
|
660
|
+
|
|
661
|
+
# Validate each context has at least one step
|
|
662
|
+
for context_name, context in self._contexts.items():
|
|
663
|
+
if not context._steps:
|
|
664
|
+
raise ValueError(f"Context '{context_name}' must have at least one step")
|
|
665
|
+
|
|
666
|
+
# Validate step references in valid_steps
|
|
667
|
+
for context_name, context in self._contexts.items():
|
|
668
|
+
for step_name, step in context._steps.items():
|
|
669
|
+
if step._valid_steps:
|
|
670
|
+
for valid_step in step._valid_steps:
|
|
671
|
+
if valid_step != "next" and valid_step not in context._steps:
|
|
672
|
+
raise ValueError(
|
|
673
|
+
f"Step '{step_name}' in context '{context_name}' "
|
|
674
|
+
f"references unknown step '{valid_step}'"
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
# Validate context references in valid_contexts
|
|
678
|
+
for context_name, context in self._contexts.items():
|
|
679
|
+
if context._valid_contexts:
|
|
680
|
+
for valid_context in context._valid_contexts:
|
|
681
|
+
if valid_context not in self._contexts:
|
|
682
|
+
raise ValueError(
|
|
683
|
+
f"Context '{context_name}' references unknown context '{valid_context}'"
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
687
|
+
"""Convert all contexts to dictionary for SWML generation"""
|
|
688
|
+
self.validate()
|
|
689
|
+
|
|
690
|
+
return {
|
|
691
|
+
context_name: context.to_dict()
|
|
692
|
+
for context_name in self._context_order
|
|
693
|
+
for context_name, context in [(context_name, self._contexts[context_name])]
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def create_simple_context(name: str = "default") -> Context:
|
|
698
|
+
"""
|
|
699
|
+
Helper function to create a simple single context
|
|
700
|
+
|
|
701
|
+
Args:
|
|
702
|
+
name: Context name (defaults to "default")
|
|
703
|
+
|
|
704
|
+
Returns:
|
|
705
|
+
Context object for method chaining
|
|
706
|
+
"""
|
|
707
|
+
return Context(name)
|