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.
Files changed (314) hide show
  1. agno/agent/agent.py +6015 -2823
  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 +594 -186
  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 +2 -8
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +72 -0
  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 +999 -519
  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 +103 -31
  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 +139 -0
  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 +59 -5
  142. agno/models/openai/chat.py +69 -29
  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 +77 -1
  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 -178
  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 +248 -94
  205. agno/run/base.py +44 -5
  206. agno/run/team.py +238 -97
  207. agno/run/workflow.py +144 -33
  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 -1610
  213. agno/tools/dalle.py +2 -4
  214. agno/tools/decorator.py +4 -2
  215. agno/tools/duckduckgo.py +15 -11
  216. agno/tools/e2b.py +14 -7
  217. agno/tools/eleven_labs.py +23 -25
  218. agno/tools/exa.py +21 -16
  219. agno/tools/file.py +153 -23
  220. agno/tools/file_generation.py +350 -0
  221. agno/tools/firecrawl.py +4 -4
  222. agno/tools/function.py +250 -30
  223. agno/tools/gmail.py +238 -14
  224. agno/tools/google_drive.py +270 -0
  225. agno/tools/googlecalendar.py +36 -8
  226. agno/tools/googlesheets.py +20 -5
  227. agno/tools/jira.py +20 -0
  228. agno/tools/knowledge.py +3 -3
  229. agno/tools/mcp/__init__.py +10 -0
  230. agno/tools/mcp/mcp.py +331 -0
  231. agno/tools/mcp/multi_mcp.py +347 -0
  232. agno/tools/mcp/params.py +24 -0
  233. agno/tools/mcp_toolbox.py +284 -0
  234. agno/tools/mem0.py +11 -17
  235. agno/tools/memori.py +1 -53
  236. agno/tools/memory.py +419 -0
  237. agno/tools/models/nebius.py +5 -5
  238. agno/tools/models_labs.py +20 -10
  239. agno/tools/notion.py +204 -0
  240. agno/tools/parallel.py +314 -0
  241. agno/tools/scrapegraph.py +58 -31
  242. agno/tools/searxng.py +2 -2
  243. agno/tools/serper.py +2 -2
  244. agno/tools/slack.py +18 -3
  245. agno/tools/spider.py +2 -2
  246. agno/tools/tavily.py +146 -0
  247. agno/tools/whatsapp.py +1 -1
  248. agno/tools/workflow.py +278 -0
  249. agno/tools/yfinance.py +12 -11
  250. agno/utils/agent.py +820 -0
  251. agno/utils/audio.py +27 -0
  252. agno/utils/common.py +90 -1
  253. agno/utils/events.py +217 -2
  254. agno/utils/gemini.py +180 -22
  255. agno/utils/hooks.py +57 -0
  256. agno/utils/http.py +111 -0
  257. agno/utils/knowledge.py +12 -5
  258. agno/utils/log.py +1 -0
  259. agno/utils/mcp.py +92 -2
  260. agno/utils/media.py +188 -10
  261. agno/utils/merge_dict.py +22 -1
  262. agno/utils/message.py +60 -0
  263. agno/utils/models/claude.py +40 -11
  264. agno/utils/print_response/agent.py +105 -21
  265. agno/utils/print_response/team.py +103 -38
  266. agno/utils/print_response/workflow.py +251 -34
  267. agno/utils/reasoning.py +22 -1
  268. agno/utils/serialize.py +32 -0
  269. agno/utils/streamlit.py +16 -10
  270. agno/utils/string.py +41 -0
  271. agno/utils/team.py +98 -9
  272. agno/utils/tools.py +1 -1
  273. agno/vectordb/base.py +23 -4
  274. agno/vectordb/cassandra/cassandra.py +65 -9
  275. agno/vectordb/chroma/chromadb.py +182 -38
  276. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  277. agno/vectordb/couchbase/couchbase.py +105 -10
  278. agno/vectordb/lancedb/lance_db.py +124 -133
  279. agno/vectordb/langchaindb/langchaindb.py +25 -7
  280. agno/vectordb/lightrag/lightrag.py +17 -3
  281. agno/vectordb/llamaindex/__init__.py +3 -0
  282. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  283. agno/vectordb/milvus/milvus.py +126 -9
  284. agno/vectordb/mongodb/__init__.py +7 -1
  285. agno/vectordb/mongodb/mongodb.py +112 -7
  286. agno/vectordb/pgvector/pgvector.py +142 -21
  287. agno/vectordb/pineconedb/pineconedb.py +80 -8
  288. agno/vectordb/qdrant/qdrant.py +125 -39
  289. agno/vectordb/redis/__init__.py +9 -0
  290. agno/vectordb/redis/redisdb.py +694 -0
  291. agno/vectordb/singlestore/singlestore.py +111 -25
  292. agno/vectordb/surrealdb/surrealdb.py +31 -5
  293. agno/vectordb/upstashdb/upstashdb.py +76 -8
  294. agno/vectordb/weaviate/weaviate.py +86 -15
  295. agno/workflow/__init__.py +2 -0
  296. agno/workflow/agent.py +299 -0
  297. agno/workflow/condition.py +112 -18
  298. agno/workflow/loop.py +69 -10
  299. agno/workflow/parallel.py +266 -118
  300. agno/workflow/router.py +110 -17
  301. agno/workflow/step.py +638 -129
  302. agno/workflow/steps.py +65 -6
  303. agno/workflow/types.py +61 -23
  304. agno/workflow/workflow.py +2085 -272
  305. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
  306. agno-2.3.0.dist-info/RECORD +577 -0
  307. agno/knowledge/reader/url_reader.py +0 -128
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -610
  310. agno/utils/models/aws_claude.py +0 -170
  311. agno-2.0.1.dist-info/RECORD +0 -515
  312. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  313. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/run/agent.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from dataclasses import asdict, dataclass, field
2
2
  from enum import Enum
3
3
  from time import time
4
- from typing import Any, Dict, List, Optional, Sequence, Union
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Union
5
5
 
6
6
  from pydantic import BaseModel
7
7
 
@@ -12,6 +12,122 @@ from agno.models.response import ToolExecution
12
12
  from agno.reasoning.step import ReasoningStep
13
13
  from agno.run.base import BaseRunOutputEvent, MessageReferences, RunStatus
14
14
  from agno.utils.log import logger
15
+ from agno.utils.media import (
16
+ reconstruct_audio_list,
17
+ reconstruct_files,
18
+ reconstruct_images,
19
+ reconstruct_response_audio,
20
+ reconstruct_videos,
21
+ )
22
+
23
+ if TYPE_CHECKING:
24
+ from agno.session.summary import SessionSummary
25
+
26
+
27
+ @dataclass
28
+ class RunInput:
29
+ """Container for the raw input data passed to Agent.run().
30
+
31
+ This captures the original input exactly as provided by the user,
32
+ separate from the processed messages that go to the model.
33
+
34
+ Attributes:
35
+ input_content: The literal input message/content passed to run()
36
+ images: Images directly passed to run()
37
+ videos: Videos directly passed to run()
38
+ audios: Audio files directly passed to run()
39
+ files: Files directly passed to run()
40
+ """
41
+
42
+ input_content: Union[str, List, Dict, Message, BaseModel, List[Message]]
43
+ images: Optional[Sequence[Image]] = None
44
+ videos: Optional[Sequence[Video]] = None
45
+ audios: Optional[Sequence[Audio]] = None
46
+ files: Optional[Sequence[File]] = None
47
+
48
+ def input_content_string(self) -> str:
49
+ import json
50
+
51
+ if isinstance(self.input_content, (str)):
52
+ return self.input_content
53
+ elif isinstance(self.input_content, BaseModel):
54
+ return self.input_content.model_dump_json(exclude_none=True)
55
+ elif isinstance(self.input_content, Message):
56
+ return json.dumps(self.input_content.to_dict())
57
+ elif isinstance(self.input_content, list) and self.input_content and isinstance(self.input_content[0], Message):
58
+ return json.dumps([m.to_dict() for m in self.input_content])
59
+ else:
60
+ return str(self.input_content)
61
+
62
+ def to_dict(self) -> Dict[str, Any]:
63
+ """Convert to dictionary representation"""
64
+ result: Dict[str, Any] = {}
65
+
66
+ if self.input_content is not None:
67
+ if isinstance(self.input_content, (str)):
68
+ result["input_content"] = self.input_content
69
+ elif isinstance(self.input_content, BaseModel):
70
+ result["input_content"] = self.input_content.model_dump(exclude_none=True)
71
+ elif isinstance(self.input_content, Message):
72
+ result["input_content"] = self.input_content.to_dict()
73
+
74
+ # Handle input_content provided as a list of Message objects
75
+ elif (
76
+ isinstance(self.input_content, list)
77
+ and self.input_content
78
+ and isinstance(self.input_content[0], Message)
79
+ ):
80
+ result["input_content"] = [m.to_dict() for m in self.input_content]
81
+
82
+ # Handle input_content provided as a list of dicts
83
+ elif (
84
+ isinstance(self.input_content, list) and self.input_content and isinstance(self.input_content[0], dict)
85
+ ):
86
+ for content in self.input_content:
87
+ # Handle media input
88
+ if isinstance(content, dict):
89
+ if content.get("images"):
90
+ content["images"] = [
91
+ img.to_dict() if isinstance(img, Image) else img for img in content["images"]
92
+ ]
93
+ if content.get("videos"):
94
+ content["videos"] = [
95
+ vid.to_dict() if isinstance(vid, Video) else vid for vid in content["videos"]
96
+ ]
97
+ if content.get("audios"):
98
+ content["audios"] = [
99
+ aud.to_dict() if isinstance(aud, Audio) else aud for aud in content["audios"]
100
+ ]
101
+ if content.get("files"):
102
+ content["files"] = [
103
+ file.to_dict() if isinstance(file, File) else file for file in content["files"]
104
+ ]
105
+ result["input_content"] = self.input_content
106
+ else:
107
+ result["input_content"] = self.input_content
108
+
109
+ if self.images:
110
+ result["images"] = [img.to_dict() for img in self.images]
111
+ if self.videos:
112
+ result["videos"] = [vid.to_dict() for vid in self.videos]
113
+ if self.audios:
114
+ result["audios"] = [aud.to_dict() for aud in self.audios]
115
+ if self.files:
116
+ result["files"] = [file.to_dict() for file in self.files]
117
+
118
+ return result
119
+
120
+ @classmethod
121
+ def from_dict(cls, data: Dict[str, Any]) -> "RunInput":
122
+ """Create RunInput from dictionary"""
123
+ images = reconstruct_images(data.get("images"))
124
+ videos = reconstruct_videos(data.get("videos"))
125
+ audios = reconstruct_audio_list(data.get("audios"))
126
+ files = reconstruct_files(data.get("files"))
127
+
128
+ return cls(
129
+ input_content=data.get("input_content", ""), images=images, videos=videos, audios=audios, files=files
130
+ )
15
131
 
16
132
 
17
133
  class RunEvent(str, Enum):
@@ -19,6 +135,7 @@ class RunEvent(str, Enum):
19
135
 
20
136
  run_started = "RunStarted"
21
137
  run_content = "RunContent"
138
+ run_content_completed = "RunContentCompleted"
22
139
  run_intermediate_content = "RunIntermediateContent"
23
140
  run_completed = "RunCompleted"
24
141
  run_error = "RunError"
@@ -27,6 +144,12 @@ class RunEvent(str, Enum):
27
144
  run_paused = "RunPaused"
28
145
  run_continued = "RunContinued"
29
146
 
147
+ pre_hook_started = "PreHookStarted"
148
+ pre_hook_completed = "PreHookCompleted"
149
+
150
+ post_hook_started = "PostHookStarted"
151
+ post_hook_completed = "PostHookCompleted"
152
+
30
153
  tool_call_started = "ToolCallStarted"
31
154
  tool_call_completed = "ToolCallCompleted"
32
155
 
@@ -37,6 +160,9 @@ class RunEvent(str, Enum):
37
160
  memory_update_started = "MemoryUpdateStarted"
38
161
  memory_update_completed = "MemoryUpdateCompleted"
39
162
 
163
+ session_summary_started = "SessionSummaryStarted"
164
+ session_summary_completed = "SessionSummaryCompleted"
165
+
40
166
  parser_model_response_started = "ParserModelResponseStarted"
41
167
  parser_model_response_completed = "ParserModelResponseCompleted"
42
168
 
@@ -53,6 +179,7 @@ class BaseAgentRunEvent(BaseRunOutputEvent):
53
179
  agent_id: str = ""
54
180
  agent_name: str = ""
55
181
  run_id: Optional[str] = None
182
+ parent_run_id: Optional[str] = None
56
183
  session_id: Optional[str] = None
57
184
 
58
185
  # Step context for workflow execution
@@ -94,8 +221,12 @@ class RunContentEvent(BaseAgentRunEvent):
94
221
 
95
222
  event: str = RunEvent.run_content.value
96
223
  content: Optional[Any] = None
224
+ workflow_agent: bool = (
225
+ False # Used by consumers of the events to distinguish between workflow agent and regular agent
226
+ )
97
227
  content_type: str = "str"
98
228
  reasoning_content: Optional[str] = None
229
+ model_provider_data: Optional[Dict[str, Any]] = None
99
230
  citations: Optional[Citations] = None
100
231
  response_audio: Optional[Audio] = None # Model audio response
101
232
  image: Optional[Image] = None # Image attached to the response
@@ -105,6 +236,11 @@ class RunContentEvent(BaseAgentRunEvent):
105
236
  reasoning_messages: Optional[List[Message]] = None
106
237
 
107
238
 
239
+ @dataclass
240
+ class RunContentCompletedEvent(BaseAgentRunEvent):
241
+ event: str = RunEvent.run_content_completed.value
242
+
243
+
108
244
  @dataclass
109
245
  class IntermediateRunContentEvent(BaseAgentRunEvent):
110
246
  event: str = RunEvent.run_intermediate_content.value
@@ -119,6 +255,7 @@ class RunCompletedEvent(BaseAgentRunEvent):
119
255
  content_type: str = "str"
120
256
  reasoning_content: Optional[str] = None
121
257
  citations: Optional[Citations] = None
258
+ model_provider_data: Optional[Dict[str, Any]] = None
122
259
  images: Optional[List[Image]] = None # Images attached to the response
123
260
  videos: Optional[List[Video]] = None # Videos attached to the response
124
261
  audio: Optional[List[Audio]] = None # Audio attached to the response
@@ -129,6 +266,7 @@ class RunCompletedEvent(BaseAgentRunEvent):
129
266
  reasoning_messages: Optional[List[Message]] = None
130
267
  metadata: Optional[Dict[str, Any]] = None
131
268
  metrics: Optional[Metrics] = None
269
+ session_state: Optional[Dict[str, Any]] = None
132
270
 
133
271
 
134
272
  @dataclass
@@ -151,6 +289,11 @@ class RunErrorEvent(BaseAgentRunEvent):
151
289
  event: str = RunEvent.run_error.value
152
290
  content: Optional[str] = None
153
291
 
292
+ # From exceptions
293
+ error_type: Optional[str] = None
294
+ error_id: Optional[str] = None
295
+ additional_data: Optional[Dict[str, Any]] = None
296
+
154
297
 
155
298
  @dataclass
156
299
  class RunCancelledEvent(BaseAgentRunEvent):
@@ -162,6 +305,32 @@ class RunCancelledEvent(BaseAgentRunEvent):
162
305
  return True
163
306
 
164
307
 
308
+ @dataclass
309
+ class PreHookStartedEvent(BaseAgentRunEvent):
310
+ event: str = RunEvent.pre_hook_started.value
311
+ pre_hook_name: Optional[str] = None
312
+ run_input: Optional[RunInput] = None
313
+
314
+
315
+ @dataclass
316
+ class PreHookCompletedEvent(BaseAgentRunEvent):
317
+ event: str = RunEvent.pre_hook_completed.value
318
+ pre_hook_name: Optional[str] = None
319
+ run_input: Optional[RunInput] = None
320
+
321
+
322
+ @dataclass
323
+ class PostHookStartedEvent(BaseAgentRunEvent):
324
+ event: str = RunEvent.post_hook_started.value
325
+ post_hook_name: Optional[str] = None
326
+
327
+
328
+ @dataclass
329
+ class PostHookCompletedEvent(BaseAgentRunEvent):
330
+ event: str = RunEvent.post_hook_completed.value
331
+ post_hook_name: Optional[str] = None
332
+
333
+
165
334
  @dataclass
166
335
  class MemoryUpdateStartedEvent(BaseAgentRunEvent):
167
336
  event: str = RunEvent.memory_update_started.value
@@ -172,6 +341,17 @@ class MemoryUpdateCompletedEvent(BaseAgentRunEvent):
172
341
  event: str = RunEvent.memory_update_completed.value
173
342
 
174
343
 
344
+ @dataclass
345
+ class SessionSummaryStartedEvent(BaseAgentRunEvent):
346
+ event: str = RunEvent.session_summary_started.value
347
+
348
+
349
+ @dataclass
350
+ class SessionSummaryCompletedEvent(BaseAgentRunEvent):
351
+ event: str = RunEvent.session_summary_completed.value
352
+ session_summary: Optional["SessionSummary"] = None
353
+
354
+
175
355
  @dataclass
176
356
  class ReasoningStartedEvent(BaseAgentRunEvent):
177
357
  event: str = RunEvent.reasoning_started.value
@@ -232,21 +412,33 @@ class OutputModelResponseCompletedEvent(BaseAgentRunEvent):
232
412
  class CustomEvent(BaseAgentRunEvent):
233
413
  event: str = RunEvent.custom_event.value
234
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
+
235
420
 
236
421
  RunOutputEvent = Union[
237
422
  RunStartedEvent,
238
423
  RunContentEvent,
239
424
  IntermediateRunContentEvent,
425
+ RunContentCompletedEvent,
240
426
  RunCompletedEvent,
241
427
  RunErrorEvent,
242
428
  RunCancelledEvent,
243
429
  RunPausedEvent,
244
430
  RunContinuedEvent,
431
+ PreHookStartedEvent,
432
+ PreHookCompletedEvent,
433
+ PostHookStartedEvent,
434
+ PostHookCompletedEvent,
245
435
  ReasoningStartedEvent,
246
436
  ReasoningStepEvent,
247
437
  ReasoningCompletedEvent,
248
438
  MemoryUpdateStartedEvent,
249
439
  MemoryUpdateCompletedEvent,
440
+ SessionSummaryStartedEvent,
441
+ SessionSummaryCompletedEvent,
250
442
  ToolCallStartedEvent,
251
443
  ToolCallCompletedEvent,
252
444
  ParserModelResponseStartedEvent,
@@ -261,17 +453,24 @@ RunOutputEvent = Union[
261
453
  RUN_EVENT_TYPE_REGISTRY = {
262
454
  RunEvent.run_started.value: RunStartedEvent,
263
455
  RunEvent.run_content.value: RunContentEvent,
456
+ RunEvent.run_content_completed.value: RunContentCompletedEvent,
264
457
  RunEvent.run_intermediate_content.value: IntermediateRunContentEvent,
265
458
  RunEvent.run_completed.value: RunCompletedEvent,
266
459
  RunEvent.run_error.value: RunErrorEvent,
267
460
  RunEvent.run_cancelled.value: RunCancelledEvent,
268
461
  RunEvent.run_paused.value: RunPausedEvent,
269
462
  RunEvent.run_continued.value: RunContinuedEvent,
463
+ RunEvent.pre_hook_started.value: PreHookStartedEvent,
464
+ RunEvent.pre_hook_completed.value: PreHookCompletedEvent,
465
+ RunEvent.post_hook_started.value: PostHookStartedEvent,
466
+ RunEvent.post_hook_completed.value: PostHookCompletedEvent,
270
467
  RunEvent.reasoning_started.value: ReasoningStartedEvent,
271
468
  RunEvent.reasoning_step.value: ReasoningStepEvent,
272
469
  RunEvent.reasoning_completed.value: ReasoningCompletedEvent,
273
470
  RunEvent.memory_update_started.value: MemoryUpdateStartedEvent,
274
471
  RunEvent.memory_update_completed.value: MemoryUpdateCompletedEvent,
472
+ RunEvent.session_summary_started.value: SessionSummaryStartedEvent,
473
+ RunEvent.session_summary_completed.value: SessionSummaryCompletedEvent,
275
474
  RunEvent.tool_call_started.value: ToolCallStartedEvent,
276
475
  RunEvent.tool_call_completed.value: ToolCallCompletedEvent,
277
476
  RunEvent.parser_model_response_started.value: ParserModelResponseStartedEvent,
@@ -290,78 +489,6 @@ def run_output_event_from_dict(data: dict) -> BaseRunOutputEvent:
290
489
  return cls.from_dict(data) # type: ignore
291
490
 
292
491
 
293
- @dataclass
294
- class RunInput:
295
- """Container for the raw input data passed to Agent.run().
296
-
297
- This captures the original input exactly as provided by the user,
298
- separate from the processed messages that go to the model.
299
-
300
- Attributes:
301
- input_content: The literal input message/content passed to run()
302
- images: Images directly passed to run()
303
- videos: Videos directly passed to run()
304
- audios: Audio files directly passed to run()
305
- files: Files directly passed to run()
306
- """
307
-
308
- input_content: Optional[Union[str, List, Dict, Message, BaseModel, List[Message]]] = None
309
- images: Optional[Sequence[Image]] = None
310
- videos: Optional[Sequence[Video]] = None
311
- audios: Optional[Sequence[Audio]] = None
312
- files: Optional[Sequence[File]] = None
313
-
314
- def to_dict(self) -> Dict[str, Any]:
315
- """Convert to dictionary representation"""
316
- result: Dict[str, Any] = {}
317
-
318
- if self.input_content is not None:
319
- if isinstance(self.input_content, (str)):
320
- result["input_content"] = self.input_content
321
- elif isinstance(self.input_content, BaseModel):
322
- result["input_content"] = self.input_content.model_dump(exclude_none=True)
323
- elif isinstance(self.input_content, Message):
324
- result["input_content"] = self.input_content.to_dict()
325
- elif (
326
- isinstance(self.input_content, list)
327
- and self.input_content
328
- and isinstance(self.input_content[0], Message)
329
- ):
330
- result["input_content"] = [m.to_dict() for m in self.input_content]
331
- else:
332
- result["input_content"] = self.input_content
333
-
334
- if self.images:
335
- result["images"] = [img.to_dict() for img in self.images]
336
- if self.videos:
337
- result["videos"] = [vid.to_dict() for vid in self.videos]
338
- if self.audios:
339
- result["audios"] = [aud.to_dict() for aud in self.audios]
340
-
341
- return result
342
-
343
- @classmethod
344
- def from_dict(cls, data: Dict[str, Any]) -> "RunInput":
345
- """Create RunInput from dictionary"""
346
- images = None
347
- if data.get("images"):
348
- images = [Image.model_validate(img_data) for img_data in data["images"]]
349
-
350
- videos = None
351
- if data.get("videos"):
352
- videos = [Video.model_validate(vid_data) for vid_data in data["videos"]]
353
-
354
- audios = None
355
- if data.get("audios"):
356
- audios = [Audio.model_validate(aud_data) for aud_data in data["audios"]]
357
-
358
- files = None
359
- if data.get("files"):
360
- files = [File.model_validate(file_data) for file_data in data["files"]]
361
-
362
- return cls(input_content=data.get("input_content"), images=images, videos=videos, audios=audios, files=files)
363
-
364
-
365
492
  @dataclass
366
493
  class RunOutput:
367
494
  """Response returned by Agent.run() or Workflow.run() functions"""
@@ -374,6 +501,9 @@ class RunOutput:
374
501
  workflow_id: Optional[str] = None
375
502
  user_id: Optional[str] = None
376
503
 
504
+ # Input media and messages from user
505
+ input: Optional[RunInput] = None
506
+
377
507
  content: Optional[Any] = None
378
508
  content_type: str = "str"
379
509
 
@@ -381,6 +511,8 @@ class RunOutput:
381
511
  reasoning_steps: Optional[List[ReasoningStep]] = None
382
512
  reasoning_messages: Optional[List[Message]] = None
383
513
 
514
+ model_provider_data: Optional[Dict[str, Any]] = None
515
+
384
516
  model: Optional[str] = None
385
517
  model_provider: Optional[str] = None
386
518
  messages: Optional[List[Message]] = None
@@ -392,15 +524,14 @@ class RunOutput:
392
524
  images: Optional[List[Image]] = None # Images attached to the response
393
525
  videos: Optional[List[Video]] = None # Videos attached to the response
394
526
  audio: Optional[List[Audio]] = None # Audio attached to the response
527
+ files: Optional[List[File]] = None # Files attached to the response
395
528
  response_audio: Optional[Audio] = None # Model audio response
396
529
 
397
- # Input media and messages from user
398
- input: Optional[RunInput] = None
399
-
400
530
  citations: Optional[Citations] = None
401
531
  references: Optional[List[MessageReferences]] = None
402
532
 
403
533
  metadata: Optional[Dict[str, Any]] = None
534
+ session_state: Optional[Dict[str, Any]] = None
404
535
 
405
536
  created_at: int = field(default_factory=lambda: int(time()))
406
537
 
@@ -446,6 +577,7 @@ class RunOutput:
446
577
  "images",
447
578
  "videos",
448
579
  "audio",
580
+ "files",
449
581
  "response_audio",
450
582
  "input",
451
583
  "citations",
@@ -508,6 +640,14 @@ class RunOutput:
508
640
  else:
509
641
  _dict["audio"].append(aud)
510
642
 
643
+ if self.files is not None:
644
+ _dict["files"] = []
645
+ for file in self.files:
646
+ if isinstance(file, File):
647
+ _dict["files"].append(file.to_dict())
648
+ else:
649
+ _dict["files"].append(file)
650
+
511
651
  if self.response_audio is not None:
512
652
  if isinstance(self.response_audio, Audio):
513
653
  _dict["response_audio"] = self.response_audio.to_dict()
@@ -536,7 +676,7 @@ class RunOutput:
536
676
 
537
677
  return _dict
538
678
 
539
- def to_json(self) -> str:
679
+ def to_json(self, separators=(", ", ": "), indent: Optional[int] = 2) -> str:
540
680
  import json
541
681
 
542
682
  try:
@@ -545,7 +685,10 @@ class RunOutput:
545
685
  logger.error("Failed to convert response to json", exc_info=True)
546
686
  raise
547
687
 
548
- return json.dumps(_dict, indent=2)
688
+ if indent is None:
689
+ return json.dumps(_dict, separators=separators)
690
+ else:
691
+ return json.dumps(_dict, indent=indent, separators=separators)
549
692
 
550
693
  @classmethod
551
694
  def from_dict(cls, data: Dict[str, Any]) -> "RunOutput":
@@ -553,10 +696,20 @@ class RunOutput:
553
696
  data = data.pop("run")
554
697
 
555
698
  events = data.pop("events", None)
556
- events = [run_output_event_from_dict(event) for event in events] if events else None
699
+ final_events = []
700
+ for event in events or []:
701
+ if "agent_id" in event:
702
+ event = run_output_event_from_dict(event)
703
+ else:
704
+ # Use the factory from response.py for agent events
705
+ from agno.run.team import team_run_output_event_from_dict
706
+
707
+ event = team_run_output_event_from_dict(event)
708
+ final_events.append(event)
709
+ events = final_events
557
710
 
558
711
  messages = data.pop("messages", None)
559
- messages = [Message.model_validate(message) for message in messages] if messages else None
712
+ messages = [Message.from_dict(message) for message in messages] if messages else None
560
713
 
561
714
  citations = data.pop("citations", None)
562
715
  citations = Citations.model_validate(citations) if citations else None
@@ -564,17 +717,11 @@ class RunOutput:
564
717
  tools = data.pop("tools", [])
565
718
  tools = [ToolExecution.from_dict(tool) for tool in tools] if tools else None
566
719
 
567
- images = data.pop("images", [])
568
- images = [Image.model_validate(image) for image in images] if images else None
569
-
570
- videos = data.pop("videos", [])
571
- videos = [Video.model_validate(video) for video in videos] if videos else None
572
-
573
- audio = data.pop("audio", [])
574
- audio = [Audio.model_validate(audio) for audio in audio] if audio else None
575
-
576
- response_audio = data.pop("response_audio", None)
577
- response_audio = Audio.model_validate(response_audio) if response_audio else None
720
+ images = reconstruct_images(data.pop("images", []))
721
+ videos = reconstruct_videos(data.pop("videos", []))
722
+ audio = reconstruct_audio_list(data.pop("audio", []))
723
+ files = reconstruct_files(data.pop("files", []))
724
+ response_audio = reconstruct_response_audio(data.pop("response_audio", None))
578
725
 
579
726
  input_data = data.pop("input", None)
580
727
  input_obj = None
@@ -588,7 +735,7 @@ class RunOutput:
588
735
  additional_input = data.pop("additional_input", None)
589
736
 
590
737
  if additional_input is not None:
591
- additional_input = [Message.model_validate(message) for message in additional_input]
738
+ additional_input = [Message.from_dict(message) for message in additional_input]
592
739
 
593
740
  reasoning_steps = data.pop("reasoning_steps", None)
594
741
  if reasoning_steps is not None:
@@ -596,12 +743,18 @@ class RunOutput:
596
743
 
597
744
  reasoning_messages = data.pop("reasoning_messages", None)
598
745
  if reasoning_messages is not None:
599
- reasoning_messages = [Message.model_validate(message) for message in reasoning_messages]
746
+ reasoning_messages = [Message.from_dict(message) for message in reasoning_messages]
600
747
 
601
748
  references = data.pop("references", None)
602
749
  if references is not None:
603
750
  references = [MessageReferences.model_validate(reference) for reference in references]
604
751
 
752
+ # Filter data to only include fields that are actually defined in the RunOutput dataclass
753
+ from dataclasses import fields
754
+
755
+ supported_fields = {f.name for f in fields(cls)}
756
+ filtered_data = {k: v for k, v in data.items() if k in supported_fields}
757
+
605
758
  return cls(
606
759
  messages=messages,
607
760
  metrics=metrics,
@@ -610,6 +763,7 @@ class RunOutput:
610
763
  images=images,
611
764
  audio=audio,
612
765
  videos=videos,
766
+ files=files,
613
767
  response_audio=response_audio,
614
768
  input=input_obj,
615
769
  events=events,
@@ -617,7 +771,7 @@ class RunOutput:
617
771
  reasoning_steps=reasoning_steps,
618
772
  reasoning_messages=reasoning_messages,
619
773
  references=references,
620
- **data,
774
+ **filtered_data,
621
775
  )
622
776
 
623
777
  def get_content_as_string(self, **kwargs) -> str:
agno/run/base.py CHANGED
@@ -1,17 +1,29 @@
1
1
  from dataclasses import asdict, dataclass
2
2
  from enum import Enum
3
- from typing import Any, Dict
3
+ from typing import Any, Dict, List, Optional, Union
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
7
+ from agno.filters import FilterExpr
7
8
  from agno.media import Audio, Image, Video
8
9
  from agno.models.message import Citations, Message, MessageReferences
9
10
  from agno.models.metrics import Metrics
10
- from agno.models.response import ToolExecution
11
11
  from agno.reasoning.step import ReasoningStep
12
12
  from agno.utils.log import log_error
13
13
 
14
14
 
15
+ @dataclass
16
+ class RunContext:
17
+ run_id: str
18
+ session_id: str
19
+ user_id: Optional[str] = None
20
+
21
+ dependencies: Optional[Dict[str, Any]] = None
22
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
23
+ metadata: Optional[Dict[str, Any]] = None
24
+ session_state: Optional[Dict[str, Any]] = None
25
+
26
+
15
27
  @dataclass
16
28
  class BaseRunOutputEvent:
17
29
  def to_dict(self) -> Dict[str, Any]:
@@ -35,6 +47,7 @@ class BaseRunOutputEvent:
35
47
  "reasoning_steps",
36
48
  "references",
37
49
  "additional_input",
50
+ "session_summary",
38
51
  "metrics",
39
52
  ]
40
53
  }
@@ -97,6 +110,8 @@ class BaseRunOutputEvent:
97
110
  _dict["content"] = self.content.model_dump(exclude_none=True)
98
111
 
99
112
  if hasattr(self, "tools") and self.tools is not None:
113
+ from agno.models.response import ToolExecution
114
+
100
115
  _dict["tools"] = []
101
116
  for tool in self.tools:
102
117
  if isinstance(tool, ToolExecution):
@@ -105,6 +120,8 @@ class BaseRunOutputEvent:
105
120
  _dict["tools"].append(tool)
106
121
 
107
122
  if hasattr(self, "tool") and self.tool is not None:
123
+ from agno.models.response import ToolExecution
124
+
108
125
  if isinstance(self.tool, ToolExecution):
109
126
  _dict["tool"] = self.tool.to_dict()
110
127
  else:
@@ -113,23 +130,33 @@ class BaseRunOutputEvent:
113
130
  if hasattr(self, "metrics") and self.metrics is not None:
114
131
  _dict["metrics"] = self.metrics.to_dict()
115
132
 
133
+ if hasattr(self, "session_summary") and self.session_summary is not None:
134
+ _dict["session_summary"] = self.session_summary.to_dict()
135
+
116
136
  return _dict
117
137
 
118
- def to_json(self) -> str:
138
+ def to_json(self, separators=(", ", ": "), indent: Optional[int] = 2) -> str:
119
139
  import json
120
140
 
141
+ from agno.utils.serialize import json_serializer
142
+
121
143
  try:
122
144
  _dict = self.to_dict()
123
145
  except Exception:
124
146
  log_error("Failed to convert response event to json", exc_info=True)
125
147
  raise
126
148
 
127
- return json.dumps(_dict, indent=2)
149
+ if indent is None:
150
+ return json.dumps(_dict, separators=separators, default=json_serializer, ensure_ascii=False)
151
+ else:
152
+ return json.dumps(_dict, indent=indent, separators=separators, default=json_serializer, ensure_ascii=False)
128
153
 
129
154
  @classmethod
130
155
  def from_dict(cls, data: Dict[str, Any]):
131
156
  tool = data.pop("tool", None)
132
157
  if tool:
158
+ from agno.models.response import ToolExecution
159
+
133
160
  data["tool"] = ToolExecution.from_dict(tool)
134
161
 
135
162
  images = data.pop("images", None)
@@ -168,7 +195,19 @@ class BaseRunOutputEvent:
168
195
  if metrics:
169
196
  data["metrics"] = Metrics(**metrics)
170
197
 
171
- return cls(**data)
198
+ session_summary = data.pop("session_summary", None)
199
+ if session_summary:
200
+ from agno.session.summary import SessionSummary
201
+
202
+ data["session_summary"] = SessionSummary.from_dict(session_summary)
203
+
204
+ # Filter data to only include fields that are actually defined in the target class
205
+ from dataclasses import fields
206
+
207
+ supported_fields = {f.name for f in fields(cls)}
208
+ filtered_data = {k: v for k, v in data.items() if k in supported_fields}
209
+
210
+ return cls(**filtered_data)
172
211
 
173
212
  @property
174
213
  def is_paused(self):