jaf-py 2.5.10__py3-none-any.whl → 2.5.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. jaf/__init__.py +154 -57
  2. jaf/a2a/__init__.py +42 -21
  3. jaf/a2a/agent.py +79 -126
  4. jaf/a2a/agent_card.py +87 -78
  5. jaf/a2a/client.py +30 -66
  6. jaf/a2a/examples/client_example.py +12 -12
  7. jaf/a2a/examples/integration_example.py +38 -47
  8. jaf/a2a/examples/server_example.py +56 -53
  9. jaf/a2a/memory/__init__.py +0 -4
  10. jaf/a2a/memory/cleanup.py +28 -21
  11. jaf/a2a/memory/factory.py +155 -133
  12. jaf/a2a/memory/providers/composite.py +21 -26
  13. jaf/a2a/memory/providers/in_memory.py +89 -83
  14. jaf/a2a/memory/providers/postgres.py +117 -115
  15. jaf/a2a/memory/providers/redis.py +128 -121
  16. jaf/a2a/memory/serialization.py +77 -87
  17. jaf/a2a/memory/tests/run_comprehensive_tests.py +112 -83
  18. jaf/a2a/memory/tests/test_cleanup.py +211 -94
  19. jaf/a2a/memory/tests/test_serialization.py +73 -68
  20. jaf/a2a/memory/tests/test_stress_concurrency.py +186 -133
  21. jaf/a2a/memory/tests/test_task_lifecycle.py +138 -120
  22. jaf/a2a/memory/types.py +91 -53
  23. jaf/a2a/protocol.py +95 -125
  24. jaf/a2a/server.py +90 -118
  25. jaf/a2a/standalone_client.py +30 -43
  26. jaf/a2a/tests/__init__.py +16 -33
  27. jaf/a2a/tests/run_tests.py +17 -53
  28. jaf/a2a/tests/test_agent.py +40 -140
  29. jaf/a2a/tests/test_client.py +54 -117
  30. jaf/a2a/tests/test_integration.py +28 -82
  31. jaf/a2a/tests/test_protocol.py +54 -139
  32. jaf/a2a/tests/test_types.py +50 -136
  33. jaf/a2a/types.py +58 -34
  34. jaf/cli.py +21 -41
  35. jaf/core/__init__.py +7 -1
  36. jaf/core/agent_tool.py +93 -72
  37. jaf/core/analytics.py +257 -207
  38. jaf/core/checkpoint.py +223 -0
  39. jaf/core/composition.py +249 -235
  40. jaf/core/engine.py +817 -519
  41. jaf/core/errors.py +55 -42
  42. jaf/core/guardrails.py +276 -202
  43. jaf/core/handoff.py +47 -31
  44. jaf/core/parallel_agents.py +69 -75
  45. jaf/core/performance.py +75 -73
  46. jaf/core/proxy.py +43 -44
  47. jaf/core/proxy_helpers.py +24 -27
  48. jaf/core/regeneration.py +220 -129
  49. jaf/core/state.py +68 -66
  50. jaf/core/streaming.py +115 -108
  51. jaf/core/tool_results.py +111 -101
  52. jaf/core/tools.py +114 -116
  53. jaf/core/tracing.py +269 -210
  54. jaf/core/types.py +371 -151
  55. jaf/core/workflows.py +209 -168
  56. jaf/exceptions.py +46 -38
  57. jaf/memory/__init__.py +1 -6
  58. jaf/memory/approval_storage.py +54 -77
  59. jaf/memory/factory.py +4 -4
  60. jaf/memory/providers/in_memory.py +216 -180
  61. jaf/memory/providers/postgres.py +216 -146
  62. jaf/memory/providers/redis.py +173 -116
  63. jaf/memory/types.py +70 -51
  64. jaf/memory/utils.py +36 -34
  65. jaf/plugins/__init__.py +12 -12
  66. jaf/plugins/base.py +105 -96
  67. jaf/policies/__init__.py +0 -1
  68. jaf/policies/handoff.py +37 -46
  69. jaf/policies/validation.py +76 -52
  70. jaf/providers/__init__.py +6 -3
  71. jaf/providers/mcp.py +97 -51
  72. jaf/providers/model.py +360 -279
  73. jaf/server/__init__.py +1 -1
  74. jaf/server/main.py +7 -11
  75. jaf/server/server.py +514 -359
  76. jaf/server/types.py +208 -52
  77. jaf/utils/__init__.py +17 -18
  78. jaf/utils/attachments.py +111 -116
  79. jaf/utils/document_processor.py +175 -174
  80. jaf/visualization/__init__.py +1 -1
  81. jaf/visualization/example.py +111 -110
  82. jaf/visualization/functional_core.py +46 -71
  83. jaf/visualization/graphviz.py +154 -189
  84. jaf/visualization/imperative_shell.py +7 -16
  85. jaf/visualization/types.py +8 -4
  86. {jaf_py-2.5.10.dist-info → jaf_py-2.5.11.dist-info}/METADATA +2 -2
  87. jaf_py-2.5.11.dist-info/RECORD +97 -0
  88. jaf_py-2.5.10.dist-info/RECORD +0 -96
  89. {jaf_py-2.5.10.dist-info → jaf_py-2.5.11.dist-info}/WHEEL +0 -0
  90. {jaf_py-2.5.10.dist-info → jaf_py-2.5.11.dist-info}/entry_points.txt +0 -0
  91. {jaf_py-2.5.10.dist-info → jaf_py-2.5.11.dist-info}/licenses/LICENSE +0 -0
  92. {jaf_py-2.5.10.dist-info → jaf_py-2.5.11.dist-info}/top_level.txt +0 -0
jaf/core/workflows.py CHANGED
@@ -20,6 +20,7 @@ from .analytics import global_analytics_engine
20
20
 
21
21
  class WorkflowStatus(Enum):
22
22
  """Status of workflow execution."""
23
+
23
24
  PENDING = "pending"
24
25
  RUNNING = "running"
25
26
  COMPLETED = "completed"
@@ -30,6 +31,7 @@ class WorkflowStatus(Enum):
30
31
 
31
32
  class StepStatus(Enum):
32
33
  """Status of individual workflow steps."""
34
+
33
35
  PENDING = "pending"
34
36
  RUNNING = "running"
35
37
  COMPLETED = "completed"
@@ -41,47 +43,49 @@ class StepStatus(Enum):
41
43
  @dataclass(frozen=True)
42
44
  class WorkflowContext:
43
45
  """Context passed through workflow execution."""
46
+
44
47
  workflow_id: str
45
48
  user_context: Any
46
49
  variables: Dict[str, Any] = field(default_factory=dict)
47
50
  metadata: Dict[str, Any] = field(default_factory=dict)
48
-
49
- def with_variable(self, key: str, value: Any) -> 'WorkflowContext':
51
+
52
+ def with_variable(self, key: str, value: Any) -> "WorkflowContext":
50
53
  """Create new context with additional variable."""
51
54
  new_vars = {**self.variables, key: value}
52
55
  return WorkflowContext(
53
56
  workflow_id=self.workflow_id,
54
57
  user_context=self.user_context,
55
58
  variables=new_vars,
56
- metadata=self.metadata
59
+ metadata=self.metadata,
57
60
  )
58
-
59
- def with_metadata(self, key: str, value: Any) -> 'WorkflowContext':
61
+
62
+ def with_metadata(self, key: str, value: Any) -> "WorkflowContext":
60
63
  """Create new context with additional metadata."""
61
64
  new_metadata = {**self.metadata, key: value}
62
65
  return WorkflowContext(
63
66
  workflow_id=self.workflow_id,
64
67
  user_context=self.user_context,
65
68
  variables=self.variables,
66
- metadata=new_metadata
69
+ metadata=new_metadata,
67
70
  )
68
71
 
69
72
 
70
73
  @dataclass(frozen=True)
71
74
  class StepResult:
72
75
  """Result of a workflow step execution."""
76
+
73
77
  step_id: str
74
78
  status: StepStatus
75
79
  output: Any = None
76
80
  error: Optional[str] = None
77
81
  execution_time_ms: float = 0
78
82
  metadata: Dict[str, Any] = field(default_factory=dict)
79
-
83
+
80
84
  @property
81
85
  def is_success(self) -> bool:
82
86
  """Check if step completed successfully."""
83
87
  return self.status == StepStatus.COMPLETED
84
-
88
+
85
89
  @property
86
90
  def is_failure(self) -> bool:
87
91
  """Check if step failed."""
@@ -91,6 +95,7 @@ class StepResult:
91
95
  @dataclass(frozen=True)
92
96
  class WorkflowResult:
93
97
  """Result of complete workflow execution."""
98
+
94
99
  workflow_id: str
95
100
  status: WorkflowStatus
96
101
  steps: List[StepResult]
@@ -98,17 +103,17 @@ class WorkflowResult:
98
103
  error: Optional[str] = None
99
104
  total_execution_time_ms: float = 0
100
105
  context: Optional[WorkflowContext] = None
101
-
106
+
102
107
  @property
103
108
  def is_success(self) -> bool:
104
109
  """Check if workflow completed successfully."""
105
110
  return self.status == WorkflowStatus.COMPLETED
106
-
111
+
107
112
  @property
108
113
  def failed_steps(self) -> List[StepResult]:
109
114
  """Get list of failed steps."""
110
115
  return [step for step in self.steps if step.is_failure]
111
-
116
+
112
117
  @property
113
118
  def success_rate(self) -> float:
114
119
  """Calculate success rate of steps."""
@@ -120,7 +125,7 @@ class WorkflowResult:
120
125
 
121
126
  class WorkflowStep(ABC):
122
127
  """Abstract base class for workflow steps."""
123
-
128
+
124
129
  def __init__(self, step_id: str, name: str, description: str = ""):
125
130
  self.step_id = step_id
126
131
  self.name = name
@@ -129,27 +134,27 @@ class WorkflowStep(ABC):
129
134
  self.max_retries = 3
130
135
  self.timeout_seconds = 30
131
136
  self.conditions: List[Callable[[WorkflowContext], bool]] = []
132
-
137
+
133
138
  @abstractmethod
134
139
  async def execute(self, context: WorkflowContext) -> StepResult:
135
140
  """Execute the workflow step."""
136
141
  pass
137
-
138
- def add_condition(self, condition: Callable[[WorkflowContext], bool]) -> 'WorkflowStep':
142
+
143
+ def add_condition(self, condition: Callable[[WorkflowContext], bool]) -> "WorkflowStep":
139
144
  """Add execution condition."""
140
145
  self.conditions.append(condition)
141
146
  return self
142
-
143
- def with_retry(self, max_retries: int) -> 'WorkflowStep':
147
+
148
+ def with_retry(self, max_retries: int) -> "WorkflowStep":
144
149
  """Configure retry behavior."""
145
150
  self.max_retries = max_retries
146
151
  return self
147
-
148
- def with_timeout(self, timeout_seconds: int) -> 'WorkflowStep':
152
+
153
+ def with_timeout(self, timeout_seconds: int) -> "WorkflowStep":
149
154
  """Configure timeout."""
150
155
  self.timeout_seconds = timeout_seconds
151
156
  return self
152
-
157
+
153
158
  def should_execute(self, context: WorkflowContext) -> bool:
154
159
  """Check if step should execute based on conditions."""
155
160
  return all(condition(context) for condition in self.conditions)
@@ -157,105 +162,110 @@ class WorkflowStep(ABC):
157
162
 
158
163
  class AgentStep(WorkflowStep):
159
164
  """Workflow step that executes an agent."""
160
-
165
+
161
166
  def __init__(self, step_id: str, agent: Agent, message: str, name: str = ""):
162
167
  super().__init__(step_id, name or f"Agent: {agent.name}")
163
168
  self.agent = agent
164
169
  self.message = message
165
-
170
+
166
171
  async def execute(self, context: WorkflowContext) -> StepResult:
167
172
  """Execute agent step."""
168
173
  start_time = time.time()
169
-
174
+
170
175
  try:
171
176
  # Create run state for agent
172
177
  from .types import RunState, create_run_id, create_trace_id
173
-
178
+
174
179
  run_state = RunState(
175
180
  run_id=create_run_id(f"{context.workflow_id}_{self.step_id}"),
176
181
  trace_id=create_trace_id(f"{context.workflow_id}"),
177
182
  messages=[Message(role=ContentRole.USER, content=self.message)],
178
183
  current_agent_name=self.agent.name,
179
184
  context=context.user_context,
180
- turn_count=0
185
+ turn_count=0,
181
186
  )
182
-
187
+
183
188
  # Get agent instructions
184
189
  instructions = self.agent.instructions(run_state)
185
-
190
+
186
191
  # Simulate agent execution (in real implementation, use JAF engine)
187
192
  await asyncio.sleep(0.1) # Simulate processing
188
-
193
+
189
194
  execution_time = (time.time() - start_time) * 1000
190
-
195
+
191
196
  return StepResult(
192
197
  step_id=self.step_id,
193
198
  status=StepStatus.COMPLETED,
194
199
  output=f"Agent {self.agent.name} processed: {self.message}",
195
200
  execution_time_ms=execution_time,
196
- metadata={'agent_name': self.agent.name, 'instructions': instructions}
201
+ metadata={"agent_name": self.agent.name, "instructions": instructions},
197
202
  )
198
-
203
+
199
204
  except Exception as e:
200
205
  execution_time = (time.time() - start_time) * 1000
201
206
  return StepResult(
202
207
  step_id=self.step_id,
203
208
  status=StepStatus.FAILED,
204
209
  error=str(e),
205
- execution_time_ms=execution_time
210
+ execution_time_ms=execution_time,
206
211
  )
207
212
 
208
213
 
209
214
  class ToolStep(WorkflowStep):
210
215
  """Workflow step that executes a tool."""
211
-
216
+
212
217
  def __init__(self, step_id: str, tool: Tool, args: Any, name: str = ""):
213
218
  super().__init__(step_id, name or f"Tool: {tool.schema.name}")
214
219
  self.tool = tool
215
220
  self.args = args
216
-
221
+
217
222
  async def execute(self, context: WorkflowContext) -> StepResult:
218
223
  """Execute tool step."""
219
224
  start_time = time.time()
220
-
225
+
221
226
  try:
222
227
  # Execute tool
223
228
  result = await self.tool.execute(self.args, context.user_context)
224
-
229
+
225
230
  execution_time = (time.time() - start_time) * 1000
226
-
231
+
227
232
  return StepResult(
228
233
  step_id=self.step_id,
229
234
  status=StepStatus.COMPLETED,
230
235
  output=result,
231
236
  execution_time_ms=execution_time,
232
- metadata={'tool_name': self.tool.schema.name}
237
+ metadata={"tool_name": self.tool.schema.name},
233
238
  )
234
-
239
+
235
240
  except Exception as e:
236
241
  execution_time = (time.time() - start_time) * 1000
237
242
  return StepResult(
238
243
  step_id=self.step_id,
239
244
  status=StepStatus.FAILED,
240
245
  error=str(e),
241
- execution_time_ms=execution_time
246
+ execution_time_ms=execution_time,
242
247
  )
243
248
 
244
249
 
245
250
  class ConditionalStep(WorkflowStep):
246
251
  """Workflow step that executes conditionally."""
247
-
248
- def __init__(self, step_id: str, condition: Callable[[WorkflowContext], bool],
249
- true_step: WorkflowStep, false_step: Optional[WorkflowStep] = None):
252
+
253
+ def __init__(
254
+ self,
255
+ step_id: str,
256
+ condition: Callable[[WorkflowContext], bool],
257
+ true_step: WorkflowStep,
258
+ false_step: Optional[WorkflowStep] = None,
259
+ ):
250
260
  super().__init__(step_id, "Conditional Step")
251
261
  self.condition = condition
252
262
  self.true_step = true_step
253
263
  self.false_step = false_step
254
-
264
+
255
265
  async def execute(self, context: WorkflowContext) -> StepResult:
256
266
  """Execute conditional step."""
257
267
  start_time = time.time()
258
-
268
+
259
269
  try:
260
270
  if self.condition(context):
261
271
  result = await self.true_step.execute(context)
@@ -267,9 +277,9 @@ class ConditionalStep(WorkflowStep):
267
277
  step_id=self.step_id,
268
278
  status=StepStatus.SKIPPED,
269
279
  output="Condition not met, no alternative step",
270
- execution_time_ms=execution_time
280
+ execution_time_ms=execution_time,
271
281
  )
272
-
282
+
273
283
  execution_time = (time.time() - start_time) * 1000
274
284
  return StepResult(
275
285
  step_id=self.step_id,
@@ -277,36 +287,37 @@ class ConditionalStep(WorkflowStep):
277
287
  output=result.output,
278
288
  error=result.error,
279
289
  execution_time_ms=execution_time,
280
- metadata={'delegated_to': result.step_id}
290
+ metadata={"delegated_to": result.step_id},
281
291
  )
282
-
292
+
283
293
  except Exception as e:
284
294
  execution_time = (time.time() - start_time) * 1000
285
295
  return StepResult(
286
296
  step_id=self.step_id,
287
297
  status=StepStatus.FAILED,
288
298
  error=str(e),
289
- execution_time_ms=execution_time
299
+ execution_time_ms=execution_time,
290
300
  )
291
301
 
292
302
 
293
303
  class ParallelStep(WorkflowStep):
294
304
  """Workflow step that executes multiple steps in parallel."""
295
-
296
- def __init__(self, step_id: str, steps: List[WorkflowStep],
297
- wait_for_all: bool = True, name: str = ""):
305
+
306
+ def __init__(
307
+ self, step_id: str, steps: List[WorkflowStep], wait_for_all: bool = True, name: str = ""
308
+ ):
298
309
  super().__init__(step_id, name or "Parallel Execution")
299
310
  self.steps = steps
300
311
  self.wait_for_all = wait_for_all
301
-
312
+
302
313
  async def execute(self, context: WorkflowContext) -> StepResult:
303
314
  """Execute parallel steps."""
304
315
  start_time = time.time()
305
-
316
+
306
317
  try:
307
318
  # Execute all steps in parallel
308
319
  tasks = [step.execute(context) for step in self.steps]
309
-
320
+
310
321
  if self.wait_for_all:
311
322
  results = await asyncio.gather(*tasks, return_exceptions=True)
312
323
  else:
@@ -316,22 +327,24 @@ class ParallelStep(WorkflowStep):
316
327
  # Cancel pending tasks
317
328
  for task in pending:
318
329
  task.cancel()
319
-
330
+
320
331
  execution_time = (time.time() - start_time) * 1000
321
-
332
+
322
333
  # Process results
323
334
  step_results = []
324
335
  for i, result in enumerate(results):
325
336
  if isinstance(result, Exception):
326
- step_results.append(StepResult(
327
- step_id=f"{self.step_id}_parallel_{i}",
328
- status=StepStatus.FAILED,
329
- error=str(result),
330
- execution_time_ms=0
331
- ))
337
+ step_results.append(
338
+ StepResult(
339
+ step_id=f"{self.step_id}_parallel_{i}",
340
+ status=StepStatus.FAILED,
341
+ error=str(result),
342
+ execution_time_ms=0,
343
+ )
344
+ )
332
345
  else:
333
346
  step_results.append(result)
334
-
347
+
335
348
  # Determine overall status
336
349
  if all(r.is_success for r in step_results):
337
350
  status = StepStatus.COMPLETED
@@ -339,57 +352,63 @@ class ParallelStep(WorkflowStep):
339
352
  status = StepStatus.COMPLETED
340
353
  else:
341
354
  status = StepStatus.FAILED
342
-
355
+
343
356
  return StepResult(
344
357
  step_id=self.step_id,
345
358
  status=status,
346
359
  output=step_results,
347
360
  execution_time_ms=execution_time,
348
- metadata={'parallel_results': len(step_results)}
361
+ metadata={"parallel_results": len(step_results)},
349
362
  )
350
-
363
+
351
364
  except Exception as e:
352
365
  execution_time = (time.time() - start_time) * 1000
353
366
  return StepResult(
354
367
  step_id=self.step_id,
355
368
  status=StepStatus.FAILED,
356
369
  error=str(e),
357
- execution_time_ms=execution_time
370
+ execution_time_ms=execution_time,
358
371
  )
359
372
 
360
373
 
361
374
  class LoopStep(WorkflowStep):
362
375
  """Workflow step that executes in a loop."""
363
-
364
- def __init__(self, step_id: str, step: WorkflowStep,
365
- condition: Callable[[WorkflowContext, int], bool],
366
- max_iterations: int = 10):
376
+
377
+ def __init__(
378
+ self,
379
+ step_id: str,
380
+ step: WorkflowStep,
381
+ condition: Callable[[WorkflowContext, int], bool],
382
+ max_iterations: int = 10,
383
+ ):
367
384
  super().__init__(step_id, "Loop Step")
368
385
  self.step = step
369
386
  self.condition = condition
370
387
  self.max_iterations = max_iterations
371
-
388
+
372
389
  async def execute(self, context: WorkflowContext) -> StepResult:
373
390
  """Execute loop step."""
374
391
  start_time = time.time()
375
392
  results = []
376
393
  iteration = 0
377
-
394
+
378
395
  try:
379
396
  while iteration < self.max_iterations and self.condition(context, iteration):
380
397
  result = await self.step.execute(context)
381
398
  results.append(result)
382
-
399
+
383
400
  # Update context with iteration results
384
- context = context.with_variable(f"loop_{self.step_id}_iteration_{iteration}", result.output)
385
-
401
+ context = context.with_variable(
402
+ f"loop_{self.step_id}_iteration_{iteration}", result.output
403
+ )
404
+
386
405
  if result.is_failure:
387
406
  break
388
-
407
+
389
408
  iteration += 1
390
-
409
+
391
410
  execution_time = (time.time() - start_time) * 1000
392
-
411
+
393
412
  # Determine overall status
394
413
  if results and all(r.is_success for r in results):
395
414
  status = StepStatus.COMPLETED
@@ -397,28 +416,28 @@ class LoopStep(WorkflowStep):
397
416
  status = StepStatus.SKIPPED
398
417
  else:
399
418
  status = StepStatus.FAILED
400
-
419
+
401
420
  return StepResult(
402
421
  step_id=self.step_id,
403
422
  status=status,
404
423
  output=results,
405
424
  execution_time_ms=execution_time,
406
- metadata={'iterations': iteration, 'results_count': len(results)}
425
+ metadata={"iterations": iteration, "results_count": len(results)},
407
426
  )
408
-
427
+
409
428
  except Exception as e:
410
429
  execution_time = (time.time() - start_time) * 1000
411
430
  return StepResult(
412
431
  step_id=self.step_id,
413
432
  status=StepStatus.FAILED,
414
433
  error=str(e),
415
- execution_time_ms=execution_time
434
+ execution_time_ms=execution_time,
416
435
  )
417
436
 
418
437
 
419
438
  class Workflow:
420
439
  """Main workflow orchestrator."""
421
-
440
+
422
441
  def __init__(self, workflow_id: str, name: str, description: str = ""):
423
442
  self.workflow_id = workflow_id
424
443
  self.name = name
@@ -428,36 +447,39 @@ class Workflow:
428
447
  self.on_step_complete: Optional[Callable[[StepResult, WorkflowContext], None]] = None
429
448
  self.on_workflow_complete: Optional[Callable[[WorkflowResult], None]] = None
430
449
  self.performance_monitor = PerformanceMonitor()
431
-
432
- def add_step(self, step: WorkflowStep) -> 'Workflow':
450
+
451
+ def add_step(self, step: WorkflowStep) -> "Workflow":
433
452
  """Add a step to the workflow."""
434
453
  self.steps.append(step)
435
454
  return self
436
-
437
- def add_error_handler(self, step_id: str,
438
- handler: Callable[[StepResult, WorkflowContext], WorkflowStep]) -> 'Workflow':
455
+
456
+ def add_error_handler(
457
+ self, step_id: str, handler: Callable[[StepResult, WorkflowContext], WorkflowStep]
458
+ ) -> "Workflow":
439
459
  """Add error handler for specific step."""
440
460
  self.error_handlers[step_id] = handler
441
461
  return self
442
-
443
- def on_step_completed(self, callback: Callable[[StepResult, WorkflowContext], None]) -> 'Workflow':
462
+
463
+ def on_step_completed(
464
+ self, callback: Callable[[StepResult, WorkflowContext], None]
465
+ ) -> "Workflow":
444
466
  """Set callback for step completion."""
445
467
  self.on_step_complete = callback
446
468
  return self
447
-
448
- def on_workflow_completed(self, callback: Callable[[WorkflowResult], None]) -> 'Workflow':
469
+
470
+ def on_workflow_completed(self, callback: Callable[[WorkflowResult], None]) -> "Workflow":
449
471
  """Set callback for workflow completion."""
450
472
  self.on_workflow_complete = callback
451
473
  return self
452
-
474
+
453
475
  async def execute(self, context: WorkflowContext) -> WorkflowResult:
454
476
  """Execute the complete workflow."""
455
477
  start_time = time.time()
456
478
  self.performance_monitor.start_monitoring()
457
-
479
+
458
480
  step_results: List[StepResult] = []
459
481
  current_context = context
460
-
482
+
461
483
  try:
462
484
  for step in self.steps:
463
485
  # Check if step should execute
@@ -466,186 +488,203 @@ class Workflow:
466
488
  step_id=step.step_id,
467
489
  status=StepStatus.SKIPPED,
468
490
  output="Conditions not met",
469
- execution_time_ms=0
491
+ execution_time_ms=0,
470
492
  )
471
493
  step_results.append(step_result)
472
494
  continue
473
-
495
+
474
496
  # Execute step with retry logic
475
497
  step_result = await self._execute_step_with_retry(step, current_context)
476
498
  step_results.append(step_result)
477
-
499
+
478
500
  # Handle step completion
479
501
  if self.on_step_complete:
480
502
  self.on_step_complete(step_result, current_context)
481
-
503
+
482
504
  # Update context with step result
483
505
  current_context = current_context.with_variable(
484
- f"step_{step.step_id}_result",
485
- step_result.output
506
+ f"step_{step.step_id}_result", step_result.output
486
507
  )
487
-
508
+
488
509
  # Handle errors
489
510
  if step_result.is_failure and step.step_id in self.error_handlers:
490
511
  error_handler = self.error_handlers[step.step_id]
491
512
  recovery_step = error_handler(step_result, current_context)
492
- recovery_result = await self._execute_step_with_retry(recovery_step, current_context)
513
+ recovery_result = await self._execute_step_with_retry(
514
+ recovery_step, current_context
515
+ )
493
516
  step_results.append(recovery_result)
494
-
517
+
495
518
  if recovery_result.is_failure:
496
519
  # Recovery failed, stop workflow
497
520
  break
498
521
  elif step_result.is_failure:
499
522
  # No error handler, stop workflow
500
523
  break
501
-
524
+
502
525
  # Calculate final status
503
526
  if all(r.is_success or r.status == StepStatus.SKIPPED for r in step_results):
504
527
  final_status = WorkflowStatus.COMPLETED
505
528
  else:
506
529
  final_status = WorkflowStatus.FAILED
507
-
530
+
508
531
  execution_time = (time.time() - start_time) * 1000
509
532
  performance_metrics = self.performance_monitor.stop_monitoring()
510
-
533
+
511
534
  # Get final output from last successful step
512
535
  final_output = None
513
536
  for result in reversed(step_results):
514
537
  if result.is_success and result.output is not None:
515
538
  final_output = result.output
516
539
  break
517
-
540
+
518
541
  workflow_result = WorkflowResult(
519
542
  workflow_id=self.workflow_id,
520
543
  status=final_status,
521
544
  steps=step_results,
522
545
  final_output=final_output,
523
546
  total_execution_time_ms=execution_time,
524
- context=current_context
547
+ context=current_context,
525
548
  )
526
-
549
+
527
550
  # Record analytics
528
- global_analytics_engine.record_system_metrics(performance_metrics, f"workflow_{self.workflow_id}")
529
-
551
+ global_analytics_engine.record_system_metrics(
552
+ performance_metrics, f"workflow_{self.workflow_id}"
553
+ )
554
+
530
555
  # Handle workflow completion
531
556
  if self.on_workflow_complete:
532
557
  self.on_workflow_complete(workflow_result)
533
-
558
+
534
559
  return workflow_result
535
-
560
+
536
561
  except Exception as e:
537
562
  execution_time = (time.time() - start_time) * 1000
538
-
563
+
539
564
  return WorkflowResult(
540
565
  workflow_id=self.workflow_id,
541
566
  status=WorkflowStatus.FAILED,
542
567
  steps=step_results,
543
568
  error=str(e),
544
569
  total_execution_time_ms=execution_time,
545
- context=current_context
570
+ context=current_context,
546
571
  )
547
-
548
- async def _execute_step_with_retry(self, step: WorkflowStep, context: WorkflowContext) -> StepResult:
572
+
573
+ async def _execute_step_with_retry(
574
+ self, step: WorkflowStep, context: WorkflowContext
575
+ ) -> StepResult:
549
576
  """Execute step with retry logic."""
550
577
  last_result = None
551
-
578
+
552
579
  for attempt in range(step.max_retries + 1):
553
580
  try:
554
581
  # Execute with timeout
555
- result = await asyncio.wait_for(
556
- step.execute(context),
557
- timeout=step.timeout_seconds
558
- )
559
-
582
+ result = await asyncio.wait_for(step.execute(context), timeout=step.timeout_seconds)
583
+
560
584
  if result.is_success:
561
585
  return result
562
-
586
+
563
587
  last_result = result
564
-
588
+
565
589
  if attempt < step.max_retries:
566
590
  # Wait before retry with exponential backoff
567
- wait_time = 2 ** attempt
591
+ wait_time = 2**attempt
568
592
  await asyncio.sleep(wait_time)
569
-
593
+
570
594
  except asyncio.TimeoutError:
571
595
  last_result = StepResult(
572
596
  step_id=step.step_id,
573
597
  status=StepStatus.FAILED,
574
598
  error=f"Step timed out after {step.timeout_seconds} seconds",
575
- execution_time_ms=step.timeout_seconds * 1000
599
+ execution_time_ms=step.timeout_seconds * 1000,
576
600
  )
577
601
  except Exception as e:
578
602
  last_result = StepResult(
579
603
  step_id=step.step_id,
580
604
  status=StepStatus.FAILED,
581
605
  error=str(e),
582
- execution_time_ms=0
606
+ execution_time_ms=0,
583
607
  )
584
-
608
+
585
609
  return last_result or StepResult(
586
610
  step_id=step.step_id,
587
611
  status=StepStatus.FAILED,
588
612
  error="Unknown error during execution",
589
- execution_time_ms=0
613
+ execution_time_ms=0,
590
614
  )
591
615
 
592
616
 
593
617
  class WorkflowBuilder:
594
618
  """Builder for creating workflows with fluent API."""
595
-
619
+
596
620
  def __init__(self, workflow_id: str, name: str):
597
621
  self.workflow = Workflow(workflow_id, name)
598
-
599
- def add_agent_step(self, step_id: str, agent: Agent, message: str) -> 'WorkflowBuilder':
622
+
623
+ def add_agent_step(self, step_id: str, agent: Agent, message: str) -> "WorkflowBuilder":
600
624
  """Add agent execution step."""
601
625
  step = AgentStep(step_id, agent, message)
602
626
  self.workflow.add_step(step)
603
627
  return self
604
-
605
- def add_tool_step(self, step_id: str, tool: Tool, args: Any) -> 'WorkflowBuilder':
628
+
629
+ def add_tool_step(self, step_id: str, tool: Tool, args: Any) -> "WorkflowBuilder":
606
630
  """Add tool execution step."""
607
631
  step = ToolStep(step_id, tool, args)
608
632
  self.workflow.add_step(step)
609
633
  return self
610
-
611
- def add_conditional_step(self, step_id: str, condition: Callable[[WorkflowContext], bool],
612
- true_step: WorkflowStep, false_step: Optional[WorkflowStep] = None) -> 'WorkflowBuilder':
634
+
635
+ def add_conditional_step(
636
+ self,
637
+ step_id: str,
638
+ condition: Callable[[WorkflowContext], bool],
639
+ true_step: WorkflowStep,
640
+ false_step: Optional[WorkflowStep] = None,
641
+ ) -> "WorkflowBuilder":
613
642
  """Add conditional execution step."""
614
643
  step = ConditionalStep(step_id, condition, true_step, false_step)
615
644
  self.workflow.add_step(step)
616
645
  return self
617
-
618
- def add_parallel_step(self, step_id: str, steps: List[WorkflowStep],
619
- wait_for_all: bool = True) -> 'WorkflowBuilder':
646
+
647
+ def add_parallel_step(
648
+ self, step_id: str, steps: List[WorkflowStep], wait_for_all: bool = True
649
+ ) -> "WorkflowBuilder":
620
650
  """Add parallel execution step."""
621
651
  step = ParallelStep(step_id, steps, wait_for_all)
622
652
  self.workflow.add_step(step)
623
653
  return self
624
-
625
- def add_loop_step(self, step_id: str, step: WorkflowStep,
626
- condition: Callable[[WorkflowContext, int], bool],
627
- max_iterations: int = 10) -> 'WorkflowBuilder':
654
+
655
+ def add_loop_step(
656
+ self,
657
+ step_id: str,
658
+ step: WorkflowStep,
659
+ condition: Callable[[WorkflowContext, int], bool],
660
+ max_iterations: int = 10,
661
+ ) -> "WorkflowBuilder":
628
662
  """Add loop execution step."""
629
663
  loop_step = LoopStep(step_id, step, condition, max_iterations)
630
664
  self.workflow.add_step(loop_step)
631
665
  return self
632
-
633
- def with_error_handler(self, step_id: str,
634
- handler: Callable[[StepResult, WorkflowContext], WorkflowStep]) -> 'WorkflowBuilder':
666
+
667
+ def with_error_handler(
668
+ self, step_id: str, handler: Callable[[StepResult, WorkflowContext], WorkflowStep]
669
+ ) -> "WorkflowBuilder":
635
670
  """Add error handler."""
636
671
  self.workflow.add_error_handler(step_id, handler)
637
672
  return self
638
-
639
- def with_step_callback(self, callback: Callable[[StepResult, WorkflowContext], None]) -> 'WorkflowBuilder':
673
+
674
+ def with_step_callback(
675
+ self, callback: Callable[[StepResult, WorkflowContext], None]
676
+ ) -> "WorkflowBuilder":
640
677
  """Add step completion callback."""
641
678
  self.workflow.on_step_completed(callback)
642
679
  return self
643
-
644
- def with_completion_callback(self, callback: Callable[[WorkflowResult], None]) -> 'WorkflowBuilder':
680
+
681
+ def with_completion_callback(
682
+ self, callback: Callable[[WorkflowResult], None]
683
+ ) -> "WorkflowBuilder":
645
684
  """Add workflow completion callback."""
646
685
  self.workflow.on_workflow_completed(callback)
647
686
  return self
648
-
687
+
649
688
  def build(self) -> Workflow:
650
689
  """Build the workflow."""
651
690
  return self.workflow
@@ -665,8 +704,9 @@ def create_sequential_workflow(workflow_id: str, name: str, steps: List[Workflow
665
704
  return workflow
666
705
 
667
706
 
668
- def create_parallel_workflow(workflow_id: str, name: str, steps: List[WorkflowStep],
669
- wait_for_all: bool = True) -> Workflow:
707
+ def create_parallel_workflow(
708
+ workflow_id: str, name: str, steps: List[WorkflowStep], wait_for_all: bool = True
709
+ ) -> Workflow:
670
710
  """Create a workflow that executes all steps in parallel."""
671
711
  workflow = Workflow(workflow_id, name)
672
712
  parallel_step = ParallelStep("parallel_execution", steps, wait_for_all)
@@ -674,32 +714,33 @@ def create_parallel_workflow(workflow_id: str, name: str, steps: List[WorkflowSt
674
714
  return workflow
675
715
 
676
716
 
677
- async def execute_workflow_stream(workflow: Workflow, context: WorkflowContext) -> AsyncIterator[StepResult]:
717
+ async def execute_workflow_stream(
718
+ workflow: Workflow, context: WorkflowContext
719
+ ) -> AsyncIterator[StepResult]:
678
720
  """Execute workflow and stream step results as they complete."""
679
721
  step_results: List[StepResult] = []
680
722
  current_context = context
681
-
723
+
682
724
  for step in workflow.steps:
683
725
  if not step.should_execute(current_context):
684
726
  step_result = StepResult(
685
727
  step_id=step.step_id,
686
728
  status=StepStatus.SKIPPED,
687
729
  output="Conditions not met",
688
- execution_time_ms=0
730
+ execution_time_ms=0,
689
731
  )
690
732
  step_results.append(step_result)
691
733
  yield step_result
692
734
  continue
693
-
735
+
694
736
  step_result = await workflow._execute_step_with_retry(step, current_context)
695
737
  step_results.append(step_result)
696
738
  yield step_result
697
-
739
+
698
740
  # Update context
699
741
  current_context = current_context.with_variable(
700
- f"step_{step.step_id}_result",
701
- step_result.output
742
+ f"step_{step.step_id}_result", step_result.output
702
743
  )
703
-
744
+
704
745
  if step_result.is_failure:
705
746
  break