soe-ai 0.1.1__py3-none-any.whl → 0.1.2__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/builtin_tools/__init__.py +39 -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_workflows.py +63 -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_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/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 +118 -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 +1 -1
- 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/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.1.1.dist-info → soe_ai-0.1.2.dist-info}/METADATA +4 -4
- soe_ai-0.1.2.dist-info/RECORD +137 -0
- {soe_ai-0.1.1.dist-info → soe_ai-0.1.2.dist-info}/WHEEL +1 -1
- soe_ai-0.1.1.dist-info/RECORD +0 -10
- {soe_ai-0.1.1.dist-info → soe_ai-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {soe_ai-0.1.1.dist-info → soe_ai-0.1.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
|
|
2
|
+
# SOE Guide: Chapter 5 - Agent Nodes
|
|
3
|
+
|
|
4
|
+
## Introduction to Agent Nodes
|
|
5
|
+
|
|
6
|
+
The **Agent Node** is a convenience wrapper that encapsulates the **ReAct pattern** (Reasoning + Acting) into a single, configurable component.
|
|
7
|
+
|
|
8
|
+
In the previous chapter, you learned how to build custom agent loops using Tool, LLM, and Router nodes. The Agent Node does the same thing—but packaged for common use cases where you want a "hands-off" tool-using agent.
|
|
9
|
+
|
|
10
|
+
### Why Use Agent Nodes?
|
|
11
|
+
|
|
12
|
+
| Building Custom (Ch. 4) | Using Agent Node |
|
|
13
|
+
|------------------------|------------------|
|
|
14
|
+
| Full control over each step | Quick to configure |
|
|
15
|
+
| Custom reasoning patterns | Standard ReAct loop |
|
|
16
|
+
| Explicit debugging points | Batteries included |
|
|
17
|
+
| Any architecture you want | Opinionated but effective |
|
|
18
|
+
|
|
19
|
+
**Use Agent Node when**: You need a straightforward tool-using agent and don't need custom reasoning patterns.
|
|
20
|
+
|
|
21
|
+
**Use custom workflows when**: You need chain-of-thought, metacognition, voting, or other advanced patterns.
|
|
22
|
+
|
|
23
|
+
**Note**: Agent nodes can also be used for other patterns (routing, summarization, etc.), but they involve more LLM calls than specialized nodes. Use the right node type for your use case.
|
|
24
|
+
|
|
25
|
+
### The Agent Loop
|
|
26
|
+
|
|
27
|
+
The Agent Node runs this loop internally:
|
|
28
|
+
|
|
29
|
+
1. **Router Stage**: The agent decides what to do (Call a Tool or Finish)
|
|
30
|
+
2. **Parameter Stage**: If calling a tool, it generates the arguments
|
|
31
|
+
3. **Execution Stage**: The tool is executed
|
|
32
|
+
4. **Loop**: The results are fed back into the history, and the agent decides again
|
|
33
|
+
|
|
34
|
+
This is exactly what you built manually in Chapter 4's "Custom ReAct Loop" pattern—but as a single node.
|
|
35
|
+
|
|
36
|
+
### Multiple Tool Calls
|
|
37
|
+
|
|
38
|
+
The agent can call multiple tools in sequence during a single node execution:
|
|
39
|
+
|
|
40
|
+
1. Agent decides to call Tool A → executes → sees result
|
|
41
|
+
2. Agent decides to call Tool B → executes → sees result
|
|
42
|
+
3. Agent decides to call Tool A again → executes → sees result
|
|
43
|
+
4. Agent decides to Finish
|
|
44
|
+
|
|
45
|
+
**Exhaustive tool calling**: The agent will keep calling tools until it decides to finish. There's no hard limit on tool calls—the agent uses its judgment. To prevent runaway loops, use operational limits (see [Operational Features](advanced_patterns/operational.md)).
|
|
46
|
+
|
|
47
|
+
## Configuring Tools
|
|
48
|
+
|
|
49
|
+
Agents have access to the tools you provide in their `tools` list.
|
|
50
|
+
|
|
51
|
+
> [!IMPORTANT]
|
|
52
|
+
> **Tool Restriction**: Agent nodes can ONLY call tools explicitly listed in their `tools` configuration, even if a tool is a SOE built-in. If you want an agent to be able to use a built-in tool like `soe_inject_node`, you must include it in the `tools` list for that node.
|
|
53
|
+
|
|
54
|
+
### Example: Tool Restriction
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
MyAgent:
|
|
58
|
+
node_type: agent
|
|
59
|
+
tools:
|
|
60
|
+
- calculate_total # User tool
|
|
61
|
+
- soe_inject_node # Built-in tool (MUST be listed here)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### The Workflow
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
example_workflow:
|
|
68
|
+
CalculatorAgent:
|
|
69
|
+
node_type: agent
|
|
70
|
+
event_triggers: [START]
|
|
71
|
+
prompt: "You are a calculator. Solve the user's math problem: {{ context.problem }}"
|
|
72
|
+
tools: [calculator]
|
|
73
|
+
output_field: result
|
|
74
|
+
event_emissions:
|
|
75
|
+
- signal_name: CALCULATION_DONE
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### How It Works
|
|
79
|
+
|
|
80
|
+
1. **`tools`**: We give the agent a list of tools (e.g., `[calculator]`).
|
|
81
|
+
2. **The Loop**:
|
|
82
|
+
* The agent sees the prompt: "Solve... {{ context.problem }}"
|
|
83
|
+
* It decides to call `calculator(5, 3, 'add')`.
|
|
84
|
+
* The tool returns `8`.
|
|
85
|
+
* The agent sees the result and decides to **Finish**.
|
|
86
|
+
3. **Output**: The final answer is stored in `context.result`.
|
|
87
|
+
|
|
88
|
+
## Advanced Configuration: Multiple Tools
|
|
89
|
+
|
|
90
|
+
Agents can handle complex tasks with multiple tools.
|
|
91
|
+
|
|
92
|
+
### The Workflow
|
|
93
|
+
|
|
94
|
+
```yaml
|
|
95
|
+
example_workflow:
|
|
96
|
+
ResearchAgent:
|
|
97
|
+
node_type: agent
|
|
98
|
+
event_triggers: [START]
|
|
99
|
+
prompt: "Research the topic: {{ context.topic }}"
|
|
100
|
+
tools: [search_web, summarize_text]
|
|
101
|
+
output_field: report
|
|
102
|
+
event_emissions:
|
|
103
|
+
- signal_name: REPORT_READY
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Robustness Features
|
|
107
|
+
|
|
108
|
+
- **Tool Selection**: The agent intelligently chooses between `search_web` and `summarize_text` based on the goal.
|
|
109
|
+
- **Error Recovery**: If a tool fails (throws an exception), the agent sees the error and can try again or try a different strategy.
|
|
110
|
+
|
|
111
|
+
## Agent Signal Selection
|
|
112
|
+
|
|
113
|
+
Like LLM nodes, Agent nodes can select which signal to emit based on their analysis:
|
|
114
|
+
|
|
115
|
+
### The Workflow
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
example_workflow:
|
|
119
|
+
AnalysisAgent:
|
|
120
|
+
node_type: agent
|
|
121
|
+
event_triggers: [START]
|
|
122
|
+
prompt: "Analyze the data and determine if it needs human review: {{ context.data }}"
|
|
123
|
+
tools: [analyze_data]
|
|
124
|
+
output_field: analysis
|
|
125
|
+
event_emissions:
|
|
126
|
+
- signal_name: AUTO_APPROVE
|
|
127
|
+
condition: "The analysis shows the data is clearly valid"
|
|
128
|
+
- signal_name: NEEDS_REVIEW
|
|
129
|
+
condition: "The analysis shows the data requires human review"
|
|
130
|
+
- signal_name: REJECT
|
|
131
|
+
condition: "The analysis shows the data is clearly invalid"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The agent completes its task, then the signal selection works the same as LLM nodes:
|
|
135
|
+
- **Plain text conditions**: LLM chooses semantically
|
|
136
|
+
- **Jinja conditions**: Evaluated programmatically
|
|
137
|
+
|
|
138
|
+
## When to Choose Agent vs Custom Workflow
|
|
139
|
+
|
|
140
|
+
### Use Agent Node For:
|
|
141
|
+
|
|
142
|
+
- **Quick prototyping**: Get a tool-using agent running fast
|
|
143
|
+
- **Standard tasks**: Research, calculation, data gathering
|
|
144
|
+
- **Simple tool orchestration**: When tool selection is the main complexity
|
|
145
|
+
|
|
146
|
+
### Build Custom Workflows For:
|
|
147
|
+
|
|
148
|
+
- **Chain-of-thought reasoning**: Explicit step-by-step thinking
|
|
149
|
+
- **Metacognition**: Self-review and refinement loops
|
|
150
|
+
- **Parallel/voting patterns**: Multiple analyses combined
|
|
151
|
+
- **Hybrid logic**: Mixing deterministic gates with AI
|
|
152
|
+
- **Audit requirements**: When you need to log/inspect each decision
|
|
153
|
+
- **Custom termination conditions**: Beyond simple "finish" detection
|
|
154
|
+
|
|
155
|
+
Remember: The Agent Node is **syntactic sugar** for a common pattern. Everything it does, you can build yourself with the three core node types.
|
|
156
|
+
|
|
157
|
+
## Next Steps
|
|
158
|
+
|
|
159
|
+
Now that you understand both custom workflows and the Agent Node, let's explore [Schemas](guide_06_schema.md) for structured LLM output →
|
|
@@ -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 →
|