agno 2.2.8__py3-none-any.whl → 2.2.10__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 (50) hide show
  1. agno/agent/agent.py +37 -19
  2. agno/db/base.py +23 -0
  3. agno/db/dynamo/dynamo.py +20 -25
  4. agno/db/dynamo/schemas.py +1 -0
  5. agno/db/firestore/firestore.py +11 -0
  6. agno/db/gcs_json/gcs_json_db.py +4 -0
  7. agno/db/in_memory/in_memory_db.py +4 -0
  8. agno/db/json/json_db.py +4 -0
  9. agno/db/mongo/async_mongo.py +27 -0
  10. agno/db/mongo/mongo.py +25 -0
  11. agno/db/mysql/mysql.py +26 -1
  12. agno/db/postgres/async_postgres.py +26 -1
  13. agno/db/postgres/postgres.py +26 -1
  14. agno/db/redis/redis.py +4 -0
  15. agno/db/singlestore/singlestore.py +24 -0
  16. agno/db/sqlite/async_sqlite.py +25 -1
  17. agno/db/sqlite/sqlite.py +25 -1
  18. agno/db/surrealdb/surrealdb.py +13 -1
  19. agno/knowledge/reader/docx_reader.py +0 -1
  20. agno/models/azure/ai_foundry.py +2 -1
  21. agno/models/cerebras/cerebras.py +3 -2
  22. agno/models/openai/chat.py +2 -1
  23. agno/models/openai/responses.py +2 -1
  24. agno/os/app.py +127 -65
  25. agno/os/config.py +1 -0
  26. agno/os/interfaces/agui/router.py +9 -0
  27. agno/os/interfaces/agui/utils.py +49 -3
  28. agno/os/mcp.py +8 -8
  29. agno/os/router.py +27 -9
  30. agno/os/routers/evals/evals.py +12 -7
  31. agno/os/routers/memory/memory.py +18 -10
  32. agno/os/routers/metrics/metrics.py +6 -4
  33. agno/os/routers/session/session.py +21 -11
  34. agno/os/utils.py +57 -11
  35. agno/team/team.py +33 -23
  36. agno/vectordb/mongodb/__init__.py +7 -1
  37. agno/vectordb/redis/__init__.py +4 -0
  38. agno/workflow/agent.py +2 -2
  39. agno/workflow/condition.py +26 -4
  40. agno/workflow/loop.py +9 -0
  41. agno/workflow/parallel.py +39 -16
  42. agno/workflow/router.py +25 -4
  43. agno/workflow/step.py +162 -91
  44. agno/workflow/steps.py +9 -0
  45. agno/workflow/workflow.py +26 -22
  46. {agno-2.2.8.dist-info → agno-2.2.10.dist-info}/METADATA +11 -13
  47. {agno-2.2.8.dist-info → agno-2.2.10.dist-info}/RECORD +50 -50
  48. {agno-2.2.8.dist-info → agno-2.2.10.dist-info}/WHEEL +0 -0
  49. {agno-2.2.8.dist-info → agno-2.2.10.dist-info}/licenses/LICENSE +0 -0
  50. {agno-2.2.8.dist-info → agno-2.2.10.dist-info}/top_level.txt +0 -0
agno/workflow/router.py CHANGED
@@ -4,6 +4,7 @@ from typing import Any, AsyncIterator, Awaitable, Callable, Dict, Iterator, List
4
4
  from uuid import uuid4
5
5
 
6
6
  from agno.run.agent import RunOutputEvent
7
+ from agno.run.base import RunContext
7
8
  from agno.run.team import TeamRunOutputEvent
8
9
  from agno.run.workflow import (
9
10
  RouterExecutionCompletedEvent,
@@ -171,6 +172,7 @@ class Router:
171
172
  session_id: Optional[str] = None,
172
173
  user_id: Optional[str] = None,
173
174
  workflow_run_response: Optional[WorkflowRunOutput] = None,
175
+ run_context: Optional[RunContext] = None,
174
176
  session_state: Optional[Dict[str, Any]] = None,
175
177
  store_executor_outputs: bool = True,
176
178
  workflow_session: Optional[WorkflowSession] = None,
@@ -185,7 +187,10 @@ class Router:
185
187
  self._prepare_steps()
186
188
 
187
189
  # Route to appropriate steps
188
- steps_to_execute = self._route_steps(step_input, session_state)
190
+ if run_context is not None and run_context.session_state is not None:
191
+ steps_to_execute = self._route_steps(step_input, session_state=run_context.session_state)
192
+ else:
193
+ steps_to_execute = self._route_steps(step_input, session_state=session_state)
189
194
  log_debug(f"Router {self.name}: Selected {len(steps_to_execute)} steps to execute")
190
195
 
191
196
  if not steps_to_execute:
@@ -209,6 +214,7 @@ class Router:
209
214
  user_id=user_id,
210
215
  workflow_run_response=workflow_run_response,
211
216
  store_executor_outputs=store_executor_outputs,
217
+ run_context=run_context,
212
218
  session_state=session_state,
213
219
  workflow_session=workflow_session,
214
220
  add_workflow_history_to_steps=add_workflow_history_to_steps,
@@ -266,6 +272,7 @@ class Router:
266
272
  step_input: StepInput,
267
273
  session_id: Optional[str] = None,
268
274
  user_id: Optional[str] = None,
275
+ run_context: Optional[RunContext] = None,
269
276
  session_state: Optional[Dict[str, Any]] = None,
270
277
  stream_events: bool = False,
271
278
  stream_intermediate_steps: bool = False,
@@ -286,7 +293,10 @@ class Router:
286
293
  router_step_id = str(uuid4())
287
294
 
288
295
  # Route to appropriate steps
289
- steps_to_execute = self._route_steps(step_input, session_state)
296
+ if run_context is not None and run_context.session_state is not None:
297
+ steps_to_execute = self._route_steps(step_input, session_state=run_context.session_state)
298
+ else:
299
+ steps_to_execute = self._route_steps(step_input, session_state=session_state)
290
300
  log_debug(f"Router {self.name}: Selected {len(steps_to_execute)} steps to execute")
291
301
 
292
302
  # Considering both stream_events and stream_intermediate_steps (deprecated)
@@ -341,6 +351,7 @@ class Router:
341
351
  workflow_run_response=workflow_run_response,
342
352
  step_index=step_index,
343
353
  store_executor_outputs=store_executor_outputs,
354
+ run_context=run_context,
344
355
  session_state=session_state,
345
356
  parent_step_id=router_step_id,
346
357
  workflow_session=workflow_session,
@@ -425,6 +436,7 @@ class Router:
425
436
  session_id: Optional[str] = None,
426
437
  user_id: Optional[str] = None,
427
438
  workflow_run_response: Optional[WorkflowRunOutput] = None,
439
+ run_context: Optional[RunContext] = None,
428
440
  session_state: Optional[Dict[str, Any]] = None,
429
441
  store_executor_outputs: bool = True,
430
442
  workflow_session: Optional[WorkflowSession] = None,
@@ -439,7 +451,10 @@ class Router:
439
451
  self._prepare_steps()
440
452
 
441
453
  # Route to appropriate steps
442
- steps_to_execute = await self._aroute_steps(step_input, session_state)
454
+ if run_context is not None and run_context.session_state is not None:
455
+ steps_to_execute = await self._aroute_steps(step_input, session_state=run_context.session_state)
456
+ else:
457
+ steps_to_execute = await self._aroute_steps(step_input, session_state=session_state)
443
458
  log_debug(f"Router {self.name} selected: {len(steps_to_execute)} steps to execute")
444
459
 
445
460
  if not steps_to_execute:
@@ -464,6 +479,7 @@ class Router:
464
479
  user_id=user_id,
465
480
  workflow_run_response=workflow_run_response,
466
481
  store_executor_outputs=store_executor_outputs,
482
+ run_context=run_context,
467
483
  session_state=session_state,
468
484
  workflow_session=workflow_session,
469
485
  add_workflow_history_to_steps=add_workflow_history_to_steps,
@@ -523,6 +539,7 @@ class Router:
523
539
  step_input: StepInput,
524
540
  session_id: Optional[str] = None,
525
541
  user_id: Optional[str] = None,
542
+ run_context: Optional[RunContext] = None,
526
543
  session_state: Optional[Dict[str, Any]] = None,
527
544
  stream_events: bool = False,
528
545
  stream_intermediate_steps: bool = False,
@@ -543,7 +560,10 @@ class Router:
543
560
  router_step_id = str(uuid4())
544
561
 
545
562
  # Route to appropriate steps
546
- steps_to_execute = await self._aroute_steps(step_input, session_state)
563
+ if run_context is not None and run_context.session_state is not None:
564
+ steps_to_execute = await self._aroute_steps(step_input, session_state=run_context.session_state)
565
+ else:
566
+ steps_to_execute = await self._aroute_steps(step_input, session_state=session_state)
547
567
  log_debug(f"Router {self.name} selected: {len(steps_to_execute)} steps to execute")
548
568
 
549
569
  # Considering both stream_events and stream_intermediate_steps (deprecated)
@@ -600,6 +620,7 @@ class Router:
600
620
  workflow_run_response=workflow_run_response,
601
621
  step_index=step_index,
602
622
  store_executor_outputs=store_executor_outputs,
623
+ run_context=run_context,
603
624
  session_state=session_state,
604
625
  parent_step_id=router_step_id,
605
626
  workflow_session=workflow_session,
agno/workflow/step.py CHANGED
@@ -11,7 +11,9 @@ 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, RunOutput, RunContentEvent
15
+ from agno.run.base import BaseRunOutputEvent
16
+ from agno.run.team import RunCompletedEvent as TeamRunCompletedEvent, RunContentEvent as TeamRunContentEvent
15
17
  from agno.run.team import TeamRunOutput
16
18
  from agno.run.workflow import (
17
19
  StepCompletedEvent,
@@ -179,31 +181,37 @@ class Step:
179
181
  func: Callable,
180
182
  step_input: StepInput,
181
183
  session_state: Optional[Dict[str, Any]] = None,
184
+ run_context: Optional[RunContext] = None,
182
185
  ) -> Any:
183
186
  """Call custom function with session_state support if the function accepts it"""
187
+
188
+ kwargs: Dict[str, Any] = {}
189
+ if run_context is not None and self._function_has_run_context_param():
190
+ kwargs["run_context"] = run_context
184
191
  if session_state is not None and self._function_has_session_state_param():
185
- return func(step_input, session_state)
186
- else:
187
- return func(step_input)
192
+ kwargs["session_state"] = session_state
193
+
194
+ return func(step_input, **kwargs)
188
195
 
189
196
  async def _acall_custom_function(
190
197
  self,
191
198
  func: Callable,
192
199
  step_input: StepInput,
193
200
  session_state: Optional[Dict[str, Any]] = None,
201
+ run_context: Optional[RunContext] = None,
194
202
  ) -> Any:
195
203
  """Call custom async function with session_state support if the function accepts it"""
196
204
 
205
+ kwargs: Dict[str, Any] = {}
206
+ if run_context is not None and self._function_has_run_context_param():
207
+ kwargs["run_context"] = run_context
208
+ if session_state is not None and self._function_has_session_state_param():
209
+ kwargs["session_state"] = session_state
210
+
197
211
  if _is_async_generator_function(func):
198
- if session_state is not None and self._function_has_session_state_param():
199
- return func(step_input, session_state)
200
- else:
201
- return func(step_input)
212
+ return func(step_input, **kwargs)
202
213
  else:
203
- if session_state is not None and self._function_has_session_state_param():
204
- return await func(step_input, session_state)
205
- else:
206
- return await func(step_input)
214
+ return await func(step_input, **kwargs)
207
215
 
208
216
  def execute(
209
217
  self,
@@ -227,8 +235,10 @@ class Step:
227
235
  if workflow_session:
228
236
  step_input.workflow_session = workflow_session
229
237
 
238
+ # Create session_state copy once to avoid duplication.
239
+ # Consider both run_context.session_state and session_state.
230
240
  if run_context is not None and run_context.session_state is not None:
231
- session_state_copy = copy(run_context.session_state)
241
+ session_state_copy = run_context.session_state
232
242
  else:
233
243
  session_state_copy = copy(session_state) if session_state is not None else {}
234
244
 
@@ -247,13 +257,13 @@ class Step:
247
257
  self.active_executor,
248
258
  step_input,
249
259
  session_state_copy, # type: ignore[arg-type]
260
+ run_context,
250
261
  ): # type: ignore
251
- if (
252
- hasattr(chunk, "content")
253
- and chunk.content is not None
254
- and isinstance(chunk.content, str)
255
- ):
256
- content += chunk.content
262
+ if isinstance(chunk, (BaseRunOutputEvent)):
263
+ if isinstance(chunk, (RunContentEvent, TeamRunContentEvent)):
264
+ content += chunk.content if chunk.content is not None else ""
265
+ elif isinstance(chunk, (RunCompletedEvent, TeamRunCompletedEvent)):
266
+ content = chunk.content if chunk.content is not None else ""
257
267
  else:
258
268
  content += str(chunk)
259
269
  if isinstance(chunk, StepOutput):
@@ -264,7 +274,7 @@ class Step:
264
274
  final_response = e.value
265
275
 
266
276
  # Merge session_state changes back
267
- if session_state is not None:
277
+ if run_context is None and session_state is not None:
268
278
  merge_dictionaries(session_state, session_state_copy)
269
279
 
270
280
  if final_response is not None:
@@ -273,10 +283,15 @@ class Step:
273
283
  response = StepOutput(content=content)
274
284
  else:
275
285
  # Execute function with signature inspection for session_state support
276
- result = self._call_custom_function(self.active_executor, step_input, session_state_copy) # type: ignore
286
+ result = self._call_custom_function(
287
+ self.active_executor, # type: ignore[arg-type]
288
+ step_input,
289
+ session_state_copy,
290
+ run_context,
291
+ )
277
292
 
278
293
  # Merge session_state changes back
279
- if session_state is not None:
294
+ if run_context is None and session_state is not None:
280
295
  merge_dictionaries(session_state, session_state_copy)
281
296
 
282
297
  # If function returns StepOutput, use it directly
@@ -334,12 +349,13 @@ class Step:
334
349
  session_id=session_id,
335
350
  user_id=user_id,
336
351
  session_state=session_state_copy, # Send a copy to the executor
352
+ run_context=run_context,
337
353
  **kwargs,
338
354
  )
339
355
 
340
- if session_state is not None:
341
- # Update workflow session state
342
- merge_dictionaries(session_state, session_state_copy) # type: ignore
356
+ # Update workflow session state
357
+ if run_context is None and session_state is not None:
358
+ merge_dictionaries(session_state, session_state_copy)
343
359
 
344
360
  if store_executor_outputs and workflow_run_response is not None:
345
361
  self._store_executor_response(workflow_run_response, response) # type: ignore
@@ -368,6 +384,17 @@ class Step:
368
384
 
369
385
  return StepOutput(content=f"Step {self.name} failed but skipped", success=False)
370
386
 
387
+ def _function_has_run_context_param(self) -> bool:
388
+ """Check if the custom function has a run_context parameter"""
389
+ if self._executor_type != "function":
390
+ return False
391
+
392
+ try:
393
+ sig = inspect.signature(self.active_executor) # type: ignore
394
+ return "run_context" in sig.parameters
395
+ except Exception:
396
+ return False
397
+
371
398
  def _function_has_session_state_param(self) -> bool:
372
399
  """Check if the custom function has a session_state parameter"""
373
400
  if self._executor_type != "function":
@@ -430,9 +457,10 @@ class Step:
430
457
  if workflow_session:
431
458
  step_input.workflow_session = workflow_session
432
459
 
433
- # Create session_state copy once to avoid duplication
460
+ # Create session_state copy once to avoid duplication.
461
+ # Consider both run_context.session_state and session_state.
434
462
  if run_context is not None and run_context.session_state is not None:
435
- session_state_copy = copy(run_context.session_state)
463
+ session_state_copy = run_context.session_state
436
464
  else:
437
465
  session_state_copy = copy(session_state) if session_state is not None else {}
438
466
 
@@ -460,22 +488,24 @@ class Step:
460
488
 
461
489
  if self._executor_type == "function":
462
490
  log_debug(f"Executing function executor for step: {self.name}")
463
-
464
491
  if _is_async_callable(self.active_executor) or _is_async_generator_function(self.active_executor):
465
492
  raise ValueError("Cannot use async function with synchronous execution")
466
-
467
493
  if _is_generator_function(self.active_executor):
468
494
  log_debug("Function returned iterable, streaming events")
469
495
  content = ""
470
496
  try:
471
- iterator = self._call_custom_function(self.active_executor, step_input, session_state_copy) # type: ignore
497
+ iterator = self._call_custom_function(
498
+ self.active_executor,
499
+ step_input,
500
+ session_state_copy,
501
+ run_context,
502
+ )
472
503
  for event in iterator: # type: ignore
473
- if (
474
- hasattr(event, "content")
475
- and event.content is not None
476
- and isinstance(event.content, str)
477
- ):
478
- content += event.content
504
+ if isinstance(event, (BaseRunOutputEvent)):
505
+ if isinstance(event, (RunContentEvent, TeamRunContentEvent)):
506
+ content += event.content if event.content is not None else ""
507
+ elif isinstance(event, (RunCompletedEvent, TeamRunCompletedEvent)):
508
+ content = event.content if event.content is not None else ""
479
509
  else:
480
510
  content += str(event)
481
511
  if isinstance(event, StepOutput):
@@ -491,7 +521,7 @@ class Step:
491
521
  yield enriched_event # type: ignore[misc]
492
522
 
493
523
  # Merge session_state changes back
494
- if session_state is not None:
524
+ if run_context is None and session_state is not None:
495
525
  merge_dictionaries(session_state, session_state_copy)
496
526
 
497
527
  if not final_response:
@@ -501,10 +531,15 @@ class Step:
501
531
  final_response = e.value
502
532
 
503
533
  else:
504
- result = self._call_custom_function(self.active_executor, step_input, session_state_copy) # type: ignore
534
+ result = self._call_custom_function(
535
+ self.active_executor, # type: ignore[arg-type]
536
+ step_input,
537
+ session_state_copy,
538
+ run_context,
539
+ )
505
540
 
506
541
  # Merge session_state changes back
507
- if session_state is not None:
542
+ if run_context is None and session_state is not None:
508
543
  merge_dictionaries(session_state, session_state_copy)
509
544
 
510
545
  if isinstance(result, StepOutput):
@@ -564,6 +599,7 @@ class Step:
564
599
  stream=True,
565
600
  stream_events=stream_events,
566
601
  yield_run_response=True,
602
+ run_context=run_context,
567
603
  **kwargs,
568
604
  )
569
605
 
@@ -577,9 +613,9 @@ class Step:
577
613
  if stream_executor_events:
578
614
  yield enriched_event # type: ignore[misc]
579
615
 
580
- if session_state is not None:
581
- # Update workflow session state
582
- merge_dictionaries(session_state, session_state_copy) # type: ignore
616
+ # Update workflow session state
617
+ if run_context is None and session_state is not None:
618
+ merge_dictionaries(session_state, session_state_copy)
583
619
 
584
620
  if store_executor_outputs and workflow_run_response is not None:
585
621
  self._store_executor_response(workflow_run_response, active_executor_run_response) # type: ignore
@@ -640,6 +676,7 @@ class Step:
640
676
  session_id: Optional[str] = None,
641
677
  user_id: Optional[str] = None,
642
678
  workflow_run_response: Optional["WorkflowRunOutput"] = None,
679
+ run_context: Optional[RunContext] = None,
643
680
  session_state: Optional[Dict[str, Any]] = None,
644
681
  store_executor_outputs: bool = True,
645
682
  workflow_session: Optional["WorkflowSession"] = None,
@@ -655,8 +692,13 @@ class Step:
655
692
 
656
693
  if workflow_session:
657
694
  step_input.workflow_session = workflow_session
658
- # Create session_state copy once to avoid duplication
659
- session_state_copy = copy(session_state) if session_state is not None else {}
695
+
696
+ # Create session_state copy once to avoid duplication.
697
+ # Consider both run_context.session_state and session_state.
698
+ if run_context is not None and run_context.session_state is not None:
699
+ session_state_copy = run_context.session_state
700
+ else:
701
+ session_state_copy = copy(session_state) if session_state is not None else {}
660
702
 
661
703
  # Execute with retries
662
704
  for attempt in range(self.max_retries + 1):
@@ -672,15 +714,15 @@ class Step:
672
714
  iterator = self._call_custom_function(
673
715
  self.active_executor,
674
716
  step_input,
675
- session_state_copy, # type: ignore[arg-type]
676
- ) # type: ignore
717
+ session_state_copy,
718
+ run_context,
719
+ )
677
720
  for chunk in iterator: # type: ignore
678
- if (
679
- hasattr(chunk, "content")
680
- and chunk.content is not None
681
- and isinstance(chunk.content, str)
682
- ):
683
- content += chunk.content
721
+ if isinstance(chunk, (BaseRunOutputEvent)):
722
+ if isinstance(chunk, (RunContentEvent, TeamRunContentEvent)):
723
+ content += chunk.content if chunk.content is not None else ""
724
+ elif isinstance(chunk, (RunCompletedEvent, TeamRunCompletedEvent)):
725
+ content = chunk.content if chunk.content is not None else ""
684
726
  else:
685
727
  content += str(chunk)
686
728
  if isinstance(chunk, StepOutput):
@@ -690,15 +732,15 @@ class Step:
690
732
  iterator = await self._acall_custom_function(
691
733
  self.active_executor,
692
734
  step_input,
693
- session_state_copy, # type: ignore[arg-type]
694
- ) # type: ignore
735
+ session_state_copy,
736
+ run_context,
737
+ )
695
738
  async for chunk in iterator: # type: ignore
696
- if (
697
- hasattr(chunk, "content")
698
- and chunk.content is not None
699
- and isinstance(chunk.content, str)
700
- ):
701
- content += chunk.content
739
+ if isinstance(chunk, (BaseRunOutputEvent)):
740
+ if isinstance(chunk, (RunContentEvent, TeamRunContentEvent)):
741
+ content += chunk.content if chunk.content is not None else ""
742
+ elif isinstance(chunk, (RunCompletedEvent, TeamRunCompletedEvent)):
743
+ content = chunk.content if chunk.content is not None else ""
702
744
  else:
703
745
  content += str(chunk)
704
746
  if isinstance(chunk, StepOutput):
@@ -709,7 +751,7 @@ class Step:
709
751
  final_response = e.value
710
752
 
711
753
  # Merge session_state changes back
712
- if session_state is not None:
754
+ if run_context is None and session_state is not None:
713
755
  merge_dictionaries(session_state, session_state_copy)
714
756
 
715
757
  if final_response is not None:
@@ -719,13 +761,21 @@ class Step:
719
761
  else:
720
762
  if _is_async_callable(self.active_executor):
721
763
  result = await self._acall_custom_function(
722
- self.active_executor, step_input, session_state_copy
723
- ) # type: ignore
764
+ self.active_executor,
765
+ step_input,
766
+ session_state_copy,
767
+ run_context,
768
+ )
724
769
  else:
725
- result = self._call_custom_function(self.active_executor, step_input, session_state_copy) # type: ignore
770
+ result = self._call_custom_function(
771
+ self.active_executor, # type: ignore[arg-type]
772
+ step_input,
773
+ session_state_copy,
774
+ run_context,
775
+ )
726
776
 
727
777
  # Merge session_state changes back
728
- if session_state is not None:
778
+ if run_context is None and session_state is not None:
729
779
  merge_dictionaries(session_state, session_state_copy)
730
780
 
731
781
  # If function returns StepOutput, use it directly
@@ -784,12 +834,13 @@ class Step:
784
834
  session_id=session_id,
785
835
  user_id=user_id,
786
836
  session_state=session_state_copy,
837
+ run_context=run_context,
787
838
  **kwargs,
788
839
  )
789
840
 
790
- if session_state is not None:
791
- # Update workflow session state
792
- merge_dictionaries(session_state, session_state_copy) # type: ignore
841
+ # Update workflow session state
842
+ if run_context is None and session_state is not None:
843
+ merge_dictionaries(session_state, session_state_copy)
793
844
 
794
845
  if store_executor_outputs and workflow_run_response is not None:
795
846
  self._store_executor_response(workflow_run_response, response) # type: ignore
@@ -827,6 +878,7 @@ class Step:
827
878
  stream_intermediate_steps: bool = False,
828
879
  stream_executor_events: bool = True,
829
880
  workflow_run_response: Optional["WorkflowRunOutput"] = None,
881
+ run_context: Optional[RunContext] = None,
830
882
  session_state: Optional[Dict[str, Any]] = None,
831
883
  step_index: Optional[Union[int, tuple]] = None,
832
884
  store_executor_outputs: bool = True,
@@ -843,8 +895,12 @@ class Step:
843
895
  if workflow_session:
844
896
  step_input.workflow_session = workflow_session
845
897
 
846
- # Create session_state copy once to avoid duplication
847
- session_state_copy = copy(session_state) if session_state is not None else {}
898
+ # Create session_state copy once to avoid duplication.
899
+ # Consider both run_context.session_state and session_state.
900
+ if run_context is not None and run_context.session_state is not None:
901
+ session_state_copy = run_context.session_state
902
+ else:
903
+ session_state_copy = copy(session_state) if session_state is not None else {}
848
904
 
849
905
  # Considering both stream_events and stream_intermediate_steps (deprecated)
850
906
  stream_events = stream_events or stream_intermediate_steps
@@ -878,15 +934,15 @@ class Step:
878
934
  iterator = await self._acall_custom_function(
879
935
  self.active_executor,
880
936
  step_input,
881
- session_state_copy, # type: ignore[arg-type]
882
- ) # type: ignore
937
+ session_state_copy,
938
+ run_context,
939
+ )
883
940
  async for event in iterator: # type: ignore
884
- if (
885
- hasattr(event, "content")
886
- and event.content is not None
887
- and isinstance(event.content, str)
888
- ):
889
- content += event.content
941
+ if isinstance(event, (BaseRunOutputEvent)):
942
+ if isinstance(event, (RunContentEvent, TeamRunContentEvent)):
943
+ content += event.content if event.content is not None else ""
944
+ elif isinstance(event, (RunCompletedEvent, TeamRunCompletedEvent)):
945
+ content = event.content if event.content is not None else ""
890
946
  else:
891
947
  content += str(event)
892
948
  if isinstance(event, StepOutput):
@@ -904,7 +960,12 @@ class Step:
904
960
  final_response = StepOutput(content=content)
905
961
  elif _is_async_callable(self.active_executor):
906
962
  # It's a regular async function - await it
907
- result = await self._acall_custom_function(self.active_executor, step_input, session_state_copy) # type: ignore
963
+ result = await self._acall_custom_function(
964
+ self.active_executor,
965
+ step_input,
966
+ session_state_copy,
967
+ run_context,
968
+ )
908
969
  if isinstance(result, StepOutput):
909
970
  final_response = result
910
971
  else:
@@ -912,14 +973,18 @@ class Step:
912
973
  elif _is_generator_function(self.active_executor):
913
974
  content = ""
914
975
  # It's a regular generator function - iterate over it
915
- iterator = self._call_custom_function(self.active_executor, step_input, session_state_copy) # type: ignore
976
+ iterator = self._call_custom_function(
977
+ self.active_executor,
978
+ step_input,
979
+ session_state_copy,
980
+ run_context,
981
+ )
916
982
  for event in iterator: # type: ignore
917
- if (
918
- hasattr(event, "content")
919
- and event.content is not None
920
- and isinstance(event.content, str)
921
- ):
922
- content += event.content
983
+ if isinstance(event, (BaseRunOutputEvent)):
984
+ if isinstance(event, (RunContentEvent, TeamRunContentEvent)):
985
+ content += event.content if event.content is not None else ""
986
+ elif isinstance(event, (RunCompletedEvent, TeamRunCompletedEvent)):
987
+ content = event.content if event.content is not None else ""
923
988
  else:
924
989
  content += str(event)
925
990
  if isinstance(event, StepOutput):
@@ -937,14 +1002,19 @@ class Step:
937
1002
  final_response = StepOutput(content=content)
938
1003
  else:
939
1004
  # It's a regular function - call it directly
940
- result = self._call_custom_function(self.active_executor, step_input, session_state_copy) # type: ignore
1005
+ result = self._call_custom_function(
1006
+ self.active_executor, # type: ignore[arg-type]
1007
+ step_input,
1008
+ session_state_copy,
1009
+ run_context,
1010
+ )
941
1011
  if isinstance(result, StepOutput):
942
1012
  final_response = result
943
1013
  else:
944
1014
  final_response = StepOutput(content=str(result))
945
1015
 
946
1016
  # Merge session_state changes back
947
- if session_state is not None:
1017
+ if run_context is None and session_state is not None:
948
1018
  merge_dictionaries(session_state, session_state_copy)
949
1019
  else:
950
1020
  # For agents and teams, prepare message with context
@@ -997,6 +1067,7 @@ class Step:
997
1067
  session_state=session_state_copy,
998
1068
  stream=True,
999
1069
  stream_events=stream_events,
1070
+ run_context=run_context,
1000
1071
  yield_run_response=True,
1001
1072
  **kwargs,
1002
1073
  )
@@ -1011,9 +1082,9 @@ class Step:
1011
1082
  if stream_executor_events:
1012
1083
  yield enriched_event # type: ignore[misc]
1013
1084
 
1014
- if session_state is not None:
1015
- # Update workflow session state
1016
- merge_dictionaries(session_state, session_state_copy) # type: ignore
1085
+ # Update workflow session state
1086
+ if run_context is None and session_state is not None:
1087
+ merge_dictionaries(session_state, session_state_copy)
1017
1088
 
1018
1089
  if store_executor_outputs and workflow_run_response is not None:
1019
1090
  self._store_executor_response(workflow_run_response, active_executor_run_response) # type: ignore