agno 2.3.26__py3-none-any.whl → 2.4.1__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/__init__.py +4 -0
- agno/agent/agent.py +1368 -541
- agno/agent/remote.py +13 -0
- agno/db/base.py +339 -0
- agno/db/postgres/async_postgres.py +116 -12
- agno/db/postgres/postgres.py +1242 -25
- agno/db/postgres/schemas.py +48 -1
- agno/db/sqlite/async_sqlite.py +119 -4
- agno/db/sqlite/schemas.py +51 -0
- agno/db/sqlite/sqlite.py +1186 -13
- agno/db/utils.py +37 -1
- agno/integrations/discord/client.py +12 -1
- agno/knowledge/__init__.py +4 -0
- agno/knowledge/chunking/code.py +1 -1
- agno/knowledge/chunking/semantic.py +1 -1
- agno/knowledge/chunking/strategy.py +4 -0
- agno/knowledge/filesystem.py +412 -0
- agno/knowledge/knowledge.py +3722 -2182
- agno/knowledge/protocol.py +134 -0
- agno/knowledge/reader/arxiv_reader.py +2 -2
- agno/knowledge/reader/base.py +9 -7
- agno/knowledge/reader/csv_reader.py +236 -13
- agno/knowledge/reader/docx_reader.py +2 -2
- agno/knowledge/reader/field_labeled_csv_reader.py +169 -5
- agno/knowledge/reader/firecrawl_reader.py +2 -2
- agno/knowledge/reader/json_reader.py +2 -2
- agno/knowledge/reader/markdown_reader.py +2 -2
- agno/knowledge/reader/pdf_reader.py +5 -4
- agno/knowledge/reader/pptx_reader.py +2 -2
- agno/knowledge/reader/reader_factory.py +118 -1
- agno/knowledge/reader/s3_reader.py +2 -2
- agno/knowledge/reader/tavily_reader.py +2 -2
- agno/knowledge/reader/text_reader.py +2 -2
- agno/knowledge/reader/web_search_reader.py +2 -2
- agno/knowledge/reader/website_reader.py +5 -3
- agno/knowledge/reader/wikipedia_reader.py +2 -2
- agno/knowledge/reader/youtube_reader.py +2 -2
- agno/knowledge/remote_content/__init__.py +29 -0
- agno/knowledge/remote_content/config.py +204 -0
- agno/knowledge/remote_content/remote_content.py +74 -17
- agno/knowledge/utils.py +37 -29
- agno/learn/__init__.py +6 -0
- agno/learn/machine.py +35 -0
- agno/learn/schemas.py +82 -11
- agno/learn/stores/__init__.py +3 -0
- agno/learn/stores/decision_log.py +1156 -0
- agno/learn/stores/learned_knowledge.py +6 -6
- agno/models/anthropic/claude.py +24 -0
- agno/models/aws/bedrock.py +20 -0
- agno/models/base.py +60 -6
- agno/models/cerebras/cerebras.py +34 -2
- agno/models/cohere/chat.py +25 -0
- agno/models/google/gemini.py +50 -5
- agno/models/litellm/chat.py +38 -0
- agno/models/n1n/__init__.py +3 -0
- agno/models/n1n/n1n.py +57 -0
- agno/models/openai/chat.py +25 -1
- agno/models/openrouter/openrouter.py +46 -0
- agno/models/perplexity/perplexity.py +2 -0
- agno/models/response.py +16 -0
- agno/os/app.py +83 -44
- agno/os/interfaces/slack/router.py +10 -1
- agno/os/interfaces/whatsapp/router.py +6 -0
- agno/os/middleware/__init__.py +2 -0
- agno/os/middleware/trailing_slash.py +27 -0
- agno/os/router.py +1 -0
- agno/os/routers/agents/router.py +29 -16
- agno/os/routers/agents/schema.py +6 -4
- agno/os/routers/components/__init__.py +3 -0
- agno/os/routers/components/components.py +475 -0
- agno/os/routers/evals/schemas.py +4 -3
- agno/os/routers/health.py +3 -3
- agno/os/routers/knowledge/knowledge.py +128 -3
- agno/os/routers/knowledge/schemas.py +12 -0
- agno/os/routers/memory/schemas.py +4 -2
- agno/os/routers/metrics/metrics.py +9 -11
- agno/os/routers/metrics/schemas.py +10 -6
- agno/os/routers/registry/__init__.py +3 -0
- agno/os/routers/registry/registry.py +337 -0
- agno/os/routers/teams/router.py +20 -8
- agno/os/routers/teams/schema.py +6 -4
- agno/os/routers/traces/traces.py +5 -5
- agno/os/routers/workflows/router.py +38 -11
- agno/os/routers/workflows/schema.py +1 -1
- agno/os/schema.py +92 -26
- agno/os/utils.py +84 -19
- agno/reasoning/anthropic.py +2 -2
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +6 -7
- agno/reasoning/gemini.py +2 -2
- agno/reasoning/helpers.py +6 -7
- agno/reasoning/manager.py +4 -10
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +2 -2
- agno/reasoning/vertexai.py +2 -2
- agno/registry/__init__.py +3 -0
- agno/registry/registry.py +68 -0
- agno/run/agent.py +59 -0
- agno/run/base.py +7 -0
- agno/run/team.py +57 -0
- agno/skills/agent_skills.py +10 -3
- agno/team/__init__.py +3 -1
- agno/team/team.py +1165 -330
- agno/tools/duckduckgo.py +25 -71
- agno/tools/exa.py +0 -21
- agno/tools/function.py +35 -83
- agno/tools/knowledge.py +9 -4
- agno/tools/mem0.py +11 -10
- agno/tools/memory.py +47 -46
- agno/tools/parallel.py +0 -7
- agno/tools/reasoning.py +30 -23
- agno/tools/tavily.py +4 -1
- agno/tools/websearch.py +93 -0
- agno/tools/website.py +1 -1
- agno/tools/wikipedia.py +1 -1
- agno/tools/workflow.py +48 -47
- agno/utils/agent.py +42 -5
- agno/utils/events.py +160 -2
- agno/utils/print_response/agent.py +0 -31
- agno/utils/print_response/team.py +0 -2
- agno/utils/print_response/workflow.py +0 -2
- agno/utils/team.py +61 -11
- agno/vectordb/lancedb/lance_db.py +4 -1
- agno/vectordb/mongodb/mongodb.py +1 -1
- agno/vectordb/pgvector/pgvector.py +3 -3
- agno/vectordb/qdrant/qdrant.py +4 -4
- agno/workflow/__init__.py +3 -1
- agno/workflow/condition.py +0 -21
- agno/workflow/loop.py +0 -21
- agno/workflow/parallel.py +0 -21
- agno/workflow/router.py +0 -21
- agno/workflow/step.py +117 -24
- agno/workflow/steps.py +0 -21
- agno/workflow/workflow.py +427 -63
- {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/METADATA +49 -76
- {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/RECORD +140 -126
- {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/WHEEL +1 -1
- {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/top_level.txt +0 -0
agno/workflow/workflow.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import warnings
|
|
3
2
|
from dataclasses import dataclass
|
|
4
3
|
from datetime import datetime
|
|
5
4
|
from os import getenv
|
|
@@ -29,11 +28,13 @@ if TYPE_CHECKING:
|
|
|
29
28
|
from agno.os.managers import WebSocketHandler
|
|
30
29
|
|
|
31
30
|
from agno.agent.agent import Agent
|
|
32
|
-
from agno.db.base import AsyncBaseDb, BaseDb, SessionType
|
|
31
|
+
from agno.db.base import AsyncBaseDb, BaseDb, ComponentType, SessionType
|
|
32
|
+
from agno.db.utils import db_from_dict
|
|
33
33
|
from agno.exceptions import InputCheckError, OutputCheckError, RunCancelledException
|
|
34
34
|
from agno.media import Audio, File, Image, Video
|
|
35
35
|
from agno.models.message import Message
|
|
36
36
|
from agno.models.metrics import Metrics
|
|
37
|
+
from agno.registry import Registry
|
|
37
38
|
from agno.run import RunContext, RunStatus
|
|
38
39
|
from agno.run.agent import RunContentEvent, RunEvent, RunOutput
|
|
39
40
|
from agno.run.cancel import (
|
|
@@ -79,6 +80,7 @@ from agno.utils.print_response.workflow import (
|
|
|
79
80
|
print_response,
|
|
80
81
|
print_response_stream,
|
|
81
82
|
)
|
|
83
|
+
from agno.utils.string import generate_id_from_name
|
|
82
84
|
from agno.workflow.agent import WorkflowAgent
|
|
83
85
|
from agno.workflow.condition import Condition
|
|
84
86
|
from agno.workflow.loop import Loop
|
|
@@ -192,9 +194,6 @@ class Workflow:
|
|
|
192
194
|
# Number of historical runs to include in the messages
|
|
193
195
|
num_history_runs: int = 3
|
|
194
196
|
|
|
195
|
-
# Deprecated. Use stream_events instead.
|
|
196
|
-
stream_intermediate_steps: bool = False
|
|
197
|
-
|
|
198
197
|
# If True, run hooks as FastAPI background tasks (non-blocking). Set by AgentOS.
|
|
199
198
|
_run_hooks_in_background: bool = False
|
|
200
199
|
|
|
@@ -214,7 +213,6 @@ class Workflow:
|
|
|
214
213
|
debug_mode: Optional[bool] = False,
|
|
215
214
|
stream: Optional[bool] = None,
|
|
216
215
|
stream_events: bool = False,
|
|
217
|
-
stream_intermediate_steps: bool = False,
|
|
218
216
|
stream_executor_events: bool = True,
|
|
219
217
|
store_events: bool = False,
|
|
220
218
|
events_to_skip: Optional[List[Union[WorkflowRunEvent, RunEvent, TeamRunEvent]]] = None,
|
|
@@ -250,14 +248,7 @@ class Workflow:
|
|
|
250
248
|
self.add_workflow_history_to_steps = add_workflow_history_to_steps
|
|
251
249
|
self.num_history_runs = num_history_runs
|
|
252
250
|
self._workflow_session: Optional[WorkflowSession] = None
|
|
253
|
-
|
|
254
|
-
if stream_intermediate_steps:
|
|
255
|
-
warnings.warn(
|
|
256
|
-
"The 'stream_intermediate_steps' parameter is deprecated and will be removed in future versions. Use 'stream_events' instead.",
|
|
257
|
-
DeprecationWarning,
|
|
258
|
-
stacklevel=2,
|
|
259
|
-
)
|
|
260
|
-
self.stream_events = stream_events or stream_intermediate_steps
|
|
251
|
+
self.stream_events = stream_events
|
|
261
252
|
|
|
262
253
|
# Warn if workflow history is enabled without a database
|
|
263
254
|
if self.add_workflow_history_to_steps and self.db is None:
|
|
@@ -268,10 +259,7 @@ class Workflow:
|
|
|
268
259
|
|
|
269
260
|
def set_id(self) -> None:
|
|
270
261
|
if self.id is None:
|
|
271
|
-
|
|
272
|
-
self.id = self.name.lower().replace(" ", "-")
|
|
273
|
-
else:
|
|
274
|
-
self.id = str(uuid4())
|
|
262
|
+
self.id = generate_id_from_name(self.name)
|
|
275
263
|
|
|
276
264
|
def _has_async_db(self) -> bool:
|
|
277
265
|
return self.db is not None and isinstance(self.db, AsyncBaseDb)
|
|
@@ -353,11 +341,14 @@ class Workflow:
|
|
|
353
341
|
def _initialize_session_state(
|
|
354
342
|
self,
|
|
355
343
|
session_state: Dict[str, Any],
|
|
356
|
-
user_id: Optional[str] = None,
|
|
357
344
|
session_id: Optional[str] = None,
|
|
345
|
+
user_id: Optional[str] = None,
|
|
358
346
|
run_id: Optional[str] = None,
|
|
359
347
|
) -> Dict[str, Any]:
|
|
360
348
|
"""Initialize the session state for the workflow."""
|
|
349
|
+
session_state["workflow_id"] = self.id
|
|
350
|
+
if self.name:
|
|
351
|
+
session_state["workflow_name"] = self.name
|
|
361
352
|
if user_id:
|
|
362
353
|
session_state["current_user_id"] = user_id
|
|
363
354
|
if session_id is not None:
|
|
@@ -365,16 +356,6 @@ class Workflow:
|
|
|
365
356
|
if run_id is not None:
|
|
366
357
|
session_state["current_run_id"] = run_id
|
|
367
358
|
|
|
368
|
-
session_state.update(
|
|
369
|
-
{
|
|
370
|
-
"workflow_id": self.id,
|
|
371
|
-
"run_id": run_id,
|
|
372
|
-
"session_id": session_id,
|
|
373
|
-
}
|
|
374
|
-
)
|
|
375
|
-
if self.name:
|
|
376
|
-
session_state["workflow_name"] = self.name
|
|
377
|
-
|
|
378
359
|
return session_state
|
|
379
360
|
|
|
380
361
|
def _generate_workflow_session_name(self) -> str:
|
|
@@ -555,6 +536,313 @@ class Workflow:
|
|
|
555
536
|
# -*- Delete session
|
|
556
537
|
self.db.delete_session(session_id=session_id)
|
|
557
538
|
|
|
539
|
+
# -*- Serialization Functions
|
|
540
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
541
|
+
"""
|
|
542
|
+
Convert the Workflow to a dictionary.
|
|
543
|
+
|
|
544
|
+
Returns:
|
|
545
|
+
Dict[str, Any]: Dictionary representation of the workflow configuration
|
|
546
|
+
"""
|
|
547
|
+
config: Dict[str, Any] = {}
|
|
548
|
+
|
|
549
|
+
# --- Workflow settings ---
|
|
550
|
+
if self.name is not None:
|
|
551
|
+
config["name"] = self.name
|
|
552
|
+
if self.id is not None:
|
|
553
|
+
config["id"] = self.id
|
|
554
|
+
if self.description is not None:
|
|
555
|
+
config["description"] = self.description
|
|
556
|
+
|
|
557
|
+
# --- User settings ---
|
|
558
|
+
if self.user_id is not None:
|
|
559
|
+
config["user_id"] = self.user_id
|
|
560
|
+
|
|
561
|
+
# --- Session settings ---
|
|
562
|
+
if self.session_id is not None:
|
|
563
|
+
config["session_id"] = self.session_id
|
|
564
|
+
if self.session_state is not None:
|
|
565
|
+
config["session_state"] = self.session_state
|
|
566
|
+
config["overwrite_db_session_state"] = self.overwrite_db_session_state
|
|
567
|
+
|
|
568
|
+
# --- Database settings ---
|
|
569
|
+
if self.db is not None and hasattr(self.db, "to_dict"):
|
|
570
|
+
config["db"] = self.db.to_dict()
|
|
571
|
+
|
|
572
|
+
# --- History settings ---
|
|
573
|
+
config["add_workflow_history_to_steps"] = self.add_workflow_history_to_steps
|
|
574
|
+
config["num_history_runs"] = self.num_history_runs
|
|
575
|
+
|
|
576
|
+
# --- Streaming settings ---
|
|
577
|
+
if self.stream is not None:
|
|
578
|
+
config["stream"] = self.stream
|
|
579
|
+
config["stream_events"] = self.stream_events
|
|
580
|
+
config["stream_executor_events"] = self.stream_executor_events
|
|
581
|
+
config["store_events"] = self.store_events
|
|
582
|
+
config["store_executor_outputs"] = self.store_executor_outputs
|
|
583
|
+
|
|
584
|
+
# --- Schema settings ---
|
|
585
|
+
if self.input_schema is not None:
|
|
586
|
+
if isinstance(self.input_schema, type) and issubclass(self.input_schema, BaseModel):
|
|
587
|
+
config["input_schema"] = self.input_schema.__name__
|
|
588
|
+
elif isinstance(self.input_schema, dict):
|
|
589
|
+
config["input_schema"] = self.input_schema
|
|
590
|
+
|
|
591
|
+
# --- Metadata ---
|
|
592
|
+
if self.metadata is not None:
|
|
593
|
+
config["metadata"] = self.metadata
|
|
594
|
+
|
|
595
|
+
# --- Debug and telemetry settings ---
|
|
596
|
+
config["debug_mode"] = self.debug_mode
|
|
597
|
+
config["telemetry"] = self.telemetry
|
|
598
|
+
|
|
599
|
+
# --- Steps ---
|
|
600
|
+
# TODO: Implement steps serialization for step types other than Step
|
|
601
|
+
if self.steps and isinstance(self.steps, list):
|
|
602
|
+
config["steps"] = [step.to_dict() for step in self.steps if hasattr(step, "to_dict")]
|
|
603
|
+
|
|
604
|
+
return config
|
|
605
|
+
|
|
606
|
+
@classmethod
|
|
607
|
+
def from_dict(
|
|
608
|
+
cls,
|
|
609
|
+
data: Dict[str, Any],
|
|
610
|
+
db: Optional["BaseDb"] = None,
|
|
611
|
+
links: Optional[List[Dict[str, Any]]] = None,
|
|
612
|
+
registry: Optional[Registry] = None,
|
|
613
|
+
) -> "Workflow":
|
|
614
|
+
"""
|
|
615
|
+
Create a Workflow from a dictionary.
|
|
616
|
+
|
|
617
|
+
Args:
|
|
618
|
+
data: Dictionary containing workflow configuration
|
|
619
|
+
db: Optional database for loading agents/teams in steps
|
|
620
|
+
links: Optional links for this workflow version
|
|
621
|
+
registry: Optional registry for rehydrating executors
|
|
622
|
+
|
|
623
|
+
Returns:
|
|
624
|
+
Workflow: Reconstructed workflow instance
|
|
625
|
+
"""
|
|
626
|
+
config = data.copy()
|
|
627
|
+
|
|
628
|
+
# --- Handle DB reconstruction ---
|
|
629
|
+
if "db" in config and isinstance(config["db"], dict):
|
|
630
|
+
db_data = config["db"]
|
|
631
|
+
db_id = db_data.get("id")
|
|
632
|
+
|
|
633
|
+
# First try to get the db from the registry (preferred - reuses existing connection)
|
|
634
|
+
if registry and db_id:
|
|
635
|
+
registry_db = registry.get_db(db_id)
|
|
636
|
+
if registry_db is not None:
|
|
637
|
+
config["db"] = registry_db
|
|
638
|
+
else:
|
|
639
|
+
del config["db"]
|
|
640
|
+
else:
|
|
641
|
+
# No registry or no db_id, fall back to creating from dict
|
|
642
|
+
config["db"] = db_from_dict(db_data)
|
|
643
|
+
if config["db"] is None:
|
|
644
|
+
del config["db"]
|
|
645
|
+
|
|
646
|
+
# --- Handle Schema reconstruction ---
|
|
647
|
+
if "input_schema" in config and isinstance(config["input_schema"], str):
|
|
648
|
+
schema_cls = registry.get_schema(config["input_schema"]) if registry else None
|
|
649
|
+
if schema_cls:
|
|
650
|
+
config["input_schema"] = schema_cls
|
|
651
|
+
else:
|
|
652
|
+
log_warning(f"Input schema {config['input_schema']} not found in registry, skipping.")
|
|
653
|
+
del config["input_schema"]
|
|
654
|
+
|
|
655
|
+
# --- Handle steps reconstruction ---
|
|
656
|
+
steps: Optional[WorkflowSteps] = None
|
|
657
|
+
if "steps" in config and config["steps"]:
|
|
658
|
+
steps = [Step.from_dict(step_data, db=db, links=links, registry=registry) for step_data in config["steps"]]
|
|
659
|
+
del config["steps"]
|
|
660
|
+
|
|
661
|
+
return cls(
|
|
662
|
+
# --- Workflow settings ---
|
|
663
|
+
name=config.get("name"),
|
|
664
|
+
id=config.get("id"),
|
|
665
|
+
description=config.get("description"),
|
|
666
|
+
# --- User settings ---
|
|
667
|
+
user_id=config.get("user_id"),
|
|
668
|
+
# --- Session settings ---
|
|
669
|
+
session_id=config.get("session_id"),
|
|
670
|
+
session_state=config.get("session_state"),
|
|
671
|
+
overwrite_db_session_state=config.get("overwrite_db_session_state", False),
|
|
672
|
+
# --- Database settings ---
|
|
673
|
+
db=config.get("db"),
|
|
674
|
+
# --- History settings ---
|
|
675
|
+
add_workflow_history_to_steps=config.get("add_workflow_history_to_steps", False),
|
|
676
|
+
num_history_runs=config.get("num_history_runs", 3),
|
|
677
|
+
# --- Streaming settings ---
|
|
678
|
+
stream=config.get("stream"),
|
|
679
|
+
stream_events=config.get("stream_events", False),
|
|
680
|
+
stream_executor_events=config.get("stream_executor_events", True),
|
|
681
|
+
store_events=config.get("store_events", False),
|
|
682
|
+
store_executor_outputs=config.get("store_executor_outputs", True),
|
|
683
|
+
# --- Schema settings ---
|
|
684
|
+
# input_schema=config.get("input_schema"), # TODO
|
|
685
|
+
# --- Metadata ---
|
|
686
|
+
metadata=config.get("metadata"),
|
|
687
|
+
# --- Debug and telemetry settings ---
|
|
688
|
+
debug_mode=config.get("debug_mode", False),
|
|
689
|
+
telemetry=config.get("telemetry", True),
|
|
690
|
+
# --- Steps ---
|
|
691
|
+
steps=steps,
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
def save(
|
|
695
|
+
self,
|
|
696
|
+
*,
|
|
697
|
+
db: Optional["BaseDb"] = None,
|
|
698
|
+
stage: str = "published",
|
|
699
|
+
label: Optional[str] = None,
|
|
700
|
+
notes: Optional[str] = None,
|
|
701
|
+
) -> Optional[int]:
|
|
702
|
+
"""
|
|
703
|
+
Save the workflow component and config.
|
|
704
|
+
|
|
705
|
+
Args:
|
|
706
|
+
db: The database to save the component and config to.
|
|
707
|
+
stage: The stage of the component. Defaults to "published".
|
|
708
|
+
label: The label of the component.
|
|
709
|
+
notes: The notes of the component.
|
|
710
|
+
|
|
711
|
+
Returns:
|
|
712
|
+
Optional[int]: The version number of the saved config.
|
|
713
|
+
"""
|
|
714
|
+
db_ = db or self.db
|
|
715
|
+
if not db_:
|
|
716
|
+
raise ValueError("Db not initialized or provided")
|
|
717
|
+
if not isinstance(db_, BaseDb):
|
|
718
|
+
raise ValueError("Async databases not yet supported for save(). Use a sync database.")
|
|
719
|
+
if self.id is None:
|
|
720
|
+
self.id = generate_id_from_name(self.name)
|
|
721
|
+
|
|
722
|
+
# Track saved entity versions for pinning links
|
|
723
|
+
saved_versions: Dict[str, int] = {}
|
|
724
|
+
|
|
725
|
+
# Collect all links
|
|
726
|
+
all_links = []
|
|
727
|
+
steps_config = []
|
|
728
|
+
|
|
729
|
+
try:
|
|
730
|
+
steps_to_save = self.steps if isinstance(self.steps, list) else []
|
|
731
|
+
for position, step in enumerate(steps_to_save):
|
|
732
|
+
# TODO: Support other Step types
|
|
733
|
+
if isinstance(step, Step):
|
|
734
|
+
# TODO: Allow not saving a new config if the agent/team already has a published config and no changes have been made
|
|
735
|
+
# Save agent/team if present and capture version
|
|
736
|
+
if step.agent and isinstance(step.agent, Agent):
|
|
737
|
+
agent_version = step.agent.save(
|
|
738
|
+
db=db_,
|
|
739
|
+
stage=stage,
|
|
740
|
+
label=label,
|
|
741
|
+
notes=notes,
|
|
742
|
+
)
|
|
743
|
+
if step.agent.id is not None and agent_version is not None:
|
|
744
|
+
saved_versions[step.agent.id] = agent_version
|
|
745
|
+
|
|
746
|
+
if step.team and isinstance(step.team, Team):
|
|
747
|
+
team_version = step.team.save(db=db_, stage=stage, label=label, notes=notes)
|
|
748
|
+
if step.team.id is not None and team_version is not None:
|
|
749
|
+
saved_versions[step.team.id] = team_version
|
|
750
|
+
|
|
751
|
+
# Add step config
|
|
752
|
+
steps_config.append(step.to_dict())
|
|
753
|
+
|
|
754
|
+
# Add links with position and pinned version
|
|
755
|
+
for link in step.get_links(position=position):
|
|
756
|
+
# Pin the version if we just saved it
|
|
757
|
+
if link["child_component_id"] in saved_versions:
|
|
758
|
+
link["child_version"] = saved_versions[link["child_component_id"]]
|
|
759
|
+
all_links.append(link)
|
|
760
|
+
|
|
761
|
+
db_.upsert_component(
|
|
762
|
+
component_id=self.id,
|
|
763
|
+
component_type=ComponentType.WORKFLOW,
|
|
764
|
+
name=self.name,
|
|
765
|
+
description=self.description,
|
|
766
|
+
metadata=self.metadata,
|
|
767
|
+
)
|
|
768
|
+
config = db_.upsert_config(
|
|
769
|
+
component_id=self.id,
|
|
770
|
+
config=self.to_dict(),
|
|
771
|
+
links=all_links,
|
|
772
|
+
label=label,
|
|
773
|
+
stage=stage,
|
|
774
|
+
notes=notes,
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
return config.get("version")
|
|
778
|
+
|
|
779
|
+
except Exception as e:
|
|
780
|
+
log_error(f"Error saving workflow: {e}")
|
|
781
|
+
return None
|
|
782
|
+
|
|
783
|
+
@classmethod
|
|
784
|
+
def load(
|
|
785
|
+
cls,
|
|
786
|
+
id: str,
|
|
787
|
+
*,
|
|
788
|
+
db: "BaseDb",
|
|
789
|
+
registry: Optional["Registry"] = None,
|
|
790
|
+
label: Optional[str] = None,
|
|
791
|
+
version: Optional[int] = None,
|
|
792
|
+
) -> Optional["Workflow"]:
|
|
793
|
+
"""
|
|
794
|
+
Load a workflow by id.
|
|
795
|
+
|
|
796
|
+
Args:
|
|
797
|
+
id: The id of the workflow to load.
|
|
798
|
+
db: The database to load the workflow from.
|
|
799
|
+
label: The label of the workflow to load.
|
|
800
|
+
|
|
801
|
+
Returns:
|
|
802
|
+
The workflow loaded from the database or None if not found.
|
|
803
|
+
"""
|
|
804
|
+
# TODO: Use db.load_component_graph instead of get_config
|
|
805
|
+
data: Optional[Dict[str, Any]] = db.get_config(component_id=id, label=label, version=version)
|
|
806
|
+
if data is None:
|
|
807
|
+
return None
|
|
808
|
+
|
|
809
|
+
config = data.get("config")
|
|
810
|
+
if config is None:
|
|
811
|
+
return None
|
|
812
|
+
|
|
813
|
+
workflow = cls.from_dict(config, db=db, registry=registry)
|
|
814
|
+
|
|
815
|
+
workflow.id = id
|
|
816
|
+
workflow.db = db
|
|
817
|
+
|
|
818
|
+
return workflow
|
|
819
|
+
|
|
820
|
+
def delete(
|
|
821
|
+
self,
|
|
822
|
+
*,
|
|
823
|
+
db: Optional["BaseDb"] = None,
|
|
824
|
+
hard_delete: bool = False,
|
|
825
|
+
) -> bool:
|
|
826
|
+
"""
|
|
827
|
+
Delete the workflow component.
|
|
828
|
+
|
|
829
|
+
Args:
|
|
830
|
+
db: The database to delete the workflow from.
|
|
831
|
+
hard_delete: Whether to hard delete the workflow.
|
|
832
|
+
|
|
833
|
+
Returns:
|
|
834
|
+
True if the workflow was deleted, False otherwise.
|
|
835
|
+
"""
|
|
836
|
+
db_ = db or self.db
|
|
837
|
+
if not db_:
|
|
838
|
+
raise ValueError("Db not initialized or provided")
|
|
839
|
+
if not isinstance(db_, BaseDb):
|
|
840
|
+
raise ValueError("Async databases not yet supported for delete(). Use a sync database.")
|
|
841
|
+
if self.id is None:
|
|
842
|
+
raise ValueError("Cannot delete workflow without an id")
|
|
843
|
+
|
|
844
|
+
return db_.delete_component(component_id=self.id, hard_delete=hard_delete)
|
|
845
|
+
|
|
558
846
|
async def aget_run_output(self, run_id: str, session_id: Optional[str] = None) -> Optional[WorkflowRunOutput]:
|
|
559
847
|
"""Get a RunOutput from the database."""
|
|
560
848
|
if self._workflow_session is not None:
|
|
@@ -696,12 +984,17 @@ class Workflow:
|
|
|
696
984
|
if workflow_session is None:
|
|
697
985
|
# Creating new session if none found
|
|
698
986
|
log_debug(f"Creating new WorkflowSession: {session_id}")
|
|
987
|
+
session_data = {}
|
|
988
|
+
if self.session_state is not None:
|
|
989
|
+
from copy import deepcopy
|
|
990
|
+
|
|
991
|
+
session_data["session_state"] = deepcopy(self.session_state)
|
|
699
992
|
workflow_session = WorkflowSession(
|
|
700
993
|
session_id=session_id,
|
|
701
994
|
workflow_id=self.id,
|
|
702
995
|
user_id=user_id,
|
|
703
996
|
workflow_data=self._get_workflow_data(),
|
|
704
|
-
session_data=
|
|
997
|
+
session_data=session_data,
|
|
705
998
|
metadata=self.metadata,
|
|
706
999
|
created_at=int(time()),
|
|
707
1000
|
)
|
|
@@ -2744,6 +3037,7 @@ class Workflow:
|
|
|
2744
3037
|
execution_input: WorkflowExecutionInput,
|
|
2745
3038
|
run_context: RunContext,
|
|
2746
3039
|
stream: bool = False,
|
|
3040
|
+
stream_events: bool = False,
|
|
2747
3041
|
**kwargs: Any,
|
|
2748
3042
|
) -> Union[WorkflowRunOutput, Iterator[WorkflowRunOutputEvent]]:
|
|
2749
3043
|
"""
|
|
@@ -2757,7 +3051,7 @@ class Workflow:
|
|
|
2757
3051
|
execution_input: The execution input
|
|
2758
3052
|
run_context: The run context
|
|
2759
3053
|
stream: Whether to stream the response
|
|
2760
|
-
|
|
3054
|
+
stream_events: Whether to stream all events
|
|
2761
3055
|
|
|
2762
3056
|
Returns:
|
|
2763
3057
|
WorkflowRunOutput if stream=False, Iterator[WorkflowRunOutputEvent] if stream=True
|
|
@@ -2769,6 +3063,7 @@ class Workflow:
|
|
|
2769
3063
|
execution_input=execution_input,
|
|
2770
3064
|
run_context=run_context,
|
|
2771
3065
|
stream=stream,
|
|
3066
|
+
stream_events=stream_events,
|
|
2772
3067
|
**kwargs,
|
|
2773
3068
|
)
|
|
2774
3069
|
else:
|
|
@@ -2803,7 +3098,7 @@ class Workflow:
|
|
|
2803
3098
|
|
|
2804
3099
|
from agno.run.workflow import WorkflowCompletedEvent, WorkflowRunOutputEvent
|
|
2805
3100
|
|
|
2806
|
-
# Initialize agent with
|
|
3101
|
+
# Initialize agent with stream_events=True so tool yields events
|
|
2807
3102
|
self._initialize_workflow_agent(session, execution_input, run_context=run_context, stream=stream)
|
|
2808
3103
|
|
|
2809
3104
|
# Build dependencies with workflow context
|
|
@@ -2843,8 +3138,8 @@ class Workflow:
|
|
|
2843
3138
|
for event in self.agent.run( # type: ignore[union-attr]
|
|
2844
3139
|
input=agent_input,
|
|
2845
3140
|
stream=True,
|
|
2846
|
-
|
|
2847
|
-
|
|
3141
|
+
stream_events=True,
|
|
3142
|
+
yield_run_output=True,
|
|
2848
3143
|
session_id=session.session_id,
|
|
2849
3144
|
dependencies=run_context.dependencies, # Pass context dynamically per-run
|
|
2850
3145
|
session_state=run_context.session_state, # Pass session state dynamically per-run
|
|
@@ -3232,8 +3527,8 @@ class Workflow:
|
|
|
3232
3527
|
async for event in self.agent.arun( # type: ignore[union-attr]
|
|
3233
3528
|
input=agent_input,
|
|
3234
3529
|
stream=True,
|
|
3235
|
-
|
|
3236
|
-
|
|
3530
|
+
stream_events=True,
|
|
3531
|
+
yield_run_output=True,
|
|
3237
3532
|
session_id=session.session_id,
|
|
3238
3533
|
dependencies=run_context.dependencies, # Pass context dynamically per-run
|
|
3239
3534
|
session_state=run_context.session_state, # Pass session state dynamically per-run
|
|
@@ -3525,7 +3820,6 @@ class Workflow:
|
|
|
3525
3820
|
files: Optional[List[File]] = None,
|
|
3526
3821
|
stream: Literal[False] = False,
|
|
3527
3822
|
stream_events: Optional[bool] = None,
|
|
3528
|
-
stream_intermediate_steps: Optional[bool] = None,
|
|
3529
3823
|
background: Optional[bool] = False,
|
|
3530
3824
|
background_tasks: Optional[Any] = None,
|
|
3531
3825
|
) -> WorkflowRunOutput: ...
|
|
@@ -3545,7 +3839,6 @@ class Workflow:
|
|
|
3545
3839
|
files: Optional[List[File]] = None,
|
|
3546
3840
|
stream: Literal[True] = True,
|
|
3547
3841
|
stream_events: Optional[bool] = None,
|
|
3548
|
-
stream_intermediate_steps: Optional[bool] = None,
|
|
3549
3842
|
background: Optional[bool] = False,
|
|
3550
3843
|
background_tasks: Optional[Any] = None,
|
|
3551
3844
|
) -> Iterator[WorkflowRunOutputEvent]: ...
|
|
@@ -3564,7 +3857,6 @@ class Workflow:
|
|
|
3564
3857
|
files: Optional[List[File]] = None,
|
|
3565
3858
|
stream: bool = False,
|
|
3566
3859
|
stream_events: Optional[bool] = None,
|
|
3567
|
-
stream_intermediate_steps: Optional[bool] = None,
|
|
3568
3860
|
background: Optional[bool] = False,
|
|
3569
3861
|
background_tasks: Optional[Any] = None,
|
|
3570
3862
|
**kwargs: Any,
|
|
@@ -3593,23 +3885,25 @@ class Workflow:
|
|
|
3593
3885
|
workflow_session = self.read_or_create_session(session_id=session_id, user_id=user_id)
|
|
3594
3886
|
self._update_metadata(session=workflow_session)
|
|
3595
3887
|
|
|
3596
|
-
# Initialize session state
|
|
3597
|
-
session_state = self.
|
|
3888
|
+
# Initialize session state. Get it from DB if relevant.
|
|
3889
|
+
session_state = self._load_session_state(
|
|
3890
|
+
session=workflow_session,
|
|
3598
3891
|
session_state=session_state if session_state is not None else {},
|
|
3599
|
-
|
|
3892
|
+
)
|
|
3893
|
+
|
|
3894
|
+
# Add current session/user/run info to session_state
|
|
3895
|
+
session_state = self._initialize_session_state(
|
|
3896
|
+
session_state=session_state,
|
|
3600
3897
|
session_id=session_id,
|
|
3898
|
+
user_id=user_id,
|
|
3601
3899
|
run_id=run_id,
|
|
3602
3900
|
)
|
|
3603
|
-
# Update session state from DB
|
|
3604
|
-
session_state = self._load_session_state(session=workflow_session, session_state=session_state)
|
|
3605
3901
|
|
|
3606
3902
|
log_debug(f"Workflow Run Start: {self.name}", center=True)
|
|
3607
3903
|
|
|
3608
3904
|
# Use simple defaults
|
|
3609
3905
|
stream = stream or self.stream or False
|
|
3610
|
-
stream_events =
|
|
3611
|
-
self.stream_events or self.stream_intermediate_steps
|
|
3612
|
-
)
|
|
3906
|
+
stream_events = stream_events or self.stream_events
|
|
3613
3907
|
|
|
3614
3908
|
# Can't stream events if streaming is disabled
|
|
3615
3909
|
if stream is False:
|
|
@@ -3641,6 +3935,8 @@ class Workflow:
|
|
|
3641
3935
|
session_id=session_id,
|
|
3642
3936
|
user_id=user_id,
|
|
3643
3937
|
session_state=session_state,
|
|
3938
|
+
workflow_id=self.id,
|
|
3939
|
+
workflow_name=self.name,
|
|
3644
3940
|
)
|
|
3645
3941
|
|
|
3646
3942
|
# Execute workflow agent if configured
|
|
@@ -3651,6 +3947,7 @@ class Workflow:
|
|
|
3651
3947
|
execution_input=inputs,
|
|
3652
3948
|
run_context=run_context,
|
|
3653
3949
|
stream=stream,
|
|
3950
|
+
stream_events=stream_events,
|
|
3654
3951
|
**kwargs,
|
|
3655
3952
|
)
|
|
3656
3953
|
|
|
@@ -3704,7 +4001,6 @@ class Workflow:
|
|
|
3704
4001
|
files: Optional[List[File]] = None,
|
|
3705
4002
|
stream: Literal[False] = False,
|
|
3706
4003
|
stream_events: Optional[bool] = None,
|
|
3707
|
-
stream_intermediate_steps: Optional[bool] = None,
|
|
3708
4004
|
background: Optional[bool] = False,
|
|
3709
4005
|
websocket: Optional[WebSocket] = None,
|
|
3710
4006
|
background_tasks: Optional[Any] = None,
|
|
@@ -3725,7 +4021,6 @@ class Workflow:
|
|
|
3725
4021
|
files: Optional[List[File]] = None,
|
|
3726
4022
|
stream: Literal[True] = True,
|
|
3727
4023
|
stream_events: Optional[bool] = None,
|
|
3728
|
-
stream_intermediate_steps: Optional[bool] = None,
|
|
3729
4024
|
background: Optional[bool] = False,
|
|
3730
4025
|
websocket: Optional[WebSocket] = None,
|
|
3731
4026
|
background_tasks: Optional[Any] = None,
|
|
@@ -3745,7 +4040,6 @@ class Workflow:
|
|
|
3745
4040
|
files: Optional[List[File]] = None,
|
|
3746
4041
|
stream: bool = False,
|
|
3747
4042
|
stream_events: Optional[bool] = None,
|
|
3748
|
-
stream_intermediate_steps: Optional[bool] = False,
|
|
3749
4043
|
background: Optional[bool] = False,
|
|
3750
4044
|
websocket: Optional[WebSocket] = None,
|
|
3751
4045
|
background_tasks: Optional[Any] = None,
|
|
@@ -3766,15 +4060,7 @@ class Workflow:
|
|
|
3766
4060
|
|
|
3767
4061
|
if background:
|
|
3768
4062
|
if stream and websocket:
|
|
3769
|
-
|
|
3770
|
-
if stream_intermediate_steps is not None:
|
|
3771
|
-
warnings.warn(
|
|
3772
|
-
"The 'stream_intermediate_steps' parameter is deprecated and will be removed in future versions. Use 'stream_events' instead.",
|
|
3773
|
-
DeprecationWarning,
|
|
3774
|
-
stacklevel=2,
|
|
3775
|
-
)
|
|
3776
|
-
stream_events = stream_events or stream_intermediate_steps or False
|
|
3777
|
-
|
|
4063
|
+
stream_events = stream_events or False
|
|
3778
4064
|
# Background + Streaming + WebSocket = Real-time events
|
|
3779
4065
|
return self._arun_background_stream( # type: ignore
|
|
3780
4066
|
input=input,
|
|
@@ -3828,9 +4114,7 @@ class Workflow:
|
|
|
3828
4114
|
|
|
3829
4115
|
# Use simple defaults
|
|
3830
4116
|
stream = stream or self.stream or False
|
|
3831
|
-
stream_events =
|
|
3832
|
-
self.stream_events or self.stream_intermediate_steps
|
|
3833
|
-
)
|
|
4117
|
+
stream_events = stream_events or self.stream_events
|
|
3834
4118
|
|
|
3835
4119
|
# Can't stream events if streaming is disabled
|
|
3836
4120
|
if stream is False:
|
|
@@ -4096,7 +4380,8 @@ class Workflow:
|
|
|
4096
4380
|
**kwargs,
|
|
4097
4381
|
)
|
|
4098
4382
|
|
|
4099
|
-
|
|
4383
|
+
# TODO: This is a temporary method to convert the workflow to a dictionary for steps. We need to find a better way to do this.
|
|
4384
|
+
def to_dict_for_steps(self) -> Dict[str, Any]:
|
|
4100
4385
|
"""Convert workflow to dictionary representation"""
|
|
4101
4386
|
|
|
4102
4387
|
def serialize_step(step):
|
|
@@ -4559,6 +4844,7 @@ class Workflow:
|
|
|
4559
4844
|
return new_workflow
|
|
4560
4845
|
except Exception as e:
|
|
4561
4846
|
from agno.utils.log import log_error
|
|
4847
|
+
|
|
4562
4848
|
log_error(f"Failed to create deep copy of {self.__class__.__name__}: {e}")
|
|
4563
4849
|
raise
|
|
4564
4850
|
|
|
@@ -4679,3 +4965,81 @@ class Workflow:
|
|
|
4679
4965
|
return copy(step)
|
|
4680
4966
|
except Exception:
|
|
4681
4967
|
return step
|
|
4968
|
+
|
|
4969
|
+
|
|
4970
|
+
def get_workflow_by_id(
|
|
4971
|
+
db: "BaseDb",
|
|
4972
|
+
id: str,
|
|
4973
|
+
version: Optional[int] = None,
|
|
4974
|
+
label: Optional[str] = None,
|
|
4975
|
+
registry: Optional["Registry"] = None,
|
|
4976
|
+
) -> Optional["Workflow"]:
|
|
4977
|
+
"""
|
|
4978
|
+
Get a Workflow by id from the database (new entities/configs schema).
|
|
4979
|
+
|
|
4980
|
+
Resolution order:
|
|
4981
|
+
- if version is provided: load that version
|
|
4982
|
+
- elif label is provided: load that labeled version
|
|
4983
|
+
- else: load entity.current_version
|
|
4984
|
+
|
|
4985
|
+
Args:
|
|
4986
|
+
db: Database handle.
|
|
4987
|
+
id: Workflow entity_id.
|
|
4988
|
+
version: Optional integer config version.
|
|
4989
|
+
label: Optional version_label.
|
|
4990
|
+
registry: Optional Registry for reconstructing unserializable components.
|
|
4991
|
+
|
|
4992
|
+
Returns:
|
|
4993
|
+
Workflow instance or None.
|
|
4994
|
+
"""
|
|
4995
|
+
try:
|
|
4996
|
+
row = db.get_config(component_id=id, version=version, label=label)
|
|
4997
|
+
if row is None:
|
|
4998
|
+
return None
|
|
4999
|
+
|
|
5000
|
+
cfg = row.get("config") if isinstance(row, dict) else None
|
|
5001
|
+
if cfg is None:
|
|
5002
|
+
raise ValueError(f"Invalid config found for workflow {id}")
|
|
5003
|
+
|
|
5004
|
+
resolved_version = row.get("version")
|
|
5005
|
+
|
|
5006
|
+
# Get links for this workflow version
|
|
5007
|
+
links = db.get_links(component_id=id, version=resolved_version) if resolved_version else []
|
|
5008
|
+
|
|
5009
|
+
workflow = Workflow.from_dict(cfg, db=db, links=links, registry=registry)
|
|
5010
|
+
|
|
5011
|
+
# Ensure workflow.id is set to the component_id
|
|
5012
|
+
workflow.id = id
|
|
5013
|
+
|
|
5014
|
+
return workflow
|
|
5015
|
+
|
|
5016
|
+
except Exception as e:
|
|
5017
|
+
log_error(f"Error loading Workflow {id} from database: {e}")
|
|
5018
|
+
return None
|
|
5019
|
+
|
|
5020
|
+
|
|
5021
|
+
def get_workflows(
|
|
5022
|
+
db: "BaseDb",
|
|
5023
|
+
registry: Optional["Registry"] = None,
|
|
5024
|
+
) -> List["Workflow"]:
|
|
5025
|
+
"""Get all workflows from the database"""
|
|
5026
|
+
workflows: List[Workflow] = []
|
|
5027
|
+
try:
|
|
5028
|
+
components, _ = db.list_components(component_type=ComponentType.WORKFLOW)
|
|
5029
|
+
for component in components:
|
|
5030
|
+
config = db.get_config(component_id=component["component_id"])
|
|
5031
|
+
if config is not None:
|
|
5032
|
+
workflow_config = config.get("config")
|
|
5033
|
+
if workflow_config is not None:
|
|
5034
|
+
component_id = component["component_id"]
|
|
5035
|
+
if "id" not in workflow_config:
|
|
5036
|
+
workflow_config["id"] = component_id
|
|
5037
|
+
workflow = Workflow.from_dict(workflow_config, db=db, registry=registry)
|
|
5038
|
+
# Ensure workflow.id is set to the component_id
|
|
5039
|
+
workflow.id = component_id
|
|
5040
|
+
workflows.append(workflow)
|
|
5041
|
+
return workflows
|
|
5042
|
+
|
|
5043
|
+
except Exception as e:
|
|
5044
|
+
log_error(f"Error loading Workflows from database: {e}")
|
|
5045
|
+
return []
|