agno 2.4.7__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/sqlite/sqlite.py +4 -4
- 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/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/session/workflow.py +1 -1
- agno/skills/utils.py +100 -2
- agno/team/team.py +6 -3
- agno/vectordb/lancedb/lance_db.py +22 -7
- agno/workflow/__init__.py +4 -0
- agno/workflow/cel.py +299 -0
- agno/workflow/condition.py +145 -2
- 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.7.dist-info → agno-2.4.8.dist-info}/METADATA +24 -36
- {agno-2.4.7.dist-info → agno-2.4.8.dist-info}/RECORD +45 -34
- {agno-2.4.7.dist-info → agno-2.4.8.dist-info}/WHEEL +0 -0
- {agno-2.4.7.dist-info → agno-2.4.8.dist-info}/licenses/LICENSE +0 -0
- {agno-2.4.7.dist-info → agno-2.4.8.dist-info}/top_level.txt +0 -0
agno/workflow/cel.py
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""CEL (Common Expression Language) support for workflow steps.
|
|
2
|
+
|
|
3
|
+
CEL spec: https://github.com/google/cel-spec
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import re
|
|
8
|
+
from typing import Any, Dict, List, Optional, Union
|
|
9
|
+
|
|
10
|
+
from agno.utils.log import logger
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import celpy
|
|
14
|
+
from celpy import celtypes
|
|
15
|
+
|
|
16
|
+
CEL_AVAILABLE = True
|
|
17
|
+
CelValue = Union[
|
|
18
|
+
celtypes.BoolType,
|
|
19
|
+
celtypes.IntType,
|
|
20
|
+
celtypes.DoubleType,
|
|
21
|
+
celtypes.StringType,
|
|
22
|
+
celtypes.ListType,
|
|
23
|
+
celtypes.MapType,
|
|
24
|
+
]
|
|
25
|
+
except ImportError:
|
|
26
|
+
CEL_AVAILABLE = False
|
|
27
|
+
celpy = None # type: ignore
|
|
28
|
+
celtypes = None # type: ignore
|
|
29
|
+
CelValue = Any # type: ignore
|
|
30
|
+
|
|
31
|
+
# Type alias for Python values that can be converted to CEL
|
|
32
|
+
PythonValue = Union[None, bool, int, float, str, List[Any], Dict[str, Any]]
|
|
33
|
+
|
|
34
|
+
# Regex for simple Python identifiers (function names)
|
|
35
|
+
_IDENTIFIER_RE = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")
|
|
36
|
+
|
|
37
|
+
# Characters/tokens that indicate a CEL expression rather than a function name
|
|
38
|
+
_CEL_INDICATORS = [
|
|
39
|
+
".",
|
|
40
|
+
"(",
|
|
41
|
+
")",
|
|
42
|
+
"[",
|
|
43
|
+
"]",
|
|
44
|
+
"==",
|
|
45
|
+
"!=",
|
|
46
|
+
"<=",
|
|
47
|
+
">=",
|
|
48
|
+
"<",
|
|
49
|
+
">",
|
|
50
|
+
"&&",
|
|
51
|
+
"||",
|
|
52
|
+
"!",
|
|
53
|
+
"+",
|
|
54
|
+
"-",
|
|
55
|
+
"*",
|
|
56
|
+
"/",
|
|
57
|
+
"%",
|
|
58
|
+
"?",
|
|
59
|
+
":",
|
|
60
|
+
'"',
|
|
61
|
+
"'",
|
|
62
|
+
"true",
|
|
63
|
+
"false",
|
|
64
|
+
" in ",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ********** Public Functions **********
|
|
69
|
+
def validate_cel_expression(expression: str) -> bool:
|
|
70
|
+
"""Validate a CEL expression without evaluating it.
|
|
71
|
+
|
|
72
|
+
Useful for UI validation before saving a workflow configuration.
|
|
73
|
+
"""
|
|
74
|
+
if not CEL_AVAILABLE:
|
|
75
|
+
logger.warning("cel-python is not installed. Install with: pip install cel-python")
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
env = celpy.Environment()
|
|
80
|
+
env.compile(expression)
|
|
81
|
+
return True
|
|
82
|
+
except Exception as e:
|
|
83
|
+
logger.debug(f"CEL expression validation failed: {e}")
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def is_cel_expression(value: str) -> bool:
|
|
88
|
+
"""Determine if a string is a CEL expression vs a function name.
|
|
89
|
+
|
|
90
|
+
Simple identifiers like ``my_evaluator`` return False.
|
|
91
|
+
Anything containing operators, dots, parens, etc. returns True.
|
|
92
|
+
"""
|
|
93
|
+
if _IDENTIFIER_RE.match(value):
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
return any(indicator in value for indicator in _CEL_INDICATORS)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def evaluate_cel_condition_evaluator(
|
|
100
|
+
expression: str,
|
|
101
|
+
step_input: "StepInput", # type: ignore # noqa: F821
|
|
102
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
103
|
+
) -> bool:
|
|
104
|
+
"""Evaluate a CEL expression for a Condition evaluator.
|
|
105
|
+
|
|
106
|
+
Context variables:
|
|
107
|
+
- input: The workflow input as a string
|
|
108
|
+
- previous_step_content: Content from the previous step
|
|
109
|
+
- previous_step_outputs: Map of step name to content string from all previous steps
|
|
110
|
+
- additional_data: Map of additional data passed to the workflow
|
|
111
|
+
- session_state: Map of session state values
|
|
112
|
+
"""
|
|
113
|
+
return _evaluate_cel(expression, _build_step_input_context(step_input, session_state))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def evaluate_cel_loop_end_condition(
|
|
117
|
+
expression: str,
|
|
118
|
+
iteration_results: "List[StepOutput]", # type: ignore # noqa: F821
|
|
119
|
+
current_iteration: int = 0,
|
|
120
|
+
max_iterations: int = 3,
|
|
121
|
+
) -> bool:
|
|
122
|
+
"""Evaluate a CEL expression as a Loop end condition.
|
|
123
|
+
|
|
124
|
+
Context variables:
|
|
125
|
+
- current_iteration: Current iteration number (1-indexed, after completion)
|
|
126
|
+
- max_iterations: Maximum iterations configured for the loop
|
|
127
|
+
- all_success: True if all steps in this iteration succeeded
|
|
128
|
+
- last_step_content: Content string from the last step in this iteration
|
|
129
|
+
- step_outputs: Map of step name to content string from the current iteration
|
|
130
|
+
"""
|
|
131
|
+
return _evaluate_cel(
|
|
132
|
+
expression, _build_loop_step_output_context(iteration_results, current_iteration, max_iterations)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def evaluate_cel_router_selector(
|
|
137
|
+
expression: str,
|
|
138
|
+
step_input: "StepInput", # type: ignore # noqa: F821
|
|
139
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
140
|
+
step_choices: Optional[List[str]] = None,
|
|
141
|
+
) -> str:
|
|
142
|
+
"""Evaluate a CEL expression for a Router selector.
|
|
143
|
+
|
|
144
|
+
Returns the name of the step to execute as a string.
|
|
145
|
+
|
|
146
|
+
Context variables (same as Condition, plus step_choices):
|
|
147
|
+
- input: The workflow input as a string
|
|
148
|
+
- previous_step_content: Content from the previous step
|
|
149
|
+
- previous_step_outputs: Map of step name to content string from all previous steps
|
|
150
|
+
- additional_data: Map of additional data passed to the workflow
|
|
151
|
+
- session_state: Map of session state values
|
|
152
|
+
- step_choices: List of step names available to the selector
|
|
153
|
+
"""
|
|
154
|
+
context = _build_step_input_context(step_input, session_state)
|
|
155
|
+
context["step_choices"] = step_choices or []
|
|
156
|
+
return _evaluate_cel_string(expression, context)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# ********** Internal Functions **********
|
|
160
|
+
def _evaluate_cel_raw(expression: str, context: Dict[str, Any]) -> Any:
|
|
161
|
+
"""Core CEL evaluation: compile, run, and return the raw result."""
|
|
162
|
+
if not CEL_AVAILABLE:
|
|
163
|
+
raise RuntimeError("cel-python is not installed. Install with: pip install cel-python")
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
env = celpy.Environment()
|
|
167
|
+
prog = env.program(env.compile(expression))
|
|
168
|
+
return prog.evaluate({k: _to_cel(v) for k, v in context.items()})
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.error(f"CEL evaluation failed for '{expression}': {e}")
|
|
171
|
+
raise ValueError(f"Failed to evaluate CEL expression '{expression}': {e}") from e
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _evaluate_cel(expression: str, context: Dict[str, Any]) -> bool:
|
|
175
|
+
"""CEL evaluation that coerces the result to bool."""
|
|
176
|
+
result = _evaluate_cel_raw(expression, context)
|
|
177
|
+
|
|
178
|
+
if isinstance(result, celtypes.BoolType):
|
|
179
|
+
return bool(result)
|
|
180
|
+
if isinstance(result, bool):
|
|
181
|
+
return result
|
|
182
|
+
|
|
183
|
+
logger.warning(f"CEL expression '{expression}' returned {type(result).__name__}, converting to bool")
|
|
184
|
+
return bool(result)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _evaluate_cel_string(expression: str, context: Dict[str, Any]) -> str:
|
|
188
|
+
"""CEL evaluation that coerces the result to string (for Router selector)."""
|
|
189
|
+
result = _evaluate_cel_raw(expression, context)
|
|
190
|
+
|
|
191
|
+
if isinstance(result, celtypes.StringType):
|
|
192
|
+
return str(result)
|
|
193
|
+
if isinstance(result, str):
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
logger.warning(f"CEL expression '{expression}' returned {type(result).__name__}, converting to string")
|
|
197
|
+
return str(result)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _to_cel(value: PythonValue) -> Union["CelValue", None]:
|
|
201
|
+
"""Convert a Python value to a CEL-compatible type.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
value: A Python value (None, bool, int, float, str, list, or dict)
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
The corresponding CEL type, or None if input is None
|
|
208
|
+
"""
|
|
209
|
+
if value is None:
|
|
210
|
+
return None
|
|
211
|
+
|
|
212
|
+
if isinstance(value, bool):
|
|
213
|
+
return celtypes.BoolType(value)
|
|
214
|
+
if isinstance(value, int):
|
|
215
|
+
return celtypes.IntType(value)
|
|
216
|
+
if isinstance(value, float):
|
|
217
|
+
return celtypes.DoubleType(value)
|
|
218
|
+
if isinstance(value, str):
|
|
219
|
+
return celtypes.StringType(value)
|
|
220
|
+
if isinstance(value, list):
|
|
221
|
+
return celtypes.ListType([_to_cel(item) for item in value])
|
|
222
|
+
if isinstance(value, dict):
|
|
223
|
+
return celtypes.MapType({celtypes.StringType(k): _to_cel(v) for k, v in value.items()})
|
|
224
|
+
|
|
225
|
+
# Fallback for any other type - convert to string
|
|
226
|
+
return celtypes.StringType(str(value))
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _build_step_input_context(
|
|
230
|
+
step_input: "StepInput", # type: ignore # noqa: F821
|
|
231
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
232
|
+
) -> Dict[str, Any]:
|
|
233
|
+
"""Build context for CEL evaluation of step input.
|
|
234
|
+
|
|
235
|
+
Maps directly to StepInput fields:
|
|
236
|
+
- input: from step_input.input (as string)
|
|
237
|
+
- previous_step_content: from step_input.previous_step_content (as string)
|
|
238
|
+
- previous_step_outputs: from step_input.previous_step_outputs (map of step name -> content string)
|
|
239
|
+
- additional_data: from step_input.additional_data
|
|
240
|
+
- session_state: passed separately
|
|
241
|
+
"""
|
|
242
|
+
input_str = ""
|
|
243
|
+
if step_input.input is not None:
|
|
244
|
+
input_str = step_input.get_input_as_string() or ""
|
|
245
|
+
|
|
246
|
+
previous_content = ""
|
|
247
|
+
if step_input.previous_step_content is not None:
|
|
248
|
+
if hasattr(step_input.previous_step_content, "model_dump_json"):
|
|
249
|
+
previous_content = step_input.previous_step_content.model_dump_json()
|
|
250
|
+
elif isinstance(step_input.previous_step_content, dict):
|
|
251
|
+
previous_content = json.dumps(step_input.previous_step_content, default=str)
|
|
252
|
+
else:
|
|
253
|
+
previous_content = str(step_input.previous_step_content)
|
|
254
|
+
|
|
255
|
+
previous_step_outputs: Dict[str, str] = {}
|
|
256
|
+
if step_input.previous_step_outputs:
|
|
257
|
+
for name, output in step_input.previous_step_outputs.items():
|
|
258
|
+
previous_step_outputs[name] = str(output.content) if output.content else ""
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
"input": input_str,
|
|
262
|
+
"previous_step_content": previous_content,
|
|
263
|
+
"previous_step_outputs": previous_step_outputs,
|
|
264
|
+
"additional_data": step_input.additional_data or {},
|
|
265
|
+
"session_state": session_state or {},
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _build_loop_step_output_context(
|
|
270
|
+
iteration_results: "List[StepOutput]", # type: ignore # noqa: F821
|
|
271
|
+
current_iteration: int = 0,
|
|
272
|
+
max_iterations: int = 3,
|
|
273
|
+
) -> Dict[str, Any]:
|
|
274
|
+
"""Build context for CEL evaluation of loop end condition from iteration results.
|
|
275
|
+
|
|
276
|
+
Maps to StepOutput fields:
|
|
277
|
+
- step_outputs: map of StepOutput.step_name -> str(StepOutput.content)
|
|
278
|
+
- all_success: derived from StepOutput.success
|
|
279
|
+
- last_step_content: content from the last StepOutput of the current loop iteration
|
|
280
|
+
"""
|
|
281
|
+
all_success = True
|
|
282
|
+
outputs: Dict[str, str] = {}
|
|
283
|
+
last_content = ""
|
|
284
|
+
|
|
285
|
+
for result in iteration_results:
|
|
286
|
+
content = str(result.content) if result.content else ""
|
|
287
|
+
name = result.step_name or f"step_{len(outputs)}"
|
|
288
|
+
outputs[name] = content
|
|
289
|
+
last_content = content
|
|
290
|
+
if not result.success:
|
|
291
|
+
all_success = False
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
"current_iteration": current_iteration,
|
|
295
|
+
"max_iterations": max_iterations,
|
|
296
|
+
"all_success": all_success,
|
|
297
|
+
"last_step_content": last_content,
|
|
298
|
+
"step_outputs": outputs,
|
|
299
|
+
}
|
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,6 +15,7 @@ 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
|
|
|
@@ -43,13 +45,33 @@ class Condition:
|
|
|
43
45
|
If the condition evaluates to True, the `steps` are executed.
|
|
44
46
|
If the condition evaluates to False and `else_steps` is provided (and not empty),
|
|
45
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")'
|
|
46
66
|
"""
|
|
47
67
|
|
|
48
68
|
# Evaluator should only return boolean
|
|
69
|
+
# Can be a callable, a bool, or a CEL expression string
|
|
49
70
|
evaluator: Union[
|
|
50
71
|
Callable[[StepInput], bool],
|
|
51
72
|
Callable[[StepInput], Awaitable[bool]],
|
|
52
73
|
bool,
|
|
74
|
+
str, # CEL expression
|
|
53
75
|
]
|
|
54
76
|
steps: WorkflowSteps
|
|
55
77
|
|
|
@@ -59,6 +81,89 @@ class Condition:
|
|
|
59
81
|
name: Optional[str] = None
|
|
60
82
|
description: Optional[str] = None
|
|
61
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
|
+
|
|
62
167
|
def _prepare_steps(self):
|
|
63
168
|
"""Prepare the steps for execution - mirrors workflow logic"""
|
|
64
169
|
from agno.agent.agent import Agent
|
|
@@ -132,10 +237,29 @@ class Condition:
|
|
|
132
237
|
)
|
|
133
238
|
|
|
134
239
|
def _evaluate_condition(self, step_input: StepInput, session_state: Optional[Dict[str, Any]] = None) -> bool:
|
|
135
|
-
"""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
|
+
"""
|
|
136
247
|
if isinstance(self.evaluator, bool):
|
|
137
248
|
return self.evaluator
|
|
138
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
|
+
|
|
139
263
|
if callable(self.evaluator):
|
|
140
264
|
if session_state is not None and self._evaluator_has_session_state_param():
|
|
141
265
|
result = self.evaluator(step_input, session_state=session_state) # type: ignore[call-arg]
|
|
@@ -151,10 +275,29 @@ class Condition:
|
|
|
151
275
|
return False
|
|
152
276
|
|
|
153
277
|
async def _aevaluate_condition(self, step_input: StepInput, session_state: Optional[Dict[str, Any]] = None) -> bool:
|
|
154
|
-
"""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
|
+
"""
|
|
155
285
|
if isinstance(self.evaluator, bool):
|
|
156
286
|
return self.evaluator
|
|
157
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
|
+
|
|
158
301
|
if callable(self.evaluator):
|
|
159
302
|
has_session_state = session_state is not None and self._evaluator_has_session_state_param()
|
|
160
303
|
|