soe-ai 0.2.0b1__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.
- soe/__init__.py +50 -0
- soe/broker.py +168 -0
- soe/builtin_tools/__init__.py +51 -0
- soe/builtin_tools/soe_add_signal.py +82 -0
- soe/builtin_tools/soe_call_tool.py +111 -0
- soe/builtin_tools/soe_copy_context.py +80 -0
- soe/builtin_tools/soe_explore_docs.py +290 -0
- soe/builtin_tools/soe_get_available_tools.py +42 -0
- soe/builtin_tools/soe_get_context.py +50 -0
- soe/builtin_tools/soe_get_context_schema.py +56 -0
- soe/builtin_tools/soe_get_identities.py +63 -0
- soe/builtin_tools/soe_get_workflows.py +63 -0
- soe/builtin_tools/soe_inject_context_schema_field.py +80 -0
- soe/builtin_tools/soe_inject_identity.py +64 -0
- soe/builtin_tools/soe_inject_node.py +86 -0
- soe/builtin_tools/soe_inject_workflow.py +105 -0
- soe/builtin_tools/soe_list_contexts.py +73 -0
- soe/builtin_tools/soe_remove_context_schema_field.py +61 -0
- soe/builtin_tools/soe_remove_identity.py +61 -0
- soe/builtin_tools/soe_remove_node.py +72 -0
- soe/builtin_tools/soe_remove_workflow.py +62 -0
- soe/builtin_tools/soe_update_context.py +54 -0
- soe/docs/_config.yml +10 -0
- soe/docs/advanced_patterns/guide_fanout_and_aggregations.md +318 -0
- soe/docs/advanced_patterns/guide_inheritance.md +435 -0
- soe/docs/advanced_patterns/hybrid_intelligence.md +237 -0
- soe/docs/advanced_patterns/index.md +49 -0
- soe/docs/advanced_patterns/operational.md +781 -0
- soe/docs/advanced_patterns/self_evolving_workflows.md +385 -0
- soe/docs/advanced_patterns/swarm_intelligence.md +211 -0
- soe/docs/builtins/context.md +164 -0
- soe/docs/builtins/context_schema.md +158 -0
- soe/docs/builtins/identity.md +139 -0
- soe/docs/builtins/soe_explore_docs.md +135 -0
- soe/docs/builtins/tools.md +164 -0
- soe/docs/builtins/workflows.md +199 -0
- soe/docs/guide_00_getting_started.md +341 -0
- soe/docs/guide_01_tool.md +206 -0
- soe/docs/guide_02_llm.md +143 -0
- soe/docs/guide_03_router.md +146 -0
- soe/docs/guide_04_patterns.md +475 -0
- soe/docs/guide_05_agent.md +159 -0
- soe/docs/guide_06_schema.md +397 -0
- soe/docs/guide_07_identity.md +540 -0
- soe/docs/guide_08_child.md +612 -0
- soe/docs/guide_09_ecosystem.md +690 -0
- soe/docs/guide_10_infrastructure.md +427 -0
- soe/docs/guide_11_builtins.md +126 -0
- soe/docs/index.md +104 -0
- soe/docs/primitives/backends.md +281 -0
- soe/docs/primitives/context.md +256 -0
- soe/docs/primitives/node_reference.md +259 -0
- soe/docs/primitives/primitives.md +331 -0
- soe/docs/primitives/signals.md +865 -0
- soe/docs_index.py +2 -0
- soe/init.py +165 -0
- soe/lib/__init__.py +0 -0
- soe/lib/child_context.py +46 -0
- soe/lib/context_fields.py +51 -0
- soe/lib/inheritance.py +172 -0
- soe/lib/jinja_render.py +113 -0
- soe/lib/operational.py +51 -0
- soe/lib/parent_sync.py +71 -0
- soe/lib/register_event.py +75 -0
- soe/lib/schema_validation.py +134 -0
- soe/lib/yaml_parser.py +14 -0
- soe/local_backends/__init__.py +18 -0
- soe/local_backends/factory.py +124 -0
- soe/local_backends/in_memory/context.py +38 -0
- soe/local_backends/in_memory/conversation_history.py +60 -0
- soe/local_backends/in_memory/identity.py +52 -0
- soe/local_backends/in_memory/schema.py +40 -0
- soe/local_backends/in_memory/telemetry.py +38 -0
- soe/local_backends/in_memory/workflow.py +33 -0
- soe/local_backends/storage/context.py +57 -0
- soe/local_backends/storage/conversation_history.py +82 -0
- soe/local_backends/storage/identity.py +118 -0
- soe/local_backends/storage/schema.py +96 -0
- soe/local_backends/storage/telemetry.py +72 -0
- soe/local_backends/storage/workflow.py +56 -0
- soe/nodes/__init__.py +13 -0
- soe/nodes/agent/__init__.py +10 -0
- soe/nodes/agent/factory.py +134 -0
- soe/nodes/agent/lib/loop_handlers.py +150 -0
- soe/nodes/agent/lib/loop_state.py +157 -0
- soe/nodes/agent/lib/prompts.py +65 -0
- soe/nodes/agent/lib/tools.py +35 -0
- soe/nodes/agent/stages/__init__.py +12 -0
- soe/nodes/agent/stages/parameter.py +37 -0
- soe/nodes/agent/stages/response.py +54 -0
- soe/nodes/agent/stages/router.py +37 -0
- soe/nodes/agent/state.py +111 -0
- soe/nodes/agent/types.py +66 -0
- soe/nodes/agent/validation/__init__.py +11 -0
- soe/nodes/agent/validation/config.py +95 -0
- soe/nodes/agent/validation/operational.py +24 -0
- soe/nodes/child/__init__.py +3 -0
- soe/nodes/child/factory.py +61 -0
- soe/nodes/child/state.py +59 -0
- soe/nodes/child/validation/__init__.py +11 -0
- soe/nodes/child/validation/config.py +126 -0
- soe/nodes/child/validation/operational.py +28 -0
- soe/nodes/lib/conditions.py +71 -0
- soe/nodes/lib/context.py +24 -0
- soe/nodes/lib/conversation_history.py +77 -0
- soe/nodes/lib/identity.py +64 -0
- soe/nodes/lib/llm_resolver.py +142 -0
- soe/nodes/lib/output.py +68 -0
- soe/nodes/lib/response_builder.py +91 -0
- soe/nodes/lib/signal_emission.py +79 -0
- soe/nodes/lib/signals.py +54 -0
- soe/nodes/lib/tools.py +100 -0
- soe/nodes/llm/__init__.py +7 -0
- soe/nodes/llm/factory.py +103 -0
- soe/nodes/llm/state.py +76 -0
- soe/nodes/llm/types.py +12 -0
- soe/nodes/llm/validation/__init__.py +11 -0
- soe/nodes/llm/validation/config.py +89 -0
- soe/nodes/llm/validation/operational.py +23 -0
- soe/nodes/router/__init__.py +3 -0
- soe/nodes/router/factory.py +37 -0
- soe/nodes/router/state.py +32 -0
- soe/nodes/router/validation/__init__.py +11 -0
- soe/nodes/router/validation/config.py +58 -0
- soe/nodes/router/validation/operational.py +16 -0
- soe/nodes/tool/factory.py +66 -0
- soe/nodes/tool/lib/__init__.py +11 -0
- soe/nodes/tool/lib/conditions.py +35 -0
- soe/nodes/tool/lib/failure.py +28 -0
- soe/nodes/tool/lib/parameters.py +67 -0
- soe/nodes/tool/state.py +66 -0
- soe/nodes/tool/types.py +27 -0
- soe/nodes/tool/validation/__init__.py +15 -0
- soe/nodes/tool/validation/config.py +132 -0
- soe/nodes/tool/validation/operational.py +16 -0
- soe/types.py +209 -0
- soe/validation/__init__.py +18 -0
- soe/validation/config.py +195 -0
- soe/validation/jinja.py +54 -0
- soe/validation/operational.py +110 -0
- soe_ai-0.2.0b1.dist-info/METADATA +262 -0
- soe_ai-0.2.0b1.dist-info/RECORD +145 -0
- soe_ai-0.2.0b1.dist-info/WHEEL +5 -0
- soe_ai-0.2.0b1.dist-info/licenses/LICENSE +21 -0
- soe_ai-0.2.0b1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
|
|
2
|
+
# SOE Guide: Chapter 6 - Context Schema
|
|
3
|
+
|
|
4
|
+
## Introduction to Context Schema
|
|
5
|
+
|
|
6
|
+
**Context Schema** provides optional type validation for context fields. When an LLM node writes to a context field, the schema ensures the output matches the expected type (string, integer, object, etc.).
|
|
7
|
+
|
|
8
|
+
> **Note**: This was previously called just "Schema". We renamed it to "Context Schema" to distinguish it from the Identity Schema (see [Chapter 7](guide_07_identity.md)).
|
|
9
|
+
|
|
10
|
+
### Why Use Context Schema?
|
|
11
|
+
|
|
12
|
+
- **Type Safety**: Catch malformed LLM output before it breaks downstream nodes.
|
|
13
|
+
- **Tool Integration**: Ensure LLM output has the correct structure for tools.
|
|
14
|
+
- **Documentation**: Schema definitions serve as documentation for your workflow's data model.
|
|
15
|
+
- **Removes Prompt Boilerplate**: You don't need to specify output format in every prompt—the schema handles it.
|
|
16
|
+
|
|
17
|
+
## Defining a Schema
|
|
18
|
+
|
|
19
|
+
Schemas are defined per-workflow, mapping field names to their types:
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
schemas = {
|
|
23
|
+
"example_workflow": {
|
|
24
|
+
"summary": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "A one-sentence summary of the input text"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Available Types
|
|
33
|
+
|
|
34
|
+
| Type | Python Type | Description |
|
|
35
|
+
|------|-------------|-------------|
|
|
36
|
+
| `string` | `str` | Text values |
|
|
37
|
+
| `integer` | `int` | Whole numbers |
|
|
38
|
+
| `number` | `float` | Decimal numbers |
|
|
39
|
+
| `boolean` | `bool` | True/False |
|
|
40
|
+
| `object` | `dict` | JSON objects |
|
|
41
|
+
| `list` | `list` | Arrays |
|
|
42
|
+
| `dict` | `dict` | Alias for object |
|
|
43
|
+
|
|
44
|
+
## Your First Schema (Full Config)
|
|
45
|
+
|
|
46
|
+
Let's validate that an LLM returns a proper string summary using the **combined config** format (workflows + context_schema in one YAML).
|
|
47
|
+
|
|
48
|
+
### Full Workflow + Schema (Config)
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
workflows:
|
|
52
|
+
example_workflow:
|
|
53
|
+
SummarizeLLM:
|
|
54
|
+
node_type: llm
|
|
55
|
+
event_triggers: [START]
|
|
56
|
+
prompt: "Summarize the following text in one sentence: {{ context.input_text }}"
|
|
57
|
+
output_field: summary
|
|
58
|
+
event_emissions:
|
|
59
|
+
- signal_name: SUMMARY_COMPLETE
|
|
60
|
+
|
|
61
|
+
context_schema:
|
|
62
|
+
summary:
|
|
63
|
+
type: string
|
|
64
|
+
description: A one-sentence summary of the input text
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### How It Works
|
|
68
|
+
|
|
69
|
+
1. The LLM node writes to `output_field: summary`.
|
|
70
|
+
2. Schema backend finds the schema for `summary`.
|
|
71
|
+
3. The LLM returns the **schema value directly** (no wrapper key).
|
|
72
|
+
4. Valid output → saved to context under `summary` → `SUMMARY_COMPLETE` emitted.
|
|
73
|
+
|
|
74
|
+
## Integer Schema (Full Config)
|
|
75
|
+
|
|
76
|
+
For numeric outputs like counts or scores:
|
|
77
|
+
|
|
78
|
+
### Full Workflow + Schema (Config)
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
workflows:
|
|
82
|
+
example_workflow:
|
|
83
|
+
CounterLLM:
|
|
84
|
+
node_type: llm
|
|
85
|
+
event_triggers: [START]
|
|
86
|
+
prompt: "Count the number of words in this text: {{ context.input_text }}. Return only the count."
|
|
87
|
+
output_field: word_count
|
|
88
|
+
event_emissions:
|
|
89
|
+
- signal_name: COUNT_COMPLETE
|
|
90
|
+
|
|
91
|
+
context_schema:
|
|
92
|
+
word_count:
|
|
93
|
+
type: integer
|
|
94
|
+
description: The number of words in the input text
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The LLM must return `42` (an integer), not `"forty-two"`.
|
|
98
|
+
|
|
99
|
+
## Object Schema (Full Config)
|
|
100
|
+
|
|
101
|
+
For structured data extraction:
|
|
102
|
+
|
|
103
|
+
### Full Workflow + Schema (Config)
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
workflows:
|
|
107
|
+
example_workflow:
|
|
108
|
+
ExtractorLLM:
|
|
109
|
+
node_type: llm
|
|
110
|
+
event_triggers: [START]
|
|
111
|
+
prompt: "Extract the person's name and age from: {{ context.input_text }}. Return as JSON with 'name' and 'age' fields."
|
|
112
|
+
output_field: person_data
|
|
113
|
+
event_emissions:
|
|
114
|
+
- signal_name: EXTRACTION_COMPLETE
|
|
115
|
+
|
|
116
|
+
context_schema:
|
|
117
|
+
person_data:
|
|
118
|
+
type: object
|
|
119
|
+
description: Extracted person data with name and age
|
|
120
|
+
properties:
|
|
121
|
+
name:
|
|
122
|
+
type: string
|
|
123
|
+
age:
|
|
124
|
+
type: integer
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Object schemas accept JSON objects. You can also define nested fields with `properties`.
|
|
128
|
+
|
|
129
|
+
### Nested Object Schema (with `properties`)
|
|
130
|
+
|
|
131
|
+
```yaml
|
|
132
|
+
context_schema:
|
|
133
|
+
person_data:
|
|
134
|
+
type: object
|
|
135
|
+
description: Person data
|
|
136
|
+
properties:
|
|
137
|
+
name:
|
|
138
|
+
type: string
|
|
139
|
+
age:
|
|
140
|
+
type: integer
|
|
141
|
+
address:
|
|
142
|
+
type: object
|
|
143
|
+
properties:
|
|
144
|
+
city:
|
|
145
|
+
type: string
|
|
146
|
+
zip:
|
|
147
|
+
type: string
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Valid LLM output (no wrapper):**
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{"name": "Bob", "age": 25, "address": {"city": "NYC", "zip": "10001"}}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Schema with Tool Integration (Full Config)
|
|
157
|
+
|
|
158
|
+
Schema shines when LLM output feeds into tool parameters. This ensures the LLM returns data in the exact format your tool expects.
|
|
159
|
+
|
|
160
|
+
### Full Workflow + Schema (Config)
|
|
161
|
+
|
|
162
|
+
```yaml
|
|
163
|
+
workflows:
|
|
164
|
+
example_workflow:
|
|
165
|
+
ParameterExtractor:
|
|
166
|
+
node_type: llm
|
|
167
|
+
event_triggers: [START]
|
|
168
|
+
prompt: "Extract the operation and numbers from: {{ context.user_request }}. Return JSON with 'operation' (add/multiply) and 'numbers' (list of integers)."
|
|
169
|
+
output_field: params
|
|
170
|
+
event_emissions:
|
|
171
|
+
- signal_name: PARAMS_EXTRACTED
|
|
172
|
+
|
|
173
|
+
Calculator:
|
|
174
|
+
node_type: tool
|
|
175
|
+
event_triggers: [PARAMS_EXTRACTED]
|
|
176
|
+
tool_name: calculate
|
|
177
|
+
context_parameter_field: params
|
|
178
|
+
output_field: result
|
|
179
|
+
event_emissions:
|
|
180
|
+
- signal_name: CALCULATED
|
|
181
|
+
|
|
182
|
+
context_schema:
|
|
183
|
+
params:
|
|
184
|
+
type: object
|
|
185
|
+
description: Extracted parameters with operation and numbers
|
|
186
|
+
properties:
|
|
187
|
+
operation:
|
|
188
|
+
type: string
|
|
189
|
+
numbers:
|
|
190
|
+
type: list
|
|
191
|
+
items:
|
|
192
|
+
type: integer
|
|
193
|
+
result:
|
|
194
|
+
type: object
|
|
195
|
+
description: Calculation result
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Data Flow
|
|
199
|
+
|
|
200
|
+
1. `ParameterExtractor` LLM extracts `{ "operation": "add", "numbers": [10, 20, 30] }`.
|
|
201
|
+
2. Schema validates this is an object (dict).
|
|
202
|
+
3. `Calculator` tool receives the validated params.
|
|
203
|
+
4. Tool returns result, also validated against schema.
|
|
204
|
+
|
|
205
|
+
## Multiple Fields (Full Config)
|
|
206
|
+
|
|
207
|
+
A single workflow can have schemas for multiple fields:
|
|
208
|
+
|
|
209
|
+
### Full Workflow + Schema (Config)
|
|
210
|
+
|
|
211
|
+
```yaml
|
|
212
|
+
workflows:
|
|
213
|
+
example_workflow:
|
|
214
|
+
AnalyzerLLM:
|
|
215
|
+
node_type: llm
|
|
216
|
+
event_triggers: [START]
|
|
217
|
+
prompt: "Analyze this text: {{ context.input_text }}. Extract the topic and key points."
|
|
218
|
+
output_field: topic
|
|
219
|
+
event_emissions:
|
|
220
|
+
- signal_name: TOPIC_EXTRACTED
|
|
221
|
+
|
|
222
|
+
SummarizerLLM:
|
|
223
|
+
node_type: llm
|
|
224
|
+
event_triggers: [TOPIC_EXTRACTED]
|
|
225
|
+
prompt: "Given the topic '{{ context.topic }}', provide a brief summary of: {{ context.input_text }}"
|
|
226
|
+
output_field: summary
|
|
227
|
+
event_emissions:
|
|
228
|
+
- signal_name: ANALYSIS_COMPLETE
|
|
229
|
+
|
|
230
|
+
context_schema:
|
|
231
|
+
topic:
|
|
232
|
+
type: string
|
|
233
|
+
description: The main topic of the text
|
|
234
|
+
summary:
|
|
235
|
+
type: string
|
|
236
|
+
description: A brief summary based on the topic
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Each field is validated independently when its LLM node completes.
|
|
240
|
+
|
|
241
|
+
## Agent Node + Schema (Full Config)
|
|
242
|
+
|
|
243
|
+
```yaml
|
|
244
|
+
workflows:
|
|
245
|
+
example_workflow:
|
|
246
|
+
DataAgent:
|
|
247
|
+
node_type: agent
|
|
248
|
+
event_triggers: [START]
|
|
249
|
+
prompt: "Process this request: {{ context.user_request }}"
|
|
250
|
+
tools: [fetch_data]
|
|
251
|
+
output_field: response
|
|
252
|
+
event_emissions:
|
|
253
|
+
- signal_name: AGENT_COMPLETE
|
|
254
|
+
|
|
255
|
+
context_schema:
|
|
256
|
+
response:
|
|
257
|
+
type: string
|
|
258
|
+
description: The agent's final response to the user
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The agent response is validated against the schema for `response`.
|
|
262
|
+
|
|
263
|
+
## Schema is Optional (Workflow Only)
|
|
264
|
+
|
|
265
|
+
Schemas are completely optional. Workflows work fine without them:
|
|
266
|
+
|
|
267
|
+
### The Workflow (No context_schema)
|
|
268
|
+
|
|
269
|
+
```yaml
|
|
270
|
+
example_workflow:
|
|
271
|
+
FreeLLM:
|
|
272
|
+
node_type: llm
|
|
273
|
+
event_triggers: [START]
|
|
274
|
+
prompt: "Do whatever you want with: {{ context.input_text }}"
|
|
275
|
+
output_field: output
|
|
276
|
+
event_emissions:
|
|
277
|
+
- signal_name: DONE
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Without schema, LLM output is saved as-is without validation. This is fine for:
|
|
281
|
+
- Prototyping
|
|
282
|
+
- Free-form text generation
|
|
283
|
+
- When you trust the LLM output format
|
|
284
|
+
|
|
285
|
+
## Output Shape (Important)
|
|
286
|
+
|
|
287
|
+
When `context_schema` is present, the LLM should return the **schema value directly**:
|
|
288
|
+
|
|
289
|
+
- For `string`: `"short summary"`
|
|
290
|
+
- For `integer`: `42`
|
|
291
|
+
- For `object`: `{ "domain": "ECOSYSTEM", "instruction": "..." }`
|
|
292
|
+
- For `list`: `["a", "b", "c"]`
|
|
293
|
+
|
|
294
|
+
SOE stores that value under `context[output_field]`.
|
|
295
|
+
|
|
296
|
+
## Defining Schemas in Config (Recommended)
|
|
297
|
+
|
|
298
|
+
The simplest approach is including `context_schema` directly in your config YAML:
|
|
299
|
+
|
|
300
|
+
```yaml
|
|
301
|
+
# Complete config with workflows and context_schema
|
|
302
|
+
workflows:
|
|
303
|
+
example_workflow:
|
|
304
|
+
Summarizer:
|
|
305
|
+
node_type: llm
|
|
306
|
+
event_triggers: [START]
|
|
307
|
+
prompt: "Summarize: {{ context.input }}"
|
|
308
|
+
output_field: summary
|
|
309
|
+
event_emissions:
|
|
310
|
+
- signal_name: DONE
|
|
311
|
+
|
|
312
|
+
context_schema:
|
|
313
|
+
summary:
|
|
314
|
+
type: string
|
|
315
|
+
description: A one-sentence summary
|
|
316
|
+
result:
|
|
317
|
+
type: object
|
|
318
|
+
description: The workflow result
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Then pass the entire config to orchestrate:
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
from soe import orchestrate
|
|
325
|
+
|
|
326
|
+
execution_id = orchestrate(
|
|
327
|
+
config=CONFIG_YAML, # The YAML string above
|
|
328
|
+
initial_workflow_name="example_workflow",
|
|
329
|
+
initial_signals=["START"],
|
|
330
|
+
initial_context={"input": "test"},
|
|
331
|
+
backends=backends,
|
|
332
|
+
broadcast_signals_caller=broadcast_signals_caller,
|
|
333
|
+
)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
When `context_schema` is included in config:
|
|
337
|
+
1. It's automatically extracted and saved to the `ContextSchemaBackend`
|
|
338
|
+
2. It's keyed by `execution_id` (specifically `main_execution_id`)
|
|
339
|
+
3. Child workflows can access parent's schema through the same `main_execution_id`
|
|
340
|
+
|
|
341
|
+
### Backend Requirement
|
|
342
|
+
|
|
343
|
+
For context schema to work, you need a `ContextSchemaBackend`. The local backends include one:
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
from soe.local_backends import create_local_backends
|
|
347
|
+
|
|
348
|
+
backends = create_local_backends(
|
|
349
|
+
context_storage_dir="./data/contexts",
|
|
350
|
+
workflow_storage_dir="./data/workflows",
|
|
351
|
+
schema_storage_dir="./data/schemas", # Context schema storage
|
|
352
|
+
)
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**Recommendation**: Use the same database for workflows, context, identities, and context_schema. The backend methods create separate tables, not separate databases. This simplifies infrastructure management.
|
|
356
|
+
|
|
357
|
+
## Saving Schemas Programmatically
|
|
358
|
+
|
|
359
|
+
You can also save schemas via the backend directly. Note that schemas are keyed by `execution_id`, not workflow name:
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
from soe import orchestrate
|
|
363
|
+
from soe.local_backends import create_local_backends
|
|
364
|
+
|
|
365
|
+
backends = create_local_backends(...)
|
|
366
|
+
|
|
367
|
+
# Run orchestrate first to get the execution_id
|
|
368
|
+
execution_id = orchestrate(
|
|
369
|
+
config=MY_WORKFLOW,
|
|
370
|
+
initial_workflow_name="my_workflow",
|
|
371
|
+
initial_signals=["START"],
|
|
372
|
+
initial_context={"input": "test"},
|
|
373
|
+
backends=backends,
|
|
374
|
+
broadcast_signals_caller=broadcast_signals_caller,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# Retrieve schema (keyed by execution_id)
|
|
378
|
+
schema = backends.context_schema.get_context_schema(execution_id)
|
|
379
|
+
|
|
380
|
+
# Get schema for specific field
|
|
381
|
+
field_schema = backends.context_schema.get_field_schema(execution_id, "result")
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**Important**: The preferred approach is defining `context_schema` in your config, which automatically saves it before orchestration begins.
|
|
385
|
+
|
|
386
|
+
## Key Points
|
|
387
|
+
|
|
388
|
+
- **Optional but powerful**: Use schemas when type safety matters.
|
|
389
|
+
- **Define in config**: Use `context_schema` section in your config for automatic setup.
|
|
390
|
+
- **Keyed by execution_id**: Schemas are stored by `main_execution_id`, enabling child workflow access.
|
|
391
|
+
- **Per-field types**: Each context field can have its own type.
|
|
392
|
+
- **LLM validation**: Ensures LLM output matches expected structure.
|
|
393
|
+
- **Tool integration**: Critical when LLM output feeds tool parameters.
|
|
394
|
+
|
|
395
|
+
## Next Steps
|
|
396
|
+
|
|
397
|
+
Now that you understand how to validate LLM output structure, let's explore [Identity](guide_07_identity.md) for persisting conversation history across LLM calls →
|