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