agno 2.1.3__py3-none-any.whl → 2.1.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 (94) hide show
  1. agno/agent/agent.py +1779 -577
  2. agno/db/async_postgres/__init__.py +3 -0
  3. agno/db/async_postgres/async_postgres.py +1668 -0
  4. agno/db/async_postgres/schemas.py +124 -0
  5. agno/db/async_postgres/utils.py +289 -0
  6. agno/db/base.py +237 -2
  7. agno/db/dynamo/dynamo.py +10 -8
  8. agno/db/dynamo/schemas.py +1 -10
  9. agno/db/dynamo/utils.py +2 -2
  10. agno/db/firestore/firestore.py +2 -2
  11. agno/db/firestore/utils.py +4 -2
  12. agno/db/gcs_json/gcs_json_db.py +2 -2
  13. agno/db/in_memory/in_memory_db.py +2 -2
  14. agno/db/json/json_db.py +2 -2
  15. agno/db/migrations/v1_to_v2.py +30 -13
  16. agno/db/mongo/mongo.py +18 -6
  17. agno/db/mysql/mysql.py +35 -13
  18. agno/db/postgres/postgres.py +29 -6
  19. agno/db/redis/redis.py +2 -2
  20. agno/db/singlestore/singlestore.py +2 -2
  21. agno/db/sqlite/sqlite.py +34 -12
  22. agno/db/sqlite/utils.py +8 -3
  23. agno/eval/accuracy.py +50 -43
  24. agno/eval/performance.py +6 -3
  25. agno/eval/reliability.py +6 -3
  26. agno/eval/utils.py +33 -16
  27. agno/exceptions.py +8 -2
  28. agno/knowledge/embedder/fastembed.py +1 -1
  29. agno/knowledge/knowledge.py +260 -46
  30. agno/knowledge/reader/pdf_reader.py +4 -6
  31. agno/knowledge/reader/reader_factory.py +2 -3
  32. agno/memory/manager.py +241 -33
  33. agno/models/anthropic/claude.py +37 -0
  34. agno/os/app.py +15 -10
  35. agno/os/interfaces/a2a/router.py +3 -5
  36. agno/os/interfaces/agui/router.py +4 -1
  37. agno/os/interfaces/agui/utils.py +33 -6
  38. agno/os/interfaces/slack/router.py +2 -4
  39. agno/os/mcp.py +98 -41
  40. agno/os/router.py +23 -0
  41. agno/os/routers/evals/evals.py +52 -20
  42. agno/os/routers/evals/utils.py +14 -14
  43. agno/os/routers/knowledge/knowledge.py +130 -9
  44. agno/os/routers/knowledge/schemas.py +57 -0
  45. agno/os/routers/memory/memory.py +116 -44
  46. agno/os/routers/metrics/metrics.py +16 -6
  47. agno/os/routers/session/session.py +65 -22
  48. agno/os/schema.py +38 -0
  49. agno/os/utils.py +69 -13
  50. agno/reasoning/anthropic.py +80 -0
  51. agno/reasoning/gemini.py +73 -0
  52. agno/reasoning/openai.py +5 -0
  53. agno/reasoning/vertexai.py +76 -0
  54. agno/session/workflow.py +69 -1
  55. agno/team/team.py +934 -241
  56. agno/tools/function.py +36 -18
  57. agno/tools/google_drive.py +270 -0
  58. agno/tools/googlesheets.py +20 -5
  59. agno/tools/mcp_toolbox.py +3 -3
  60. agno/tools/scrapegraph.py +1 -1
  61. agno/utils/models/claude.py +3 -1
  62. agno/utils/print_response/workflow.py +112 -12
  63. agno/utils/streamlit.py +1 -1
  64. agno/vectordb/base.py +22 -1
  65. agno/vectordb/cassandra/cassandra.py +9 -0
  66. agno/vectordb/chroma/chromadb.py +26 -6
  67. agno/vectordb/clickhouse/clickhousedb.py +9 -1
  68. agno/vectordb/couchbase/couchbase.py +11 -0
  69. agno/vectordb/lancedb/lance_db.py +20 -0
  70. agno/vectordb/langchaindb/langchaindb.py +11 -0
  71. agno/vectordb/lightrag/lightrag.py +9 -0
  72. agno/vectordb/llamaindex/llamaindexdb.py +15 -1
  73. agno/vectordb/milvus/milvus.py +23 -0
  74. agno/vectordb/mongodb/mongodb.py +22 -0
  75. agno/vectordb/pgvector/pgvector.py +19 -0
  76. agno/vectordb/pineconedb/pineconedb.py +35 -4
  77. agno/vectordb/qdrant/qdrant.py +24 -0
  78. agno/vectordb/singlestore/singlestore.py +25 -17
  79. agno/vectordb/surrealdb/surrealdb.py +18 -1
  80. agno/vectordb/upstashdb/upstashdb.py +26 -1
  81. agno/vectordb/weaviate/weaviate.py +18 -0
  82. agno/workflow/condition.py +29 -0
  83. agno/workflow/loop.py +29 -0
  84. agno/workflow/parallel.py +141 -113
  85. agno/workflow/router.py +29 -0
  86. agno/workflow/step.py +146 -25
  87. agno/workflow/steps.py +29 -0
  88. agno/workflow/types.py +26 -1
  89. agno/workflow/workflow.py +507 -22
  90. {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/METADATA +100 -41
  91. {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/RECORD +94 -86
  92. {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/WHEEL +0 -0
  93. {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/licenses/LICENSE +0 -0
  94. {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/top_level.txt +0 -0
agno/workflow/step.py CHANGED
@@ -17,6 +17,7 @@ from agno.run.workflow import (
17
17
  WorkflowRunOutput,
18
18
  WorkflowRunOutputEvent,
19
19
  )
20
+ from agno.session.workflow import WorkflowSession
20
21
  from agno.team import Team
21
22
  from agno.utils.log import log_debug, logger, use_agent_logger, use_team_logger, use_workflow_logger
22
23
  from agno.utils.merge_dict import merge_dictionaries
@@ -60,6 +61,9 @@ class Step:
60
61
  # If False, only warn about missing inputs
61
62
  strict_input_validation: bool = False
62
63
 
64
+ add_workflow_history: Optional[bool] = None
65
+ num_history_runs: int = 3
66
+
63
67
  _retry_count: int = 0
64
68
 
65
69
  def __init__(
@@ -74,6 +78,8 @@ class Step:
74
78
  timeout_seconds: Optional[int] = None,
75
79
  skip_on_failure: bool = False,
76
80
  strict_input_validation: bool = False,
81
+ add_workflow_history: Optional[bool] = None,
82
+ num_history_runs: int = 3,
77
83
  ):
78
84
  # Auto-detect name for function executors if not provided
79
85
  if name is None and executor is not None:
@@ -93,6 +99,8 @@ class Step:
93
99
  self.timeout_seconds = timeout_seconds
94
100
  self.skip_on_failure = skip_on_failure
95
101
  self.strict_input_validation = strict_input_validation
102
+ self.add_workflow_history = add_workflow_history
103
+ self.num_history_runs = num_history_runs
96
104
  self.step_id = step_id
97
105
 
98
106
  if step_id is None:
@@ -204,6 +212,9 @@ class Step:
204
212
  workflow_run_response: Optional["WorkflowRunOutput"] = None,
205
213
  session_state: Optional[Dict[str, Any]] = None,
206
214
  store_executor_outputs: bool = True,
215
+ workflow_session: Optional[WorkflowSession] = None,
216
+ add_workflow_history_to_steps: Optional[bool] = False,
217
+ num_history_runs: int = 3,
207
218
  ) -> StepOutput:
208
219
  """Execute the step with StepInput, returning final StepOutput (non-streaming)"""
209
220
  log_debug(f"Executing step: {self.name}")
@@ -211,6 +222,8 @@ class Step:
211
222
  if step_input.previous_step_outputs:
212
223
  step_input.previous_step_content = step_input.get_last_step_content()
213
224
 
225
+ if workflow_session:
226
+ step_input.workflow_session = workflow_session
214
227
  session_state_copy = copy(session_state) if session_state is not None else {}
215
228
 
216
229
  # Execute with retries
@@ -292,8 +305,22 @@ class Step:
292
305
  if isinstance(self.active_executor, Team):
293
306
  kwargs["store_member_responses"] = True
294
307
 
308
+ num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
309
+
310
+ use_history = (
311
+ self.add_workflow_history
312
+ if self.add_workflow_history is not None
313
+ else add_workflow_history_to_steps
314
+ )
315
+
316
+ final_message = message
317
+ if use_history and workflow_session:
318
+ history_messages = workflow_session.get_workflow_history_context(num_runs=num_history_runs)
319
+ if history_messages:
320
+ final_message = f"{history_messages}{message}"
321
+
295
322
  response = self.active_executor.run( # type: ignore
296
- input=message, # type: ignore
323
+ input=final_message, # type: ignore
297
324
  images=images,
298
325
  videos=videos,
299
326
  audio=audios,
@@ -348,23 +375,55 @@ class Step:
348
375
  except Exception:
349
376
  return False
350
377
 
378
+ def _enrich_event_with_context(
379
+ self,
380
+ event: Any,
381
+ workflow_run_response: Optional["WorkflowRunOutput"] = None,
382
+ step_index: Optional[Union[int, tuple]] = None,
383
+ ) -> Any:
384
+ """Enrich event with step and workflow context information"""
385
+ if workflow_run_response is None:
386
+ return event
387
+
388
+ if hasattr(event, "workflow_id"):
389
+ event.workflow_id = workflow_run_response.workflow_id
390
+ if hasattr(event, "workflow_run_id"):
391
+ event.workflow_run_id = workflow_run_response.run_id
392
+ if hasattr(event, "step_id"):
393
+ event.step_id = self.step_id
394
+ if hasattr(event, "step_name") and self.name is not None:
395
+ if getattr(event, "step_name", None) is None:
396
+ event.step_name = self.name
397
+ # Only set step_index if it's not already set (preserve parallel.py's tuples)
398
+ if hasattr(event, "step_index") and step_index is not None:
399
+ if event.step_index is None:
400
+ event.step_index = step_index
401
+
402
+ return event
403
+
351
404
  def execute_stream(
352
405
  self,
353
406
  step_input: StepInput,
354
407
  session_id: Optional[str] = None,
355
408
  user_id: Optional[str] = None,
356
409
  stream_intermediate_steps: bool = False,
410
+ stream_executor_events: bool = True,
357
411
  workflow_run_response: Optional["WorkflowRunOutput"] = None,
358
412
  session_state: Optional[Dict[str, Any]] = None,
359
413
  step_index: Optional[Union[int, tuple]] = None,
360
414
  store_executor_outputs: bool = True,
361
415
  parent_step_id: Optional[str] = None,
416
+ workflow_session: Optional["WorkflowSession"] = None,
417
+ add_workflow_history_to_steps: Optional[bool] = False,
418
+ num_history_runs: int = 3,
362
419
  ) -> Iterator[Union[WorkflowRunOutputEvent, StepOutput]]:
363
420
  """Execute the step with event-driven streaming support"""
364
421
 
365
422
  if step_input.previous_step_outputs:
366
423
  step_input.previous_step_content = step_input.get_last_step_content()
367
424
 
425
+ if workflow_session:
426
+ step_input.workflow_session = workflow_session
368
427
  # Create session_state copy once to avoid duplication
369
428
  session_state_copy = copy(session_state) if session_state is not None else {}
370
429
 
@@ -413,7 +472,13 @@ class Step:
413
472
  final_response = event
414
473
  break
415
474
  else:
416
- yield event # type: ignore[misc]
475
+ # Enrich event with workflow context before yielding
476
+ enriched_event = self._enrich_event_with_context(
477
+ event, workflow_run_response, step_index
478
+ )
479
+ # Only yield executor events if stream_executor_events is True
480
+ if stream_executor_events:
481
+ yield enriched_event # type: ignore[misc]
417
482
 
418
483
  # Merge session_state changes back
419
484
  if session_state is not None:
@@ -463,8 +528,22 @@ class Step:
463
528
  if isinstance(self.active_executor, Team):
464
529
  kwargs["store_member_responses"] = True
465
530
 
531
+ num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
532
+
533
+ use_history = (
534
+ self.add_workflow_history
535
+ if self.add_workflow_history is not None
536
+ else add_workflow_history_to_steps
537
+ )
538
+
539
+ final_message = message
540
+ if use_history and workflow_session:
541
+ history_messages = workflow_session.get_workflow_history_context(num_runs=num_history_runs)
542
+ if history_messages:
543
+ final_message = f"{history_messages}{message}"
544
+
466
545
  response_stream = self.active_executor.run( # type: ignore[call-overload, misc]
467
- input=message,
546
+ input=final_message,
468
547
  images=images,
469
548
  videos=videos,
470
549
  audio=audios,
@@ -474,14 +553,6 @@ class Step:
474
553
  session_state=session_state_copy, # Send a copy to the executor
475
554
  stream=True,
476
555
  stream_intermediate_steps=stream_intermediate_steps,
477
- # Pass workflow context directly via kwargs
478
- workflow_context={
479
- "workflow_id": workflow_run_response.workflow_id if workflow_run_response else None,
480
- "workflow_run_id": workflow_run_response.run_id if workflow_run_response else None,
481
- "step_id": self.step_id,
482
- "step_name": self.name,
483
- "step_index": step_index,
484
- },
485
556
  yield_run_response=True,
486
557
  **kwargs,
487
558
  )
@@ -491,7 +562,10 @@ class Step:
491
562
  if isinstance(event, RunOutput) or isinstance(event, TeamRunOutput):
492
563
  active_executor_run_response = event
493
564
  break
494
- yield event # type: ignore[misc]
565
+ enriched_event = self._enrich_event_with_context(event, workflow_run_response, step_index)
566
+ # Only yield executor events if stream_executor_events is True
567
+ if stream_executor_events:
568
+ yield enriched_event # type: ignore[misc]
495
569
 
496
570
  if session_state is not None:
497
571
  # Update workflow session state
@@ -558,6 +632,9 @@ class Step:
558
632
  workflow_run_response: Optional["WorkflowRunOutput"] = None,
559
633
  session_state: Optional[Dict[str, Any]] = None,
560
634
  store_executor_outputs: bool = True,
635
+ workflow_session: Optional["WorkflowSession"] = None,
636
+ add_workflow_history_to_steps: Optional[bool] = False,
637
+ num_history_runs: int = 3,
561
638
  ) -> StepOutput:
562
639
  """Execute the step with StepInput, returning final StepOutput (non-streaming)"""
563
640
  logger.info(f"Executing async step (non-streaming): {self.name}")
@@ -566,6 +643,8 @@ class Step:
566
643
  if step_input.previous_step_outputs:
567
644
  step_input.previous_step_content = step_input.get_last_step_content()
568
645
 
646
+ if workflow_session:
647
+ step_input.workflow_session = workflow_session
569
648
  # Create session_state copy once to avoid duplication
570
649
  session_state_copy = copy(session_state) if session_state is not None else {}
571
650
 
@@ -670,8 +749,22 @@ class Step:
670
749
  if isinstance(self.active_executor, Team):
671
750
  kwargs["store_member_responses"] = True
672
751
 
752
+ num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
753
+
754
+ use_history = (
755
+ self.add_workflow_history
756
+ if self.add_workflow_history is not None
757
+ else add_workflow_history_to_steps
758
+ )
759
+
760
+ final_message = message
761
+ if use_history and workflow_session:
762
+ history_messages = workflow_session.get_workflow_history_context(num_runs=num_history_runs)
763
+ if history_messages:
764
+ final_message = f"{history_messages}{message}"
765
+
673
766
  response = await self.active_executor.arun( # type: ignore
674
- input=message, # type: ignore
767
+ input=final_message, # type: ignore
675
768
  images=images,
676
769
  videos=videos,
677
770
  audio=audios,
@@ -719,17 +812,24 @@ class Step:
719
812
  session_id: Optional[str] = None,
720
813
  user_id: Optional[str] = None,
721
814
  stream_intermediate_steps: bool = False,
815
+ stream_executor_events: bool = True,
722
816
  workflow_run_response: Optional["WorkflowRunOutput"] = None,
723
817
  session_state: Optional[Dict[str, Any]] = None,
724
818
  step_index: Optional[Union[int, tuple]] = None,
725
819
  store_executor_outputs: bool = True,
726
820
  parent_step_id: Optional[str] = None,
821
+ workflow_session: Optional["WorkflowSession"] = None,
822
+ add_workflow_history_to_steps: Optional[bool] = False,
823
+ num_history_runs: int = 3,
727
824
  ) -> AsyncIterator[Union[WorkflowRunOutputEvent, StepOutput]]:
728
825
  """Execute the step with event-driven streaming support"""
729
826
 
730
827
  if step_input.previous_step_outputs:
731
828
  step_input.previous_step_content = step_input.get_last_step_content()
732
829
 
830
+ if workflow_session:
831
+ step_input.workflow_session = workflow_session
832
+
733
833
  # Create session_state copy once to avoid duplication
734
834
  session_state_copy = copy(session_state) if session_state is not None else {}
735
835
 
@@ -776,7 +876,13 @@ class Step:
776
876
  final_response = event
777
877
  break
778
878
  else:
779
- yield event # type: ignore[misc]
879
+ # Enrich event with workflow context before yielding
880
+ enriched_event = self._enrich_event_with_context(
881
+ event, workflow_run_response, step_index
882
+ )
883
+ # Only yield executor events if stream_executor_events is True
884
+ if stream_executor_events:
885
+ yield enriched_event # type: ignore[misc]
780
886
  if not final_response:
781
887
  final_response = StepOutput(content=content)
782
888
  elif inspect.iscoroutinefunction(self.active_executor):
@@ -803,7 +909,13 @@ class Step:
803
909
  final_response = event
804
910
  break
805
911
  else:
806
- yield event # type: ignore[misc]
912
+ # Enrich event with workflow context before yielding
913
+ enriched_event = self._enrich_event_with_context(
914
+ event, workflow_run_response, step_index
915
+ )
916
+ # Only yield executor events if stream_executor_events is True
917
+ if stream_executor_events:
918
+ yield enriched_event # type: ignore[misc]
807
919
  if not final_response:
808
920
  final_response = StepOutput(content=content)
809
921
  else:
@@ -843,8 +955,22 @@ class Step:
843
955
  if isinstance(self.active_executor, Team):
844
956
  kwargs["store_member_responses"] = True
845
957
 
958
+ num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
959
+
960
+ use_history = (
961
+ self.add_workflow_history
962
+ if self.add_workflow_history is not None
963
+ else add_workflow_history_to_steps
964
+ )
965
+
966
+ final_message = message
967
+ if use_history and workflow_session:
968
+ history_messages = workflow_session.get_workflow_history_context(num_runs=num_history_runs)
969
+ if history_messages:
970
+ final_message = f"{history_messages}{message}"
971
+
846
972
  response_stream = self.active_executor.arun( # type: ignore
847
- input=message,
973
+ input=final_message,
848
974
  images=images,
849
975
  videos=videos,
850
976
  audio=audios,
@@ -854,14 +980,6 @@ class Step:
854
980
  session_state=session_state_copy,
855
981
  stream=True,
856
982
  stream_intermediate_steps=stream_intermediate_steps,
857
- # Pass workflow context directly via kwargs
858
- workflow_context={
859
- "workflow_id": workflow_run_response.workflow_id if workflow_run_response else None,
860
- "workflow_run_id": workflow_run_response.run_id if workflow_run_response else None,
861
- "step_id": self.step_id,
862
- "step_name": self.name,
863
- "step_index": step_index,
864
- },
865
983
  yield_run_response=True,
866
984
  **kwargs,
867
985
  )
@@ -871,7 +989,10 @@ class Step:
871
989
  if isinstance(event, RunOutput) or isinstance(event, TeamRunOutput):
872
990
  active_executor_run_response = event
873
991
  break
874
- yield event # type: ignore[misc]
992
+ enriched_event = self._enrich_event_with_context(event, workflow_run_response, step_index)
993
+ # Only yield executor events if stream_executor_events is True
994
+ if stream_executor_events:
995
+ yield enriched_event # type: ignore[misc]
875
996
 
876
997
  if session_state is not None:
877
998
  # Update workflow session state
agno/workflow/steps.py CHANGED
@@ -10,6 +10,7 @@ from agno.run.workflow import (
10
10
  WorkflowRunOutput,
11
11
  WorkflowRunOutputEvent,
12
12
  )
13
+ from agno.session.workflow import WorkflowSession
13
14
  from agno.utils.log import log_debug, logger
14
15
  from agno.workflow.step import Step, StepInput, StepOutput, StepType
15
16
 
@@ -119,6 +120,9 @@ class Steps:
119
120
  workflow_run_response: Optional[WorkflowRunOutput] = None,
120
121
  session_state: Optional[Dict[str, Any]] = None,
121
122
  store_executor_outputs: bool = True,
123
+ workflow_session: Optional[WorkflowSession] = None,
124
+ add_workflow_history_to_steps: Optional[bool] = False,
125
+ num_history_runs: int = 3,
122
126
  ) -> StepOutput:
123
127
  """Execute all steps in sequence and return the final result"""
124
128
  log_debug(f"Steps Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="-")
@@ -148,6 +152,9 @@ class Steps:
148
152
  workflow_run_response=workflow_run_response,
149
153
  store_executor_outputs=store_executor_outputs,
150
154
  session_state=session_state,
155
+ workflow_session=workflow_session,
156
+ add_workflow_history_to_steps=add_workflow_history_to_steps,
157
+ num_history_runs=num_history_runs,
151
158
  )
152
159
 
153
160
  # Handle both single StepOutput and List[StepOutput] (from Loop/Condition/Router steps)
@@ -201,9 +208,13 @@ class Steps:
201
208
  session_id: Optional[str] = None,
202
209
  user_id: Optional[str] = None,
203
210
  stream_intermediate_steps: bool = False,
211
+ stream_executor_events: bool = True,
204
212
  step_index: Optional[Union[int, tuple]] = None,
205
213
  store_executor_outputs: bool = True,
206
214
  parent_step_id: Optional[str] = None,
215
+ workflow_session: Optional[WorkflowSession] = None,
216
+ add_workflow_history_to_steps: Optional[bool] = False,
217
+ num_history_runs: int = 3,
207
218
  ) -> Iterator[Union[WorkflowRunOutputEvent, TeamRunOutputEvent, RunOutputEvent, StepOutput]]:
208
219
  """Execute all steps in sequence with streaming support"""
209
220
  log_debug(f"Steps Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="-")
@@ -256,10 +267,14 @@ class Steps:
256
267
  user_id=user_id,
257
268
  session_state=session_state,
258
269
  stream_intermediate_steps=stream_intermediate_steps,
270
+ stream_executor_events=stream_executor_events,
259
271
  workflow_run_response=workflow_run_response,
260
272
  step_index=child_step_index,
261
273
  store_executor_outputs=store_executor_outputs,
262
274
  parent_step_id=steps_id,
275
+ workflow_session=workflow_session,
276
+ add_workflow_history_to_steps=add_workflow_history_to_steps,
277
+ num_history_runs=num_history_runs,
263
278
  ):
264
279
  if isinstance(event, StepOutput):
265
280
  step_outputs_for_step.append(event)
@@ -337,6 +352,9 @@ class Steps:
337
352
  workflow_run_response: Optional[WorkflowRunOutput] = None,
338
353
  session_state: Optional[Dict[str, Any]] = None,
339
354
  store_executor_outputs: bool = True,
355
+ workflow_session: Optional[WorkflowSession] = None,
356
+ add_workflow_history_to_steps: Optional[bool] = False,
357
+ num_history_runs: int = 3,
340
358
  ) -> StepOutput:
341
359
  """Execute all steps in sequence asynchronously and return the final result"""
342
360
  log_debug(f"Steps Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="-")
@@ -366,6 +384,9 @@ class Steps:
366
384
  workflow_run_response=workflow_run_response,
367
385
  store_executor_outputs=store_executor_outputs,
368
386
  session_state=session_state,
387
+ workflow_session=workflow_session,
388
+ add_workflow_history_to_steps=add_workflow_history_to_steps,
389
+ num_history_runs=num_history_runs,
369
390
  )
370
391
 
371
392
  # Handle both single StepOutput and List[StepOutput] (from Loop/Condition/Router steps)
@@ -418,9 +439,13 @@ class Steps:
418
439
  session_id: Optional[str] = None,
419
440
  user_id: Optional[str] = None,
420
441
  stream_intermediate_steps: bool = False,
442
+ stream_executor_events: bool = True,
421
443
  step_index: Optional[Union[int, tuple]] = None,
422
444
  store_executor_outputs: bool = True,
423
445
  parent_step_id: Optional[str] = None,
446
+ workflow_session: Optional[WorkflowSession] = None,
447
+ add_workflow_history_to_steps: Optional[bool] = False,
448
+ num_history_runs: int = 3,
424
449
  ) -> AsyncIterator[Union[WorkflowRunOutputEvent, TeamRunOutputEvent, RunOutputEvent, StepOutput]]:
425
450
  """Execute all steps in sequence with async streaming support"""
426
451
  log_debug(f"Steps Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="-")
@@ -473,10 +498,14 @@ class Steps:
473
498
  user_id=user_id,
474
499
  session_state=session_state,
475
500
  stream_intermediate_steps=stream_intermediate_steps,
501
+ stream_executor_events=stream_executor_events,
476
502
  workflow_run_response=workflow_run_response,
477
503
  step_index=child_step_index,
478
504
  store_executor_outputs=store_executor_outputs,
479
505
  parent_step_id=steps_id,
506
+ workflow_session=workflow_session,
507
+ add_workflow_history_to_steps=add_workflow_history_to_steps,
508
+ num_history_runs=num_history_runs,
480
509
  ):
481
510
  if isinstance(event, StepOutput):
482
511
  step_outputs_for_step.append(event)
agno/workflow/types.py CHANGED
@@ -1,13 +1,14 @@
1
1
  import json
2
2
  from dataclasses import dataclass
3
3
  from enum import Enum
4
- from typing import Any, Dict, List, Optional, Union
4
+ from typing import Any, Dict, List, Optional, Tuple, Union
5
5
 
6
6
  from fastapi import WebSocket
7
7
  from pydantic import BaseModel
8
8
 
9
9
  from agno.media import Audio, File, Image, Video
10
10
  from agno.models.metrics import Metrics
11
+ from agno.session.workflow import WorkflowSession
11
12
  from agno.utils.log import log_warning
12
13
  from agno.utils.serialize import json_serializer
13
14
 
@@ -80,6 +81,8 @@ class StepInput:
80
81
  audio: Optional[List[Audio]] = None
81
82
  files: Optional[List[File]] = None
82
83
 
84
+ workflow_session: Optional["WorkflowSession"] = None
85
+
83
86
  def get_input_as_string(self) -> Optional[str]:
84
87
  """Convert input to string representation"""
85
88
  if self.input is None:
@@ -171,6 +174,28 @@ class StepInput:
171
174
  # Use the helper method to get the deepest content
172
175
  return self._get_deepest_step_content(last_output) # type: ignore[return-value]
173
176
 
177
+ def get_workflow_history(self, num_runs: Optional[int] = None) -> List[Tuple[str, str]]:
178
+ """Get workflow conversation history as structured data for custom function steps
179
+
180
+ Args:
181
+ num_runs: Number of recent runs to include. If None, returns all available history.
182
+ """
183
+ if not self.workflow_session:
184
+ return []
185
+
186
+ return self.workflow_session.get_workflow_history(num_runs=num_runs)
187
+
188
+ def get_workflow_history_context(self, num_runs: Optional[int] = None) -> Optional[str]:
189
+ """Get formatted workflow conversation history context for custom function steps
190
+
191
+ Args:
192
+ num_runs: Number of recent runs to include. If None, returns all available history.
193
+ """
194
+ if not self.workflow_session:
195
+ return None
196
+
197
+ return self.workflow_session.get_workflow_history_context(num_runs=num_runs)
198
+
174
199
  def to_dict(self) -> Dict[str, Any]:
175
200
  """Convert to dictionary"""
176
201
  # Handle the unified message field