kailash 0.1.5__py3-none-any.whl → 0.2.1__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.
- kailash/__init__.py +1 -1
- kailash/access_control.py +740 -0
- kailash/api/__main__.py +6 -0
- kailash/api/auth.py +668 -0
- kailash/api/custom_nodes.py +285 -0
- kailash/api/custom_nodes_secure.py +377 -0
- kailash/api/database.py +620 -0
- kailash/api/studio.py +915 -0
- kailash/api/studio_secure.py +893 -0
- kailash/mcp/__init__.py +53 -0
- kailash/mcp/__main__.py +13 -0
- kailash/mcp/ai_registry_server.py +712 -0
- kailash/mcp/client.py +447 -0
- kailash/mcp/client_new.py +334 -0
- kailash/mcp/server.py +293 -0
- kailash/mcp/server_new.py +336 -0
- kailash/mcp/servers/__init__.py +12 -0
- kailash/mcp/servers/ai_registry.py +289 -0
- kailash/nodes/__init__.py +4 -2
- kailash/nodes/ai/__init__.py +2 -0
- kailash/nodes/ai/a2a.py +714 -67
- kailash/nodes/ai/intelligent_agent_orchestrator.py +31 -37
- kailash/nodes/ai/iterative_llm_agent.py +1280 -0
- kailash/nodes/ai/llm_agent.py +324 -1
- kailash/nodes/ai/self_organizing.py +5 -6
- kailash/nodes/base.py +15 -2
- kailash/nodes/base_async.py +45 -0
- kailash/nodes/base_cycle_aware.py +374 -0
- kailash/nodes/base_with_acl.py +338 -0
- kailash/nodes/code/python.py +135 -27
- kailash/nodes/data/__init__.py +1 -2
- kailash/nodes/data/readers.py +16 -6
- kailash/nodes/data/sql.py +699 -256
- kailash/nodes/data/writers.py +16 -6
- kailash/nodes/logic/__init__.py +8 -0
- kailash/nodes/logic/convergence.py +642 -0
- kailash/nodes/logic/loop.py +153 -0
- kailash/nodes/logic/operations.py +187 -27
- kailash/nodes/mixins/__init__.py +11 -0
- kailash/nodes/mixins/mcp.py +228 -0
- kailash/nodes/mixins.py +387 -0
- kailash/runtime/__init__.py +2 -1
- kailash/runtime/access_controlled.py +458 -0
- kailash/runtime/local.py +106 -33
- kailash/runtime/parallel_cyclic.py +529 -0
- kailash/sdk_exceptions.py +90 -5
- kailash/security.py +845 -0
- kailash/tracking/manager.py +38 -15
- kailash/tracking/models.py +1 -1
- kailash/tracking/storage/filesystem.py +30 -2
- kailash/utils/__init__.py +8 -0
- kailash/workflow/__init__.py +18 -0
- kailash/workflow/convergence.py +270 -0
- kailash/workflow/cycle_analyzer.py +889 -0
- kailash/workflow/cycle_builder.py +579 -0
- kailash/workflow/cycle_config.py +725 -0
- kailash/workflow/cycle_debugger.py +860 -0
- kailash/workflow/cycle_exceptions.py +615 -0
- kailash/workflow/cycle_profiler.py +741 -0
- kailash/workflow/cycle_state.py +338 -0
- kailash/workflow/cyclic_runner.py +985 -0
- kailash/workflow/graph.py +500 -39
- kailash/workflow/migration.py +809 -0
- kailash/workflow/safety.py +365 -0
- kailash/workflow/templates.py +763 -0
- kailash/workflow/validation.py +751 -0
- {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/METADATA +259 -12
- kailash-0.2.1.dist-info/RECORD +125 -0
- kailash/nodes/mcp/__init__.py +0 -11
- kailash/nodes/mcp/client.py +0 -554
- kailash/nodes/mcp/resource.py +0 -682
- kailash/nodes/mcp/server.py +0 -577
- kailash-0.1.5.dist-info/RECORD +0 -88
- {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/WHEEL +0 -0
- {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/entry_points.txt +0 -0
- {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,763 @@
|
|
1
|
+
"""
|
2
|
+
Pre-Built Workflow Templates for Common Cyclic Patterns.
|
3
|
+
|
4
|
+
This module provides a comprehensive collection of pre-built cycle templates
|
5
|
+
and patterns that dramatically simplify the creation of common workflow
|
6
|
+
structures. It eliminates boilerplate code and ensures best practices are
|
7
|
+
followed automatically for standard cyclic workflow patterns.
|
8
|
+
|
9
|
+
Design Philosophy:
|
10
|
+
Provides curated, battle-tested templates for common cycle patterns,
|
11
|
+
reducing development time and ensuring optimal configurations. Each
|
12
|
+
template encapsulates best practices and proven patterns for specific
|
13
|
+
use cases with sensible defaults and customization options.
|
14
|
+
|
15
|
+
Key Features:
|
16
|
+
- Pre-built templates for common patterns
|
17
|
+
- Automatic best-practice configuration
|
18
|
+
- Customizable parameters with validation
|
19
|
+
- Generated helper nodes for complex patterns
|
20
|
+
- Workflow class extensions for seamless integration
|
21
|
+
|
22
|
+
Template Categories:
|
23
|
+
- Optimization Cycles: Iterative improvement patterns
|
24
|
+
- Retry Cycles: Error recovery and fault tolerance
|
25
|
+
- Data Quality Cycles: Iterative data cleaning and validation
|
26
|
+
- Learning Cycles: Machine learning training patterns
|
27
|
+
- Convergence Cycles: Numerical convergence patterns
|
28
|
+
- Batch Processing Cycles: Large dataset processing patterns
|
29
|
+
|
30
|
+
Core Components:
|
31
|
+
- CycleTemplate: Configuration dataclass for templates
|
32
|
+
- CycleTemplates: Static factory methods for template creation
|
33
|
+
- Generated helper nodes for pattern-specific logic
|
34
|
+
- Workflow class extensions for direct integration
|
35
|
+
|
36
|
+
Automatic Optimizations:
|
37
|
+
- Pattern-specific convergence conditions
|
38
|
+
- Appropriate safety limits and timeouts
|
39
|
+
- Optimal iteration limits for each pattern
|
40
|
+
- Memory management for data-intensive patterns
|
41
|
+
- Error handling and recovery strategies
|
42
|
+
|
43
|
+
Upstream Dependencies:
|
44
|
+
- Core workflow and node implementations
|
45
|
+
- PythonCodeNode for generated helper logic
|
46
|
+
- SwitchNode for conditional routing patterns
|
47
|
+
- Convergence and safety systems
|
48
|
+
|
49
|
+
Downstream Consumers:
|
50
|
+
- Workflow builders and automation tools
|
51
|
+
- Template-based workflow generation systems
|
52
|
+
- Development tools and IDEs
|
53
|
+
- Educational and training materials
|
54
|
+
|
55
|
+
Examples:
|
56
|
+
Optimization cycle template:
|
57
|
+
|
58
|
+
>>> from kailash.workflow.templates import CycleTemplates
|
59
|
+
>>> workflow = Workflow("optimization", "Quality Optimization")
|
60
|
+
>>> workflow.add_node("processor", ProcessorNode())
|
61
|
+
>>> workflow.add_node("evaluator", EvaluatorNode())
|
62
|
+
>>> cycle_id = CycleTemplates.optimization_cycle(
|
63
|
+
... workflow,
|
64
|
+
... processor_node="processor",
|
65
|
+
... evaluator_node="evaluator",
|
66
|
+
... convergence="quality > 0.95",
|
67
|
+
... max_iterations=100
|
68
|
+
... )
|
69
|
+
|
70
|
+
Retry cycle with backoff:
|
71
|
+
|
72
|
+
>>> cycle_id = CycleTemplates.retry_cycle(
|
73
|
+
... workflow,
|
74
|
+
... target_node="api_call",
|
75
|
+
... max_retries=5,
|
76
|
+
... backoff_strategy="exponential",
|
77
|
+
... success_condition="success == True"
|
78
|
+
... )
|
79
|
+
|
80
|
+
Direct workflow integration:
|
81
|
+
|
82
|
+
>>> # Templates extend Workflow class
|
83
|
+
>>> workflow = Workflow("ml_training", "Model Training")
|
84
|
+
>>> cycle_id = workflow.add_learning_cycle(
|
85
|
+
... trainer_node="trainer",
|
86
|
+
... evaluator_node="evaluator",
|
87
|
+
... target_accuracy=0.98,
|
88
|
+
... early_stopping_patience=10
|
89
|
+
... )
|
90
|
+
|
91
|
+
Custom template configuration:
|
92
|
+
|
93
|
+
>>> # Numerical convergence with custom tolerance
|
94
|
+
>>> cycle_id = workflow.add_convergence_cycle(
|
95
|
+
... processor_node="newton_raphson",
|
96
|
+
... tolerance=0.0001,
|
97
|
+
max_iterations=1000
|
98
|
+
)
|
99
|
+
|
100
|
+
# Batch processing for large datasets
|
101
|
+
cycle_id = workflow.add_batch_processing_cycle(
|
102
|
+
processor_node="data_processor",
|
103
|
+
batch_size=1000,
|
104
|
+
total_items=1000000
|
105
|
+
)
|
106
|
+
|
107
|
+
See Also:
|
108
|
+
- :mod:`kailash.workflow.cycle_config` for advanced configuration
|
109
|
+
- :mod:`kailash.workflow.cycle_builder` for custom cycle creation
|
110
|
+
- :doc:`/examples/patterns` for comprehensive pattern examples
|
111
|
+
"""
|
112
|
+
|
113
|
+
import math
|
114
|
+
import time
|
115
|
+
from dataclasses import dataclass
|
116
|
+
from typing import Any, Dict, List, Optional
|
117
|
+
|
118
|
+
from ..nodes.code import PythonCodeNode
|
119
|
+
from . import Workflow
|
120
|
+
|
121
|
+
|
122
|
+
@dataclass
|
123
|
+
class CycleTemplate:
|
124
|
+
"""Configuration for a cycle template."""
|
125
|
+
|
126
|
+
name: str
|
127
|
+
description: str
|
128
|
+
nodes: List[str]
|
129
|
+
convergence_condition: Optional[str] = None
|
130
|
+
max_iterations: int = 100
|
131
|
+
timeout: Optional[float] = None
|
132
|
+
parameters: Optional[Dict[str, Any]] = None
|
133
|
+
|
134
|
+
|
135
|
+
class CycleTemplates:
|
136
|
+
"""Collection of pre-built cycle templates for common patterns."""
|
137
|
+
|
138
|
+
@staticmethod
|
139
|
+
def optimization_cycle(
|
140
|
+
workflow: Workflow,
|
141
|
+
processor_node: str,
|
142
|
+
evaluator_node: str,
|
143
|
+
convergence: str = "quality > 0.9",
|
144
|
+
max_iterations: int = 50,
|
145
|
+
cycle_id: Optional[str] = None,
|
146
|
+
) -> str:
|
147
|
+
"""
|
148
|
+
Add an optimization cycle pattern to workflow.
|
149
|
+
|
150
|
+
Creates a cycle where a processor generates solutions and an evaluator
|
151
|
+
assesses quality, continuing until convergence criteria is met.
|
152
|
+
|
153
|
+
Args:
|
154
|
+
workflow: Target workflow
|
155
|
+
processor_node: Node that generates/improves solutions
|
156
|
+
evaluator_node: Node that evaluates solution quality
|
157
|
+
convergence: Convergence condition (e.g., "quality > 0.9")
|
158
|
+
max_iterations: Maximum iterations before stopping
|
159
|
+
cycle_id: Optional custom cycle identifier
|
160
|
+
|
161
|
+
Returns:
|
162
|
+
str: The cycle identifier for reference
|
163
|
+
|
164
|
+
Example:
|
165
|
+
>>> workflow = Workflow("optimization", "Optimization Example")
|
166
|
+
>>> workflow.add_node("processor", PythonCodeNode(), code="...")
|
167
|
+
>>> workflow.add_node("evaluator", PythonCodeNode(), code="...")
|
168
|
+
>>> cycle_id = CycleTemplates.optimization_cycle(
|
169
|
+
... workflow, "processor", "evaluator",
|
170
|
+
... convergence="quality > 0.95", max_iterations=100
|
171
|
+
... )
|
172
|
+
"""
|
173
|
+
if cycle_id is None:
|
174
|
+
cycle_id = f"optimization_cycle_{int(time.time())}"
|
175
|
+
|
176
|
+
# Connect processor to evaluator
|
177
|
+
workflow.connect(processor_node, evaluator_node)
|
178
|
+
|
179
|
+
# Close the cycle with convergence condition
|
180
|
+
workflow.connect(
|
181
|
+
evaluator_node,
|
182
|
+
processor_node,
|
183
|
+
cycle=True,
|
184
|
+
max_iterations=max_iterations,
|
185
|
+
convergence_check=convergence,
|
186
|
+
cycle_id=cycle_id,
|
187
|
+
)
|
188
|
+
|
189
|
+
return cycle_id
|
190
|
+
|
191
|
+
@staticmethod
|
192
|
+
def retry_cycle(
|
193
|
+
workflow: Workflow,
|
194
|
+
target_node: str,
|
195
|
+
max_retries: int = 3,
|
196
|
+
backoff_strategy: str = "exponential",
|
197
|
+
success_condition: str = "success == True",
|
198
|
+
cycle_id: Optional[str] = None,
|
199
|
+
) -> str:
|
200
|
+
"""
|
201
|
+
Add a retry cycle pattern to workflow.
|
202
|
+
|
203
|
+
Creates a cycle that retries a node operation with configurable
|
204
|
+
backoff strategy until success or max retries reached.
|
205
|
+
|
206
|
+
Args:
|
207
|
+
workflow: Target workflow
|
208
|
+
target_node: Node to retry on failure
|
209
|
+
max_retries: Maximum number of retry attempts
|
210
|
+
backoff_strategy: Backoff strategy ("linear", "exponential", "fixed")
|
211
|
+
success_condition: Condition that indicates success
|
212
|
+
cycle_id: Optional custom cycle identifier
|
213
|
+
|
214
|
+
Returns:
|
215
|
+
str: The cycle identifier for reference
|
216
|
+
|
217
|
+
Example:
|
218
|
+
>>> workflow = Workflow("retry", "Retry Example")
|
219
|
+
>>> workflow.add_node("api_call", PythonCodeNode(), code="...")
|
220
|
+
>>> cycle_id = CycleTemplates.retry_cycle(
|
221
|
+
... workflow, "api_call", max_retries=5,
|
222
|
+
... backoff_strategy="exponential"
|
223
|
+
... )
|
224
|
+
"""
|
225
|
+
if cycle_id is None:
|
226
|
+
cycle_id = f"retry_cycle_{int(time.time())}"
|
227
|
+
|
228
|
+
# Create retry controller node
|
229
|
+
retry_controller_id = f"{target_node}_retry_controller"
|
230
|
+
|
231
|
+
retry_code = f"""
|
232
|
+
import time
|
233
|
+
import random
|
234
|
+
|
235
|
+
# Initialize retry state
|
236
|
+
try:
|
237
|
+
attempt = attempt
|
238
|
+
backoff_time = backoff_time
|
239
|
+
except NameError:
|
240
|
+
attempt = 0
|
241
|
+
backoff_time = 1.0
|
242
|
+
|
243
|
+
attempt += 1
|
244
|
+
|
245
|
+
# Check if we should retry
|
246
|
+
should_retry = attempt <= {max_retries}
|
247
|
+
final_attempt = attempt >= {max_retries}
|
248
|
+
|
249
|
+
# Calculate backoff delay
|
250
|
+
if "{backoff_strategy}" == "exponential":
|
251
|
+
backoff_time = min(60, 2 ** (attempt - 1))
|
252
|
+
elif "{backoff_strategy}" == "linear":
|
253
|
+
backoff_time = attempt * 1.0
|
254
|
+
else: # fixed
|
255
|
+
backoff_time = 1.0
|
256
|
+
|
257
|
+
# Add jitter to prevent thundering herd
|
258
|
+
jitter = random.uniform(0.1, 0.3) * backoff_time
|
259
|
+
actual_delay = backoff_time + jitter
|
260
|
+
|
261
|
+
print(f"Retry attempt {{attempt}}/{max_retries}, delay: {{actual_delay:.2f}}s")
|
262
|
+
|
263
|
+
# Simulate delay (in real scenario, this would be handled by scheduler)
|
264
|
+
if attempt > 1:
|
265
|
+
time.sleep(min(actual_delay, 5.0)) # Cap delay for examples
|
266
|
+
|
267
|
+
result = {{
|
268
|
+
"attempt": attempt,
|
269
|
+
"should_retry": should_retry,
|
270
|
+
"final_attempt": final_attempt,
|
271
|
+
"backoff_time": backoff_time,
|
272
|
+
"retry_exhausted": attempt > {max_retries}
|
273
|
+
}}
|
274
|
+
"""
|
275
|
+
|
276
|
+
workflow.add_node(
|
277
|
+
retry_controller_id,
|
278
|
+
PythonCodeNode(name=retry_controller_id, code=retry_code),
|
279
|
+
)
|
280
|
+
|
281
|
+
# Connect retry controller to target node
|
282
|
+
workflow.connect(retry_controller_id, target_node)
|
283
|
+
|
284
|
+
# Close the cycle with retry logic
|
285
|
+
workflow.connect(
|
286
|
+
target_node,
|
287
|
+
retry_controller_id,
|
288
|
+
cycle=True,
|
289
|
+
max_iterations=max_retries + 1,
|
290
|
+
convergence_check=f"({success_condition}) or (retry_exhausted == True)",
|
291
|
+
cycle_id=cycle_id,
|
292
|
+
)
|
293
|
+
|
294
|
+
return cycle_id
|
295
|
+
|
296
|
+
@staticmethod
|
297
|
+
def data_quality_cycle(
|
298
|
+
workflow: Workflow,
|
299
|
+
cleaner_node: str,
|
300
|
+
validator_node: str,
|
301
|
+
quality_threshold: float = 0.95,
|
302
|
+
max_iterations: int = 10,
|
303
|
+
cycle_id: Optional[str] = None,
|
304
|
+
) -> str:
|
305
|
+
"""
|
306
|
+
Add a data quality improvement cycle to workflow.
|
307
|
+
|
308
|
+
Creates a cycle where data is cleaned and validated iteratively
|
309
|
+
until quality threshold is met.
|
310
|
+
|
311
|
+
Args:
|
312
|
+
workflow: Target workflow
|
313
|
+
cleaner_node: Node that cleans/improves data
|
314
|
+
validator_node: Node that validates data quality
|
315
|
+
quality_threshold: Minimum quality score to achieve
|
316
|
+
max_iterations: Maximum cleaning iterations
|
317
|
+
cycle_id: Optional custom cycle identifier
|
318
|
+
|
319
|
+
Returns:
|
320
|
+
str: The cycle identifier for reference
|
321
|
+
|
322
|
+
Example:
|
323
|
+
>>> workflow = Workflow("data_quality", "Data Quality Example")
|
324
|
+
>>> workflow.add_node("cleaner", PythonCodeNode(), code="...")
|
325
|
+
>>> workflow.add_node("validator", PythonCodeNode(), code="...")
|
326
|
+
>>> cycle_id = CycleTemplates.data_quality_cycle(
|
327
|
+
... workflow, "cleaner", "validator", quality_threshold=0.98
|
328
|
+
... )
|
329
|
+
"""
|
330
|
+
if cycle_id is None:
|
331
|
+
cycle_id = f"data_quality_cycle_{int(time.time())}"
|
332
|
+
|
333
|
+
# Connect cleaner to validator
|
334
|
+
workflow.connect(cleaner_node, validator_node)
|
335
|
+
|
336
|
+
# Close the cycle with quality threshold
|
337
|
+
workflow.connect(
|
338
|
+
validator_node,
|
339
|
+
cleaner_node,
|
340
|
+
cycle=True,
|
341
|
+
max_iterations=max_iterations,
|
342
|
+
convergence_check=f"quality_score >= {quality_threshold}",
|
343
|
+
cycle_id=cycle_id,
|
344
|
+
)
|
345
|
+
|
346
|
+
return cycle_id
|
347
|
+
|
348
|
+
@staticmethod
|
349
|
+
def learning_cycle(
|
350
|
+
workflow: Workflow,
|
351
|
+
trainer_node: str,
|
352
|
+
evaluator_node: str,
|
353
|
+
target_accuracy: float = 0.95,
|
354
|
+
max_epochs: int = 100,
|
355
|
+
early_stopping_patience: int = 10,
|
356
|
+
cycle_id: Optional[str] = None,
|
357
|
+
) -> str:
|
358
|
+
"""
|
359
|
+
Add a machine learning training cycle to workflow.
|
360
|
+
|
361
|
+
Creates a cycle for iterative model training with early stopping
|
362
|
+
based on validation performance.
|
363
|
+
|
364
|
+
Args:
|
365
|
+
workflow: Target workflow
|
366
|
+
trainer_node: Node that trains the model
|
367
|
+
evaluator_node: Node that evaluates model performance
|
368
|
+
target_accuracy: Target accuracy to achieve
|
369
|
+
max_epochs: Maximum training epochs
|
370
|
+
early_stopping_patience: Epochs to wait without improvement
|
371
|
+
cycle_id: Optional custom cycle identifier
|
372
|
+
|
373
|
+
Returns:
|
374
|
+
str: The cycle identifier for reference
|
375
|
+
|
376
|
+
Example:
|
377
|
+
>>> workflow = Workflow("ml_training", "ML Training Example")
|
378
|
+
>>> workflow.add_node("trainer", PythonCodeNode(), code="...")
|
379
|
+
>>> workflow.add_node("evaluator", PythonCodeNode(), code="...")
|
380
|
+
>>> cycle_id = CycleTemplates.learning_cycle(
|
381
|
+
... workflow, "trainer", "evaluator", target_accuracy=0.98
|
382
|
+
... )
|
383
|
+
"""
|
384
|
+
if cycle_id is None:
|
385
|
+
cycle_id = f"learning_cycle_{int(time.time())}"
|
386
|
+
|
387
|
+
# Create early stopping controller
|
388
|
+
early_stop_controller_id = f"{trainer_node}_early_stop"
|
389
|
+
|
390
|
+
early_stop_code = f"""
|
391
|
+
# Initialize early stopping state
|
392
|
+
try:
|
393
|
+
best_accuracy = best_accuracy
|
394
|
+
epochs_without_improvement = epochs_without_improvement
|
395
|
+
epoch = epoch
|
396
|
+
except NameError:
|
397
|
+
best_accuracy = 0.0
|
398
|
+
epochs_without_improvement = 0
|
399
|
+
epoch = 0
|
400
|
+
|
401
|
+
epoch += 1
|
402
|
+
|
403
|
+
# Get current accuracy from evaluator
|
404
|
+
current_accuracy = accuracy if 'accuracy' in locals() else 0.0
|
405
|
+
|
406
|
+
# Check for improvement
|
407
|
+
if current_accuracy > best_accuracy:
|
408
|
+
best_accuracy = current_accuracy
|
409
|
+
epochs_without_improvement = 0
|
410
|
+
improved = True
|
411
|
+
else:
|
412
|
+
epochs_without_improvement += 1
|
413
|
+
improved = False
|
414
|
+
|
415
|
+
# Determine if should continue training
|
416
|
+
target_reached = current_accuracy >= {target_accuracy}
|
417
|
+
early_stop = epochs_without_improvement >= {early_stopping_patience}
|
418
|
+
max_epochs_reached = epoch >= {max_epochs}
|
419
|
+
|
420
|
+
should_continue = not (target_reached or early_stop or max_epochs_reached)
|
421
|
+
|
422
|
+
print(f"Epoch {{epoch}}: accuracy={{current_accuracy:.4f}}, best={{best_accuracy:.4f}}")
|
423
|
+
if not improved:
|
424
|
+
print(f"No improvement for {{epochs_without_improvement}} epochs")
|
425
|
+
|
426
|
+
result = {{
|
427
|
+
"epoch": epoch,
|
428
|
+
"current_accuracy": current_accuracy,
|
429
|
+
"best_accuracy": best_accuracy,
|
430
|
+
"epochs_without_improvement": epochs_without_improvement,
|
431
|
+
"should_continue": should_continue,
|
432
|
+
"target_reached": target_reached,
|
433
|
+
"early_stopped": early_stop,
|
434
|
+
"training_complete": not should_continue
|
435
|
+
}}
|
436
|
+
"""
|
437
|
+
|
438
|
+
workflow.add_node(
|
439
|
+
early_stop_controller_id,
|
440
|
+
PythonCodeNode(name=early_stop_controller_id, code=early_stop_code),
|
441
|
+
)
|
442
|
+
|
443
|
+
# Connect the training cycle
|
444
|
+
workflow.connect(trainer_node, evaluator_node)
|
445
|
+
workflow.connect(evaluator_node, early_stop_controller_id)
|
446
|
+
|
447
|
+
# Close the cycle with early stopping logic
|
448
|
+
workflow.connect(
|
449
|
+
early_stop_controller_id,
|
450
|
+
trainer_node,
|
451
|
+
cycle=True,
|
452
|
+
max_iterations=max_epochs,
|
453
|
+
convergence_check="training_complete == True",
|
454
|
+
cycle_id=cycle_id,
|
455
|
+
)
|
456
|
+
|
457
|
+
return cycle_id
|
458
|
+
|
459
|
+
@staticmethod
|
460
|
+
def convergence_cycle(
|
461
|
+
workflow: Workflow,
|
462
|
+
processor_node: str,
|
463
|
+
tolerance: float = 0.001,
|
464
|
+
max_iterations: int = 1000,
|
465
|
+
cycle_id: Optional[str] = None,
|
466
|
+
) -> str:
|
467
|
+
"""
|
468
|
+
Add a numerical convergence cycle to workflow.
|
469
|
+
|
470
|
+
Creates a cycle that continues until successive iterations
|
471
|
+
produce values within a specified tolerance.
|
472
|
+
|
473
|
+
Args:
|
474
|
+
workflow: Target workflow
|
475
|
+
processor_node: Node that produces values to check for convergence
|
476
|
+
tolerance: Maximum difference between iterations for convergence
|
477
|
+
max_iterations: Maximum iterations before forced termination
|
478
|
+
cycle_id: Optional custom cycle identifier
|
479
|
+
|
480
|
+
Returns:
|
481
|
+
str: The cycle identifier for reference
|
482
|
+
|
483
|
+
Example:
|
484
|
+
>>> workflow = Workflow("convergence", "Convergence Example")
|
485
|
+
>>> workflow.add_node("processor", PythonCodeNode(), code="...")
|
486
|
+
>>> cycle_id = CycleTemplates.convergence_cycle(
|
487
|
+
... workflow, "processor", tolerance=0.0001
|
488
|
+
... )
|
489
|
+
"""
|
490
|
+
if cycle_id is None:
|
491
|
+
cycle_id = f"convergence_cycle_{int(time.time())}"
|
492
|
+
|
493
|
+
# Create convergence checker node
|
494
|
+
convergence_checker_id = f"{processor_node}_convergence_checker"
|
495
|
+
|
496
|
+
convergence_code = f"""
|
497
|
+
import math
|
498
|
+
|
499
|
+
# Initialize convergence state
|
500
|
+
try:
|
501
|
+
previous_value = previous_value
|
502
|
+
iteration = iteration
|
503
|
+
except NameError:
|
504
|
+
previous_value = None
|
505
|
+
iteration = 0
|
506
|
+
|
507
|
+
iteration += 1
|
508
|
+
|
509
|
+
# Get current value (assume processor outputs 'value' field)
|
510
|
+
current_value = value if 'value' in locals() else 0.0
|
511
|
+
|
512
|
+
# Check convergence
|
513
|
+
if previous_value is not None:
|
514
|
+
difference = abs(current_value - previous_value)
|
515
|
+
converged = difference <= {tolerance}
|
516
|
+
relative_change = difference / abs(previous_value) if previous_value != 0 else float('inf')
|
517
|
+
else:
|
518
|
+
difference = float('inf')
|
519
|
+
converged = False
|
520
|
+
relative_change = float('inf')
|
521
|
+
|
522
|
+
print(f"Iteration {{iteration}}: value={{current_value:.6f}}, diff={{difference:.6f}}, converged={{converged}}")
|
523
|
+
|
524
|
+
result = {{
|
525
|
+
"iteration": iteration,
|
526
|
+
"current_value": current_value,
|
527
|
+
"previous_value": previous_value,
|
528
|
+
"difference": difference,
|
529
|
+
"relative_change": relative_change,
|
530
|
+
"converged": converged,
|
531
|
+
"tolerance": {tolerance}
|
532
|
+
}}
|
533
|
+
|
534
|
+
# Update for next iteration
|
535
|
+
previous_value = current_value
|
536
|
+
"""
|
537
|
+
|
538
|
+
workflow.add_node(
|
539
|
+
convergence_checker_id,
|
540
|
+
PythonCodeNode(name=convergence_checker_id, code=convergence_code),
|
541
|
+
)
|
542
|
+
|
543
|
+
# Connect processor to convergence checker
|
544
|
+
workflow.connect(processor_node, convergence_checker_id)
|
545
|
+
|
546
|
+
# Close the cycle with convergence condition
|
547
|
+
workflow.connect(
|
548
|
+
convergence_checker_id,
|
549
|
+
processor_node,
|
550
|
+
cycle=True,
|
551
|
+
max_iterations=max_iterations,
|
552
|
+
convergence_check="converged == True",
|
553
|
+
cycle_id=cycle_id,
|
554
|
+
)
|
555
|
+
|
556
|
+
return cycle_id
|
557
|
+
|
558
|
+
@staticmethod
|
559
|
+
def batch_processing_cycle(
|
560
|
+
workflow: Workflow,
|
561
|
+
processor_node: str,
|
562
|
+
batch_size: int = 100,
|
563
|
+
total_items: Optional[int] = None,
|
564
|
+
cycle_id: Optional[str] = None,
|
565
|
+
) -> str:
|
566
|
+
"""
|
567
|
+
Add a batch processing cycle to workflow.
|
568
|
+
|
569
|
+
Creates a cycle that processes data in batches, continuing
|
570
|
+
until all items are processed.
|
571
|
+
|
572
|
+
Args:
|
573
|
+
workflow: Target workflow
|
574
|
+
processor_node: Node that processes batches
|
575
|
+
batch_size: Number of items to process per batch
|
576
|
+
total_items: Total number of items to process (if known)
|
577
|
+
cycle_id: Optional custom cycle identifier
|
578
|
+
|
579
|
+
Returns:
|
580
|
+
str: The cycle identifier for reference
|
581
|
+
|
582
|
+
Example:
|
583
|
+
>>> workflow = Workflow("batch", "Batch Processing Example")
|
584
|
+
>>> workflow.add_node("processor", PythonCodeNode(), code="...")
|
585
|
+
>>> cycle_id = CycleTemplates.batch_processing_cycle(
|
586
|
+
... workflow, "processor", batch_size=50, total_items=1000
|
587
|
+
... )
|
588
|
+
"""
|
589
|
+
if cycle_id is None:
|
590
|
+
cycle_id = f"batch_cycle_{int(time.time())}"
|
591
|
+
|
592
|
+
# Create batch controller node
|
593
|
+
batch_controller_id = f"{processor_node}_batch_controller"
|
594
|
+
|
595
|
+
batch_code = f"""
|
596
|
+
# Initialize batch state
|
597
|
+
try:
|
598
|
+
batch_number = batch_number
|
599
|
+
items_processed = items_processed
|
600
|
+
start_index = start_index
|
601
|
+
except NameError:
|
602
|
+
batch_number = 0
|
603
|
+
items_processed = 0
|
604
|
+
start_index = 0
|
605
|
+
|
606
|
+
batch_number += 1
|
607
|
+
end_index = start_index + {batch_size}
|
608
|
+
|
609
|
+
# Calculate progress
|
610
|
+
if {total_items} is not None:
|
611
|
+
remaining_items = max(0, {total_items} - items_processed)
|
612
|
+
actual_batch_size = min({batch_size}, remaining_items)
|
613
|
+
progress_percentage = (items_processed / {total_items}) * 100
|
614
|
+
all_processed = items_processed >= {total_items}
|
615
|
+
else:
|
616
|
+
# If total unknown, rely on processor to indicate completion
|
617
|
+
actual_batch_size = {batch_size}
|
618
|
+
progress_percentage = None
|
619
|
+
all_processed = False # Will be determined by processor
|
620
|
+
|
621
|
+
print(f"Processing batch {{batch_number}}: items {{start_index}}-{{end_index-1}}")
|
622
|
+
if progress_percentage is not None:
|
623
|
+
print(f"Progress: {{progress_percentage:.1f}}% ({{items_processed}}/{total_items})")
|
624
|
+
|
625
|
+
result = {{
|
626
|
+
"batch_number": batch_number,
|
627
|
+
"start_index": start_index,
|
628
|
+
"end_index": end_index,
|
629
|
+
"batch_size": actual_batch_size,
|
630
|
+
"items_processed": items_processed,
|
631
|
+
"all_processed": all_processed,
|
632
|
+
"progress_percentage": progress_percentage
|
633
|
+
}}
|
634
|
+
|
635
|
+
# Update for next iteration
|
636
|
+
start_index = end_index
|
637
|
+
items_processed += actual_batch_size
|
638
|
+
"""
|
639
|
+
|
640
|
+
workflow.add_node(
|
641
|
+
batch_controller_id,
|
642
|
+
PythonCodeNode(name=batch_controller_id, code=batch_code),
|
643
|
+
)
|
644
|
+
|
645
|
+
# Connect batch controller to processor
|
646
|
+
workflow.connect(batch_controller_id, processor_node)
|
647
|
+
|
648
|
+
# Calculate max iterations based on total items
|
649
|
+
if total_items is not None:
|
650
|
+
max_iterations = math.ceil(total_items / batch_size) + 1
|
651
|
+
else:
|
652
|
+
max_iterations = 1000 # Default upper bound
|
653
|
+
|
654
|
+
# Close the cycle with completion condition
|
655
|
+
workflow.connect(
|
656
|
+
processor_node,
|
657
|
+
batch_controller_id,
|
658
|
+
cycle=True,
|
659
|
+
max_iterations=max_iterations,
|
660
|
+
convergence_check="all_processed == True",
|
661
|
+
cycle_id=cycle_id,
|
662
|
+
)
|
663
|
+
|
664
|
+
return cycle_id
|
665
|
+
|
666
|
+
|
667
|
+
# Convenience methods to add to Workflow class
|
668
|
+
def add_optimization_cycle(
|
669
|
+
self,
|
670
|
+
processor_node: str,
|
671
|
+
evaluator_node: str,
|
672
|
+
convergence: str = "quality > 0.9",
|
673
|
+
max_iterations: int = 50,
|
674
|
+
cycle_id: Optional[str] = None,
|
675
|
+
) -> str:
|
676
|
+
"""Add an optimization cycle pattern to this workflow."""
|
677
|
+
return CycleTemplates.optimization_cycle(
|
678
|
+
self, processor_node, evaluator_node, convergence, max_iterations, cycle_id
|
679
|
+
)
|
680
|
+
|
681
|
+
|
682
|
+
def add_retry_cycle(
|
683
|
+
self,
|
684
|
+
target_node: str,
|
685
|
+
max_retries: int = 3,
|
686
|
+
backoff_strategy: str = "exponential",
|
687
|
+
success_condition: str = "success == True",
|
688
|
+
cycle_id: Optional[str] = None,
|
689
|
+
) -> str:
|
690
|
+
"""Add a retry cycle pattern to this workflow."""
|
691
|
+
return CycleTemplates.retry_cycle(
|
692
|
+
self, target_node, max_retries, backoff_strategy, success_condition, cycle_id
|
693
|
+
)
|
694
|
+
|
695
|
+
|
696
|
+
def add_data_quality_cycle(
|
697
|
+
self,
|
698
|
+
cleaner_node: str,
|
699
|
+
validator_node: str,
|
700
|
+
quality_threshold: float = 0.95,
|
701
|
+
max_iterations: int = 10,
|
702
|
+
cycle_id: Optional[str] = None,
|
703
|
+
) -> str:
|
704
|
+
"""Add a data quality improvement cycle to this workflow."""
|
705
|
+
return CycleTemplates.data_quality_cycle(
|
706
|
+
self, cleaner_node, validator_node, quality_threshold, max_iterations, cycle_id
|
707
|
+
)
|
708
|
+
|
709
|
+
|
710
|
+
def add_learning_cycle(
|
711
|
+
self,
|
712
|
+
trainer_node: str,
|
713
|
+
evaluator_node: str,
|
714
|
+
target_accuracy: float = 0.95,
|
715
|
+
max_epochs: int = 100,
|
716
|
+
early_stopping_patience: int = 10,
|
717
|
+
cycle_id: Optional[str] = None,
|
718
|
+
) -> str:
|
719
|
+
"""Add a machine learning training cycle to this workflow."""
|
720
|
+
return CycleTemplates.learning_cycle(
|
721
|
+
self,
|
722
|
+
trainer_node,
|
723
|
+
evaluator_node,
|
724
|
+
target_accuracy,
|
725
|
+
max_epochs,
|
726
|
+
early_stopping_patience,
|
727
|
+
cycle_id,
|
728
|
+
)
|
729
|
+
|
730
|
+
|
731
|
+
def add_convergence_cycle(
|
732
|
+
self,
|
733
|
+
processor_node: str,
|
734
|
+
tolerance: float = 0.001,
|
735
|
+
max_iterations: int = 1000,
|
736
|
+
cycle_id: Optional[str] = None,
|
737
|
+
) -> str:
|
738
|
+
"""Add a numerical convergence cycle to this workflow."""
|
739
|
+
return CycleTemplates.convergence_cycle(
|
740
|
+
self, processor_node, tolerance, max_iterations, cycle_id
|
741
|
+
)
|
742
|
+
|
743
|
+
|
744
|
+
def add_batch_processing_cycle(
|
745
|
+
self,
|
746
|
+
processor_node: str,
|
747
|
+
batch_size: int = 100,
|
748
|
+
total_items: Optional[int] = None,
|
749
|
+
cycle_id: Optional[str] = None,
|
750
|
+
) -> str:
|
751
|
+
"""Add a batch processing cycle to this workflow."""
|
752
|
+
return CycleTemplates.batch_processing_cycle(
|
753
|
+
self, processor_node, batch_size, total_items, cycle_id
|
754
|
+
)
|
755
|
+
|
756
|
+
|
757
|
+
# Add convenience methods to Workflow class
|
758
|
+
Workflow.add_optimization_cycle = add_optimization_cycle
|
759
|
+
Workflow.add_retry_cycle = add_retry_cycle
|
760
|
+
Workflow.add_data_quality_cycle = add_data_quality_cycle
|
761
|
+
Workflow.add_learning_cycle = add_learning_cycle
|
762
|
+
Workflow.add_convergence_cycle = add_convergence_cycle
|
763
|
+
Workflow.add_batch_processing_cycle = add_batch_processing_cycle
|