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,146 @@
|
|
|
1
|
+
|
|
2
|
+
# SOE Guide: Chapter 3 - Router Nodes
|
|
3
|
+
|
|
4
|
+
## Introduction to Router Nodes
|
|
5
|
+
|
|
6
|
+
The **Router** is a pure routing node—it doesn't execute code or call LLMs. It simply reads the context, evaluates conditions, and emits signals to direct the workflow.
|
|
7
|
+
|
|
8
|
+
Think of a Router as a **traffic controller**: based on the current state (context), it decides which signal to emit, directing the workflow to the next step. Routers are the glue that connects your Tool and LLM nodes together.
|
|
9
|
+
|
|
10
|
+
### When to Use Router Nodes
|
|
11
|
+
|
|
12
|
+
- **Entry Points**: Start a workflow and fan out to multiple paths
|
|
13
|
+
- **Conditional Branching**: Route based on context values
|
|
14
|
+
- **Signal Transformation**: Convert one signal to another
|
|
15
|
+
- **Checkpoints**: Create explicit decision points in your workflow
|
|
16
|
+
|
|
17
|
+
### Why Router Comes Third
|
|
18
|
+
|
|
19
|
+
We covered Tool and LLM nodes first because they **do work**. Routers don't—they route. Now that you understand the nodes that execute, you can see how Routers connect them into powerful workflows.
|
|
20
|
+
|
|
21
|
+
## Your First Router: Input Validation
|
|
22
|
+
|
|
23
|
+
Let's validate that user input exists before processing it.
|
|
24
|
+
|
|
25
|
+
### The Workflow
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
example_workflow:
|
|
29
|
+
InputValidator:
|
|
30
|
+
node_type: router
|
|
31
|
+
event_triggers: [START]
|
|
32
|
+
event_emissions:
|
|
33
|
+
- signal_name: VALID_INPUT
|
|
34
|
+
condition: "{{ context.user_input is defined and context.user_input != '' }}"
|
|
35
|
+
- signal_name: INVALID_INPUT
|
|
36
|
+
condition: "{{ context.user_input is not defined or context.user_input == '' }}"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### How It Works
|
|
40
|
+
|
|
41
|
+
1. **Event Trigger**: The `START` signal triggers the `InputValidator` node.
|
|
42
|
+
2. **Condition Evaluation**: The router checks two conditions using Jinja2 templates:
|
|
43
|
+
- If `context.user_input` is defined and not empty → emit `VALID_INPUT`
|
|
44
|
+
- If `context.user_input` is missing or empty → emit `INVALID_INPUT`
|
|
45
|
+
3. **Signal Emission**: One or more signals are emitted based on which conditions are true.
|
|
46
|
+
|
|
47
|
+
### Key Concepts
|
|
48
|
+
|
|
49
|
+
- **Routers** evaluate Jinja2 conditions and emit signals
|
|
50
|
+
- **Conditions** can check if variables exist, compare values, etc.
|
|
51
|
+
- **Signals** are the "nervous system" of SOE—they trigger the next steps
|
|
52
|
+
|
|
53
|
+
## Unconditional Signals
|
|
54
|
+
|
|
55
|
+
Not every router needs a condition. Sometimes you just want to forward a signal unconditionally.
|
|
56
|
+
|
|
57
|
+
### The Workflow
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
example_workflow:
|
|
61
|
+
Forwarder:
|
|
62
|
+
node_type: router
|
|
63
|
+
event_triggers: [START]
|
|
64
|
+
event_emissions:
|
|
65
|
+
- signal_name: CONTINUE
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This router simply receives `START` and immediately emits `CONTINUE`. It's useful when you want to:
|
|
69
|
+
- **Rename signals**: Transform external signals to internal naming
|
|
70
|
+
- **Create checkpoints**: Make workflow structure explicit
|
|
71
|
+
- **Fan-out**: Emit multiple signals to trigger parallel processing
|
|
72
|
+
|
|
73
|
+
## Decision Trees: Routers Calling Routers
|
|
74
|
+
|
|
75
|
+
The real power of routers emerges when you chain them together. Since each router emits signals that trigger other nodes, you can build **decision trees** (or more generally, **directed graphs**) where the workflow branches based on context.
|
|
76
|
+
|
|
77
|
+
### The Pattern
|
|
78
|
+
|
|
79
|
+
Think of it as a flowchart:
|
|
80
|
+
- **Level 1 Router**: Classifies the initial request
|
|
81
|
+
- **Level 2+ Routers**: Handle sub-cases, triggered by signals from Level 1
|
|
82
|
+
- **Terminal Nodes**: Complete specific branches of the tree
|
|
83
|
+
|
|
84
|
+
### The Workflow
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
example_workflow:
|
|
88
|
+
# Level 1: Is the user premium or free?
|
|
89
|
+
UserTierCheck:
|
|
90
|
+
node_type: router
|
|
91
|
+
event_triggers: [START]
|
|
92
|
+
event_emissions:
|
|
93
|
+
- signal_name: PREMIUM_PATH
|
|
94
|
+
condition: "{{ context.user_tier == 'premium' }}"
|
|
95
|
+
- signal_name: FREE_PATH
|
|
96
|
+
condition: "{{ context.user_tier == 'free' }}"
|
|
97
|
+
|
|
98
|
+
# Level 2a: Premium users get feature checks
|
|
99
|
+
PremiumFeatureRouter:
|
|
100
|
+
node_type: router
|
|
101
|
+
event_triggers: [PREMIUM_PATH]
|
|
102
|
+
event_emissions:
|
|
103
|
+
- signal_name: ENABLE_ADVANCED
|
|
104
|
+
condition: "{{ context.feature_level == 'advanced' }}"
|
|
105
|
+
- signal_name: ENABLE_BASIC
|
|
106
|
+
condition: "{{ context.feature_level != 'advanced' }}"
|
|
107
|
+
|
|
108
|
+
# Level 2b: Free users get upgrade prompts
|
|
109
|
+
FreeUserRouter:
|
|
110
|
+
node_type: router
|
|
111
|
+
event_triggers: [FREE_PATH]
|
|
112
|
+
event_emissions:
|
|
113
|
+
- signal_name: SHOW_UPGRADE
|
|
114
|
+
condition: "{{ context.show_upsell }}"
|
|
115
|
+
- signal_name: CONTINUE_FREE
|
|
116
|
+
condition: "{{ not context.show_upsell }}"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### How It Works
|
|
120
|
+
|
|
121
|
+
1. `UserTierCheck` receives `START` and evaluates `context.user_tier`
|
|
122
|
+
2. Depending on the tier, it emits either `PREMIUM_PATH` or `FREE_PATH`
|
|
123
|
+
3. The corresponding router (`PremiumFeatureRouter` or `FreeUserRouter`) activates
|
|
124
|
+
4. That router evaluates its own conditions and emits the final signal
|
|
125
|
+
|
|
126
|
+
### Why This Matters
|
|
127
|
+
|
|
128
|
+
- **Modularity**: Each router handles one decision. Easy to test, easy to modify.
|
|
129
|
+
- **Composition**: Add new branches by adding new routers—no need to modify existing ones.
|
|
130
|
+
- **Visibility**: The workflow YAML *is* the flowchart. No hidden logic in code.
|
|
131
|
+
|
|
132
|
+
## The Three Core Node Types
|
|
133
|
+
|
|
134
|
+
You now know the three core node types in SOE:
|
|
135
|
+
|
|
136
|
+
| Node | Purpose | Does Work? |
|
|
137
|
+
|------|---------|-----------|
|
|
138
|
+
| **Tool** | Execute Python functions | ✅ Yes |
|
|
139
|
+
| **LLM** | Call language models | ✅ Yes |
|
|
140
|
+
| **Router** | Route signals based on context | ❌ No (pure routing) |
|
|
141
|
+
|
|
142
|
+
With just these three nodes, you can build remarkably sophisticated workflows—including custom agent patterns, chain-of-thought reasoning, and more. The next chapter shows you how.
|
|
143
|
+
|
|
144
|
+
## Next Steps
|
|
145
|
+
|
|
146
|
+
Now that you understand the three core node types, let's combine them into powerful patterns with [Building Custom Workflows](guide_04_patterns.md) →
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
|
|
2
|
+
# SOE Guide: Chapter 4 - Building Custom Workflows
|
|
3
|
+
|
|
4
|
+
## The Three Core Node Types
|
|
5
|
+
|
|
6
|
+
You now know SOE's three core node types:
|
|
7
|
+
|
|
8
|
+
| Node | What It Does | Key Capability |
|
|
9
|
+
|------|--------------|----------------|
|
|
10
|
+
| **Tool** | Executes Python functions | Real-world actions |
|
|
11
|
+
| **LLM** | Calls language models | Intelligence & generation |
|
|
12
|
+
| **Router** | Routes based on context | Control flow & branching |
|
|
13
|
+
|
|
14
|
+
These three nodes are **all you need** to build sophisticated AI workflows. This chapter shows you how to combine them into powerful patterns—including building your own agent loops from scratch.
|
|
15
|
+
|
|
16
|
+
## Why Build Custom Workflows?
|
|
17
|
+
|
|
18
|
+
The built-in **Agent Node** (covered in the next chapter) provides a convenient ReAct loop. But sometimes you need:
|
|
19
|
+
|
|
20
|
+
- **Custom reasoning patterns** (chain-of-thought, tree-of-thought, metacognition)
|
|
21
|
+
- **Fine-grained control** over each step
|
|
22
|
+
- **Hybrid logic** mixing deterministic and AI-driven decisions
|
|
23
|
+
- **Domain-specific agent architectures**
|
|
24
|
+
|
|
25
|
+
With these three core nodes, you can build any pattern—the Agent Node is just one opinionated implementation.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Pattern 1: Chain of Thought
|
|
30
|
+
|
|
31
|
+
**The Pattern**: Break complex reasoning into explicit steps, where each LLM call builds on the previous one.
|
|
32
|
+
|
|
33
|
+
### The Workflow
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
example_workflow:
|
|
37
|
+
Understand:
|
|
38
|
+
node_type: llm
|
|
39
|
+
event_triggers: [START]
|
|
40
|
+
prompt: "Analyze this problem and restate it in your own words: {{ context.problem }}"
|
|
41
|
+
output_field: understanding
|
|
42
|
+
event_emissions:
|
|
43
|
+
- signal_name: UNDERSTOOD
|
|
44
|
+
|
|
45
|
+
Plan:
|
|
46
|
+
node_type: llm
|
|
47
|
+
event_triggers: [UNDERSTOOD]
|
|
48
|
+
prompt: |
|
|
49
|
+
Based on your understanding: {{ context.understanding }}
|
|
50
|
+
|
|
51
|
+
Create a step-by-step plan to solve this problem.
|
|
52
|
+
output_field: plan
|
|
53
|
+
event_emissions:
|
|
54
|
+
- signal_name: PLANNED
|
|
55
|
+
|
|
56
|
+
Execute:
|
|
57
|
+
node_type: llm
|
|
58
|
+
event_triggers: [PLANNED]
|
|
59
|
+
prompt: |
|
|
60
|
+
Understanding: {{ context.understanding }}
|
|
61
|
+
Plan: {{ context.plan }}
|
|
62
|
+
|
|
63
|
+
Now execute the plan and provide the final answer.
|
|
64
|
+
output_field: answer
|
|
65
|
+
event_emissions:
|
|
66
|
+
- signal_name: COMPLETE
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### How It Works
|
|
70
|
+
|
|
71
|
+
1. **Understand**: LLM analyzes and restates the problem
|
|
72
|
+
2. **Plan**: LLM creates a step-by-step plan based on its understanding
|
|
73
|
+
3. **Execute**: LLM generates the final answer using the plan
|
|
74
|
+
|
|
75
|
+
Each step stores its output in context, and the next step reads it.
|
|
76
|
+
|
|
77
|
+
### Why This Pattern?
|
|
78
|
+
|
|
79
|
+
- **Transparency**: Each reasoning step is visible and inspectable
|
|
80
|
+
- **Debuggability**: If the answer is wrong, you can see exactly where reasoning failed
|
|
81
|
+
- **Control**: You can add validation routers between steps
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Pattern 2: Custom ReAct Loop
|
|
86
|
+
|
|
87
|
+
**The Pattern**: Build your own Reasoning + Acting loop using the three core node types.
|
|
88
|
+
|
|
89
|
+
### The Workflow
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
example_workflow:
|
|
93
|
+
Reason:
|
|
94
|
+
node_type: llm
|
|
95
|
+
event_triggers: [START, TOOL_RESULT]
|
|
96
|
+
prompt: |
|
|
97
|
+
Task: {{ context.task }}
|
|
98
|
+
{% if context.tool_results %}
|
|
99
|
+
Previous tool results: {{ context.tool_results }}
|
|
100
|
+
{% endif %}
|
|
101
|
+
|
|
102
|
+
Decide what to do next. If you need to use a tool, output:
|
|
103
|
+
{"action": "use_tool", "tool": "tool_name", "args": {...}}
|
|
104
|
+
|
|
105
|
+
If you have the final answer, output:
|
|
106
|
+
{"action": "finish", "answer": "your answer"}
|
|
107
|
+
output_field: decision
|
|
108
|
+
event_emissions:
|
|
109
|
+
- signal_name: DECIDED
|
|
110
|
+
|
|
111
|
+
Route:
|
|
112
|
+
node_type: router
|
|
113
|
+
event_triggers: [DECIDED]
|
|
114
|
+
event_emissions:
|
|
115
|
+
- signal_name: USE_TOOL
|
|
116
|
+
condition: "{{ context.decision.action == 'use_tool' }}"
|
|
117
|
+
- signal_name: FINISH
|
|
118
|
+
condition: "{{ context.decision.action == 'finish' }}"
|
|
119
|
+
|
|
120
|
+
Act:
|
|
121
|
+
node_type: tool
|
|
122
|
+
event_triggers: [USE_TOOL]
|
|
123
|
+
tool_name: dynamic_tool
|
|
124
|
+
context_parameter_field: decision
|
|
125
|
+
output_field: tool_results
|
|
126
|
+
event_emissions:
|
|
127
|
+
- signal_name: TOOL_RESULT
|
|
128
|
+
|
|
129
|
+
Complete:
|
|
130
|
+
node_type: router
|
|
131
|
+
event_triggers: [FINISH]
|
|
132
|
+
event_emissions:
|
|
133
|
+
- signal_name: TASK_COMPLETE
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### How It Works
|
|
137
|
+
|
|
138
|
+
1. **Reason**: LLM analyzes the situation and decides what to do
|
|
139
|
+
2. **Route**: Router checks if LLM wants to use a tool or finish
|
|
140
|
+
3. **Act**: If tool needed, execute it and loop back to Reason
|
|
141
|
+
4. **Complete**: When done, emit final signal
|
|
142
|
+
|
|
143
|
+
### The Loop Structure
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
┌──────────────────────────────────────────────────────┐
|
|
147
|
+
│ │
|
|
148
|
+
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
|
149
|
+
│ │ Reason │───▶│ Route │───▶│ Act │──────────┘
|
|
150
|
+
│ │ (LLM) │ │(Router) │ │ (Tool) │
|
|
151
|
+
│ └─────────┘ └────┬────┘ └─────────┘
|
|
152
|
+
│ │
|
|
153
|
+
│ ▼
|
|
154
|
+
│ ┌─────────┐
|
|
155
|
+
│ │Complete │
|
|
156
|
+
│ │(Router) │
|
|
157
|
+
│ └─────────┘
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
This is exactly what the Agent Node does internally—but now you control every piece.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Pattern 3: Metacognition (Self-Reflection)
|
|
165
|
+
|
|
166
|
+
**The Pattern**: Have the LLM review and critique its own output before finalizing.
|
|
167
|
+
|
|
168
|
+
### The Workflow
|
|
169
|
+
|
|
170
|
+
```yaml
|
|
171
|
+
example_workflow:
|
|
172
|
+
Generate:
|
|
173
|
+
node_type: llm
|
|
174
|
+
event_triggers: [START]
|
|
175
|
+
prompt: "Write a response to: {{ context.request }}"
|
|
176
|
+
output_field: draft
|
|
177
|
+
event_emissions:
|
|
178
|
+
- signal_name: DRAFT_READY
|
|
179
|
+
|
|
180
|
+
Critique:
|
|
181
|
+
node_type: llm
|
|
182
|
+
event_triggers: [DRAFT_READY]
|
|
183
|
+
prompt: |
|
|
184
|
+
Review this response for errors, unclear language, or improvements:
|
|
185
|
+
{{ context.draft }}
|
|
186
|
+
|
|
187
|
+
Output JSON with needs_revision (true or false) and critique fields.
|
|
188
|
+
output_field: review
|
|
189
|
+
event_emissions:
|
|
190
|
+
- signal_name: REVIEWED
|
|
191
|
+
|
|
192
|
+
CheckRevision:
|
|
193
|
+
node_type: router
|
|
194
|
+
event_triggers: [REVIEWED]
|
|
195
|
+
event_emissions:
|
|
196
|
+
- signal_name: REVISE
|
|
197
|
+
condition: "{{ context.review.needs_revision == true }}"
|
|
198
|
+
- signal_name: FINALIZE
|
|
199
|
+
condition: "{{ context.review.needs_revision == false }}"
|
|
200
|
+
|
|
201
|
+
Refine:
|
|
202
|
+
node_type: llm
|
|
203
|
+
event_triggers: [REVISE]
|
|
204
|
+
prompt: |
|
|
205
|
+
Original: {{ context.draft }}
|
|
206
|
+
Critique: {{ context.review.critique }}
|
|
207
|
+
|
|
208
|
+
Write an improved version addressing the feedback.
|
|
209
|
+
output_field: final_response
|
|
210
|
+
event_emissions:
|
|
211
|
+
- signal_name: COMPLETE
|
|
212
|
+
|
|
213
|
+
AcceptDraft:
|
|
214
|
+
node_type: router
|
|
215
|
+
event_triggers: [FINALIZE]
|
|
216
|
+
event_emissions:
|
|
217
|
+
- signal_name: COMPLETE
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### How It Works
|
|
221
|
+
|
|
222
|
+
1. **Generate**: LLM produces an initial response
|
|
223
|
+
2. **Critique**: A second LLM call reviews the response for errors or improvements
|
|
224
|
+
3. **Route**: Check if revision is needed
|
|
225
|
+
4. **Refine** (if needed): Generate improved response based on critique
|
|
226
|
+
5. **Finalize**: Output the best version
|
|
227
|
+
|
|
228
|
+
### Why This Pattern?
|
|
229
|
+
|
|
230
|
+
- **Quality improvement**: Catches errors the first pass missed
|
|
231
|
+
- **Self-correction**: The model identifies its own weaknesses
|
|
232
|
+
- **Controllable iteration**: You decide when to stop refining
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Pattern 4: Parallel Analysis with Voting
|
|
237
|
+
|
|
238
|
+
**The Pattern**: Run multiple LLM analyses in parallel, then aggregate results.
|
|
239
|
+
|
|
240
|
+
### The Workflow
|
|
241
|
+
|
|
242
|
+
```yaml
|
|
243
|
+
example_workflow:
|
|
244
|
+
FanOut:
|
|
245
|
+
node_type: router
|
|
246
|
+
event_triggers: [START]
|
|
247
|
+
event_emissions:
|
|
248
|
+
- signal_name: ANALYZE_SAFETY
|
|
249
|
+
- signal_name: ANALYZE_QUALITY
|
|
250
|
+
- signal_name: ANALYZE_RELEVANCE
|
|
251
|
+
|
|
252
|
+
SafetyCheck:
|
|
253
|
+
node_type: llm
|
|
254
|
+
event_triggers: [ANALYZE_SAFETY]
|
|
255
|
+
prompt: |
|
|
256
|
+
Check if this content is safe: {{ context.content }}
|
|
257
|
+
Return JSON with safe (boolean) and reason (string) fields.
|
|
258
|
+
output_field: safety_result
|
|
259
|
+
event_emissions:
|
|
260
|
+
- signal_name: SAFETY_DONE
|
|
261
|
+
|
|
262
|
+
QualityCheck:
|
|
263
|
+
node_type: llm
|
|
264
|
+
event_triggers: [ANALYZE_QUALITY]
|
|
265
|
+
prompt: |
|
|
266
|
+
Rate the quality of this content: {{ context.content }}
|
|
267
|
+
Return JSON with score (1-10) and feedback (string) fields.
|
|
268
|
+
output_field: quality_result
|
|
269
|
+
event_emissions:
|
|
270
|
+
- signal_name: QUALITY_DONE
|
|
271
|
+
|
|
272
|
+
RelevanceCheck:
|
|
273
|
+
node_type: llm
|
|
274
|
+
event_triggers: [ANALYZE_RELEVANCE]
|
|
275
|
+
prompt: |
|
|
276
|
+
Check relevance to topic '{{ context.topic }}': {{ context.content }}
|
|
277
|
+
Return JSON with relevant (boolean) field.
|
|
278
|
+
output_field: relevance_result
|
|
279
|
+
event_emissions:
|
|
280
|
+
- signal_name: RELEVANCE_DONE
|
|
281
|
+
|
|
282
|
+
Aggregate:
|
|
283
|
+
node_type: tool
|
|
284
|
+
event_triggers: [SAFETY_DONE, QUALITY_DONE, RELEVANCE_DONE]
|
|
285
|
+
tool_name: aggregate_votes
|
|
286
|
+
context_parameter_field: vote_params
|
|
287
|
+
output_field: final_decision
|
|
288
|
+
event_emissions:
|
|
289
|
+
- signal_name: APPROVED
|
|
290
|
+
condition: "{{ result.approved == true }}"
|
|
291
|
+
- signal_name: REJECTED
|
|
292
|
+
condition: "{{ result.approved == false }}"
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### How It Works
|
|
296
|
+
|
|
297
|
+
1. **Fan-Out**: Router emits multiple signals simultaneously
|
|
298
|
+
2. **Parallel Analysis**: Multiple LLM nodes run independently
|
|
299
|
+
3. **Aggregate**: Tool collects all analyses and determines consensus
|
|
300
|
+
4. **Route Result**: Based on the aggregated vote
|
|
301
|
+
|
|
302
|
+
### Why This Pattern?
|
|
303
|
+
|
|
304
|
+
- **Diversity of perspective**: Different prompts catch different issues
|
|
305
|
+
- **Robustness**: Single LLM errors are outvoted
|
|
306
|
+
- **Speed**: Parallel execution (if your infrastructure supports it)
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Pattern 5: Iterative Refinement with Tools
|
|
311
|
+
|
|
312
|
+
**The Pattern**: LLM generates, tool validates, loop until valid.
|
|
313
|
+
|
|
314
|
+
### The Workflow
|
|
315
|
+
|
|
316
|
+
```yaml
|
|
317
|
+
example_workflow:
|
|
318
|
+
Generate:
|
|
319
|
+
node_type: llm
|
|
320
|
+
event_triggers: [START, RETRY]
|
|
321
|
+
prompt: |
|
|
322
|
+
Generate Python code for: {{ context.task }}
|
|
323
|
+
{% if context.errors %}
|
|
324
|
+
Previous attempt had these errors: {{ context.errors }}
|
|
325
|
+
Fix them in this attempt.
|
|
326
|
+
{% endif %}
|
|
327
|
+
output_field: code
|
|
328
|
+
event_emissions:
|
|
329
|
+
- signal_name: CODE_GENERATED
|
|
330
|
+
|
|
331
|
+
Validate:
|
|
332
|
+
node_type: tool
|
|
333
|
+
event_triggers: [CODE_GENERATED]
|
|
334
|
+
tool_name: lint_code
|
|
335
|
+
context_parameter_field: code
|
|
336
|
+
output_field: validation
|
|
337
|
+
event_emissions:
|
|
338
|
+
- signal_name: VALID
|
|
339
|
+
condition: "{{ result.errors|length == 0 }}"
|
|
340
|
+
- signal_name: INVALID
|
|
341
|
+
condition: "{{ result.errors|length > 0 }}"
|
|
342
|
+
|
|
343
|
+
PrepareRetry:
|
|
344
|
+
node_type: router
|
|
345
|
+
event_triggers: [INVALID]
|
|
346
|
+
event_emissions:
|
|
347
|
+
- signal_name: RETRY
|
|
348
|
+
|
|
349
|
+
Complete:
|
|
350
|
+
node_type: router
|
|
351
|
+
event_triggers: [VALID]
|
|
352
|
+
event_emissions:
|
|
353
|
+
- signal_name: CODE_COMPLETE
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### How It Works
|
|
357
|
+
|
|
358
|
+
1. **Generate**: LLM creates code/content
|
|
359
|
+
2. **Validate**: Tool runs linter, tests, or other validation
|
|
360
|
+
3. **Route**: Check validation result
|
|
361
|
+
4. **Loop or Complete**: If invalid, inject errors and regenerate
|
|
362
|
+
|
|
363
|
+
### The Loop Structure
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
┌────────────────────────────────────────────────┐
|
|
367
|
+
│ │
|
|
368
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
369
|
+
│ │ Generate │───▶│ Validate │───▶│ Route │─┘
|
|
370
|
+
│ │ (LLM) │ │ (Tool) │ │ (Router) │
|
|
371
|
+
│ └──────────┘ └──────────┘ └────┬─────┘
|
|
372
|
+
│ │
|
|
373
|
+
│ ▼
|
|
374
|
+
│ ┌──────────┐
|
|
375
|
+
│ │ Complete │
|
|
376
|
+
│ └──────────┘
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Pattern 6: Hierarchical Task Decomposition
|
|
382
|
+
|
|
383
|
+
**The Pattern**: Break complex tasks into subtasks, solve each, then synthesize.
|
|
384
|
+
|
|
385
|
+
### The Workflow
|
|
386
|
+
|
|
387
|
+
```yaml
|
|
388
|
+
example_workflow:
|
|
389
|
+
Decompose:
|
|
390
|
+
node_type: llm
|
|
391
|
+
event_triggers: [START]
|
|
392
|
+
prompt: |
|
|
393
|
+
Break this task into subtasks: {{ context.task }}
|
|
394
|
+
Output JSON with subtasks array and types array (research, code, writing).
|
|
395
|
+
output_field: breakdown
|
|
396
|
+
event_emissions:
|
|
397
|
+
- signal_name: DECOMPOSED
|
|
398
|
+
|
|
399
|
+
RouteSubtasks:
|
|
400
|
+
node_type: router
|
|
401
|
+
event_triggers: [DECOMPOSED]
|
|
402
|
+
event_emissions:
|
|
403
|
+
- signal_name: DO_RESEARCH
|
|
404
|
+
condition: "{{ 'research' in context.breakdown.types }}"
|
|
405
|
+
- signal_name: DO_CODING
|
|
406
|
+
condition: "{{ 'code' in context.breakdown.types }}"
|
|
407
|
+
- signal_name: DO_WRITING
|
|
408
|
+
condition: "{{ 'writing' in context.breakdown.types }}"
|
|
409
|
+
|
|
410
|
+
ResearchSubtask:
|
|
411
|
+
node_type: llm
|
|
412
|
+
event_triggers: [DO_RESEARCH]
|
|
413
|
+
prompt: "Research: {{ context.breakdown.subtasks | selectattr('type', 'equalto', 'research') | list }}"
|
|
414
|
+
output_field: research_result
|
|
415
|
+
event_emissions:
|
|
416
|
+
- signal_name: SUBTASK_DONE
|
|
417
|
+
|
|
418
|
+
CodingSubtask:
|
|
419
|
+
node_type: llm
|
|
420
|
+
event_triggers: [DO_CODING]
|
|
421
|
+
prompt: "Code: {{ context.breakdown.subtasks | selectattr('type', 'equalto', 'code') | list }}"
|
|
422
|
+
output_field: code_result
|
|
423
|
+
event_emissions:
|
|
424
|
+
- signal_name: SUBTASK_DONE
|
|
425
|
+
|
|
426
|
+
Synthesize:
|
|
427
|
+
node_type: llm
|
|
428
|
+
event_triggers: [SUBTASK_DONE]
|
|
429
|
+
prompt: |
|
|
430
|
+
Combine these results into a final answer:
|
|
431
|
+
Research: {{ context.research_result }}
|
|
432
|
+
Code: {{ context.code_result }}
|
|
433
|
+
output_field: final_answer
|
|
434
|
+
event_emissions:
|
|
435
|
+
- signal_name: COMPLETE
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### How It Works
|
|
439
|
+
|
|
440
|
+
1. **Decompose**: LLM breaks the task into subtasks
|
|
441
|
+
2. **Fan-Out**: Router emits signals for each subtask type
|
|
442
|
+
3. **Solve**: Specialized nodes handle each subtask
|
|
443
|
+
4. **Synthesize**: Combine subtask results into final answer
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## Combining Patterns
|
|
448
|
+
|
|
449
|
+
The real power comes from combining patterns. For example:
|
|
450
|
+
|
|
451
|
+
- **Chain-of-Thought + Metacognition**: Reason step-by-step, then self-review
|
|
452
|
+
- **Custom ReAct + Iterative Refinement**: Agent loop with validation gates
|
|
453
|
+
- **Parallel Voting + Hierarchical Decomposition**: Ensemble of specialized agents
|
|
454
|
+
|
|
455
|
+
Since everything is YAML, you can compose patterns freely.
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## When to Use Custom Workflows vs Agent Node
|
|
460
|
+
|
|
461
|
+
| Use Custom Workflows When... | Use Agent Node When... |
|
|
462
|
+
|------------------------------|------------------------|
|
|
463
|
+
| You need non-standard reasoning patterns | Standard ReAct loop is sufficient |
|
|
464
|
+
| You want explicit control over each step | You want hands-off tool-using agent |
|
|
465
|
+
| You're debugging or iterating on agent logic | You need quick prototyping |
|
|
466
|
+
| You need deterministic checkpoints | Tool selection is the main complexity |
|
|
467
|
+
| You're building production systems with audit requirements | Development speed matters most |
|
|
468
|
+
|
|
469
|
+
The Agent Node is a **convenience**—it encapsulates a common pattern. These custom workflows show you can build anything.
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## Next Steps
|
|
474
|
+
|
|
475
|
+
Now that you can build custom agent patterns, see how the built-in [Agent Node](guide_05_agent.md) encapsulates the ReAct pattern for convenience →
|