agno 2.2.9__py3-none-any.whl → 2.2.11__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 +27 -5
- agno/db/dynamo/utils.py +1 -1
- agno/db/firestore/utils.py +1 -1
- agno/db/gcs_json/utils.py +1 -1
- agno/db/in_memory/utils.py +1 -1
- agno/db/json/utils.py +1 -1
- agno/db/mongo/utils.py +3 -3
- agno/db/mysql/utils.py +1 -1
- agno/db/postgres/utils.py +1 -1
- agno/db/redis/utils.py +1 -1
- agno/db/singlestore/utils.py +1 -1
- agno/db/sqlite/utils.py +1 -1
- agno/knowledge/chunking/agentic.py +8 -9
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/sentence_transformer.py +6 -2
- agno/knowledge/reader/base.py +6 -2
- agno/knowledge/utils.py +20 -0
- agno/models/anthropic/claude.py +45 -9
- agno/models/base.py +4 -0
- agno/os/app.py +35 -19
- agno/os/routers/health.py +5 -3
- agno/os/routers/knowledge/knowledge.py +43 -17
- agno/os/routers/knowledge/schemas.py +4 -3
- agno/run/agent.py +11 -1
- agno/team/team.py +20 -3
- agno/tools/file_generation.py +4 -4
- agno/tools/gmail.py +179 -0
- agno/tools/parallel.py +314 -0
- agno/utils/models/claude.py +2 -1
- agno/workflow/agent.py +2 -2
- agno/workflow/condition.py +26 -4
- agno/workflow/loop.py +9 -0
- agno/workflow/parallel.py +39 -16
- agno/workflow/router.py +25 -4
- agno/workflow/step.py +163 -91
- agno/workflow/steps.py +9 -0
- agno/workflow/types.py +20 -1
- agno/workflow/workflow.py +117 -30
- {agno-2.2.9.dist-info → agno-2.2.11.dist-info}/METADATA +4 -1
- {agno-2.2.9.dist-info → agno-2.2.11.dist-info}/RECORD +43 -42
- {agno-2.2.9.dist-info → agno-2.2.11.dist-info}/WHEEL +0 -0
- {agno-2.2.9.dist-info → agno-2.2.11.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.9.dist-info → agno-2.2.11.dist-info}/top_level.txt +0 -0
agno/workflow/step.py
CHANGED
|
@@ -11,7 +11,10 @@ from agno.agent import Agent
|
|
|
11
11
|
from agno.media import Audio, Image, Video
|
|
12
12
|
from agno.models.metrics import Metrics
|
|
13
13
|
from agno.run import RunContext
|
|
14
|
-
from agno.run.agent import RunOutput
|
|
14
|
+
from agno.run.agent import RunCompletedEvent, RunContentEvent, RunOutput
|
|
15
|
+
from agno.run.base import BaseRunOutputEvent
|
|
16
|
+
from agno.run.team import RunCompletedEvent as TeamRunCompletedEvent
|
|
17
|
+
from agno.run.team import RunContentEvent as TeamRunContentEvent
|
|
15
18
|
from agno.run.team import TeamRunOutput
|
|
16
19
|
from agno.run.workflow import (
|
|
17
20
|
StepCompletedEvent,
|
|
@@ -179,31 +182,37 @@ class Step:
|
|
|
179
182
|
func: Callable,
|
|
180
183
|
step_input: StepInput,
|
|
181
184
|
session_state: Optional[Dict[str, Any]] = None,
|
|
185
|
+
run_context: Optional[RunContext] = None,
|
|
182
186
|
) -> Any:
|
|
183
187
|
"""Call custom function with session_state support if the function accepts it"""
|
|
188
|
+
|
|
189
|
+
kwargs: Dict[str, Any] = {}
|
|
190
|
+
if run_context is not None and self._function_has_run_context_param():
|
|
191
|
+
kwargs["run_context"] = run_context
|
|
184
192
|
if session_state is not None and self._function_has_session_state_param():
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
193
|
+
kwargs["session_state"] = session_state
|
|
194
|
+
|
|
195
|
+
return func(step_input, **kwargs)
|
|
188
196
|
|
|
189
197
|
async def _acall_custom_function(
|
|
190
198
|
self,
|
|
191
199
|
func: Callable,
|
|
192
200
|
step_input: StepInput,
|
|
193
201
|
session_state: Optional[Dict[str, Any]] = None,
|
|
202
|
+
run_context: Optional[RunContext] = None,
|
|
194
203
|
) -> Any:
|
|
195
204
|
"""Call custom async function with session_state support if the function accepts it"""
|
|
196
205
|
|
|
206
|
+
kwargs: Dict[str, Any] = {}
|
|
207
|
+
if run_context is not None and self._function_has_run_context_param():
|
|
208
|
+
kwargs["run_context"] = run_context
|
|
209
|
+
if session_state is not None and self._function_has_session_state_param():
|
|
210
|
+
kwargs["session_state"] = session_state
|
|
211
|
+
|
|
197
212
|
if _is_async_generator_function(func):
|
|
198
|
-
|
|
199
|
-
return func(step_input, session_state)
|
|
200
|
-
else:
|
|
201
|
-
return func(step_input)
|
|
213
|
+
return func(step_input, **kwargs)
|
|
202
214
|
else:
|
|
203
|
-
|
|
204
|
-
return await func(step_input, session_state)
|
|
205
|
-
else:
|
|
206
|
-
return await func(step_input)
|
|
215
|
+
return await func(step_input, **kwargs)
|
|
207
216
|
|
|
208
217
|
def execute(
|
|
209
218
|
self,
|
|
@@ -227,8 +236,10 @@ class Step:
|
|
|
227
236
|
if workflow_session:
|
|
228
237
|
step_input.workflow_session = workflow_session
|
|
229
238
|
|
|
239
|
+
# Create session_state copy once to avoid duplication.
|
|
240
|
+
# Consider both run_context.session_state and session_state.
|
|
230
241
|
if run_context is not None and run_context.session_state is not None:
|
|
231
|
-
session_state_copy =
|
|
242
|
+
session_state_copy = run_context.session_state
|
|
232
243
|
else:
|
|
233
244
|
session_state_copy = copy(session_state) if session_state is not None else {}
|
|
234
245
|
|
|
@@ -247,13 +258,13 @@ class Step:
|
|
|
247
258
|
self.active_executor,
|
|
248
259
|
step_input,
|
|
249
260
|
session_state_copy, # type: ignore[arg-type]
|
|
261
|
+
run_context,
|
|
250
262
|
): # type: ignore
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
content += chunk.content
|
|
263
|
+
if isinstance(chunk, (BaseRunOutputEvent)):
|
|
264
|
+
if isinstance(chunk, (RunContentEvent, TeamRunContentEvent)):
|
|
265
|
+
content += chunk.content if chunk.content is not None else ""
|
|
266
|
+
elif isinstance(chunk, (RunCompletedEvent, TeamRunCompletedEvent)):
|
|
267
|
+
content = chunk.content if chunk.content is not None else ""
|
|
257
268
|
else:
|
|
258
269
|
content += str(chunk)
|
|
259
270
|
if isinstance(chunk, StepOutput):
|
|
@@ -264,7 +275,7 @@ class Step:
|
|
|
264
275
|
final_response = e.value
|
|
265
276
|
|
|
266
277
|
# Merge session_state changes back
|
|
267
|
-
if session_state is not None:
|
|
278
|
+
if run_context is None and session_state is not None:
|
|
268
279
|
merge_dictionaries(session_state, session_state_copy)
|
|
269
280
|
|
|
270
281
|
if final_response is not None:
|
|
@@ -273,10 +284,15 @@ class Step:
|
|
|
273
284
|
response = StepOutput(content=content)
|
|
274
285
|
else:
|
|
275
286
|
# Execute function with signature inspection for session_state support
|
|
276
|
-
result = self._call_custom_function(
|
|
287
|
+
result = self._call_custom_function(
|
|
288
|
+
self.active_executor, # type: ignore[arg-type]
|
|
289
|
+
step_input,
|
|
290
|
+
session_state_copy,
|
|
291
|
+
run_context,
|
|
292
|
+
)
|
|
277
293
|
|
|
278
294
|
# Merge session_state changes back
|
|
279
|
-
if session_state is not None:
|
|
295
|
+
if run_context is None and session_state is not None:
|
|
280
296
|
merge_dictionaries(session_state, session_state_copy)
|
|
281
297
|
|
|
282
298
|
# If function returns StepOutput, use it directly
|
|
@@ -334,12 +350,13 @@ class Step:
|
|
|
334
350
|
session_id=session_id,
|
|
335
351
|
user_id=user_id,
|
|
336
352
|
session_state=session_state_copy, # Send a copy to the executor
|
|
353
|
+
run_context=run_context,
|
|
337
354
|
**kwargs,
|
|
338
355
|
)
|
|
339
356
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
merge_dictionaries(session_state, session_state_copy)
|
|
357
|
+
# Update workflow session state
|
|
358
|
+
if run_context is None and session_state is not None:
|
|
359
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
343
360
|
|
|
344
361
|
if store_executor_outputs and workflow_run_response is not None:
|
|
345
362
|
self._store_executor_response(workflow_run_response, response) # type: ignore
|
|
@@ -368,6 +385,17 @@ class Step:
|
|
|
368
385
|
|
|
369
386
|
return StepOutput(content=f"Step {self.name} failed but skipped", success=False)
|
|
370
387
|
|
|
388
|
+
def _function_has_run_context_param(self) -> bool:
|
|
389
|
+
"""Check if the custom function has a run_context parameter"""
|
|
390
|
+
if self._executor_type != "function":
|
|
391
|
+
return False
|
|
392
|
+
|
|
393
|
+
try:
|
|
394
|
+
sig = inspect.signature(self.active_executor) # type: ignore
|
|
395
|
+
return "run_context" in sig.parameters
|
|
396
|
+
except Exception:
|
|
397
|
+
return False
|
|
398
|
+
|
|
371
399
|
def _function_has_session_state_param(self) -> bool:
|
|
372
400
|
"""Check if the custom function has a session_state parameter"""
|
|
373
401
|
if self._executor_type != "function":
|
|
@@ -430,9 +458,10 @@ class Step:
|
|
|
430
458
|
if workflow_session:
|
|
431
459
|
step_input.workflow_session = workflow_session
|
|
432
460
|
|
|
433
|
-
# Create session_state copy once to avoid duplication
|
|
461
|
+
# Create session_state copy once to avoid duplication.
|
|
462
|
+
# Consider both run_context.session_state and session_state.
|
|
434
463
|
if run_context is not None and run_context.session_state is not None:
|
|
435
|
-
session_state_copy =
|
|
464
|
+
session_state_copy = run_context.session_state
|
|
436
465
|
else:
|
|
437
466
|
session_state_copy = copy(session_state) if session_state is not None else {}
|
|
438
467
|
|
|
@@ -460,22 +489,24 @@ class Step:
|
|
|
460
489
|
|
|
461
490
|
if self._executor_type == "function":
|
|
462
491
|
log_debug(f"Executing function executor for step: {self.name}")
|
|
463
|
-
|
|
464
492
|
if _is_async_callable(self.active_executor) or _is_async_generator_function(self.active_executor):
|
|
465
493
|
raise ValueError("Cannot use async function with synchronous execution")
|
|
466
|
-
|
|
467
494
|
if _is_generator_function(self.active_executor):
|
|
468
495
|
log_debug("Function returned iterable, streaming events")
|
|
469
496
|
content = ""
|
|
470
497
|
try:
|
|
471
|
-
iterator = self._call_custom_function(
|
|
498
|
+
iterator = self._call_custom_function(
|
|
499
|
+
self.active_executor,
|
|
500
|
+
step_input,
|
|
501
|
+
session_state_copy,
|
|
502
|
+
run_context,
|
|
503
|
+
)
|
|
472
504
|
for event in iterator: # type: ignore
|
|
473
|
-
if (
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
content += event.content
|
|
505
|
+
if isinstance(event, (BaseRunOutputEvent)):
|
|
506
|
+
if isinstance(event, (RunContentEvent, TeamRunContentEvent)):
|
|
507
|
+
content += event.content if event.content is not None else ""
|
|
508
|
+
elif isinstance(event, (RunCompletedEvent, TeamRunCompletedEvent)):
|
|
509
|
+
content = event.content if event.content is not None else ""
|
|
479
510
|
else:
|
|
480
511
|
content += str(event)
|
|
481
512
|
if isinstance(event, StepOutput):
|
|
@@ -491,7 +522,7 @@ class Step:
|
|
|
491
522
|
yield enriched_event # type: ignore[misc]
|
|
492
523
|
|
|
493
524
|
# Merge session_state changes back
|
|
494
|
-
if session_state is not None:
|
|
525
|
+
if run_context is None and session_state is not None:
|
|
495
526
|
merge_dictionaries(session_state, session_state_copy)
|
|
496
527
|
|
|
497
528
|
if not final_response:
|
|
@@ -501,10 +532,15 @@ class Step:
|
|
|
501
532
|
final_response = e.value
|
|
502
533
|
|
|
503
534
|
else:
|
|
504
|
-
result = self._call_custom_function(
|
|
535
|
+
result = self._call_custom_function(
|
|
536
|
+
self.active_executor, # type: ignore[arg-type]
|
|
537
|
+
step_input,
|
|
538
|
+
session_state_copy,
|
|
539
|
+
run_context,
|
|
540
|
+
)
|
|
505
541
|
|
|
506
542
|
# Merge session_state changes back
|
|
507
|
-
if session_state is not None:
|
|
543
|
+
if run_context is None and session_state is not None:
|
|
508
544
|
merge_dictionaries(session_state, session_state_copy)
|
|
509
545
|
|
|
510
546
|
if isinstance(result, StepOutput):
|
|
@@ -564,6 +600,7 @@ class Step:
|
|
|
564
600
|
stream=True,
|
|
565
601
|
stream_events=stream_events,
|
|
566
602
|
yield_run_response=True,
|
|
603
|
+
run_context=run_context,
|
|
567
604
|
**kwargs,
|
|
568
605
|
)
|
|
569
606
|
|
|
@@ -577,9 +614,9 @@ class Step:
|
|
|
577
614
|
if stream_executor_events:
|
|
578
615
|
yield enriched_event # type: ignore[misc]
|
|
579
616
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
merge_dictionaries(session_state, session_state_copy)
|
|
617
|
+
# Update workflow session state
|
|
618
|
+
if run_context is None and session_state is not None:
|
|
619
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
583
620
|
|
|
584
621
|
if store_executor_outputs and workflow_run_response is not None:
|
|
585
622
|
self._store_executor_response(workflow_run_response, active_executor_run_response) # type: ignore
|
|
@@ -640,6 +677,7 @@ class Step:
|
|
|
640
677
|
session_id: Optional[str] = None,
|
|
641
678
|
user_id: Optional[str] = None,
|
|
642
679
|
workflow_run_response: Optional["WorkflowRunOutput"] = None,
|
|
680
|
+
run_context: Optional[RunContext] = None,
|
|
643
681
|
session_state: Optional[Dict[str, Any]] = None,
|
|
644
682
|
store_executor_outputs: bool = True,
|
|
645
683
|
workflow_session: Optional["WorkflowSession"] = None,
|
|
@@ -655,8 +693,13 @@ class Step:
|
|
|
655
693
|
|
|
656
694
|
if workflow_session:
|
|
657
695
|
step_input.workflow_session = workflow_session
|
|
658
|
-
|
|
659
|
-
|
|
696
|
+
|
|
697
|
+
# Create session_state copy once to avoid duplication.
|
|
698
|
+
# Consider both run_context.session_state and session_state.
|
|
699
|
+
if run_context is not None and run_context.session_state is not None:
|
|
700
|
+
session_state_copy = run_context.session_state
|
|
701
|
+
else:
|
|
702
|
+
session_state_copy = copy(session_state) if session_state is not None else {}
|
|
660
703
|
|
|
661
704
|
# Execute with retries
|
|
662
705
|
for attempt in range(self.max_retries + 1):
|
|
@@ -672,15 +715,15 @@ class Step:
|
|
|
672
715
|
iterator = self._call_custom_function(
|
|
673
716
|
self.active_executor,
|
|
674
717
|
step_input,
|
|
675
|
-
session_state_copy,
|
|
676
|
-
|
|
718
|
+
session_state_copy,
|
|
719
|
+
run_context,
|
|
720
|
+
)
|
|
677
721
|
for chunk in iterator: # type: ignore
|
|
678
|
-
if (
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
content += chunk.content
|
|
722
|
+
if isinstance(chunk, (BaseRunOutputEvent)):
|
|
723
|
+
if isinstance(chunk, (RunContentEvent, TeamRunContentEvent)):
|
|
724
|
+
content += chunk.content if chunk.content is not None else ""
|
|
725
|
+
elif isinstance(chunk, (RunCompletedEvent, TeamRunCompletedEvent)):
|
|
726
|
+
content = chunk.content if chunk.content is not None else ""
|
|
684
727
|
else:
|
|
685
728
|
content += str(chunk)
|
|
686
729
|
if isinstance(chunk, StepOutput):
|
|
@@ -690,15 +733,15 @@ class Step:
|
|
|
690
733
|
iterator = await self._acall_custom_function(
|
|
691
734
|
self.active_executor,
|
|
692
735
|
step_input,
|
|
693
|
-
session_state_copy,
|
|
694
|
-
|
|
736
|
+
session_state_copy,
|
|
737
|
+
run_context,
|
|
738
|
+
)
|
|
695
739
|
async for chunk in iterator: # type: ignore
|
|
696
|
-
if (
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
content += chunk.content
|
|
740
|
+
if isinstance(chunk, (BaseRunOutputEvent)):
|
|
741
|
+
if isinstance(chunk, (RunContentEvent, TeamRunContentEvent)):
|
|
742
|
+
content += chunk.content if chunk.content is not None else ""
|
|
743
|
+
elif isinstance(chunk, (RunCompletedEvent, TeamRunCompletedEvent)):
|
|
744
|
+
content = chunk.content if chunk.content is not None else ""
|
|
702
745
|
else:
|
|
703
746
|
content += str(chunk)
|
|
704
747
|
if isinstance(chunk, StepOutput):
|
|
@@ -709,7 +752,7 @@ class Step:
|
|
|
709
752
|
final_response = e.value
|
|
710
753
|
|
|
711
754
|
# Merge session_state changes back
|
|
712
|
-
if session_state is not None:
|
|
755
|
+
if run_context is None and session_state is not None:
|
|
713
756
|
merge_dictionaries(session_state, session_state_copy)
|
|
714
757
|
|
|
715
758
|
if final_response is not None:
|
|
@@ -719,13 +762,21 @@ class Step:
|
|
|
719
762
|
else:
|
|
720
763
|
if _is_async_callable(self.active_executor):
|
|
721
764
|
result = await self._acall_custom_function(
|
|
722
|
-
self.active_executor,
|
|
723
|
-
|
|
765
|
+
self.active_executor,
|
|
766
|
+
step_input,
|
|
767
|
+
session_state_copy,
|
|
768
|
+
run_context,
|
|
769
|
+
)
|
|
724
770
|
else:
|
|
725
|
-
result = self._call_custom_function(
|
|
771
|
+
result = self._call_custom_function(
|
|
772
|
+
self.active_executor, # type: ignore[arg-type]
|
|
773
|
+
step_input,
|
|
774
|
+
session_state_copy,
|
|
775
|
+
run_context,
|
|
776
|
+
)
|
|
726
777
|
|
|
727
778
|
# Merge session_state changes back
|
|
728
|
-
if session_state is not None:
|
|
779
|
+
if run_context is None and session_state is not None:
|
|
729
780
|
merge_dictionaries(session_state, session_state_copy)
|
|
730
781
|
|
|
731
782
|
# If function returns StepOutput, use it directly
|
|
@@ -784,12 +835,13 @@ class Step:
|
|
|
784
835
|
session_id=session_id,
|
|
785
836
|
user_id=user_id,
|
|
786
837
|
session_state=session_state_copy,
|
|
838
|
+
run_context=run_context,
|
|
787
839
|
**kwargs,
|
|
788
840
|
)
|
|
789
841
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
merge_dictionaries(session_state, session_state_copy)
|
|
842
|
+
# Update workflow session state
|
|
843
|
+
if run_context is None and session_state is not None:
|
|
844
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
793
845
|
|
|
794
846
|
if store_executor_outputs and workflow_run_response is not None:
|
|
795
847
|
self._store_executor_response(workflow_run_response, response) # type: ignore
|
|
@@ -827,6 +879,7 @@ class Step:
|
|
|
827
879
|
stream_intermediate_steps: bool = False,
|
|
828
880
|
stream_executor_events: bool = True,
|
|
829
881
|
workflow_run_response: Optional["WorkflowRunOutput"] = None,
|
|
882
|
+
run_context: Optional[RunContext] = None,
|
|
830
883
|
session_state: Optional[Dict[str, Any]] = None,
|
|
831
884
|
step_index: Optional[Union[int, tuple]] = None,
|
|
832
885
|
store_executor_outputs: bool = True,
|
|
@@ -843,8 +896,12 @@ class Step:
|
|
|
843
896
|
if workflow_session:
|
|
844
897
|
step_input.workflow_session = workflow_session
|
|
845
898
|
|
|
846
|
-
# Create session_state copy once to avoid duplication
|
|
847
|
-
|
|
899
|
+
# Create session_state copy once to avoid duplication.
|
|
900
|
+
# Consider both run_context.session_state and session_state.
|
|
901
|
+
if run_context is not None and run_context.session_state is not None:
|
|
902
|
+
session_state_copy = run_context.session_state
|
|
903
|
+
else:
|
|
904
|
+
session_state_copy = copy(session_state) if session_state is not None else {}
|
|
848
905
|
|
|
849
906
|
# Considering both stream_events and stream_intermediate_steps (deprecated)
|
|
850
907
|
stream_events = stream_events or stream_intermediate_steps
|
|
@@ -878,15 +935,15 @@ class Step:
|
|
|
878
935
|
iterator = await self._acall_custom_function(
|
|
879
936
|
self.active_executor,
|
|
880
937
|
step_input,
|
|
881
|
-
session_state_copy,
|
|
882
|
-
|
|
938
|
+
session_state_copy,
|
|
939
|
+
run_context,
|
|
940
|
+
)
|
|
883
941
|
async for event in iterator: # type: ignore
|
|
884
|
-
if (
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
content += event.content
|
|
942
|
+
if isinstance(event, (BaseRunOutputEvent)):
|
|
943
|
+
if isinstance(event, (RunContentEvent, TeamRunContentEvent)):
|
|
944
|
+
content += event.content if event.content is not None else ""
|
|
945
|
+
elif isinstance(event, (RunCompletedEvent, TeamRunCompletedEvent)):
|
|
946
|
+
content = event.content if event.content is not None else ""
|
|
890
947
|
else:
|
|
891
948
|
content += str(event)
|
|
892
949
|
if isinstance(event, StepOutput):
|
|
@@ -904,7 +961,12 @@ class Step:
|
|
|
904
961
|
final_response = StepOutput(content=content)
|
|
905
962
|
elif _is_async_callable(self.active_executor):
|
|
906
963
|
# It's a regular async function - await it
|
|
907
|
-
result = await self._acall_custom_function(
|
|
964
|
+
result = await self._acall_custom_function(
|
|
965
|
+
self.active_executor,
|
|
966
|
+
step_input,
|
|
967
|
+
session_state_copy,
|
|
968
|
+
run_context,
|
|
969
|
+
)
|
|
908
970
|
if isinstance(result, StepOutput):
|
|
909
971
|
final_response = result
|
|
910
972
|
else:
|
|
@@ -912,14 +974,18 @@ class Step:
|
|
|
912
974
|
elif _is_generator_function(self.active_executor):
|
|
913
975
|
content = ""
|
|
914
976
|
# It's a regular generator function - iterate over it
|
|
915
|
-
iterator = self._call_custom_function(
|
|
977
|
+
iterator = self._call_custom_function(
|
|
978
|
+
self.active_executor,
|
|
979
|
+
step_input,
|
|
980
|
+
session_state_copy,
|
|
981
|
+
run_context,
|
|
982
|
+
)
|
|
916
983
|
for event in iterator: # type: ignore
|
|
917
|
-
if (
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
content += event.content
|
|
984
|
+
if isinstance(event, (BaseRunOutputEvent)):
|
|
985
|
+
if isinstance(event, (RunContentEvent, TeamRunContentEvent)):
|
|
986
|
+
content += event.content if event.content is not None else ""
|
|
987
|
+
elif isinstance(event, (RunCompletedEvent, TeamRunCompletedEvent)):
|
|
988
|
+
content = event.content if event.content is not None else ""
|
|
923
989
|
else:
|
|
924
990
|
content += str(event)
|
|
925
991
|
if isinstance(event, StepOutput):
|
|
@@ -937,14 +1003,19 @@ class Step:
|
|
|
937
1003
|
final_response = StepOutput(content=content)
|
|
938
1004
|
else:
|
|
939
1005
|
# It's a regular function - call it directly
|
|
940
|
-
result = self._call_custom_function(
|
|
1006
|
+
result = self._call_custom_function(
|
|
1007
|
+
self.active_executor, # type: ignore[arg-type]
|
|
1008
|
+
step_input,
|
|
1009
|
+
session_state_copy,
|
|
1010
|
+
run_context,
|
|
1011
|
+
)
|
|
941
1012
|
if isinstance(result, StepOutput):
|
|
942
1013
|
final_response = result
|
|
943
1014
|
else:
|
|
944
1015
|
final_response = StepOutput(content=str(result))
|
|
945
1016
|
|
|
946
1017
|
# Merge session_state changes back
|
|
947
|
-
if session_state is not None:
|
|
1018
|
+
if run_context is None and session_state is not None:
|
|
948
1019
|
merge_dictionaries(session_state, session_state_copy)
|
|
949
1020
|
else:
|
|
950
1021
|
# For agents and teams, prepare message with context
|
|
@@ -997,6 +1068,7 @@ class Step:
|
|
|
997
1068
|
session_state=session_state_copy,
|
|
998
1069
|
stream=True,
|
|
999
1070
|
stream_events=stream_events,
|
|
1071
|
+
run_context=run_context,
|
|
1000
1072
|
yield_run_response=True,
|
|
1001
1073
|
**kwargs,
|
|
1002
1074
|
)
|
|
@@ -1011,9 +1083,9 @@ class Step:
|
|
|
1011
1083
|
if stream_executor_events:
|
|
1012
1084
|
yield enriched_event # type: ignore[misc]
|
|
1013
1085
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
merge_dictionaries(session_state, session_state_copy)
|
|
1086
|
+
# Update workflow session state
|
|
1087
|
+
if run_context is None and session_state is not None:
|
|
1088
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
1017
1089
|
|
|
1018
1090
|
if store_executor_outputs and workflow_run_response is not None:
|
|
1019
1091
|
self._store_executor_response(workflow_run_response, active_executor_run_response) # type: ignore
|
agno/workflow/steps.py
CHANGED
|
@@ -3,6 +3,7 @@ from typing import Any, AsyncIterator, Awaitable, Callable, Dict, Iterator, List
|
|
|
3
3
|
from uuid import uuid4
|
|
4
4
|
|
|
5
5
|
from agno.run.agent import RunOutputEvent
|
|
6
|
+
from agno.run.base import RunContext
|
|
6
7
|
from agno.run.team import TeamRunOutputEvent
|
|
7
8
|
from agno.run.workflow import (
|
|
8
9
|
StepsExecutionCompletedEvent,
|
|
@@ -118,6 +119,7 @@ class Steps:
|
|
|
118
119
|
session_id: Optional[str] = None,
|
|
119
120
|
user_id: Optional[str] = None,
|
|
120
121
|
workflow_run_response: Optional[WorkflowRunOutput] = None,
|
|
122
|
+
run_context: Optional[RunContext] = None,
|
|
121
123
|
session_state: Optional[Dict[str, Any]] = None,
|
|
122
124
|
store_executor_outputs: bool = True,
|
|
123
125
|
workflow_session: Optional[WorkflowSession] = None,
|
|
@@ -151,6 +153,7 @@ class Steps:
|
|
|
151
153
|
user_id=user_id,
|
|
152
154
|
workflow_run_response=workflow_run_response,
|
|
153
155
|
store_executor_outputs=store_executor_outputs,
|
|
156
|
+
run_context=run_context,
|
|
154
157
|
session_state=session_state,
|
|
155
158
|
workflow_session=workflow_session,
|
|
156
159
|
add_workflow_history_to_steps=add_workflow_history_to_steps,
|
|
@@ -204,6 +207,7 @@ class Steps:
|
|
|
204
207
|
self,
|
|
205
208
|
step_input: StepInput,
|
|
206
209
|
workflow_run_response: WorkflowRunOutput,
|
|
210
|
+
run_context: Optional[RunContext] = None,
|
|
207
211
|
session_state: Optional[Dict[str, Any]] = None,
|
|
208
212
|
session_id: Optional[str] = None,
|
|
209
213
|
user_id: Optional[str] = None,
|
|
@@ -269,6 +273,7 @@ class Steps:
|
|
|
269
273
|
current_step_input,
|
|
270
274
|
session_id=session_id,
|
|
271
275
|
user_id=user_id,
|
|
276
|
+
run_context=run_context,
|
|
272
277
|
session_state=session_state,
|
|
273
278
|
stream_events=stream_events,
|
|
274
279
|
stream_executor_events=stream_executor_events,
|
|
@@ -354,6 +359,7 @@ class Steps:
|
|
|
354
359
|
session_id: Optional[str] = None,
|
|
355
360
|
user_id: Optional[str] = None,
|
|
356
361
|
workflow_run_response: Optional[WorkflowRunOutput] = None,
|
|
362
|
+
run_context: Optional[RunContext] = None,
|
|
357
363
|
session_state: Optional[Dict[str, Any]] = None,
|
|
358
364
|
store_executor_outputs: bool = True,
|
|
359
365
|
workflow_session: Optional[WorkflowSession] = None,
|
|
@@ -387,6 +393,7 @@ class Steps:
|
|
|
387
393
|
user_id=user_id,
|
|
388
394
|
workflow_run_response=workflow_run_response,
|
|
389
395
|
store_executor_outputs=store_executor_outputs,
|
|
396
|
+
run_context=run_context,
|
|
390
397
|
session_state=session_state,
|
|
391
398
|
workflow_session=workflow_session,
|
|
392
399
|
add_workflow_history_to_steps=add_workflow_history_to_steps,
|
|
@@ -439,6 +446,7 @@ class Steps:
|
|
|
439
446
|
self,
|
|
440
447
|
step_input: StepInput,
|
|
441
448
|
workflow_run_response: WorkflowRunOutput,
|
|
449
|
+
run_context: Optional[RunContext] = None,
|
|
442
450
|
session_state: Optional[Dict[str, Any]] = None,
|
|
443
451
|
session_id: Optional[str] = None,
|
|
444
452
|
user_id: Optional[str] = None,
|
|
@@ -504,6 +512,7 @@ class Steps:
|
|
|
504
512
|
current_step_input,
|
|
505
513
|
session_id=session_id,
|
|
506
514
|
user_id=user_id,
|
|
515
|
+
run_context=run_context,
|
|
507
516
|
session_state=session_state,
|
|
508
517
|
stream_events=stream_events,
|
|
509
518
|
stream_executor_events=stream_executor_events,
|
agno/workflow/types.py
CHANGED
|
@@ -17,6 +17,7 @@ from agno.utils.media import (
|
|
|
17
17
|
reconstruct_videos,
|
|
18
18
|
)
|
|
19
19
|
from agno.utils.serialize import json_serializer
|
|
20
|
+
from agno.utils.timer import Timer
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
@dataclass
|
|
@@ -405,12 +406,18 @@ class WorkflowMetrics:
|
|
|
405
406
|
"""Complete metrics for a workflow execution"""
|
|
406
407
|
|
|
407
408
|
steps: Dict[str, StepMetrics]
|
|
409
|
+
# Timer utility for tracking execution time
|
|
410
|
+
timer: Optional[Timer] = None
|
|
411
|
+
# Total workflow execution time
|
|
412
|
+
duration: Optional[float] = None
|
|
408
413
|
|
|
409
414
|
def to_dict(self) -> Dict[str, Any]:
|
|
410
415
|
"""Convert to dictionary"""
|
|
411
|
-
|
|
416
|
+
result: Dict[str, Any] = {
|
|
412
417
|
"steps": {name: step.to_dict() for name, step in self.steps.items()},
|
|
418
|
+
"duration": self.duration,
|
|
413
419
|
}
|
|
420
|
+
return result
|
|
414
421
|
|
|
415
422
|
@classmethod
|
|
416
423
|
def from_dict(cls, data: Dict[str, Any]) -> "WorkflowMetrics":
|
|
@@ -419,8 +426,20 @@ class WorkflowMetrics:
|
|
|
419
426
|
|
|
420
427
|
return cls(
|
|
421
428
|
steps=steps,
|
|
429
|
+
duration=data.get("duration"),
|
|
422
430
|
)
|
|
423
431
|
|
|
432
|
+
def start_timer(self):
|
|
433
|
+
if self.timer is None:
|
|
434
|
+
self.timer = Timer()
|
|
435
|
+
self.timer.start()
|
|
436
|
+
|
|
437
|
+
def stop_timer(self, set_duration: bool = True):
|
|
438
|
+
if self.timer is not None:
|
|
439
|
+
self.timer.stop()
|
|
440
|
+
if set_duration:
|
|
441
|
+
self.duration = self.timer.elapsed
|
|
442
|
+
|
|
424
443
|
|
|
425
444
|
@dataclass
|
|
426
445
|
class WebSocketHandler:
|