kailash 0.2.0__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/mcp/server_new.py +6 -6
- kailash/nodes/data/__init__.py +1 -2
- kailash/nodes/data/sql.py +699 -256
- kailash/workflow/cycle_analyzer.py +346 -225
- kailash/workflow/cycle_builder.py +75 -69
- kailash/workflow/cycle_config.py +62 -46
- kailash/workflow/cycle_debugger.py +284 -184
- kailash/workflow/cycle_exceptions.py +111 -97
- kailash/workflow/cycle_profiler.py +272 -202
- kailash/workflow/migration.py +238 -197
- kailash/workflow/templates.py +124 -105
- kailash/workflow/validation.py +356 -298
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/METADATA +4 -1
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/RECORD +18 -18
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/WHEEL +0 -0
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/entry_points.txt +0 -0
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ production phases with detailed execution tracking and rich analytics.
|
|
8
8
|
|
9
9
|
Examples:
|
10
10
|
Basic debugging setup:
|
11
|
-
|
11
|
+
|
12
12
|
>>> debugger = CycleDebugger(
|
13
13
|
... debug_level="detailed",
|
14
14
|
... enable_profiling=True
|
@@ -19,9 +19,9 @@ Examples:
|
|
19
19
|
... "workflow_1",
|
20
20
|
... max_iterations=100
|
21
21
|
... )
|
22
|
-
|
22
|
+
|
23
23
|
Iteration tracking:
|
24
|
-
|
24
|
+
|
25
25
|
>>> # Track each iteration
|
26
26
|
>>> iteration = debugger.start_iteration(trace, input_data)
|
27
27
|
>>> # ... cycle execution ...
|
@@ -30,9 +30,9 @@ Examples:
|
|
30
30
|
... convergence_value=0.05,
|
31
31
|
... node_executions=["processor", "evaluator"]
|
32
32
|
... )
|
33
|
-
|
33
|
+
|
34
34
|
Analysis and reporting:
|
35
|
-
|
35
|
+
|
36
36
|
>>> # Complete cycle
|
37
37
|
>>> debugger.end_cycle(
|
38
38
|
... trace, converged=True,
|
@@ -58,11 +58,11 @@ logger = logging.getLogger(__name__)
|
|
58
58
|
class CycleIteration:
|
59
59
|
"""
|
60
60
|
Represents a single iteration within a cycle execution.
|
61
|
-
|
61
|
+
|
62
62
|
This class captures all relevant information about a cycle iteration,
|
63
63
|
including input/output data, execution time, memory usage, and any
|
64
64
|
errors that occurred during execution.
|
65
|
-
|
65
|
+
|
66
66
|
Attributes:
|
67
67
|
iteration_number: The iteration count (starting from 1).
|
68
68
|
start_time: When this iteration began execution.
|
@@ -76,7 +76,7 @@ class CycleIteration:
|
|
76
76
|
error: Error message if iteration failed.
|
77
77
|
node_executions: List of nodes executed in this iteration.
|
78
78
|
"""
|
79
|
-
|
79
|
+
|
80
80
|
iteration_number: int
|
81
81
|
start_time: datetime
|
82
82
|
end_time: Optional[datetime] = None
|
@@ -88,11 +88,13 @@ class CycleIteration:
|
|
88
88
|
convergence_value: Optional[float] = None
|
89
89
|
error: Optional[str] = None
|
90
90
|
node_executions: List[str] = field(default_factory=list)
|
91
|
-
|
92
|
-
def complete(
|
91
|
+
|
92
|
+
def complete(
|
93
|
+
self, output_data: Dict[str, Any], convergence_value: Optional[float] = None
|
94
|
+
):
|
93
95
|
"""
|
94
96
|
Mark iteration as complete with output data.
|
95
|
-
|
97
|
+
|
96
98
|
Args:
|
97
99
|
output_data: The output data from this iteration.
|
98
100
|
convergence_value: Convergence metric if available.
|
@@ -101,37 +103,37 @@ class CycleIteration:
|
|
101
103
|
self.execution_time = (self.end_time - self.start_time).total_seconds()
|
102
104
|
self.output_data = output_data
|
103
105
|
self.convergence_value = convergence_value
|
104
|
-
|
106
|
+
|
105
107
|
def fail(self, error: str):
|
106
108
|
"""
|
107
109
|
Mark iteration as failed with error message.
|
108
|
-
|
110
|
+
|
109
111
|
Args:
|
110
112
|
error: Error message describing the failure.
|
111
113
|
"""
|
112
114
|
self.end_time = datetime.now()
|
113
115
|
self.execution_time = (self.end_time - self.start_time).total_seconds()
|
114
116
|
self.error = error
|
115
|
-
|
117
|
+
|
116
118
|
def is_completed(self) -> bool:
|
117
119
|
"""Check if iteration has completed (successfully or with error).
|
118
|
-
|
120
|
+
|
119
121
|
Returns:
|
120
122
|
True if iteration has completed.
|
121
123
|
"""
|
122
124
|
return self.end_time is not None
|
123
|
-
|
125
|
+
|
124
126
|
def is_successful(self) -> bool:
|
125
127
|
"""Check if iteration completed successfully.
|
126
|
-
|
128
|
+
|
127
129
|
Returns:
|
128
130
|
True if iteration completed without error.
|
129
131
|
"""
|
130
132
|
return self.end_time is not None and self.error is None
|
131
|
-
|
133
|
+
|
132
134
|
def to_dict(self) -> Dict[str, Any]:
|
133
135
|
"""Convert iteration to dictionary for serialization.
|
134
|
-
|
136
|
+
|
135
137
|
Returns:
|
136
138
|
Dictionary representation of the iteration.
|
137
139
|
"""
|
@@ -146,19 +148,19 @@ class CycleIteration:
|
|
146
148
|
"error": self.error,
|
147
149
|
"node_executions": self.node_executions,
|
148
150
|
"input_size": len(str(self.input_data)) if self.input_data else 0,
|
149
|
-
"output_size": len(str(self.output_data)) if self.output_data else 0
|
151
|
+
"output_size": len(str(self.output_data)) if self.output_data else 0,
|
150
152
|
}
|
151
153
|
|
152
154
|
|
153
|
-
@dataclass
|
155
|
+
@dataclass
|
154
156
|
class CycleExecutionTrace:
|
155
157
|
"""
|
156
158
|
Complete execution trace for a cycle, containing all iterations and metadata.
|
157
|
-
|
159
|
+
|
158
160
|
This class provides a comprehensive record of cycle execution, including
|
159
161
|
all iterations, overall statistics, convergence analysis, and performance
|
160
162
|
metrics. It serves as the primary data structure for cycle debugging.
|
161
|
-
|
163
|
+
|
162
164
|
Attributes:
|
163
165
|
cycle_id: Unique identifier for the cycle.
|
164
166
|
workflow_id: Parent workflow identifier.
|
@@ -173,7 +175,7 @@ class CycleExecutionTrace:
|
|
173
175
|
timeout_configured: Configured timeout limit.
|
174
176
|
convergence_condition: Configured convergence condition.
|
175
177
|
"""
|
176
|
-
|
178
|
+
|
177
179
|
cycle_id: str
|
178
180
|
workflow_id: str
|
179
181
|
start_time: datetime
|
@@ -188,27 +190,37 @@ class CycleExecutionTrace:
|
|
188
190
|
convergence_condition: Optional[str] = None
|
189
191
|
memory_peak_mb: Optional[float] = None
|
190
192
|
cpu_peak_percent: Optional[float] = None
|
191
|
-
|
193
|
+
|
192
194
|
def add_iteration(self, iteration: CycleIteration):
|
193
195
|
"""
|
194
196
|
Add an iteration to the trace.
|
195
|
-
|
197
|
+
|
196
198
|
Args:
|
197
199
|
iteration: The iteration to add.
|
198
200
|
"""
|
199
201
|
self.iterations.append(iteration)
|
200
|
-
|
202
|
+
|
201
203
|
# Update peak metrics
|
202
|
-
if iteration.memory_usage_mb and (
|
204
|
+
if iteration.memory_usage_mb and (
|
205
|
+
not self.memory_peak_mb or iteration.memory_usage_mb > self.memory_peak_mb
|
206
|
+
):
|
203
207
|
self.memory_peak_mb = iteration.memory_usage_mb
|
204
|
-
|
205
|
-
if iteration.cpu_usage_percent and (
|
208
|
+
|
209
|
+
if iteration.cpu_usage_percent and (
|
210
|
+
not self.cpu_peak_percent
|
211
|
+
or iteration.cpu_usage_percent > self.cpu_peak_percent
|
212
|
+
):
|
206
213
|
self.cpu_peak_percent = iteration.cpu_usage_percent
|
207
|
-
|
208
|
-
def complete(
|
214
|
+
|
215
|
+
def complete(
|
216
|
+
self,
|
217
|
+
converged: bool,
|
218
|
+
termination_reason: str,
|
219
|
+
convergence_iteration: Optional[int] = None,
|
220
|
+
):
|
209
221
|
"""
|
210
222
|
Mark cycle execution as complete.
|
211
|
-
|
223
|
+
|
212
224
|
Args:
|
213
225
|
converged: Whether the cycle converged successfully.
|
214
226
|
termination_reason: Why the cycle terminated.
|
@@ -219,14 +231,14 @@ class CycleExecutionTrace:
|
|
219
231
|
self.converged = converged
|
220
232
|
self.termination_reason = termination_reason
|
221
233
|
self.convergence_iteration = convergence_iteration
|
222
|
-
|
234
|
+
|
223
235
|
def get_statistics(self) -> Dict[str, Any]:
|
224
236
|
"""
|
225
237
|
Get comprehensive statistics about the cycle execution.
|
226
|
-
|
238
|
+
|
227
239
|
Returns:
|
228
240
|
Statistics including timing, convergence, and performance metrics.
|
229
|
-
|
241
|
+
|
230
242
|
Examples:
|
231
243
|
>>> stats = trace.get_statistics()
|
232
244
|
>>> print(f"Average iteration time: {stats['avg_iteration_time']:.3f}s")
|
@@ -238,17 +250,27 @@ class CycleExecutionTrace:
|
|
238
250
|
"min_iteration_time": 0.0,
|
239
251
|
"max_iteration_time": 0.0,
|
240
252
|
"convergence_rate": 0.0,
|
241
|
-
"efficiency_score": 0.0
|
253
|
+
"efficiency_score": 0.0,
|
242
254
|
}
|
243
|
-
|
255
|
+
|
244
256
|
# Calculate timing statistics
|
245
|
-
iteration_times = [
|
246
|
-
|
257
|
+
iteration_times = [
|
258
|
+
iter.execution_time
|
259
|
+
for iter in self.iterations
|
260
|
+
if iter.execution_time is not None
|
261
|
+
]
|
262
|
+
|
247
263
|
stats = {
|
248
264
|
"total_iterations": len(self.iterations),
|
249
|
-
"successful_iterations": len(
|
250
|
-
|
251
|
-
|
265
|
+
"successful_iterations": len(
|
266
|
+
[iter for iter in self.iterations if iter.is_successful()]
|
267
|
+
),
|
268
|
+
"failed_iterations": len(
|
269
|
+
[iter for iter in self.iterations if iter.error is not None]
|
270
|
+
),
|
271
|
+
"avg_iteration_time": (
|
272
|
+
sum(iteration_times) / len(iteration_times) if iteration_times else 0.0
|
273
|
+
),
|
252
274
|
"min_iteration_time": min(iteration_times) if iteration_times else 0.0,
|
253
275
|
"max_iteration_time": max(iteration_times) if iteration_times else 0.0,
|
254
276
|
"total_execution_time": self.total_execution_time or 0.0,
|
@@ -256,43 +278,63 @@ class CycleExecutionTrace:
|
|
256
278
|
"convergence_iteration": self.convergence_iteration,
|
257
279
|
"termination_reason": self.termination_reason,
|
258
280
|
"memory_peak_mb": self.memory_peak_mb,
|
259
|
-
"cpu_peak_percent": self.cpu_peak_percent
|
281
|
+
"cpu_peak_percent": self.cpu_peak_percent,
|
260
282
|
}
|
261
|
-
|
283
|
+
|
262
284
|
# Calculate convergence rate (how quickly it converged relative to max_iterations)
|
263
|
-
if
|
264
|
-
|
285
|
+
if (
|
286
|
+
self.converged
|
287
|
+
and self.convergence_iteration
|
288
|
+
and self.max_iterations_configured
|
289
|
+
):
|
290
|
+
stats["convergence_rate"] = (
|
291
|
+
self.convergence_iteration / self.max_iterations_configured
|
292
|
+
)
|
265
293
|
else:
|
266
294
|
stats["convergence_rate"] = 1.0 if self.converged else 0.0
|
267
|
-
|
295
|
+
|
268
296
|
# Calculate efficiency score (0-1, higher is better)
|
269
297
|
if self.max_iterations_configured and len(self.iterations) > 0:
|
270
|
-
iteration_efficiency = 1.0 - (
|
271
|
-
|
298
|
+
iteration_efficiency = 1.0 - (
|
299
|
+
len(self.iterations) / self.max_iterations_configured
|
300
|
+
)
|
301
|
+
time_efficiency = (
|
302
|
+
1.0
|
303
|
+
if not self.timeout_configured
|
304
|
+
else max(
|
305
|
+
0.0,
|
306
|
+
1.0 - (self.total_execution_time or 0) / self.timeout_configured,
|
307
|
+
)
|
308
|
+
)
|
272
309
|
convergence_bonus = 0.2 if self.converged else 0.0
|
273
|
-
stats["efficiency_score"] = min(
|
310
|
+
stats["efficiency_score"] = min(
|
311
|
+
1.0,
|
312
|
+
iteration_efficiency * 0.5 + time_efficiency * 0.3 + convergence_bonus,
|
313
|
+
)
|
274
314
|
else:
|
275
315
|
stats["efficiency_score"] = 0.5 if self.converged else 0.0
|
276
|
-
|
316
|
+
|
277
317
|
return stats
|
278
|
-
|
318
|
+
|
279
319
|
def get_convergence_trend(self) -> List[Tuple[int, Optional[float]]]:
|
280
320
|
"""
|
281
321
|
Get convergence values over iterations for trend analysis.
|
282
|
-
|
322
|
+
|
283
323
|
Returns:
|
284
324
|
List[Tuple[int, Optional[float]]]: List of (iteration_number, convergence_value) pairs
|
285
|
-
|
325
|
+
|
286
326
|
Side Effects:
|
287
327
|
None - this is a pure calculation method
|
288
|
-
|
328
|
+
|
289
329
|
Example:
|
290
330
|
>>> trend = trace.get_convergence_trend()
|
291
331
|
>>> for iteration, value in trend:
|
292
332
|
... print(f"Iteration {iteration}: {value}")
|
293
333
|
"""
|
294
|
-
return [
|
295
|
-
|
334
|
+
return [
|
335
|
+
(iter.iteration_number, iter.convergence_value) for iter in self.iterations
|
336
|
+
]
|
337
|
+
|
296
338
|
def to_dict(self) -> Dict[str, Any]:
|
297
339
|
"""Convert trace to dictionary for serialization."""
|
298
340
|
return {
|
@@ -310,67 +352,67 @@ class CycleExecutionTrace:
|
|
310
352
|
"convergence_condition": self.convergence_condition,
|
311
353
|
"memory_peak_mb": self.memory_peak_mb,
|
312
354
|
"cpu_peak_percent": self.cpu_peak_percent,
|
313
|
-
"statistics": self.get_statistics()
|
355
|
+
"statistics": self.get_statistics(),
|
314
356
|
}
|
315
357
|
|
316
358
|
|
317
359
|
class CycleDebugger:
|
318
360
|
"""
|
319
361
|
Comprehensive debugging tool for cyclic workflow execution.
|
320
|
-
|
362
|
+
|
321
363
|
This class provides real-time debugging capabilities for cycles, including
|
322
364
|
iteration tracking, performance monitoring, convergence analysis, and
|
323
365
|
detailed execution tracing. It integrates with the cycle execution system
|
324
366
|
to provide insights into cycle behavior and performance.
|
325
|
-
|
367
|
+
|
326
368
|
Design Philosophy:
|
327
369
|
Provides non-intrusive debugging that doesn't affect cycle performance
|
328
370
|
in production. Offers multiple levels of debugging detail from basic
|
329
371
|
tracking to comprehensive profiling with rich analytics.
|
330
|
-
|
372
|
+
|
331
373
|
Upstream Dependencies:
|
332
374
|
- Used by CyclicWorkflowExecutor when debug mode is enabled
|
333
375
|
- Integrates with cycle configuration and execution systems
|
334
|
-
|
376
|
+
|
335
377
|
Downstream Consumers:
|
336
378
|
- Debug reports and analysis tools
|
337
379
|
- Performance optimization recommendations
|
338
380
|
- Cycle visualization and monitoring dashboards
|
339
|
-
|
381
|
+
|
340
382
|
Usage Patterns:
|
341
383
|
1. Real-time debugging during development
|
342
384
|
2. Performance profiling for optimization
|
343
385
|
3. Production monitoring for cycle health
|
344
386
|
4. Post-execution analysis for troubleshooting
|
345
|
-
|
387
|
+
|
346
388
|
Example:
|
347
389
|
>>> debugger = CycleDebugger(debug_level="detailed")
|
348
390
|
>>> trace = debugger.start_cycle("optimization", "workflow_1")
|
349
|
-
>>>
|
391
|
+
>>>
|
350
392
|
>>> # During cycle execution
|
351
393
|
>>> iteration = debugger.start_iteration(trace, input_data)
|
352
394
|
>>> debugger.end_iteration(iteration, output_data)
|
353
|
-
>>>
|
395
|
+
>>>
|
354
396
|
>>> # After cycle completion
|
355
397
|
>>> debugger.end_cycle(trace, converged=True, reason="convergence")
|
356
398
|
>>> report = debugger.generate_report(trace)
|
357
399
|
"""
|
358
|
-
|
400
|
+
|
359
401
|
def __init__(self, debug_level: str = "basic", enable_profiling: bool = False):
|
360
402
|
"""
|
361
403
|
Initialize cycle debugger.
|
362
|
-
|
404
|
+
|
363
405
|
Args:
|
364
406
|
debug_level (str): Level of debugging detail ("basic", "detailed", "verbose")
|
365
407
|
enable_profiling (bool): Whether to enable detailed profiling
|
366
|
-
|
408
|
+
|
367
409
|
Side Effects:
|
368
410
|
Configures logging and profiling settings
|
369
411
|
"""
|
370
412
|
self.debug_level = debug_level
|
371
413
|
self.enable_profiling = enable_profiling
|
372
414
|
self.active_traces: Dict[str, CycleExecutionTrace] = {}
|
373
|
-
|
415
|
+
|
374
416
|
# Configure logging based on debug level
|
375
417
|
if debug_level == "verbose":
|
376
418
|
logger.setLevel(logging.DEBUG)
|
@@ -378,35 +420,35 @@ class CycleDebugger:
|
|
378
420
|
logger.setLevel(logging.INFO)
|
379
421
|
else:
|
380
422
|
logger.setLevel(logging.WARNING)
|
381
|
-
|
423
|
+
|
382
424
|
def start_cycle(
|
383
425
|
self,
|
384
426
|
cycle_id: str,
|
385
427
|
workflow_id: str,
|
386
428
|
max_iterations: Optional[int] = None,
|
387
429
|
timeout: Optional[float] = None,
|
388
|
-
convergence_condition: Optional[str] = None
|
430
|
+
convergence_condition: Optional[str] = None,
|
389
431
|
) -> CycleExecutionTrace:
|
390
432
|
"""
|
391
433
|
Start debugging a new cycle execution.
|
392
|
-
|
434
|
+
|
393
435
|
Creates a new execution trace and begins tracking cycle execution
|
394
436
|
with all configured debugging features enabled.
|
395
|
-
|
437
|
+
|
396
438
|
Args:
|
397
439
|
cycle_id (str): Unique identifier for the cycle
|
398
440
|
workflow_id (str): Parent workflow identifier
|
399
441
|
max_iterations (Optional[int]): Configured iteration limit
|
400
442
|
timeout (Optional[float]): Configured timeout limit
|
401
443
|
convergence_condition (Optional[str]): Convergence condition expression
|
402
|
-
|
444
|
+
|
403
445
|
Returns:
|
404
446
|
CycleExecutionTrace: New trace object for tracking execution
|
405
|
-
|
447
|
+
|
406
448
|
Side Effects:
|
407
449
|
Creates new trace and adds to active_traces
|
408
450
|
Logs cycle start event
|
409
|
-
|
451
|
+
|
410
452
|
Example:
|
411
453
|
>>> trace = debugger.start_cycle("opt_cycle", "workflow_1", max_iterations=100)
|
412
454
|
"""
|
@@ -416,197 +458,210 @@ class CycleDebugger:
|
|
416
458
|
start_time=datetime.now(),
|
417
459
|
max_iterations_configured=max_iterations,
|
418
460
|
timeout_configured=timeout,
|
419
|
-
convergence_condition=convergence_condition
|
461
|
+
convergence_condition=convergence_condition,
|
420
462
|
)
|
421
|
-
|
463
|
+
|
422
464
|
self.active_traces[cycle_id] = trace
|
423
|
-
|
465
|
+
|
424
466
|
logger.info(
|
425
467
|
f"Started debugging cycle '{cycle_id}' in workflow '{workflow_id}' "
|
426
468
|
f"with max_iterations={max_iterations}, timeout={timeout}"
|
427
469
|
)
|
428
|
-
|
470
|
+
|
429
471
|
return trace
|
430
|
-
|
472
|
+
|
431
473
|
def start_iteration(
|
432
474
|
self,
|
433
475
|
trace: CycleExecutionTrace,
|
434
476
|
input_data: Dict[str, Any],
|
435
|
-
iteration_number: Optional[int] = None
|
477
|
+
iteration_number: Optional[int] = None,
|
436
478
|
) -> CycleIteration:
|
437
479
|
"""
|
438
480
|
Start debugging a new cycle iteration.
|
439
|
-
|
481
|
+
|
440
482
|
Creates a new iteration object and begins tracking execution time,
|
441
483
|
resource usage, and other iteration-specific metrics.
|
442
|
-
|
484
|
+
|
443
485
|
Args:
|
444
486
|
trace (CycleExecutionTrace): Parent cycle trace
|
445
487
|
input_data (Dict[str, Any]): Input data for this iteration
|
446
488
|
iteration_number (Optional[int]): Iteration number (auto-calculated if None)
|
447
|
-
|
489
|
+
|
448
490
|
Returns:
|
449
491
|
CycleIteration: New iteration object for tracking
|
450
|
-
|
492
|
+
|
451
493
|
Side Effects:
|
452
494
|
Creates new iteration and adds to trace
|
453
495
|
Begins resource monitoring if profiling enabled
|
454
|
-
|
496
|
+
|
455
497
|
Example:
|
456
498
|
>>> iteration = debugger.start_iteration(trace, {"value": 10})
|
457
499
|
"""
|
458
500
|
if iteration_number is None:
|
459
501
|
iteration_number = len(trace.iterations) + 1
|
460
|
-
|
502
|
+
|
461
503
|
iteration = CycleIteration(
|
462
504
|
iteration_number=iteration_number,
|
463
505
|
start_time=datetime.now(),
|
464
|
-
input_data=
|
506
|
+
input_data=(
|
507
|
+
input_data.copy() if self.debug_level in ["detailed", "verbose"] else {}
|
508
|
+
),
|
465
509
|
)
|
466
|
-
|
510
|
+
|
467
511
|
# Add profiling data if enabled
|
468
512
|
if self.enable_profiling:
|
469
513
|
try:
|
470
514
|
import psutil
|
515
|
+
|
471
516
|
process = psutil.Process()
|
472
517
|
iteration.memory_usage_mb = process.memory_info().rss / 1024 / 1024
|
473
518
|
iteration.cpu_usage_percent = process.cpu_percent()
|
474
519
|
except ImportError:
|
475
|
-
logger.warning(
|
476
|
-
|
520
|
+
logger.warning(
|
521
|
+
"psutil not available for profiling. Install with: pip install psutil"
|
522
|
+
)
|
523
|
+
|
477
524
|
if self.debug_level == "verbose":
|
478
525
|
logger.debug(
|
479
526
|
f"Started iteration {iteration_number} for cycle '{trace.cycle_id}' "
|
480
527
|
f"with input keys: {list(input_data.keys())}"
|
481
528
|
)
|
482
|
-
|
529
|
+
|
483
530
|
return iteration
|
484
|
-
|
531
|
+
|
485
532
|
def end_iteration(
|
486
533
|
self,
|
487
534
|
trace: CycleExecutionTrace,
|
488
535
|
iteration: CycleIteration,
|
489
536
|
output_data: Dict[str, Any],
|
490
537
|
convergence_value: Optional[float] = None,
|
491
|
-
node_executions: Optional[List[str]] = None
|
538
|
+
node_executions: Optional[List[str]] = None,
|
492
539
|
):
|
493
540
|
"""
|
494
541
|
Complete iteration tracking with output data and metrics.
|
495
|
-
|
542
|
+
|
496
543
|
Finalizes iteration tracking by recording output data, convergence
|
497
544
|
metrics, and final resource usage measurements.
|
498
|
-
|
545
|
+
|
499
546
|
Args:
|
500
547
|
trace (CycleExecutionTrace): Parent cycle trace
|
501
548
|
iteration (CycleIteration): Iteration object to complete
|
502
549
|
output_data (Dict[str, Any]): Output data from iteration
|
503
550
|
convergence_value (Optional[float]): Convergence metric if available
|
504
551
|
node_executions (Optional[List[str]]): List of executed nodes
|
505
|
-
|
552
|
+
|
506
553
|
Side Effects:
|
507
554
|
Completes iteration and adds to trace
|
508
555
|
Updates peak resource usage in trace
|
509
556
|
Logs iteration completion
|
510
|
-
|
557
|
+
|
511
558
|
Example:
|
512
559
|
>>> debugger.end_iteration(trace, iteration, {"result": 20}, convergence_value=0.05)
|
513
560
|
"""
|
514
|
-
iteration.complete(
|
515
|
-
|
561
|
+
iteration.complete(
|
562
|
+
output_data.copy() if self.debug_level in ["detailed", "verbose"] else {},
|
563
|
+
convergence_value,
|
564
|
+
)
|
565
|
+
|
516
566
|
if node_executions:
|
517
567
|
iteration.node_executions = node_executions
|
518
|
-
|
568
|
+
|
519
569
|
# Update profiling data if enabled
|
520
570
|
if self.enable_profiling:
|
521
571
|
try:
|
522
572
|
import psutil
|
573
|
+
|
523
574
|
process = psutil.Process()
|
524
575
|
end_memory = process.memory_info().rss / 1024 / 1024
|
525
576
|
end_cpu = process.cpu_percent()
|
526
|
-
|
577
|
+
|
527
578
|
# Use the higher value for this iteration
|
528
|
-
iteration.memory_usage_mb = max(
|
529
|
-
|
579
|
+
iteration.memory_usage_mb = max(
|
580
|
+
iteration.memory_usage_mb or 0, end_memory
|
581
|
+
)
|
582
|
+
iteration.cpu_usage_percent = max(
|
583
|
+
iteration.cpu_usage_percent or 0, end_cpu
|
584
|
+
)
|
530
585
|
except ImportError:
|
531
586
|
pass # Already warned during start_iteration
|
532
|
-
|
587
|
+
|
533
588
|
trace.add_iteration(iteration)
|
534
|
-
|
589
|
+
|
535
590
|
if self.debug_level in ["detailed", "verbose"]:
|
536
591
|
logger.info(
|
537
592
|
f"Completed iteration {iteration.iteration_number} for cycle '{trace.cycle_id}' "
|
538
593
|
f"in {iteration.execution_time:.3f}s, convergence={convergence_value}"
|
539
594
|
)
|
540
|
-
|
595
|
+
|
541
596
|
def end_cycle(
|
542
597
|
self,
|
543
598
|
trace: CycleExecutionTrace,
|
544
599
|
converged: bool,
|
545
600
|
termination_reason: str,
|
546
|
-
convergence_iteration: Optional[int] = None
|
601
|
+
convergence_iteration: Optional[int] = None,
|
547
602
|
):
|
548
603
|
"""
|
549
604
|
Complete cycle tracking with final results and analysis.
|
550
|
-
|
605
|
+
|
551
606
|
Finalizes cycle execution tracking and generates comprehensive
|
552
607
|
statistics and analysis for the complete cycle execution.
|
553
|
-
|
608
|
+
|
554
609
|
Args:
|
555
610
|
trace (CycleExecutionTrace): Cycle trace to complete
|
556
611
|
converged (bool): Whether the cycle converged successfully
|
557
612
|
termination_reason (str): Why the cycle terminated
|
558
613
|
convergence_iteration (Optional[int]): Iteration where convergence occurred
|
559
|
-
|
614
|
+
|
560
615
|
Side Effects:
|
561
616
|
Completes trace and removes from active_traces
|
562
617
|
Logs cycle completion with statistics
|
563
|
-
|
618
|
+
|
564
619
|
Example:
|
565
620
|
>>> debugger.end_cycle(trace, converged=True, termination_reason="convergence", convergence_iteration=15)
|
566
621
|
"""
|
567
622
|
trace.complete(converged, termination_reason, convergence_iteration)
|
568
|
-
|
623
|
+
|
569
624
|
# Remove from active traces
|
570
625
|
if trace.cycle_id in self.active_traces:
|
571
626
|
del self.active_traces[trace.cycle_id]
|
572
|
-
|
627
|
+
|
573
628
|
stats = trace.get_statistics()
|
574
629
|
logger.info(
|
575
630
|
f"Completed cycle '{trace.cycle_id}' in {trace.total_execution_time:.3f}s "
|
576
631
|
f"with {stats['total_iterations']} iterations, "
|
577
632
|
f"converged={converged}, efficiency={stats['efficiency_score']:.2f}"
|
578
633
|
)
|
579
|
-
|
634
|
+
|
580
635
|
def generate_report(self, trace: CycleExecutionTrace) -> Dict[str, Any]:
|
581
636
|
"""
|
582
637
|
Generate comprehensive debugging report for a cycle execution.
|
583
|
-
|
638
|
+
|
584
639
|
Creates a detailed report including execution statistics, performance
|
585
640
|
analysis, convergence trends, and optimization recommendations based
|
586
641
|
on the complete cycle execution trace.
|
587
|
-
|
642
|
+
|
588
643
|
Args:
|
589
644
|
trace (CycleExecutionTrace): Completed cycle trace to analyze
|
590
|
-
|
645
|
+
|
591
646
|
Returns:
|
592
647
|
Dict[str, Any]: Comprehensive debugging report
|
593
|
-
|
648
|
+
|
594
649
|
Side Effects:
|
595
650
|
None - this is a pure analysis method
|
596
|
-
|
651
|
+
|
597
652
|
Example:
|
598
653
|
>>> report = debugger.generate_report(trace)
|
599
654
|
>>> print(f"Efficiency score: {report['performance']['efficiency_score']}")
|
600
655
|
"""
|
601
656
|
stats = trace.get_statistics()
|
602
657
|
convergence_trend = trace.get_convergence_trend()
|
603
|
-
|
658
|
+
|
604
659
|
# Analyze convergence pattern
|
605
660
|
convergence_analysis = self._analyze_convergence(convergence_trend)
|
606
|
-
|
661
|
+
|
607
662
|
# Generate optimization recommendations
|
608
663
|
recommendations = self._generate_recommendations(trace, stats)
|
609
|
-
|
664
|
+
|
610
665
|
# Create performance summary
|
611
666
|
performance = {
|
612
667
|
"efficiency_score": stats["efficiency_score"],
|
@@ -614,46 +669,59 @@ class CycleDebugger:
|
|
614
669
|
"convergence_rate": stats["convergence_rate"],
|
615
670
|
"resource_usage": {
|
616
671
|
"memory_peak_mb": trace.memory_peak_mb,
|
617
|
-
"cpu_peak_percent": trace.cpu_peak_percent
|
618
|
-
}
|
672
|
+
"cpu_peak_percent": trace.cpu_peak_percent,
|
673
|
+
},
|
619
674
|
}
|
620
|
-
|
675
|
+
|
621
676
|
report = {
|
622
677
|
"cycle_info": {
|
623
678
|
"cycle_id": trace.cycle_id,
|
624
679
|
"workflow_id": trace.workflow_id,
|
625
680
|
"execution_time": trace.total_execution_time,
|
626
681
|
"converged": trace.converged,
|
627
|
-
"termination_reason": trace.termination_reason
|
682
|
+
"termination_reason": trace.termination_reason,
|
628
683
|
},
|
629
684
|
"statistics": stats,
|
630
685
|
"performance": performance,
|
631
686
|
"convergence_analysis": convergence_analysis,
|
632
687
|
"recommendations": recommendations,
|
633
|
-
"trace_data": trace.to_dict() if self.debug_level == "verbose" else None
|
688
|
+
"trace_data": trace.to_dict() if self.debug_level == "verbose" else None,
|
634
689
|
}
|
635
|
-
|
690
|
+
|
636
691
|
return report
|
637
|
-
|
638
|
-
def _analyze_convergence(
|
692
|
+
|
693
|
+
def _analyze_convergence(
|
694
|
+
self, convergence_trend: List[Tuple[int, Optional[float]]]
|
695
|
+
) -> Dict[str, Any]:
|
639
696
|
"""Analyze convergence pattern from trend data."""
|
640
|
-
if not convergence_trend or all(
|
697
|
+
if not convergence_trend or all(
|
698
|
+
value is None for _, value in convergence_trend
|
699
|
+
):
|
641
700
|
return {"pattern": "no_data", "analysis": "No convergence data available"}
|
642
|
-
|
701
|
+
|
643
702
|
# Filter out None values
|
644
|
-
valid_points = [
|
645
|
-
|
703
|
+
valid_points = [
|
704
|
+
(iter_num, value)
|
705
|
+
for iter_num, value in convergence_trend
|
706
|
+
if value is not None
|
707
|
+
]
|
708
|
+
|
646
709
|
if len(valid_points) < 2:
|
647
|
-
return {
|
648
|
-
|
710
|
+
return {
|
711
|
+
"pattern": "insufficient_data",
|
712
|
+
"analysis": "Insufficient convergence data for analysis",
|
713
|
+
}
|
714
|
+
|
649
715
|
# Analyze trend
|
650
716
|
values = [value for _, value in valid_points]
|
651
|
-
|
717
|
+
|
652
718
|
# Check for improvement pattern
|
653
719
|
if len(values) >= 3:
|
654
|
-
improving_count = sum(
|
720
|
+
improving_count = sum(
|
721
|
+
1 for i in range(1, len(values)) if values[i] < values[i - 1]
|
722
|
+
)
|
655
723
|
improvement_ratio = improving_count / (len(values) - 1)
|
656
|
-
|
724
|
+
|
657
725
|
if improvement_ratio > 0.8:
|
658
726
|
pattern = "steady_improvement"
|
659
727
|
analysis = "Convergence is steadily improving"
|
@@ -669,92 +737,124 @@ class CycleDebugger:
|
|
669
737
|
else:
|
670
738
|
pattern = "limited_data"
|
671
739
|
analysis = "Limited data for pattern analysis"
|
672
|
-
|
740
|
+
|
673
741
|
return {
|
674
742
|
"pattern": pattern,
|
675
743
|
"analysis": analysis,
|
676
744
|
"initial_value": values[0] if values else None,
|
677
745
|
"final_value": values[-1] if values else None,
|
678
746
|
"improvement": values[0] - values[-1] if len(values) >= 2 else None,
|
679
|
-
"data_points": len(valid_points)
|
747
|
+
"data_points": len(valid_points),
|
680
748
|
}
|
681
|
-
|
682
|
-
def _generate_recommendations(
|
749
|
+
|
750
|
+
def _generate_recommendations(
|
751
|
+
self, trace: CycleExecutionTrace, stats: Dict[str, Any]
|
752
|
+
) -> List[str]:
|
683
753
|
"""Generate optimization recommendations based on execution analysis."""
|
684
754
|
recommendations = []
|
685
|
-
|
755
|
+
|
686
756
|
# Efficiency recommendations
|
687
757
|
if stats["efficiency_score"] < 0.5:
|
688
|
-
recommendations.append(
|
689
|
-
|
758
|
+
recommendations.append(
|
759
|
+
"Consider reducing max_iterations or improving convergence condition"
|
760
|
+
)
|
761
|
+
|
690
762
|
# Convergence recommendations
|
691
763
|
if not trace.converged:
|
692
764
|
if stats["total_iterations"] >= (trace.max_iterations_configured or 0):
|
693
|
-
recommendations.append(
|
765
|
+
recommendations.append(
|
766
|
+
"Cycle reached max_iterations without converging - increase limit or improve algorithm"
|
767
|
+
)
|
694
768
|
else:
|
695
|
-
recommendations.append(
|
696
|
-
|
769
|
+
recommendations.append(
|
770
|
+
"Cycle terminated early - check for errors or timeout issues"
|
771
|
+
)
|
772
|
+
|
697
773
|
# Performance recommendations
|
698
774
|
if stats["avg_iteration_time"] > 1.0:
|
699
|
-
recommendations.append(
|
700
|
-
|
775
|
+
recommendations.append(
|
776
|
+
"Average iteration time is high - consider optimizing node performance"
|
777
|
+
)
|
778
|
+
|
701
779
|
if trace.memory_peak_mb and trace.memory_peak_mb > 1000:
|
702
|
-
recommendations.append(
|
703
|
-
|
780
|
+
recommendations.append(
|
781
|
+
"High memory usage detected - consider data streaming or chunking"
|
782
|
+
)
|
783
|
+
|
704
784
|
# Convergence pattern recommendations
|
705
785
|
convergence_trend = trace.get_convergence_trend()
|
706
786
|
convergence_analysis = self._analyze_convergence(convergence_trend)
|
707
|
-
|
787
|
+
|
708
788
|
if convergence_analysis["pattern"] == "plateau":
|
709
|
-
recommendations.append(
|
789
|
+
recommendations.append(
|
790
|
+
"Convergence plateaued - try different learning rate or algorithm parameters"
|
791
|
+
)
|
710
792
|
elif convergence_analysis["pattern"] == "unstable":
|
711
|
-
recommendations.append(
|
712
|
-
|
793
|
+
recommendations.append(
|
794
|
+
"Unstable convergence - reduce learning rate or add regularization"
|
795
|
+
)
|
796
|
+
|
713
797
|
# Success recommendations
|
714
798
|
if trace.converged and stats["efficiency_score"] > 0.8:
|
715
|
-
recommendations.append(
|
716
|
-
|
799
|
+
recommendations.append(
|
800
|
+
"Excellent cycle performance - consider using as template for similar workflows"
|
801
|
+
)
|
802
|
+
|
717
803
|
return recommendations
|
718
|
-
|
719
|
-
def export_trace(
|
804
|
+
|
805
|
+
def export_trace(
|
806
|
+
self, trace: CycleExecutionTrace, filepath: str, format: str = "json"
|
807
|
+
):
|
720
808
|
"""
|
721
809
|
Export cycle trace to file for external analysis.
|
722
|
-
|
810
|
+
|
723
811
|
Args:
|
724
812
|
trace (CycleExecutionTrace): Trace to export
|
725
813
|
filepath (str): Output file path
|
726
814
|
format (str): Export format ("json", "csv")
|
727
|
-
|
815
|
+
|
728
816
|
Side Effects:
|
729
817
|
Creates file at specified path with trace data
|
730
|
-
|
818
|
+
|
731
819
|
Example:
|
732
820
|
>>> debugger.export_trace(trace, "cycle_debug.json", "json")
|
733
821
|
"""
|
734
822
|
trace_data = trace.to_dict()
|
735
|
-
|
823
|
+
|
736
824
|
if format == "json":
|
737
|
-
with open(filepath,
|
825
|
+
with open(filepath, "w") as f:
|
738
826
|
json.dump(trace_data, f, indent=2)
|
739
827
|
elif format == "csv":
|
740
828
|
import csv
|
741
|
-
|
829
|
+
|
830
|
+
with open(filepath, "w", newline="") as f:
|
742
831
|
writer = csv.writer(f)
|
743
|
-
|
832
|
+
|
744
833
|
# Write header
|
745
|
-
writer.writerow(
|
746
|
-
|
834
|
+
writer.writerow(
|
835
|
+
[
|
836
|
+
"iteration",
|
837
|
+
"execution_time",
|
838
|
+
"memory_mb",
|
839
|
+
"cpu_percent",
|
840
|
+
"convergence",
|
841
|
+
"error",
|
842
|
+
]
|
843
|
+
)
|
844
|
+
|
747
845
|
# Write iteration data
|
748
846
|
for iteration in trace.iterations:
|
749
|
-
writer.writerow(
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
847
|
+
writer.writerow(
|
848
|
+
[
|
849
|
+
iteration.iteration_number,
|
850
|
+
iteration.execution_time,
|
851
|
+
iteration.memory_usage_mb,
|
852
|
+
iteration.cpu_usage_percent,
|
853
|
+
iteration.convergence_value,
|
854
|
+
iteration.error or "",
|
855
|
+
]
|
856
|
+
)
|
757
857
|
else:
|
758
858
|
raise ValueError(f"Unsupported export format: {format}")
|
759
|
-
|
760
|
-
logger.info(f"Exported cycle trace to {filepath} in {format} format")
|
859
|
+
|
860
|
+
logger.info(f"Exported cycle trace to {filepath} in {format} format")
|