agno 2.1.2__py3-none-any.whl → 2.3.13__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 +5540 -2273
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/compression/__init__.py +3 -0
  5. agno/compression/manager.py +247 -0
  6. agno/culture/__init__.py +3 -0
  7. agno/culture/manager.py +956 -0
  8. agno/db/async_postgres/__init__.py +3 -0
  9. agno/db/base.py +689 -6
  10. agno/db/dynamo/dynamo.py +933 -37
  11. agno/db/dynamo/schemas.py +174 -10
  12. agno/db/dynamo/utils.py +63 -4
  13. agno/db/firestore/firestore.py +831 -9
  14. agno/db/firestore/schemas.py +51 -0
  15. agno/db/firestore/utils.py +102 -4
  16. agno/db/gcs_json/gcs_json_db.py +660 -12
  17. agno/db/gcs_json/utils.py +60 -26
  18. agno/db/in_memory/in_memory_db.py +287 -14
  19. agno/db/in_memory/utils.py +60 -2
  20. agno/db/json/json_db.py +590 -14
  21. agno/db/json/utils.py +60 -26
  22. agno/db/migrations/manager.py +199 -0
  23. agno/db/migrations/v1_to_v2.py +43 -13
  24. agno/db/migrations/versions/__init__.py +0 -0
  25. agno/db/migrations/versions/v2_3_0.py +938 -0
  26. agno/db/mongo/__init__.py +15 -1
  27. agno/db/mongo/async_mongo.py +2760 -0
  28. agno/db/mongo/mongo.py +879 -11
  29. agno/db/mongo/schemas.py +42 -0
  30. agno/db/mongo/utils.py +80 -8
  31. agno/db/mysql/__init__.py +2 -1
  32. agno/db/mysql/async_mysql.py +2912 -0
  33. agno/db/mysql/mysql.py +946 -68
  34. agno/db/mysql/schemas.py +72 -10
  35. agno/db/mysql/utils.py +198 -7
  36. agno/db/postgres/__init__.py +2 -1
  37. agno/db/postgres/async_postgres.py +2579 -0
  38. agno/db/postgres/postgres.py +942 -57
  39. agno/db/postgres/schemas.py +81 -18
  40. agno/db/postgres/utils.py +164 -2
  41. agno/db/redis/redis.py +671 -7
  42. agno/db/redis/schemas.py +50 -0
  43. agno/db/redis/utils.py +65 -7
  44. agno/db/schemas/__init__.py +2 -1
  45. agno/db/schemas/culture.py +120 -0
  46. agno/db/schemas/evals.py +1 -0
  47. agno/db/schemas/memory.py +17 -2
  48. agno/db/singlestore/schemas.py +63 -0
  49. agno/db/singlestore/singlestore.py +949 -83
  50. agno/db/singlestore/utils.py +60 -2
  51. agno/db/sqlite/__init__.py +2 -1
  52. agno/db/sqlite/async_sqlite.py +2911 -0
  53. agno/db/sqlite/schemas.py +62 -0
  54. agno/db/sqlite/sqlite.py +965 -46
  55. agno/db/sqlite/utils.py +169 -8
  56. agno/db/surrealdb/__init__.py +3 -0
  57. agno/db/surrealdb/metrics.py +292 -0
  58. agno/db/surrealdb/models.py +334 -0
  59. agno/db/surrealdb/queries.py +71 -0
  60. agno/db/surrealdb/surrealdb.py +1908 -0
  61. agno/db/surrealdb/utils.py +147 -0
  62. agno/db/utils.py +2 -0
  63. agno/eval/__init__.py +10 -0
  64. agno/eval/accuracy.py +75 -55
  65. agno/eval/agent_as_judge.py +861 -0
  66. agno/eval/base.py +29 -0
  67. agno/eval/performance.py +16 -7
  68. agno/eval/reliability.py +28 -16
  69. agno/eval/utils.py +35 -17
  70. agno/exceptions.py +27 -2
  71. agno/filters.py +354 -0
  72. agno/guardrails/prompt_injection.py +1 -0
  73. agno/hooks/__init__.py +3 -0
  74. agno/hooks/decorator.py +164 -0
  75. agno/integrations/discord/client.py +1 -1
  76. agno/knowledge/chunking/agentic.py +13 -10
  77. agno/knowledge/chunking/fixed.py +4 -1
  78. agno/knowledge/chunking/semantic.py +9 -4
  79. agno/knowledge/chunking/strategy.py +59 -15
  80. agno/knowledge/embedder/fastembed.py +1 -1
  81. agno/knowledge/embedder/nebius.py +1 -1
  82. agno/knowledge/embedder/ollama.py +8 -0
  83. agno/knowledge/embedder/openai.py +8 -8
  84. agno/knowledge/embedder/sentence_transformer.py +6 -2
  85. agno/knowledge/embedder/vllm.py +262 -0
  86. agno/knowledge/knowledge.py +1618 -318
  87. agno/knowledge/reader/base.py +6 -2
  88. agno/knowledge/reader/csv_reader.py +8 -10
  89. agno/knowledge/reader/docx_reader.py +5 -6
  90. agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
  91. agno/knowledge/reader/json_reader.py +5 -4
  92. agno/knowledge/reader/markdown_reader.py +8 -8
  93. agno/knowledge/reader/pdf_reader.py +17 -19
  94. agno/knowledge/reader/pptx_reader.py +101 -0
  95. agno/knowledge/reader/reader_factory.py +32 -3
  96. agno/knowledge/reader/s3_reader.py +3 -3
  97. agno/knowledge/reader/tavily_reader.py +193 -0
  98. agno/knowledge/reader/text_reader.py +22 -10
  99. agno/knowledge/reader/web_search_reader.py +1 -48
  100. agno/knowledge/reader/website_reader.py +10 -10
  101. agno/knowledge/reader/wikipedia_reader.py +33 -1
  102. agno/knowledge/types.py +1 -0
  103. agno/knowledge/utils.py +72 -7
  104. agno/media.py +22 -6
  105. agno/memory/__init__.py +14 -1
  106. agno/memory/manager.py +544 -83
  107. agno/memory/strategies/__init__.py +15 -0
  108. agno/memory/strategies/base.py +66 -0
  109. agno/memory/strategies/summarize.py +196 -0
  110. agno/memory/strategies/types.py +37 -0
  111. agno/models/aimlapi/aimlapi.py +17 -0
  112. agno/models/anthropic/claude.py +515 -40
  113. agno/models/aws/bedrock.py +102 -21
  114. agno/models/aws/claude.py +131 -274
  115. agno/models/azure/ai_foundry.py +41 -19
  116. agno/models/azure/openai_chat.py +39 -8
  117. agno/models/base.py +1249 -525
  118. agno/models/cerebras/cerebras.py +91 -21
  119. agno/models/cerebras/cerebras_openai.py +21 -2
  120. agno/models/cohere/chat.py +40 -6
  121. agno/models/cometapi/cometapi.py +18 -1
  122. agno/models/dashscope/dashscope.py +2 -3
  123. agno/models/deepinfra/deepinfra.py +18 -1
  124. agno/models/deepseek/deepseek.py +69 -3
  125. agno/models/fireworks/fireworks.py +18 -1
  126. agno/models/google/gemini.py +877 -80
  127. agno/models/google/utils.py +22 -0
  128. agno/models/groq/groq.py +51 -18
  129. agno/models/huggingface/huggingface.py +17 -6
  130. agno/models/ibm/watsonx.py +16 -6
  131. agno/models/internlm/internlm.py +18 -1
  132. agno/models/langdb/langdb.py +13 -1
  133. agno/models/litellm/chat.py +44 -9
  134. agno/models/litellm/litellm_openai.py +18 -1
  135. agno/models/message.py +28 -5
  136. agno/models/meta/llama.py +47 -14
  137. agno/models/meta/llama_openai.py +22 -17
  138. agno/models/mistral/mistral.py +8 -4
  139. agno/models/nebius/nebius.py +6 -7
  140. agno/models/nvidia/nvidia.py +20 -3
  141. agno/models/ollama/chat.py +24 -8
  142. agno/models/openai/chat.py +104 -29
  143. agno/models/openai/responses.py +101 -81
  144. agno/models/openrouter/openrouter.py +60 -3
  145. agno/models/perplexity/perplexity.py +17 -1
  146. agno/models/portkey/portkey.py +7 -6
  147. agno/models/requesty/requesty.py +24 -4
  148. agno/models/response.py +73 -2
  149. agno/models/sambanova/sambanova.py +20 -3
  150. agno/models/siliconflow/siliconflow.py +19 -2
  151. agno/models/together/together.py +20 -3
  152. agno/models/utils.py +254 -8
  153. agno/models/vercel/v0.py +20 -3
  154. agno/models/vertexai/__init__.py +0 -0
  155. agno/models/vertexai/claude.py +190 -0
  156. agno/models/vllm/vllm.py +19 -14
  157. agno/models/xai/xai.py +19 -2
  158. agno/os/app.py +549 -152
  159. agno/os/auth.py +190 -3
  160. agno/os/config.py +23 -0
  161. agno/os/interfaces/a2a/router.py +8 -11
  162. agno/os/interfaces/a2a/utils.py +1 -1
  163. agno/os/interfaces/agui/router.py +18 -3
  164. agno/os/interfaces/agui/utils.py +152 -39
  165. agno/os/interfaces/slack/router.py +55 -37
  166. agno/os/interfaces/slack/slack.py +9 -1
  167. agno/os/interfaces/whatsapp/router.py +0 -1
  168. agno/os/interfaces/whatsapp/security.py +3 -1
  169. agno/os/mcp.py +110 -52
  170. agno/os/middleware/__init__.py +2 -0
  171. agno/os/middleware/jwt.py +676 -112
  172. agno/os/router.py +40 -1478
  173. agno/os/routers/agents/__init__.py +3 -0
  174. agno/os/routers/agents/router.py +599 -0
  175. agno/os/routers/agents/schema.py +261 -0
  176. agno/os/routers/evals/evals.py +96 -39
  177. agno/os/routers/evals/schemas.py +65 -33
  178. agno/os/routers/evals/utils.py +80 -10
  179. agno/os/routers/health.py +10 -4
  180. agno/os/routers/knowledge/knowledge.py +196 -38
  181. agno/os/routers/knowledge/schemas.py +82 -22
  182. agno/os/routers/memory/memory.py +279 -52
  183. agno/os/routers/memory/schemas.py +46 -17
  184. agno/os/routers/metrics/metrics.py +20 -8
  185. agno/os/routers/metrics/schemas.py +16 -16
  186. agno/os/routers/session/session.py +462 -34
  187. agno/os/routers/teams/__init__.py +3 -0
  188. agno/os/routers/teams/router.py +512 -0
  189. agno/os/routers/teams/schema.py +257 -0
  190. agno/os/routers/traces/__init__.py +3 -0
  191. agno/os/routers/traces/schemas.py +414 -0
  192. agno/os/routers/traces/traces.py +499 -0
  193. agno/os/routers/workflows/__init__.py +3 -0
  194. agno/os/routers/workflows/router.py +624 -0
  195. agno/os/routers/workflows/schema.py +75 -0
  196. agno/os/schema.py +256 -693
  197. agno/os/scopes.py +469 -0
  198. agno/os/utils.py +514 -36
  199. agno/reasoning/anthropic.py +80 -0
  200. agno/reasoning/gemini.py +73 -0
  201. agno/reasoning/openai.py +5 -0
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +155 -32
  205. agno/run/base.py +55 -3
  206. agno/run/requirement.py +181 -0
  207. agno/run/team.py +125 -38
  208. agno/run/workflow.py +72 -18
  209. agno/session/agent.py +102 -89
  210. agno/session/summary.py +56 -15
  211. agno/session/team.py +164 -90
  212. agno/session/workflow.py +405 -40
  213. agno/table.py +10 -0
  214. agno/team/team.py +3974 -1903
  215. agno/tools/dalle.py +2 -4
  216. agno/tools/eleven_labs.py +23 -25
  217. agno/tools/exa.py +21 -16
  218. agno/tools/file.py +153 -23
  219. agno/tools/file_generation.py +16 -10
  220. agno/tools/firecrawl.py +15 -7
  221. agno/tools/function.py +193 -38
  222. agno/tools/gmail.py +238 -14
  223. agno/tools/google_drive.py +271 -0
  224. agno/tools/googlecalendar.py +36 -8
  225. agno/tools/googlesheets.py +20 -5
  226. agno/tools/jira.py +20 -0
  227. agno/tools/mcp/__init__.py +10 -0
  228. agno/tools/mcp/mcp.py +331 -0
  229. agno/tools/mcp/multi_mcp.py +347 -0
  230. agno/tools/mcp/params.py +24 -0
  231. agno/tools/mcp_toolbox.py +3 -3
  232. agno/tools/models/nebius.py +5 -5
  233. agno/tools/models_labs.py +20 -10
  234. agno/tools/nano_banana.py +151 -0
  235. agno/tools/notion.py +204 -0
  236. agno/tools/parallel.py +314 -0
  237. agno/tools/postgres.py +76 -36
  238. agno/tools/redshift.py +406 -0
  239. agno/tools/scrapegraph.py +1 -1
  240. agno/tools/shopify.py +1519 -0
  241. agno/tools/slack.py +18 -3
  242. agno/tools/spotify.py +919 -0
  243. agno/tools/tavily.py +146 -0
  244. agno/tools/toolkit.py +25 -0
  245. agno/tools/workflow.py +8 -1
  246. agno/tools/yfinance.py +12 -11
  247. agno/tracing/__init__.py +12 -0
  248. agno/tracing/exporter.py +157 -0
  249. agno/tracing/schemas.py +276 -0
  250. agno/tracing/setup.py +111 -0
  251. agno/utils/agent.py +938 -0
  252. agno/utils/cryptography.py +22 -0
  253. agno/utils/dttm.py +33 -0
  254. agno/utils/events.py +151 -3
  255. agno/utils/gemini.py +15 -5
  256. agno/utils/hooks.py +118 -4
  257. agno/utils/http.py +113 -2
  258. agno/utils/knowledge.py +12 -5
  259. agno/utils/log.py +1 -0
  260. agno/utils/mcp.py +92 -2
  261. agno/utils/media.py +187 -1
  262. agno/utils/merge_dict.py +3 -3
  263. agno/utils/message.py +60 -0
  264. agno/utils/models/ai_foundry.py +9 -2
  265. agno/utils/models/claude.py +49 -14
  266. agno/utils/models/cohere.py +9 -2
  267. agno/utils/models/llama.py +9 -2
  268. agno/utils/models/mistral.py +4 -2
  269. agno/utils/print_response/agent.py +109 -16
  270. agno/utils/print_response/team.py +223 -30
  271. agno/utils/print_response/workflow.py +251 -34
  272. agno/utils/streamlit.py +1 -1
  273. agno/utils/team.py +98 -9
  274. agno/utils/tokens.py +657 -0
  275. agno/vectordb/base.py +39 -7
  276. agno/vectordb/cassandra/cassandra.py +21 -5
  277. agno/vectordb/chroma/chromadb.py +43 -12
  278. agno/vectordb/clickhouse/clickhousedb.py +21 -5
  279. agno/vectordb/couchbase/couchbase.py +29 -5
  280. agno/vectordb/lancedb/lance_db.py +92 -181
  281. agno/vectordb/langchaindb/langchaindb.py +24 -4
  282. agno/vectordb/lightrag/lightrag.py +17 -3
  283. agno/vectordb/llamaindex/llamaindexdb.py +25 -5
  284. agno/vectordb/milvus/milvus.py +50 -37
  285. agno/vectordb/mongodb/__init__.py +7 -1
  286. agno/vectordb/mongodb/mongodb.py +36 -30
  287. agno/vectordb/pgvector/pgvector.py +201 -77
  288. agno/vectordb/pineconedb/pineconedb.py +41 -23
  289. agno/vectordb/qdrant/qdrant.py +67 -54
  290. agno/vectordb/redis/__init__.py +9 -0
  291. agno/vectordb/redis/redisdb.py +682 -0
  292. agno/vectordb/singlestore/singlestore.py +50 -29
  293. agno/vectordb/surrealdb/surrealdb.py +31 -41
  294. agno/vectordb/upstashdb/upstashdb.py +34 -6
  295. agno/vectordb/weaviate/weaviate.py +53 -14
  296. agno/workflow/__init__.py +2 -0
  297. agno/workflow/agent.py +299 -0
  298. agno/workflow/condition.py +120 -18
  299. agno/workflow/loop.py +77 -10
  300. agno/workflow/parallel.py +231 -143
  301. agno/workflow/router.py +118 -17
  302. agno/workflow/step.py +609 -170
  303. agno/workflow/steps.py +73 -6
  304. agno/workflow/types.py +96 -21
  305. agno/workflow/workflow.py +2039 -262
  306. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
  307. agno-2.3.13.dist-info/RECORD +613 -0
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -679
  310. agno/tools/memori.py +0 -339
  311. agno-2.1.2.dist-info/RECORD +0 -543
  312. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
  313. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
agno/run/base.py CHANGED
@@ -1,17 +1,30 @@
1
1
  from dataclasses import asdict, dataclass
2
2
  from enum import Enum
3
- from typing import Any, Dict, Optional
3
+ from typing import Any, Dict, List, Optional, Type, 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
+ output_schema: Optional[Type[BaseModel]] = None
26
+
27
+
15
28
  @dataclass
16
29
  class BaseRunOutputEvent:
17
30
  def to_dict(self) -> Dict[str, Any]:
@@ -35,7 +48,9 @@ class BaseRunOutputEvent:
35
48
  "reasoning_steps",
36
49
  "references",
37
50
  "additional_input",
51
+ "session_summary",
38
52
  "metrics",
53
+ "run_input",
39
54
  ]
40
55
  }
41
56
 
@@ -97,6 +112,8 @@ class BaseRunOutputEvent:
97
112
  _dict["content"] = self.content.model_dump(exclude_none=True)
98
113
 
99
114
  if hasattr(self, "tools") and self.tools is not None:
115
+ from agno.models.response import ToolExecution
116
+
100
117
  _dict["tools"] = []
101
118
  for tool in self.tools:
102
119
  if isinstance(tool, ToolExecution):
@@ -105,6 +122,8 @@ class BaseRunOutputEvent:
105
122
  _dict["tools"].append(tool)
106
123
 
107
124
  if hasattr(self, "tool") and self.tool is not None:
125
+ from agno.models.response import ToolExecution
126
+
108
127
  if isinstance(self.tool, ToolExecution):
109
128
  _dict["tool"] = self.tool.to_dict()
110
129
  else:
@@ -113,6 +132,12 @@ class BaseRunOutputEvent:
113
132
  if hasattr(self, "metrics") and self.metrics is not None:
114
133
  _dict["metrics"] = self.metrics.to_dict()
115
134
 
135
+ if hasattr(self, "session_summary") and self.session_summary is not None:
136
+ _dict["session_summary"] = self.session_summary.to_dict()
137
+
138
+ if hasattr(self, "run_input") and self.run_input is not None:
139
+ _dict["run_input"] = self.run_input.to_dict()
140
+
116
141
  return _dict
117
142
 
118
143
  def to_json(self, separators=(", ", ": "), indent: Optional[int] = 2) -> str:
@@ -135,6 +160,8 @@ class BaseRunOutputEvent:
135
160
  def from_dict(cls, data: Dict[str, Any]):
136
161
  tool = data.pop("tool", None)
137
162
  if tool:
163
+ from agno.models.response import ToolExecution
164
+
138
165
  data["tool"] = ToolExecution.from_dict(tool)
139
166
 
140
167
  images = data.pop("images", None)
@@ -173,7 +200,32 @@ class BaseRunOutputEvent:
173
200
  if metrics:
174
201
  data["metrics"] = Metrics(**metrics)
175
202
 
176
- return cls(**data)
203
+ session_summary = data.pop("session_summary", None)
204
+ if session_summary:
205
+ from agno.session.summary import SessionSummary
206
+
207
+ data["session_summary"] = SessionSummary.from_dict(session_summary)
208
+
209
+ run_input = data.pop("run_input", None)
210
+ if run_input:
211
+ from agno.run.team import BaseTeamRunEvent
212
+
213
+ if issubclass(cls, BaseTeamRunEvent):
214
+ from agno.run.team import TeamRunInput
215
+
216
+ data["run_input"] = TeamRunInput.from_dict(run_input)
217
+ else:
218
+ from agno.run.agent import RunInput
219
+
220
+ data["run_input"] = RunInput.from_dict(run_input)
221
+
222
+ # Filter data to only include fields that are actually defined in the target class
223
+ from dataclasses import fields
224
+
225
+ supported_fields = {f.name for f in fields(cls)}
226
+ filtered_data = {k: v for k, v in data.items() if k in supported_fields}
227
+
228
+ return cls(**filtered_data)
177
229
 
178
230
  @property
179
231
  def is_paused(self):
@@ -0,0 +1,181 @@
1
+ from dataclasses import dataclass, field
2
+ from datetime import datetime, timezone
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
4
+ from uuid import uuid4
5
+
6
+ from agno.models.response import ToolExecution, UserInputField
7
+
8
+ if TYPE_CHECKING:
9
+ pass
10
+
11
+
12
+ @dataclass
13
+ class RunRequirement:
14
+ """Requirement to complete a paused run (used in HITL flows)"""
15
+
16
+ tool_execution: Optional[ToolExecution] = None
17
+ created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
18
+
19
+ # User confirmation
20
+ confirmation: Optional[bool] = None
21
+ confirmation_note: Optional[str] = None
22
+
23
+ # User input
24
+ user_input_schema: Optional[List[UserInputField]] = None
25
+
26
+ # External execution
27
+ external_execution_result: Optional[str] = None
28
+
29
+ def __init__(
30
+ self,
31
+ tool_execution: ToolExecution,
32
+ id: Optional[str] = None,
33
+ created_at: Optional[datetime] = None,
34
+ ):
35
+ self.id = id or str(uuid4())
36
+ self.tool_execution = tool_execution
37
+ self.user_input_schema = tool_execution.user_input_schema if tool_execution else None
38
+ self.created_at = created_at or datetime.now(timezone.utc)
39
+ self.confirmation = None
40
+ self.confirmation_note = None
41
+ self.external_execution_result = None
42
+
43
+ @property
44
+ def needs_confirmation(self) -> bool:
45
+ if self.confirmation is not None:
46
+ return False
47
+ if not self.tool_execution:
48
+ return False
49
+ if self.tool_execution.confirmed is True:
50
+ return True
51
+
52
+ return self.tool_execution.requires_confirmation or False
53
+
54
+ @property
55
+ def needs_user_input(self) -> bool:
56
+ if not self.tool_execution:
57
+ return False
58
+ if self.tool_execution.answered is True:
59
+ return False
60
+ if self.user_input_schema and not all(field.value is not None for field in self.user_input_schema):
61
+ return True
62
+
63
+ return self.tool_execution.requires_user_input or False
64
+
65
+ @property
66
+ def needs_external_execution(self) -> bool:
67
+ if not self.tool_execution:
68
+ return False
69
+ if self.external_execution_result is not None:
70
+ return True
71
+
72
+ return self.tool_execution.external_execution_required or False
73
+
74
+ def confirm(self):
75
+ if not self.needs_confirmation:
76
+ raise ValueError("This requirement does not require confirmation")
77
+ self.confirmation = True
78
+ if self.tool_execution:
79
+ self.tool_execution.confirmed = True
80
+
81
+ def reject(self):
82
+ if not self.needs_confirmation:
83
+ raise ValueError("This requirement does not require confirmation")
84
+ self.confirmation = False
85
+ if self.tool_execution:
86
+ self.tool_execution.confirmed = False
87
+
88
+ def set_external_execution_result(self, result: str):
89
+ if not self.needs_external_execution:
90
+ raise ValueError("This requirement does not require external execution")
91
+ self.external_execution_result = result
92
+ if self.tool_execution:
93
+ self.tool_execution.result = result
94
+
95
+ def update_tool(self):
96
+ if not self.tool_execution:
97
+ return
98
+ if self.confirmation is True:
99
+ self.tool_execution.confirmed = True
100
+ elif self.confirmation is False:
101
+ self.tool_execution.confirmed = False
102
+ else:
103
+ raise ValueError("This requirement does not require confirmation or user input")
104
+
105
+ def is_resolved(self) -> bool:
106
+ """Return True if the requirement has been resolved"""
107
+ return not self.needs_confirmation and not self.needs_user_input and not self.needs_external_execution
108
+
109
+ def to_dict(self) -> Dict[str, Any]:
110
+ """Convert to JSON-serializable dictionary for storage."""
111
+ _dict: Dict[str, Any] = {
112
+ "id": self.id,
113
+ "created_at": self.created_at.isoformat() if isinstance(self.created_at, datetime) else self.created_at,
114
+ "confirmation": self.confirmation,
115
+ "confirmation_note": self.confirmation_note,
116
+ "external_execution_result": self.external_execution_result,
117
+ }
118
+
119
+ if self.tool_execution is not None:
120
+ _dict["tool_execution"] = (
121
+ self.tool_execution.to_dict() if isinstance(self.tool_execution, ToolExecution) else self.tool_execution
122
+ )
123
+
124
+ if self.user_input_schema is not None:
125
+ _dict["user_input_schema"] = [f.to_dict() if hasattr(f, "to_dict") else f for f in self.user_input_schema]
126
+
127
+ return {k: v for k, v in _dict.items() if v is not None}
128
+
129
+ @classmethod
130
+ def from_dict(cls, data: Dict[str, Any]) -> "RunRequirement":
131
+ """Reconstruct from stored dictionary."""
132
+ if data is None:
133
+ raise ValueError("RunRequirement.from_dict() requires a non-None dict")
134
+
135
+ # Handle tool_execution
136
+ tool_data = data.get("tool_execution")
137
+ tool_execution: Optional[ToolExecution] = None
138
+ if isinstance(tool_data, ToolExecution):
139
+ tool_execution = tool_data
140
+ elif isinstance(tool_data, dict):
141
+ tool_execution = ToolExecution.from_dict(tool_data)
142
+
143
+ # Handle created_at (ISO string or datetime)
144
+ created_at_raw = data.get("created_at")
145
+ created_at: Optional[datetime] = None
146
+ if isinstance(created_at_raw, datetime):
147
+ created_at = created_at_raw
148
+ elif isinstance(created_at_raw, str):
149
+ try:
150
+ created_at = datetime.fromisoformat(created_at_raw)
151
+ except ValueError:
152
+ created_at = None
153
+
154
+ # Build requirement - tool_execution is required by __init__
155
+ # For legacy data without tool_execution, create a minimal placeholder
156
+ if tool_execution is None:
157
+ tool_execution = ToolExecution(tool_name="unknown", tool_args={})
158
+
159
+ requirement = cls(
160
+ tool_execution=tool_execution,
161
+ id=data.get("id"),
162
+ created_at=created_at,
163
+ )
164
+
165
+ # Set optional fields
166
+ requirement.confirmation = data.get("confirmation")
167
+ requirement.confirmation_note = data.get("confirmation_note")
168
+ requirement.external_execution_result = data.get("external_execution_result")
169
+
170
+ # Handle user_input_schema
171
+ schema_raw = data.get("user_input_schema")
172
+ if schema_raw is not None:
173
+ rebuilt_schema: List[UserInputField] = []
174
+ for item in schema_raw:
175
+ if isinstance(item, UserInputField):
176
+ rebuilt_schema.append(item)
177
+ elif isinstance(item, dict):
178
+ rebuilt_schema.append(UserInputField.from_dict(item))
179
+ requirement.user_input_schema = rebuilt_schema if rebuilt_schema else None
180
+
181
+ return requirement
agno/run/team.py CHANGED
@@ -12,7 +12,15 @@ from agno.models.response import ToolExecution
12
12
  from agno.reasoning.step import ReasoningStep
13
13
  from agno.run.agent import RunEvent, RunOutput, RunOutputEvent, run_output_event_from_dict
14
14
  from agno.run.base import BaseRunOutputEvent, MessageReferences, RunStatus
15
+ from agno.run.requirement import RunRequirement
15
16
  from agno.utils.log import log_error
17
+ from agno.utils.media import (
18
+ reconstruct_audio_list,
19
+ reconstruct_files,
20
+ reconstruct_images,
21
+ reconstruct_response_audio,
22
+ reconstruct_videos,
23
+ )
16
24
 
17
25
 
18
26
  @dataclass
@@ -59,12 +67,39 @@ class TeamRunInput:
59
67
  result["input_content"] = self.input_content.model_dump(exclude_none=True)
60
68
  elif isinstance(self.input_content, Message):
61
69
  result["input_content"] = self.input_content.to_dict()
70
+
71
+ # Handle input_content provided as a list of Message objects
62
72
  elif (
63
73
  isinstance(self.input_content, list)
64
74
  and self.input_content
65
75
  and isinstance(self.input_content[0], Message)
66
76
  ):
67
77
  result["input_content"] = [m.to_dict() for m in self.input_content]
78
+
79
+ # Handle input_content provided as a list of dicts
80
+ elif (
81
+ isinstance(self.input_content, list) and self.input_content and isinstance(self.input_content[0], dict)
82
+ ):
83
+ for content in self.input_content:
84
+ # Handle media input
85
+ if isinstance(content, dict):
86
+ if content.get("images"):
87
+ content["images"] = [
88
+ img.to_dict() if isinstance(img, Image) else img for img in content["images"]
89
+ ]
90
+ if content.get("videos"):
91
+ content["videos"] = [
92
+ vid.to_dict() if isinstance(vid, Video) else vid for vid in content["videos"]
93
+ ]
94
+ if content.get("audios"):
95
+ content["audios"] = [
96
+ aud.to_dict() if isinstance(aud, Audio) else aud for aud in content["audios"]
97
+ ]
98
+ if content.get("files"):
99
+ content["files"] = [
100
+ file.to_dict() if isinstance(file, File) else file for file in content["files"]
101
+ ]
102
+ result["input_content"] = self.input_content
68
103
  else:
69
104
  result["input_content"] = self.input_content
70
105
 
@@ -82,21 +117,10 @@ class TeamRunInput:
82
117
  @classmethod
83
118
  def from_dict(cls, data: Dict[str, Any]) -> "TeamRunInput":
84
119
  """Create TeamRunInput from dictionary"""
85
- images = None
86
- if data.get("images"):
87
- images = [Image.model_validate(img_data) for img_data in data["images"]]
88
-
89
- videos = None
90
- if data.get("videos"):
91
- videos = [Video.model_validate(vid_data) for vid_data in data["videos"]]
92
-
93
- audios = None
94
- if data.get("audios"):
95
- audios = [Audio.model_validate(aud_data) for aud_data in data["audios"]]
96
-
97
- files = None
98
- if data.get("files"):
99
- files = [File.model_validate(file_data) for file_data in data["files"]]
120
+ images = reconstruct_images(data.get("images"))
121
+ videos = reconstruct_videos(data.get("videos"))
122
+ audios = reconstruct_audio_list(data.get("audios"))
123
+ files = reconstruct_files(data.get("files"))
100
124
 
101
125
  return cls(
102
126
  input_content=data.get("input_content", ""), images=images, videos=videos, audios=audios, files=files
@@ -109,6 +133,7 @@ class TeamRunEvent(str, Enum):
109
133
  run_started = "TeamRunStarted"
110
134
  run_content = "TeamRunContent"
111
135
  run_intermediate_content = "TeamRunIntermediateContent"
136
+ run_content_completed = "TeamRunContentCompleted"
112
137
  run_completed = "TeamRunCompleted"
113
138
  run_error = "TeamRunError"
114
139
  run_cancelled = "TeamRunCancelled"
@@ -116,6 +141,9 @@ class TeamRunEvent(str, Enum):
116
141
  pre_hook_started = "TeamPreHookStarted"
117
142
  pre_hook_completed = "TeamPreHookCompleted"
118
143
 
144
+ post_hook_started = "TeamPostHookStarted"
145
+ post_hook_completed = "TeamPostHookCompleted"
146
+
119
147
  tool_call_started = "TeamToolCallStarted"
120
148
  tool_call_completed = "TeamToolCallCompleted"
121
149
 
@@ -126,6 +154,9 @@ class TeamRunEvent(str, Enum):
126
154
  memory_update_started = "TeamMemoryUpdateStarted"
127
155
  memory_update_completed = "TeamMemoryUpdateCompleted"
128
156
 
157
+ session_summary_started = "TeamSessionSummaryStarted"
158
+ session_summary_completed = "TeamSessionSummaryCompleted"
159
+
129
160
  parser_model_response_started = "TeamParserModelResponseStarted"
130
161
  parser_model_response_completed = "TeamParserModelResponseCompleted"
131
162
 
@@ -207,6 +238,11 @@ class IntermediateRunContentEvent(BaseTeamRunEvent):
207
238
  content_type: str = "str"
208
239
 
209
240
 
241
+ @dataclass
242
+ class RunContentCompletedEvent(BaseTeamRunEvent):
243
+ event: str = TeamRunEvent.run_content_completed.value
244
+
245
+
210
246
  @dataclass
211
247
  class RunCompletedEvent(BaseTeamRunEvent):
212
248
  event: str = TeamRunEvent.run_completed.value
@@ -226,6 +262,7 @@ class RunCompletedEvent(BaseTeamRunEvent):
226
262
  member_responses: List[Union["TeamRunOutput", RunOutput]] = field(default_factory=list)
227
263
  metadata: Optional[Dict[str, Any]] = None
228
264
  metrics: Optional[Metrics] = None
265
+ session_state: Optional[Dict[str, Any]] = None
229
266
 
230
267
 
231
268
  @dataclass
@@ -263,6 +300,18 @@ class PreHookCompletedEvent(BaseTeamRunEvent):
263
300
  run_input: Optional[TeamRunInput] = None
264
301
 
265
302
 
303
+ @dataclass
304
+ class PostHookStartedEvent(BaseTeamRunEvent):
305
+ event: str = TeamRunEvent.post_hook_started.value
306
+ post_hook_name: Optional[str] = None
307
+
308
+
309
+ @dataclass
310
+ class PostHookCompletedEvent(BaseTeamRunEvent):
311
+ event: str = TeamRunEvent.post_hook_completed.value
312
+ post_hook_name: Optional[str] = None
313
+
314
+
266
315
  @dataclass
267
316
  class MemoryUpdateStartedEvent(BaseTeamRunEvent):
268
317
  event: str = TeamRunEvent.memory_update_started.value
@@ -273,6 +322,17 @@ class MemoryUpdateCompletedEvent(BaseTeamRunEvent):
273
322
  event: str = TeamRunEvent.memory_update_completed.value
274
323
 
275
324
 
325
+ @dataclass
326
+ class SessionSummaryStartedEvent(BaseTeamRunEvent):
327
+ event: str = TeamRunEvent.session_summary_started.value
328
+
329
+
330
+ @dataclass
331
+ class SessionSummaryCompletedEvent(BaseTeamRunEvent):
332
+ event: str = TeamRunEvent.session_summary_completed.value
333
+ session_summary: Optional[Any] = None
334
+
335
+
276
336
  @dataclass
277
337
  class ReasoningStartedEvent(BaseTeamRunEvent):
278
338
  event: str = TeamRunEvent.reasoning_started.value
@@ -333,11 +393,17 @@ class OutputModelResponseCompletedEvent(BaseTeamRunEvent):
333
393
  class CustomEvent(BaseTeamRunEvent):
334
394
  event: str = TeamRunEvent.custom_event.value
335
395
 
396
+ def __init__(self, **kwargs):
397
+ # Store arbitrary attributes directly on the instance
398
+ for key, value in kwargs.items():
399
+ setattr(self, key, value)
400
+
336
401
 
337
402
  TeamRunOutputEvent = Union[
338
403
  RunStartedEvent,
339
404
  RunContentEvent,
340
405
  IntermediateRunContentEvent,
406
+ RunContentCompletedEvent,
341
407
  RunCompletedEvent,
342
408
  RunErrorEvent,
343
409
  RunCancelledEvent,
@@ -348,6 +414,8 @@ TeamRunOutputEvent = Union[
348
414
  ReasoningCompletedEvent,
349
415
  MemoryUpdateStartedEvent,
350
416
  MemoryUpdateCompletedEvent,
417
+ SessionSummaryStartedEvent,
418
+ SessionSummaryCompletedEvent,
351
419
  ToolCallStartedEvent,
352
420
  ToolCallCompletedEvent,
353
421
  ParserModelResponseStartedEvent,
@@ -362,16 +430,21 @@ TEAM_RUN_EVENT_TYPE_REGISTRY = {
362
430
  TeamRunEvent.run_started.value: RunStartedEvent,
363
431
  TeamRunEvent.run_content.value: RunContentEvent,
364
432
  TeamRunEvent.run_intermediate_content.value: IntermediateRunContentEvent,
433
+ TeamRunEvent.run_content_completed.value: RunContentCompletedEvent,
365
434
  TeamRunEvent.run_completed.value: RunCompletedEvent,
366
435
  TeamRunEvent.run_error.value: RunErrorEvent,
367
436
  TeamRunEvent.run_cancelled.value: RunCancelledEvent,
368
437
  TeamRunEvent.pre_hook_started.value: PreHookStartedEvent,
369
438
  TeamRunEvent.pre_hook_completed.value: PreHookCompletedEvent,
439
+ TeamRunEvent.post_hook_started.value: PostHookStartedEvent,
440
+ TeamRunEvent.post_hook_completed.value: PostHookCompletedEvent,
370
441
  TeamRunEvent.reasoning_started.value: ReasoningStartedEvent,
371
442
  TeamRunEvent.reasoning_step.value: ReasoningStepEvent,
372
443
  TeamRunEvent.reasoning_completed.value: ReasoningCompletedEvent,
373
444
  TeamRunEvent.memory_update_started.value: MemoryUpdateStartedEvent,
374
445
  TeamRunEvent.memory_update_completed.value: MemoryUpdateCompletedEvent,
446
+ TeamRunEvent.session_summary_started.value: SessionSummaryStartedEvent,
447
+ TeamRunEvent.session_summary_completed.value: SessionSummaryCompletedEvent,
375
448
  TeamRunEvent.tool_call_started.value: ToolCallStartedEvent,
376
449
  TeamRunEvent.tool_call_completed.value: ToolCallCompletedEvent,
377
450
  TeamRunEvent.parser_model_response_started.value: ParserModelResponseStartedEvent,
@@ -397,8 +470,19 @@ def team_run_output_event_from_dict(data: dict) -> BaseTeamRunEvent:
397
470
  class TeamRunOutput:
398
471
  """Response returned by Team.run() functions"""
399
472
 
473
+ run_id: Optional[str] = None
474
+ team_id: Optional[str] = None
475
+ team_name: Optional[str] = None
476
+ session_id: Optional[str] = None
477
+ parent_run_id: Optional[str] = None
478
+ user_id: Optional[str] = None
479
+
480
+ # Input media and messages from user
481
+ input: Optional[TeamRunInput] = None
482
+
400
483
  content: Optional[Any] = None
401
484
  content_type: str = "str"
485
+
402
486
  messages: Optional[List[Message]] = None
403
487
  metrics: Optional[Metrics] = None
404
488
  model: Optional[str] = None
@@ -406,12 +490,6 @@ class TeamRunOutput:
406
490
 
407
491
  member_responses: List[Union["TeamRunOutput", RunOutput]] = field(default_factory=list)
408
492
 
409
- run_id: Optional[str] = None
410
- team_id: Optional[str] = None
411
- team_name: Optional[str] = None
412
- session_id: Optional[str] = None
413
- parent_run_id: Optional[str] = None
414
-
415
493
  tools: Optional[List[ToolExecution]] = None
416
494
 
417
495
  images: Optional[List[Image]] = None # Images from member runs
@@ -421,14 +499,12 @@ class TeamRunOutput:
421
499
 
422
500
  response_audio: Optional[Audio] = None # Model audio response
423
501
 
424
- # Input media and messages from user
425
- input: Optional[TeamRunInput] = None
426
-
427
502
  reasoning_content: Optional[str] = None
428
503
 
429
504
  citations: Optional[Citations] = None
430
505
  model_provider_data: Optional[Dict[str, Any]] = None
431
506
  metadata: Optional[Dict[str, Any]] = None
507
+ session_state: Optional[Dict[str, Any]] = None
432
508
 
433
509
  references: Optional[List[MessageReferences]] = None
434
510
  additional_input: Optional[List[Message]] = None
@@ -440,11 +516,20 @@ class TeamRunOutput:
440
516
 
441
517
  status: RunStatus = RunStatus.running
442
518
 
519
+ # User control flow (HITL) requirements to continue a run when paused, in order of arrival
520
+ requirements: Optional[list[RunRequirement]] = None
521
+
443
522
  # === FOREIGN KEY RELATIONSHIPS ===
444
523
  # These fields establish relationships to parent workflow/step structures
445
524
  # and should be treated as foreign keys for data integrity
446
525
  workflow_step_id: Optional[str] = None # FK: Points to StepOutput.step_id
447
526
 
527
+ @property
528
+ def active_requirements(self) -> list[RunRequirement]:
529
+ if not self.requirements:
530
+ return []
531
+ return [requirement for requirement in self.requirements if not requirement.is_resolved()]
532
+
448
533
  @property
449
534
  def is_paused(self):
450
535
  return self.status == RunStatus.paused
@@ -461,6 +546,7 @@ class TeamRunOutput:
461
546
  and k
462
547
  not in [
463
548
  "messages",
549
+ "metrics",
464
550
  "status",
465
551
  "tools",
466
552
  "metadata",
@@ -480,6 +566,9 @@ class TeamRunOutput:
480
566
  if self.events is not None:
481
567
  _dict["events"] = [e.to_dict() for e in self.events]
482
568
 
569
+ if self.metrics is not None:
570
+ _dict["metrics"] = self.metrics.to_dict() if isinstance(self.metrics, Metrics) else self.metrics
571
+
483
572
  if self.status is not None:
484
573
  _dict["status"] = self.status.value if isinstance(self.status, RunStatus) else self.status
485
574
 
@@ -598,23 +687,15 @@ class TeamRunOutput:
598
687
  if references is not None:
599
688
  references = [MessageReferences.model_validate(reference) for reference in references]
600
689
 
601
- images = data.pop("images", [])
602
- images = [Image.model_validate(image) for image in images] if images else None
603
-
604
- videos = data.pop("videos", [])
605
- videos = [Video.model_validate(video) for video in videos] if videos else None
606
-
607
- audio = data.pop("audio", [])
608
- audio = [Audio.model_validate(audio) for audio in audio] if audio else None
609
-
610
- files = data.pop("files", [])
611
- files = [File.model_validate(file) for file in files] if files else None
690
+ images = reconstruct_images(data.pop("images", []))
691
+ videos = reconstruct_videos(data.pop("videos", []))
692
+ audio = reconstruct_audio_list(data.pop("audio", []))
693
+ files = reconstruct_files(data.pop("files", []))
612
694
 
613
695
  tools = data.pop("tools", [])
614
696
  tools = [ToolExecution.from_dict(tool) for tool in tools] if tools else None
615
697
 
616
- response_audio = data.pop("response_audio", None)
617
- response_audio = Audio.model_validate(response_audio) if response_audio else None
698
+ response_audio = reconstruct_response_audio(data.pop("response_audio", None))
618
699
 
619
700
  input_data = data.pop("input", None)
620
701
  input_obj = None
@@ -628,6 +709,12 @@ class TeamRunOutput:
628
709
  citations = data.pop("citations", None)
629
710
  citations = Citations.model_validate(citations) if citations else None
630
711
 
712
+ # Filter data to only include fields that are actually defined in the TeamRunOutput dataclass
713
+ from dataclasses import fields
714
+
715
+ supported_fields = {f.name for f in fields(cls)}
716
+ filtered_data = {k: v for k, v in data.items() if k in supported_fields}
717
+
631
718
  return cls(
632
719
  messages=messages,
633
720
  metrics=metrics,
@@ -645,7 +732,7 @@ class TeamRunOutput:
645
732
  citations=citations,
646
733
  tools=tools,
647
734
  events=events,
648
- **data,
735
+ **filtered_data,
649
736
  )
650
737
 
651
738
  def get_content_as_string(self, **kwargs) -> str: