agno 2.1.3__py3-none-any.whl → 2.1.4__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/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,6 +375,32 @@ 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,
@@ -359,12 +412,17 @@ class Step:
359
412
  step_index: Optional[Union[int, tuple]] = None,
360
413
  store_executor_outputs: bool = True,
361
414
  parent_step_id: Optional[str] = None,
415
+ workflow_session: Optional["WorkflowSession"] = None,
416
+ add_workflow_history_to_steps: Optional[bool] = False,
417
+ num_history_runs: int = 3,
362
418
  ) -> Iterator[Union[WorkflowRunOutputEvent, StepOutput]]:
363
419
  """Execute the step with event-driven streaming support"""
364
420
 
365
421
  if step_input.previous_step_outputs:
366
422
  step_input.previous_step_content = step_input.get_last_step_content()
367
423
 
424
+ if workflow_session:
425
+ step_input.workflow_session = workflow_session
368
426
  # Create session_state copy once to avoid duplication
369
427
  session_state_copy = copy(session_state) if session_state is not None else {}
370
428
 
@@ -413,7 +471,11 @@ class Step:
413
471
  final_response = event
414
472
  break
415
473
  else:
416
- yield event # type: ignore[misc]
474
+ # Enrich event with workflow context before yielding
475
+ enriched_event = self._enrich_event_with_context(
476
+ event, workflow_run_response, step_index
477
+ )
478
+ yield enriched_event # type: ignore[misc]
417
479
 
418
480
  # Merge session_state changes back
419
481
  if session_state is not None:
@@ -463,8 +525,22 @@ class Step:
463
525
  if isinstance(self.active_executor, Team):
464
526
  kwargs["store_member_responses"] = True
465
527
 
528
+ num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
529
+
530
+ use_history = (
531
+ self.add_workflow_history
532
+ if self.add_workflow_history is not None
533
+ else add_workflow_history_to_steps
534
+ )
535
+
536
+ final_message = message
537
+ if use_history and workflow_session:
538
+ history_messages = workflow_session.get_workflow_history_context(num_runs=num_history_runs)
539
+ if history_messages:
540
+ final_message = f"{history_messages}{message}"
541
+
466
542
  response_stream = self.active_executor.run( # type: ignore[call-overload, misc]
467
- input=message,
543
+ input=final_message,
468
544
  images=images,
469
545
  videos=videos,
470
546
  audio=audios,
@@ -474,14 +550,6 @@ class Step:
474
550
  session_state=session_state_copy, # Send a copy to the executor
475
551
  stream=True,
476
552
  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
553
  yield_run_response=True,
486
554
  **kwargs,
487
555
  )
@@ -491,7 +559,10 @@ class Step:
491
559
  if isinstance(event, RunOutput) or isinstance(event, TeamRunOutput):
492
560
  active_executor_run_response = event
493
561
  break
494
- yield event # type: ignore[misc]
562
+ enriched_event = self._enrich_event_with_context(
563
+ event, workflow_run_response, step_index
564
+ )
565
+ yield enriched_event # type: ignore[misc]
495
566
 
496
567
  if session_state is not None:
497
568
  # Update workflow session state
@@ -558,6 +629,9 @@ class Step:
558
629
  workflow_run_response: Optional["WorkflowRunOutput"] = None,
559
630
  session_state: Optional[Dict[str, Any]] = None,
560
631
  store_executor_outputs: bool = True,
632
+ workflow_session: Optional["WorkflowSession"] = None,
633
+ add_workflow_history_to_steps: Optional[bool] = False,
634
+ num_history_runs: int = 3,
561
635
  ) -> StepOutput:
562
636
  """Execute the step with StepInput, returning final StepOutput (non-streaming)"""
563
637
  logger.info(f"Executing async step (non-streaming): {self.name}")
@@ -566,6 +640,8 @@ class Step:
566
640
  if step_input.previous_step_outputs:
567
641
  step_input.previous_step_content = step_input.get_last_step_content()
568
642
 
643
+ if workflow_session:
644
+ step_input.workflow_session = workflow_session
569
645
  # Create session_state copy once to avoid duplication
570
646
  session_state_copy = copy(session_state) if session_state is not None else {}
571
647
 
@@ -670,8 +746,22 @@ class Step:
670
746
  if isinstance(self.active_executor, Team):
671
747
  kwargs["store_member_responses"] = True
672
748
 
749
+ num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
750
+
751
+ use_history = (
752
+ self.add_workflow_history
753
+ if self.add_workflow_history is not None
754
+ else add_workflow_history_to_steps
755
+ )
756
+
757
+ final_message = message
758
+ if use_history and workflow_session:
759
+ history_messages = workflow_session.get_workflow_history_context(num_runs=num_history_runs)
760
+ if history_messages:
761
+ final_message = f"{history_messages}{message}"
762
+
673
763
  response = await self.active_executor.arun( # type: ignore
674
- input=message, # type: ignore
764
+ input=final_message, # type: ignore
675
765
  images=images,
676
766
  videos=videos,
677
767
  audio=audios,
@@ -724,12 +814,18 @@ class Step:
724
814
  step_index: Optional[Union[int, tuple]] = None,
725
815
  store_executor_outputs: bool = True,
726
816
  parent_step_id: Optional[str] = None,
817
+ workflow_session: Optional["WorkflowSession"] = None,
818
+ add_workflow_history_to_steps: Optional[bool] = False,
819
+ num_history_runs: int = 3,
727
820
  ) -> AsyncIterator[Union[WorkflowRunOutputEvent, StepOutput]]:
728
821
  """Execute the step with event-driven streaming support"""
729
822
 
730
823
  if step_input.previous_step_outputs:
731
824
  step_input.previous_step_content = step_input.get_last_step_content()
732
825
 
826
+ if workflow_session:
827
+ step_input.workflow_session = workflow_session
828
+
733
829
  # Create session_state copy once to avoid duplication
734
830
  session_state_copy = copy(session_state) if session_state is not None else {}
735
831
 
@@ -776,7 +872,11 @@ class Step:
776
872
  final_response = event
777
873
  break
778
874
  else:
779
- yield event # type: ignore[misc]
875
+ # Enrich event with workflow context before yielding
876
+ enriched_event = self._enrich_event_with_context(
877
+ event, workflow_run_response, step_index
878
+ )
879
+ yield enriched_event # type: ignore[misc]
780
880
  if not final_response:
781
881
  final_response = StepOutput(content=content)
782
882
  elif inspect.iscoroutinefunction(self.active_executor):
@@ -803,7 +903,11 @@ class Step:
803
903
  final_response = event
804
904
  break
805
905
  else:
806
- yield event # type: ignore[misc]
906
+ # Enrich event with workflow context before yielding
907
+ enriched_event = self._enrich_event_with_context(
908
+ event, workflow_run_response, step_index
909
+ )
910
+ yield enriched_event # type: ignore[misc]
807
911
  if not final_response:
808
912
  final_response = StepOutput(content=content)
809
913
  else:
@@ -843,8 +947,22 @@ class Step:
843
947
  if isinstance(self.active_executor, Team):
844
948
  kwargs["store_member_responses"] = True
845
949
 
950
+ num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
951
+
952
+ use_history = (
953
+ self.add_workflow_history
954
+ if self.add_workflow_history is not None
955
+ else add_workflow_history_to_steps
956
+ )
957
+
958
+ final_message = message
959
+ if use_history and workflow_session:
960
+ history_messages = workflow_session.get_workflow_history_context(num_runs=num_history_runs)
961
+ if history_messages:
962
+ final_message = f"{history_messages}{message}"
963
+
846
964
  response_stream = self.active_executor.arun( # type: ignore
847
- input=message,
965
+ input=final_message,
848
966
  images=images,
849
967
  videos=videos,
850
968
  audio=audios,
@@ -854,14 +972,6 @@ class Step:
854
972
  session_state=session_state_copy,
855
973
  stream=True,
856
974
  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
975
  yield_run_response=True,
866
976
  **kwargs,
867
977
  )
@@ -871,7 +981,10 @@ class Step:
871
981
  if isinstance(event, RunOutput) or isinstance(event, TeamRunOutput):
872
982
  active_executor_run_response = event
873
983
  break
874
- yield event # type: ignore[misc]
984
+ enriched_event = self._enrich_event_with_context(
985
+ event, workflow_run_response, step_index
986
+ )
987
+ yield enriched_event # type: ignore[misc]
875
988
 
876
989
  if session_state is not None:
877
990
  # 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)
@@ -204,6 +211,9 @@ class Steps:
204
211
  step_index: Optional[Union[int, tuple]] = None,
205
212
  store_executor_outputs: bool = True,
206
213
  parent_step_id: Optional[str] = None,
214
+ workflow_session: Optional[WorkflowSession] = None,
215
+ add_workflow_history_to_steps: Optional[bool] = False,
216
+ num_history_runs: int = 3,
207
217
  ) -> Iterator[Union[WorkflowRunOutputEvent, TeamRunOutputEvent, RunOutputEvent, StepOutput]]:
208
218
  """Execute all steps in sequence with streaming support"""
209
219
  log_debug(f"Steps Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="-")
@@ -260,6 +270,9 @@ class Steps:
260
270
  step_index=child_step_index,
261
271
  store_executor_outputs=store_executor_outputs,
262
272
  parent_step_id=steps_id,
273
+ workflow_session=workflow_session,
274
+ add_workflow_history_to_steps=add_workflow_history_to_steps,
275
+ num_history_runs=num_history_runs,
263
276
  ):
264
277
  if isinstance(event, StepOutput):
265
278
  step_outputs_for_step.append(event)
@@ -337,6 +350,9 @@ class Steps:
337
350
  workflow_run_response: Optional[WorkflowRunOutput] = None,
338
351
  session_state: Optional[Dict[str, Any]] = None,
339
352
  store_executor_outputs: bool = True,
353
+ workflow_session: Optional[WorkflowSession] = None,
354
+ add_workflow_history_to_steps: Optional[bool] = False,
355
+ num_history_runs: int = 3,
340
356
  ) -> StepOutput:
341
357
  """Execute all steps in sequence asynchronously and return the final result"""
342
358
  log_debug(f"Steps Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="-")
@@ -366,6 +382,9 @@ class Steps:
366
382
  workflow_run_response=workflow_run_response,
367
383
  store_executor_outputs=store_executor_outputs,
368
384
  session_state=session_state,
385
+ workflow_session=workflow_session,
386
+ add_workflow_history_to_steps=add_workflow_history_to_steps,
387
+ num_history_runs=num_history_runs,
369
388
  )
370
389
 
371
390
  # Handle both single StepOutput and List[StepOutput] (from Loop/Condition/Router steps)
@@ -421,6 +440,9 @@ class Steps:
421
440
  step_index: Optional[Union[int, tuple]] = None,
422
441
  store_executor_outputs: bool = True,
423
442
  parent_step_id: Optional[str] = None,
443
+ workflow_session: Optional[WorkflowSession] = None,
444
+ add_workflow_history_to_steps: Optional[bool] = False,
445
+ num_history_runs: int = 3,
424
446
  ) -> AsyncIterator[Union[WorkflowRunOutputEvent, TeamRunOutputEvent, RunOutputEvent, StepOutput]]:
425
447
  """Execute all steps in sequence with async streaming support"""
426
448
  log_debug(f"Steps Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="-")
@@ -477,6 +499,9 @@ class Steps:
477
499
  step_index=child_step_index,
478
500
  store_executor_outputs=store_executor_outputs,
479
501
  parent_step_id=steps_id,
502
+ workflow_session=workflow_session,
503
+ add_workflow_history_to_steps=add_workflow_history_to_steps,
504
+ num_history_runs=num_history_runs,
480
505
  ):
481
506
  if isinstance(event, StepOutput):
482
507
  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