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,725 @@
|
|
1
|
+
"""
|
2
|
+
Type-Safe Configuration System for Cyclic Workflows.
|
3
|
+
|
4
|
+
This module provides a comprehensive, type-safe configuration system for cyclic
|
5
|
+
workflows using Python dataclasses with runtime validation. It enables
|
6
|
+
structured cycle configuration with full IDE support, compile-time type
|
7
|
+
checking, and extensive validation to prevent common configuration errors.
|
8
|
+
|
9
|
+
Design Philosophy:
|
10
|
+
Provides compile-time type safety and runtime validation for cycle
|
11
|
+
configurations, replacing loose parameter passing with validated
|
12
|
+
configuration objects. Enables configuration reuse, templating, and
|
13
|
+
standardization across workflows while maintaining maximum flexibility.
|
14
|
+
|
15
|
+
Key Features:
|
16
|
+
- Dataclass-based configuration with automatic validation
|
17
|
+
- Pre-built templates for common cycle patterns
|
18
|
+
- Configuration merging and composition
|
19
|
+
- Export/import capabilities for configuration persistence
|
20
|
+
- Comprehensive validation with actionable error messages
|
21
|
+
|
22
|
+
Configuration Categories:
|
23
|
+
1. Termination Conditions: max_iterations, timeout, convergence_check
|
24
|
+
2. Safety Limits: memory_limit, iteration_safety_factor
|
25
|
+
3. Cycle Metadata: cycle_id, parent_cycle, description
|
26
|
+
4. Execution Control: condition, priority, retry_policy
|
27
|
+
|
28
|
+
Validation Strategy:
|
29
|
+
Performs comprehensive validation at initialization and modification,
|
30
|
+
checking parameter types, ranges, safety constraints, and logical
|
31
|
+
consistency. Provides specific, actionable error messages.
|
32
|
+
|
33
|
+
Upstream Dependencies:
|
34
|
+
- Used by CycleBuilder.build() for configuration validation
|
35
|
+
- Can be used directly with Workflow.connect() for type safety
|
36
|
+
- Supports serialization for configuration persistence
|
37
|
+
|
38
|
+
Downstream Consumers:
|
39
|
+
- CyclicWorkflowExecutor for execution of configured cycles
|
40
|
+
- Cycle debugging and profiling tools for configuration analysis
|
41
|
+
- Configuration templates and presets for common patterns
|
42
|
+
- Workflow generation and automation tools
|
43
|
+
|
44
|
+
Template System:
|
45
|
+
CycleTemplates class provides factory methods for creating optimized
|
46
|
+
configurations for specific use cases, reducing boilerplate and ensuring
|
47
|
+
best practices are followed automatically.
|
48
|
+
|
49
|
+
Example Usage:
|
50
|
+
Basic configuration:
|
51
|
+
|
52
|
+
>>> from kailash.workflow.cycle_config import CycleConfig
|
53
|
+
>>> config = CycleConfig(
|
54
|
+
... max_iterations=100,
|
55
|
+
... convergence_check="error < 0.01",
|
56
|
+
... timeout=300.0,
|
57
|
+
... cycle_id="optimization_loop"
|
58
|
+
... )
|
59
|
+
>>> # Use with workflow
|
60
|
+
>>> workflow.connect(
|
61
|
+
... "processor", "evaluator",
|
62
|
+
... cycle=True, cycle_config=config
|
63
|
+
... )
|
64
|
+
|
65
|
+
Template usage:
|
66
|
+
|
67
|
+
>>> from kailash.workflow.cycle_config import CycleTemplates
|
68
|
+
>>> # Pre-optimized configuration
|
69
|
+
>>> config = CycleTemplates.optimization_loop(
|
70
|
+
... max_iterations=200,
|
71
|
+
... convergence_threshold=0.001
|
72
|
+
... )
|
73
|
+
>>> # Customize template
|
74
|
+
>>> custom_config = config.merge(CycleConfig(
|
75
|
+
... timeout=600.0,
|
76
|
+
... memory_limit=2048
|
77
|
+
... ))
|
78
|
+
|
79
|
+
Configuration management:
|
80
|
+
|
81
|
+
>>> # Export for reuse
|
82
|
+
>>> template_data = config.create_template("ml_training")
|
83
|
+
>>> # Import and modify
|
84
|
+
>>> loaded_config = CycleConfig.from_dict(template_data["configuration"])
|
85
|
+
>>> # Validation and safety
|
86
|
+
>>> config.validate() # Explicit validation
|
87
|
+
>>> effective_max = config.get_effective_max_iterations() # With safety factor
|
88
|
+
|
89
|
+
See Also:
|
90
|
+
- :mod:`kailash.workflow.cycle_builder` for fluent configuration API
|
91
|
+
- :mod:`kailash.workflow.templates` for pre-built cycle patterns
|
92
|
+
- :doc:`/guides/configuration` for configuration best practices
|
93
|
+
"""
|
94
|
+
|
95
|
+
import logging
|
96
|
+
from dataclasses import dataclass, field
|
97
|
+
from typing import Any, Callable, Dict, Optional, Union
|
98
|
+
|
99
|
+
from kailash.workflow.cycle_exceptions import CycleConfigurationError
|
100
|
+
|
101
|
+
logger = logging.getLogger(__name__)
|
102
|
+
|
103
|
+
|
104
|
+
@dataclass
|
105
|
+
class CycleConfig:
|
106
|
+
"""
|
107
|
+
Type-safe configuration for cyclic workflow connections.
|
108
|
+
|
109
|
+
This dataclass provides a structured, type-safe way to configure cycle
|
110
|
+
parameters with validation, default values, and comprehensive error
|
111
|
+
checking. It replaces loose parameter passing with a validated configuration
|
112
|
+
object that can be reused across multiple cycles.
|
113
|
+
|
114
|
+
Design Philosophy:
|
115
|
+
Provides compile-time type safety and runtime validation for cycle
|
116
|
+
configurations. Enables configuration reuse, templating, and
|
117
|
+
standardization across workflows while maintaining flexibility.
|
118
|
+
|
119
|
+
Upstream Dependencies:
|
120
|
+
- Used by CycleBuilder.build() for configuration validation
|
121
|
+
- Can be used directly with Workflow.connect() for type safety
|
122
|
+
- Supports serialization for configuration persistence
|
123
|
+
|
124
|
+
Downstream Consumers:
|
125
|
+
- CyclicWorkflowExecutor for execution of configured cycles
|
126
|
+
- Cycle debugging and profiling tools for configuration analysis
|
127
|
+
- Configuration templates and presets for common patterns
|
128
|
+
|
129
|
+
Configuration Categories:
|
130
|
+
1. Termination Conditions: max_iterations, timeout, convergence_check
|
131
|
+
2. Safety Limits: memory_limit, iteration_safety_factor
|
132
|
+
3. Cycle Metadata: cycle_id, parent_cycle, description
|
133
|
+
4. Execution Control: condition, priority, retry_policy
|
134
|
+
|
135
|
+
Example:
|
136
|
+
>>> # Basic configuration
|
137
|
+
>>> config = CycleConfig(max_iterations=100, convergence_check="error < 0.01")
|
138
|
+
>>> workflow.connect("a", "b", cycle_config=config)
|
139
|
+
|
140
|
+
>>> # Advanced configuration with all features
|
141
|
+
>>> config = CycleConfig(
|
142
|
+
... max_iterations=50,
|
143
|
+
... convergence_check="quality > 0.95",
|
144
|
+
... timeout=300.0,
|
145
|
+
... memory_limit=1024,
|
146
|
+
... cycle_id="optimization_loop",
|
147
|
+
... description="Quality optimization cycle",
|
148
|
+
... condition="needs_optimization == True"
|
149
|
+
... )
|
150
|
+
"""
|
151
|
+
|
152
|
+
# Termination conditions (at least one required)
|
153
|
+
max_iterations: Optional[int] = None
|
154
|
+
convergence_check: Optional[Union[str, Callable]] = None
|
155
|
+
timeout: Optional[float] = None
|
156
|
+
|
157
|
+
# Safety and resource limits
|
158
|
+
memory_limit: Optional[int] = None
|
159
|
+
iteration_safety_factor: float = 1.5 # Multiplier for max_iterations safety
|
160
|
+
|
161
|
+
# Cycle metadata and identification
|
162
|
+
cycle_id: Optional[str] = None
|
163
|
+
parent_cycle: Optional[str] = None
|
164
|
+
description: str = ""
|
165
|
+
|
166
|
+
# Execution control and conditions
|
167
|
+
condition: Optional[str] = None # When to execute the cycle
|
168
|
+
priority: int = 0 # Execution priority for multiple cycles
|
169
|
+
|
170
|
+
# Advanced configuration
|
171
|
+
retry_policy: Dict[str, Any] = field(default_factory=dict)
|
172
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
173
|
+
|
174
|
+
def __post_init__(self):
|
175
|
+
"""
|
176
|
+
Validate configuration after initialization.
|
177
|
+
|
178
|
+
Performs comprehensive validation of all configuration parameters
|
179
|
+
to ensure they are valid, compatible, and safe for cycle execution.
|
180
|
+
|
181
|
+
Raises:
|
182
|
+
CycleConfigurationError: If configuration is invalid or unsafe
|
183
|
+
|
184
|
+
Side Effects:
|
185
|
+
Logs validation warnings for suboptimal configurations
|
186
|
+
Applies automatic fixes for minor configuration issues
|
187
|
+
"""
|
188
|
+
self.validate()
|
189
|
+
|
190
|
+
def validate(self) -> None:
|
191
|
+
"""
|
192
|
+
Validate the cycle configuration for correctness and safety.
|
193
|
+
|
194
|
+
Performs comprehensive validation of all configuration parameters,
|
195
|
+
checking for required fields, valid ranges, unsafe expressions,
|
196
|
+
and configuration conflicts. Provides actionable error messages
|
197
|
+
for any validation failures.
|
198
|
+
|
199
|
+
Raises:
|
200
|
+
CycleConfigurationError: If configuration is invalid
|
201
|
+
|
202
|
+
Side Effects:
|
203
|
+
Logs warnings for suboptimal but valid configurations
|
204
|
+
May modify configuration for automatic safety improvements
|
205
|
+
|
206
|
+
Example:
|
207
|
+
>>> config = CycleConfig(max_iterations=-5) # Will raise error
|
208
|
+
>>> config.validate() # CycleConfigurationError
|
209
|
+
"""
|
210
|
+
errors = []
|
211
|
+
warnings = []
|
212
|
+
|
213
|
+
# Validate termination conditions (at least one required)
|
214
|
+
termination_conditions = [
|
215
|
+
self.max_iterations is not None,
|
216
|
+
self.convergence_check is not None,
|
217
|
+
self.timeout is not None,
|
218
|
+
]
|
219
|
+
|
220
|
+
if not any(termination_conditions):
|
221
|
+
errors.append(
|
222
|
+
"At least one termination condition is required: "
|
223
|
+
"max_iterations, convergence_check, or timeout. "
|
224
|
+
"Recommendation: Always include max_iterations as a safety net."
|
225
|
+
)
|
226
|
+
|
227
|
+
# Validate max_iterations
|
228
|
+
if self.max_iterations is not None:
|
229
|
+
if not isinstance(self.max_iterations, int):
|
230
|
+
raise CycleConfigurationError(
|
231
|
+
f"max_iterations must be an integer, got {type(self.max_iterations)}",
|
232
|
+
error_code="CYCLE_CONFIG_002",
|
233
|
+
invalid_params={"max_iterations": self.max_iterations},
|
234
|
+
suggestions=[
|
235
|
+
"Use integer values for max_iterations",
|
236
|
+
"Convert float values to int if needed",
|
237
|
+
],
|
238
|
+
)
|
239
|
+
elif self.max_iterations <= 0:
|
240
|
+
raise CycleConfigurationError(
|
241
|
+
f"max_iterations must be positive, got {self.max_iterations}",
|
242
|
+
error_code="CYCLE_CONFIG_002",
|
243
|
+
invalid_params={"max_iterations": self.max_iterations},
|
244
|
+
suggestions=[
|
245
|
+
"Use 10-100 iterations for quick convergence",
|
246
|
+
"Use 100-1000 iterations for complex optimization",
|
247
|
+
"Consider adding convergence_check for early termination",
|
248
|
+
],
|
249
|
+
)
|
250
|
+
elif self.max_iterations > 10000:
|
251
|
+
warnings.append(
|
252
|
+
f"max_iterations={self.max_iterations} is very high. "
|
253
|
+
"Consider using convergence_check for efficiency."
|
254
|
+
)
|
255
|
+
|
256
|
+
# Validate convergence_check
|
257
|
+
if self.convergence_check is not None:
|
258
|
+
if isinstance(self.convergence_check, str):
|
259
|
+
if not self.convergence_check.strip():
|
260
|
+
errors.append(
|
261
|
+
"Convergence condition cannot be empty. "
|
262
|
+
"Examples: 'error < 0.01', 'quality > 0.9', 'count >= 10'"
|
263
|
+
)
|
264
|
+
else:
|
265
|
+
# Validate expression safety
|
266
|
+
unsafe_patterns = [
|
267
|
+
"import ",
|
268
|
+
"exec(",
|
269
|
+
"eval(",
|
270
|
+
"__",
|
271
|
+
"open(",
|
272
|
+
"file(",
|
273
|
+
]
|
274
|
+
for pattern in unsafe_patterns:
|
275
|
+
if pattern in self.convergence_check:
|
276
|
+
errors.append(
|
277
|
+
f"Convergence condition contains unsafe operation: '{pattern}'. "
|
278
|
+
"Use simple comparison expressions only."
|
279
|
+
)
|
280
|
+
elif not callable(self.convergence_check):
|
281
|
+
errors.append(
|
282
|
+
f"convergence_check must be string or callable, got {type(self.convergence_check)}"
|
283
|
+
)
|
284
|
+
|
285
|
+
# Validate timeout
|
286
|
+
if self.timeout is not None:
|
287
|
+
if not isinstance(self.timeout, (int, float)):
|
288
|
+
errors.append(f"timeout must be numeric, got {type(self.timeout)}")
|
289
|
+
elif self.timeout <= 0:
|
290
|
+
errors.append(
|
291
|
+
f"timeout must be positive, got {self.timeout}. "
|
292
|
+
"Recommendation: Use 30-300 seconds for most cycles."
|
293
|
+
)
|
294
|
+
elif self.timeout > 3600:
|
295
|
+
warnings.append(
|
296
|
+
f"timeout={self.timeout} seconds is very long (>1 hour). "
|
297
|
+
"Consider breaking into smaller cycles."
|
298
|
+
)
|
299
|
+
|
300
|
+
# Validate memory_limit
|
301
|
+
if self.memory_limit is not None:
|
302
|
+
if not isinstance(self.memory_limit, int):
|
303
|
+
errors.append(
|
304
|
+
f"memory_limit must be an integer, got {type(self.memory_limit)}"
|
305
|
+
)
|
306
|
+
elif self.memory_limit <= 0:
|
307
|
+
errors.append(
|
308
|
+
f"memory_limit must be positive, got {self.memory_limit}. "
|
309
|
+
"Recommendation: Use 100-1000 MB for most cycles."
|
310
|
+
)
|
311
|
+
elif self.memory_limit > 100000: # 100GB
|
312
|
+
warnings.append(
|
313
|
+
f"memory_limit={self.memory_limit} MB is very high. "
|
314
|
+
"Verify this is intentional for your use case."
|
315
|
+
)
|
316
|
+
|
317
|
+
# Validate iteration_safety_factor
|
318
|
+
if not isinstance(self.iteration_safety_factor, (int, float)):
|
319
|
+
errors.append(
|
320
|
+
f"iteration_safety_factor must be numeric, got {type(self.iteration_safety_factor)}"
|
321
|
+
)
|
322
|
+
elif self.iteration_safety_factor < 1.0:
|
323
|
+
errors.append(
|
324
|
+
f"iteration_safety_factor must be >= 1.0, got {self.iteration_safety_factor}. "
|
325
|
+
"This factor provides safety buffer for max_iterations."
|
326
|
+
)
|
327
|
+
elif self.iteration_safety_factor > 10.0:
|
328
|
+
warnings.append(
|
329
|
+
f"iteration_safety_factor={self.iteration_safety_factor} is very high. "
|
330
|
+
"This may cause excessive iteration limits."
|
331
|
+
)
|
332
|
+
|
333
|
+
# Validate cycle_id
|
334
|
+
if self.cycle_id is not None:
|
335
|
+
if not isinstance(self.cycle_id, str) or not self.cycle_id.strip():
|
336
|
+
errors.append("cycle_id must be a non-empty string")
|
337
|
+
elif len(self.cycle_id) > 100:
|
338
|
+
warnings.append(f"cycle_id='{self.cycle_id}' is very long (>100 chars)")
|
339
|
+
|
340
|
+
# Validate parent_cycle
|
341
|
+
if self.parent_cycle is not None:
|
342
|
+
if not isinstance(self.parent_cycle, str) or not self.parent_cycle.strip():
|
343
|
+
errors.append("parent_cycle must be a non-empty string")
|
344
|
+
elif self.parent_cycle == self.cycle_id:
|
345
|
+
errors.append("parent_cycle cannot be the same as cycle_id")
|
346
|
+
|
347
|
+
# Validate condition
|
348
|
+
if self.condition is not None:
|
349
|
+
if not isinstance(self.condition, str) or not self.condition.strip():
|
350
|
+
errors.append(
|
351
|
+
"condition must be a non-empty string expression. "
|
352
|
+
"Examples: 'retry_count < 3', 'needs_improvement == True'"
|
353
|
+
)
|
354
|
+
|
355
|
+
# Validate priority
|
356
|
+
if not isinstance(self.priority, int):
|
357
|
+
errors.append(f"priority must be an integer, got {type(self.priority)}")
|
358
|
+
elif abs(self.priority) > 1000:
|
359
|
+
warnings.append(f"priority={self.priority} is very high/low")
|
360
|
+
|
361
|
+
# Log warnings
|
362
|
+
for warning in warnings:
|
363
|
+
logger.warning(f"CycleConfig validation warning: {warning}")
|
364
|
+
|
365
|
+
# Raise errors if any found
|
366
|
+
if errors:
|
367
|
+
error_message = "CycleConfig validation failed:\n" + "\n".join(
|
368
|
+
f"• {error}" for error in errors
|
369
|
+
)
|
370
|
+
raise CycleConfigurationError(
|
371
|
+
error_message,
|
372
|
+
error_code="CYCLE_CONFIG_001",
|
373
|
+
suggestions=[
|
374
|
+
"Ensure at least one termination condition (max_iterations, convergence_check, or timeout)",
|
375
|
+
"Use positive values for numeric parameters",
|
376
|
+
"Avoid unsafe operations in convergence expressions",
|
377
|
+
"Check the CycleConfig documentation for valid parameter ranges",
|
378
|
+
],
|
379
|
+
)
|
380
|
+
|
381
|
+
def get_effective_max_iterations(self) -> Optional[int]:
|
382
|
+
"""
|
383
|
+
Get the effective maximum iterations with safety factor applied.
|
384
|
+
|
385
|
+
Calculates the actual maximum iterations that will be used during
|
386
|
+
cycle execution, including the safety factor multiplier to prevent
|
387
|
+
runaway cycles even when convergence conditions fail.
|
388
|
+
|
389
|
+
Returns:
|
390
|
+
Optional[int]: Effective maximum iterations, or None if not configured
|
391
|
+
|
392
|
+
Side Effects:
|
393
|
+
None - this is a pure calculation method
|
394
|
+
|
395
|
+
Example:
|
396
|
+
>>> config = CycleConfig(max_iterations=100, iteration_safety_factor=1.5)
|
397
|
+
>>> config.get_effective_max_iterations()
|
398
|
+
150
|
399
|
+
"""
|
400
|
+
if self.max_iterations is None:
|
401
|
+
return None
|
402
|
+
return int(self.max_iterations * self.iteration_safety_factor)
|
403
|
+
|
404
|
+
def to_dict(self) -> Dict[str, Any]:
|
405
|
+
"""
|
406
|
+
Convert configuration to dictionary format.
|
407
|
+
|
408
|
+
Serializes the configuration to a dictionary suitable for JSON/YAML
|
409
|
+
export, API transmission, or storage. Excludes None values and
|
410
|
+
callable convergence checks for clean serialization.
|
411
|
+
|
412
|
+
Returns:
|
413
|
+
Dict[str, Any]: Dictionary representation of configuration
|
414
|
+
|
415
|
+
Side Effects:
|
416
|
+
None - this method is pure
|
417
|
+
|
418
|
+
Example:
|
419
|
+
>>> config = CycleConfig(max_iterations=100)
|
420
|
+
>>> config.to_dict()
|
421
|
+
{'max_iterations': 100, 'iteration_safety_factor': 1.5, ...}
|
422
|
+
"""
|
423
|
+
result = {}
|
424
|
+
|
425
|
+
for key, value in self.__dict__.items():
|
426
|
+
if value is not None:
|
427
|
+
# Skip callable convergence_check for serialization
|
428
|
+
if key == "convergence_check" and callable(value):
|
429
|
+
result[key] = "<callable>"
|
430
|
+
else:
|
431
|
+
result[key] = value
|
432
|
+
|
433
|
+
return result
|
434
|
+
|
435
|
+
@classmethod
|
436
|
+
def from_dict(cls, data: Dict[str, Any]) -> "CycleConfig":
|
437
|
+
"""
|
438
|
+
Create configuration from dictionary data.
|
439
|
+
|
440
|
+
Deserializes a configuration from dictionary format, typically
|
441
|
+
loaded from JSON/YAML files or API requests. Handles missing
|
442
|
+
fields gracefully with default values.
|
443
|
+
|
444
|
+
Args:
|
445
|
+
data (Dict[str, Any]): Dictionary containing configuration data
|
446
|
+
|
447
|
+
Returns:
|
448
|
+
CycleConfig: New configuration instance
|
449
|
+
|
450
|
+
Raises:
|
451
|
+
CycleConfigurationError: If data contains invalid values
|
452
|
+
|
453
|
+
Side Effects:
|
454
|
+
Validates the resulting configuration automatically
|
455
|
+
|
456
|
+
Example:
|
457
|
+
>>> data = {'max_iterations': 100, 'timeout': 60.0}
|
458
|
+
>>> config = CycleConfig.from_dict(data)
|
459
|
+
"""
|
460
|
+
# Filter out unknown fields
|
461
|
+
known_fields = {f.name for f in cls.__dataclass_fields__.values()}
|
462
|
+
filtered_data = {k: v for k, v in data.items() if k in known_fields}
|
463
|
+
|
464
|
+
try:
|
465
|
+
return cls(**filtered_data)
|
466
|
+
except Exception as e:
|
467
|
+
raise CycleConfigurationError(
|
468
|
+
f"Failed to create CycleConfig from data: {e}"
|
469
|
+
) from e
|
470
|
+
|
471
|
+
def merge(self, other: "CycleConfig") -> "CycleConfig":
|
472
|
+
"""
|
473
|
+
Merge this configuration with another, with other taking precedence.
|
474
|
+
|
475
|
+
Creates a new configuration by merging two configurations, where
|
476
|
+
non-None values from the other configuration override values in
|
477
|
+
this configuration. Useful for applying templates and overlays.
|
478
|
+
|
479
|
+
Args:
|
480
|
+
other (CycleConfig): Configuration to merge with (takes precedence)
|
481
|
+
|
482
|
+
Returns:
|
483
|
+
CycleConfig: New merged configuration instance
|
484
|
+
|
485
|
+
Raises:
|
486
|
+
CycleConfigurationError: If merged configuration is invalid
|
487
|
+
|
488
|
+
Side Effects:
|
489
|
+
Validates the resulting merged configuration
|
490
|
+
|
491
|
+
Example:
|
492
|
+
>>> base = CycleConfig(max_iterations=100)
|
493
|
+
>>> override = CycleConfig(timeout=60.0, cycle_id="custom")
|
494
|
+
>>> merged = base.merge(override)
|
495
|
+
>>> # Result has max_iterations=100, timeout=60.0, cycle_id="custom"
|
496
|
+
"""
|
497
|
+
merged_data = {}
|
498
|
+
|
499
|
+
# Start with this configuration
|
500
|
+
for key, value in self.__dict__.items():
|
501
|
+
if value is not None:
|
502
|
+
merged_data[key] = value
|
503
|
+
|
504
|
+
# Override with other configuration
|
505
|
+
for key, value in other.__dict__.items():
|
506
|
+
if value is not None:
|
507
|
+
merged_data[key] = value
|
508
|
+
|
509
|
+
return CycleConfig(**merged_data)
|
510
|
+
|
511
|
+
def create_template(self, template_name: str) -> Dict[str, Any]:
|
512
|
+
"""
|
513
|
+
Create a reusable template from this configuration.
|
514
|
+
|
515
|
+
Exports the configuration as a named template that can be stored,
|
516
|
+
shared, and reused across multiple workflows. Templates include
|
517
|
+
metadata about their intended use case and recommended parameters.
|
518
|
+
|
519
|
+
Args:
|
520
|
+
template_name (str): Name for the template
|
521
|
+
|
522
|
+
Returns:
|
523
|
+
Dict[str, Any]: Template data including metadata
|
524
|
+
|
525
|
+
Side Effects:
|
526
|
+
None - this method is pure
|
527
|
+
|
528
|
+
Example:
|
529
|
+
>>> config = CycleConfig(max_iterations=50, convergence_check="quality > 0.9")
|
530
|
+
>>> template = config.create_template("quality_optimization")
|
531
|
+
"""
|
532
|
+
template_data = {
|
533
|
+
"template_name": template_name,
|
534
|
+
"description": self.description or f"Cycle template: {template_name}",
|
535
|
+
"created_from": "CycleConfig.create_template()",
|
536
|
+
"configuration": self.to_dict(),
|
537
|
+
"usage_notes": {
|
538
|
+
"max_iterations": "Adjust based on expected convergence time",
|
539
|
+
"convergence_check": "Modify condition for your specific metrics",
|
540
|
+
"timeout": "Set based on acceptable execution time",
|
541
|
+
},
|
542
|
+
}
|
543
|
+
|
544
|
+
return template_data
|
545
|
+
|
546
|
+
def __repr__(self) -> str:
|
547
|
+
"""
|
548
|
+
Return string representation of the configuration.
|
549
|
+
|
550
|
+
Returns:
|
551
|
+
str: Human-readable representation showing key configuration values
|
552
|
+
|
553
|
+
Example:
|
554
|
+
>>> config = CycleConfig(max_iterations=100, timeout=60.0)
|
555
|
+
>>> str(config)
|
556
|
+
'CycleConfig(max_iterations=100, timeout=60.0, cycle_id=None)'
|
557
|
+
"""
|
558
|
+
key_params = []
|
559
|
+
|
560
|
+
if self.max_iterations is not None:
|
561
|
+
key_params.append(f"max_iterations={self.max_iterations}")
|
562
|
+
if self.convergence_check is not None:
|
563
|
+
conv_str = (
|
564
|
+
self.convergence_check
|
565
|
+
if isinstance(self.convergence_check, str)
|
566
|
+
else "<callable>"
|
567
|
+
)
|
568
|
+
key_params.append(f"convergence_check='{conv_str}'")
|
569
|
+
if self.timeout is not None:
|
570
|
+
key_params.append(f"timeout={self.timeout}")
|
571
|
+
if self.cycle_id is not None:
|
572
|
+
key_params.append(f"cycle_id='{self.cycle_id}'")
|
573
|
+
|
574
|
+
return f"CycleConfig({', '.join(key_params)})"
|
575
|
+
|
576
|
+
|
577
|
+
# Pre-defined configuration templates for common use cases
|
578
|
+
class CycleTemplates:
|
579
|
+
"""
|
580
|
+
Pre-defined cycle configuration templates for common patterns.
|
581
|
+
|
582
|
+
This class provides factory methods for creating CycleConfig instances
|
583
|
+
optimized for specific use cases, reducing boilerplate and ensuring
|
584
|
+
best practices are followed for common cycle patterns.
|
585
|
+
|
586
|
+
Design Philosophy:
|
587
|
+
Provides curated, tested configurations for common cycle patterns
|
588
|
+
to reduce setup time and ensure optimal performance. Templates
|
589
|
+
can be customized after creation for specific requirements.
|
590
|
+
|
591
|
+
Example:
|
592
|
+
>>> # Quick optimization cycle
|
593
|
+
>>> config = CycleTemplates.optimization_loop(max_iterations=50)
|
594
|
+
|
595
|
+
>>> # Retry logic with exponential backoff
|
596
|
+
>>> config = CycleTemplates.retry_cycle(max_retries=3)
|
597
|
+
"""
|
598
|
+
|
599
|
+
@staticmethod
|
600
|
+
def optimization_loop(
|
601
|
+
max_iterations: int = 100,
|
602
|
+
convergence_threshold: float = 0.01,
|
603
|
+
timeout: Optional[float] = None,
|
604
|
+
) -> CycleConfig:
|
605
|
+
"""
|
606
|
+
Create configuration for optimization cycles.
|
607
|
+
|
608
|
+
Optimized for iterative improvement algorithms like gradient descent,
|
609
|
+
quality improvement, or parameter tuning. Focuses on convergence
|
610
|
+
detection with reasonable iteration limits.
|
611
|
+
|
612
|
+
Args:
|
613
|
+
max_iterations (int): Maximum optimization iterations
|
614
|
+
convergence_threshold (float): Convergence threshold for stopping
|
615
|
+
timeout (Optional[float]): Optional timeout in seconds
|
616
|
+
|
617
|
+
Returns:
|
618
|
+
CycleConfig: Configured for optimization patterns
|
619
|
+
|
620
|
+
Example:
|
621
|
+
>>> config = CycleTemplates.optimization_loop(max_iterations=200)
|
622
|
+
>>> workflow.connect("optimizer", "evaluator", cycle_config=config)
|
623
|
+
"""
|
624
|
+
return CycleConfig(
|
625
|
+
max_iterations=max_iterations,
|
626
|
+
convergence_check=f"improvement < {convergence_threshold}",
|
627
|
+
timeout=timeout,
|
628
|
+
cycle_id="optimization_loop",
|
629
|
+
description="Iterative optimization cycle with convergence detection",
|
630
|
+
iteration_safety_factor=2.0, # Higher safety for optimization
|
631
|
+
)
|
632
|
+
|
633
|
+
@staticmethod
|
634
|
+
def retry_cycle(
|
635
|
+
max_retries: int = 3, timeout_per_retry: float = 30.0
|
636
|
+
) -> CycleConfig:
|
637
|
+
"""
|
638
|
+
Create configuration for retry logic patterns.
|
639
|
+
|
640
|
+
Optimized for error recovery, API retry logic, and fault-tolerant
|
641
|
+
operations. Includes reasonable timeout per attempt and limited
|
642
|
+
retry counts to prevent indefinite hanging.
|
643
|
+
|
644
|
+
Args:
|
645
|
+
max_retries (int): Maximum number of retry attempts
|
646
|
+
timeout_per_retry (float): Timeout per individual retry
|
647
|
+
|
648
|
+
Returns:
|
649
|
+
CycleConfig: Configured for retry patterns
|
650
|
+
|
651
|
+
Example:
|
652
|
+
>>> config = CycleTemplates.retry_cycle(max_retries=5)
|
653
|
+
>>> workflow.connect("api_call", "error_handler", cycle_config=config)
|
654
|
+
"""
|
655
|
+
return CycleConfig(
|
656
|
+
max_iterations=max_retries,
|
657
|
+
timeout=timeout_per_retry * max_retries,
|
658
|
+
cycle_id="retry_cycle",
|
659
|
+
description="Retry cycle with exponential backoff support",
|
660
|
+
condition="error_occurred == True",
|
661
|
+
iteration_safety_factor=1.2, # Conservative safety for retries
|
662
|
+
)
|
663
|
+
|
664
|
+
@staticmethod
|
665
|
+
def data_quality_cycle(
|
666
|
+
quality_threshold: float = 0.95, max_iterations: int = 10
|
667
|
+
) -> CycleConfig:
|
668
|
+
"""
|
669
|
+
Create configuration for data quality improvement cycles.
|
670
|
+
|
671
|
+
Optimized for iterative data cleaning, validation, and quality
|
672
|
+
enhancement workflows. Focuses on quality metrics and reasonable
|
673
|
+
iteration limits for data processing.
|
674
|
+
|
675
|
+
Args:
|
676
|
+
quality_threshold (float): Quality threshold for stopping (0.0-1.0)
|
677
|
+
max_iterations (int): Maximum cleaning iterations
|
678
|
+
|
679
|
+
Returns:
|
680
|
+
CycleConfig: Configured for data quality patterns
|
681
|
+
|
682
|
+
Example:
|
683
|
+
>>> config = CycleTemplates.data_quality_cycle(quality_threshold=0.98)
|
684
|
+
>>> workflow.connect("cleaner", "validator", cycle_config=config)
|
685
|
+
"""
|
686
|
+
return CycleConfig(
|
687
|
+
max_iterations=max_iterations,
|
688
|
+
convergence_check=f"quality >= {quality_threshold}",
|
689
|
+
timeout=300.0, # 5 minutes for data processing
|
690
|
+
cycle_id="data_quality_cycle",
|
691
|
+
description="Data quality improvement cycle with quality metrics",
|
692
|
+
memory_limit=2048, # 2GB for data processing
|
693
|
+
)
|
694
|
+
|
695
|
+
@staticmethod
|
696
|
+
def training_loop(
|
697
|
+
max_epochs: int = 100, early_stopping_patience: int = 10
|
698
|
+
) -> CycleConfig:
|
699
|
+
"""
|
700
|
+
Create configuration for machine learning training cycles.
|
701
|
+
|
702
|
+
Optimized for ML model training with early stopping, validation
|
703
|
+
monitoring, and resource management. Includes higher memory limits
|
704
|
+
and longer timeouts typical for training workflows.
|
705
|
+
|
706
|
+
Args:
|
707
|
+
max_epochs (int): Maximum training epochs
|
708
|
+
early_stopping_patience (int): Epochs to wait for improvement
|
709
|
+
|
710
|
+
Returns:
|
711
|
+
CycleConfig: Configured for ML training patterns
|
712
|
+
|
713
|
+
Example:
|
714
|
+
>>> config = CycleTemplates.training_loop(max_epochs=200)
|
715
|
+
>>> workflow.connect("trainer", "evaluator", cycle_config=config)
|
716
|
+
"""
|
717
|
+
return CycleConfig(
|
718
|
+
max_iterations=max_epochs,
|
719
|
+
convergence_check=f"epochs_without_improvement >= {early_stopping_patience}",
|
720
|
+
timeout=3600.0, # 1 hour for training
|
721
|
+
cycle_id="training_loop",
|
722
|
+
description="ML training cycle with early stopping",
|
723
|
+
memory_limit=8192, # 8GB for ML training
|
724
|
+
iteration_safety_factor=1.1, # Conservative for long training
|
725
|
+
)
|