agno 2.4.6__py3-none-any.whl → 2.4.8__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.
- agno/agent/agent.py +5 -1
- agno/db/base.py +2 -0
- agno/db/postgres/postgres.py +5 -5
- agno/db/singlestore/singlestore.py +4 -5
- agno/db/sqlite/sqlite.py +4 -4
- agno/knowledge/embedder/aws_bedrock.py +325 -106
- agno/knowledge/knowledge.py +83 -1853
- agno/knowledge/loaders/__init__.py +29 -0
- agno/knowledge/loaders/azure_blob.py +423 -0
- agno/knowledge/loaders/base.py +187 -0
- agno/knowledge/loaders/gcs.py +267 -0
- agno/knowledge/loaders/github.py +415 -0
- agno/knowledge/loaders/s3.py +281 -0
- agno/knowledge/loaders/sharepoint.py +439 -0
- agno/knowledge/reader/website_reader.py +2 -2
- agno/knowledge/remote_knowledge.py +151 -0
- agno/knowledge/reranker/aws_bedrock.py +299 -0
- agno/learn/machine.py +5 -6
- agno/learn/stores/session_context.py +10 -2
- agno/models/azure/openai_chat.py +6 -11
- agno/models/neosantara/__init__.py +5 -0
- agno/models/neosantara/neosantara.py +42 -0
- agno/models/utils.py +5 -0
- agno/os/app.py +4 -1
- agno/os/interfaces/agui/router.py +1 -1
- agno/os/routers/components/components.py +2 -0
- agno/os/routers/knowledge/knowledge.py +0 -1
- agno/os/routers/registry/registry.py +340 -192
- agno/os/routers/workflows/router.py +7 -1
- agno/os/schema.py +104 -0
- agno/registry/registry.py +4 -0
- agno/run/workflow.py +3 -0
- agno/session/workflow.py +1 -1
- agno/skills/utils.py +100 -2
- agno/team/team.py +6 -3
- agno/tools/mcp/mcp.py +26 -1
- agno/vectordb/lancedb/lance_db.py +22 -7
- agno/workflow/__init__.py +4 -0
- agno/workflow/cel.py +299 -0
- agno/workflow/condition.py +280 -58
- agno/workflow/loop.py +177 -46
- agno/workflow/parallel.py +75 -4
- agno/workflow/router.py +260 -44
- agno/workflow/step.py +14 -7
- agno/workflow/steps.py +43 -0
- agno/workflow/workflow.py +104 -46
- {agno-2.4.6.dist-info → agno-2.4.8.dist-info}/METADATA +25 -37
- {agno-2.4.6.dist-info → agno-2.4.8.dist-info}/RECORD +51 -39
- {agno-2.4.6.dist-info → agno-2.4.8.dist-info}/WHEEL +0 -0
- {agno-2.4.6.dist-info → agno-2.4.8.dist-info}/licenses/LICENSE +0 -0
- {agno-2.4.6.dist-info → agno-2.4.8.dist-info}/top_level.txt +0 -0
agno/workflow/condition.py
CHANGED
|
@@ -3,6 +3,7 @@ from dataclasses import dataclass
|
|
|
3
3
|
from typing import Any, AsyncIterator, Awaitable, Callable, Dict, Iterator, List, Optional, Union
|
|
4
4
|
from uuid import uuid4
|
|
5
5
|
|
|
6
|
+
from agno.registry import Registry
|
|
6
7
|
from agno.run.agent import RunOutputEvent
|
|
7
8
|
from agno.run.base import RunContext
|
|
8
9
|
from agno.run.team import TeamRunOutputEvent
|
|
@@ -14,9 +15,14 @@ from agno.run.workflow import (
|
|
|
14
15
|
)
|
|
15
16
|
from agno.session.workflow import WorkflowSession
|
|
16
17
|
from agno.utils.log import log_debug, logger
|
|
18
|
+
from agno.workflow.cel import CEL_AVAILABLE, evaluate_cel_condition_evaluator, is_cel_expression
|
|
17
19
|
from agno.workflow.step import Step
|
|
18
20
|
from agno.workflow.types import StepInput, StepOutput, StepType
|
|
19
21
|
|
|
22
|
+
# Constants for condition branch identifiers
|
|
23
|
+
CONDITION_BRANCH_IF = "if"
|
|
24
|
+
CONDITION_BRANCH_ELSE = "else"
|
|
25
|
+
|
|
20
26
|
WorkflowSteps = List[
|
|
21
27
|
Union[
|
|
22
28
|
Callable[
|
|
@@ -34,19 +40,130 @@ WorkflowSteps = List[
|
|
|
34
40
|
|
|
35
41
|
@dataclass
|
|
36
42
|
class Condition:
|
|
37
|
-
"""A condition that executes a step (or list of steps) if the condition is met
|
|
43
|
+
"""A condition that executes a step (or list of steps) if the condition is met.
|
|
44
|
+
|
|
45
|
+
If the condition evaluates to True, the `steps` are executed.
|
|
46
|
+
If the condition evaluates to False and `else_steps` is provided (and not empty),
|
|
47
|
+
the `else_steps` are executed instead.
|
|
48
|
+
|
|
49
|
+
The evaluator can be:
|
|
50
|
+
- A callable function that returns bool
|
|
51
|
+
- A boolean literal (True/False)
|
|
52
|
+
- A CEL (Common Expression Language) expression string
|
|
53
|
+
|
|
54
|
+
CEL expressions have access to these variables:
|
|
55
|
+
- input: The workflow input as a string
|
|
56
|
+
- previous_step_content: Content from the previous step
|
|
57
|
+
- previous_step_outputs: Map of step name to content string from all previous steps
|
|
58
|
+
- additional_data: Map of additional data passed to the workflow
|
|
59
|
+
- session_state: Map of session state values
|
|
60
|
+
|
|
61
|
+
Example CEL expressions:
|
|
62
|
+
- 'input.contains("urgent")'
|
|
63
|
+
- 'session_state.retry_count < 3'
|
|
64
|
+
- 'additional_data.priority > 5'
|
|
65
|
+
- 'previous_step_outputs.research.contains("error")'
|
|
66
|
+
"""
|
|
38
67
|
|
|
39
68
|
# Evaluator should only return boolean
|
|
69
|
+
# Can be a callable, a bool, or a CEL expression string
|
|
40
70
|
evaluator: Union[
|
|
41
71
|
Callable[[StepInput], bool],
|
|
42
72
|
Callable[[StepInput], Awaitable[bool]],
|
|
43
73
|
bool,
|
|
74
|
+
str, # CEL expression
|
|
44
75
|
]
|
|
45
76
|
steps: WorkflowSteps
|
|
46
77
|
|
|
78
|
+
# Steps to execute when condition is False (optional)
|
|
79
|
+
else_steps: Optional[WorkflowSteps] = None
|
|
80
|
+
|
|
47
81
|
name: Optional[str] = None
|
|
48
82
|
description: Optional[str] = None
|
|
49
83
|
|
|
84
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
85
|
+
result: Dict[str, Any] = {
|
|
86
|
+
"type": "Condition",
|
|
87
|
+
"name": self.name,
|
|
88
|
+
"description": self.description,
|
|
89
|
+
"steps": [step.to_dict() for step in self.steps if hasattr(step, "to_dict")],
|
|
90
|
+
"else_steps": [step.to_dict() for step in (self.else_steps or []) if hasattr(step, "to_dict")],
|
|
91
|
+
}
|
|
92
|
+
if callable(self.evaluator):
|
|
93
|
+
result["evaluator"] = self.evaluator.__name__
|
|
94
|
+
result["evaluator_type"] = "function"
|
|
95
|
+
elif isinstance(self.evaluator, bool):
|
|
96
|
+
result["evaluator"] = self.evaluator
|
|
97
|
+
result["evaluator_type"] = "bool"
|
|
98
|
+
elif isinstance(self.evaluator, str):
|
|
99
|
+
# CEL expression string
|
|
100
|
+
result["evaluator"] = self.evaluator
|
|
101
|
+
result["evaluator_type"] = "cel"
|
|
102
|
+
else:
|
|
103
|
+
raise ValueError(f"Invalid evaluator type: {type(self.evaluator).__name__}")
|
|
104
|
+
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def from_dict(
|
|
109
|
+
cls,
|
|
110
|
+
data: Dict[str, Any],
|
|
111
|
+
registry: Optional["Registry"] = None,
|
|
112
|
+
db: Optional[Any] = None,
|
|
113
|
+
links: Optional[List[Dict[str, Any]]] = None,
|
|
114
|
+
) -> "Condition":
|
|
115
|
+
from agno.workflow.loop import Loop
|
|
116
|
+
from agno.workflow.parallel import Parallel
|
|
117
|
+
from agno.workflow.router import Router
|
|
118
|
+
from agno.workflow.steps import Steps
|
|
119
|
+
|
|
120
|
+
def deserialize_step(step_data: Dict[str, Any]) -> Any:
|
|
121
|
+
step_type = step_data.get("type", "Step")
|
|
122
|
+
if step_type == "Loop":
|
|
123
|
+
return Loop.from_dict(step_data, registry=registry, db=db, links=links)
|
|
124
|
+
elif step_type == "Parallel":
|
|
125
|
+
return Parallel.from_dict(step_data, registry=registry, db=db, links=links)
|
|
126
|
+
elif step_type == "Steps":
|
|
127
|
+
return Steps.from_dict(step_data, registry=registry, db=db, links=links)
|
|
128
|
+
elif step_type == "Condition":
|
|
129
|
+
return cls.from_dict(step_data, registry=registry, db=db, links=links)
|
|
130
|
+
elif step_type == "Router":
|
|
131
|
+
return Router.from_dict(step_data, registry=registry, db=db, links=links)
|
|
132
|
+
else:
|
|
133
|
+
return Step.from_dict(step_data, registry=registry, db=db, links=links)
|
|
134
|
+
|
|
135
|
+
evaluator_data = data.get("evaluator", True)
|
|
136
|
+
evaluator_type = data.get("evaluator_type")
|
|
137
|
+
evaluator: Union[Callable[[StepInput], bool], Callable[[StepInput], Awaitable[bool]], bool, str]
|
|
138
|
+
|
|
139
|
+
if isinstance(evaluator_data, bool):
|
|
140
|
+
evaluator = evaluator_data
|
|
141
|
+
elif isinstance(evaluator_data, str):
|
|
142
|
+
# Determine if this is a CEL expression or a function name
|
|
143
|
+
# Use evaluator_type if provided, otherwise detect
|
|
144
|
+
if evaluator_type == "cel" or (evaluator_type is None and is_cel_expression(evaluator_data)):
|
|
145
|
+
# CEL expression - use as-is
|
|
146
|
+
evaluator = evaluator_data
|
|
147
|
+
else:
|
|
148
|
+
# Function name - look up in registry
|
|
149
|
+
if registry:
|
|
150
|
+
func = registry.get_function(evaluator_data)
|
|
151
|
+
if func is None:
|
|
152
|
+
raise ValueError(f"Evaluator function '{evaluator_data}' not found in registry")
|
|
153
|
+
evaluator = func
|
|
154
|
+
else:
|
|
155
|
+
raise ValueError(f"Registry required to deserialize evaluator function '{evaluator_data}'")
|
|
156
|
+
else:
|
|
157
|
+
raise ValueError(f"Invalid evaluator type in data: {type(evaluator_data).__name__}")
|
|
158
|
+
|
|
159
|
+
return cls(
|
|
160
|
+
evaluator=evaluator,
|
|
161
|
+
steps=[deserialize_step(step) for step in data.get("steps", [])],
|
|
162
|
+
else_steps=[deserialize_step(step) for step in data.get("else_steps", [])],
|
|
163
|
+
name=data.get("name"),
|
|
164
|
+
description=data.get("description"),
|
|
165
|
+
)
|
|
166
|
+
|
|
50
167
|
def _prepare_steps(self):
|
|
51
168
|
"""Prepare the steps for execution - mirrors workflow logic"""
|
|
52
169
|
from agno.agent.agent import Agent
|
|
@@ -57,20 +174,27 @@ class Condition:
|
|
|
57
174
|
from agno.workflow.step import Step
|
|
58
175
|
from agno.workflow.steps import Steps
|
|
59
176
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
177
|
+
def prepare_step_list(steps: WorkflowSteps) -> WorkflowSteps:
|
|
178
|
+
"""Helper to prepare a list of steps."""
|
|
179
|
+
prepared: WorkflowSteps = []
|
|
180
|
+
for step in steps:
|
|
181
|
+
if callable(step) and hasattr(step, "__name__"):
|
|
182
|
+
prepared.append(Step(name=step.__name__, description="User-defined callable step", executor=step))
|
|
183
|
+
elif isinstance(step, Agent):
|
|
184
|
+
prepared.append(Step(name=step.name, description=step.description, agent=step))
|
|
185
|
+
elif isinstance(step, Team):
|
|
186
|
+
prepared.append(Step(name=step.name, description=step.description, team=step))
|
|
187
|
+
elif isinstance(step, (Step, Steps, Loop, Parallel, Condition, Router)):
|
|
188
|
+
prepared.append(step)
|
|
189
|
+
else:
|
|
190
|
+
raise ValueError(f"Invalid step type: {type(step).__name__}")
|
|
191
|
+
return prepared
|
|
192
|
+
|
|
193
|
+
self.steps = prepare_step_list(self.steps)
|
|
72
194
|
|
|
73
|
-
|
|
195
|
+
# Also prepare else_steps if provided and not empty
|
|
196
|
+
if self.else_steps and len(self.else_steps) > 0:
|
|
197
|
+
self.else_steps = prepare_step_list(self.else_steps)
|
|
74
198
|
|
|
75
199
|
def _update_step_input_from_outputs(
|
|
76
200
|
self,
|
|
@@ -113,10 +237,29 @@ class Condition:
|
|
|
113
237
|
)
|
|
114
238
|
|
|
115
239
|
def _evaluate_condition(self, step_input: StepInput, session_state: Optional[Dict[str, Any]] = None) -> bool:
|
|
116
|
-
"""Evaluate the condition and return boolean result
|
|
240
|
+
"""Evaluate the condition and return boolean result.
|
|
241
|
+
|
|
242
|
+
Supports:
|
|
243
|
+
- Boolean literals (True/False)
|
|
244
|
+
- Callable functions
|
|
245
|
+
- CEL expression strings
|
|
246
|
+
"""
|
|
117
247
|
if isinstance(self.evaluator, bool):
|
|
118
248
|
return self.evaluator
|
|
119
249
|
|
|
250
|
+
if isinstance(self.evaluator, str):
|
|
251
|
+
# CEL expression
|
|
252
|
+
if not CEL_AVAILABLE:
|
|
253
|
+
logger.error(
|
|
254
|
+
"CEL expression used but cel-python is not installed. Install with: pip install cel-python"
|
|
255
|
+
)
|
|
256
|
+
return False
|
|
257
|
+
try:
|
|
258
|
+
return evaluate_cel_condition_evaluator(self.evaluator, step_input, session_state)
|
|
259
|
+
except Exception as e:
|
|
260
|
+
logger.error(f"CEL expression evaluation failed: {e}")
|
|
261
|
+
return False
|
|
262
|
+
|
|
120
263
|
if callable(self.evaluator):
|
|
121
264
|
if session_state is not None and self._evaluator_has_session_state_param():
|
|
122
265
|
result = self.evaluator(step_input, session_state=session_state) # type: ignore[call-arg]
|
|
@@ -132,10 +275,29 @@ class Condition:
|
|
|
132
275
|
return False
|
|
133
276
|
|
|
134
277
|
async def _aevaluate_condition(self, step_input: StepInput, session_state: Optional[Dict[str, Any]] = None) -> bool:
|
|
135
|
-
"""Async version of condition evaluation
|
|
278
|
+
"""Async version of condition evaluation.
|
|
279
|
+
|
|
280
|
+
Supports:
|
|
281
|
+
- Boolean literals (True/False)
|
|
282
|
+
- Callable functions (sync and async)
|
|
283
|
+
- CEL expression strings
|
|
284
|
+
"""
|
|
136
285
|
if isinstance(self.evaluator, bool):
|
|
137
286
|
return self.evaluator
|
|
138
287
|
|
|
288
|
+
if isinstance(self.evaluator, str):
|
|
289
|
+
# CEL expression - CEL evaluation is synchronous
|
|
290
|
+
if not CEL_AVAILABLE:
|
|
291
|
+
logger.error(
|
|
292
|
+
"CEL expression used but cel-python is not installed. Install with: pip install cel-python"
|
|
293
|
+
)
|
|
294
|
+
return False
|
|
295
|
+
try:
|
|
296
|
+
return evaluate_cel_condition_evaluator(self.evaluator, step_input, session_state)
|
|
297
|
+
except Exception as e:
|
|
298
|
+
logger.error(f"CEL expression evaluation failed: {e}")
|
|
299
|
+
return False
|
|
300
|
+
|
|
139
301
|
if callable(self.evaluator):
|
|
140
302
|
has_session_state = session_state is not None and self._evaluator_has_session_state_param()
|
|
141
303
|
|
|
@@ -169,6 +331,10 @@ class Condition:
|
|
|
169
331
|
except Exception:
|
|
170
332
|
return False
|
|
171
333
|
|
|
334
|
+
def _has_else_steps(self) -> bool:
|
|
335
|
+
"""Check if else_steps is provided and not empty."""
|
|
336
|
+
return self.else_steps is not None and len(self.else_steps) > 0
|
|
337
|
+
|
|
172
338
|
def execute(
|
|
173
339
|
self,
|
|
174
340
|
step_input: StepInput,
|
|
@@ -183,7 +349,12 @@ class Condition:
|
|
|
183
349
|
num_history_runs: int = 3,
|
|
184
350
|
background_tasks: Optional[Any] = None,
|
|
185
351
|
) -> StepOutput:
|
|
186
|
-
"""Execute the condition and its steps with sequential chaining
|
|
352
|
+
"""Execute the condition and its steps with sequential chaining.
|
|
353
|
+
|
|
354
|
+
If condition is True, executes `steps`.
|
|
355
|
+
If condition is False and `else_steps` is provided (and not empty), executes `else_steps`.
|
|
356
|
+
If condition is False and no `else_steps`, returns a "not met" message.
|
|
357
|
+
"""
|
|
187
358
|
log_debug(f"Condition Start: {self.name}", center=True, symbol="-")
|
|
188
359
|
|
|
189
360
|
conditional_step_id = str(uuid4())
|
|
@@ -198,7 +369,17 @@ class Condition:
|
|
|
198
369
|
|
|
199
370
|
log_debug(f"Condition {self.name} evaluated to: {condition_result}")
|
|
200
371
|
|
|
201
|
-
|
|
372
|
+
# Determine which steps to execute
|
|
373
|
+
if condition_result:
|
|
374
|
+
steps_to_execute = self.steps
|
|
375
|
+
branch = CONDITION_BRANCH_IF
|
|
376
|
+
log_debug(f"Condition {self.name} met, executing {len(steps_to_execute)} steps (if branch)")
|
|
377
|
+
elif self._has_else_steps():
|
|
378
|
+
steps_to_execute = self.else_steps # type: ignore[assignment]
|
|
379
|
+
branch = CONDITION_BRANCH_ELSE
|
|
380
|
+
log_debug(f"Condition {self.name} not met, executing {len(steps_to_execute)} else_steps (else branch)")
|
|
381
|
+
else:
|
|
382
|
+
# No else_steps provided, return "not met" message
|
|
202
383
|
log_debug(f"Condition {self.name} not met, skipping {len(self.steps)} steps")
|
|
203
384
|
return StepOutput(
|
|
204
385
|
step_name=self.name,
|
|
@@ -208,12 +389,11 @@ class Condition:
|
|
|
208
389
|
success=True,
|
|
209
390
|
)
|
|
210
391
|
|
|
211
|
-
log_debug(f"Condition {self.name} met, executing {len(self.steps)} steps")
|
|
212
392
|
all_results: List[StepOutput] = []
|
|
213
393
|
current_step_input = step_input
|
|
214
|
-
condition_step_outputs = {}
|
|
394
|
+
condition_step_outputs: Dict[str, StepOutput] = {}
|
|
215
395
|
|
|
216
|
-
for i, step in enumerate(
|
|
396
|
+
for i, step in enumerate(steps_to_execute):
|
|
217
397
|
try:
|
|
218
398
|
step_output = step.execute( # type: ignore[union-attr]
|
|
219
399
|
current_step_input,
|
|
@@ -234,7 +414,7 @@ class Condition:
|
|
|
234
414
|
all_results.extend(step_output)
|
|
235
415
|
if step_output:
|
|
236
416
|
step_name = getattr(step, "name", f"step_{i}")
|
|
237
|
-
log_debug(f"Executing condition step {i + 1}/{len(
|
|
417
|
+
log_debug(f"Executing condition step {i + 1}/{len(steps_to_execute)}: {step_name}")
|
|
238
418
|
|
|
239
419
|
condition_step_outputs[step_name] = step_output[-1]
|
|
240
420
|
|
|
@@ -269,13 +449,13 @@ class Condition:
|
|
|
269
449
|
all_results.append(error_output)
|
|
270
450
|
break
|
|
271
451
|
|
|
272
|
-
log_debug(f"Condition End: {self.name} ({len(all_results)} results)", center=True, symbol="-")
|
|
452
|
+
log_debug(f"Condition End: {self.name} ({len(all_results)} results, {branch} branch)", center=True, symbol="-")
|
|
273
453
|
|
|
274
454
|
return StepOutput(
|
|
275
455
|
step_name=self.name,
|
|
276
456
|
step_id=conditional_step_id,
|
|
277
457
|
step_type=StepType.CONDITION,
|
|
278
|
-
content=f"Condition {self.name} completed with {len(all_results)} results",
|
|
458
|
+
content=f"Condition {self.name} completed with {len(all_results)} results ({branch} branch)",
|
|
279
459
|
success=all(result.success for result in all_results) if all_results else True,
|
|
280
460
|
error=None,
|
|
281
461
|
stop=any(result.stop for result in all_results) if all_results else False,
|
|
@@ -300,7 +480,12 @@ class Condition:
|
|
|
300
480
|
num_history_runs: int = 3,
|
|
301
481
|
background_tasks: Optional[Any] = None,
|
|
302
482
|
) -> Iterator[Union[WorkflowRunOutputEvent, StepOutput]]:
|
|
303
|
-
"""Execute the condition with streaming support
|
|
483
|
+
"""Execute the condition with streaming support.
|
|
484
|
+
|
|
485
|
+
If condition is True, executes `steps`.
|
|
486
|
+
If condition is False and `else_steps` is provided (and not empty), executes `else_steps`.
|
|
487
|
+
If condition is False and no `else_steps`, yields completed event and returns.
|
|
488
|
+
"""
|
|
304
489
|
log_debug(f"Condition Start: {self.name}", center=True, symbol="-")
|
|
305
490
|
|
|
306
491
|
conditional_step_id = str(uuid4())
|
|
@@ -328,9 +513,18 @@ class Condition:
|
|
|
328
513
|
parent_step_id=parent_step_id,
|
|
329
514
|
)
|
|
330
515
|
|
|
331
|
-
|
|
516
|
+
# Determine which steps to execute
|
|
517
|
+
if condition_result:
|
|
518
|
+
steps_to_execute = self.steps
|
|
519
|
+
branch = CONDITION_BRANCH_IF
|
|
520
|
+
log_debug(f"Condition {self.name} met, executing {len(steps_to_execute)} steps (if branch)")
|
|
521
|
+
elif self._has_else_steps():
|
|
522
|
+
steps_to_execute = self.else_steps # type: ignore[assignment]
|
|
523
|
+
branch = CONDITION_BRANCH_ELSE
|
|
524
|
+
log_debug(f"Condition {self.name} not met, executing {len(steps_to_execute)} else_steps (else branch)")
|
|
525
|
+
else:
|
|
526
|
+
# No else_steps provided, yield completed event and return
|
|
332
527
|
if stream_events and workflow_run_response:
|
|
333
|
-
# Yield condition completed event for empty case
|
|
334
528
|
yield ConditionExecutionCompletedEvent(
|
|
335
529
|
run_id=workflow_run_response.run_id or "",
|
|
336
530
|
workflow_name=workflow_run_response.workflow_name or "",
|
|
@@ -340,20 +534,20 @@ class Condition:
|
|
|
340
534
|
step_index=step_index,
|
|
341
535
|
condition_result=False,
|
|
342
536
|
executed_steps=0,
|
|
537
|
+
branch=None,
|
|
343
538
|
step_results=[],
|
|
344
539
|
step_id=conditional_step_id,
|
|
345
540
|
parent_step_id=parent_step_id,
|
|
346
541
|
)
|
|
347
542
|
return
|
|
348
543
|
|
|
349
|
-
|
|
350
|
-
all_results = []
|
|
544
|
+
all_results: List[StepOutput] = []
|
|
351
545
|
current_step_input = step_input
|
|
352
|
-
condition_step_outputs = {}
|
|
546
|
+
condition_step_outputs: Dict[str, StepOutput] = {}
|
|
353
547
|
|
|
354
|
-
for i, step in enumerate(
|
|
548
|
+
for i, step in enumerate(steps_to_execute):
|
|
355
549
|
try:
|
|
356
|
-
step_outputs_for_step = []
|
|
550
|
+
step_outputs_for_step: List[StepOutput] = []
|
|
357
551
|
|
|
358
552
|
# Create child index for each step within condition
|
|
359
553
|
if step_index is None or isinstance(step_index, int):
|
|
@@ -426,7 +620,7 @@ class Condition:
|
|
|
426
620
|
all_results.append(error_output)
|
|
427
621
|
break
|
|
428
622
|
|
|
429
|
-
log_debug(f"Condition End: {self.name} ({len(all_results)} results)", center=True, symbol="-")
|
|
623
|
+
log_debug(f"Condition End: {self.name} ({len(all_results)} results, {branch} branch)", center=True, symbol="-")
|
|
430
624
|
if stream_events and workflow_run_response:
|
|
431
625
|
# Yield condition completed event
|
|
432
626
|
yield ConditionExecutionCompletedEvent(
|
|
@@ -436,8 +630,9 @@ class Condition:
|
|
|
436
630
|
session_id=workflow_run_response.session_id or "",
|
|
437
631
|
step_name=self.name,
|
|
438
632
|
step_index=step_index,
|
|
439
|
-
condition_result=
|
|
440
|
-
executed_steps=len(
|
|
633
|
+
condition_result=condition_result,
|
|
634
|
+
executed_steps=len(steps_to_execute),
|
|
635
|
+
branch=branch,
|
|
441
636
|
step_results=all_results,
|
|
442
637
|
step_id=conditional_step_id,
|
|
443
638
|
parent_step_id=parent_step_id,
|
|
@@ -447,7 +642,7 @@ class Condition:
|
|
|
447
642
|
step_name=self.name,
|
|
448
643
|
step_id=conditional_step_id,
|
|
449
644
|
step_type=StepType.CONDITION,
|
|
450
|
-
content=f"Condition {self.name} completed with {len(all_results)} results",
|
|
645
|
+
content=f"Condition {self.name} completed with {len(all_results)} results ({branch} branch)",
|
|
451
646
|
success=all(result.success for result in all_results) if all_results else True,
|
|
452
647
|
stop=any(result.stop for result in all_results) if all_results else False,
|
|
453
648
|
steps=all_results,
|
|
@@ -467,7 +662,12 @@ class Condition:
|
|
|
467
662
|
num_history_runs: int = 3,
|
|
468
663
|
background_tasks: Optional[Any] = None,
|
|
469
664
|
) -> StepOutput:
|
|
470
|
-
"""Async execute the condition and its steps with sequential chaining
|
|
665
|
+
"""Async execute the condition and its steps with sequential chaining.
|
|
666
|
+
|
|
667
|
+
If condition is True, executes `steps`.
|
|
668
|
+
If condition is False and `else_steps` is provided (and not empty), executes `else_steps`.
|
|
669
|
+
If condition is False and no `else_steps`, returns a "not met" message.
|
|
670
|
+
"""
|
|
471
671
|
log_debug(f"Condition Start: {self.name}", center=True, symbol="-")
|
|
472
672
|
|
|
473
673
|
conditional_step_id = str(uuid4())
|
|
@@ -481,24 +681,32 @@ class Condition:
|
|
|
481
681
|
condition_result = await self._aevaluate_condition(step_input, session_state=session_state)
|
|
482
682
|
log_debug(f"Condition {self.name} evaluated to: {condition_result}")
|
|
483
683
|
|
|
484
|
-
|
|
684
|
+
# Determine which steps to execute
|
|
685
|
+
if condition_result:
|
|
686
|
+
steps_to_execute = self.steps
|
|
687
|
+
branch = CONDITION_BRANCH_IF
|
|
688
|
+
log_debug(f"Condition {self.name} met, executing {len(steps_to_execute)} steps (if branch)")
|
|
689
|
+
elif self._has_else_steps():
|
|
690
|
+
steps_to_execute = self.else_steps # type: ignore[assignment]
|
|
691
|
+
branch = CONDITION_BRANCH_ELSE
|
|
692
|
+
log_debug(f"Condition {self.name} not met, executing {len(steps_to_execute)} else_steps (else branch)")
|
|
693
|
+
else:
|
|
694
|
+
# No else_steps provided, return "not met" message
|
|
485
695
|
log_debug(f"Condition {self.name} not met, skipping {len(self.steps)} steps")
|
|
486
696
|
return StepOutput(
|
|
487
697
|
step_name=self.name,
|
|
488
|
-
step_id=
|
|
698
|
+
step_id=conditional_step_id,
|
|
489
699
|
step_type=StepType.CONDITION,
|
|
490
700
|
content=f"Condition {self.name} not met - skipped {len(self.steps)} steps",
|
|
491
701
|
success=True,
|
|
492
702
|
)
|
|
493
703
|
|
|
494
|
-
log_debug(f"Condition {self.name} met, executing {len(self.steps)} steps")
|
|
495
|
-
|
|
496
704
|
# Chain steps sequentially like Loop does
|
|
497
705
|
all_results: List[StepOutput] = []
|
|
498
706
|
current_step_input = step_input
|
|
499
|
-
condition_step_outputs = {}
|
|
707
|
+
condition_step_outputs: Dict[str, StepOutput] = {}
|
|
500
708
|
|
|
501
|
-
for i, step in enumerate(
|
|
709
|
+
for i, step in enumerate(steps_to_execute):
|
|
502
710
|
try:
|
|
503
711
|
step_output = await step.aexecute( # type: ignore[union-attr]
|
|
504
712
|
current_step_input,
|
|
@@ -552,13 +760,13 @@ class Condition:
|
|
|
552
760
|
all_results.append(error_output)
|
|
553
761
|
break
|
|
554
762
|
|
|
555
|
-
log_debug(f"Condition End: {self.name} ({len(all_results)} results)", center=True, symbol="-")
|
|
763
|
+
log_debug(f"Condition End: {self.name} ({len(all_results)} results, {branch} branch)", center=True, symbol="-")
|
|
556
764
|
|
|
557
765
|
return StepOutput(
|
|
558
766
|
step_name=self.name,
|
|
559
767
|
step_id=conditional_step_id,
|
|
560
768
|
step_type=StepType.CONDITION,
|
|
561
|
-
content=f"Condition {self.name} completed with {len(all_results)} results",
|
|
769
|
+
content=f"Condition {self.name} completed with {len(all_results)} results ({branch} branch)",
|
|
562
770
|
success=all(result.success for result in all_results) if all_results else True,
|
|
563
771
|
error=None,
|
|
564
772
|
stop=any(result.stop for result in all_results) if all_results else False,
|
|
@@ -583,7 +791,12 @@ class Condition:
|
|
|
583
791
|
num_history_runs: int = 3,
|
|
584
792
|
background_tasks: Optional[Any] = None,
|
|
585
793
|
) -> AsyncIterator[Union[WorkflowRunOutputEvent, TeamRunOutputEvent, RunOutputEvent, StepOutput]]:
|
|
586
|
-
"""Async execute the condition with streaming support
|
|
794
|
+
"""Async execute the condition with streaming support.
|
|
795
|
+
|
|
796
|
+
If condition is True, executes `steps`.
|
|
797
|
+
If condition is False and `else_steps` is provided (and not empty), executes `else_steps`.
|
|
798
|
+
If condition is False and no `else_steps`, yields completed event and returns.
|
|
799
|
+
"""
|
|
587
800
|
log_debug(f"Condition Start: {self.name}", center=True, symbol="-")
|
|
588
801
|
|
|
589
802
|
conditional_step_id = str(uuid4())
|
|
@@ -611,9 +824,18 @@ class Condition:
|
|
|
611
824
|
parent_step_id=parent_step_id,
|
|
612
825
|
)
|
|
613
826
|
|
|
614
|
-
|
|
827
|
+
# Determine which steps to execute
|
|
828
|
+
if condition_result:
|
|
829
|
+
steps_to_execute = self.steps
|
|
830
|
+
branch = CONDITION_BRANCH_IF
|
|
831
|
+
log_debug(f"Condition {self.name} met, executing {len(steps_to_execute)} steps (if branch)")
|
|
832
|
+
elif self._has_else_steps():
|
|
833
|
+
steps_to_execute = self.else_steps # type: ignore[assignment]
|
|
834
|
+
branch = CONDITION_BRANCH_ELSE
|
|
835
|
+
log_debug(f"Condition {self.name} not met, executing {len(steps_to_execute)} else_steps (else branch)")
|
|
836
|
+
else:
|
|
837
|
+
# No else_steps provided, yield completed event and return
|
|
615
838
|
if stream_events and workflow_run_response:
|
|
616
|
-
# Yield condition completed event for empty case
|
|
617
839
|
yield ConditionExecutionCompletedEvent(
|
|
618
840
|
run_id=workflow_run_response.run_id or "",
|
|
619
841
|
workflow_name=workflow_run_response.workflow_name or "",
|
|
@@ -623,22 +845,21 @@ class Condition:
|
|
|
623
845
|
step_index=step_index,
|
|
624
846
|
condition_result=False,
|
|
625
847
|
executed_steps=0,
|
|
848
|
+
branch=None,
|
|
626
849
|
step_results=[],
|
|
627
850
|
step_id=conditional_step_id,
|
|
628
851
|
parent_step_id=parent_step_id,
|
|
629
852
|
)
|
|
630
853
|
return
|
|
631
854
|
|
|
632
|
-
log_debug(f"Condition {self.name} met, executing {len(self.steps)} steps")
|
|
633
|
-
|
|
634
855
|
# Chain steps sequentially like Loop does
|
|
635
|
-
all_results = []
|
|
856
|
+
all_results: List[StepOutput] = []
|
|
636
857
|
current_step_input = step_input
|
|
637
|
-
condition_step_outputs = {}
|
|
858
|
+
condition_step_outputs: Dict[str, StepOutput] = {}
|
|
638
859
|
|
|
639
|
-
for i, step in enumerate(
|
|
860
|
+
for i, step in enumerate(steps_to_execute):
|
|
640
861
|
try:
|
|
641
|
-
step_outputs_for_step = []
|
|
862
|
+
step_outputs_for_step: List[StepOutput] = []
|
|
642
863
|
|
|
643
864
|
# Create child index for each step within condition
|
|
644
865
|
if step_index is None or isinstance(step_index, int):
|
|
@@ -711,7 +932,7 @@ class Condition:
|
|
|
711
932
|
all_results.append(error_output)
|
|
712
933
|
break
|
|
713
934
|
|
|
714
|
-
log_debug(f"Condition End: {self.name} ({len(all_results)} results)", center=True, symbol="-")
|
|
935
|
+
log_debug(f"Condition End: {self.name} ({len(all_results)} results, {branch} branch)", center=True, symbol="-")
|
|
715
936
|
|
|
716
937
|
if stream_events and workflow_run_response:
|
|
717
938
|
# Yield condition completed event
|
|
@@ -722,8 +943,9 @@ class Condition:
|
|
|
722
943
|
session_id=workflow_run_response.session_id or "",
|
|
723
944
|
step_name=self.name,
|
|
724
945
|
step_index=step_index,
|
|
725
|
-
condition_result=
|
|
726
|
-
executed_steps=len(
|
|
946
|
+
condition_result=condition_result,
|
|
947
|
+
executed_steps=len(steps_to_execute),
|
|
948
|
+
branch=branch,
|
|
727
949
|
step_results=all_results,
|
|
728
950
|
step_id=conditional_step_id,
|
|
729
951
|
parent_step_id=parent_step_id,
|
|
@@ -733,7 +955,7 @@ class Condition:
|
|
|
733
955
|
step_name=self.name,
|
|
734
956
|
step_id=conditional_step_id,
|
|
735
957
|
step_type=StepType.CONDITION,
|
|
736
|
-
content=f"Condition {self.name} completed with {len(all_results)} results",
|
|
958
|
+
content=f"Condition {self.name} completed with {len(all_results)} results ({branch} branch)",
|
|
737
959
|
success=all(result.success for result in all_results) if all_results else True,
|
|
738
960
|
stop=any(result.stop for result in all_results) if all_results else False,
|
|
739
961
|
steps=all_results,
|