agno 2.1.4__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.
- agno/agent/agent.py +1767 -535
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/async_postgres/async_postgres.py +1668 -0
- agno/db/async_postgres/schemas.py +124 -0
- agno/db/async_postgres/utils.py +289 -0
- agno/db/base.py +237 -2
- agno/db/dynamo/dynamo.py +2 -2
- agno/db/firestore/firestore.py +2 -2
- agno/db/firestore/utils.py +4 -2
- agno/db/gcs_json/gcs_json_db.py +2 -2
- agno/db/in_memory/in_memory_db.py +2 -2
- agno/db/json/json_db.py +2 -2
- agno/db/migrations/v1_to_v2.py +30 -13
- agno/db/mongo/mongo.py +18 -6
- agno/db/mysql/mysql.py +35 -13
- agno/db/postgres/postgres.py +29 -6
- agno/db/redis/redis.py +2 -2
- agno/db/singlestore/singlestore.py +2 -2
- agno/db/sqlite/sqlite.py +34 -12
- agno/db/sqlite/utils.py +8 -3
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +8 -2
- agno/knowledge/knowledge.py +260 -46
- agno/knowledge/reader/pdf_reader.py +4 -6
- agno/knowledge/reader/reader_factory.py +2 -3
- agno/memory/manager.py +241 -33
- agno/models/anthropic/claude.py +37 -0
- agno/os/app.py +8 -7
- agno/os/interfaces/a2a/router.py +3 -5
- agno/os/interfaces/agui/router.py +4 -1
- agno/os/interfaces/agui/utils.py +27 -6
- agno/os/interfaces/slack/router.py +2 -4
- agno/os/mcp.py +98 -41
- agno/os/router.py +23 -0
- agno/os/routers/evals/evals.py +52 -20
- agno/os/routers/evals/utils.py +14 -14
- agno/os/routers/knowledge/knowledge.py +130 -9
- agno/os/routers/knowledge/schemas.py +57 -0
- agno/os/routers/memory/memory.py +116 -44
- agno/os/routers/metrics/metrics.py +16 -6
- agno/os/routers/session/session.py +65 -22
- agno/os/schema.py +36 -0
- agno/os/utils.py +67 -12
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/openai.py +5 -0
- agno/reasoning/vertexai.py +76 -0
- agno/session/workflow.py +3 -3
- agno/team/team.py +918 -175
- agno/tools/googlesheets.py +20 -5
- agno/tools/mcp_toolbox.py +3 -3
- agno/tools/scrapegraph.py +1 -1
- agno/utils/models/claude.py +3 -1
- agno/utils/streamlit.py +1 -1
- agno/vectordb/base.py +22 -1
- agno/vectordb/cassandra/cassandra.py +9 -0
- agno/vectordb/chroma/chromadb.py +26 -6
- agno/vectordb/clickhouse/clickhousedb.py +9 -1
- agno/vectordb/couchbase/couchbase.py +11 -0
- agno/vectordb/lancedb/lance_db.py +20 -0
- agno/vectordb/langchaindb/langchaindb.py +11 -0
- agno/vectordb/lightrag/lightrag.py +9 -0
- agno/vectordb/llamaindex/llamaindexdb.py +15 -1
- agno/vectordb/milvus/milvus.py +23 -0
- agno/vectordb/mongodb/mongodb.py +22 -0
- agno/vectordb/pgvector/pgvector.py +19 -0
- agno/vectordb/pineconedb/pineconedb.py +35 -4
- agno/vectordb/qdrant/qdrant.py +24 -0
- agno/vectordb/singlestore/singlestore.py +25 -17
- agno/vectordb/surrealdb/surrealdb.py +18 -1
- agno/vectordb/upstashdb/upstashdb.py +26 -1
- agno/vectordb/weaviate/weaviate.py +18 -0
- agno/workflow/condition.py +4 -0
- agno/workflow/loop.py +4 -0
- agno/workflow/parallel.py +4 -0
- agno/workflow/router.py +4 -0
- agno/workflow/step.py +22 -14
- agno/workflow/steps.py +4 -0
- agno/workflow/types.py +2 -2
- agno/workflow/workflow.py +328 -61
- {agno-2.1.4.dist-info → agno-2.1.5.dist-info}/METADATA +100 -41
- {agno-2.1.4.dist-info → agno-2.1.5.dist-info}/RECORD +88 -81
- {agno-2.1.4.dist-info → agno-2.1.5.dist-info}/WHEEL +0 -0
- {agno-2.1.4.dist-info → agno-2.1.5.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.4.dist-info → agno-2.1.5.dist-info}/top_level.txt +0 -0
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
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
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
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
|