agno 2.3.3__py3-none-any.whl → 2.3.5__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 (108) hide show
  1. agno/agent/agent.py +177 -41
  2. agno/culture/manager.py +2 -2
  3. agno/db/base.py +330 -8
  4. agno/db/dynamo/dynamo.py +722 -2
  5. agno/db/dynamo/schemas.py +127 -0
  6. agno/db/firestore/firestore.py +573 -1
  7. agno/db/firestore/schemas.py +40 -0
  8. agno/db/gcs_json/gcs_json_db.py +446 -1
  9. agno/db/in_memory/in_memory_db.py +143 -1
  10. agno/db/json/json_db.py +438 -1
  11. agno/db/mongo/async_mongo.py +522 -0
  12. agno/db/mongo/mongo.py +523 -1
  13. agno/db/mongo/schemas.py +29 -0
  14. agno/db/mysql/mysql.py +536 -3
  15. agno/db/mysql/schemas.py +38 -0
  16. agno/db/postgres/async_postgres.py +546 -14
  17. agno/db/postgres/postgres.py +535 -2
  18. agno/db/postgres/schemas.py +38 -0
  19. agno/db/redis/redis.py +468 -1
  20. agno/db/redis/schemas.py +32 -0
  21. agno/db/singlestore/schemas.py +38 -0
  22. agno/db/singlestore/singlestore.py +523 -1
  23. agno/db/sqlite/async_sqlite.py +548 -9
  24. agno/db/sqlite/schemas.py +38 -0
  25. agno/db/sqlite/sqlite.py +537 -5
  26. agno/db/sqlite/utils.py +6 -8
  27. agno/db/surrealdb/models.py +25 -0
  28. agno/db/surrealdb/surrealdb.py +548 -1
  29. agno/eval/accuracy.py +10 -4
  30. agno/eval/performance.py +10 -4
  31. agno/eval/reliability.py +22 -13
  32. agno/exceptions.py +11 -0
  33. agno/hooks/__init__.py +3 -0
  34. agno/hooks/decorator.py +164 -0
  35. agno/knowledge/chunking/semantic.py +2 -2
  36. agno/models/aimlapi/aimlapi.py +17 -0
  37. agno/models/anthropic/claude.py +19 -12
  38. agno/models/aws/bedrock.py +3 -4
  39. agno/models/aws/claude.py +5 -1
  40. agno/models/azure/ai_foundry.py +2 -2
  41. agno/models/azure/openai_chat.py +8 -0
  42. agno/models/cerebras/cerebras.py +61 -4
  43. agno/models/cerebras/cerebras_openai.py +17 -0
  44. agno/models/cohere/chat.py +5 -1
  45. agno/models/cometapi/cometapi.py +18 -1
  46. agno/models/dashscope/dashscope.py +2 -3
  47. agno/models/deepinfra/deepinfra.py +18 -1
  48. agno/models/deepseek/deepseek.py +2 -3
  49. agno/models/fireworks/fireworks.py +18 -1
  50. agno/models/google/gemini.py +8 -2
  51. agno/models/groq/groq.py +5 -2
  52. agno/models/internlm/internlm.py +18 -1
  53. agno/models/langdb/langdb.py +13 -1
  54. agno/models/litellm/chat.py +2 -2
  55. agno/models/litellm/litellm_openai.py +18 -1
  56. agno/models/meta/llama_openai.py +19 -2
  57. agno/models/nebius/nebius.py +2 -3
  58. agno/models/nvidia/nvidia.py +20 -3
  59. agno/models/openai/chat.py +17 -2
  60. agno/models/openai/responses.py +17 -2
  61. agno/models/openrouter/openrouter.py +21 -2
  62. agno/models/perplexity/perplexity.py +17 -1
  63. agno/models/portkey/portkey.py +7 -6
  64. agno/models/requesty/requesty.py +19 -2
  65. agno/models/response.py +2 -1
  66. agno/models/sambanova/sambanova.py +20 -3
  67. agno/models/siliconflow/siliconflow.py +19 -2
  68. agno/models/together/together.py +20 -3
  69. agno/models/vercel/v0.py +20 -3
  70. agno/models/vllm/vllm.py +19 -14
  71. agno/models/xai/xai.py +19 -2
  72. agno/os/app.py +104 -0
  73. agno/os/config.py +13 -0
  74. agno/os/interfaces/whatsapp/router.py +0 -1
  75. agno/os/mcp.py +1 -0
  76. agno/os/router.py +31 -0
  77. agno/os/routers/traces/__init__.py +3 -0
  78. agno/os/routers/traces/schemas.py +414 -0
  79. agno/os/routers/traces/traces.py +499 -0
  80. agno/os/schema.py +22 -1
  81. agno/os/utils.py +57 -0
  82. agno/run/agent.py +1 -0
  83. agno/run/base.py +17 -0
  84. agno/run/team.py +4 -0
  85. agno/session/team.py +1 -0
  86. agno/table.py +10 -0
  87. agno/team/team.py +215 -65
  88. agno/tools/function.py +10 -8
  89. agno/tools/nano_banana.py +1 -1
  90. agno/tracing/__init__.py +12 -0
  91. agno/tracing/exporter.py +157 -0
  92. agno/tracing/schemas.py +276 -0
  93. agno/tracing/setup.py +111 -0
  94. agno/utils/agent.py +4 -4
  95. agno/utils/hooks.py +56 -1
  96. agno/vectordb/qdrant/qdrant.py +22 -22
  97. agno/workflow/condition.py +8 -0
  98. agno/workflow/loop.py +8 -0
  99. agno/workflow/parallel.py +8 -0
  100. agno/workflow/router.py +8 -0
  101. agno/workflow/step.py +20 -0
  102. agno/workflow/steps.py +8 -0
  103. agno/workflow/workflow.py +83 -17
  104. {agno-2.3.3.dist-info → agno-2.3.5.dist-info}/METADATA +2 -2
  105. {agno-2.3.3.dist-info → agno-2.3.5.dist-info}/RECORD +108 -98
  106. {agno-2.3.3.dist-info → agno-2.3.5.dist-info}/WHEEL +0 -0
  107. {agno-2.3.3.dist-info → agno-2.3.5.dist-info}/licenses/LICENSE +0 -0
  108. {agno-2.3.3.dist-info → agno-2.3.5.dist-info}/top_level.txt +0 -0
agno/utils/hooks.py CHANGED
@@ -1,14 +1,69 @@
1
+ from copy import deepcopy
1
2
  from typing import Any, Callable, Dict, List, Optional, Union
2
3
 
3
4
  from agno.guardrails.base import BaseGuardrail
5
+ from agno.hooks.decorator import HOOK_RUN_IN_BACKGROUND_ATTR
4
6
  from agno.utils.log import log_warning
5
7
 
8
+ # Keys that should be deep copied for background hooks to prevent race conditions
9
+ BACKGROUND_HOOK_COPY_KEYS = frozenset(
10
+ {"run_input", "run_context", "run_output", "session_state", "dependencies", "metadata"}
11
+ )
12
+
13
+
14
+ def copy_args_for_background(args: Dict[str, Any]) -> Dict[str, Any]:
15
+ """
16
+ Create a copy of hook arguments for background execution.
17
+
18
+ This deep copies run_input, run_context, run_output, session_state, dependencies,
19
+ and metadata to prevent race conditions when hooks run in the background.
20
+
21
+ Args:
22
+ args: The original arguments dictionary
23
+
24
+ Returns:
25
+ A new dictionary with copied values for sensitive keys
26
+ """
27
+ copied_args = {}
28
+ for key, value in args.items():
29
+ if key in BACKGROUND_HOOK_COPY_KEYS and value is not None:
30
+ try:
31
+ copied_args[key] = deepcopy(value)
32
+ except Exception:
33
+ # If deepcopy fails (e.g., for non-copyable objects), use the original
34
+ log_warning(f"Could not deepcopy {key} for background hook, using original reference")
35
+ copied_args[key] = value
36
+ else:
37
+ copied_args[key] = value
38
+ return copied_args
39
+
40
+
41
+ def should_run_hook_in_background(hook: Callable[..., Any]) -> bool:
42
+ """
43
+ Check if a hook function should run in background.
44
+
45
+ This checks for the _agno_run_in_background attribute set by the @hook decorator.
46
+
47
+ Args:
48
+ hook: The hook function to check
49
+
50
+ Returns:
51
+ True if the hook is decorated with @hook(run_in_background=True)
52
+ """
53
+ return getattr(hook, HOOK_RUN_IN_BACKGROUND_ATTR, False)
54
+
6
55
 
7
56
  def normalize_hooks(
8
57
  hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail]]],
9
58
  async_mode: bool = False,
10
59
  ) -> Optional[List[Callable[..., Any]]]:
11
- """Normalize hooks to a list format"""
60
+ """Normalize hooks to a list format
61
+
62
+ Args:
63
+ hooks: List of hook functions or hook instances
64
+ async_mode: Whether to use async versions of methods
65
+ """
66
+
12
67
  result_hooks: List[Callable[..., Any]] = []
13
68
 
14
69
  if hooks is not None:
@@ -545,13 +545,13 @@ class Qdrant(VectorDb):
545
545
  log_warning("Filters Expressions are not supported in Qdrant. No filters will be applied.")
546
546
  filters = None
547
547
 
548
- filters = self._format_filters(filters or {}) # type: ignore
548
+ formatted_filters = self._format_filters(filters or {}) # type: ignore
549
549
  if self.search_type == SearchType.vector:
550
- results = self._run_vector_search_sync(query, limit, filters) # type: ignore
550
+ results = self._run_vector_search_sync(query, limit, formatted_filters=formatted_filters) # type: ignore
551
551
  elif self.search_type == SearchType.keyword:
552
- results = self._run_keyword_search_sync(query, limit, filters) # type: ignore
552
+ results = self._run_keyword_search_sync(query, limit, formatted_filters=formatted_filters) # type: ignore
553
553
  elif self.search_type == SearchType.hybrid:
554
- results = self._run_hybrid_search_sync(query, limit, filters) # type: ignore
554
+ results = self._run_hybrid_search_sync(query, limit, formatted_filters=formatted_filters) # type: ignore
555
555
  else:
556
556
  raise ValueError(f"Unsupported search type: {self.search_type}")
557
557
 
@@ -564,13 +564,13 @@ class Qdrant(VectorDb):
564
564
  log_warning("Filters Expressions are not supported in Qdrant. No filters will be applied.")
565
565
  filters = None
566
566
 
567
- filters = self._format_filters(filters or {}) # type: ignore
567
+ formatted_filters = self._format_filters(filters or {}) # type: ignore
568
568
  if self.search_type == SearchType.vector:
569
- results = await self._run_vector_search_async(query, limit, filters) # type: ignore
569
+ results = await self._run_vector_search_async(query, limit, formatted_filters=formatted_filters) # type: ignore
570
570
  elif self.search_type == SearchType.keyword:
571
- results = await self._run_keyword_search_async(query, limit, filters) # type: ignore
571
+ results = await self._run_keyword_search_async(query, limit, formatted_filters=formatted_filters) # type: ignore
572
572
  elif self.search_type == SearchType.hybrid:
573
- results = await self._run_hybrid_search_async(query, limit, filters) # type: ignore
573
+ results = await self._run_hybrid_search_async(query, limit, formatted_filters=formatted_filters) # type: ignore
574
574
  else:
575
575
  raise ValueError(f"Unsupported search type: {self.search_type}")
576
576
 
@@ -580,7 +580,7 @@ class Qdrant(VectorDb):
580
580
  self,
581
581
  query: str,
582
582
  limit: int,
583
- filters: Optional[Union[Dict[str, Any], List[FilterExpr]]],
583
+ formatted_filters: Optional[models.Filter],
584
584
  ) -> List[models.ScoredPoint]:
585
585
  dense_embedding = self.embedder.get_embedding(query)
586
586
  sparse_embedding = next(iter(self.sparse_encoder.embed([query]))).as_object()
@@ -598,7 +598,7 @@ class Qdrant(VectorDb):
598
598
  with_vectors=True,
599
599
  with_payload=True,
600
600
  limit=limit,
601
- query_filter=filters,
601
+ query_filter=formatted_filters,
602
602
  )
603
603
  return call.points
604
604
 
@@ -606,7 +606,7 @@ class Qdrant(VectorDb):
606
606
  self,
607
607
  query: str,
608
608
  limit: int,
609
- filters: Optional[Union[Dict[str, Any], List[FilterExpr]]],
609
+ formatted_filters: Optional[models.Filter],
610
610
  ) -> List[models.ScoredPoint]:
611
611
  dense_embedding = self.embedder.get_embedding(query)
612
612
 
@@ -618,7 +618,7 @@ class Qdrant(VectorDb):
618
618
  with_vectors=True,
619
619
  with_payload=True,
620
620
  limit=limit,
621
- query_filter=filters,
621
+ query_filter=formatted_filters,
622
622
  using=self.dense_vector_name,
623
623
  )
624
624
  else:
@@ -629,7 +629,7 @@ class Qdrant(VectorDb):
629
629
  with_vectors=True,
630
630
  with_payload=True,
631
631
  limit=limit,
632
- query_filter=filters,
632
+ query_filter=formatted_filters,
633
633
  )
634
634
  return call.points
635
635
 
@@ -637,7 +637,7 @@ class Qdrant(VectorDb):
637
637
  self,
638
638
  query: str,
639
639
  limit: int,
640
- filters: Optional[Union[Dict[str, Any], List[FilterExpr]]],
640
+ formatted_filters: Optional[models.Filter],
641
641
  ) -> List[models.ScoredPoint]:
642
642
  sparse_embedding = next(iter(self.sparse_encoder.embed([query]))).as_object()
643
643
  call = self.client.query_points(
@@ -647,7 +647,7 @@ class Qdrant(VectorDb):
647
647
  with_payload=True,
648
648
  limit=limit,
649
649
  using=self.sparse_vector_name,
650
- query_filter=filters,
650
+ query_filter=formatted_filters,
651
651
  )
652
652
  return call.points
653
653
 
@@ -655,7 +655,7 @@ class Qdrant(VectorDb):
655
655
  self,
656
656
  query: str,
657
657
  limit: int,
658
- filters: Optional[Dict[str, Any]],
658
+ formatted_filters: Optional[models.Filter],
659
659
  ) -> List[models.ScoredPoint]:
660
660
  dense_embedding = self.embedder.get_embedding(query)
661
661
 
@@ -667,7 +667,7 @@ class Qdrant(VectorDb):
667
667
  with_vectors=True,
668
668
  with_payload=True,
669
669
  limit=limit,
670
- query_filter=filters,
670
+ query_filter=formatted_filters,
671
671
  using=self.dense_vector_name,
672
672
  )
673
673
  else:
@@ -678,7 +678,7 @@ class Qdrant(VectorDb):
678
678
  with_vectors=True,
679
679
  with_payload=True,
680
680
  limit=limit,
681
- query_filter=filters,
681
+ query_filter=formatted_filters,
682
682
  )
683
683
  return call.points
684
684
 
@@ -686,7 +686,7 @@ class Qdrant(VectorDb):
686
686
  self,
687
687
  query: str,
688
688
  limit: int,
689
- filters: Optional[Dict[str, Any]],
689
+ formatted_filters: Optional[models.Filter],
690
690
  ) -> List[models.ScoredPoint]:
691
691
  sparse_embedding = next(iter(self.sparse_encoder.embed([query]))).as_object()
692
692
  call = await self.async_client.query_points(
@@ -696,7 +696,7 @@ class Qdrant(VectorDb):
696
696
  with_payload=True,
697
697
  limit=limit,
698
698
  using=self.sparse_vector_name,
699
- query_filter=filters,
699
+ query_filter=formatted_filters,
700
700
  )
701
701
  return call.points
702
702
 
@@ -704,7 +704,7 @@ class Qdrant(VectorDb):
704
704
  self,
705
705
  query: str,
706
706
  limit: int,
707
- filters: Optional[Union[Dict[str, Any], List[FilterExpr]]],
707
+ formatted_filters: Optional[models.Filter],
708
708
  ) -> List[models.ScoredPoint]:
709
709
  dense_embedding = self.embedder.get_embedding(query)
710
710
  sparse_embedding = next(iter(self.sparse_encoder.embed([query]))).as_object()
@@ -722,7 +722,7 @@ class Qdrant(VectorDb):
722
722
  with_vectors=True,
723
723
  with_payload=True,
724
724
  limit=limit,
725
- query_filter=filters,
725
+ query_filter=formatted_filters,
726
726
  )
727
727
  return call.points
728
728
 
@@ -182,6 +182,7 @@ class Condition:
182
182
  workflow_session: Optional[WorkflowSession] = None,
183
183
  add_workflow_history_to_steps: Optional[bool] = False,
184
184
  num_history_runs: int = 3,
185
+ background_tasks: Optional[Any] = None,
185
186
  ) -> StepOutput:
186
187
  """Execute the condition and its steps with sequential chaining if condition is true"""
187
188
  log_debug(f"Condition Start: {self.name}", center=True, symbol="-")
@@ -226,6 +227,7 @@ class Condition:
226
227
  workflow_session=workflow_session,
227
228
  add_workflow_history_to_steps=add_workflow_history_to_steps,
228
229
  num_history_runs=num_history_runs,
230
+ background_tasks=background_tasks,
229
231
  )
230
232
 
231
233
  # Handle both single StepOutput and List[StepOutput] (from Loop/Condition/Router steps)
@@ -298,6 +300,7 @@ class Condition:
298
300
  workflow_session: Optional[WorkflowSession] = None,
299
301
  add_workflow_history_to_steps: Optional[bool] = False,
300
302
  num_history_runs: int = 3,
303
+ background_tasks: Optional[Any] = None,
301
304
  ) -> Iterator[Union[WorkflowRunOutputEvent, StepOutput]]:
302
305
  """Execute the condition with streaming support - mirrors Loop logic"""
303
306
  log_debug(f"Condition Start: {self.name}", center=True, symbol="-")
@@ -387,6 +390,7 @@ class Condition:
387
390
  workflow_session=workflow_session,
388
391
  add_workflow_history_to_steps=add_workflow_history_to_steps,
389
392
  num_history_runs=num_history_runs,
393
+ background_tasks=background_tasks,
390
394
  ):
391
395
  if isinstance(event, StepOutput):
392
396
  step_outputs_for_step.append(event)
@@ -471,6 +475,7 @@ class Condition:
471
475
  workflow_session: Optional[WorkflowSession] = None,
472
476
  add_workflow_history_to_steps: Optional[bool] = False,
473
477
  num_history_runs: int = 3,
478
+ background_tasks: Optional[Any] = None,
474
479
  ) -> StepOutput:
475
480
  """Async execute the condition and its steps with sequential chaining"""
476
481
  log_debug(f"Condition Start: {self.name}", center=True, symbol="-")
@@ -516,6 +521,7 @@ class Condition:
516
521
  workflow_session=workflow_session,
517
522
  add_workflow_history_to_steps=add_workflow_history_to_steps,
518
523
  num_history_runs=num_history_runs,
524
+ background_tasks=background_tasks,
519
525
  )
520
526
 
521
527
  # Handle both single StepOutput and List[StepOutput]
@@ -586,6 +592,7 @@ class Condition:
586
592
  workflow_session: Optional[WorkflowSession] = None,
587
593
  add_workflow_history_to_steps: Optional[bool] = False,
588
594
  num_history_runs: int = 3,
595
+ background_tasks: Optional[Any] = None,
589
596
  ) -> AsyncIterator[Union[WorkflowRunOutputEvent, TeamRunOutputEvent, RunOutputEvent, StepOutput]]:
590
597
  """Async execute the condition with streaming support - mirrors Loop logic"""
591
598
  log_debug(f"Condition Start: {self.name}", center=True, symbol="-")
@@ -677,6 +684,7 @@ class Condition:
677
684
  workflow_session=workflow_session,
678
685
  add_workflow_history_to_steps=add_workflow_history_to_steps,
679
686
  num_history_runs=num_history_runs,
687
+ background_tasks=background_tasks,
680
688
  ):
681
689
  if isinstance(event, StepOutput):
682
690
  step_outputs_for_step.append(event)
agno/workflow/loop.py CHANGED
@@ -139,6 +139,7 @@ class Loop:
139
139
  workflow_session: Optional[WorkflowSession] = None,
140
140
  add_workflow_history_to_steps: Optional[bool] = False,
141
141
  num_history_runs: int = 3,
142
+ background_tasks: Optional[Any] = None,
142
143
  ) -> StepOutput:
143
144
  """Execute loop steps with iteration control - mirrors workflow execution logic"""
144
145
  # Use workflow logger for loop orchestration
@@ -168,6 +169,7 @@ class Loop:
168
169
  workflow_session=workflow_session,
169
170
  add_workflow_history_to_steps=add_workflow_history_to_steps,
170
171
  num_history_runs=num_history_runs,
172
+ background_tasks=background_tasks,
171
173
  )
172
174
 
173
175
  # Handle both single StepOutput and List[StepOutput] (from Loop/Condition steps)
@@ -242,6 +244,7 @@ class Loop:
242
244
  workflow_session: Optional[WorkflowSession] = None,
243
245
  add_workflow_history_to_steps: Optional[bool] = False,
244
246
  num_history_runs: int = 3,
247
+ background_tasks: Optional[Any] = None,
245
248
  ) -> Iterator[Union[WorkflowRunOutputEvent, StepOutput]]:
246
249
  """Execute loop steps with streaming support - mirrors workflow execution logic"""
247
250
  log_debug(f"Loop Start: {self.name}", center=True, symbol="=")
@@ -328,6 +331,7 @@ class Loop:
328
331
  add_workflow_history_to_steps=add_workflow_history_to_steps,
329
332
  workflow_session=workflow_session,
330
333
  num_history_runs=num_history_runs,
334
+ background_tasks=background_tasks,
331
335
  ):
332
336
  if isinstance(event, StepOutput):
333
337
  step_outputs_for_iteration.append(event)
@@ -445,6 +449,7 @@ class Loop:
445
449
  workflow_session: Optional[WorkflowSession] = None,
446
450
  add_workflow_history_to_steps: Optional[bool] = False,
447
451
  num_history_runs: int = 3,
452
+ background_tasks: Optional[Any] = None,
448
453
  ) -> StepOutput:
449
454
  """Execute loop steps asynchronously with iteration control - mirrors workflow execution logic"""
450
455
  # Use workflow logger for async loop orchestration
@@ -476,6 +481,7 @@ class Loop:
476
481
  workflow_session=workflow_session,
477
482
  add_workflow_history_to_steps=add_workflow_history_to_steps,
478
483
  num_history_runs=num_history_runs,
484
+ background_tasks=background_tasks,
479
485
  )
480
486
 
481
487
  # Handle both single StepOutput and List[StepOutput] (from Loop/Condition steps)
@@ -553,6 +559,7 @@ class Loop:
553
559
  workflow_session: Optional[WorkflowSession] = None,
554
560
  add_workflow_history_to_steps: Optional[bool] = False,
555
561
  num_history_runs: int = 3,
562
+ background_tasks: Optional[Any] = None,
556
563
  ) -> AsyncIterator[Union[WorkflowRunOutputEvent, TeamRunOutputEvent, RunOutputEvent, StepOutput]]:
557
564
  """Execute loop steps with async streaming support - mirrors workflow execution logic"""
558
565
  log_debug(f"Loop Start: {self.name}", center=True, symbol="=")
@@ -639,6 +646,7 @@ class Loop:
639
646
  workflow_session=workflow_session,
640
647
  add_workflow_history_to_steps=add_workflow_history_to_steps,
641
648
  num_history_runs=num_history_runs,
649
+ background_tasks=background_tasks,
642
650
  ):
643
651
  if isinstance(event, StepOutput):
644
652
  step_outputs_for_iteration.append(event)
agno/workflow/parallel.py CHANGED
@@ -207,6 +207,7 @@ class Parallel:
207
207
  workflow_session: Optional[WorkflowSession] = None,
208
208
  add_workflow_history_to_steps: Optional[bool] = False,
209
209
  num_history_runs: int = 3,
210
+ background_tasks: Optional[Any] = None,
210
211
  ) -> StepOutput:
211
212
  """Execute all steps in parallel and return aggregated result"""
212
213
  # Use workflow logger for parallel orchestration
@@ -244,6 +245,7 @@ class Parallel:
244
245
  num_history_runs=num_history_runs,
245
246
  run_context=run_context,
246
247
  session_state=step_session_state,
248
+ background_tasks=background_tasks,
247
249
  ) # type: ignore[union-attr]
248
250
  return idx, step_result, step_session_state
249
251
  except Exception as exc:
@@ -336,6 +338,7 @@ class Parallel:
336
338
  workflow_session: Optional[WorkflowSession] = None,
337
339
  add_workflow_history_to_steps: Optional[bool] = False,
338
340
  num_history_runs: int = 3,
341
+ background_tasks: Optional[Any] = None,
339
342
  ) -> Iterator[Union[WorkflowRunOutputEvent, StepOutput]]:
340
343
  """Execute all steps in parallel with streaming support"""
341
344
  log_debug(f"Parallel Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="=")
@@ -418,6 +421,7 @@ class Parallel:
418
421
  workflow_session=workflow_session,
419
422
  add_workflow_history_to_steps=add_workflow_history_to_steps,
420
423
  num_history_runs=num_history_runs,
424
+ background_tasks=background_tasks,
421
425
  ):
422
426
  # Put event immediately in queue
423
427
  event_queue.put(("event", idx, event))
@@ -533,6 +537,7 @@ class Parallel:
533
537
  workflow_session: Optional[WorkflowSession] = None,
534
538
  add_workflow_history_to_steps: Optional[bool] = False,
535
539
  num_history_runs: int = 3,
540
+ background_tasks: Optional[Any] = None,
536
541
  ) -> StepOutput:
537
542
  """Execute all steps in parallel using asyncio and return aggregated result"""
538
543
  # Use workflow logger for async parallel orchestration
@@ -569,6 +574,7 @@ class Parallel:
569
574
  add_workflow_history_to_steps=add_workflow_history_to_steps,
570
575
  num_history_runs=num_history_runs,
571
576
  session_state=step_session_state,
577
+ background_tasks=background_tasks,
572
578
  ) # type: ignore[union-attr]
573
579
  return idx, inner_step_result, step_session_state
574
580
  except Exception as exc:
@@ -662,6 +668,7 @@ class Parallel:
662
668
  workflow_session: Optional[WorkflowSession] = None,
663
669
  add_workflow_history_to_steps: Optional[bool] = False,
664
670
  num_history_runs: int = 3,
671
+ background_tasks: Optional[Any] = None,
665
672
  ) -> AsyncIterator[Union[WorkflowRunOutputEvent, TeamRunOutputEvent, RunOutputEvent, StepOutput]]:
666
673
  """Execute all steps in parallel with async streaming support"""
667
674
  log_debug(f"Parallel Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="=")
@@ -745,6 +752,7 @@ class Parallel:
745
752
  workflow_session=workflow_session,
746
753
  add_workflow_history_to_steps=add_workflow_history_to_steps,
747
754
  num_history_runs=num_history_runs,
755
+ background_tasks=background_tasks,
748
756
  ): # type: ignore[union-attr]
749
757
  # Yield events immediately to the queue
750
758
  await event_queue.put(("event", idx, event))
agno/workflow/router.py CHANGED
@@ -179,6 +179,7 @@ class Router:
179
179
  workflow_session: Optional[WorkflowSession] = None,
180
180
  add_workflow_history_to_steps: Optional[bool] = False,
181
181
  num_history_runs: int = 3,
182
+ background_tasks: Optional[Any] = None,
182
183
  ) -> StepOutput:
183
184
  """Execute the router and its selected steps with sequential chaining"""
184
185
  log_debug(f"Router Start: {self.name}", center=True, symbol="-")
@@ -220,6 +221,7 @@ class Router:
220
221
  workflow_session=workflow_session,
221
222
  add_workflow_history_to_steps=add_workflow_history_to_steps,
222
223
  num_history_runs=num_history_runs,
224
+ background_tasks=background_tasks,
223
225
  )
224
226
 
225
227
  # Handle both single StepOutput and List[StepOutput]
@@ -285,6 +287,7 @@ class Router:
285
287
  workflow_session: Optional[WorkflowSession] = None,
286
288
  add_workflow_history_to_steps: Optional[bool] = False,
287
289
  num_history_runs: int = 3,
290
+ background_tasks: Optional[Any] = None,
288
291
  ) -> Iterator[Union[WorkflowRunOutputEvent, StepOutput]]:
289
292
  """Execute the router with streaming support"""
290
293
  log_debug(f"Router Start: {self.name}", center=True, symbol="-")
@@ -364,6 +367,7 @@ class Router:
364
367
  workflow_session=workflow_session,
365
368
  add_workflow_history_to_steps=add_workflow_history_to_steps,
366
369
  num_history_runs=num_history_runs,
370
+ background_tasks=background_tasks,
367
371
  ):
368
372
  if isinstance(event, StepOutput):
369
373
  step_outputs_for_step.append(event)
@@ -449,6 +453,7 @@ class Router:
449
453
  workflow_session: Optional[WorkflowSession] = None,
450
454
  add_workflow_history_to_steps: Optional[bool] = False,
451
455
  num_history_runs: int = 3,
456
+ background_tasks: Optional[Any] = None,
452
457
  ) -> StepOutput:
453
458
  """Async execute the router and its selected steps with sequential chaining"""
454
459
  log_debug(f"Router Start: {self.name}", center=True, symbol="-")
@@ -491,6 +496,7 @@ class Router:
491
496
  workflow_session=workflow_session,
492
497
  add_workflow_history_to_steps=add_workflow_history_to_steps,
493
498
  num_history_runs=num_history_runs,
499
+ background_tasks=background_tasks,
494
500
  )
495
501
  # Handle both single StepOutput and List[StepOutput]
496
502
  if isinstance(step_output, list):
@@ -558,6 +564,7 @@ class Router:
558
564
  workflow_session: Optional[WorkflowSession] = None,
559
565
  add_workflow_history_to_steps: Optional[bool] = False,
560
566
  num_history_runs: int = 3,
567
+ background_tasks: Optional[Any] = None,
561
568
  ) -> AsyncIterator[Union[WorkflowRunOutputEvent, TeamRunOutputEvent, RunOutputEvent, StepOutput]]:
562
569
  """Async execute the router with streaming support"""
563
570
  log_debug(f"Router Start: {self.name}", center=True, symbol="-")
@@ -639,6 +646,7 @@ class Router:
639
646
  workflow_session=workflow_session,
640
647
  add_workflow_history_to_steps=add_workflow_history_to_steps,
641
648
  num_history_runs=num_history_runs,
649
+ background_tasks=background_tasks,
642
650
  ):
643
651
  if isinstance(event, StepOutput):
644
652
  step_outputs_for_step.append(event)
agno/workflow/step.py CHANGED
@@ -229,6 +229,7 @@ class Step:
229
229
  workflow_session: Optional[WorkflowSession] = None,
230
230
  add_workflow_history_to_steps: Optional[bool] = False,
231
231
  num_history_runs: int = 3,
232
+ background_tasks: Optional[Any] = None,
232
233
  ) -> StepOutput:
233
234
  """Execute the step with StepInput, returning final StepOutput (non-streaming)"""
234
235
  log_debug(f"Executing step: {self.name}")
@@ -348,6 +349,10 @@ class Step:
348
349
  if isinstance(self.active_executor, Team):
349
350
  kwargs["store_member_responses"] = True
350
351
 
352
+ # Forward background_tasks if provided
353
+ if background_tasks is not None:
354
+ kwargs["background_tasks"] = background_tasks
355
+
351
356
  num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
352
357
 
353
358
  use_history = (
@@ -470,6 +475,7 @@ class Step:
470
475
  workflow_session: Optional["WorkflowSession"] = None,
471
476
  add_workflow_history_to_steps: Optional[bool] = False,
472
477
  num_history_runs: int = 3,
478
+ background_tasks: Optional[Any] = None,
473
479
  ) -> Iterator[Union[WorkflowRunOutputEvent, StepOutput]]:
474
480
  """Execute the step with event-driven streaming support"""
475
481
 
@@ -609,6 +615,10 @@ class Step:
609
615
  if isinstance(self.active_executor, Team):
610
616
  kwargs["store_member_responses"] = True
611
617
 
618
+ # Forward background_tasks if provided
619
+ if background_tasks is not None:
620
+ kwargs["background_tasks"] = background_tasks
621
+
612
622
  num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
613
623
 
614
624
  use_history = (
@@ -720,6 +730,7 @@ class Step:
720
730
  workflow_session: Optional["WorkflowSession"] = None,
721
731
  add_workflow_history_to_steps: Optional[bool] = False,
722
732
  num_history_runs: int = 3,
733
+ background_tasks: Optional[Any] = None,
723
734
  ) -> StepOutput:
724
735
  """Execute the step with StepInput, returning final StepOutput (non-streaming)"""
725
736
  logger.info(f"Executing async step (non-streaming): {self.name}")
@@ -870,6 +881,10 @@ class Step:
870
881
  if isinstance(self.active_executor, Team):
871
882
  kwargs["store_member_responses"] = True
872
883
 
884
+ # Forward background_tasks if provided
885
+ if background_tasks is not None:
886
+ kwargs["background_tasks"] = background_tasks
887
+
873
888
  num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
874
889
 
875
890
  use_history = (
@@ -945,6 +960,7 @@ class Step:
945
960
  workflow_session: Optional["WorkflowSession"] = None,
946
961
  add_workflow_history_to_steps: Optional[bool] = False,
947
962
  num_history_runs: int = 3,
963
+ background_tasks: Optional[Any] = None,
948
964
  ) -> AsyncIterator[Union[WorkflowRunOutputEvent, StepOutput]]:
949
965
  """Execute the step with event-driven streaming support"""
950
966
 
@@ -1128,6 +1144,10 @@ class Step:
1128
1144
  if isinstance(self.active_executor, Team):
1129
1145
  kwargs["store_member_responses"] = True
1130
1146
 
1147
+ # Forward background_tasks if provided
1148
+ if background_tasks is not None:
1149
+ kwargs["background_tasks"] = background_tasks
1150
+
1131
1151
  num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
1132
1152
 
1133
1153
  use_history = (
agno/workflow/steps.py CHANGED
@@ -126,6 +126,7 @@ class Steps:
126
126
  workflow_session: Optional[WorkflowSession] = None,
127
127
  add_workflow_history_to_steps: Optional[bool] = False,
128
128
  num_history_runs: int = 3,
129
+ background_tasks: Optional[Any] = None,
129
130
  ) -> StepOutput:
130
131
  """Execute all steps in sequence and return the final result"""
131
132
  log_debug(f"Steps Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="-")
@@ -159,6 +160,7 @@ class Steps:
159
160
  workflow_session=workflow_session,
160
161
  add_workflow_history_to_steps=add_workflow_history_to_steps,
161
162
  num_history_runs=num_history_runs,
163
+ background_tasks=background_tasks,
162
164
  )
163
165
 
164
166
  # Handle both single StepOutput and List[StepOutput] (from Loop/Condition/Router steps)
@@ -221,6 +223,7 @@ class Steps:
221
223
  workflow_session: Optional[WorkflowSession] = None,
222
224
  add_workflow_history_to_steps: Optional[bool] = False,
223
225
  num_history_runs: int = 3,
226
+ background_tasks: Optional[Any] = None,
224
227
  ) -> Iterator[Union[WorkflowRunOutputEvent, TeamRunOutputEvent, RunOutputEvent, StepOutput]]:
225
228
  """Execute all steps in sequence with streaming support"""
226
229
  log_debug(f"Steps Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="-")
@@ -291,6 +294,7 @@ class Steps:
291
294
  workflow_session=workflow_session,
292
295
  add_workflow_history_to_steps=add_workflow_history_to_steps,
293
296
  num_history_runs=num_history_runs,
297
+ background_tasks=background_tasks,
294
298
  ):
295
299
  if isinstance(event, StepOutput):
296
300
  step_outputs_for_step.append(event)
@@ -372,6 +376,7 @@ class Steps:
372
376
  workflow_session: Optional[WorkflowSession] = None,
373
377
  add_workflow_history_to_steps: Optional[bool] = False,
374
378
  num_history_runs: int = 3,
379
+ background_tasks: Optional[Any] = None,
375
380
  ) -> StepOutput:
376
381
  """Execute all steps in sequence asynchronously and return the final result"""
377
382
  log_debug(f"Steps Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="-")
@@ -405,6 +410,7 @@ class Steps:
405
410
  workflow_session=workflow_session,
406
411
  add_workflow_history_to_steps=add_workflow_history_to_steps,
407
412
  num_history_runs=num_history_runs,
413
+ background_tasks=background_tasks,
408
414
  )
409
415
 
410
416
  # Handle both single StepOutput and List[StepOutput] (from Loop/Condition/Router steps)
@@ -466,6 +472,7 @@ class Steps:
466
472
  workflow_session: Optional[WorkflowSession] = None,
467
473
  add_workflow_history_to_steps: Optional[bool] = False,
468
474
  num_history_runs: int = 3,
475
+ background_tasks: Optional[Any] = None,
469
476
  ) -> AsyncIterator[Union[WorkflowRunOutputEvent, TeamRunOutputEvent, RunOutputEvent, StepOutput]]:
470
477
  """Execute all steps in sequence with async streaming support"""
471
478
  log_debug(f"Steps Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="-")
@@ -536,6 +543,7 @@ class Steps:
536
543
  workflow_session=workflow_session,
537
544
  add_workflow_history_to_steps=add_workflow_history_to_steps,
538
545
  num_history_runs=num_history_runs,
546
+ background_tasks=background_tasks,
539
547
  ):
540
548
  if isinstance(event, StepOutput):
541
549
  step_outputs_for_step.append(event)