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.
Files changed (77) hide show
  1. kailash/__init__.py +1 -1
  2. kailash/access_control.py +740 -0
  3. kailash/api/__main__.py +6 -0
  4. kailash/api/auth.py +668 -0
  5. kailash/api/custom_nodes.py +285 -0
  6. kailash/api/custom_nodes_secure.py +377 -0
  7. kailash/api/database.py +620 -0
  8. kailash/api/studio.py +915 -0
  9. kailash/api/studio_secure.py +893 -0
  10. kailash/mcp/__init__.py +53 -0
  11. kailash/mcp/__main__.py +13 -0
  12. kailash/mcp/ai_registry_server.py +712 -0
  13. kailash/mcp/client.py +447 -0
  14. kailash/mcp/client_new.py +334 -0
  15. kailash/mcp/server.py +293 -0
  16. kailash/mcp/server_new.py +336 -0
  17. kailash/mcp/servers/__init__.py +12 -0
  18. kailash/mcp/servers/ai_registry.py +289 -0
  19. kailash/nodes/__init__.py +4 -2
  20. kailash/nodes/ai/__init__.py +2 -0
  21. kailash/nodes/ai/a2a.py +714 -67
  22. kailash/nodes/ai/intelligent_agent_orchestrator.py +31 -37
  23. kailash/nodes/ai/iterative_llm_agent.py +1280 -0
  24. kailash/nodes/ai/llm_agent.py +324 -1
  25. kailash/nodes/ai/self_organizing.py +5 -6
  26. kailash/nodes/base.py +15 -2
  27. kailash/nodes/base_async.py +45 -0
  28. kailash/nodes/base_cycle_aware.py +374 -0
  29. kailash/nodes/base_with_acl.py +338 -0
  30. kailash/nodes/code/python.py +135 -27
  31. kailash/nodes/data/__init__.py +1 -2
  32. kailash/nodes/data/readers.py +16 -6
  33. kailash/nodes/data/sql.py +699 -256
  34. kailash/nodes/data/writers.py +16 -6
  35. kailash/nodes/logic/__init__.py +8 -0
  36. kailash/nodes/logic/convergence.py +642 -0
  37. kailash/nodes/logic/loop.py +153 -0
  38. kailash/nodes/logic/operations.py +187 -27
  39. kailash/nodes/mixins/__init__.py +11 -0
  40. kailash/nodes/mixins/mcp.py +228 -0
  41. kailash/nodes/mixins.py +387 -0
  42. kailash/runtime/__init__.py +2 -1
  43. kailash/runtime/access_controlled.py +458 -0
  44. kailash/runtime/local.py +106 -33
  45. kailash/runtime/parallel_cyclic.py +529 -0
  46. kailash/sdk_exceptions.py +90 -5
  47. kailash/security.py +845 -0
  48. kailash/tracking/manager.py +38 -15
  49. kailash/tracking/models.py +1 -1
  50. kailash/tracking/storage/filesystem.py +30 -2
  51. kailash/utils/__init__.py +8 -0
  52. kailash/workflow/__init__.py +18 -0
  53. kailash/workflow/convergence.py +270 -0
  54. kailash/workflow/cycle_analyzer.py +889 -0
  55. kailash/workflow/cycle_builder.py +579 -0
  56. kailash/workflow/cycle_config.py +725 -0
  57. kailash/workflow/cycle_debugger.py +860 -0
  58. kailash/workflow/cycle_exceptions.py +615 -0
  59. kailash/workflow/cycle_profiler.py +741 -0
  60. kailash/workflow/cycle_state.py +338 -0
  61. kailash/workflow/cyclic_runner.py +985 -0
  62. kailash/workflow/graph.py +500 -39
  63. kailash/workflow/migration.py +809 -0
  64. kailash/workflow/safety.py +365 -0
  65. kailash/workflow/templates.py +763 -0
  66. kailash/workflow/validation.py +751 -0
  67. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/METADATA +259 -12
  68. kailash-0.2.1.dist-info/RECORD +125 -0
  69. kailash/nodes/mcp/__init__.py +0 -11
  70. kailash/nodes/mcp/client.py +0 -554
  71. kailash/nodes/mcp/resource.py +0 -682
  72. kailash/nodes/mcp/server.py +0 -577
  73. kailash-0.1.5.dist-info/RECORD +0 -88
  74. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/WHEEL +0 -0
  75. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/entry_points.txt +0 -0
  76. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/licenses/LICENSE +0 -0
  77. {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