agno 2.1.4__py3-none-any.whl → 2.1.6__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 (95) hide show
  1. agno/agent/agent.py +1775 -538
  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 +2 -2
  8. agno/db/firestore/firestore.py +2 -2
  9. agno/db/firestore/utils.py +4 -2
  10. agno/db/gcs_json/gcs_json_db.py +2 -2
  11. agno/db/in_memory/in_memory_db.py +2 -2
  12. agno/db/json/json_db.py +2 -2
  13. agno/db/migrations/v1_to_v2.py +43 -13
  14. agno/db/mongo/mongo.py +14 -6
  15. agno/db/mongo/utils.py +0 -4
  16. agno/db/mysql/mysql.py +23 -13
  17. agno/db/postgres/postgres.py +17 -6
  18. agno/db/redis/redis.py +2 -2
  19. agno/db/singlestore/singlestore.py +19 -10
  20. agno/db/sqlite/sqlite.py +22 -12
  21. agno/db/sqlite/utils.py +8 -3
  22. agno/db/surrealdb/__init__.py +3 -0
  23. agno/db/surrealdb/metrics.py +292 -0
  24. agno/db/surrealdb/models.py +259 -0
  25. agno/db/surrealdb/queries.py +71 -0
  26. agno/db/surrealdb/surrealdb.py +1193 -0
  27. agno/db/surrealdb/utils.py +87 -0
  28. agno/eval/accuracy.py +50 -43
  29. agno/eval/performance.py +6 -3
  30. agno/eval/reliability.py +6 -3
  31. agno/eval/utils.py +33 -16
  32. agno/exceptions.py +8 -2
  33. agno/knowledge/knowledge.py +260 -46
  34. agno/knowledge/reader/pdf_reader.py +4 -6
  35. agno/knowledge/reader/reader_factory.py +2 -3
  36. agno/memory/manager.py +254 -46
  37. agno/models/anthropic/claude.py +37 -0
  38. agno/os/app.py +8 -7
  39. agno/os/interfaces/a2a/router.py +3 -5
  40. agno/os/interfaces/agui/router.py +4 -1
  41. agno/os/interfaces/agui/utils.py +27 -6
  42. agno/os/interfaces/slack/router.py +2 -4
  43. agno/os/mcp.py +98 -41
  44. agno/os/router.py +23 -0
  45. agno/os/routers/evals/evals.py +52 -20
  46. agno/os/routers/evals/utils.py +14 -14
  47. agno/os/routers/knowledge/knowledge.py +130 -9
  48. agno/os/routers/knowledge/schemas.py +57 -0
  49. agno/os/routers/memory/memory.py +116 -44
  50. agno/os/routers/metrics/metrics.py +16 -6
  51. agno/os/routers/session/session.py +65 -22
  52. agno/os/schema.py +36 -0
  53. agno/os/utils.py +64 -11
  54. agno/reasoning/anthropic.py +80 -0
  55. agno/reasoning/gemini.py +73 -0
  56. agno/reasoning/openai.py +5 -0
  57. agno/reasoning/vertexai.py +76 -0
  58. agno/session/workflow.py +3 -3
  59. agno/team/team.py +968 -179
  60. agno/tools/googlesheets.py +20 -5
  61. agno/tools/mcp_toolbox.py +3 -3
  62. agno/tools/scrapegraph.py +1 -1
  63. agno/utils/models/claude.py +3 -1
  64. agno/utils/streamlit.py +1 -1
  65. agno/vectordb/base.py +22 -1
  66. agno/vectordb/cassandra/cassandra.py +9 -0
  67. agno/vectordb/chroma/chromadb.py +26 -6
  68. agno/vectordb/clickhouse/clickhousedb.py +9 -1
  69. agno/vectordb/couchbase/couchbase.py +11 -0
  70. agno/vectordb/lancedb/lance_db.py +20 -0
  71. agno/vectordb/langchaindb/langchaindb.py +11 -0
  72. agno/vectordb/lightrag/lightrag.py +9 -0
  73. agno/vectordb/llamaindex/llamaindexdb.py +15 -1
  74. agno/vectordb/milvus/milvus.py +23 -0
  75. agno/vectordb/mongodb/mongodb.py +22 -0
  76. agno/vectordb/pgvector/pgvector.py +19 -0
  77. agno/vectordb/pineconedb/pineconedb.py +35 -4
  78. agno/vectordb/qdrant/qdrant.py +24 -0
  79. agno/vectordb/singlestore/singlestore.py +25 -17
  80. agno/vectordb/surrealdb/surrealdb.py +18 -2
  81. agno/vectordb/upstashdb/upstashdb.py +26 -1
  82. agno/vectordb/weaviate/weaviate.py +18 -0
  83. agno/workflow/condition.py +4 -0
  84. agno/workflow/loop.py +4 -0
  85. agno/workflow/parallel.py +4 -0
  86. agno/workflow/router.py +4 -0
  87. agno/workflow/step.py +30 -14
  88. agno/workflow/steps.py +4 -0
  89. agno/workflow/types.py +2 -2
  90. agno/workflow/workflow.py +328 -61
  91. {agno-2.1.4.dist-info → agno-2.1.6.dist-info}/METADATA +100 -41
  92. {agno-2.1.4.dist-info → agno-2.1.6.dist-info}/RECORD +95 -82
  93. {agno-2.1.4.dist-info → agno-2.1.6.dist-info}/WHEEL +0 -0
  94. {agno-2.1.4.dist-info → agno-2.1.6.dist-info}/licenses/LICENSE +0 -0
  95. {agno-2.1.4.dist-info → agno-2.1.6.dist-info}/top_level.txt +0 -0
agno/workflow/types.py CHANGED
@@ -176,7 +176,7 @@ class StepInput:
176
176
 
177
177
  def get_workflow_history(self, num_runs: Optional[int] = None) -> List[Tuple[str, str]]:
178
178
  """Get workflow conversation history as structured data for custom function steps
179
-
179
+
180
180
  Args:
181
181
  num_runs: Number of recent runs to include. If None, returns all available history.
182
182
  """
@@ -187,7 +187,7 @@ class StepInput:
187
187
 
188
188
  def get_workflow_history_context(self, num_runs: Optional[int] = None) -> Optional[str]:
189
189
  """Get formatted workflow conversation history context for custom function steps
190
-
190
+
191
191
  Args:
192
192
  num_runs: Number of recent runs to include. If None, returns all available history.
193
193
  """
agno/workflow/workflow.py CHANGED
@@ -24,7 +24,7 @@ from fastapi import WebSocket
24
24
  from pydantic import BaseModel
25
25
 
26
26
  from agno.agent.agent import Agent
27
- from agno.db.base import BaseDb, SessionType
27
+ from agno.db.base import AsyncBaseDb, BaseDb, SessionType
28
28
  from agno.exceptions import InputCheckError, OutputCheckError, RunCancelledException
29
29
  from agno.media import Audio, File, Image, Video
30
30
  from agno.models.message import Message
@@ -129,7 +129,7 @@ class Workflow:
129
129
  steps: Optional[WorkflowSteps] = None
130
130
 
131
131
  # Database to use for this workflow
132
- db: Optional[BaseDb] = None
132
+ db: Optional[Union[BaseDb, AsyncBaseDb]] = None
133
133
 
134
134
  # Default session_id to use for this workflow (autogenerated if not set)
135
135
  session_id: Optional[str] = None
@@ -148,6 +148,8 @@ class Workflow:
148
148
  stream: Optional[bool] = None
149
149
  # Stream the intermediate steps from the Workflow
150
150
  stream_intermediate_steps: bool = False
151
+ # Stream events from executors (agents/teams/functions) within steps
152
+ stream_executor_events: bool = True
151
153
 
152
154
  # Persist the events on the run response
153
155
  store_events: bool = False
@@ -180,7 +182,7 @@ class Workflow:
180
182
  id: Optional[str] = None,
181
183
  name: Optional[str] = None,
182
184
  description: Optional[str] = None,
183
- db: Optional[BaseDb] = None,
185
+ db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
184
186
  steps: Optional[WorkflowSteps] = None,
185
187
  session_id: Optional[str] = None,
186
188
  session_state: Optional[Dict[str, Any]] = None,
@@ -189,6 +191,7 @@ class Workflow:
189
191
  debug_mode: Optional[bool] = False,
190
192
  stream: Optional[bool] = None,
191
193
  stream_intermediate_steps: bool = False,
194
+ stream_executor_events: bool = True,
192
195
  store_events: bool = False,
193
196
  events_to_skip: Optional[List[Union[WorkflowRunEvent, RunEvent, TeamRunEvent]]] = None,
194
197
  store_executor_outputs: bool = True,
@@ -212,6 +215,7 @@ class Workflow:
212
215
  self.events_to_skip = events_to_skip or []
213
216
  self.stream = stream
214
217
  self.stream_intermediate_steps = stream_intermediate_steps
218
+ self.stream_executor_events = stream_executor_events
215
219
  self.store_executor_outputs = store_executor_outputs
216
220
  self.input_schema = input_schema
217
221
  self.metadata = metadata
@@ -229,6 +233,9 @@ class Workflow:
229
233
  else:
230
234
  self.id = str(uuid4())
231
235
 
236
+ def _has_async_db(self) -> bool:
237
+ return self.db is not None and isinstance(self.db, AsyncBaseDb)
238
+
232
239
  def _validate_input(
233
240
  self, input: Optional[Union[str, Dict[str, Any], List[Any], BaseModel, List[Message]]]
234
241
  ) -> Optional[Union[str, List, Dict, Message, BaseModel]]:
@@ -401,6 +408,33 @@ class Workflow:
401
408
  new_session_name = f"{truncated_desc} - {datetime_str}"
402
409
  return new_session_name
403
410
 
411
+ async def aset_session_name(
412
+ self, session_id: Optional[str] = None, autogenerate: bool = False, session_name: Optional[str] = None
413
+ ) -> WorkflowSession:
414
+ """Set the session name and save to storage, using an async database"""
415
+ session_id = session_id or self.session_id
416
+
417
+ if session_id is None:
418
+ raise Exception("Session ID is not set")
419
+
420
+ # -*- Read from storage
421
+ session = await self.aget_session(session_id=session_id) # type: ignore
422
+
423
+ if autogenerate:
424
+ # -*- Generate name for session
425
+ session_name = self._generate_workflow_session_name()
426
+ log_debug(f"Generated Workflow Session Name: {session_name}")
427
+ elif session_name is None:
428
+ raise Exception("Session name is not set")
429
+
430
+ # -*- Rename session
431
+ session.session_data["session_name"] = session_name # type: ignore
432
+
433
+ # -*- Save to storage
434
+ await self.asave_session(session=session) # type: ignore
435
+
436
+ return session # type: ignore
437
+
404
438
  def set_session_name(
405
439
  self, session_id: Optional[str] = None, autogenerate: bool = False, session_name: Optional[str] = None
406
440
  ) -> WorkflowSession:
@@ -428,6 +462,16 @@ class Workflow:
428
462
 
429
463
  return session # type: ignore
430
464
 
465
+ async def aget_session_name(self, session_id: Optional[str] = None) -> str:
466
+ """Get the session name for the given session ID and user ID."""
467
+ session_id = session_id or self.session_id
468
+ if session_id is None:
469
+ raise Exception("Session ID is not set")
470
+ session = await self.aget_session(session_id=session_id) # type: ignore
471
+ if session is None:
472
+ raise Exception("Session not found")
473
+ return session.session_data.get("session_name", "") if session.session_data else ""
474
+
431
475
  def get_session_name(self, session_id: Optional[str] = None) -> str:
432
476
  """Get the session name for the given session ID and user ID."""
433
477
  session_id = session_id or self.session_id
@@ -438,6 +482,16 @@ class Workflow:
438
482
  raise Exception("Session not found")
439
483
  return session.session_data.get("session_name", "") if session.session_data else ""
440
484
 
485
+ async def aget_session_state(self, session_id: Optional[str] = None) -> Dict[str, Any]:
486
+ """Get the session state for the given session ID and user ID."""
487
+ session_id = session_id or self.session_id
488
+ if session_id is None:
489
+ raise Exception("Session ID is not set")
490
+ session = await self.aget_session(session_id=session_id) # type: ignore
491
+ if session is None:
492
+ raise Exception("Session not found")
493
+ return session.session_data.get("session_state", {}) if session.session_data else {}
494
+
441
495
  def get_session_state(self, session_id: Optional[str] = None) -> Dict[str, Any]:
442
496
  """Get the session state for the given session ID and user ID."""
443
497
  session_id = session_id or self.session_id
@@ -448,6 +502,13 @@ class Workflow:
448
502
  raise Exception("Session not found")
449
503
  return session.session_data.get("session_state", {}) if session.session_data else {}
450
504
 
505
+ async def adelete_session(self, session_id: str):
506
+ """Delete the current session and save to storage"""
507
+ if self.db is None:
508
+ return
509
+ # -*- Delete session
510
+ await self.db.delete_session(session_id=session_id) # type: ignore
511
+
451
512
  def delete_session(self, session_id: str):
452
513
  """Delete the current session and save to storage"""
453
514
  if self.db is None:
@@ -455,6 +516,25 @@ class Workflow:
455
516
  # -*- Delete session
456
517
  self.db.delete_session(session_id=session_id)
457
518
 
519
+ async def aget_run_output(self, run_id: str, session_id: Optional[str] = None) -> Optional[WorkflowRunOutput]:
520
+ """Get a RunOutput from the database."""
521
+ if self._workflow_session is not None:
522
+ run_response = self._workflow_session.get_run(run_id=run_id)
523
+ if run_response is not None:
524
+ return run_response
525
+ else:
526
+ log_warning(f"RunOutput {run_id} not found in AgentSession {self._workflow_session.session_id}")
527
+ return None
528
+ else:
529
+ workflow_session = await self.aget_session(session_id=session_id) # type: ignore
530
+ if workflow_session is not None:
531
+ run_response = workflow_session.get_run(run_id=run_id)
532
+ if run_response is not None:
533
+ return run_response
534
+ else:
535
+ log_warning(f"RunOutput {run_id} not found in AgentSession {session_id}")
536
+ return None
537
+
458
538
  def get_run_output(self, run_id: str, session_id: Optional[str] = None) -> Optional[WorkflowRunOutput]:
459
539
  """Get a RunOutput from the database."""
460
540
  if self._workflow_session is not None:
@@ -474,6 +554,26 @@ class Workflow:
474
554
  log_warning(f"RunOutput {run_id} not found in AgentSession {session_id}")
475
555
  return None
476
556
 
557
+ async def aget_last_run_output(self, session_id: Optional[str] = None) -> Optional[WorkflowRunOutput]:
558
+ """Get the last run response from the database."""
559
+ if (
560
+ self._workflow_session is not None
561
+ and self._workflow_session.runs is not None
562
+ and len(self._workflow_session.runs) > 0
563
+ ):
564
+ run_response = self._workflow_session.runs[-1]
565
+ if run_response is not None:
566
+ return run_response
567
+ else:
568
+ workflow_session = await self.aget_session(session_id=session_id) # type: ignore
569
+ if workflow_session is not None and workflow_session.runs is not None and len(workflow_session.runs) > 0:
570
+ run_response = workflow_session.runs[-1]
571
+ if run_response is not None:
572
+ return run_response
573
+ else:
574
+ log_warning(f"No run responses found in WorkflowSession {session_id}")
575
+ return None
576
+
477
577
  def get_last_run_output(self, session_id: Optional[str] = None) -> Optional[WorkflowRunOutput]:
478
578
  """Get the last run response from the database."""
479
579
  if (
@@ -531,6 +631,68 @@ class Workflow:
531
631
 
532
632
  return workflow_session
533
633
 
634
+ async def aread_or_create_session(
635
+ self,
636
+ session_id: str,
637
+ user_id: Optional[str] = None,
638
+ ) -> WorkflowSession:
639
+ from time import time
640
+
641
+ # Returning cached session if we have one
642
+ if self._workflow_session is not None and self._workflow_session.session_id == session_id:
643
+ return self._workflow_session
644
+
645
+ # Try to load from database
646
+ workflow_session = None
647
+ if self.db is not None:
648
+ log_debug(f"Reading WorkflowSession: {session_id}")
649
+
650
+ workflow_session = cast(WorkflowSession, await self._aread_session(session_id=session_id))
651
+
652
+ if workflow_session is None:
653
+ # Creating new session if none found
654
+ log_debug(f"Creating new WorkflowSession: {session_id}")
655
+ workflow_session = WorkflowSession(
656
+ session_id=session_id,
657
+ workflow_id=self.id,
658
+ user_id=user_id,
659
+ workflow_data=self._get_workflow_data(),
660
+ session_data={},
661
+ metadata=self.metadata,
662
+ created_at=int(time()),
663
+ )
664
+
665
+ # Cache the session if relevant
666
+ if workflow_session is not None and self.cache_session:
667
+ self._workflow_session = workflow_session
668
+
669
+ return workflow_session
670
+
671
+ async def aget_session(
672
+ self,
673
+ session_id: Optional[str] = None,
674
+ ) -> Optional[WorkflowSession]:
675
+ """Load an WorkflowSession from database.
676
+
677
+ Args:
678
+ session_id: The session_id to load from storage.
679
+
680
+ Returns:
681
+ WorkflowSession: The WorkflowSession loaded from the database or created if it does not exist.
682
+ """
683
+ if not session_id and not self.session_id:
684
+ raise Exception("No session_id provided")
685
+
686
+ session_id_to_load = session_id or self.session_id
687
+
688
+ # Try to load from database
689
+ if self.db is not None and session_id_to_load is not None:
690
+ workflow_session = cast(WorkflowSession, await self._aread_session(session_id=session_id_to_load))
691
+ return workflow_session
692
+
693
+ log_warning(f"WorkflowSession {session_id_to_load} not found in db")
694
+ return None
695
+
534
696
  def get_session(
535
697
  self,
536
698
  session_id: Optional[str] = None,
@@ -556,6 +718,25 @@ class Workflow:
556
718
  log_warning(f"WorkflowSession {session_id_to_load} not found in db")
557
719
  return None
558
720
 
721
+ async def asave_session(self, session: WorkflowSession) -> None:
722
+ """Save the WorkflowSession to storage, using an async database.
723
+
724
+ Returns:
725
+ Optional[WorkflowSession]: The saved WorkflowSession or None if not saved.
726
+ """
727
+ if self.db is not None and session.session_data is not None:
728
+ if session.session_data.get("session_state") is not None:
729
+ session.session_data["session_state"].pop("current_session_id", None)
730
+ session.session_data["session_state"].pop("current_user_id", None)
731
+ session.session_data["session_state"].pop("current_run_id", None)
732
+ session.session_data["session_state"].pop("workflow_id", None)
733
+ session.session_data["session_state"].pop("run_id", None)
734
+ session.session_data["session_state"].pop("session_id", None)
735
+ session.session_data["session_state"].pop("workflow_name", None)
736
+
737
+ await self._aupsert_session(session=session) # type: ignore
738
+ log_debug(f"Created or updated WorkflowSession record: {session.session_id}")
739
+
559
740
  def save_session(self, session: WorkflowSession) -> None:
560
741
  """Save the WorkflowSession to storage
561
742
 
@@ -576,6 +757,17 @@ class Workflow:
576
757
  log_debug(f"Created or updated WorkflowSession record: {session.session_id}")
577
758
 
578
759
  # -*- Session Database Functions
760
+ async def _aread_session(self, session_id: str) -> Optional[WorkflowSession]:
761
+ """Get a Session from the database."""
762
+ try:
763
+ if not self.db:
764
+ raise ValueError("Db not initialized")
765
+ session = await self.db.get_session(session_id=session_id, session_type=SessionType.WORKFLOW) # type: ignore
766
+ return session if isinstance(session, (WorkflowSession, type(None))) else None
767
+ except Exception as e:
768
+ log_warning(f"Error getting session from db: {e}")
769
+ return None
770
+
579
771
  def _read_session(self, session_id: str) -> Optional[WorkflowSession]:
580
772
  """Get a Session from the database."""
581
773
  try:
@@ -587,9 +779,19 @@ class Workflow:
587
779
  log_warning(f"Error getting session from db: {e}")
588
780
  return None
589
781
 
590
- def _upsert_session(self, session: WorkflowSession) -> Optional[WorkflowSession]:
782
+ async def _aupsert_session(self, session: WorkflowSession) -> Optional[WorkflowSession]:
591
783
  """Upsert a Session into the database."""
784
+ try:
785
+ if not self.db:
786
+ raise ValueError("Db not initialized")
787
+ result = await self.db.upsert_session(session=session) # type: ignore
788
+ return result if isinstance(result, (WorkflowSession, type(None))) else None
789
+ except Exception as e:
790
+ log_warning(f"Error upserting session into db: {e}")
791
+ return None
592
792
 
793
+ def _upsert_session(self, session: WorkflowSession) -> Optional[WorkflowSession]:
794
+ """Upsert a Session into the database."""
593
795
  try:
594
796
  if not self.db:
595
797
  raise ValueError("Db not initialized")
@@ -1158,6 +1360,7 @@ class Workflow:
1158
1360
  session_id=session.session_id,
1159
1361
  user_id=self.user_id,
1160
1362
  stream_intermediate_steps=stream_intermediate_steps,
1363
+ stream_executor_events=self.stream_executor_events,
1161
1364
  workflow_run_response=workflow_run_response,
1162
1365
  session_state=session_state,
1163
1366
  step_index=i,
@@ -1228,7 +1431,8 @@ class Workflow:
1228
1431
  enriched_event = self._enrich_event_with_workflow_context(
1229
1432
  event, workflow_run_response, step_index=i, step=step
1230
1433
  )
1231
- yield self._handle_event(enriched_event, workflow_run_response) # type: ignore
1434
+ if self.stream_executor_events:
1435
+ yield self._handle_event(enriched_event, workflow_run_response) # type: ignore
1232
1436
 
1233
1437
  # Break out of main step loop if early termination was requested
1234
1438
  if "early_termination" in locals() and early_termination:
@@ -1540,7 +1744,10 @@ class Workflow:
1540
1744
 
1541
1745
  self._update_session_metrics(session=session, workflow_run_response=workflow_run_response)
1542
1746
  session.upsert_run(run=workflow_run_response)
1543
- self.save_session(session=session)
1747
+ if self._has_async_db():
1748
+ await self.asave_session(session=session)
1749
+ else:
1750
+ self.save_session(session=session)
1544
1751
  # Always clean up the run tracking
1545
1752
  cleanup_run(workflow_run_response.run_id) # type: ignore
1546
1753
 
@@ -1643,6 +1850,7 @@ class Workflow:
1643
1850
  session_id=session.session_id,
1644
1851
  user_id=self.user_id,
1645
1852
  stream_intermediate_steps=stream_intermediate_steps,
1853
+ stream_executor_events=self.stream_executor_events,
1646
1854
  workflow_run_response=workflow_run_response,
1647
1855
  session_state=session_state,
1648
1856
  step_index=i,
@@ -1714,9 +1922,10 @@ class Workflow:
1714
1922
  enriched_event = self._enrich_event_with_workflow_context(
1715
1923
  event, workflow_run_response, step_index=i, step=step
1716
1924
  )
1717
- yield self._handle_event(
1718
- enriched_event, workflow_run_response, websocket_handler=websocket_handler
1719
- ) # type: ignore
1925
+ if self.stream_executor_events:
1926
+ yield self._handle_event(
1927
+ enriched_event, workflow_run_response, websocket_handler=websocket_handler
1928
+ ) # type: ignore
1720
1929
 
1721
1930
  # Break out of main step loop if early termination was requested
1722
1931
  if "early_termination" in locals() and early_termination:
@@ -1817,7 +2026,10 @@ class Workflow:
1817
2026
  # Store the completed workflow response
1818
2027
  self._update_session_metrics(session=session, workflow_run_response=workflow_run_response)
1819
2028
  session.upsert_run(run=workflow_run_response)
1820
- self.save_session(session=session)
2029
+ if self._has_async_db():
2030
+ await self.asave_session(session=session)
2031
+ else:
2032
+ self.save_session(session=session)
1821
2033
 
1822
2034
  # Log Workflow Telemetry
1823
2035
  if self.telemetry:
@@ -1850,7 +2062,10 @@ class Workflow:
1850
2062
  )
1851
2063
 
1852
2064
  # Read existing session from database
1853
- workflow_session = self.read_or_create_session(session_id=session_id, user_id=user_id)
2065
+ if self._has_async_db():
2066
+ workflow_session = await self.aread_or_create_session(session_id=session_id, user_id=user_id)
2067
+ else:
2068
+ workflow_session = self.read_or_create_session(session_id=session_id, user_id=user_id)
1854
2069
  self._update_metadata(session=workflow_session)
1855
2070
 
1856
2071
  # Update session state from DB
@@ -1871,7 +2086,10 @@ class Workflow:
1871
2086
 
1872
2087
  # Store PENDING response immediately
1873
2088
  workflow_session.upsert_run(run=workflow_run_response)
1874
- self.save_session(session=workflow_session)
2089
+ if self._has_async_db():
2090
+ await self.asave_session(session=workflow_session)
2091
+ else:
2092
+ self.save_session(session=workflow_session)
1875
2093
 
1876
2094
  # Prepare execution input
1877
2095
  inputs = WorkflowExecutionInput(
@@ -1890,7 +2108,10 @@ class Workflow:
1890
2108
  try:
1891
2109
  # Update status to RUNNING and save
1892
2110
  workflow_run_response.status = RunStatus.running
1893
- self.save_session(session=workflow_session)
2111
+ if self._has_async_db():
2112
+ await self.asave_session(session=workflow_session)
2113
+ else:
2114
+ self.save_session(session=workflow_session)
1894
2115
 
1895
2116
  await self._aexecute(
1896
2117
  session=workflow_session,
@@ -1906,7 +2127,10 @@ class Workflow:
1906
2127
  logger.error(f"Background workflow execution failed: {e}")
1907
2128
  workflow_run_response.status = RunStatus.error
1908
2129
  workflow_run_response.content = f"Background execution failed: {str(e)}"
1909
- self.save_session(session=workflow_session)
2130
+ if self._has_async_db():
2131
+ await self.asave_session(session=workflow_session)
2132
+ else:
2133
+ self.save_session(session=workflow_session)
1910
2134
 
1911
2135
  # Create and start asyncio task
1912
2136
  loop = asyncio.get_running_loop()
@@ -1941,7 +2165,10 @@ class Workflow:
1941
2165
  )
1942
2166
 
1943
2167
  # Read existing session from database
1944
- workflow_session = self.read_or_create_session(session_id=session_id, user_id=user_id)
2168
+ if self._has_async_db():
2169
+ workflow_session = await self.aread_or_create_session(session_id=session_id, user_id=user_id)
2170
+ else:
2171
+ workflow_session = self.read_or_create_session(session_id=session_id, user_id=user_id)
1945
2172
  self._update_metadata(session=workflow_session)
1946
2173
 
1947
2174
  # Update session state from DB
@@ -1962,7 +2189,10 @@ class Workflow:
1962
2189
 
1963
2190
  # Store PENDING response immediately
1964
2191
  workflow_session.upsert_run(run=workflow_run_response)
1965
- self.save_session(session=workflow_session)
2192
+ if self._has_async_db():
2193
+ await self.asave_session(session=workflow_session)
2194
+ else:
2195
+ self.save_session(session=workflow_session)
1966
2196
 
1967
2197
  # Prepare execution input
1968
2198
  inputs = WorkflowExecutionInput(
@@ -1981,7 +2211,10 @@ class Workflow:
1981
2211
  try:
1982
2212
  # Update status to RUNNING and save
1983
2213
  workflow_run_response.status = RunStatus.running
1984
- self.save_session(session=workflow_session)
2214
+ if self._has_async_db():
2215
+ await self.asave_session(session=workflow_session)
2216
+ else:
2217
+ self.save_session(session=workflow_session)
1985
2218
 
1986
2219
  # Execute with streaming - consume all events (they're auto-broadcast via _handle_event)
1987
2220
  async for event in self._aexecute_stream(
@@ -2003,7 +2236,10 @@ class Workflow:
2003
2236
  logger.error(f"Background streaming workflow execution failed: {e}")
2004
2237
  workflow_run_response.status = RunStatus.error
2005
2238
  workflow_run_response.content = f"Background streaming execution failed: {str(e)}"
2006
- self.save_session(session=workflow_session)
2239
+ if self._has_async_db():
2240
+ await self.asave_session(session=workflow_session)
2241
+ else:
2242
+ self.save_session(session=workflow_session)
2007
2243
 
2008
2244
  # Create and start asyncio task for background streaming execution
2009
2245
  loop = asyncio.get_running_loop()
@@ -2012,6 +2248,18 @@ class Workflow:
2012
2248
  # Return SAME object that will be updated by background execution
2013
2249
  return workflow_run_response
2014
2250
 
2251
+ async def aget_run(self, run_id: str) -> Optional[WorkflowRunOutput]:
2252
+ """Get the status and details of a background workflow run - SIMPLIFIED"""
2253
+ if self.db is not None and self.session_id is not None:
2254
+ session = await self.db.aget_session(session_id=self.session_id, session_type=SessionType.WORKFLOW) # type: ignore
2255
+ if session and isinstance(session, WorkflowSession) and session.runs:
2256
+ # Find the run by ID
2257
+ for run in session.runs:
2258
+ if run.run_id == run_id:
2259
+ return run
2260
+
2261
+ return None
2262
+
2015
2263
  def get_run(self, run_id: str) -> Optional[WorkflowRunOutput]:
2016
2264
  """Get the status and details of a background workflow run - SIMPLIFIED"""
2017
2265
  if self.db is not None and self.session_id is not None:
@@ -2086,6 +2334,8 @@ class Workflow:
2086
2334
  **kwargs: Any,
2087
2335
  ) -> Union[WorkflowRunOutput, Iterator[WorkflowRunOutputEvent]]:
2088
2336
  """Execute the workflow synchronously with optional streaming"""
2337
+ if self._has_async_db():
2338
+ raise Exception("`run()` is not supported with an async DB. Please use `arun()`.")
2089
2339
 
2090
2340
  input = self._validate_input(input)
2091
2341
  if background:
@@ -2273,7 +2523,10 @@ class Workflow:
2273
2523
  )
2274
2524
 
2275
2525
  # Read existing session from database
2276
- workflow_session = self.read_or_create_session(session_id=session_id, user_id=user_id)
2526
+ if self._has_async_db():
2527
+ workflow_session = await self.aread_or_create_session(session_id=session_id, user_id=user_id)
2528
+ else:
2529
+ workflow_session = self.read_or_create_session(session_id=session_id, user_id=user_id)
2277
2530
  self._update_metadata(session=workflow_session)
2278
2531
 
2279
2532
  # Update session state from DB
@@ -2403,6 +2656,8 @@ class Workflow:
2403
2656
  show_step_details: Whether to show individual step outputs
2404
2657
  console: Rich console instance (optional)
2405
2658
  """
2659
+ if self._has_async_db():
2660
+ raise Exception("`print_response()` is not supported with an async DB. Please use `aprint_response()`.")
2406
2661
 
2407
2662
  if stream is None:
2408
2663
  stream = self.stream or False
@@ -2635,6 +2890,18 @@ class Workflow:
2635
2890
  session.session_data = {}
2636
2891
  session.session_data["session_metrics"] = session_metrics.to_dict()
2637
2892
 
2893
+ async def aget_session_metrics(self, session_id: Optional[str] = None) -> Optional[Metrics]:
2894
+ """Get the session metrics for the given session ID and user ID."""
2895
+ session_id = session_id or self.session_id
2896
+ if session_id is None:
2897
+ raise Exception("Session ID is required")
2898
+
2899
+ session = await self.aget_session(session_id=session_id) # type: ignore
2900
+ if session is None:
2901
+ raise Exception("Session not found")
2902
+
2903
+ return self._get_session_metrics(session=session)
2904
+
2638
2905
  def get_session_metrics(self, session_id: Optional[str] = None) -> Optional[Metrics]:
2639
2906
  """Get the session metrics for the given session ID and user ID."""
2640
2907
  session_id = session_id or self.session_id
@@ -2727,27 +2994,27 @@ class Workflow:
2727
2994
  **kwargs: Any,
2728
2995
  ) -> None:
2729
2996
  """
2730
- Run an interactive command-line interface to interact with the workflow.
2731
-
2732
- This method creates a CLI interface that allows users to interact with the workflow
2733
- either by providing a single input or through continuous interactive prompts.
2734
-
2735
- Arguments:
2736
- input: Optional initial input to process before starting interactive mode.
2737
- session_id: Optional session identifier for maintaining conversation context.
2738
- user_id: Optional user identifier for tracking user-specific data.
2739
- user: Display name for the user in the CLI prompt. Defaults to "User".
2740
- emoji: Emoji to display next to the user name in prompts. Defaults to ":technologist:".
2741
- stream: Whether to stream the workflow response. If None, uses workflow default.
2742
- stream_intermediate_steps: Whether to stream intermediate step outputs. If None, uses workflow default.
2743
- markdown: Whether to render output as markdown. Defaults to True.
2744
- show_time: Whether to display timestamps in the output. Defaults to True.
2745
- show_step_details: Whether to show detailed step information. Defaults to True.
2746
- exit_on: List of commands that will exit the CLI. Defaults to ["exit", "quit", "bye", "stop"].
2747
- **kwargs: Additional keyword arguments passed to the workflow's print_response method.
2748
-
2749
- Returns:
2750
- None: This method runs interactively and does not return a value.
2997
+ Run an interactive command-line interface to interact with the workflow.
2998
+
2999
+ This method creates a CLI interface that allows users to interact with the workflow
3000
+ either by providing a single input or through continuous interactive prompts.
3001
+
3002
+ Arguments:
3003
+ input: Optional initial input to process before starting interactive mode.
3004
+ session_id: Optional session identifier for maintaining conversation context.
3005
+ user_id: Optional user identifier for tracking user-specific data.
3006
+ user: Display name for the user in the CLI prompt. Defaults to "User".
3007
+ emoji: Emoji to display next to the user name in prompts. Defaults to ":technologist:".
3008
+ stream: Whether to stream the workflow response. If None, uses workflow default.
3009
+ stream_intermediate_steps: Whether to stream intermediate step outputs. If None, uses workflow default.
3010
+ markdown: Whether to render output as markdown. Defaults to True.
3011
+ show_time: Whether to display timestamps in the output. Defaults to True.
3012
+ show_step_details: Whether to show detailed step information. Defaults to True.
3013
+ exit_on: List of commands that will exit the CLI. Defaults to ["exit", "quit", "bye", "stop"].
3014
+ **kwargs: Additional keyword arguments passed to the workflow's print_response method.
3015
+
3016
+ Returns:
3017
+ None: This method runs interactively and does not return a value.
2751
3018
  """
2752
3019
 
2753
3020
  from rich.prompt import Prompt
@@ -2799,27 +3066,27 @@ class Workflow:
2799
3066
  **kwargs: Any,
2800
3067
  ) -> None:
2801
3068
  """
2802
- Run an interactive command-line interface to interact with the workflow.
2803
-
2804
- This method creates a CLI interface that allows users to interact with the workflow
2805
- either by providing a single input or through continuous interactive prompts.
2806
-
2807
- Arguments:
2808
- input: Optional initial input to process before starting interactive mode.
2809
- session_id: Optional session identifier for maintaining conversation context.
2810
- user_id: Optional user identifier for tracking user-specific data.
2811
- user: Display name for the user in the CLI prompt. Defaults to "User".
2812
- emoji: Emoji to display next to the user name in prompts. Defaults to ":technologist:".
2813
- stream: Whether to stream the workflow response. If None, uses workflow default.
2814
- stream_intermediate_steps: Whether to stream intermediate step outputs. If None, uses workflow default.
2815
- markdown: Whether to render output as markdown. Defaults to True.
2816
- show_time: Whether to display timestamps in the output. Defaults to True.
2817
- show_step_details: Whether to show detailed step information. Defaults to True.
2818
- exit_on: List of commands that will exit the CLI. Defaults to ["exit", "quit", "bye", "stop"].
2819
- **kwargs: Additional keyword arguments passed to the workflow's print_response method.
2820
-
2821
- Returns:
2822
- None: This method runs interactively and does not return a value.
3069
+ Run an interactive command-line interface to interact with the workflow.
3070
+
3071
+ This method creates a CLI interface that allows users to interact with the workflow
3072
+ either by providing a single input or through continuous interactive prompts.
3073
+
3074
+ Arguments:
3075
+ input: Optional initial input to process before starting interactive mode.
3076
+ session_id: Optional session identifier for maintaining conversation context.
3077
+ user_id: Optional user identifier for tracking user-specific data.
3078
+ user: Display name for the user in the CLI prompt. Defaults to "User".
3079
+ emoji: Emoji to display next to the user name in prompts. Defaults to ":technologist:".
3080
+ stream: Whether to stream the workflow response. If None, uses workflow default.
3081
+ stream_intermediate_steps: Whether to stream intermediate step outputs. If None, uses workflow default.
3082
+ markdown: Whether to render output as markdown. Defaults to True.
3083
+ show_time: Whether to display timestamps in the output. Defaults to True.
3084
+ show_step_details: Whether to show detailed step information. Defaults to True.
3085
+ exit_on: List of commands that will exit the CLI. Defaults to ["exit", "quit", "bye", "stop"].
3086
+ **kwargs: Additional keyword arguments passed to the workflow's print_response method.
3087
+
3088
+ Returns:
3089
+ None: This method runs interactively and does not return a value.
2823
3090
  """
2824
3091
 
2825
3092
  from rich.prompt import Prompt