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/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
|
+
}
|