agno 2.0.0rc2__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.
Files changed (331) hide show
  1. agno/agent/agent.py +6009 -2874
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +595 -187
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +3 -0
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +339 -266
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +1011 -566
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +110 -37
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +143 -4
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +60 -6
  142. agno/models/openai/chat.py +102 -43
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +81 -5
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -175
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +266 -112
  205. agno/run/base.py +53 -24
  206. agno/run/team.py +252 -111
  207. agno/run/workflow.py +156 -45
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1692
  213. agno/tools/brightdata.py +3 -3
  214. agno/tools/cartesia.py +3 -5
  215. agno/tools/dalle.py +9 -8
  216. agno/tools/decorator.py +4 -2
  217. agno/tools/desi_vocal.py +2 -2
  218. agno/tools/duckduckgo.py +15 -11
  219. agno/tools/e2b.py +20 -13
  220. agno/tools/eleven_labs.py +26 -28
  221. agno/tools/exa.py +21 -16
  222. agno/tools/fal.py +4 -4
  223. agno/tools/file.py +153 -23
  224. agno/tools/file_generation.py +350 -0
  225. agno/tools/firecrawl.py +4 -4
  226. agno/tools/function.py +257 -37
  227. agno/tools/giphy.py +2 -2
  228. agno/tools/gmail.py +238 -14
  229. agno/tools/google_drive.py +270 -0
  230. agno/tools/googlecalendar.py +36 -8
  231. agno/tools/googlesheets.py +20 -5
  232. agno/tools/jira.py +20 -0
  233. agno/tools/knowledge.py +3 -3
  234. agno/tools/lumalab.py +3 -3
  235. agno/tools/mcp/__init__.py +10 -0
  236. agno/tools/mcp/mcp.py +331 -0
  237. agno/tools/mcp/multi_mcp.py +347 -0
  238. agno/tools/mcp/params.py +24 -0
  239. agno/tools/mcp_toolbox.py +284 -0
  240. agno/tools/mem0.py +11 -17
  241. agno/tools/memori.py +1 -53
  242. agno/tools/memory.py +419 -0
  243. agno/tools/models/azure_openai.py +2 -2
  244. agno/tools/models/gemini.py +3 -3
  245. agno/tools/models/groq.py +3 -5
  246. agno/tools/models/nebius.py +7 -7
  247. agno/tools/models_labs.py +25 -15
  248. agno/tools/notion.py +204 -0
  249. agno/tools/openai.py +4 -9
  250. agno/tools/opencv.py +3 -3
  251. agno/tools/parallel.py +314 -0
  252. agno/tools/replicate.py +7 -7
  253. agno/tools/scrapegraph.py +58 -31
  254. agno/tools/searxng.py +2 -2
  255. agno/tools/serper.py +2 -2
  256. agno/tools/slack.py +18 -3
  257. agno/tools/spider.py +2 -2
  258. agno/tools/tavily.py +146 -0
  259. agno/tools/whatsapp.py +1 -1
  260. agno/tools/workflow.py +278 -0
  261. agno/tools/yfinance.py +12 -11
  262. agno/utils/agent.py +820 -0
  263. agno/utils/audio.py +27 -0
  264. agno/utils/common.py +90 -1
  265. agno/utils/events.py +222 -7
  266. agno/utils/gemini.py +181 -23
  267. agno/utils/hooks.py +57 -0
  268. agno/utils/http.py +111 -0
  269. agno/utils/knowledge.py +12 -5
  270. agno/utils/log.py +1 -0
  271. agno/utils/mcp.py +95 -5
  272. agno/utils/media.py +188 -10
  273. agno/utils/merge_dict.py +22 -1
  274. agno/utils/message.py +60 -0
  275. agno/utils/models/claude.py +40 -11
  276. agno/utils/models/cohere.py +1 -1
  277. agno/utils/models/watsonx.py +1 -1
  278. agno/utils/openai.py +1 -1
  279. agno/utils/print_response/agent.py +105 -21
  280. agno/utils/print_response/team.py +103 -38
  281. agno/utils/print_response/workflow.py +251 -34
  282. agno/utils/reasoning.py +22 -1
  283. agno/utils/serialize.py +32 -0
  284. agno/utils/streamlit.py +16 -10
  285. agno/utils/string.py +41 -0
  286. agno/utils/team.py +98 -9
  287. agno/utils/tools.py +1 -1
  288. agno/vectordb/base.py +23 -4
  289. agno/vectordb/cassandra/cassandra.py +65 -9
  290. agno/vectordb/chroma/chromadb.py +182 -38
  291. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  292. agno/vectordb/couchbase/couchbase.py +105 -10
  293. agno/vectordb/lancedb/lance_db.py +183 -135
  294. agno/vectordb/langchaindb/langchaindb.py +25 -7
  295. agno/vectordb/lightrag/lightrag.py +17 -3
  296. agno/vectordb/llamaindex/__init__.py +3 -0
  297. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  298. agno/vectordb/milvus/milvus.py +126 -9
  299. agno/vectordb/mongodb/__init__.py +7 -1
  300. agno/vectordb/mongodb/mongodb.py +112 -7
  301. agno/vectordb/pgvector/pgvector.py +142 -21
  302. agno/vectordb/pineconedb/pineconedb.py +80 -8
  303. agno/vectordb/qdrant/qdrant.py +125 -39
  304. agno/vectordb/redis/__init__.py +9 -0
  305. agno/vectordb/redis/redisdb.py +694 -0
  306. agno/vectordb/singlestore/singlestore.py +111 -25
  307. agno/vectordb/surrealdb/surrealdb.py +31 -5
  308. agno/vectordb/upstashdb/upstashdb.py +76 -8
  309. agno/vectordb/weaviate/weaviate.py +86 -15
  310. agno/workflow/__init__.py +2 -0
  311. agno/workflow/agent.py +299 -0
  312. agno/workflow/condition.py +112 -18
  313. agno/workflow/loop.py +69 -10
  314. agno/workflow/parallel.py +266 -118
  315. agno/workflow/router.py +110 -17
  316. agno/workflow/step.py +645 -136
  317. agno/workflow/steps.py +65 -6
  318. agno/workflow/types.py +71 -33
  319. agno/workflow/workflow.py +2113 -300
  320. agno-2.3.0.dist-info/METADATA +618 -0
  321. agno-2.3.0.dist-info/RECORD +577 -0
  322. agno-2.3.0.dist-info/licenses/LICENSE +201 -0
  323. agno/knowledge/reader/url_reader.py +0 -128
  324. agno/tools/googlesearch.py +0 -98
  325. agno/tools/mcp.py +0 -610
  326. agno/utils/models/aws_claude.py +0 -170
  327. agno-2.0.0rc2.dist-info/METADATA +0 -355
  328. agno-2.0.0rc2.dist-info/RECORD +0 -515
  329. agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
  330. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  331. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/run/workflow.py CHANGED
@@ -5,11 +5,16 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
5
5
 
6
6
  from pydantic import BaseModel
7
7
 
8
- from agno.media import AudioArtifact, AudioResponse, ImageArtifact, VideoArtifact
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.log import log_error
8
+ from agno.media import Audio, Image, Video
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"] = self.step_response.to_dict()
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"] = [step.to_dict() for step in self.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"] = [[step.to_dict() for step in iteration] for iteration in self.all_results]
88
-
89
- if hasattr(self, "step_results") and self.step_results is not None:
90
- _dict["step_results"] = [step.to_dict() for step in self.step_results]
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):
@@ -182,10 +203,10 @@ class StepCompletedEvent(BaseWorkflowRunOutputEvent):
182
203
  content_type: str = "str"
183
204
 
184
205
  # Media content fields
185
- images: Optional[List[ImageArtifact]] = None
186
- videos: Optional[List[VideoArtifact]] = None
187
- audio: Optional[List[AudioArtifact]] = None
188
- response_audio: Optional[AudioResponse] = None
206
+ images: Optional[List[Image]] = None
207
+ videos: Optional[List[Video]] = None
208
+ audio: Optional[List[Audio]] = None
209
+ response_audio: Optional[Audio] = None
189
210
 
190
211
  # Store actual step execution results as StepOutput objects
191
212
  step_response: Optional[StepOutput] = None
@@ -361,15 +382,15 @@ class StepOutputEvent(BaseWorkflowRunOutputEvent):
361
382
  return self.step_output.content if self.step_output else None
362
383
 
363
384
  @property
364
- def images(self) -> Optional[List[ImageArtifact]]:
385
+ def images(self) -> Optional[List[Image]]:
365
386
  return self.step_output.images if self.step_output else None
366
387
 
367
388
  @property
368
- def videos(self) -> Optional[List[VideoArtifact]]:
389
+ def videos(self) -> Optional[List[Video]]:
369
390
  return self.step_output.videos if self.step_output else None
370
391
 
371
392
  @property
372
- def audio(self) -> Optional[List[AudioArtifact]]:
393
+ def audio(self) -> Optional[List[Audio]]:
373
394
  return self.step_output.audio if self.step_output else None
374
395
 
375
396
  @property
@@ -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
 
@@ -433,10 +502,10 @@ class WorkflowRunOutput:
433
502
  session_id: Optional[str] = None
434
503
 
435
504
  # Media content fields
436
- images: Optional[List[ImageArtifact]] = None
437
- videos: Optional[List[VideoArtifact]] = None
438
- audio: Optional[List[AudioArtifact]] = None
439
- response_audio: Optional[AudioResponse] = None
505
+ images: Optional[List[Image]] = None
506
+ videos: Optional[List[Video]] = None
507
+ audio: Optional[List[Audio]] = None
508
+ response_audio: Optional[Audio] = None
440
509
 
441
510
  # Store actual step execution results as StepOutput objects
442
511
  step_results: List[Union[StepOutput, List[StepOutput]]] = field(default_factory=list)
@@ -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
- images = [ImageArtifact.model_validate(image) for image in images] if images else None
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
- videos = data.pop("videos", [])
560
- videos = [VideoArtifact.model_validate(video) for video in videos] if videos else None
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
- audio = data.pop("audio", [])
563
- audio = [AudioArtifact.model_validate(audio) for audio in audio] if audio else None
671
+ input_data = data.pop("input", None)
564
672
 
565
- response_audio = data.pop("response_audio", None)
566
- response_audio = AudioResponse.model_validate(response_audio) if response_audio else None
673
+ # Filter data to only include fields that are actually defined in the WorkflowRunOutput dataclass
674
+ from dataclasses import fields
567
675
 
568
- events = data.pop("events", [])
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
- **data,
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
- runs = [RunOutput.from_dict(run) for run in runs]
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=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 get_messages_from_last_n_runs(
115
+ def get_messages(
110
116
  self,
111
117
  agent_id: Optional[str] = None,
112
118
  team_id: Optional[str] = None,
113
- last_n: Optional[int] = None,
114
- skip_role: Optional[str] = None,
115
- skip_status: Optional[List[RunStatus]] = None,
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 from the last_n runs, excluding previously tagged history 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
- last_n: The number of runs to return from the end of the conversation. Defaults to all runs.
123
- skip_role: Skip messages with this role.
124
- skip_status: Skip messages with this status.
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 from the specified runs, excluding history 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 skip_status is None:
133
- skip_status = [RunStatus.paused, RunStatus.cancelled, RunStatus.error]
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
- session_runs = [run for run in session_runs if hasattr(run, "agent_id") and run.agent_id == agent_id] # type: ignore
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
- session_runs = [run for run in session_runs if hasattr(run, "team_id") and run.team_id == team_id] # type: ignore
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
- session_runs = [run for run in session_runs if hasattr(run, "status") and run.status not in skip_status] # type: ignore
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
- for message in run_response.messages or []:
154
- # Skip messages with specified role
155
- if skip_role and message.role == skip_role:
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
- # Skip messages that were tagged as history in previous runs
158
- if hasattr(message, "from_history") and message.from_history and skip_history_messages:
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
- if message.role == "system":
161
- # Only add the system message once
162
- if system_message is None:
163
- system_message = message
164
- messages_from_history.append(system_message)
165
- else:
166
- messages_from_history.append(message)
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