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,427 @@
|
|
|
1
|
+
# SOE Guide: Chapter 10 - Custom Infrastructure
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
SOE is **infrastructure agnostic** by design. The core orchestration engine doesn't care *where* your data lives or *how* signals are broadcast—it only requires that you provide implementations that follow the defined protocols.
|
|
6
|
+
|
|
7
|
+
This chapter covers two distinct infrastructure concerns:
|
|
8
|
+
|
|
9
|
+
1. **Persistent Infrastructure (Backends)** — Where data is stored
|
|
10
|
+
2. **Communication Infrastructure (Callers)** — How signals are broadcast
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Part 1: Persistent Infrastructure (Backends)
|
|
15
|
+
|
|
16
|
+
### What Are Backends?
|
|
17
|
+
|
|
18
|
+
Backends are the **persistence layer** of SOE. They store:
|
|
19
|
+
|
|
20
|
+
| Backend | Purpose | Example Use Cases |
|
|
21
|
+
|---------|---------|-------------------|
|
|
22
|
+
| `context` | Workflow execution state | DynamoDB, Redis, PostgreSQL |
|
|
23
|
+
| `workflow` | Workflow definitions | S3, local files, database |
|
|
24
|
+
| `telemetry` | Logs and events | Datadog, CloudWatch, Elasticsearch |
|
|
25
|
+
| `conversation_history` | LLM chat history | MongoDB, PostgreSQL, text files |
|
|
26
|
+
| `schema` | Context validation schemas | Database, config files |
|
|
27
|
+
|
|
28
|
+
### Built-in Backends
|
|
29
|
+
|
|
30
|
+
SOE ships with two backend implementations for immediate use:
|
|
31
|
+
|
|
32
|
+
#### In-Memory Backends
|
|
33
|
+
```python
|
|
34
|
+
from soe.local_backends import create_in_memory_backends
|
|
35
|
+
|
|
36
|
+
backends = create_in_memory_backends()
|
|
37
|
+
```
|
|
38
|
+
- **Use case**: Unit tests, prototyping
|
|
39
|
+
- **Pros**: Fast, no setup required
|
|
40
|
+
- **Cons**: Data lost on process exit, not distributable
|
|
41
|
+
|
|
42
|
+
#### Local File Backends
|
|
43
|
+
```python
|
|
44
|
+
from soe.local_backends import create_local_backends
|
|
45
|
+
|
|
46
|
+
backends = create_local_backends(
|
|
47
|
+
context_storage_dir="./orchestration_data/contexts",
|
|
48
|
+
workflow_storage_dir="./orchestration_data/workflows",
|
|
49
|
+
telemetry_storage_dir="./orchestration_data/telemetry", # Optional
|
|
50
|
+
conversation_history_storage_dir="./orchestration_data/conversations", # Optional
|
|
51
|
+
schema_storage_dir="./orchestration_data/schemas", # Optional
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
- **Use case**: Local development, debugging, simple deployments
|
|
55
|
+
- **Pros**: Persistent, human-readable (JSON files)
|
|
56
|
+
- **Cons**: Not distributable, no concurrent access
|
|
57
|
+
|
|
58
|
+
### The Backend Protocols
|
|
59
|
+
|
|
60
|
+
Each backend type follows a Python `Protocol` (structural typing). Here are the required interfaces:
|
|
61
|
+
|
|
62
|
+
#### ContextBackend Protocol
|
|
63
|
+
```python
|
|
64
|
+
class ContextBackend(Protocol):
|
|
65
|
+
def save_context(self, id: str, context: dict) -> None: ...
|
|
66
|
+
def get_context(self, id: str) -> dict: ...
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### WorkflowBackend Protocol
|
|
70
|
+
```python
|
|
71
|
+
class WorkflowBackend(Protocol):
|
|
72
|
+
def save_workflows_registry(self, id: str, workflows: Any) -> None: ...
|
|
73
|
+
def get_workflows_registry(self, id: str) -> Any: ...
|
|
74
|
+
def save_current_workflow_name(self, id: str, name: str) -> None: ...
|
|
75
|
+
def get_current_workflow_name(self, id: str) -> str: ...
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### TelemetryBackend Protocol (Optional)
|
|
79
|
+
```python
|
|
80
|
+
class TelemetryBackend(Protocol):
|
|
81
|
+
def log_event(self, execution_id: str, event_type: str, **event_data) -> None: ...
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### ConversationHistoryBackend Protocol (Optional)
|
|
85
|
+
```python
|
|
86
|
+
class ConversationHistoryBackend(Protocol):
|
|
87
|
+
def get_conversation_history(self, identity: str) -> List[Dict[str, Any]]: ...
|
|
88
|
+
def append_to_conversation_history(self, identity: str, entry: Dict[str, Any]) -> None: ...
|
|
89
|
+
def save_conversation_history(self, identity: str, history: List[Dict[str, Any]]) -> None: ...
|
|
90
|
+
def delete_conversation_history(self, identity: str) -> None: ...
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### ContextSchemaBackend Protocol (Optional)
|
|
94
|
+
```python
|
|
95
|
+
class ContextSchemaBackend(Protocol):
|
|
96
|
+
def save_context_schema(self, execution_id: str, schema: Dict[str, Any]) -> None: ...
|
|
97
|
+
def get_context_schema(self, execution_id: str) -> Optional[Dict[str, Any]]: ...
|
|
98
|
+
def get_field_schema(self, execution_id: str, field_name: str) -> Optional[Dict[str, Any]]: ...
|
|
99
|
+
def delete_context_schema(self, execution_id: str) -> bool: ...
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Note**: Context schemas are keyed by `execution_id` (specifically `main_execution_id`), not workflow name. This allows child workflows to access the parent's schema definitions.
|
|
103
|
+
|
|
104
|
+
#### IdentityBackend Protocol (Optional)
|
|
105
|
+
```python
|
|
106
|
+
class IdentityBackend(Protocol):
|
|
107
|
+
def save_identities(self, execution_id: str, identities: Dict[str, str]) -> None: ...
|
|
108
|
+
def get_identities(self, execution_id: str) -> Optional[Dict[str, str]]: ...
|
|
109
|
+
def get_identity(self, execution_id: str, identity_name: str) -> Optional[str]: ...
|
|
110
|
+
def delete_identities(self, execution_id: str) -> bool: ...
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Note**: Identities are simple mappings of `identity_name` → `system_prompt` (string). Like context schemas, they're keyed by `main_execution_id` for child workflow access.
|
|
114
|
+
|
|
115
|
+
### Database Recommendations
|
|
116
|
+
|
|
117
|
+
For production deployments, we recommend using **the same database** for all config-related backends:
|
|
118
|
+
|
|
119
|
+
| Backend | Recommended Storage | Why |
|
|
120
|
+
|---------|---------------------|-----|
|
|
121
|
+
| `context` | Same DB, separate table | Simplifies connection management |
|
|
122
|
+
| `workflow` | Same DB, separate table | Allows atomic config updates |
|
|
123
|
+
| `context_schema` | Same DB, separate table | Related to workflows |
|
|
124
|
+
| `identity` | Same DB, separate table | Related to workflows |
|
|
125
|
+
| `conversation_history` | Same DB, separate table | Per-execution keying |
|
|
126
|
+
| `telemetry` | Can be separate | High volume, different access patterns |
|
|
127
|
+
|
|
128
|
+
The backend methods handle table creation. Using one database (with separate tables) is simpler to manage than multiple databases.
|
|
129
|
+
|
|
130
|
+
### Creating Custom Backends
|
|
131
|
+
|
|
132
|
+
#### Example: PostgreSQL Context Backend
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
import psycopg2
|
|
136
|
+
import json
|
|
137
|
+
from typing import Dict, Any
|
|
138
|
+
|
|
139
|
+
class PostgresContextBackend:
|
|
140
|
+
"""PostgreSQL-based context storage"""
|
|
141
|
+
|
|
142
|
+
def __init__(self, connection_string: str):
|
|
143
|
+
self.conn = psycopg2.connect(connection_string)
|
|
144
|
+
self._ensure_table()
|
|
145
|
+
|
|
146
|
+
def _ensure_table(self):
|
|
147
|
+
with self.conn.cursor() as cur:
|
|
148
|
+
cur.execute("""
|
|
149
|
+
CREATE TABLE IF NOT EXISTS soe_context (
|
|
150
|
+
execution_id VARCHAR(255) PRIMARY KEY,
|
|
151
|
+
context JSONB NOT NULL,
|
|
152
|
+
updated_at TIMESTAMP DEFAULT NOW()
|
|
153
|
+
)
|
|
154
|
+
""")
|
|
155
|
+
self.conn.commit()
|
|
156
|
+
|
|
157
|
+
def save_context(self, id: str, context: Dict[str, Any]) -> None:
|
|
158
|
+
with self.conn.cursor() as cur:
|
|
159
|
+
cur.execute("""
|
|
160
|
+
INSERT INTO soe_context (execution_id, context)
|
|
161
|
+
VALUES (%s, %s)
|
|
162
|
+
ON CONFLICT (execution_id)
|
|
163
|
+
DO UPDATE SET context = %s, updated_at = NOW()
|
|
164
|
+
""", (id, json.dumps(context), json.dumps(context)))
|
|
165
|
+
self.conn.commit()
|
|
166
|
+
|
|
167
|
+
def get_context(self, id: str) -> Dict[str, Any]:
|
|
168
|
+
with self.conn.cursor() as cur:
|
|
169
|
+
cur.execute(
|
|
170
|
+
"SELECT context FROM soe_context WHERE execution_id = %s",
|
|
171
|
+
(id,)
|
|
172
|
+
)
|
|
173
|
+
row = cur.fetchone()
|
|
174
|
+
return row[0] if row else {}
|
|
175
|
+
|
|
176
|
+
def cleanup_all(self) -> None:
|
|
177
|
+
with self.conn.cursor() as cur:
|
|
178
|
+
cur.execute("DELETE FROM soe_context")
|
|
179
|
+
self.conn.commit()
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### Example: DynamoDB Context Backend
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
import boto3
|
|
186
|
+
import json
|
|
187
|
+
from typing import Dict, Any
|
|
188
|
+
|
|
189
|
+
class DynamoDBContextBackend:
|
|
190
|
+
"""AWS DynamoDB-based context storage"""
|
|
191
|
+
|
|
192
|
+
def __init__(self, table_name: str, region: str = "us-east-1"):
|
|
193
|
+
self.dynamodb = boto3.resource('dynamodb', region_name=region)
|
|
194
|
+
self.table = self.dynamodb.Table(table_name)
|
|
195
|
+
|
|
196
|
+
def save_context(self, id: str, context: Dict[str, Any]) -> None:
|
|
197
|
+
self.table.put_item(Item={
|
|
198
|
+
'execution_id': id,
|
|
199
|
+
'context': json.dumps(context)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
def get_context(self, id: str) -> Dict[str, Any]:
|
|
203
|
+
response = self.table.get_item(Key={'execution_id': id})
|
|
204
|
+
if 'Item' in response:
|
|
205
|
+
return json.loads(response['Item']['context'])
|
|
206
|
+
return {}
|
|
207
|
+
|
|
208
|
+
def cleanup_all(self) -> None:
|
|
209
|
+
# Scan and delete all items (use with caution in production)
|
|
210
|
+
scan = self.table.scan()
|
|
211
|
+
with self.table.batch_writer() as batch:
|
|
212
|
+
for item in scan['Items']:
|
|
213
|
+
batch.delete_item(Key={'execution_id': item['execution_id']})
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### Example: Datadog Telemetry Backend
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
from datadog import initialize, statsd
|
|
220
|
+
from typing import Any
|
|
221
|
+
|
|
222
|
+
class DatadogTelemetryBackend:
|
|
223
|
+
"""Datadog-based telemetry backend"""
|
|
224
|
+
|
|
225
|
+
def __init__(self, api_key: str, app_key: str):
|
|
226
|
+
initialize(api_key=api_key, app_key=app_key)
|
|
227
|
+
|
|
228
|
+
def log_event(self, execution_id: str, event_type: str, **event_data) -> None:
|
|
229
|
+
# Send as a custom metric
|
|
230
|
+
statsd.event(
|
|
231
|
+
title=f"SOE: {event_type}",
|
|
232
|
+
text=f"Execution: {execution_id}",
|
|
233
|
+
tags=[
|
|
234
|
+
f"execution_id:{execution_id}",
|
|
235
|
+
f"event_type:{event_type}",
|
|
236
|
+
*[f"{k}:{v}" for k, v in event_data.items()]
|
|
237
|
+
]
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def cleanup_all(self) -> None:
|
|
241
|
+
# Datadog events are immutable; no cleanup needed
|
|
242
|
+
pass
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Assembling Custom Backends
|
|
246
|
+
|
|
247
|
+
Create a `Backends` container with your custom implementations:
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from soe.local_backends.factory import LocalBackends
|
|
251
|
+
|
|
252
|
+
# Mix and match backends based on your infrastructure
|
|
253
|
+
backends = LocalBackends(
|
|
254
|
+
context_backend=PostgresContextBackend("postgresql://..."),
|
|
255
|
+
workflow_backend=S3WorkflowBackend("my-bucket"),
|
|
256
|
+
telemetry_backend=DatadogTelemetryBackend(api_key="...", app_key="..."),
|
|
257
|
+
conversation_history_backend=MongoConversationBackend("mongodb://..."),
|
|
258
|
+
schema_backend=None, # Optional
|
|
259
|
+
)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Part 2: Communication Infrastructure (Callers)
|
|
265
|
+
|
|
266
|
+
### What Are Callers?
|
|
267
|
+
|
|
268
|
+
Callers are the **communication layer** of SOE. The most important is `broadcast_signals_caller`, which controls how signals propagate through the system.
|
|
269
|
+
|
|
270
|
+
### The Default Pattern (In-Process)
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
from soe import broadcast_signals
|
|
274
|
+
|
|
275
|
+
def broadcast_signals_caller(id: str, signals: List[str]) -> None:
|
|
276
|
+
broadcast_signals(id, signals, nodes, backends)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
This is synchronous and in-process—perfect for testing and simple deployments.
|
|
280
|
+
|
|
281
|
+
### Distributed Callers
|
|
282
|
+
|
|
283
|
+
For production systems, you might want signals to trigger external services.
|
|
284
|
+
|
|
285
|
+
#### Example: AWS Lambda Caller
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
import boto3
|
|
289
|
+
import json
|
|
290
|
+
|
|
291
|
+
def create_lambda_caller(function_name: str, region: str = "us-east-1"):
|
|
292
|
+
"""Create a caller that invokes AWS Lambda for signal broadcasting"""
|
|
293
|
+
lambda_client = boto3.client('lambda', region_name=region)
|
|
294
|
+
|
|
295
|
+
def broadcast_signals_caller(id: str, signals: List[str]) -> None:
|
|
296
|
+
payload = {
|
|
297
|
+
'execution_id': id,
|
|
298
|
+
'signals': signals
|
|
299
|
+
}
|
|
300
|
+
lambda_client.invoke(
|
|
301
|
+
FunctionName=function_name,
|
|
302
|
+
InvocationType='Event', # Async invocation
|
|
303
|
+
Payload=json.dumps(payload)
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
return broadcast_signals_caller
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
#### Example: HTTP Webhook Caller
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
import requests
|
|
313
|
+
|
|
314
|
+
def create_http_caller(webhook_url: str, auth_token: str = None):
|
|
315
|
+
"""Create a caller that sends signals via HTTP POST"""
|
|
316
|
+
headers = {'Content-Type': 'application/json'}
|
|
317
|
+
if auth_token:
|
|
318
|
+
headers['Authorization'] = f'Bearer {auth_token}'
|
|
319
|
+
|
|
320
|
+
def broadcast_signals_caller(id: str, signals: List[str]) -> None:
|
|
321
|
+
payload = {
|
|
322
|
+
'execution_id': id,
|
|
323
|
+
'signals': signals
|
|
324
|
+
}
|
|
325
|
+
response = requests.post(webhook_url, json=payload, headers=headers)
|
|
326
|
+
response.raise_for_status()
|
|
327
|
+
|
|
328
|
+
return broadcast_signals_caller
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
#### Example: Message Queue Caller (SQS)
|
|
332
|
+
|
|
333
|
+
```python
|
|
334
|
+
import boto3
|
|
335
|
+
import json
|
|
336
|
+
|
|
337
|
+
def create_sqs_caller(queue_url: str, region: str = "us-east-1"):
|
|
338
|
+
"""Create a caller that sends signals to an SQS queue"""
|
|
339
|
+
sqs = boto3.client('sqs', region_name=region)
|
|
340
|
+
|
|
341
|
+
def broadcast_signals_caller(id: str, signals: List[str]) -> None:
|
|
342
|
+
message = {
|
|
343
|
+
'execution_id': id,
|
|
344
|
+
'signals': signals
|
|
345
|
+
}
|
|
346
|
+
sqs.send_message(
|
|
347
|
+
QueueUrl=queue_url,
|
|
348
|
+
MessageBody=json.dumps(message)
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
return broadcast_signals_caller
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Part 3: The LLM Caller
|
|
357
|
+
|
|
358
|
+
### SOE Doesn't Provide an LLM
|
|
359
|
+
|
|
360
|
+
Unlike some frameworks that bundle specific LLM providers, SOE is **LLM agnostic**. You provide a `call_llm` function that wraps your chosen provider.
|
|
361
|
+
|
|
362
|
+
### The CallLlm Protocol
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
class CallLlm(Protocol):
|
|
366
|
+
def __call__(self, prompt: str, config: Dict[str, Any]) -> str:
|
|
367
|
+
...
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Example: OpenAI Caller
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
import openai
|
|
374
|
+
|
|
375
|
+
def create_openai_caller(api_key: str, default_model: str = "gpt-4o"):
|
|
376
|
+
"""Create an LLM caller for OpenAI API"""
|
|
377
|
+
client = openai.OpenAI(api_key=api_key)
|
|
378
|
+
|
|
379
|
+
def call_llm(prompt: str, config: dict) -> str:
|
|
380
|
+
model = config.get("model", default_model)
|
|
381
|
+
temperature = config.get("temperature", 1.0)
|
|
382
|
+
|
|
383
|
+
response = client.chat.completions.create(
|
|
384
|
+
model=model,
|
|
385
|
+
temperature=temperature,
|
|
386
|
+
messages=[{"role": "user", "content": prompt}]
|
|
387
|
+
)
|
|
388
|
+
return response.choices[0].message.content
|
|
389
|
+
|
|
390
|
+
return call_llm
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Example: Test Stub Caller
|
|
394
|
+
|
|
395
|
+
```python
|
|
396
|
+
def create_stub_caller(responses: dict = None):
|
|
397
|
+
"""Create a stub caller for testing"""
|
|
398
|
+
responses = responses or {}
|
|
399
|
+
|
|
400
|
+
def call_llm(prompt: str, config: dict) -> str:
|
|
401
|
+
for pattern, response in responses.items():
|
|
402
|
+
if pattern in prompt:
|
|
403
|
+
return response
|
|
404
|
+
return '{"response": "stub response"}'
|
|
405
|
+
|
|
406
|
+
return call_llm
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## Summary
|
|
412
|
+
|
|
413
|
+
| Concern | Component | Built-in Options | Custom Examples |
|
|
414
|
+
|---------|-----------|------------------|-----------------|
|
|
415
|
+
| **Persistence** | Backends | In-Memory, Local Files | PostgreSQL, DynamoDB, MongoDB |
|
|
416
|
+
| **Telemetry** | TelemetryBackend | In-Memory, Local Files | Datadog, CloudWatch |
|
|
417
|
+
| **Communication** | Callers | In-process | Lambda, HTTP, SQS |
|
|
418
|
+
| **LLM** | CallLlm | None (you provide) | OpenAI, Anthropic, Ollama |
|
|
419
|
+
|
|
420
|
+
The key principle: **SOE defines the protocols, you provide the implementations**.
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Next Steps
|
|
425
|
+
|
|
426
|
+
Now that you've mastered the infrastructure, explore the capabilities built directly into the engine:
|
|
427
|
+
- **[Chapter 11: Built-in Tools](guide_11_builtins.md)** — Self-evolution, documentation exploration, and runtime modification
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
|
|
2
|
+
# SOE Guide: Chapter 11 - Built-in Tools
|
|
3
|
+
|
|
4
|
+
## The Power of Self-Evolution
|
|
5
|
+
|
|
6
|
+
SOE includes a set of **built-in tools** that are always available to your workflows. These aren't ordinary utilities—they're the foundation for **self-evolving systems**.
|
|
7
|
+
|
|
8
|
+
Built-in tools enable workflows to:
|
|
9
|
+
|
|
10
|
+
- **Explore their own documentation** — Understand what they can do
|
|
11
|
+
- **Modify workflows at runtime** — Inject, remove, or reconfigure nodes
|
|
12
|
+
- **Manage execution context** — Read, update, and copy context data
|
|
13
|
+
- **Query available capabilities** — Discover registered tools dynamically
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Naming Convention
|
|
18
|
+
|
|
19
|
+
All built-in tools use the `soe_` prefix to clearly distinguish them from user-defined tools. This prevents naming conflicts and makes it clear these are system-provided utilities.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Principles
|
|
24
|
+
|
|
25
|
+
### Always Available
|
|
26
|
+
|
|
27
|
+
Built-in tools require no registration. Simply reference them by name in any tool node:
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
example_workflow:
|
|
31
|
+
ExploreDocs:
|
|
32
|
+
node_type: tool
|
|
33
|
+
event_triggers: [START]
|
|
34
|
+
tool_name: soe_explore_docs
|
|
35
|
+
context_parameter_field: explore_params
|
|
36
|
+
output_field: docs_list
|
|
37
|
+
event_emissions:
|
|
38
|
+
- signal_name: DOCS_LISTED
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Essential for Self-Evolution
|
|
42
|
+
|
|
43
|
+
These tools are the building blocks for autonomous systems. An LLM can:
|
|
44
|
+
|
|
45
|
+
1. **Read documentation** to understand capabilities
|
|
46
|
+
2. **Query workflows** to see current structure
|
|
47
|
+
3. **Inject new nodes** to extend behavior
|
|
48
|
+
4. **Manage context** to persist learned patterns
|
|
49
|
+
|
|
50
|
+
### No Registration Required
|
|
51
|
+
|
|
52
|
+
Unlike custom tools that need a `tools_registry`, built-ins work immediately. SOE provides them automatically during orchestration.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Available Built-ins
|
|
57
|
+
|
|
58
|
+
| Built-in | Purpose | Documentation |
|
|
59
|
+
|----------|---------|---------------|
|
|
60
|
+
| `soe_explore_docs` | Make SOE self-aware by exploring its documentation | [explore_docs](builtins/explore_docs.md) |
|
|
61
|
+
| `soe_get_workflows` | Query registered workflow definitions | [workflows](builtins/workflows.md) |
|
|
62
|
+
| `soe_inject_workflow` | Add new workflows at runtime | [workflows](builtins/workflows.md) |
|
|
63
|
+
| `soe_inject_node` | Add or modify nodes in workflows | [workflows](builtins/workflows.md) |
|
|
64
|
+
| `soe_remove_workflow` | Remove workflows from registry | [workflows](builtins/workflows.md) |
|
|
65
|
+
| `soe_remove_node` | Remove nodes from workflows | [workflows](builtins/workflows.md) |
|
|
66
|
+
| `soe_get_context` | Read execution context | [context](builtins/context.md) |
|
|
67
|
+
| `soe_update_context` | Modify execution context | [context](builtins/context.md) |
|
|
68
|
+
| `soe_copy_context` | Clone context for parallel execution | [context](builtins/context.md) |
|
|
69
|
+
| `soe_list_contexts` | Discover available contexts | [context](builtins/context.md) |
|
|
70
|
+
| `soe_get_available_tools` | List all registered tools | [tools](builtins/tools.md) |
|
|
71
|
+
| `soe_call_tool` | Invoke a tool by name dynamically | [tools](builtins/tools.md) |
|
|
72
|
+
| `soe_add_signal` | Emit signals programmatically | [workflows](builtins/workflows.md) |
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Self-Awareness in Practice
|
|
77
|
+
|
|
78
|
+
Here's a workflow that becomes self-aware by exploring docs and querying its own state:
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
self_aware_workflow:
|
|
82
|
+
ExploreCapabilities:
|
|
83
|
+
node_type: tool
|
|
84
|
+
event_triggers: [START]
|
|
85
|
+
tool_name: soe_explore_docs
|
|
86
|
+
context_parameter_field: explore_params
|
|
87
|
+
output_field: capabilities_tree
|
|
88
|
+
event_emissions:
|
|
89
|
+
- signal_name: CAPABILITIES_KNOWN
|
|
90
|
+
|
|
91
|
+
QueryCurrentState:
|
|
92
|
+
node_type: tool
|
|
93
|
+
event_triggers: [CAPABILITIES_KNOWN]
|
|
94
|
+
tool_name: soe_get_workflows
|
|
95
|
+
output_field: current_workflows
|
|
96
|
+
event_emissions:
|
|
97
|
+
- signal_name: STATE_KNOWN
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
This pattern is the foundation for metacognitive workflows that can reason about their own capabilities.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Next Steps
|
|
105
|
+
|
|
106
|
+
Explore each built-in category in detail:
|
|
107
|
+
|
|
108
|
+
- **[explore_docs](builtins/explore_docs.md)** — Self-awareness through documentation
|
|
109
|
+
- **[workflows](builtins/workflows.md)** — Runtime workflow modification
|
|
110
|
+
- **[context](builtins/context.md)** — Execution state management
|
|
111
|
+
- **[tools](builtins/tools.md)** — Dynamic tool discovery and invocation
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Related
|
|
116
|
+
|
|
117
|
+
- [Advanced Patterns](advanced_patterns/index.md) — Complex workflow patterns
|
|
118
|
+
- [Self-Evolving Workflows](advanced_patterns/self_evolving_workflows.md) — Full self-evolution guide
|
soe/docs/index.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# SOE User Guide
|
|
2
|
+
|
|
3
|
+
Welcome to the **Signal-driven Orchestration Engine** user guide. This is your starting point for learning how to build workflows with SOE.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
New to SOE? Start here:
|
|
10
|
+
|
|
11
|
+
1. **[Getting Started](guide_00_getting_started.md)** — Installation, core concepts, and your first workflow
|
|
12
|
+
2. **[Tool Nodes](guide_01_tool.md)** — Execute Python functions in your workflows
|
|
13
|
+
3. **[LLM Nodes](guide_02_llm.md)** — Integrate language models into your workflows
|
|
14
|
+
4. **[Router Nodes](guide_03_router.md)** — Pure routing and branching (recommended)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## The Guide
|
|
19
|
+
|
|
20
|
+
### The Three Core Node Types
|
|
21
|
+
|
|
22
|
+
SOE has three core node types that can build **any** workflow pattern:
|
|
23
|
+
|
|
24
|
+
| Chapter | Topic | What You'll Learn |
|
|
25
|
+
|---------|-------|-------------------|
|
|
26
|
+
| [00 - Getting Started](guide_00_getting_started.md) | Installation & First Run | How SOE works, synchronous execution, copy-paste template |
|
|
27
|
+
| [01 - Tool](guide_01_tool.md) | Tool Nodes | Execute Python functions—APIs, databases, real work |
|
|
28
|
+
| [02 - LLM](guide_02_llm.md) | LLM Nodes | Call language models, prompt templating, signal selection |
|
|
29
|
+
| [03 - Router](guide_03_router.md) | Router Nodes | Pure routing—entry points, branching, no execution |
|
|
30
|
+
| [04 - Patterns](guide_04_patterns.md) | Building Custom Workflows | Combine core nodes: ReAct, chain-of-thought, metacognition |
|
|
31
|
+
|
|
32
|
+
With just **Tool**, **LLM**, and **Router**, you can build remarkably sophisticated workflows—including custom agent loops, reasoning chains, and multi-step AI systems.
|
|
33
|
+
|
|
34
|
+
### Convenience & Advanced Features
|
|
35
|
+
|
|
36
|
+
These chapters cover higher-level abstractions and advanced capabilities:
|
|
37
|
+
|
|
38
|
+
| Chapter | Topic | What You'll Learn |
|
|
39
|
+
|---------|-------|-------------------|
|
|
40
|
+
| [05 - Agent](guide_05_agent.md) | Agent Nodes | Built-in ReAct loop—convenience wrapper for tool-using agents |
|
|
41
|
+
| [06 - Schema](guide_06_schema.md) | Context Schema | Type validation for structured LLM output |
|
|
42
|
+
| [07 - Identity](guide_07_identity.md) | Identity & History | System prompts and conversation history across nodes |
|
|
43
|
+
| [08 - Child Workflows](guide_08_child.md) | Sub-orchestration | Nested workflows with signal communication |
|
|
44
|
+
| [09 - Ecosystem](guide_09_ecosystem.md) | The Workflows Ecosystem | Multi-workflow registries, parallel execution, versioning |
|
|
45
|
+
| [10 - Infrastructure](guide_10_infrastructure.md) | Custom Backends & Callers | PostgreSQL, DynamoDB, Lambda, HTTP webhooks |
|
|
46
|
+
| [11 - Built-in Tools](guide_11_builtins.md) | Self-Evolution | Always-available tools for introspection and evolution |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Primitives
|
|
51
|
+
|
|
52
|
+
Understand the core building blocks:
|
|
53
|
+
|
|
54
|
+
| Concept | Description |
|
|
55
|
+
|---------|-------------|
|
|
56
|
+
| [The 7 Primitives](primitives/primitives.md) | Signals, Context, Workflows, Backends, Nodes, Identities, Context Schema |
|
|
57
|
+
| [Signals Reference](primitives/signals.md) | Signal emission, conditions, and propagation |
|
|
58
|
+
| [Context Deep Dive](primitives/context.md) | History, accumulated filter, Jinja access |
|
|
59
|
+
| [Backends Reference](primitives/backends.md) | Setup, protocols, custom implementations |
|
|
60
|
+
| [Node Reference](primitives/node_reference.md) | Complete node configuration reference |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Advanced Patterns
|
|
65
|
+
|
|
66
|
+
For researchers and advanced users exploring SOE's full potential:
|
|
67
|
+
|
|
68
|
+
| Pattern | Description |
|
|
69
|
+
|---------|-------------|
|
|
70
|
+
| [Advanced Patterns Index](advanced_patterns/index.md) | Overview of all advanced patterns |
|
|
71
|
+
| [Fan-Out & Aggregations](advanced_patterns/guide_fanout_and_aggregations.md) | Parallel processing and accumulated context |
|
|
72
|
+
| [Self-Evolving Workflows](advanced_patterns/self_evolving_workflows.md) | LLM-driven workflow generation and injection |
|
|
73
|
+
| [Swarm Intelligence](advanced_patterns/swarm_intelligence.md) | Multi-agent voting and consensus |
|
|
74
|
+
| [Hybrid Intelligence](advanced_patterns/hybrid_intelligence.md) | Mixing deterministic logic with AI |
|
|
75
|
+
| [Inheritance](advanced_patterns/guide_inheritance.md) | Config and context inheritance between executions |
|
|
76
|
+
| [Operational Features](advanced_patterns/operational.md) | Runtime control, limits, and guardrails |
|
|
77
|
+
|
|
78
|
+
### Built-in Tools for Self-Evolution
|
|
79
|
+
|
|
80
|
+
Built-in tools enable granular runtime modifications:
|
|
81
|
+
|
|
82
|
+
| Built-in | Description |
|
|
83
|
+
|----------|-------------|
|
|
84
|
+
| [explore_docs](builtins/explore_docs.md) | Make SOE self-aware by exploring its documentation |
|
|
85
|
+
| [workflows](builtins/workflows.md) | Query, inject, and modify workflows at runtime |
|
|
86
|
+
| [context](builtins/context.md) | Read, update, and copy execution context |
|
|
87
|
+
| [tools](builtins/tools.md) | Discover and dynamically call registered tools |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Philosophy
|
|
92
|
+
|
|
93
|
+
SOE is designed around three core principles:
|
|
94
|
+
|
|
95
|
+
1. **Signal-Driven** — Nodes communicate through signals, not direct calls
|
|
96
|
+
2. **Infrastructure-Agnostic** — Plug in your own databases, LLMs, and distribution mechanisms
|
|
97
|
+
3. **Data-Driven** — Everything flows through context; master the primitives to build anything
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Getting Help
|
|
102
|
+
|
|
103
|
+
- **Source Code**: [github.com/pgarcia14180/soe](https://github.com/pgarcia14180/soe)
|
|
104
|
+
- **PyPI Package**: [pypi.org/project/soe](https://pypi.org/project/soe/)
|