kailash 0.1.5__py3-none-any.whl → 0.2.0__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/readers.py +16 -6
- 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 +768 -0
- kailash/workflow/cycle_builder.py +573 -0
- kailash/workflow/cycle_config.py +709 -0
- kailash/workflow/cycle_debugger.py +760 -0
- kailash/workflow/cycle_exceptions.py +601 -0
- kailash/workflow/cycle_profiler.py +671 -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 +768 -0
- kailash/workflow/safety.py +365 -0
- kailash/workflow/templates.py +744 -0
- kailash/workflow/validation.py +693 -0
- {kailash-0.1.5.dist-info → kailash-0.2.0.dist-info}/METADATA +256 -12
- kailash-0.2.0.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.0.dist-info}/WHEEL +0 -0
- {kailash-0.1.5.dist-info → kailash-0.2.0.dist-info}/entry_points.txt +0 -0
- {kailash-0.1.5.dist-info → kailash-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.1.5.dist-info → kailash-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,374 @@
|
|
1
|
+
"""
|
2
|
+
Base class for cycle-aware nodes that provides built-in helpers for cyclic workflow patterns.
|
3
|
+
|
4
|
+
This module provides CycleAwareNode, a base class that eliminates boilerplate code
|
5
|
+
for nodes that need to work with cyclic workflows. It provides convenient methods
|
6
|
+
for accessing cycle information, managing state across iterations, and implementing
|
7
|
+
common cycle patterns.
|
8
|
+
|
9
|
+
Design Philosophy:
|
10
|
+
CycleAwareNode abstracts away the complexity of managing state and iteration
|
11
|
+
tracking in cyclic workflows. It provides a clean API for nodes to focus on
|
12
|
+
their business logic while the base class handles cycle mechanics.
|
13
|
+
|
14
|
+
Example usage:
|
15
|
+
>>> from kailash.nodes.base_cycle_aware import CycleAwareNode
|
16
|
+
>>> from kailash.nodes.base import NodeParameter
|
17
|
+
>>>
|
18
|
+
>>> class MyProcessorNode(CycleAwareNode):
|
19
|
+
... def get_parameters(self):
|
20
|
+
... return {
|
21
|
+
... "data": NodeParameter(name="data", type=list, required=True)
|
22
|
+
... }
|
23
|
+
...
|
24
|
+
... def run(self, context, **kwargs):
|
25
|
+
... iteration = self.get_iteration(context)
|
26
|
+
... is_first = self.is_first_iteration(context)
|
27
|
+
... prev_results = self.get_previous_state(context)
|
28
|
+
...
|
29
|
+
... # Process data with cycle awareness
|
30
|
+
... processed = self.process_with_history(kwargs["data"], prev_results)
|
31
|
+
...
|
32
|
+
... # Return result with state for next iteration
|
33
|
+
... return {
|
34
|
+
... "result": processed,
|
35
|
+
... **self.set_cycle_state({"last_result": processed})
|
36
|
+
... }
|
37
|
+
"""
|
38
|
+
|
39
|
+
import time
|
40
|
+
from typing import Any, Dict
|
41
|
+
|
42
|
+
from .base import Node
|
43
|
+
|
44
|
+
|
45
|
+
class CycleAwareNode(Node):
|
46
|
+
"""
|
47
|
+
Base class for nodes that are cycle-aware with built-in helpers.
|
48
|
+
|
49
|
+
This class provides convenient methods for working with cyclic workflows,
|
50
|
+
eliminating common boilerplate code for cycle information access and
|
51
|
+
state management across iterations.
|
52
|
+
|
53
|
+
Design Philosophy:
|
54
|
+
CycleAwareNode is designed to make cyclic workflows as simple to write
|
55
|
+
as regular nodes. It handles all the complexity of iteration tracking,
|
56
|
+
state persistence, and cycle information management, allowing developers
|
57
|
+
to focus on the iterative logic.
|
58
|
+
|
59
|
+
Upstream Dependencies:
|
60
|
+
- Node: Base class that provides core node functionality
|
61
|
+
- CyclicWorkflowExecutor: Provides cycle context in execution
|
62
|
+
- Workflow: Must be configured with cycle=True connections
|
63
|
+
|
64
|
+
Downstream Consumers:
|
65
|
+
- ConvergenceCheckerNode: Uses cycle helpers for convergence detection
|
66
|
+
- A2ACoordinatorNode: Tracks agent performance across iterations
|
67
|
+
- Any custom nodes needing cycle-aware behavior
|
68
|
+
|
69
|
+
Configuration:
|
70
|
+
No specific configuration required. Inherit from this class and use
|
71
|
+
the provided helper methods in your run() implementation.
|
72
|
+
|
73
|
+
Implementation Details:
|
74
|
+
- Extracts cycle information from execution context
|
75
|
+
- Provides safe accessors with sensible defaults
|
76
|
+
- Manages state persistence through _cycle_state convention
|
77
|
+
- Offers utility methods for common patterns
|
78
|
+
|
79
|
+
Error Handling:
|
80
|
+
- Returns default values if cycle information is missing
|
81
|
+
- Handles missing state gracefully with empty dicts
|
82
|
+
- Safe for use in non-cyclic contexts (acts as regular node)
|
83
|
+
|
84
|
+
Side Effects:
|
85
|
+
- Logs cycle progress when log_cycle_info() is called
|
86
|
+
- No other external side effects
|
87
|
+
|
88
|
+
Examples:
|
89
|
+
>>> class QualityImproverNode(CycleAwareNode):
|
90
|
+
... def run(self, context, **kwargs):
|
91
|
+
... iteration = self.get_iteration(context)
|
92
|
+
... quality = kwargs.get("quality", 0.0)
|
93
|
+
...
|
94
|
+
... if self.is_first_iteration(context):
|
95
|
+
... print("Starting quality improvement process")
|
96
|
+
...
|
97
|
+
... # Improve quality based on iteration
|
98
|
+
... improved_quality = quality + (0.1 * iteration)
|
99
|
+
...
|
100
|
+
... return {
|
101
|
+
... "quality": improved_quality,
|
102
|
+
... **self.set_cycle_state({"last_quality": improved_quality})
|
103
|
+
... }
|
104
|
+
"""
|
105
|
+
|
106
|
+
def get_cycle_info(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
107
|
+
"""
|
108
|
+
Get cycle information with sensible defaults.
|
109
|
+
|
110
|
+
Extracts cycle information from the execution context, providing
|
111
|
+
default values for missing fields to prevent KeyError exceptions.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
context: Execution context containing cycle information
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
Dictionary containing cycle information with guaranteed fields:
|
118
|
+
- iteration: Current iteration number (default: 0)
|
119
|
+
- elapsed_time: Time elapsed in seconds (default: 0.0)
|
120
|
+
- cycle_id: Unique cycle identifier (default: "default")
|
121
|
+
- max_iterations: Maximum allowed iterations (default: 100)
|
122
|
+
- start_time: Cycle start timestamp (default: current time)
|
123
|
+
|
124
|
+
Example:
|
125
|
+
>>> cycle_info = self.get_cycle_info(context)
|
126
|
+
>>> print(f"Iteration {cycle_info['iteration']} of {cycle_info['max_iterations']}")
|
127
|
+
"""
|
128
|
+
cycle_info = context.get("cycle", {})
|
129
|
+
current_time = time.time()
|
130
|
+
|
131
|
+
return {
|
132
|
+
"iteration": cycle_info.get("iteration", 0),
|
133
|
+
"elapsed_time": cycle_info.get("elapsed_time", 0.0),
|
134
|
+
"cycle_id": cycle_info.get("cycle_id", "default"),
|
135
|
+
"max_iterations": cycle_info.get("max_iterations", 100),
|
136
|
+
"start_time": cycle_info.get("start_time", current_time),
|
137
|
+
"convergence_check": cycle_info.get("convergence_check"),
|
138
|
+
**cycle_info, # Include any additional cycle information
|
139
|
+
}
|
140
|
+
|
141
|
+
def get_iteration(self, context: Dict[str, Any]) -> int:
|
142
|
+
"""
|
143
|
+
Get current iteration number.
|
144
|
+
|
145
|
+
Args:
|
146
|
+
context: Execution context
|
147
|
+
|
148
|
+
Returns:
|
149
|
+
Current iteration number (0-based)
|
150
|
+
|
151
|
+
Example:
|
152
|
+
>>> iteration = self.get_iteration(context)
|
153
|
+
>>> if iteration > 10:
|
154
|
+
... print("Long-running cycle detected")
|
155
|
+
"""
|
156
|
+
return self.get_cycle_info(context)["iteration"]
|
157
|
+
|
158
|
+
def is_first_iteration(self, context: Dict[str, Any]) -> bool:
|
159
|
+
"""
|
160
|
+
Check if this is the first iteration of the cycle.
|
161
|
+
|
162
|
+
Args:
|
163
|
+
context: Execution context
|
164
|
+
|
165
|
+
Returns:
|
166
|
+
True if this is iteration 0, False otherwise
|
167
|
+
|
168
|
+
Example:
|
169
|
+
>>> if self.is_first_iteration(context):
|
170
|
+
... print("Initializing cycle state")
|
171
|
+
... return self.initialize_state()
|
172
|
+
"""
|
173
|
+
return self.get_iteration(context) == 0
|
174
|
+
|
175
|
+
def is_last_iteration(self, context: Dict[str, Any]) -> bool:
|
176
|
+
"""
|
177
|
+
Check if this is the last iteration of the cycle.
|
178
|
+
|
179
|
+
Args:
|
180
|
+
context: Execution context
|
181
|
+
|
182
|
+
Returns:
|
183
|
+
True if this is the final iteration, False otherwise
|
184
|
+
|
185
|
+
Example:
|
186
|
+
>>> if self.is_last_iteration(context):
|
187
|
+
... print("Performing final cleanup")
|
188
|
+
"""
|
189
|
+
cycle_info = self.get_cycle_info(context)
|
190
|
+
return cycle_info["iteration"] >= cycle_info["max_iterations"] - 1
|
191
|
+
|
192
|
+
def get_previous_state(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
193
|
+
"""
|
194
|
+
Get previous iteration state safely.
|
195
|
+
|
196
|
+
Retrieves state that was persisted from the previous iteration
|
197
|
+
using set_cycle_state(). Returns empty dict if no state exists.
|
198
|
+
|
199
|
+
Args:
|
200
|
+
context: Execution context
|
201
|
+
|
202
|
+
Returns:
|
203
|
+
Dictionary containing state from previous iteration
|
204
|
+
|
205
|
+
Example:
|
206
|
+
>>> prev_state = self.get_previous_state(context)
|
207
|
+
>>> history = prev_state.get("value_history", [])
|
208
|
+
>>> print(f"Previous values: {history}")
|
209
|
+
"""
|
210
|
+
return self.get_cycle_info(context).get("node_state", {})
|
211
|
+
|
212
|
+
def set_cycle_state(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
213
|
+
"""
|
214
|
+
Set state to persist to next iteration.
|
215
|
+
|
216
|
+
Creates the special _cycle_state return value that the cycle
|
217
|
+
executor will persist and make available in the next iteration
|
218
|
+
via get_previous_state().
|
219
|
+
|
220
|
+
Args:
|
221
|
+
state: Dictionary of state to persist
|
222
|
+
|
223
|
+
Returns:
|
224
|
+
Dictionary with _cycle_state key for return from run()
|
225
|
+
|
226
|
+
Example:
|
227
|
+
>>> # In run() method:
|
228
|
+
>>> current_values = [1, 2, 3]
|
229
|
+
>>> return {
|
230
|
+
... "result": processed_data,
|
231
|
+
... **self.set_cycle_state({"values": current_values})
|
232
|
+
... }
|
233
|
+
"""
|
234
|
+
return {"_cycle_state": state}
|
235
|
+
|
236
|
+
def get_cycle_progress(self, context: Dict[str, Any]) -> float:
|
237
|
+
"""
|
238
|
+
Get cycle progress as a percentage.
|
239
|
+
|
240
|
+
Args:
|
241
|
+
context: Execution context
|
242
|
+
|
243
|
+
Returns:
|
244
|
+
Progress percentage (0.0 to 1.0)
|
245
|
+
|
246
|
+
Example:
|
247
|
+
>>> progress = self.get_cycle_progress(context)
|
248
|
+
>>> print(f"Cycle {progress*100:.1f}% complete")
|
249
|
+
"""
|
250
|
+
cycle_info = self.get_cycle_info(context)
|
251
|
+
iteration = cycle_info["iteration"]
|
252
|
+
max_iterations = cycle_info["max_iterations"]
|
253
|
+
|
254
|
+
if max_iterations <= 0:
|
255
|
+
return 1.0
|
256
|
+
|
257
|
+
return min(iteration / max_iterations, 1.0)
|
258
|
+
|
259
|
+
def log_cycle_info(self, context: Dict[str, Any], message: str = "") -> None:
|
260
|
+
"""
|
261
|
+
Log cycle information for debugging.
|
262
|
+
|
263
|
+
Convenient method to log current cycle state with optional message.
|
264
|
+
|
265
|
+
Args:
|
266
|
+
context: Execution context
|
267
|
+
message: Optional message to include in log
|
268
|
+
|
269
|
+
Example:
|
270
|
+
>>> self.log_cycle_info(context, "Processing batch")
|
271
|
+
# Output: [Cycle default] Iteration 3/10 (30.0%): Processing batch
|
272
|
+
"""
|
273
|
+
cycle_info = self.get_cycle_info(context)
|
274
|
+
progress = self.get_cycle_progress(context)
|
275
|
+
|
276
|
+
log_msg = (
|
277
|
+
f"[Cycle {cycle_info['cycle_id']}] "
|
278
|
+
f"Iteration {cycle_info['iteration']}/{cycle_info['max_iterations']} "
|
279
|
+
f"({progress*100:.1f}%)"
|
280
|
+
)
|
281
|
+
|
282
|
+
if message:
|
283
|
+
log_msg += f": {message}"
|
284
|
+
|
285
|
+
print(log_msg)
|
286
|
+
|
287
|
+
def should_continue_cycle(self, context: Dict[str, Any], **kwargs) -> bool:
|
288
|
+
"""
|
289
|
+
Helper method to determine if cycle should continue.
|
290
|
+
|
291
|
+
This is a convenience method that can be overridden by subclasses
|
292
|
+
to implement custom continuation logic. Default implementation
|
293
|
+
checks if max iterations reached.
|
294
|
+
|
295
|
+
Args:
|
296
|
+
context: Execution context
|
297
|
+
**kwargs: Additional parameters for decision making
|
298
|
+
|
299
|
+
Returns:
|
300
|
+
True if cycle should continue, False otherwise
|
301
|
+
|
302
|
+
Example:
|
303
|
+
>>> def should_continue_cycle(self, context, **kwargs):
|
304
|
+
... quality = kwargs.get("quality", 0.0)
|
305
|
+
... return quality < 0.95 and not self.is_last_iteration(context)
|
306
|
+
"""
|
307
|
+
return not self.is_last_iteration(context)
|
308
|
+
|
309
|
+
def accumulate_values(
|
310
|
+
self, context: Dict[str, Any], key: str, value: Any, max_history: int = 100
|
311
|
+
) -> list:
|
312
|
+
"""
|
313
|
+
Accumulate values across iterations with automatic history management.
|
314
|
+
|
315
|
+
Convenience method for maintaining a list of values across iterations
|
316
|
+
with automatic size management to prevent memory growth.
|
317
|
+
|
318
|
+
Args:
|
319
|
+
context: Execution context
|
320
|
+
key: State key for the value list
|
321
|
+
value: Value to add to the list
|
322
|
+
max_history: Maximum number of values to keep
|
323
|
+
|
324
|
+
Returns:
|
325
|
+
Updated list of values
|
326
|
+
|
327
|
+
Example:
|
328
|
+
>>> quality_history = self.accumulate_values(context, "quality", current_quality)
|
329
|
+
>>> avg_quality = sum(quality_history) / len(quality_history)
|
330
|
+
"""
|
331
|
+
prev_state = self.get_previous_state(context)
|
332
|
+
values = prev_state.get(key, [])
|
333
|
+
values.append(value)
|
334
|
+
|
335
|
+
# Keep only recent values to prevent memory growth
|
336
|
+
if len(values) > max_history:
|
337
|
+
values = values[-max_history:]
|
338
|
+
|
339
|
+
return values
|
340
|
+
|
341
|
+
def detect_convergence_trend(
|
342
|
+
self,
|
343
|
+
context: Dict[str, Any],
|
344
|
+
key: str,
|
345
|
+
threshold: float = 0.01,
|
346
|
+
window: int = 3,
|
347
|
+
) -> bool:
|
348
|
+
"""
|
349
|
+
Detect if values are converging (becoming stable).
|
350
|
+
|
351
|
+
Analyzes recent values to determine if they are converging to a stable value.
|
352
|
+
|
353
|
+
Args:
|
354
|
+
context: Execution context
|
355
|
+
key: State key containing value history
|
356
|
+
threshold: Maximum variance for convergence
|
357
|
+
window: Number of recent values to analyze
|
358
|
+
|
359
|
+
Returns:
|
360
|
+
True if values are converging, False otherwise
|
361
|
+
|
362
|
+
Example:
|
363
|
+
>>> if self.detect_convergence_trend(context, "error_rate", 0.001):
|
364
|
+
... return {"converged": True, "reason": "error_rate_stable"}
|
365
|
+
"""
|
366
|
+
prev_state = self.get_previous_state(context)
|
367
|
+
values = prev_state.get(key, [])
|
368
|
+
|
369
|
+
if len(values) < window:
|
370
|
+
return False
|
371
|
+
|
372
|
+
recent_values = values[-window:]
|
373
|
+
variance = max(recent_values) - min(recent_values)
|
374
|
+
return variance <= threshold
|