agno 2.0.1__py3-none-any.whl → 2.3.0__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 +6015 -2823
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +385 -6
- agno/db/dynamo/dynamo.py +388 -81
- agno/db/dynamo/schemas.py +47 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +435 -64
- agno/db/firestore/schemas.py +11 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +384 -42
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +351 -66
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +339 -48
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +510 -37
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2036 -0
- agno/db/mongo/mongo.py +653 -76
- agno/db/mongo/schemas.py +13 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/mysql.py +687 -25
- agno/db/mysql/schemas.py +61 -37
- agno/db/mysql/utils.py +60 -2
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2001 -0
- agno/db/postgres/postgres.py +676 -57
- agno/db/postgres/schemas.py +43 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +344 -38
- agno/db/redis/schemas.py +18 -0
- agno/db/redis/utils.py +60 -2
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +26 -1
- agno/db/singlestore/singlestore.py +687 -53
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2371 -0
- agno/db/sqlite/schemas.py +24 -0
- agno/db/sqlite/sqlite.py +774 -85
- agno/db/sqlite/utils.py +168 -5
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1361 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +50 -22
- 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 +68 -1
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/integrations/discord/client.py +1 -0
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/chunking/semantic.py +40 -8
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/aws_bedrock.py +9 -4
- agno/knowledge/embedder/azure_openai.py +54 -0
- agno/knowledge/embedder/base.py +2 -0
- agno/knowledge/embedder/cohere.py +184 -5
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/google.py +79 -1
- agno/knowledge/embedder/huggingface.py +9 -4
- agno/knowledge/embedder/jina.py +63 -0
- agno/knowledge/embedder/mistral.py +78 -11
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +13 -0
- agno/knowledge/embedder/openai.py +37 -65
- agno/knowledge/embedder/sentence_transformer.py +8 -4
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +594 -186
- agno/knowledge/reader/base.py +9 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
- agno/knowledge/reader/json_reader.py +6 -5
- agno/knowledge/reader/markdown_reader.py +13 -13
- agno/knowledge/reader/pdf_reader.py +43 -68
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +51 -6
- agno/knowledge/reader/s3_reader.py +3 -15
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +13 -13
- agno/knowledge/reader/web_search_reader.py +2 -43
- agno/knowledge/reader/website_reader.py +43 -25
- agno/knowledge/reranker/__init__.py +2 -8
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +72 -0
- agno/memory/manager.py +336 -82
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/anthropic/claude.py +183 -37
- agno/models/aws/bedrock.py +52 -112
- agno/models/aws/claude.py +33 -1
- agno/models/azure/ai_foundry.py +33 -15
- agno/models/azure/openai_chat.py +25 -8
- agno/models/base.py +999 -519
- agno/models/cerebras/cerebras.py +19 -13
- agno/models/cerebras/cerebras_openai.py +8 -5
- agno/models/cohere/chat.py +27 -1
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/google/gemini.py +103 -31
- agno/models/groq/groq.py +28 -11
- agno/models/huggingface/huggingface.py +2 -1
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/chat.py +18 -1
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/message.py +139 -0
- agno/models/meta/llama.py +27 -10
- agno/models/meta/llama_openai.py +5 -17
- agno/models/nebius/nebius.py +6 -6
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/ollama/chat.py +59 -5
- agno/models/openai/chat.py +69 -29
- agno/models/openai/responses.py +103 -106
- agno/models/openrouter/openrouter.py +41 -3
- agno/models/perplexity/perplexity.py +4 -5
- agno/models/portkey/portkey.py +3 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +77 -1
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/together.py +2 -2
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +2 -2
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +96 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +3 -2
- agno/os/app.py +543 -178
- agno/os/auth.py +24 -14
- agno/os/config.py +1 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/agui.py +23 -7
- agno/os/interfaces/agui/router.py +27 -3
- agno/os/interfaces/agui/utils.py +242 -142
- agno/os/interfaces/base.py +6 -2
- agno/os/interfaces/slack/router.py +81 -23
- agno/os/interfaces/slack/slack.py +29 -14
- agno/os/interfaces/whatsapp/router.py +11 -4
- agno/os/interfaces/whatsapp/whatsapp.py +14 -7
- agno/os/mcp.py +111 -54
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +556 -139
- agno/os/routers/evals/evals.py +71 -34
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/evals/utils.py +6 -5
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +185 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +158 -53
- agno/os/routers/memory/schemas.py +20 -16
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +499 -38
- agno/os/schema.py +308 -198
- agno/os/utils.py +401 -41
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +3 -1
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +2 -2
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +7 -2
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +248 -94
- agno/run/base.py +44 -5
- agno/run/team.py +238 -97
- agno/run/workflow.py +144 -33
- agno/session/agent.py +105 -89
- agno/session/summary.py +65 -25
- agno/session/team.py +176 -96
- agno/session/workflow.py +406 -40
- agno/team/team.py +3854 -1610
- agno/tools/dalle.py +2 -4
- agno/tools/decorator.py +4 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +14 -7
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +250 -30
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +270 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/knowledge.py +3 -3
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +284 -0
- agno/tools/mem0.py +11 -17
- agno/tools/memori.py +1 -53
- agno/tools/memory.py +419 -0
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/scrapegraph.py +58 -31
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/slack.py +18 -3
- agno/tools/spider.py +2 -2
- agno/tools/tavily.py +146 -0
- agno/tools/whatsapp.py +1 -1
- agno/tools/workflow.py +278 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +27 -0
- agno/utils/common.py +90 -1
- agno/utils/events.py +217 -2
- agno/utils/gemini.py +180 -22
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +111 -0
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +188 -10
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +60 -0
- agno/utils/models/claude.py +40 -11
- agno/utils/print_response/agent.py +105 -21
- agno/utils/print_response/team.py +103 -38
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/reasoning.py +22 -1
- agno/utils/serialize.py +32 -0
- agno/utils/streamlit.py +16 -10
- agno/utils/string.py +41 -0
- agno/utils/team.py +98 -9
- agno/utils/tools.py +1 -1
- agno/vectordb/base.py +23 -4
- agno/vectordb/cassandra/cassandra.py +65 -9
- agno/vectordb/chroma/chromadb.py +182 -38
- agno/vectordb/clickhouse/clickhousedb.py +64 -11
- agno/vectordb/couchbase/couchbase.py +105 -10
- agno/vectordb/lancedb/lance_db.py +124 -133
- agno/vectordb/langchaindb/langchaindb.py +25 -7
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +46 -7
- agno/vectordb/milvus/milvus.py +126 -9
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +112 -7
- agno/vectordb/pgvector/pgvector.py +142 -21
- agno/vectordb/pineconedb/pineconedb.py +80 -8
- agno/vectordb/qdrant/qdrant.py +125 -39
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/singlestore/singlestore.py +111 -25
- agno/vectordb/surrealdb/surrealdb.py +31 -5
- agno/vectordb/upstashdb/upstashdb.py +76 -8
- agno/vectordb/weaviate/weaviate.py +86 -15
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +112 -18
- agno/workflow/loop.py +69 -10
- agno/workflow/parallel.py +266 -118
- agno/workflow/router.py +110 -17
- agno/workflow/step.py +638 -129
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +61 -23
- agno/workflow/workflow.py +2085 -272
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
- agno-2.3.0.dist-info/RECORD +577 -0
- agno/knowledge/reader/url_reader.py +0 -128
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -610
- agno/utils/models/aws_claude.py +0 -170
- agno-2.0.1.dist-info/RECORD +0 -515
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/run/workflow.py
CHANGED
|
@@ -6,10 +6,15 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
8
|
from agno.media import Audio, Image, Video
|
|
9
|
-
from agno.run.agent import RunOutput
|
|
10
|
-
from agno.run.base import RunStatus
|
|
11
|
-
from agno.run.team import TeamRunOutput
|
|
12
|
-
from agno.utils.
|
|
9
|
+
from agno.run.agent import RunEvent, RunOutput, run_output_event_from_dict
|
|
10
|
+
from agno.run.base import BaseRunOutputEvent, RunStatus
|
|
11
|
+
from agno.run.team import TeamRunEvent, TeamRunOutput, team_run_output_event_from_dict
|
|
12
|
+
from agno.utils.media import (
|
|
13
|
+
reconstruct_audio_list,
|
|
14
|
+
reconstruct_images,
|
|
15
|
+
reconstruct_response_audio,
|
|
16
|
+
reconstruct_videos,
|
|
17
|
+
)
|
|
13
18
|
|
|
14
19
|
if TYPE_CHECKING:
|
|
15
20
|
from agno.workflow.types import StepOutput, WorkflowMetrics
|
|
@@ -26,6 +31,9 @@ class WorkflowRunEvent(str, Enum):
|
|
|
26
31
|
workflow_cancelled = "WorkflowCancelled"
|
|
27
32
|
workflow_error = "WorkflowError"
|
|
28
33
|
|
|
34
|
+
workflow_agent_started = "WorkflowAgentStarted"
|
|
35
|
+
workflow_agent_completed = "WorkflowAgentCompleted"
|
|
36
|
+
|
|
29
37
|
step_started = "StepStarted"
|
|
30
38
|
step_completed = "StepCompleted"
|
|
31
39
|
step_error = "StepError"
|
|
@@ -53,7 +61,7 @@ class WorkflowRunEvent(str, Enum):
|
|
|
53
61
|
|
|
54
62
|
|
|
55
63
|
@dataclass
|
|
56
|
-
class BaseWorkflowRunOutputEvent:
|
|
64
|
+
class BaseWorkflowRunOutputEvent(BaseRunOutputEvent):
|
|
57
65
|
"""Base class for all workflow run response events"""
|
|
58
66
|
|
|
59
67
|
created_at: int = field(default_factory=lambda: int(time()))
|
|
@@ -75,33 +83,26 @@ class BaseWorkflowRunOutputEvent:
|
|
|
75
83
|
|
|
76
84
|
# Handle StepOutput fields that contain Message objects
|
|
77
85
|
if hasattr(self, "step_results") and self.step_results is not None:
|
|
78
|
-
_dict["step_results"] = [step.to_dict() for step in self.step_results]
|
|
86
|
+
_dict["step_results"] = [step.to_dict() if hasattr(step, "to_dict") else step for step in self.step_results]
|
|
79
87
|
|
|
80
88
|
if hasattr(self, "step_response") and self.step_response is not None:
|
|
81
|
-
_dict["step_response"] =
|
|
89
|
+
_dict["step_response"] = (
|
|
90
|
+
self.step_response.to_dict() if hasattr(self.step_response, "to_dict") else self.step_response
|
|
91
|
+
)
|
|
82
92
|
|
|
83
93
|
if hasattr(self, "iteration_results") and self.iteration_results is not None:
|
|
84
|
-
_dict["iteration_results"] = [
|
|
94
|
+
_dict["iteration_results"] = [
|
|
95
|
+
step.to_dict() if hasattr(step, "to_dict") else step for step in self.iteration_results
|
|
96
|
+
]
|
|
85
97
|
|
|
86
98
|
if hasattr(self, "all_results") and self.all_results is not None:
|
|
87
|
-
_dict["all_results"] = [
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
99
|
+
_dict["all_results"] = [
|
|
100
|
+
[step.to_dict() if hasattr(step, "to_dict") else step for step in iteration]
|
|
101
|
+
for iteration in self.all_results
|
|
102
|
+
]
|
|
91
103
|
|
|
92
104
|
return _dict
|
|
93
105
|
|
|
94
|
-
def to_json(self) -> str:
|
|
95
|
-
import json
|
|
96
|
-
|
|
97
|
-
try:
|
|
98
|
-
_dict = self.to_dict()
|
|
99
|
-
except Exception:
|
|
100
|
-
log_error("Failed to convert response to json", exc_info=True)
|
|
101
|
-
raise
|
|
102
|
-
|
|
103
|
-
return json.dumps(_dict, indent=2)
|
|
104
|
-
|
|
105
106
|
@property
|
|
106
107
|
def is_cancelled(self):
|
|
107
108
|
return False
|
|
@@ -128,6 +129,21 @@ class WorkflowStartedEvent(BaseWorkflowRunOutputEvent):
|
|
|
128
129
|
event: str = WorkflowRunEvent.workflow_started.value
|
|
129
130
|
|
|
130
131
|
|
|
132
|
+
@dataclass
|
|
133
|
+
class WorkflowAgentStartedEvent(BaseWorkflowRunOutputEvent):
|
|
134
|
+
"""Event sent when workflow agent starts (before deciding to run workflow or answer directly)"""
|
|
135
|
+
|
|
136
|
+
event: str = WorkflowRunEvent.workflow_agent_started.value
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass
|
|
140
|
+
class WorkflowAgentCompletedEvent(BaseWorkflowRunOutputEvent):
|
|
141
|
+
"""Event sent when workflow agent completes (after running workflow or answering directly)"""
|
|
142
|
+
|
|
143
|
+
event: str = WorkflowRunEvent.workflow_agent_completed.value
|
|
144
|
+
content: Optional[Any] = None
|
|
145
|
+
|
|
146
|
+
|
|
131
147
|
@dataclass
|
|
132
148
|
class WorkflowCompletedEvent(BaseWorkflowRunOutputEvent):
|
|
133
149
|
"""Event sent when workflow execution completes"""
|
|
@@ -148,6 +164,11 @@ class WorkflowErrorEvent(BaseWorkflowRunOutputEvent):
|
|
|
148
164
|
event: str = WorkflowRunEvent.workflow_error.value
|
|
149
165
|
error: Optional[str] = None
|
|
150
166
|
|
|
167
|
+
# From exceptions
|
|
168
|
+
error_type: Optional[str] = None
|
|
169
|
+
error_id: Optional[str] = None
|
|
170
|
+
additional_data: Optional[Dict[str, Any]] = None
|
|
171
|
+
|
|
151
172
|
|
|
152
173
|
@dataclass
|
|
153
174
|
class WorkflowCancelledEvent(BaseWorkflowRunOutputEvent):
|
|
@@ -391,10 +412,17 @@ class CustomEvent(BaseWorkflowRunOutputEvent):
|
|
|
391
412
|
|
|
392
413
|
event: str = WorkflowRunEvent.custom_event.value
|
|
393
414
|
|
|
415
|
+
def __init__(self, **kwargs):
|
|
416
|
+
# Store arbitrary attributes directly on the instance
|
|
417
|
+
for key, value in kwargs.items():
|
|
418
|
+
setattr(self, key, value)
|
|
419
|
+
|
|
394
420
|
|
|
395
421
|
# Union type for all workflow run response events
|
|
396
422
|
WorkflowRunOutputEvent = Union[
|
|
397
423
|
WorkflowStartedEvent,
|
|
424
|
+
WorkflowAgentStartedEvent,
|
|
425
|
+
WorkflowAgentCompletedEvent,
|
|
398
426
|
WorkflowCompletedEvent,
|
|
399
427
|
WorkflowErrorEvent,
|
|
400
428
|
WorkflowCancelledEvent,
|
|
@@ -417,11 +445,52 @@ WorkflowRunOutputEvent = Union[
|
|
|
417
445
|
CustomEvent,
|
|
418
446
|
]
|
|
419
447
|
|
|
448
|
+
# Map event string to dataclass for workflow events
|
|
449
|
+
WORKFLOW_RUN_EVENT_TYPE_REGISTRY = {
|
|
450
|
+
WorkflowRunEvent.workflow_started.value: WorkflowStartedEvent,
|
|
451
|
+
WorkflowRunEvent.workflow_agent_started.value: WorkflowAgentStartedEvent,
|
|
452
|
+
WorkflowRunEvent.workflow_agent_completed.value: WorkflowAgentCompletedEvent,
|
|
453
|
+
WorkflowRunEvent.workflow_completed.value: WorkflowCompletedEvent,
|
|
454
|
+
WorkflowRunEvent.workflow_cancelled.value: WorkflowCancelledEvent,
|
|
455
|
+
WorkflowRunEvent.workflow_error.value: WorkflowErrorEvent,
|
|
456
|
+
WorkflowRunEvent.step_started.value: StepStartedEvent,
|
|
457
|
+
WorkflowRunEvent.step_completed.value: StepCompletedEvent,
|
|
458
|
+
WorkflowRunEvent.step_error.value: StepErrorEvent,
|
|
459
|
+
WorkflowRunEvent.loop_execution_started.value: LoopExecutionStartedEvent,
|
|
460
|
+
WorkflowRunEvent.loop_iteration_started.value: LoopIterationStartedEvent,
|
|
461
|
+
WorkflowRunEvent.loop_iteration_completed.value: LoopIterationCompletedEvent,
|
|
462
|
+
WorkflowRunEvent.loop_execution_completed.value: LoopExecutionCompletedEvent,
|
|
463
|
+
WorkflowRunEvent.parallel_execution_started.value: ParallelExecutionStartedEvent,
|
|
464
|
+
WorkflowRunEvent.parallel_execution_completed.value: ParallelExecutionCompletedEvent,
|
|
465
|
+
WorkflowRunEvent.condition_execution_started.value: ConditionExecutionStartedEvent,
|
|
466
|
+
WorkflowRunEvent.condition_execution_completed.value: ConditionExecutionCompletedEvent,
|
|
467
|
+
WorkflowRunEvent.router_execution_started.value: RouterExecutionStartedEvent,
|
|
468
|
+
WorkflowRunEvent.router_execution_completed.value: RouterExecutionCompletedEvent,
|
|
469
|
+
WorkflowRunEvent.steps_execution_started.value: StepsExecutionStartedEvent,
|
|
470
|
+
WorkflowRunEvent.steps_execution_completed.value: StepsExecutionCompletedEvent,
|
|
471
|
+
WorkflowRunEvent.step_output.value: StepOutputEvent,
|
|
472
|
+
WorkflowRunEvent.custom_event.value: CustomEvent,
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def workflow_run_output_event_from_dict(data: dict) -> BaseWorkflowRunOutputEvent:
|
|
477
|
+
event_type = data.get("event", "")
|
|
478
|
+
if event_type in {e.value for e in RunEvent}:
|
|
479
|
+
return run_output_event_from_dict(data) # type: ignore
|
|
480
|
+
elif event_type in {e.value for e in TeamRunEvent}:
|
|
481
|
+
return team_run_output_event_from_dict(data) # type: ignore
|
|
482
|
+
else:
|
|
483
|
+
event_class = WORKFLOW_RUN_EVENT_TYPE_REGISTRY.get(event_type)
|
|
484
|
+
if not event_class:
|
|
485
|
+
raise ValueError(f"Unknown workflow event type: {event_type}")
|
|
486
|
+
return event_class.from_dict(data) # type: ignore
|
|
487
|
+
|
|
420
488
|
|
|
421
489
|
@dataclass
|
|
422
490
|
class WorkflowRunOutput:
|
|
423
491
|
"""Response returned by Workflow.run() functions - kept for backwards compatibility"""
|
|
424
492
|
|
|
493
|
+
input: Optional[Union[str, Dict[str, Any], List[Any], BaseModel]] = None
|
|
425
494
|
content: Optional[Union[str, Dict[str, Any], List[Any], BaseModel, Any]] = None
|
|
426
495
|
content_type: str = "str"
|
|
427
496
|
|
|
@@ -444,6 +513,10 @@ class WorkflowRunOutput:
|
|
|
444
513
|
# Store agent/team responses separately with parent_run_id references
|
|
445
514
|
step_executor_runs: Optional[List[Union[RunOutput, TeamRunOutput]]] = None
|
|
446
515
|
|
|
516
|
+
# Workflow agent run - stores the full agent RunOutput when workflow agent is used
|
|
517
|
+
# The agent's parent_run_id will point to this workflow run's run_id to establish the relationship
|
|
518
|
+
workflow_agent_run: Optional[RunOutput] = None
|
|
519
|
+
|
|
447
520
|
# Store events from workflow execution
|
|
448
521
|
events: Optional[List[WorkflowRunOutputEvent]] = None
|
|
449
522
|
|
|
@@ -475,6 +548,7 @@ class WorkflowRunOutput:
|
|
|
475
548
|
"step_executor_runs",
|
|
476
549
|
"events",
|
|
477
550
|
"metrics",
|
|
551
|
+
"workflow_agent_run",
|
|
478
552
|
]
|
|
479
553
|
}
|
|
480
554
|
|
|
@@ -510,9 +584,18 @@ class WorkflowRunOutput:
|
|
|
510
584
|
if self.step_executor_runs:
|
|
511
585
|
_dict["step_executor_runs"] = [run.to_dict() for run in self.step_executor_runs]
|
|
512
586
|
|
|
587
|
+
if self.workflow_agent_run is not None:
|
|
588
|
+
_dict["workflow_agent_run"] = self.workflow_agent_run.to_dict()
|
|
589
|
+
|
|
513
590
|
if self.metrics is not None:
|
|
514
591
|
_dict["metrics"] = self.metrics.to_dict()
|
|
515
592
|
|
|
593
|
+
if self.input is not None:
|
|
594
|
+
if isinstance(self.input, BaseModel):
|
|
595
|
+
_dict["input"] = self.input.model_dump(exclude_none=True)
|
|
596
|
+
else:
|
|
597
|
+
_dict["input"] = self.input
|
|
598
|
+
|
|
516
599
|
if self.content and isinstance(self.content, BaseModel):
|
|
517
600
|
_dict["content"] = self.content.model_dump(exclude_none=True)
|
|
518
601
|
|
|
@@ -551,24 +634,51 @@ class WorkflowRunOutput:
|
|
|
551
634
|
else:
|
|
552
635
|
step_executor_runs.append(RunOutput.from_dict(run_data))
|
|
553
636
|
|
|
637
|
+
workflow_agent_run_data = data.pop("workflow_agent_run", None)
|
|
638
|
+
workflow_agent_run = None
|
|
639
|
+
if workflow_agent_run_data:
|
|
640
|
+
if isinstance(workflow_agent_run_data, dict):
|
|
641
|
+
workflow_agent_run = RunOutput.from_dict(workflow_agent_run_data)
|
|
642
|
+
elif isinstance(workflow_agent_run_data, RunOutput):
|
|
643
|
+
workflow_agent_run = workflow_agent_run_data
|
|
644
|
+
|
|
554
645
|
metadata = data.pop("metadata", None)
|
|
555
646
|
|
|
556
|
-
images = data.pop("images", [])
|
|
557
|
-
|
|
647
|
+
images = reconstruct_images(data.pop("images", []))
|
|
648
|
+
videos = reconstruct_videos(data.pop("videos", []))
|
|
649
|
+
audio = reconstruct_audio_list(data.pop("audio", []))
|
|
650
|
+
response_audio = reconstruct_response_audio(data.pop("response_audio", None))
|
|
651
|
+
|
|
652
|
+
events_data = data.pop("events", [])
|
|
653
|
+
final_events = []
|
|
654
|
+
for event in events_data or []:
|
|
655
|
+
if "agent_id" in event:
|
|
656
|
+
# Agent event from agent step
|
|
657
|
+
from agno.run.agent import run_output_event_from_dict
|
|
658
|
+
|
|
659
|
+
event = run_output_event_from_dict(event)
|
|
660
|
+
elif "team_id" in event:
|
|
661
|
+
# Team event from team step
|
|
662
|
+
from agno.run.team import team_run_output_event_from_dict
|
|
558
663
|
|
|
559
|
-
|
|
560
|
-
|
|
664
|
+
event = team_run_output_event_from_dict(event)
|
|
665
|
+
else:
|
|
666
|
+
# Pure workflow event
|
|
667
|
+
event = workflow_run_output_event_from_dict(event)
|
|
668
|
+
final_events.append(event)
|
|
669
|
+
events = final_events
|
|
561
670
|
|
|
562
|
-
|
|
563
|
-
audio = [Audio.model_validate(audio) for audio in audio] if audio else None
|
|
671
|
+
input_data = data.pop("input", None)
|
|
564
672
|
|
|
565
|
-
|
|
566
|
-
|
|
673
|
+
# Filter data to only include fields that are actually defined in the WorkflowRunOutput dataclass
|
|
674
|
+
from dataclasses import fields
|
|
567
675
|
|
|
568
|
-
|
|
676
|
+
supported_fields = {f.name for f in fields(cls)}
|
|
677
|
+
filtered_data = {k: v for k, v in data.items() if k in supported_fields}
|
|
569
678
|
|
|
570
679
|
return cls(
|
|
571
680
|
step_results=parsed_step_results,
|
|
681
|
+
workflow_agent_run=workflow_agent_run,
|
|
572
682
|
metadata=metadata,
|
|
573
683
|
images=images,
|
|
574
684
|
videos=videos,
|
|
@@ -577,7 +687,8 @@ class WorkflowRunOutput:
|
|
|
577
687
|
events=events,
|
|
578
688
|
metrics=workflow_metrics,
|
|
579
689
|
step_executor_runs=step_executor_runs,
|
|
580
|
-
|
|
690
|
+
input=input_data,
|
|
691
|
+
**filtered_data,
|
|
581
692
|
)
|
|
582
693
|
|
|
583
694
|
def get_content_as_string(self, **kwargs) -> str:
|
agno/session/agent.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import asdict, dataclass
|
|
4
|
-
from typing import Any, Dict, List, Mapping, Optional
|
|
4
|
+
from typing import Any, Dict, List, Mapping, Optional, Union
|
|
5
5
|
|
|
6
6
|
from agno.models.message import Message
|
|
7
7
|
from agno.run.agent import RunOutput
|
|
8
8
|
from agno.run.base import RunStatus
|
|
9
|
+
from agno.run.team import TeamRunOutput
|
|
9
10
|
from agno.session.summary import SessionSummary
|
|
10
11
|
from agno.utils.log import log_debug, log_warning
|
|
11
12
|
|
|
@@ -33,7 +34,7 @@ class AgentSession:
|
|
|
33
34
|
# Agent Data: agent_id, name and model
|
|
34
35
|
agent_data: Optional[Dict[str, Any]] = None
|
|
35
36
|
# List of all runs in the session
|
|
36
|
-
runs: Optional[List[RunOutput]] = None
|
|
37
|
+
runs: Optional[List[Union[RunOutput, TeamRunOutput]]] = None
|
|
37
38
|
# Summary of the session
|
|
38
39
|
summary: Optional["SessionSummary"] = None
|
|
39
40
|
|
|
@@ -57,8 +58,13 @@ class AgentSession:
|
|
|
57
58
|
return None
|
|
58
59
|
|
|
59
60
|
runs = data.get("runs")
|
|
61
|
+
serialized_runs: List[Union[RunOutput, TeamRunOutput]] = []
|
|
60
62
|
if runs is not None and isinstance(runs[0], dict):
|
|
61
|
-
|
|
63
|
+
for run in runs:
|
|
64
|
+
if "agent_id" in run:
|
|
65
|
+
serialized_runs.append(RunOutput.from_dict(run))
|
|
66
|
+
elif "team_id" in run:
|
|
67
|
+
serialized_runs.append(TeamRunOutput.from_dict(run))
|
|
62
68
|
|
|
63
69
|
summary = data.get("summary")
|
|
64
70
|
if summary is not None and isinstance(summary, dict):
|
|
@@ -77,7 +83,7 @@ class AgentSession:
|
|
|
77
83
|
metadata=metadata,
|
|
78
84
|
created_at=data.get("created_at"),
|
|
79
85
|
updated_at=data.get("updated_at"),
|
|
80
|
-
runs=
|
|
86
|
+
runs=serialized_runs,
|
|
81
87
|
summary=summary,
|
|
82
88
|
)
|
|
83
89
|
|
|
@@ -100,74 +106,136 @@ class AgentSession:
|
|
|
100
106
|
|
|
101
107
|
log_debug("Added RunOutput to Agent Session")
|
|
102
108
|
|
|
103
|
-
def get_run(self, run_id: str) -> Optional[RunOutput]:
|
|
109
|
+
def get_run(self, run_id: str) -> Optional[Union[RunOutput, TeamRunOutput]]:
|
|
104
110
|
for run in self.runs or []:
|
|
105
111
|
if run.run_id == run_id:
|
|
106
112
|
return run
|
|
107
113
|
return None
|
|
108
114
|
|
|
109
|
-
def
|
|
115
|
+
def get_messages(
|
|
110
116
|
self,
|
|
111
117
|
agent_id: Optional[str] = None,
|
|
112
118
|
team_id: Optional[str] = None,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
119
|
+
last_n_runs: Optional[int] = None,
|
|
120
|
+
limit: Optional[int] = None,
|
|
121
|
+
skip_roles: Optional[List[str]] = None,
|
|
122
|
+
skip_statuses: Optional[List[RunStatus]] = None,
|
|
116
123
|
skip_history_messages: bool = True,
|
|
117
124
|
) -> List[Message]:
|
|
118
|
-
"""Returns the messages
|
|
125
|
+
"""Returns the messages belonging to the session that fit the given criteria.
|
|
126
|
+
|
|
119
127
|
Args:
|
|
120
128
|
agent_id: The id of the agent to get the messages from.
|
|
121
129
|
team_id: The id of the team to get the messages from.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
130
|
+
last_n_runs: The number of runs to return messages from, counting from the latest. Defaults to all runs.
|
|
131
|
+
last_n_messages: The number of messages to return, counting from the latest. Defaults to all messages.
|
|
132
|
+
skip_roles: Skip messages with these roles.
|
|
133
|
+
skip_statuses: Skip messages with these statuses.
|
|
125
134
|
skip_history_messages: Skip messages that were tagged as history in previous runs.
|
|
135
|
+
|
|
126
136
|
Returns:
|
|
127
|
-
A list of Messages
|
|
137
|
+
A list of Messages belonging to the session.
|
|
128
138
|
"""
|
|
139
|
+
|
|
140
|
+
def _should_skip_message(
|
|
141
|
+
message: Message, skip_roles: Optional[List[str]] = None, skip_history_messages: bool = True
|
|
142
|
+
) -> bool:
|
|
143
|
+
"""Logic to determine if a message should be skipped"""
|
|
144
|
+
# Skip messages that were tagged as history in previous runs
|
|
145
|
+
if hasattr(message, "from_history") and message.from_history and skip_history_messages:
|
|
146
|
+
return True
|
|
147
|
+
|
|
148
|
+
# Skip messages with specified role
|
|
149
|
+
if skip_roles and message.role in skip_roles:
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
return False
|
|
153
|
+
|
|
129
154
|
if not self.runs:
|
|
130
155
|
return []
|
|
131
156
|
|
|
132
|
-
if
|
|
133
|
-
|
|
157
|
+
if skip_statuses is None:
|
|
158
|
+
skip_statuses = [RunStatus.paused, RunStatus.cancelled, RunStatus.error]
|
|
159
|
+
|
|
160
|
+
runs = self.runs
|
|
134
161
|
|
|
135
|
-
session_runs = self.runs
|
|
136
162
|
# Filter by agent_id and team_id
|
|
137
163
|
if agent_id:
|
|
138
|
-
|
|
164
|
+
runs = [run for run in runs if hasattr(run, "agent_id") and run.agent_id == agent_id] # type: ignore
|
|
139
165
|
if team_id:
|
|
140
|
-
|
|
166
|
+
runs = [run for run in runs if hasattr(run, "team_id") and run.team_id == team_id] # type: ignore
|
|
167
|
+
|
|
168
|
+
# Skip any messages that might be part of members of teams (for session re-use)
|
|
169
|
+
runs = [run for run in runs if run.parent_run_id is None] # type: ignore
|
|
141
170
|
|
|
142
171
|
# Filter by status
|
|
143
|
-
|
|
172
|
+
runs = [run for run in runs if hasattr(run, "status") and run.status not in skip_statuses] # type: ignore
|
|
144
173
|
|
|
145
|
-
# Filter by last_n
|
|
146
|
-
runs_to_process = session_runs[-last_n:] if last_n is not None else session_runs
|
|
147
174
|
messages_from_history = []
|
|
148
175
|
system_message = None
|
|
149
|
-
for run_response in runs_to_process:
|
|
150
|
-
if not (run_response and run_response.messages):
|
|
151
|
-
continue
|
|
152
176
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
177
|
+
# Limit the number of messages returned if limit is set
|
|
178
|
+
if limit is not None:
|
|
179
|
+
for run_response in runs:
|
|
180
|
+
if not run_response or not run_response.messages:
|
|
156
181
|
continue
|
|
157
|
-
|
|
158
|
-
|
|
182
|
+
|
|
183
|
+
for message in run_response.messages or []:
|
|
184
|
+
if _should_skip_message(message, skip_roles, skip_history_messages):
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
if message.role == "system":
|
|
188
|
+
# Only add the system message once
|
|
189
|
+
if system_message is None:
|
|
190
|
+
system_message = message
|
|
191
|
+
else:
|
|
192
|
+
messages_from_history.append(message)
|
|
193
|
+
|
|
194
|
+
if system_message:
|
|
195
|
+
messages_from_history = [system_message] + messages_from_history[
|
|
196
|
+
-(limit - 1) :
|
|
197
|
+
] # Grab one less message then add the system message
|
|
198
|
+
else:
|
|
199
|
+
messages_from_history = messages_from_history[-limit:]
|
|
200
|
+
|
|
201
|
+
# Remove tool result messages that don't have an associated assistant message with tool calls
|
|
202
|
+
while len(messages_from_history) > 0 and messages_from_history[0].role == "tool":
|
|
203
|
+
messages_from_history.pop(0)
|
|
204
|
+
|
|
205
|
+
# If limit is not set, return all messages
|
|
206
|
+
else:
|
|
207
|
+
runs_to_process = runs[-last_n_runs:] if last_n_runs is not None else runs
|
|
208
|
+
for run_response in runs_to_process:
|
|
209
|
+
if not run_response or not run_response.messages:
|
|
159
210
|
continue
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
211
|
+
|
|
212
|
+
for message in run_response.messages or []:
|
|
213
|
+
if _should_skip_message(message, skip_roles, skip_history_messages):
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
if message.role == "system":
|
|
217
|
+
# Only add the system message once
|
|
218
|
+
if system_message is None:
|
|
219
|
+
system_message = message
|
|
220
|
+
messages_from_history.append(system_message)
|
|
221
|
+
else:
|
|
222
|
+
messages_from_history.append(message)
|
|
167
223
|
|
|
168
224
|
log_debug(f"Getting messages from previous runs: {len(messages_from_history)}")
|
|
169
225
|
return messages_from_history
|
|
170
226
|
|
|
227
|
+
def get_chat_history(self, last_n_runs: Optional[int] = None) -> List[Message]:
|
|
228
|
+
"""Return the chat history (user and assistant messages) for the session.
|
|
229
|
+
Use get_messages() for more filtering options.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
last_n_runs: Number of recent runs to include. If None, all runs will be considered.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
A list of user and assistant Messages belonging to the session.
|
|
236
|
+
"""
|
|
237
|
+
return self.get_messages(skip_roles=["system", "tool"], last_n_runs=last_n_runs)
|
|
238
|
+
|
|
171
239
|
def get_tool_calls(self, num_calls: Optional[int] = None) -> List[Dict[str, Any]]:
|
|
172
240
|
"""Returns a list of tool calls from the messages"""
|
|
173
241
|
|
|
@@ -184,61 +252,9 @@ class AgentSession:
|
|
|
184
252
|
return tool_calls
|
|
185
253
|
return tool_calls
|
|
186
254
|
|
|
187
|
-
def get_messages_for_session(
|
|
188
|
-
self,
|
|
189
|
-
user_role: str = "user",
|
|
190
|
-
assistant_role: Optional[List[str]] = None,
|
|
191
|
-
skip_history_messages: bool = True,
|
|
192
|
-
) -> List[Message]:
|
|
193
|
-
"""Returns a list of messages for the session that iterate through user message and assistant response."""
|
|
194
|
-
|
|
195
|
-
if assistant_role is None:
|
|
196
|
-
# TODO: Check if we still need CHATBOT as a role
|
|
197
|
-
assistant_role = ["assistant", "model", "CHATBOT"]
|
|
198
|
-
|
|
199
|
-
final_messages: List[Message] = []
|
|
200
|
-
session_runs = self.runs
|
|
201
|
-
if not session_runs:
|
|
202
|
-
return []
|
|
203
|
-
|
|
204
|
-
for run_response in session_runs:
|
|
205
|
-
if run_response and run_response.messages:
|
|
206
|
-
user_message_from_run = None
|
|
207
|
-
assistant_message_from_run = None
|
|
208
|
-
|
|
209
|
-
# Start from the beginning to look for the user message
|
|
210
|
-
for message in run_response.messages or []:
|
|
211
|
-
if hasattr(message, "from_history") and message.from_history and skip_history_messages:
|
|
212
|
-
continue
|
|
213
|
-
if message.role == user_role:
|
|
214
|
-
user_message_from_run = message
|
|
215
|
-
break
|
|
216
|
-
|
|
217
|
-
# Start from the end to look for the assistant response
|
|
218
|
-
for message in run_response.messages[::-1]:
|
|
219
|
-
if hasattr(message, "from_history") and message.from_history and skip_history_messages:
|
|
220
|
-
continue
|
|
221
|
-
if message.role in assistant_role:
|
|
222
|
-
assistant_message_from_run = message
|
|
223
|
-
break
|
|
224
|
-
|
|
225
|
-
if user_message_from_run and assistant_message_from_run:
|
|
226
|
-
final_messages.append(user_message_from_run)
|
|
227
|
-
final_messages.append(assistant_message_from_run)
|
|
228
|
-
return final_messages
|
|
229
|
-
|
|
230
255
|
def get_session_summary(self) -> Optional[SessionSummary]:
|
|
231
256
|
"""Get the session summary for the session"""
|
|
232
257
|
|
|
233
258
|
if self.summary is None:
|
|
234
259
|
return None
|
|
235
260
|
return self.summary
|
|
236
|
-
|
|
237
|
-
# Chat History functions
|
|
238
|
-
def get_chat_history(self) -> List[Message]:
|
|
239
|
-
"""Get the chat history for the session"""
|
|
240
|
-
|
|
241
|
-
messages = []
|
|
242
|
-
for run in self.runs or []:
|
|
243
|
-
messages.extend([msg for msg in run.messages or [] if not msg.from_history])
|
|
244
|
-
return messages
|