soe-ai 0.1.0__tar.gz → 0.1.2__tar.gz
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_ai-0.1.0 → soe_ai-0.1.2}/PKG-INFO +72 -9
- {soe_ai-0.1.0 → soe_ai-0.1.2}/README.md +70 -7
- {soe_ai-0.1.0 → soe_ai-0.1.2}/pyproject.toml +6 -5
- {soe_ai-0.1.0 → soe_ai-0.1.2}/soe/broker.py +4 -5
- soe_ai-0.1.2/soe/builtin_tools/__init__.py +39 -0
- soe_ai-0.1.2/soe/builtin_tools/soe_add_signal.py +82 -0
- soe_ai-0.1.2/soe/builtin_tools/soe_call_tool.py +111 -0
- soe_ai-0.1.2/soe/builtin_tools/soe_copy_context.py +80 -0
- soe_ai-0.1.2/soe/builtin_tools/soe_explore_docs.py +290 -0
- soe_ai-0.1.2/soe/builtin_tools/soe_get_available_tools.py +42 -0
- soe_ai-0.1.2/soe/builtin_tools/soe_get_context.py +50 -0
- soe_ai-0.1.2/soe/builtin_tools/soe_get_workflows.py +63 -0
- soe_ai-0.1.2/soe/builtin_tools/soe_inject_node.py +86 -0
- soe_ai-0.1.2/soe/builtin_tools/soe_inject_workflow.py +105 -0
- soe_ai-0.1.2/soe/builtin_tools/soe_list_contexts.py +73 -0
- soe_ai-0.1.2/soe/builtin_tools/soe_remove_node.py +72 -0
- soe_ai-0.1.2/soe/builtin_tools/soe_remove_workflow.py +62 -0
- soe_ai-0.1.2/soe/builtin_tools/soe_update_context.py +54 -0
- soe_ai-0.1.2/soe/docs/_config.yml +10 -0
- soe_ai-0.1.2/soe/docs/advanced_patterns/guide_fanout_and_aggregations.md +318 -0
- soe_ai-0.1.2/soe/docs/advanced_patterns/guide_inheritance.md +435 -0
- soe_ai-0.1.2/soe/docs/advanced_patterns/hybrid_intelligence.md +237 -0
- soe_ai-0.1.2/soe/docs/advanced_patterns/index.md +49 -0
- soe_ai-0.1.2/soe/docs/advanced_patterns/operational.md +781 -0
- soe_ai-0.1.2/soe/docs/advanced_patterns/self_evolving_workflows.md +385 -0
- soe_ai-0.1.2/soe/docs/advanced_patterns/swarm_intelligence.md +211 -0
- soe_ai-0.1.2/soe/docs/builtins/context.md +164 -0
- soe_ai-0.1.2/soe/docs/builtins/explore_docs.md +135 -0
- soe_ai-0.1.2/soe/docs/builtins/tools.md +164 -0
- soe_ai-0.1.2/soe/docs/builtins/workflows.md +199 -0
- soe_ai-0.1.2/soe/docs/guide_00_getting_started.md +341 -0
- soe_ai-0.1.2/soe/docs/guide_01_tool.md +206 -0
- soe_ai-0.1.2/soe/docs/guide_02_llm.md +143 -0
- soe_ai-0.1.2/soe/docs/guide_03_router.md +146 -0
- soe_ai-0.1.2/soe/docs/guide_04_patterns.md +475 -0
- soe_ai-0.1.2/soe/docs/guide_05_agent.md +159 -0
- soe_ai-0.1.2/soe/docs/guide_06_schema.md +397 -0
- soe_ai-0.1.2/soe/docs/guide_07_identity.md +540 -0
- soe_ai-0.1.2/soe/docs/guide_08_child.md +612 -0
- soe_ai-0.1.2/soe/docs/guide_09_ecosystem.md +690 -0
- soe_ai-0.1.2/soe/docs/guide_10_infrastructure.md +427 -0
- soe_ai-0.1.2/soe/docs/guide_11_builtins.md +118 -0
- soe_ai-0.1.2/soe/docs/index.md +104 -0
- soe_ai-0.1.2/soe/docs/primitives/backends.md +281 -0
- soe_ai-0.1.2/soe/docs/primitives/context.md +256 -0
- soe_ai-0.1.2/soe/docs/primitives/node_reference.md +259 -0
- soe_ai-0.1.2/soe/docs/primitives/primitives.md +331 -0
- soe_ai-0.1.2/soe/docs/primitives/signals.md +865 -0
- soe_ai-0.1.2/soe/docs_index.py +2 -0
- {soe_ai-0.1.0 → soe_ai-0.1.2}/soe/init.py +2 -2
- soe_ai-0.1.2/soe/lib/__init__.py +0 -0
- soe_ai-0.1.2/soe/lib/child_context.py +46 -0
- soe_ai-0.1.2/soe/lib/context_fields.py +51 -0
- soe_ai-0.1.2/soe/lib/inheritance.py +172 -0
- soe_ai-0.1.2/soe/lib/jinja_render.py +113 -0
- soe_ai-0.1.2/soe/lib/operational.py +51 -0
- soe_ai-0.1.2/soe/lib/parent_sync.py +71 -0
- soe_ai-0.1.2/soe/lib/register_event.py +75 -0
- soe_ai-0.1.2/soe/lib/schema_validation.py +134 -0
- soe_ai-0.1.2/soe/lib/yaml_parser.py +14 -0
- soe_ai-0.1.2/soe/local_backends/__init__.py +18 -0
- soe_ai-0.1.2/soe/local_backends/factory.py +124 -0
- soe_ai-0.1.2/soe/local_backends/in_memory/context.py +38 -0
- soe_ai-0.1.2/soe/local_backends/in_memory/conversation_history.py +60 -0
- soe_ai-0.1.2/soe/local_backends/in_memory/identity.py +52 -0
- soe_ai-0.1.2/soe/local_backends/in_memory/schema.py +40 -0
- soe_ai-0.1.2/soe/local_backends/in_memory/telemetry.py +38 -0
- soe_ai-0.1.2/soe/local_backends/in_memory/workflow.py +33 -0
- soe_ai-0.1.2/soe/local_backends/storage/context.py +57 -0
- soe_ai-0.1.2/soe/local_backends/storage/conversation_history.py +82 -0
- soe_ai-0.1.2/soe/local_backends/storage/identity.py +118 -0
- soe_ai-0.1.2/soe/local_backends/storage/schema.py +96 -0
- soe_ai-0.1.2/soe/local_backends/storage/telemetry.py +72 -0
- soe_ai-0.1.2/soe/local_backends/storage/workflow.py +56 -0
- soe_ai-0.1.2/soe/nodes/__init__.py +13 -0
- soe_ai-0.1.2/soe/nodes/agent/__init__.py +10 -0
- soe_ai-0.1.2/soe/nodes/agent/factory.py +134 -0
- soe_ai-0.1.2/soe/nodes/agent/lib/loop_handlers.py +150 -0
- soe_ai-0.1.2/soe/nodes/agent/lib/loop_state.py +157 -0
- soe_ai-0.1.2/soe/nodes/agent/lib/prompts.py +65 -0
- soe_ai-0.1.2/soe/nodes/agent/lib/tools.py +35 -0
- soe_ai-0.1.2/soe/nodes/agent/stages/__init__.py +12 -0
- soe_ai-0.1.2/soe/nodes/agent/stages/parameter.py +37 -0
- soe_ai-0.1.2/soe/nodes/agent/stages/response.py +54 -0
- soe_ai-0.1.2/soe/nodes/agent/stages/router.py +37 -0
- soe_ai-0.1.2/soe/nodes/agent/state.py +111 -0
- soe_ai-0.1.2/soe/nodes/agent/types.py +66 -0
- soe_ai-0.1.2/soe/nodes/agent/validation/__init__.py +11 -0
- soe_ai-0.1.2/soe/nodes/agent/validation/config.py +95 -0
- soe_ai-0.1.2/soe/nodes/agent/validation/operational.py +24 -0
- soe_ai-0.1.2/soe/nodes/child/__init__.py +3 -0
- soe_ai-0.1.2/soe/nodes/child/factory.py +61 -0
- soe_ai-0.1.2/soe/nodes/child/state.py +59 -0
- soe_ai-0.1.2/soe/nodes/child/validation/__init__.py +11 -0
- soe_ai-0.1.2/soe/nodes/child/validation/config.py +126 -0
- soe_ai-0.1.2/soe/nodes/child/validation/operational.py +28 -0
- soe_ai-0.1.2/soe/nodes/lib/conditions.py +71 -0
- soe_ai-0.1.2/soe/nodes/lib/context.py +24 -0
- soe_ai-0.1.2/soe/nodes/lib/conversation_history.py +77 -0
- soe_ai-0.1.2/soe/nodes/lib/identity.py +64 -0
- soe_ai-0.1.2/soe/nodes/lib/llm_resolver.py +142 -0
- soe_ai-0.1.2/soe/nodes/lib/output.py +68 -0
- soe_ai-0.1.2/soe/nodes/lib/response_builder.py +91 -0
- soe_ai-0.1.2/soe/nodes/lib/signal_emission.py +79 -0
- soe_ai-0.1.2/soe/nodes/lib/signals.py +54 -0
- soe_ai-0.1.2/soe/nodes/lib/tools.py +100 -0
- soe_ai-0.1.2/soe/nodes/llm/__init__.py +7 -0
- soe_ai-0.1.2/soe/nodes/llm/factory.py +103 -0
- soe_ai-0.1.2/soe/nodes/llm/state.py +76 -0
- soe_ai-0.1.2/soe/nodes/llm/types.py +12 -0
- soe_ai-0.1.2/soe/nodes/llm/validation/__init__.py +11 -0
- soe_ai-0.1.2/soe/nodes/llm/validation/config.py +89 -0
- soe_ai-0.1.2/soe/nodes/llm/validation/operational.py +23 -0
- soe_ai-0.1.2/soe/nodes/router/__init__.py +3 -0
- soe_ai-0.1.2/soe/nodes/router/factory.py +37 -0
- soe_ai-0.1.2/soe/nodes/router/state.py +32 -0
- soe_ai-0.1.2/soe/nodes/router/validation/__init__.py +11 -0
- soe_ai-0.1.2/soe/nodes/router/validation/config.py +58 -0
- soe_ai-0.1.2/soe/nodes/router/validation/operational.py +16 -0
- soe_ai-0.1.2/soe/nodes/tool/factory.py +66 -0
- soe_ai-0.1.2/soe/nodes/tool/lib/__init__.py +11 -0
- soe_ai-0.1.2/soe/nodes/tool/lib/conditions.py +35 -0
- soe_ai-0.1.2/soe/nodes/tool/lib/failure.py +28 -0
- soe_ai-0.1.2/soe/nodes/tool/lib/parameters.py +67 -0
- soe_ai-0.1.2/soe/nodes/tool/state.py +66 -0
- soe_ai-0.1.2/soe/nodes/tool/types.py +27 -0
- soe_ai-0.1.2/soe/nodes/tool/validation/__init__.py +15 -0
- soe_ai-0.1.2/soe/nodes/tool/validation/config.py +132 -0
- soe_ai-0.1.2/soe/nodes/tool/validation/operational.py +16 -0
- {soe_ai-0.1.0 → soe_ai-0.1.2}/soe/types.py +40 -28
- soe_ai-0.1.2/soe/validation/__init__.py +18 -0
- soe_ai-0.1.2/soe/validation/config.py +195 -0
- soe_ai-0.1.2/soe/validation/jinja.py +54 -0
- soe_ai-0.1.2/soe/validation/operational.py +110 -0
- {soe_ai-0.1.0 → soe_ai-0.1.2}/soe_ai.egg-info/PKG-INFO +72 -9
- soe_ai-0.1.2/soe_ai.egg-info/SOURCES.txt +142 -0
- {soe_ai-0.1.0 → soe_ai-0.1.2}/tests/test_local_storage_backends.py +3 -47
- soe_ai-0.1.0/soe/docs_index.py +0 -2
- soe_ai-0.1.0/soe/validation.py +0 -8
- soe_ai-0.1.0/soe_ai.egg-info/SOURCES.txt +0 -16
- {soe_ai-0.1.0 → soe_ai-0.1.2}/LICENSE +0 -0
- {soe_ai-0.1.0 → soe_ai-0.1.2}/setup.cfg +0 -0
- {soe_ai-0.1.0 → soe_ai-0.1.2}/soe/__init__.py +0 -0
- {soe_ai-0.1.0 → soe_ai-0.1.2}/soe_ai.egg-info/dependency_links.txt +0 -0
- {soe_ai-0.1.0 → soe_ai-0.1.2}/soe_ai.egg-info/requires.txt +0 -0
- {soe_ai-0.1.0 → soe_ai-0.1.2}/soe_ai.egg-info/top_level.txt +0 -0
- {soe_ai-0.1.0 → soe_ai-0.1.2}/tests/test_validation_errors.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: soe-ai
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Signal-driven Orchestration Engine - Agent orchestration with event-driven workflow engine
|
|
5
|
-
Author-email: Pedro Garcia <
|
|
5
|
+
Author-email: Pedro Garcia <pgarcia14180@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/pgarcia14180/soe
|
|
8
8
|
Project-URL: Documentation, https://github.com/pgarcia14180/soe/tree/master/docs
|
|
@@ -91,12 +91,44 @@ All workflow state flows through **context**—a shared dictionary accessible vi
|
|
|
91
91
|
- LLM prompts can interpolate any context field
|
|
92
92
|
- No hidden state—everything is inspectable
|
|
93
93
|
|
|
94
|
-
### 3. Deterministic
|
|
95
|
-
|
|
94
|
+
### 3. Purely Deterministic or Hybrid Agentic
|
|
95
|
+
SOE is a complete orchestration solution. You can use it as a purely deterministic engine for standard business logic, or mix in LLM-driven "Agentic" behavior.
|
|
96
|
+
- **Deterministic**: Use `router` and `tool` nodes for 100% predictable workflows.
|
|
97
|
+
- **Agentic**: Add `llm` and `agent` nodes for creative, reasoning-based tasks.
|
|
98
|
+
You get the safety of code with the flexibility of AI in a single, unified system.
|
|
96
99
|
|
|
97
100
|
### 4. Portable
|
|
98
101
|
Workflows are YAML. Run them locally, in CI, in production. Extract them, version them, share them.
|
|
99
102
|
|
|
103
|
+
### 5. Self-Evolving
|
|
104
|
+
Workflows can modify themselves at runtime. Built-in tools like `inject_workflow`, `inject_node_configuration`, and `add_signal` allow agents to:
|
|
105
|
+
- Create new workflows dynamically
|
|
106
|
+
- Add or modify nodes in existing workflows
|
|
107
|
+
- Update signal routing on the fly
|
|
108
|
+
|
|
109
|
+
This enables **meta-programming**: an AI system that can extend its own capabilities without human intervention.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## What SOE Unlocks
|
|
114
|
+
|
|
115
|
+
SOE is a **Protocol for Intelligence** that unlocks new forms of intelligent behavior:
|
|
116
|
+
|
|
117
|
+
### Self-Evolving Intelligence
|
|
118
|
+
AI systems that can rewrite and improve themselves at runtime - the ultimate evolution of software.
|
|
119
|
+
|
|
120
|
+
### Swarm Intelligence
|
|
121
|
+
Efficient collective decision-making among multiple agents through signal-based consensus.
|
|
122
|
+
|
|
123
|
+
### Hybrid Intelligence
|
|
124
|
+
Seamless combination of deterministic logic and AI creativity with programmatic safety rails.
|
|
125
|
+
|
|
126
|
+
### Fractal Intelligence
|
|
127
|
+
Hierarchical agent organizations that scale complexity while remaining manageable.
|
|
128
|
+
|
|
129
|
+
### Infrastructure Intelligence
|
|
130
|
+
AI orchestration that works everywhere - from edge devices to cloud platforms.
|
|
131
|
+
|
|
100
132
|
---
|
|
101
133
|
|
|
102
134
|
## Installation
|
|
@@ -117,6 +149,37 @@ cd soe && uv sync
|
|
|
117
149
|
|
|
118
150
|
## Quick Start
|
|
119
151
|
|
|
152
|
+
### 1. Provide Your LLM
|
|
153
|
+
|
|
154
|
+
SOE is LLM-agnostic. You must provide a `call_llm` function that matches this signature:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
def call_llm(
|
|
158
|
+
prompt: str,
|
|
159
|
+
config: dict,
|
|
160
|
+
) -> str:
|
|
161
|
+
"""
|
|
162
|
+
Called by SOE when a node needs LLM processing.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
prompt: The rendered prompt string (includes instructions, context, and schemas)
|
|
166
|
+
config: The full node configuration from YAML (useful for model parameters)
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
The raw text response from the LLM.
|
|
170
|
+
"""
|
|
171
|
+
# Example with OpenAI:
|
|
172
|
+
from openai import OpenAI
|
|
173
|
+
client = OpenAI()
|
|
174
|
+
response = client.chat.completions.create(
|
|
175
|
+
model=config.get("model", "gpt-4o"),
|
|
176
|
+
messages=[{"role": "user", "content": prompt}],
|
|
177
|
+
)
|
|
178
|
+
return response.choices[0].message.content
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 2. Run a Workflow
|
|
182
|
+
|
|
120
183
|
```python
|
|
121
184
|
from soe import orchestrate, create_all_nodes
|
|
122
185
|
from soe.local_backends import create_local_backends
|
|
@@ -134,8 +197,8 @@ example_workflow:
|
|
|
134
197
|
# Create backends (storage for context, workflows, etc.)
|
|
135
198
|
backends = create_local_backends("./data")
|
|
136
199
|
|
|
137
|
-
# Create all node handlers
|
|
138
|
-
nodes, broadcast = create_all_nodes(backends)
|
|
200
|
+
# Create all node handlers (pass your call_llm function)
|
|
201
|
+
nodes, broadcast = create_all_nodes(backends, call_llm=call_llm)
|
|
139
202
|
|
|
140
203
|
# Run the workflow
|
|
141
204
|
execution_id = orchestrate(
|
|
@@ -157,9 +220,9 @@ execution_id = orchestrate(
|
|
|
157
220
|
|
|
158
221
|
| Audience | Start Here |
|
|
159
222
|
|----------|------------|
|
|
160
|
-
| **Builders** (workflow authors) | [Documentation](docs/index.md) — Step-by-step chapters |
|
|
161
|
-
| **Engineers** (infrastructure) | [
|
|
162
|
-
| **Researchers** (advanced patterns) | [Advanced Patterns](docs/advanced_patterns/
|
|
223
|
+
| **Builders** (workflow authors) | [Documentation](soe/docs/index.md) — Step-by-step chapters |
|
|
224
|
+
| **Engineers** (infrastructure) | [Infrastructure Guide](soe/docs/guide_10_infrastructure.md) — Backend protocols |
|
|
225
|
+
| **Researchers** (advanced patterns) | [Advanced Patterns](docs/advanced_patterns/) — Swarm, hybrid, self-evolving |
|
|
163
226
|
|
|
164
227
|
---
|
|
165
228
|
|
|
@@ -55,12 +55,44 @@ All workflow state flows through **context**—a shared dictionary accessible vi
|
|
|
55
55
|
- LLM prompts can interpolate any context field
|
|
56
56
|
- No hidden state—everything is inspectable
|
|
57
57
|
|
|
58
|
-
### 3. Deterministic
|
|
59
|
-
|
|
58
|
+
### 3. Purely Deterministic or Hybrid Agentic
|
|
59
|
+
SOE is a complete orchestration solution. You can use it as a purely deterministic engine for standard business logic, or mix in LLM-driven "Agentic" behavior.
|
|
60
|
+
- **Deterministic**: Use `router` and `tool` nodes for 100% predictable workflows.
|
|
61
|
+
- **Agentic**: Add `llm` and `agent` nodes for creative, reasoning-based tasks.
|
|
62
|
+
You get the safety of code with the flexibility of AI in a single, unified system.
|
|
60
63
|
|
|
61
64
|
### 4. Portable
|
|
62
65
|
Workflows are YAML. Run them locally, in CI, in production. Extract them, version them, share them.
|
|
63
66
|
|
|
67
|
+
### 5. Self-Evolving
|
|
68
|
+
Workflows can modify themselves at runtime. Built-in tools like `inject_workflow`, `inject_node_configuration`, and `add_signal` allow agents to:
|
|
69
|
+
- Create new workflows dynamically
|
|
70
|
+
- Add or modify nodes in existing workflows
|
|
71
|
+
- Update signal routing on the fly
|
|
72
|
+
|
|
73
|
+
This enables **meta-programming**: an AI system that can extend its own capabilities without human intervention.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## What SOE Unlocks
|
|
78
|
+
|
|
79
|
+
SOE is a **Protocol for Intelligence** that unlocks new forms of intelligent behavior:
|
|
80
|
+
|
|
81
|
+
### Self-Evolving Intelligence
|
|
82
|
+
AI systems that can rewrite and improve themselves at runtime - the ultimate evolution of software.
|
|
83
|
+
|
|
84
|
+
### Swarm Intelligence
|
|
85
|
+
Efficient collective decision-making among multiple agents through signal-based consensus.
|
|
86
|
+
|
|
87
|
+
### Hybrid Intelligence
|
|
88
|
+
Seamless combination of deterministic logic and AI creativity with programmatic safety rails.
|
|
89
|
+
|
|
90
|
+
### Fractal Intelligence
|
|
91
|
+
Hierarchical agent organizations that scale complexity while remaining manageable.
|
|
92
|
+
|
|
93
|
+
### Infrastructure Intelligence
|
|
94
|
+
AI orchestration that works everywhere - from edge devices to cloud platforms.
|
|
95
|
+
|
|
64
96
|
---
|
|
65
97
|
|
|
66
98
|
## Installation
|
|
@@ -81,6 +113,37 @@ cd soe && uv sync
|
|
|
81
113
|
|
|
82
114
|
## Quick Start
|
|
83
115
|
|
|
116
|
+
### 1. Provide Your LLM
|
|
117
|
+
|
|
118
|
+
SOE is LLM-agnostic. You must provide a `call_llm` function that matches this signature:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
def call_llm(
|
|
122
|
+
prompt: str,
|
|
123
|
+
config: dict,
|
|
124
|
+
) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Called by SOE when a node needs LLM processing.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
prompt: The rendered prompt string (includes instructions, context, and schemas)
|
|
130
|
+
config: The full node configuration from YAML (useful for model parameters)
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
The raw text response from the LLM.
|
|
134
|
+
"""
|
|
135
|
+
# Example with OpenAI:
|
|
136
|
+
from openai import OpenAI
|
|
137
|
+
client = OpenAI()
|
|
138
|
+
response = client.chat.completions.create(
|
|
139
|
+
model=config.get("model", "gpt-4o"),
|
|
140
|
+
messages=[{"role": "user", "content": prompt}],
|
|
141
|
+
)
|
|
142
|
+
return response.choices[0].message.content
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 2. Run a Workflow
|
|
146
|
+
|
|
84
147
|
```python
|
|
85
148
|
from soe import orchestrate, create_all_nodes
|
|
86
149
|
from soe.local_backends import create_local_backends
|
|
@@ -98,8 +161,8 @@ example_workflow:
|
|
|
98
161
|
# Create backends (storage for context, workflows, etc.)
|
|
99
162
|
backends = create_local_backends("./data")
|
|
100
163
|
|
|
101
|
-
# Create all node handlers
|
|
102
|
-
nodes, broadcast = create_all_nodes(backends)
|
|
164
|
+
# Create all node handlers (pass your call_llm function)
|
|
165
|
+
nodes, broadcast = create_all_nodes(backends, call_llm=call_llm)
|
|
103
166
|
|
|
104
167
|
# Run the workflow
|
|
105
168
|
execution_id = orchestrate(
|
|
@@ -121,9 +184,9 @@ execution_id = orchestrate(
|
|
|
121
184
|
|
|
122
185
|
| Audience | Start Here |
|
|
123
186
|
|----------|------------|
|
|
124
|
-
| **Builders** (workflow authors) | [Documentation](docs/index.md) — Step-by-step chapters |
|
|
125
|
-
| **Engineers** (infrastructure) | [
|
|
126
|
-
| **Researchers** (advanced patterns) | [Advanced Patterns](docs/advanced_patterns/
|
|
187
|
+
| **Builders** (workflow authors) | [Documentation](soe/docs/index.md) — Step-by-step chapters |
|
|
188
|
+
| **Engineers** (infrastructure) | [Infrastructure Guide](soe/docs/guide_10_infrastructure.md) — Backend protocols |
|
|
189
|
+
| **Researchers** (advanced patterns) | [Advanced Patterns](docs/advanced_patterns/) — Swarm, hybrid, self-evolving |
|
|
127
190
|
|
|
128
191
|
---
|
|
129
192
|
|
|
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "soe-ai"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "Signal-driven Orchestration Engine - Agent orchestration with event-driven workflow engine"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
authors = [
|
|
13
|
-
{name = "Pedro Garcia", email = "
|
|
13
|
+
{name = "Pedro Garcia", email = "pgarcia14180@gmail.com"}
|
|
14
14
|
]
|
|
15
15
|
keywords = ["orchestration", "agent", "workflow", "automation"]
|
|
16
16
|
classifiers = [
|
|
@@ -49,11 +49,12 @@ Documentation = "https://github.com/pgarcia14180/soe/tree/master/docs"
|
|
|
49
49
|
Repository = "https://github.com/pgarcia14180/soe"
|
|
50
50
|
Issues = "https://github.com/pgarcia14180/soe/issues"
|
|
51
51
|
|
|
52
|
-
[tool.setuptools]
|
|
53
|
-
|
|
52
|
+
[tool.setuptools.packages.find]
|
|
53
|
+
include = ["soe*"]
|
|
54
|
+
exclude = ["tests*"]
|
|
54
55
|
|
|
55
56
|
[tool.setuptools.package-data]
|
|
56
|
-
soe = ["py.typed"]
|
|
57
|
+
soe = ["py.typed", "docs/**/*"]
|
|
57
58
|
|
|
58
59
|
[tool.black]
|
|
59
60
|
line-length = 88
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from uuid import uuid4
|
|
2
|
-
from typing import Dict, List, Any, Union,
|
|
3
|
-
from .types import Backends, BroadcastSignalsCaller
|
|
4
|
-
from .local_backends import EventTypes
|
|
2
|
+
from typing import Dict, List, Any, Union, Optional
|
|
3
|
+
from .types import Backends, BroadcastSignalsCaller, NodeCaller, EventTypes, WorkflowValidationError
|
|
5
4
|
from .lib.register_event import register_event
|
|
6
5
|
from .lib.yaml_parser import parse_yaml
|
|
7
6
|
from .lib.operational import add_operational_state
|
|
@@ -131,7 +130,7 @@ def orchestrate(
|
|
|
131
130
|
def broadcast_signals(
|
|
132
131
|
id: str,
|
|
133
132
|
signals: List[str],
|
|
134
|
-
nodes: Dict[str,
|
|
133
|
+
nodes: Dict[str, NodeCaller],
|
|
135
134
|
backends: Backends,
|
|
136
135
|
) -> None:
|
|
137
136
|
"""Broadcast signals to matching nodes in the current workflow"""
|
|
@@ -139,7 +138,7 @@ def broadcast_signals(
|
|
|
139
138
|
|
|
140
139
|
register_event(backends, id, EventTypes.SIGNALS_BROADCAST, {"signals": signals})
|
|
141
140
|
|
|
142
|
-
workflows_registry = backends.workflow.
|
|
141
|
+
workflows_registry = backends.workflow.get_workflows_registry(id)
|
|
143
142
|
|
|
144
143
|
workflow_name = backends.workflow.get_current_workflow_name(id)
|
|
145
144
|
workflow = workflows_registry.get(workflow_name, {})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Built-in tools registry
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .soe_inject_workflow import create_soe_inject_workflow_tool
|
|
6
|
+
from .soe_inject_node import create_soe_inject_node_tool
|
|
7
|
+
from .soe_get_workflows import create_soe_get_workflows_tool
|
|
8
|
+
from .soe_get_available_tools import create_soe_get_available_tools_tool
|
|
9
|
+
from .soe_explore_docs import create_soe_explore_docs_tool
|
|
10
|
+
from .soe_remove_workflow import create_soe_remove_workflow_tool
|
|
11
|
+
from .soe_remove_node import create_soe_remove_node_tool
|
|
12
|
+
from .soe_get_context import create_soe_get_context_tool
|
|
13
|
+
from .soe_update_context import create_soe_update_context_tool
|
|
14
|
+
from .soe_copy_context import create_soe_copy_context_tool
|
|
15
|
+
from .soe_list_contexts import create_soe_list_contexts_tool
|
|
16
|
+
from .soe_add_signal import create_soe_add_signal_tool
|
|
17
|
+
from .soe_call_tool import create_soe_call_tool_tool
|
|
18
|
+
|
|
19
|
+
# Registry of all available built-in tools
|
|
20
|
+
BUILTIN_TOOLS = {
|
|
21
|
+
"soe_inject_workflow": create_soe_inject_workflow_tool,
|
|
22
|
+
"soe_inject_node": create_soe_inject_node_tool,
|
|
23
|
+
"soe_get_workflows": create_soe_get_workflows_tool,
|
|
24
|
+
"soe_get_available_tools": create_soe_get_available_tools_tool,
|
|
25
|
+
"soe_explore_docs": create_soe_explore_docs_tool,
|
|
26
|
+
"soe_remove_workflow": create_soe_remove_workflow_tool,
|
|
27
|
+
"soe_remove_node": create_soe_remove_node_tool,
|
|
28
|
+
"soe_get_context": create_soe_get_context_tool,
|
|
29
|
+
"soe_update_context": create_soe_update_context_tool,
|
|
30
|
+
"soe_copy_context": create_soe_copy_context_tool,
|
|
31
|
+
"soe_list_contexts": create_soe_list_contexts_tool,
|
|
32
|
+
"soe_add_signal": create_soe_add_signal_tool,
|
|
33
|
+
"soe_call_tool": create_soe_call_tool_tool,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_builtin_tool_factory(tool_name: str):
|
|
38
|
+
"""Get factory function for built-in tool"""
|
|
39
|
+
return BUILTIN_TOOLS.get(tool_name)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Built-in tool to add a signal to a node's event emissions."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Any, Callable
|
|
4
|
+
from ..types import EventTypes
|
|
5
|
+
from ..lib.register_event import register_event
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def create_soe_add_signal_tool(
|
|
9
|
+
execution_id: str,
|
|
10
|
+
backends,
|
|
11
|
+
tools_registry: dict = None,
|
|
12
|
+
) -> Callable:
|
|
13
|
+
"""
|
|
14
|
+
Factory function to create add_signal tool.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def add_signal(
|
|
18
|
+
workflow_name: str, node_name: str, signal_name: str, condition: str
|
|
19
|
+
) -> Dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
Add a signal to a node's event_emissions list.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
workflow_name: Name of the workflow
|
|
25
|
+
node_name: Name of the node
|
|
26
|
+
signal_name: Name of the signal to add
|
|
27
|
+
condition: Jinja condition for the signal
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Success confirmation
|
|
31
|
+
"""
|
|
32
|
+
workflows_registry = backends.workflow.get_workflows_registry(execution_id)
|
|
33
|
+
|
|
34
|
+
if workflow_name not in workflows_registry:
|
|
35
|
+
raise ValueError(f"Workflow '{workflow_name}' not found")
|
|
36
|
+
|
|
37
|
+
workflow = workflows_registry[workflow_name]
|
|
38
|
+
if node_name not in workflow:
|
|
39
|
+
raise ValueError(f"Node '{node_name}' not found in workflow '{workflow_name}'")
|
|
40
|
+
|
|
41
|
+
node_config = workflow[node_name]
|
|
42
|
+
|
|
43
|
+
if "event_emissions" not in node_config:
|
|
44
|
+
node_config["event_emissions"] = []
|
|
45
|
+
|
|
46
|
+
# Check if signal already exists
|
|
47
|
+
for emission in node_config["event_emissions"]:
|
|
48
|
+
if emission.get("signal_name") == signal_name:
|
|
49
|
+
# Update existing
|
|
50
|
+
emission["condition"] = condition
|
|
51
|
+
backends.workflow.save_workflows_registry(execution_id, workflows_registry)
|
|
52
|
+
return {
|
|
53
|
+
"status": "updated",
|
|
54
|
+
"message": f"Updated signal '{signal_name}' in node '{node_name}'"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Add new signal
|
|
58
|
+
node_config["event_emissions"].append({
|
|
59
|
+
"signal_name": signal_name,
|
|
60
|
+
"condition": condition
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
backends.workflow.save_workflows_registry(execution_id, workflows_registry)
|
|
64
|
+
|
|
65
|
+
register_event(
|
|
66
|
+
backends,
|
|
67
|
+
execution_id,
|
|
68
|
+
EventTypes.NODE_EXECUTION,
|
|
69
|
+
{
|
|
70
|
+
"tool": "add_signal",
|
|
71
|
+
"workflow_name": workflow_name,
|
|
72
|
+
"node_name": node_name,
|
|
73
|
+
"signal": signal_name
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
"status": "added",
|
|
79
|
+
"message": f"Added signal '{signal_name}' to node '{node_name}'"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return add_signal
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Built-in dynamic tool invocation.
|
|
3
|
+
|
|
4
|
+
Allows LLMs to call any registered tool by name at runtime.
|
|
5
|
+
This enables meta-level tool orchestration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from typing import Dict, Any, Callable
|
|
10
|
+
from ..types import EventTypes
|
|
11
|
+
from ..lib.register_event import register_event
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_soe_call_tool_tool(
|
|
15
|
+
execution_id: str,
|
|
16
|
+
backends,
|
|
17
|
+
tools_registry: dict,
|
|
18
|
+
) -> Callable:
|
|
19
|
+
"""
|
|
20
|
+
Factory function to create call_tool with access to tools registry.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
execution_id: Current execution ID
|
|
24
|
+
backends: Backend services
|
|
25
|
+
tools_registry: Registry of available tools {name: {"function": callable, ...}}
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Configured call_tool function
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def call_tool(tool_name: str, arguments: str = "{}") -> Dict[str, Any]:
|
|
32
|
+
"""
|
|
33
|
+
Dynamically invoke a registered tool by name.
|
|
34
|
+
|
|
35
|
+
This is a meta-tool that allows calling any other tool at runtime.
|
|
36
|
+
Useful for dynamic workflows where the tool to call is determined
|
|
37
|
+
by context or user input.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
tool_name: Name of the tool to invoke (must be registered)
|
|
41
|
+
arguments: JSON string of arguments to pass to the tool
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
The result from the invoked tool, or an error dict
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
call_tool("get_secret", '{"key": "password"}')
|
|
48
|
+
call_tool("write_file", '{"path": "test.txt", "content": "hello"}')
|
|
49
|
+
"""
|
|
50
|
+
# Parse arguments
|
|
51
|
+
try:
|
|
52
|
+
args = json.loads(arguments) if arguments else {}
|
|
53
|
+
except json.JSONDecodeError as e:
|
|
54
|
+
return {
|
|
55
|
+
"error": f"Invalid JSON arguments: {e}",
|
|
56
|
+
"tool_name": tool_name,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Check if tool exists
|
|
60
|
+
if tool_name not in tools_registry:
|
|
61
|
+
available = list(tools_registry.keys())
|
|
62
|
+
return {
|
|
63
|
+
"error": f"Tool '{tool_name}' not found",
|
|
64
|
+
"available_tools": available[:20], # Limit to avoid huge responses
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Get tool function
|
|
68
|
+
tool_entry = tools_registry[tool_name]
|
|
69
|
+
if isinstance(tool_entry, dict):
|
|
70
|
+
tool_func = tool_entry.get("function")
|
|
71
|
+
elif callable(tool_entry):
|
|
72
|
+
tool_func = tool_entry
|
|
73
|
+
else:
|
|
74
|
+
return {"error": f"Invalid tool registry entry for '{tool_name}'"}
|
|
75
|
+
|
|
76
|
+
if not callable(tool_func):
|
|
77
|
+
return {"error": f"Tool '{tool_name}' is not callable"}
|
|
78
|
+
|
|
79
|
+
# Log the dynamic invocation
|
|
80
|
+
register_event(
|
|
81
|
+
backends,
|
|
82
|
+
execution_id,
|
|
83
|
+
EventTypes.TOOL_CALL,
|
|
84
|
+
{
|
|
85
|
+
"meta_tool": "call_tool",
|
|
86
|
+
"invoked_tool": tool_name,
|
|
87
|
+
"arguments": args,
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Invoke the tool
|
|
92
|
+
try:
|
|
93
|
+
result = tool_func(**args)
|
|
94
|
+
return {
|
|
95
|
+
"success": True,
|
|
96
|
+
"tool_name": tool_name,
|
|
97
|
+
"result": result,
|
|
98
|
+
}
|
|
99
|
+
except TypeError as e:
|
|
100
|
+
# Argument mismatch
|
|
101
|
+
return {
|
|
102
|
+
"error": f"Argument error for '{tool_name}': {e}",
|
|
103
|
+
"tool_name": tool_name,
|
|
104
|
+
}
|
|
105
|
+
except Exception as e:
|
|
106
|
+
return {
|
|
107
|
+
"error": f"Tool '{tool_name}' failed: {e}",
|
|
108
|
+
"tool_name": tool_name,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return call_tool
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Built-in tool: copy_context
|
|
3
|
+
Allows agents to copy context fields between executions or within the same execution.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Dict, Any, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def create_soe_copy_context_tool(backends, execution_id: str, tools_registry=None):
|
|
10
|
+
"""
|
|
11
|
+
Factory that creates a copy_context tool bound to the current execution.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
backends: Backend instances (needs context backend)
|
|
15
|
+
execution_id: Current execution ID
|
|
16
|
+
tools_registry: Tool registry (unused, for interface compatibility)
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Configured tool function
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def copy_context(
|
|
23
|
+
source_execution_id: Optional[str] = None,
|
|
24
|
+
fields: Optional[Dict[str, str]] = None,
|
|
25
|
+
all_fields: bool = False,
|
|
26
|
+
target_execution_id: Optional[str] = None,
|
|
27
|
+
) -> Dict[str, Any]:
|
|
28
|
+
"""
|
|
29
|
+
Copy context fields between executions or within the same execution.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
source_execution_id: Execution ID to copy from (default: current)
|
|
33
|
+
fields: Dict of {source_field: target_field} to copy
|
|
34
|
+
all_fields: If True, copy all non-operational fields
|
|
35
|
+
target_execution_id: Execution ID to copy to (default: current)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dict with copy results
|
|
39
|
+
"""
|
|
40
|
+
source_id = source_execution_id or execution_id
|
|
41
|
+
target_id = target_execution_id or execution_id
|
|
42
|
+
|
|
43
|
+
# Get source context
|
|
44
|
+
source_context = backends.context.get_context(source_id)
|
|
45
|
+
|
|
46
|
+
# Filter out operational fields
|
|
47
|
+
source_filtered = {k: v for k, v in source_context.items() if not k.startswith("__")}
|
|
48
|
+
|
|
49
|
+
# Get target context
|
|
50
|
+
target_context = backends.context.get_context(target_id)
|
|
51
|
+
|
|
52
|
+
copied_fields = {}
|
|
53
|
+
|
|
54
|
+
if all_fields:
|
|
55
|
+
# Copy all non-operational fields
|
|
56
|
+
for field, value in source_filtered.items():
|
|
57
|
+
target_context[field] = value
|
|
58
|
+
copied_fields[field] = field
|
|
59
|
+
elif fields:
|
|
60
|
+
# Copy specific field mappings
|
|
61
|
+
for source_field, target_field in fields.items():
|
|
62
|
+
if source_field in source_filtered:
|
|
63
|
+
target_context[target_field] = source_filtered[source_field]
|
|
64
|
+
copied_fields[source_field] = target_field
|
|
65
|
+
else:
|
|
66
|
+
return {"error": f"Source field '{source_field}' not found in execution {source_id}"}
|
|
67
|
+
else:
|
|
68
|
+
return {"error": "Must specify either 'fields' mapping or 'all_fields=True'"}
|
|
69
|
+
|
|
70
|
+
# Save target context
|
|
71
|
+
backends.context.save_context(target_id, target_context)
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
"status": "copied",
|
|
75
|
+
"source_execution": source_id,
|
|
76
|
+
"target_execution": target_id,
|
|
77
|
+
"fields_copied": copied_fields,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return copy_context
|