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/router.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_router_selector, is_cel_expression
|
|
17
19
|
from agno.workflow.step import Step
|
|
18
20
|
from agno.workflow.types import StepInput, StepOutput, StepType
|
|
19
21
|
|
|
@@ -34,20 +36,119 @@ WorkflowSteps = List[
|
|
|
34
36
|
|
|
35
37
|
@dataclass
|
|
36
38
|
class Router:
|
|
37
|
-
"""A router that dynamically selects which step(s) to execute based on input
|
|
39
|
+
"""A router that dynamically selects which step(s) to execute based on input.
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
The selector can be:
|
|
42
|
+
- A callable function that takes StepInput and returns step(s)
|
|
43
|
+
- A CEL (Common Expression Language) expression string that returns a step name
|
|
44
|
+
|
|
45
|
+
CEL expressions for selector have access to (same as Condition, plus step_choices):
|
|
46
|
+
- input: The workflow input as a string
|
|
47
|
+
- previous_step_content: Content from the previous step
|
|
48
|
+
- previous_step_outputs: Map of step name to content string from all previous steps
|
|
49
|
+
- additional_data: Map of additional data passed to the workflow
|
|
50
|
+
- session_state: Map of session state values
|
|
51
|
+
- step_choices: List of step names available to the selector
|
|
52
|
+
|
|
53
|
+
CEL expressions must return the name of a step from choices.
|
|
54
|
+
|
|
55
|
+
Example CEL expressions:
|
|
56
|
+
- 'input.contains("video") ? "video_step" : "image_step"'
|
|
57
|
+
- 'additional_data.route'
|
|
58
|
+
- 'previous_step_outputs.classifier.contains("billing") ? "Billing" : "Support"'
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
# Router function or CEL expression that selects step(s) to execute
|
|
40
62
|
selector: Union[
|
|
41
63
|
Callable[[StepInput], Union[WorkflowSteps, List[WorkflowSteps]]],
|
|
42
64
|
Callable[[StepInput], Awaitable[Union[WorkflowSteps, List[WorkflowSteps]]]],
|
|
65
|
+
str, # CEL expression returning step name
|
|
43
66
|
]
|
|
44
67
|
choices: WorkflowSteps # Available steps that can be selected
|
|
45
68
|
|
|
46
69
|
name: Optional[str] = None
|
|
47
70
|
description: Optional[str] = None
|
|
48
71
|
|
|
49
|
-
def
|
|
50
|
-
|
|
72
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
73
|
+
result: Dict[str, Any] = {
|
|
74
|
+
"type": "Router",
|
|
75
|
+
"name": self.name,
|
|
76
|
+
"description": self.description,
|
|
77
|
+
"choices": [step.to_dict() for step in self.choices if hasattr(step, "to_dict")],
|
|
78
|
+
}
|
|
79
|
+
# Serialize selector
|
|
80
|
+
if callable(self.selector):
|
|
81
|
+
result["selector"] = self.selector.__name__
|
|
82
|
+
result["selector_type"] = "function"
|
|
83
|
+
elif isinstance(self.selector, str):
|
|
84
|
+
result["selector"] = self.selector
|
|
85
|
+
result["selector_type"] = "cel"
|
|
86
|
+
else:
|
|
87
|
+
raise ValueError(f"Invalid selector type: {type(self.selector).__name__}")
|
|
88
|
+
|
|
89
|
+
return result
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def from_dict(
|
|
93
|
+
cls,
|
|
94
|
+
data: Dict[str, Any],
|
|
95
|
+
registry: Optional["Registry"] = None,
|
|
96
|
+
db: Optional[Any] = None,
|
|
97
|
+
links: Optional[List[Dict[str, Any]]] = None,
|
|
98
|
+
) -> "Router":
|
|
99
|
+
from agno.workflow.condition import Condition
|
|
100
|
+
from agno.workflow.loop import Loop
|
|
101
|
+
from agno.workflow.parallel import Parallel
|
|
102
|
+
from agno.workflow.steps import Steps
|
|
103
|
+
|
|
104
|
+
def deserialize_step(step_data: Dict[str, Any]) -> Any:
|
|
105
|
+
step_type = step_data.get("type", "Step")
|
|
106
|
+
if step_type == "Loop":
|
|
107
|
+
return Loop.from_dict(step_data, registry=registry, db=db, links=links)
|
|
108
|
+
elif step_type == "Parallel":
|
|
109
|
+
return Parallel.from_dict(step_data, registry=registry, db=db, links=links)
|
|
110
|
+
elif step_type == "Steps":
|
|
111
|
+
return Steps.from_dict(step_data, registry=registry, db=db, links=links)
|
|
112
|
+
elif step_type == "Condition":
|
|
113
|
+
return Condition.from_dict(step_data, registry=registry, db=db, links=links)
|
|
114
|
+
elif step_type == "Router":
|
|
115
|
+
return cls.from_dict(step_data, registry=registry, db=db, links=links)
|
|
116
|
+
else:
|
|
117
|
+
return Step.from_dict(step_data, registry=registry, db=db, links=links)
|
|
118
|
+
|
|
119
|
+
# Deserialize selector
|
|
120
|
+
selector_data = data.get("selector")
|
|
121
|
+
selector_type = data.get("selector_type")
|
|
122
|
+
|
|
123
|
+
selector: Any = None
|
|
124
|
+
if selector_data is None:
|
|
125
|
+
raise ValueError("Router requires a selector")
|
|
126
|
+
elif isinstance(selector_data, str):
|
|
127
|
+
# Determine if this is a CEL expression or a function name
|
|
128
|
+
if selector_type == "cel" or (selector_type is None and is_cel_expression(selector_data)):
|
|
129
|
+
# CEL expression - use as-is
|
|
130
|
+
selector = selector_data
|
|
131
|
+
else:
|
|
132
|
+
# Function name - look up in registry
|
|
133
|
+
if registry:
|
|
134
|
+
func = registry.get_function(selector_data)
|
|
135
|
+
if func is None:
|
|
136
|
+
raise ValueError(f"Selector function '{selector_data}' not found in registry")
|
|
137
|
+
selector = func
|
|
138
|
+
else:
|
|
139
|
+
raise ValueError(f"Registry required to deserialize selector function '{selector_data}'")
|
|
140
|
+
else:
|
|
141
|
+
raise ValueError(f"Invalid selector type in data: {type(selector_data).__name__}")
|
|
142
|
+
|
|
143
|
+
return cls(
|
|
144
|
+
selector=selector,
|
|
145
|
+
choices=[deserialize_step(step) for step in data.get("choices", [])],
|
|
146
|
+
name=data.get("name"),
|
|
147
|
+
description=data.get("description"),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def _prepare_single_step(self, step: Any) -> Any:
|
|
151
|
+
"""Prepare a single step for execution."""
|
|
51
152
|
from agno.agent.agent import Agent
|
|
52
153
|
from agno.team.team import Team
|
|
53
154
|
from agno.workflow.condition import Condition
|
|
@@ -56,20 +157,41 @@ class Router:
|
|
|
56
157
|
from agno.workflow.step import Step
|
|
57
158
|
from agno.workflow.steps import Steps
|
|
58
159
|
|
|
160
|
+
if callable(step) and hasattr(step, "__name__"):
|
|
161
|
+
return Step(name=step.__name__, description="User-defined callable step", executor=step)
|
|
162
|
+
elif isinstance(step, Agent):
|
|
163
|
+
return Step(name=step.name, description=step.description, agent=step)
|
|
164
|
+
elif isinstance(step, Team):
|
|
165
|
+
return Step(name=step.name, description=step.description, team=step)
|
|
166
|
+
elif isinstance(step, (Step, Steps, Loop, Parallel, Condition, Router)):
|
|
167
|
+
return step
|
|
168
|
+
else:
|
|
169
|
+
raise ValueError(f"Invalid step type: {type(step).__name__}")
|
|
170
|
+
|
|
171
|
+
def _prepare_steps(self):
|
|
172
|
+
"""Prepare the steps for execution - mirrors workflow logic"""
|
|
173
|
+
from agno.workflow.steps import Steps
|
|
174
|
+
|
|
59
175
|
prepared_steps: WorkflowSteps = []
|
|
60
176
|
for step in self.choices:
|
|
61
|
-
if
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
177
|
+
if isinstance(step, list):
|
|
178
|
+
# Handle nested list of steps - wrap in Steps container
|
|
179
|
+
nested_prepared = [self._prepare_single_step(s) for s in step]
|
|
180
|
+
# Create a Steps container with a generated name
|
|
181
|
+
steps_container = Steps(
|
|
182
|
+
name=f"steps_group_{len(prepared_steps)}",
|
|
183
|
+
steps=nested_prepared,
|
|
184
|
+
)
|
|
185
|
+
prepared_steps.append(steps_container)
|
|
69
186
|
else:
|
|
70
|
-
|
|
187
|
+
prepared_steps.append(self._prepare_single_step(step))
|
|
71
188
|
|
|
72
189
|
self.steps = prepared_steps
|
|
190
|
+
# Build name-to-step mapping for string-based selection (used by CEL and callable selectors)
|
|
191
|
+
self._step_name_map: Dict[str, Any] = {}
|
|
192
|
+
for step in self.steps:
|
|
193
|
+
if hasattr(step, "name") and step.name:
|
|
194
|
+
self._step_name_map[step.name] = step
|
|
73
195
|
|
|
74
196
|
def _update_step_input_from_outputs(
|
|
75
197
|
self,
|
|
@@ -109,54 +231,148 @@ class Router:
|
|
|
109
231
|
audio=current_audio + all_audio,
|
|
110
232
|
)
|
|
111
233
|
|
|
112
|
-
def
|
|
113
|
-
"""
|
|
114
|
-
if callable(self.selector):
|
|
115
|
-
if session_state is not None and self._selector_has_session_state_param():
|
|
116
|
-
result = self.selector(step_input, session_state) # type: ignore[call-arg]
|
|
117
|
-
else:
|
|
118
|
-
result = self.selector(step_input)
|
|
234
|
+
def _resolve_selector_result(self, result: Any) -> List[Any]:
|
|
235
|
+
"""Resolve selector result to a list of steps, handling strings, Steps, and lists.
|
|
119
236
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
237
|
+
This unified resolver handles:
|
|
238
|
+
- String step names (from CEL expressions or callable selectors)
|
|
239
|
+
- Step objects directly returned by callable selectors
|
|
240
|
+
- Lists of strings or Steps
|
|
241
|
+
"""
|
|
242
|
+
from agno.workflow.condition import Condition
|
|
243
|
+
from agno.workflow.loop import Loop
|
|
244
|
+
from agno.workflow.parallel import Parallel
|
|
245
|
+
from agno.workflow.steps import Steps
|
|
246
|
+
|
|
247
|
+
if result is None:
|
|
248
|
+
return []
|
|
249
|
+
|
|
250
|
+
# Handle string - look up by name in the step_name_map
|
|
251
|
+
if isinstance(result, str):
|
|
252
|
+
if result in self._step_name_map:
|
|
253
|
+
return [self._step_name_map[result]]
|
|
125
254
|
else:
|
|
126
|
-
|
|
255
|
+
available_steps = list(self._step_name_map.keys())
|
|
256
|
+
logger.warning(
|
|
257
|
+
f"Router '{self.name}' selector returned unknown step name: '{result}'. "
|
|
258
|
+
f"Available step names are: {available_steps}. "
|
|
259
|
+
f"Make sure the selector returns one of the available step names."
|
|
260
|
+
)
|
|
261
|
+
return []
|
|
262
|
+
|
|
263
|
+
# Handle step types (Step, Steps, Loop, Parallel, Condition, Router)
|
|
264
|
+
if isinstance(result, (Step, Steps, Loop, Parallel, Condition, Router)):
|
|
265
|
+
# Validate that the returned step is in the router's choices
|
|
266
|
+
step_name = getattr(result, "name", None)
|
|
267
|
+
if step_name and step_name not in self._step_name_map:
|
|
268
|
+
available_steps = list(self._step_name_map.keys())
|
|
269
|
+
logger.warning(
|
|
270
|
+
f"Router '{self.name}' selector returned a Step '{step_name}' that is not in choices. "
|
|
271
|
+
f"Available step names are: {available_steps}. "
|
|
272
|
+
f"The step will still be executed, but this may indicate a configuration error."
|
|
273
|
+
)
|
|
274
|
+
return [result]
|
|
275
|
+
|
|
276
|
+
# Handle list of results (could be strings, Steps, or mixed)
|
|
277
|
+
if isinstance(result, list):
|
|
278
|
+
resolved = []
|
|
279
|
+
for item in result:
|
|
280
|
+
resolved.extend(self._resolve_selector_result(item))
|
|
281
|
+
return resolved
|
|
282
|
+
|
|
283
|
+
logger.warning(f"Router selector returned unexpected type: {type(result)}")
|
|
284
|
+
return []
|
|
285
|
+
|
|
286
|
+
def _selector_has_step_choices_param(self) -> bool:
|
|
287
|
+
"""Check if the selector function has a step_choices parameter"""
|
|
288
|
+
if not callable(self.selector):
|
|
289
|
+
return False
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
sig = inspect.signature(self.selector)
|
|
293
|
+
return "step_choices" in sig.parameters
|
|
294
|
+
except Exception:
|
|
295
|
+
return False
|
|
296
|
+
|
|
297
|
+
def _route_steps(self, step_input: StepInput, session_state: Optional[Dict[str, Any]] = None) -> List[Step]: # type: ignore[return-value]
|
|
298
|
+
"""Route to the appropriate steps based on input."""
|
|
299
|
+
# Handle CEL expression selector
|
|
300
|
+
if isinstance(self.selector, str):
|
|
301
|
+
if not CEL_AVAILABLE:
|
|
302
|
+
logger.error(
|
|
303
|
+
"CEL expression used but cel-python is not installed. Install with: pip install cel-python"
|
|
304
|
+
)
|
|
305
|
+
return []
|
|
306
|
+
try:
|
|
307
|
+
step_names = list(self._step_name_map.keys())
|
|
308
|
+
step_name = evaluate_cel_router_selector(
|
|
309
|
+
self.selector, step_input, session_state, step_choices=step_names
|
|
310
|
+
)
|
|
311
|
+
return self._resolve_selector_result(step_name)
|
|
312
|
+
except Exception as e:
|
|
313
|
+
logger.error(f"Router CEL evaluation failed: {e}")
|
|
127
314
|
return []
|
|
128
315
|
|
|
316
|
+
# Handle callable selector
|
|
317
|
+
if callable(self.selector):
|
|
318
|
+
has_session_state = session_state is not None and self._selector_has_session_state_param()
|
|
319
|
+
has_step_choices = self._selector_has_step_choices_param()
|
|
320
|
+
|
|
321
|
+
# Build kwargs based on what parameters the selector accepts
|
|
322
|
+
kwargs: Dict[str, Any] = {}
|
|
323
|
+
if has_session_state:
|
|
324
|
+
kwargs["session_state"] = session_state
|
|
325
|
+
if has_step_choices:
|
|
326
|
+
kwargs["step_choices"] = self.steps
|
|
327
|
+
|
|
328
|
+
result = self.selector(step_input, **kwargs) # type: ignore[call-arg]
|
|
329
|
+
|
|
330
|
+
return self._resolve_selector_result(result)
|
|
331
|
+
|
|
129
332
|
return []
|
|
130
333
|
|
|
131
334
|
async def _aroute_steps(self, step_input: StepInput, session_state: Optional[Dict[str, Any]] = None) -> List[Step]: # type: ignore[return-value]
|
|
132
|
-
"""Async version of step routing"""
|
|
335
|
+
"""Async version of step routing."""
|
|
336
|
+
# Handle CEL expression selector (CEL evaluation is synchronous)
|
|
337
|
+
if isinstance(self.selector, str):
|
|
338
|
+
if not CEL_AVAILABLE:
|
|
339
|
+
logger.error(
|
|
340
|
+
"CEL expression used but cel-python is not installed. Install with: pip install cel-python"
|
|
341
|
+
)
|
|
342
|
+
return []
|
|
343
|
+
try:
|
|
344
|
+
step_names = list(self._step_name_map.keys())
|
|
345
|
+
step_name = evaluate_cel_router_selector(
|
|
346
|
+
self.selector, step_input, session_state, step_choices=step_names
|
|
347
|
+
)
|
|
348
|
+
return self._resolve_selector_result(step_name)
|
|
349
|
+
except Exception as e:
|
|
350
|
+
logger.error(f"Router CEL evaluation failed: {e}")
|
|
351
|
+
return []
|
|
352
|
+
|
|
353
|
+
# Handle callable selector
|
|
133
354
|
if callable(self.selector):
|
|
134
355
|
has_session_state = session_state is not None and self._selector_has_session_state_param()
|
|
356
|
+
has_step_choices = self._selector_has_step_choices_param()
|
|
357
|
+
|
|
358
|
+
# Build kwargs based on what parameters the selector accepts
|
|
359
|
+
kwargs: Dict[str, Any] = {}
|
|
360
|
+
if has_session_state:
|
|
361
|
+
kwargs["session_state"] = session_state
|
|
362
|
+
if has_step_choices:
|
|
363
|
+
kwargs["step_choices"] = self.steps
|
|
135
364
|
|
|
136
365
|
if inspect.iscoroutinefunction(self.selector):
|
|
137
|
-
|
|
138
|
-
result = await self.selector(step_input, session_state) # type: ignore[call-arg]
|
|
139
|
-
else:
|
|
140
|
-
result = await self.selector(step_input)
|
|
366
|
+
result = await self.selector(step_input, **kwargs) # type: ignore[call-arg]
|
|
141
367
|
else:
|
|
142
|
-
|
|
143
|
-
result = self.selector(step_input, session_state) # type: ignore[call-arg]
|
|
144
|
-
else:
|
|
145
|
-
result = self.selector(step_input)
|
|
368
|
+
result = self.selector(step_input, **kwargs) # type: ignore[call-arg]
|
|
146
369
|
|
|
147
|
-
|
|
148
|
-
if isinstance(result, Step):
|
|
149
|
-
return [result]
|
|
150
|
-
elif isinstance(result, list):
|
|
151
|
-
return result
|
|
152
|
-
else:
|
|
153
|
-
logger.warning(f"Router function returned unexpected type: {type(result)}")
|
|
154
|
-
return []
|
|
370
|
+
return self._resolve_selector_result(result)
|
|
155
371
|
|
|
156
372
|
return []
|
|
157
373
|
|
|
158
374
|
def _selector_has_session_state_param(self) -> bool:
|
|
159
|
-
"""Check if the selector function has a session_state parameter"""
|
|
375
|
+
"""Check if the selector function has a session_state parameter."""
|
|
160
376
|
if not callable(self.selector):
|
|
161
377
|
return False
|
|
162
378
|
|
agno/workflow/step.py
CHANGED
|
@@ -118,6 +118,7 @@ class Step:
|
|
|
118
118
|
def to_dict(self) -> Dict[str, Any]:
|
|
119
119
|
"""Convert step to a dictionary representation."""
|
|
120
120
|
result = {
|
|
121
|
+
"type": "Step",
|
|
121
122
|
"name": self.name,
|
|
122
123
|
"step_id": self.step_id,
|
|
123
124
|
"description": self.description,
|
|
@@ -132,7 +133,8 @@ class Step:
|
|
|
132
133
|
result["agent_id"] = self.agent.id
|
|
133
134
|
if self.team is not None:
|
|
134
135
|
result["team_id"] = self.team.id
|
|
135
|
-
|
|
136
|
+
if self.executor is not None:
|
|
137
|
+
result["executor_ref"] = self.executor.__name__
|
|
136
138
|
|
|
137
139
|
return result
|
|
138
140
|
|
|
@@ -171,14 +173,16 @@ class Step:
|
|
|
171
173
|
agent = get_agent_by_id(db=db, id=agent_id, registry=registry)
|
|
172
174
|
|
|
173
175
|
# --- Handle Team reconstruction ---
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
if "team_id" in config and config["team_id"] and registry:
|
|
177
|
+
from agno.team.team import get_team_by_id
|
|
178
|
+
|
|
179
|
+
team_id = config.get("team_id")
|
|
180
|
+
if db is not None and team_id is not None:
|
|
181
|
+
team = get_team_by_id(db=db, id=team_id, registry=registry)
|
|
177
182
|
|
|
178
183
|
# --- Handle Executor reconstruction ---
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
# executor = registry.rehydrate_function(config["executor_ref"])
|
|
184
|
+
if "executor_ref" in config and config["executor_ref"] and registry:
|
|
185
|
+
executor = registry.get_function(config["executor_ref"])
|
|
182
186
|
|
|
183
187
|
return cls(
|
|
184
188
|
name=config.get("name"),
|
|
@@ -559,6 +563,9 @@ class Step:
|
|
|
559
563
|
event.workflow_id = workflow_run_response.workflow_id
|
|
560
564
|
if hasattr(event, "workflow_run_id"):
|
|
561
565
|
event.workflow_run_id = workflow_run_response.run_id
|
|
566
|
+
# Set session_id to match workflow's session_id for consistent event tracking
|
|
567
|
+
if hasattr(event, "session_id") and workflow_run_response.session_id:
|
|
568
|
+
event.session_id = workflow_run_response.session_id
|
|
562
569
|
if hasattr(event, "step_id"):
|
|
563
570
|
event.step_id = self.step_id
|
|
564
571
|
if hasattr(event, "step_name") and self.name is not None:
|
agno/workflow/steps.py
CHANGED
|
@@ -2,6 +2,7 @@ from dataclasses import dataclass
|
|
|
2
2
|
from typing import Any, AsyncIterator, Awaitable, Callable, Dict, Iterator, List, Optional, Union
|
|
3
3
|
from uuid import uuid4
|
|
4
4
|
|
|
5
|
+
from agno.registry import Registry
|
|
5
6
|
from agno.run.agent import RunOutputEvent
|
|
6
7
|
from agno.run.base import RunContext
|
|
7
8
|
from agno.run.team import TeamRunOutputEvent
|
|
@@ -48,6 +49,48 @@ class Steps:
|
|
|
48
49
|
self.description = description
|
|
49
50
|
self.steps = steps if steps else []
|
|
50
51
|
|
|
52
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
53
|
+
return {
|
|
54
|
+
"type": "Steps",
|
|
55
|
+
"name": self.name,
|
|
56
|
+
"description": self.description,
|
|
57
|
+
"steps": [step.to_dict() for step in self.steps if hasattr(step, "to_dict")],
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def from_dict(
|
|
62
|
+
cls,
|
|
63
|
+
data: Dict[str, Any],
|
|
64
|
+
registry: Optional["Registry"] = None,
|
|
65
|
+
db: Optional[Any] = None,
|
|
66
|
+
links: Optional[List[Dict[str, Any]]] = None,
|
|
67
|
+
) -> "Steps":
|
|
68
|
+
from agno.workflow.condition import Condition
|
|
69
|
+
from agno.workflow.loop import Loop
|
|
70
|
+
from agno.workflow.parallel import Parallel
|
|
71
|
+
from agno.workflow.router import Router
|
|
72
|
+
|
|
73
|
+
def deserialize_step(step_data: Dict[str, Any]) -> Any:
|
|
74
|
+
step_type = step_data.get("type", "Step")
|
|
75
|
+
if step_type == "Loop":
|
|
76
|
+
return Loop.from_dict(step_data, registry=registry, db=db, links=links)
|
|
77
|
+
elif step_type == "Parallel":
|
|
78
|
+
return Parallel.from_dict(step_data, registry=registry, db=db, links=links)
|
|
79
|
+
elif step_type == "Steps":
|
|
80
|
+
return cls.from_dict(step_data, registry=registry, db=db, links=links)
|
|
81
|
+
elif step_type == "Condition":
|
|
82
|
+
return Condition.from_dict(step_data, registry=registry, db=db, links=links)
|
|
83
|
+
elif step_type == "Router":
|
|
84
|
+
return Router.from_dict(step_data, registry=registry, db=db, links=links)
|
|
85
|
+
else:
|
|
86
|
+
return Step.from_dict(step_data, registry=registry, db=db, links=links)
|
|
87
|
+
|
|
88
|
+
return cls(
|
|
89
|
+
name=data.get("name"),
|
|
90
|
+
description=data.get("description"),
|
|
91
|
+
steps=[deserialize_step(step) for step in data.get("steps", [])],
|
|
92
|
+
)
|
|
93
|
+
|
|
51
94
|
def _prepare_steps(self):
|
|
52
95
|
"""Prepare the steps for execution - mirrors workflow logic"""
|
|
53
96
|
from agno.agent.agent import Agent
|