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.
Files changed (51) hide show
  1. agno/agent/agent.py +5 -1
  2. agno/db/base.py +2 -0
  3. agno/db/postgres/postgres.py +5 -5
  4. agno/db/singlestore/singlestore.py +4 -5
  5. agno/db/sqlite/sqlite.py +4 -4
  6. agno/knowledge/embedder/aws_bedrock.py +325 -106
  7. agno/knowledge/knowledge.py +83 -1853
  8. agno/knowledge/loaders/__init__.py +29 -0
  9. agno/knowledge/loaders/azure_blob.py +423 -0
  10. agno/knowledge/loaders/base.py +187 -0
  11. agno/knowledge/loaders/gcs.py +267 -0
  12. agno/knowledge/loaders/github.py +415 -0
  13. agno/knowledge/loaders/s3.py +281 -0
  14. agno/knowledge/loaders/sharepoint.py +439 -0
  15. agno/knowledge/reader/website_reader.py +2 -2
  16. agno/knowledge/remote_knowledge.py +151 -0
  17. agno/knowledge/reranker/aws_bedrock.py +299 -0
  18. agno/learn/machine.py +5 -6
  19. agno/learn/stores/session_context.py +10 -2
  20. agno/models/azure/openai_chat.py +6 -11
  21. agno/models/neosantara/__init__.py +5 -0
  22. agno/models/neosantara/neosantara.py +42 -0
  23. agno/models/utils.py +5 -0
  24. agno/os/app.py +4 -1
  25. agno/os/interfaces/agui/router.py +1 -1
  26. agno/os/routers/components/components.py +2 -0
  27. agno/os/routers/knowledge/knowledge.py +0 -1
  28. agno/os/routers/registry/registry.py +340 -192
  29. agno/os/routers/workflows/router.py +7 -1
  30. agno/os/schema.py +104 -0
  31. agno/registry/registry.py +4 -0
  32. agno/run/workflow.py +3 -0
  33. agno/session/workflow.py +1 -1
  34. agno/skills/utils.py +100 -2
  35. agno/team/team.py +6 -3
  36. agno/tools/mcp/mcp.py +26 -1
  37. agno/vectordb/lancedb/lance_db.py +22 -7
  38. agno/workflow/__init__.py +4 -0
  39. agno/workflow/cel.py +299 -0
  40. agno/workflow/condition.py +280 -58
  41. agno/workflow/loop.py +177 -46
  42. agno/workflow/parallel.py +75 -4
  43. agno/workflow/router.py +260 -44
  44. agno/workflow/step.py +14 -7
  45. agno/workflow/steps.py +43 -0
  46. agno/workflow/workflow.py +104 -46
  47. {agno-2.4.6.dist-info → agno-2.4.8.dist-info}/METADATA +25 -37
  48. {agno-2.4.6.dist-info → agno-2.4.8.dist-info}/RECORD +51 -39
  49. {agno-2.4.6.dist-info → agno-2.4.8.dist-info}/WHEEL +0 -0
  50. {agno-2.4.6.dist-info → agno-2.4.8.dist-info}/licenses/LICENSE +0 -0
  51. {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
+ }