agno 2.1.3__py3-none-any.whl → 2.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agno/agent/agent.py +1779 -577
- 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 +10 -8
- agno/db/dynamo/schemas.py +1 -10
- agno/db/dynamo/utils.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/embedder/fastembed.py +1 -1
- 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 +15 -10
- agno/os/interfaces/a2a/router.py +3 -5
- agno/os/interfaces/agui/router.py +4 -1
- agno/os/interfaces/agui/utils.py +33 -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 +38 -0
- agno/os/utils.py +69 -13
- 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 +69 -1
- agno/team/team.py +934 -241
- agno/tools/function.py +36 -18
- agno/tools/google_drive.py +270 -0
- 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/print_response/workflow.py +112 -12
- 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 +29 -0
- agno/workflow/loop.py +29 -0
- agno/workflow/parallel.py +141 -113
- agno/workflow/router.py +29 -0
- agno/workflow/step.py +146 -25
- agno/workflow/steps.py +29 -0
- agno/workflow/types.py +26 -1
- agno/workflow/workflow.py +507 -22
- {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/METADATA +100 -41
- {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/RECORD +94 -86
- {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/WHEEL +0 -0
- {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.3.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
|
|
@@ -170,12 +172,17 @@ class Workflow:
|
|
|
170
172
|
# This helps us improve the Agent and provide better support
|
|
171
173
|
telemetry: bool = True
|
|
172
174
|
|
|
175
|
+
# Add this flag to control if the workflow should add history to the steps
|
|
176
|
+
add_workflow_history_to_steps: bool = False
|
|
177
|
+
# Number of historical runs to include in the messages
|
|
178
|
+
num_history_runs: int = 3
|
|
179
|
+
|
|
173
180
|
def __init__(
|
|
174
181
|
self,
|
|
175
182
|
id: Optional[str] = None,
|
|
176
183
|
name: Optional[str] = None,
|
|
177
184
|
description: Optional[str] = None,
|
|
178
|
-
db: Optional[BaseDb] = None,
|
|
185
|
+
db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
|
|
179
186
|
steps: Optional[WorkflowSteps] = None,
|
|
180
187
|
session_id: Optional[str] = None,
|
|
181
188
|
session_state: Optional[Dict[str, Any]] = None,
|
|
@@ -184,6 +191,7 @@ class Workflow:
|
|
|
184
191
|
debug_mode: Optional[bool] = False,
|
|
185
192
|
stream: Optional[bool] = None,
|
|
186
193
|
stream_intermediate_steps: bool = False,
|
|
194
|
+
stream_executor_events: bool = True,
|
|
187
195
|
store_events: bool = False,
|
|
188
196
|
events_to_skip: Optional[List[Union[WorkflowRunEvent, RunEvent, TeamRunEvent]]] = None,
|
|
189
197
|
store_executor_outputs: bool = True,
|
|
@@ -191,6 +199,8 @@ class Workflow:
|
|
|
191
199
|
metadata: Optional[Dict[str, Any]] = None,
|
|
192
200
|
cache_session: bool = False,
|
|
193
201
|
telemetry: bool = True,
|
|
202
|
+
add_workflow_history_to_steps: bool = False,
|
|
203
|
+
num_history_runs: int = 3,
|
|
194
204
|
):
|
|
195
205
|
self.id = id
|
|
196
206
|
self.name = name
|
|
@@ -205,13 +215,15 @@ class Workflow:
|
|
|
205
215
|
self.events_to_skip = events_to_skip or []
|
|
206
216
|
self.stream = stream
|
|
207
217
|
self.stream_intermediate_steps = stream_intermediate_steps
|
|
218
|
+
self.stream_executor_events = stream_executor_events
|
|
208
219
|
self.store_executor_outputs = store_executor_outputs
|
|
209
220
|
self.input_schema = input_schema
|
|
210
221
|
self.metadata = metadata
|
|
211
222
|
self.cache_session = cache_session
|
|
212
223
|
self.db = db
|
|
213
224
|
self.telemetry = telemetry
|
|
214
|
-
|
|
225
|
+
self.add_workflow_history_to_steps = add_workflow_history_to_steps
|
|
226
|
+
self.num_history_runs = num_history_runs
|
|
215
227
|
self._workflow_session: Optional[WorkflowSession] = None
|
|
216
228
|
|
|
217
229
|
def set_id(self) -> None:
|
|
@@ -221,6 +233,9 @@ class Workflow:
|
|
|
221
233
|
else:
|
|
222
234
|
self.id = str(uuid4())
|
|
223
235
|
|
|
236
|
+
def _has_async_db(self) -> bool:
|
|
237
|
+
return self.db is not None and isinstance(self.db, AsyncBaseDb)
|
|
238
|
+
|
|
224
239
|
def _validate_input(
|
|
225
240
|
self, input: Optional[Union[str, Dict[str, Any], List[Any], BaseModel, List[Message]]]
|
|
226
241
|
) -> Optional[Union[str, List, Dict, Message, BaseModel]]:
|
|
@@ -393,6 +408,33 @@ class Workflow:
|
|
|
393
408
|
new_session_name = f"{truncated_desc} - {datetime_str}"
|
|
394
409
|
return new_session_name
|
|
395
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
|
+
|
|
396
438
|
def set_session_name(
|
|
397
439
|
self, session_id: Optional[str] = None, autogenerate: bool = False, session_name: Optional[str] = None
|
|
398
440
|
) -> WorkflowSession:
|
|
@@ -420,6 +462,16 @@ class Workflow:
|
|
|
420
462
|
|
|
421
463
|
return session # type: ignore
|
|
422
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
|
+
|
|
423
475
|
def get_session_name(self, session_id: Optional[str] = None) -> str:
|
|
424
476
|
"""Get the session name for the given session ID and user ID."""
|
|
425
477
|
session_id = session_id or self.session_id
|
|
@@ -430,6 +482,16 @@ class Workflow:
|
|
|
430
482
|
raise Exception("Session not found")
|
|
431
483
|
return session.session_data.get("session_name", "") if session.session_data else ""
|
|
432
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
|
+
|
|
433
495
|
def get_session_state(self, session_id: Optional[str] = None) -> Dict[str, Any]:
|
|
434
496
|
"""Get the session state for the given session ID and user ID."""
|
|
435
497
|
session_id = session_id or self.session_id
|
|
@@ -440,6 +502,13 @@ class Workflow:
|
|
|
440
502
|
raise Exception("Session not found")
|
|
441
503
|
return session.session_data.get("session_state", {}) if session.session_data else {}
|
|
442
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
|
+
|
|
443
512
|
def delete_session(self, session_id: str):
|
|
444
513
|
"""Delete the current session and save to storage"""
|
|
445
514
|
if self.db is None:
|
|
@@ -447,6 +516,25 @@ class Workflow:
|
|
|
447
516
|
# -*- Delete session
|
|
448
517
|
self.db.delete_session(session_id=session_id)
|
|
449
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
|
+
|
|
450
538
|
def get_run_output(self, run_id: str, session_id: Optional[str] = None) -> Optional[WorkflowRunOutput]:
|
|
451
539
|
"""Get a RunOutput from the database."""
|
|
452
540
|
if self._workflow_session is not None:
|
|
@@ -466,6 +554,26 @@ class Workflow:
|
|
|
466
554
|
log_warning(f"RunOutput {run_id} not found in AgentSession {session_id}")
|
|
467
555
|
return None
|
|
468
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
|
+
|
|
469
577
|
def get_last_run_output(self, session_id: Optional[str] = None) -> Optional[WorkflowRunOutput]:
|
|
470
578
|
"""Get the last run response from the database."""
|
|
471
579
|
if (
|
|
@@ -523,6 +631,68 @@ class Workflow:
|
|
|
523
631
|
|
|
524
632
|
return workflow_session
|
|
525
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
|
+
|
|
526
696
|
def get_session(
|
|
527
697
|
self,
|
|
528
698
|
session_id: Optional[str] = None,
|
|
@@ -548,6 +718,25 @@ class Workflow:
|
|
|
548
718
|
log_warning(f"WorkflowSession {session_id_to_load} not found in db")
|
|
549
719
|
return None
|
|
550
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
|
+
|
|
551
740
|
def save_session(self, session: WorkflowSession) -> None:
|
|
552
741
|
"""Save the WorkflowSession to storage
|
|
553
742
|
|
|
@@ -568,6 +757,17 @@ class Workflow:
|
|
|
568
757
|
log_debug(f"Created or updated WorkflowSession record: {session.session_id}")
|
|
569
758
|
|
|
570
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
|
+
|
|
571
771
|
def _read_session(self, session_id: str) -> Optional[WorkflowSession]:
|
|
572
772
|
"""Get a Session from the database."""
|
|
573
773
|
try:
|
|
@@ -579,9 +779,19 @@ class Workflow:
|
|
|
579
779
|
log_warning(f"Error getting session from db: {e}")
|
|
580
780
|
return None
|
|
581
781
|
|
|
582
|
-
def
|
|
782
|
+
async def _aupsert_session(self, session: WorkflowSession) -> Optional[WorkflowSession]:
|
|
583
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
|
|
584
792
|
|
|
793
|
+
def _upsert_session(self, session: WorkflowSession) -> Optional[WorkflowSession]:
|
|
794
|
+
"""Upsert a Session into the database."""
|
|
585
795
|
try:
|
|
586
796
|
if not self.db:
|
|
587
797
|
raise ValueError("Db not initialized")
|
|
@@ -707,6 +917,34 @@ class Workflow:
|
|
|
707
917
|
|
|
708
918
|
return event
|
|
709
919
|
|
|
920
|
+
def _enrich_event_with_workflow_context(
|
|
921
|
+
self,
|
|
922
|
+
event: Any,
|
|
923
|
+
workflow_run_response: WorkflowRunOutput,
|
|
924
|
+
step_index: Optional[Union[int, tuple]] = None,
|
|
925
|
+
step: Optional[Any] = None,
|
|
926
|
+
) -> Any:
|
|
927
|
+
"""Enrich any event with workflow context information for frontend tracking"""
|
|
928
|
+
|
|
929
|
+
step_id = getattr(step, "step_id", None) if step else None
|
|
930
|
+
step_name = getattr(step, "name", None) if step else None
|
|
931
|
+
|
|
932
|
+
if hasattr(event, "workflow_id"):
|
|
933
|
+
event.workflow_id = workflow_run_response.workflow_id
|
|
934
|
+
if hasattr(event, "workflow_run_id"):
|
|
935
|
+
event.workflow_run_id = workflow_run_response.run_id
|
|
936
|
+
if hasattr(event, "step_id") and step_id:
|
|
937
|
+
event.step_id = step_id
|
|
938
|
+
if hasattr(event, "step_name") and step_name is not None:
|
|
939
|
+
if event.step_name is None:
|
|
940
|
+
event.step_name = step_name
|
|
941
|
+
# Only set step_index if it's not already set (preserve parallel.py's tuples)
|
|
942
|
+
if hasattr(event, "step_index") and step_index is not None:
|
|
943
|
+
if event.step_index is None:
|
|
944
|
+
event.step_index = step_index
|
|
945
|
+
|
|
946
|
+
return event
|
|
947
|
+
|
|
710
948
|
def _transform_step_output_to_event(
|
|
711
949
|
self, step_output: StepOutput, workflow_run_response: WorkflowRunOutput, step_index: Optional[int] = None
|
|
712
950
|
) -> StepOutputEvent:
|
|
@@ -952,6 +1190,11 @@ class Workflow:
|
|
|
952
1190
|
workflow_run_response=workflow_run_response,
|
|
953
1191
|
session_state=session_state,
|
|
954
1192
|
store_executor_outputs=self.store_executor_outputs,
|
|
1193
|
+
workflow_session=session,
|
|
1194
|
+
add_workflow_history_to_steps=self.add_workflow_history_to_steps
|
|
1195
|
+
if self.add_workflow_history_to_steps
|
|
1196
|
+
else None,
|
|
1197
|
+
num_history_runs=self.num_history_runs,
|
|
955
1198
|
)
|
|
956
1199
|
|
|
957
1200
|
# Check for cancellation after step execution
|
|
@@ -1117,10 +1360,16 @@ class Workflow:
|
|
|
1117
1360
|
session_id=session.session_id,
|
|
1118
1361
|
user_id=self.user_id,
|
|
1119
1362
|
stream_intermediate_steps=stream_intermediate_steps,
|
|
1363
|
+
stream_executor_events=self.stream_executor_events,
|
|
1120
1364
|
workflow_run_response=workflow_run_response,
|
|
1121
1365
|
session_state=session_state,
|
|
1122
1366
|
step_index=i,
|
|
1123
1367
|
store_executor_outputs=self.store_executor_outputs,
|
|
1368
|
+
workflow_session=session,
|
|
1369
|
+
add_workflow_history_to_steps=self.add_workflow_history_to_steps
|
|
1370
|
+
if self.add_workflow_history_to_steps
|
|
1371
|
+
else None,
|
|
1372
|
+
num_history_runs=self.num_history_runs,
|
|
1124
1373
|
):
|
|
1125
1374
|
raise_if_cancelled(workflow_run_response.run_id) # type: ignore
|
|
1126
1375
|
# Handle events
|
|
@@ -1171,11 +1420,19 @@ class Workflow:
|
|
|
1171
1420
|
yield step_output_event
|
|
1172
1421
|
|
|
1173
1422
|
elif isinstance(event, WorkflowRunOutputEvent): # type: ignore
|
|
1174
|
-
|
|
1423
|
+
# Enrich event with workflow context before yielding
|
|
1424
|
+
enriched_event = self._enrich_event_with_workflow_context(
|
|
1425
|
+
event, workflow_run_response, step_index=i, step=step
|
|
1426
|
+
)
|
|
1427
|
+
yield self._handle_event(enriched_event, workflow_run_response) # type: ignore
|
|
1175
1428
|
|
|
1176
1429
|
else:
|
|
1177
|
-
#
|
|
1178
|
-
|
|
1430
|
+
# Enrich other events with workflow context before yielding
|
|
1431
|
+
enriched_event = self._enrich_event_with_workflow_context(
|
|
1432
|
+
event, workflow_run_response, step_index=i, step=step
|
|
1433
|
+
)
|
|
1434
|
+
if self.stream_executor_events:
|
|
1435
|
+
yield self._handle_event(enriched_event, workflow_run_response) # type: ignore
|
|
1179
1436
|
|
|
1180
1437
|
# Break out of main step loop if early termination was requested
|
|
1181
1438
|
if "early_termination" in locals() and early_termination:
|
|
@@ -1415,6 +1672,11 @@ class Workflow:
|
|
|
1415
1672
|
workflow_run_response=workflow_run_response,
|
|
1416
1673
|
session_state=session_state,
|
|
1417
1674
|
store_executor_outputs=self.store_executor_outputs,
|
|
1675
|
+
workflow_session=session,
|
|
1676
|
+
add_workflow_history_to_steps=self.add_workflow_history_to_steps
|
|
1677
|
+
if self.add_workflow_history_to_steps
|
|
1678
|
+
else None,
|
|
1679
|
+
num_history_runs=self.num_history_runs,
|
|
1418
1680
|
)
|
|
1419
1681
|
|
|
1420
1682
|
# Check for cancellation after step execution
|
|
@@ -1482,7 +1744,10 @@ class Workflow:
|
|
|
1482
1744
|
|
|
1483
1745
|
self._update_session_metrics(session=session, workflow_run_response=workflow_run_response)
|
|
1484
1746
|
session.upsert_run(run=workflow_run_response)
|
|
1485
|
-
self.
|
|
1747
|
+
if self._has_async_db():
|
|
1748
|
+
await self.asave_session(session=session)
|
|
1749
|
+
else:
|
|
1750
|
+
self.save_session(session=session)
|
|
1486
1751
|
# Always clean up the run tracking
|
|
1487
1752
|
cleanup_run(workflow_run_response.run_id) # type: ignore
|
|
1488
1753
|
|
|
@@ -1585,10 +1850,16 @@ class Workflow:
|
|
|
1585
1850
|
session_id=session.session_id,
|
|
1586
1851
|
user_id=self.user_id,
|
|
1587
1852
|
stream_intermediate_steps=stream_intermediate_steps,
|
|
1853
|
+
stream_executor_events=self.stream_executor_events,
|
|
1588
1854
|
workflow_run_response=workflow_run_response,
|
|
1589
1855
|
session_state=session_state,
|
|
1590
1856
|
step_index=i,
|
|
1591
1857
|
store_executor_outputs=self.store_executor_outputs,
|
|
1858
|
+
workflow_session=session,
|
|
1859
|
+
add_workflow_history_to_steps=self.add_workflow_history_to_steps
|
|
1860
|
+
if self.add_workflow_history_to_steps
|
|
1861
|
+
else None,
|
|
1862
|
+
num_history_runs=self.num_history_runs,
|
|
1592
1863
|
):
|
|
1593
1864
|
if workflow_run_response.run_id:
|
|
1594
1865
|
raise_if_cancelled(workflow_run_response.run_id)
|
|
@@ -1638,11 +1909,23 @@ class Workflow:
|
|
|
1638
1909
|
yield step_output_event
|
|
1639
1910
|
|
|
1640
1911
|
elif isinstance(event, WorkflowRunOutputEvent): # type: ignore
|
|
1641
|
-
|
|
1912
|
+
# Enrich event with workflow context before yielding
|
|
1913
|
+
enriched_event = self._enrich_event_with_workflow_context(
|
|
1914
|
+
event, workflow_run_response, step_index=i, step=step
|
|
1915
|
+
)
|
|
1916
|
+
yield self._handle_event(
|
|
1917
|
+
enriched_event, workflow_run_response, websocket_handler=websocket_handler
|
|
1918
|
+
) # type: ignore
|
|
1642
1919
|
|
|
1643
1920
|
else:
|
|
1644
|
-
#
|
|
1645
|
-
|
|
1921
|
+
# Enrich other events with workflow context before yielding
|
|
1922
|
+
enriched_event = self._enrich_event_with_workflow_context(
|
|
1923
|
+
event, workflow_run_response, step_index=i, step=step
|
|
1924
|
+
)
|
|
1925
|
+
if self.stream_executor_events:
|
|
1926
|
+
yield self._handle_event(
|
|
1927
|
+
enriched_event, workflow_run_response, websocket_handler=websocket_handler
|
|
1928
|
+
) # type: ignore
|
|
1646
1929
|
|
|
1647
1930
|
# Break out of main step loop if early termination was requested
|
|
1648
1931
|
if "early_termination" in locals() and early_termination:
|
|
@@ -1743,7 +2026,10 @@ class Workflow:
|
|
|
1743
2026
|
# Store the completed workflow response
|
|
1744
2027
|
self._update_session_metrics(session=session, workflow_run_response=workflow_run_response)
|
|
1745
2028
|
session.upsert_run(run=workflow_run_response)
|
|
1746
|
-
self.
|
|
2029
|
+
if self._has_async_db():
|
|
2030
|
+
await self.asave_session(session=session)
|
|
2031
|
+
else:
|
|
2032
|
+
self.save_session(session=session)
|
|
1747
2033
|
|
|
1748
2034
|
# Log Workflow Telemetry
|
|
1749
2035
|
if self.telemetry:
|
|
@@ -1776,7 +2062,10 @@ class Workflow:
|
|
|
1776
2062
|
)
|
|
1777
2063
|
|
|
1778
2064
|
# Read existing session from database
|
|
1779
|
-
|
|
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)
|
|
1780
2069
|
self._update_metadata(session=workflow_session)
|
|
1781
2070
|
|
|
1782
2071
|
# Update session state from DB
|
|
@@ -1797,7 +2086,10 @@ class Workflow:
|
|
|
1797
2086
|
|
|
1798
2087
|
# Store PENDING response immediately
|
|
1799
2088
|
workflow_session.upsert_run(run=workflow_run_response)
|
|
1800
|
-
self.
|
|
2089
|
+
if self._has_async_db():
|
|
2090
|
+
await self.asave_session(session=workflow_session)
|
|
2091
|
+
else:
|
|
2092
|
+
self.save_session(session=workflow_session)
|
|
1801
2093
|
|
|
1802
2094
|
# Prepare execution input
|
|
1803
2095
|
inputs = WorkflowExecutionInput(
|
|
@@ -1816,7 +2108,10 @@ class Workflow:
|
|
|
1816
2108
|
try:
|
|
1817
2109
|
# Update status to RUNNING and save
|
|
1818
2110
|
workflow_run_response.status = RunStatus.running
|
|
1819
|
-
self.
|
|
2111
|
+
if self._has_async_db():
|
|
2112
|
+
await self.asave_session(session=workflow_session)
|
|
2113
|
+
else:
|
|
2114
|
+
self.save_session(session=workflow_session)
|
|
1820
2115
|
|
|
1821
2116
|
await self._aexecute(
|
|
1822
2117
|
session=workflow_session,
|
|
@@ -1832,7 +2127,10 @@ class Workflow:
|
|
|
1832
2127
|
logger.error(f"Background workflow execution failed: {e}")
|
|
1833
2128
|
workflow_run_response.status = RunStatus.error
|
|
1834
2129
|
workflow_run_response.content = f"Background execution failed: {str(e)}"
|
|
1835
|
-
self.
|
|
2130
|
+
if self._has_async_db():
|
|
2131
|
+
await self.asave_session(session=workflow_session)
|
|
2132
|
+
else:
|
|
2133
|
+
self.save_session(session=workflow_session)
|
|
1836
2134
|
|
|
1837
2135
|
# Create and start asyncio task
|
|
1838
2136
|
loop = asyncio.get_running_loop()
|
|
@@ -1867,7 +2165,10 @@ class Workflow:
|
|
|
1867
2165
|
)
|
|
1868
2166
|
|
|
1869
2167
|
# Read existing session from database
|
|
1870
|
-
|
|
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)
|
|
1871
2172
|
self._update_metadata(session=workflow_session)
|
|
1872
2173
|
|
|
1873
2174
|
# Update session state from DB
|
|
@@ -1888,7 +2189,10 @@ class Workflow:
|
|
|
1888
2189
|
|
|
1889
2190
|
# Store PENDING response immediately
|
|
1890
2191
|
workflow_session.upsert_run(run=workflow_run_response)
|
|
1891
|
-
self.
|
|
2192
|
+
if self._has_async_db():
|
|
2193
|
+
await self.asave_session(session=workflow_session)
|
|
2194
|
+
else:
|
|
2195
|
+
self.save_session(session=workflow_session)
|
|
1892
2196
|
|
|
1893
2197
|
# Prepare execution input
|
|
1894
2198
|
inputs = WorkflowExecutionInput(
|
|
@@ -1907,7 +2211,10 @@ class Workflow:
|
|
|
1907
2211
|
try:
|
|
1908
2212
|
# Update status to RUNNING and save
|
|
1909
2213
|
workflow_run_response.status = RunStatus.running
|
|
1910
|
-
self.
|
|
2214
|
+
if self._has_async_db():
|
|
2215
|
+
await self.asave_session(session=workflow_session)
|
|
2216
|
+
else:
|
|
2217
|
+
self.save_session(session=workflow_session)
|
|
1911
2218
|
|
|
1912
2219
|
# Execute with streaming - consume all events (they're auto-broadcast via _handle_event)
|
|
1913
2220
|
async for event in self._aexecute_stream(
|
|
@@ -1929,7 +2236,10 @@ class Workflow:
|
|
|
1929
2236
|
logger.error(f"Background streaming workflow execution failed: {e}")
|
|
1930
2237
|
workflow_run_response.status = RunStatus.error
|
|
1931
2238
|
workflow_run_response.content = f"Background streaming execution failed: {str(e)}"
|
|
1932
|
-
self.
|
|
2239
|
+
if self._has_async_db():
|
|
2240
|
+
await self.asave_session(session=workflow_session)
|
|
2241
|
+
else:
|
|
2242
|
+
self.save_session(session=workflow_session)
|
|
1933
2243
|
|
|
1934
2244
|
# Create and start asyncio task for background streaming execution
|
|
1935
2245
|
loop = asyncio.get_running_loop()
|
|
@@ -1938,6 +2248,18 @@ class Workflow:
|
|
|
1938
2248
|
# Return SAME object that will be updated by background execution
|
|
1939
2249
|
return workflow_run_response
|
|
1940
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
|
+
|
|
1941
2263
|
def get_run(self, run_id: str) -> Optional[WorkflowRunOutput]:
|
|
1942
2264
|
"""Get the status and details of a background workflow run - SIMPLIFIED"""
|
|
1943
2265
|
if self.db is not None and self.session_id is not None:
|
|
@@ -2012,6 +2334,8 @@ class Workflow:
|
|
|
2012
2334
|
**kwargs: Any,
|
|
2013
2335
|
) -> Union[WorkflowRunOutput, Iterator[WorkflowRunOutputEvent]]:
|
|
2014
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()`.")
|
|
2015
2339
|
|
|
2016
2340
|
input = self._validate_input(input)
|
|
2017
2341
|
if background:
|
|
@@ -2199,7 +2523,10 @@ class Workflow:
|
|
|
2199
2523
|
)
|
|
2200
2524
|
|
|
2201
2525
|
# Read existing session from database
|
|
2202
|
-
|
|
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)
|
|
2203
2530
|
self._update_metadata(session=workflow_session)
|
|
2204
2531
|
|
|
2205
2532
|
# Update session state from DB
|
|
@@ -2329,6 +2656,8 @@ class Workflow:
|
|
|
2329
2656
|
show_step_details: Whether to show individual step outputs
|
|
2330
2657
|
console: Rich console instance (optional)
|
|
2331
2658
|
"""
|
|
2659
|
+
if self._has_async_db():
|
|
2660
|
+
raise Exception("`print_response()` is not supported with an async DB. Please use `aprint_response()`.")
|
|
2332
2661
|
|
|
2333
2662
|
if stream is None:
|
|
2334
2663
|
stream = self.stream or False
|
|
@@ -2561,6 +2890,18 @@ class Workflow:
|
|
|
2561
2890
|
session.session_data = {}
|
|
2562
2891
|
session.session_data["session_metrics"] = session_metrics.to_dict()
|
|
2563
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
|
+
|
|
2564
2905
|
def get_session_metrics(self, session_id: Optional[str] = None) -> Optional[Metrics]:
|
|
2565
2906
|
"""Get the session metrics for the given session ID and user ID."""
|
|
2566
2907
|
session_id = session_id or self.session_id
|
|
@@ -2636,3 +2977,147 @@ class Workflow:
|
|
|
2636
2977
|
)
|
|
2637
2978
|
except Exception as e:
|
|
2638
2979
|
log_debug(f"Could not create Workflow run telemetry event: {e}")
|
|
2980
|
+
|
|
2981
|
+
def cli_app(
|
|
2982
|
+
self,
|
|
2983
|
+
input: Optional[str] = None,
|
|
2984
|
+
session_id: Optional[str] = None,
|
|
2985
|
+
user_id: Optional[str] = None,
|
|
2986
|
+
user: str = "User",
|
|
2987
|
+
emoji: str = ":technologist:",
|
|
2988
|
+
stream: Optional[bool] = None,
|
|
2989
|
+
stream_intermediate_steps: Optional[bool] = None,
|
|
2990
|
+
markdown: bool = True,
|
|
2991
|
+
show_time: bool = True,
|
|
2992
|
+
show_step_details: bool = True,
|
|
2993
|
+
exit_on: Optional[List[str]] = None,
|
|
2994
|
+
**kwargs: Any,
|
|
2995
|
+
) -> None:
|
|
2996
|
+
"""
|
|
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.
|
|
3018
|
+
"""
|
|
3019
|
+
|
|
3020
|
+
from rich.prompt import Prompt
|
|
3021
|
+
|
|
3022
|
+
if input:
|
|
3023
|
+
self.print_response(
|
|
3024
|
+
input=input,
|
|
3025
|
+
stream=stream,
|
|
3026
|
+
stream_intermediate_steps=stream_intermediate_steps,
|
|
3027
|
+
markdown=markdown,
|
|
3028
|
+
show_time=show_time,
|
|
3029
|
+
show_step_details=show_step_details,
|
|
3030
|
+
user_id=user_id,
|
|
3031
|
+
session_id=session_id,
|
|
3032
|
+
**kwargs,
|
|
3033
|
+
)
|
|
3034
|
+
|
|
3035
|
+
_exit_on = exit_on or ["exit", "quit", "bye", "stop"]
|
|
3036
|
+
while True:
|
|
3037
|
+
message = Prompt.ask(f"[bold] {emoji} {user} [/bold]")
|
|
3038
|
+
if message in _exit_on:
|
|
3039
|
+
break
|
|
3040
|
+
|
|
3041
|
+
self.print_response(
|
|
3042
|
+
input=message,
|
|
3043
|
+
stream=stream,
|
|
3044
|
+
stream_intermediate_steps=stream_intermediate_steps,
|
|
3045
|
+
markdown=markdown,
|
|
3046
|
+
show_time=show_time,
|
|
3047
|
+
show_step_details=show_step_details,
|
|
3048
|
+
user_id=user_id,
|
|
3049
|
+
session_id=session_id,
|
|
3050
|
+
**kwargs,
|
|
3051
|
+
)
|
|
3052
|
+
|
|
3053
|
+
async def acli_app(
|
|
3054
|
+
self,
|
|
3055
|
+
input: Optional[str] = None,
|
|
3056
|
+
session_id: Optional[str] = None,
|
|
3057
|
+
user_id: Optional[str] = None,
|
|
3058
|
+
user: str = "User",
|
|
3059
|
+
emoji: str = ":technologist:",
|
|
3060
|
+
stream: Optional[bool] = None,
|
|
3061
|
+
stream_intermediate_steps: Optional[bool] = None,
|
|
3062
|
+
markdown: bool = True,
|
|
3063
|
+
show_time: bool = True,
|
|
3064
|
+
show_step_details: bool = True,
|
|
3065
|
+
exit_on: Optional[List[str]] = None,
|
|
3066
|
+
**kwargs: Any,
|
|
3067
|
+
) -> None:
|
|
3068
|
+
"""
|
|
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.
|
|
3090
|
+
"""
|
|
3091
|
+
|
|
3092
|
+
from rich.prompt import Prompt
|
|
3093
|
+
|
|
3094
|
+
if input:
|
|
3095
|
+
await self.aprint_response(
|
|
3096
|
+
input=input,
|
|
3097
|
+
stream=stream,
|
|
3098
|
+
stream_intermediate_steps=stream_intermediate_steps,
|
|
3099
|
+
markdown=markdown,
|
|
3100
|
+
show_time=show_time,
|
|
3101
|
+
show_step_details=show_step_details,
|
|
3102
|
+
user_id=user_id,
|
|
3103
|
+
session_id=session_id,
|
|
3104
|
+
**kwargs,
|
|
3105
|
+
)
|
|
3106
|
+
|
|
3107
|
+
_exit_on = exit_on or ["exit", "quit", "bye", "stop"]
|
|
3108
|
+
while True:
|
|
3109
|
+
message = Prompt.ask(f"[bold] {emoji} {user} [/bold]")
|
|
3110
|
+
if message in _exit_on:
|
|
3111
|
+
break
|
|
3112
|
+
|
|
3113
|
+
await self.aprint_response(
|
|
3114
|
+
input=message,
|
|
3115
|
+
stream=stream,
|
|
3116
|
+
stream_intermediate_steps=stream_intermediate_steps,
|
|
3117
|
+
markdown=markdown,
|
|
3118
|
+
show_time=show_time,
|
|
3119
|
+
show_step_details=show_step_details,
|
|
3120
|
+
user_id=user_id,
|
|
3121
|
+
session_id=session_id,
|
|
3122
|
+
**kwargs,
|
|
3123
|
+
)
|