agno 2.2.13__py3-none-any.whl → 2.4.3__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 (383) hide show
  1. agno/agent/__init__.py +6 -0
  2. agno/agent/agent.py +5252 -3145
  3. agno/agent/remote.py +525 -0
  4. agno/api/api.py +2 -0
  5. agno/client/__init__.py +3 -0
  6. agno/client/a2a/__init__.py +10 -0
  7. agno/client/a2a/client.py +554 -0
  8. agno/client/a2a/schemas.py +112 -0
  9. agno/client/a2a/utils.py +369 -0
  10. agno/client/os.py +2669 -0
  11. agno/compression/__init__.py +3 -0
  12. agno/compression/manager.py +247 -0
  13. agno/culture/manager.py +2 -2
  14. agno/db/base.py +927 -6
  15. agno/db/dynamo/dynamo.py +788 -2
  16. agno/db/dynamo/schemas.py +128 -0
  17. agno/db/dynamo/utils.py +26 -3
  18. agno/db/firestore/firestore.py +674 -50
  19. agno/db/firestore/schemas.py +41 -0
  20. agno/db/firestore/utils.py +25 -10
  21. agno/db/gcs_json/gcs_json_db.py +506 -3
  22. agno/db/gcs_json/utils.py +14 -2
  23. agno/db/in_memory/in_memory_db.py +203 -4
  24. agno/db/in_memory/utils.py +14 -2
  25. agno/db/json/json_db.py +498 -2
  26. agno/db/json/utils.py +14 -2
  27. agno/db/migrations/manager.py +199 -0
  28. agno/db/migrations/utils.py +19 -0
  29. agno/db/migrations/v1_to_v2.py +54 -16
  30. agno/db/migrations/versions/__init__.py +0 -0
  31. agno/db/migrations/versions/v2_3_0.py +977 -0
  32. agno/db/mongo/async_mongo.py +1013 -39
  33. agno/db/mongo/mongo.py +684 -4
  34. agno/db/mongo/schemas.py +48 -0
  35. agno/db/mongo/utils.py +17 -0
  36. agno/db/mysql/__init__.py +2 -1
  37. agno/db/mysql/async_mysql.py +2958 -0
  38. agno/db/mysql/mysql.py +722 -53
  39. agno/db/mysql/schemas.py +77 -11
  40. agno/db/mysql/utils.py +151 -8
  41. agno/db/postgres/async_postgres.py +1254 -137
  42. agno/db/postgres/postgres.py +2316 -93
  43. agno/db/postgres/schemas.py +153 -21
  44. agno/db/postgres/utils.py +22 -7
  45. agno/db/redis/redis.py +531 -3
  46. agno/db/redis/schemas.py +36 -0
  47. agno/db/redis/utils.py +31 -15
  48. agno/db/schemas/evals.py +1 -0
  49. agno/db/schemas/memory.py +20 -9
  50. agno/db/singlestore/schemas.py +70 -1
  51. agno/db/singlestore/singlestore.py +737 -74
  52. agno/db/singlestore/utils.py +13 -3
  53. agno/db/sqlite/async_sqlite.py +1069 -89
  54. agno/db/sqlite/schemas.py +133 -1
  55. agno/db/sqlite/sqlite.py +2203 -165
  56. agno/db/sqlite/utils.py +21 -11
  57. agno/db/surrealdb/models.py +25 -0
  58. agno/db/surrealdb/surrealdb.py +603 -1
  59. agno/db/utils.py +60 -0
  60. agno/eval/__init__.py +26 -3
  61. agno/eval/accuracy.py +25 -12
  62. agno/eval/agent_as_judge.py +871 -0
  63. agno/eval/base.py +29 -0
  64. agno/eval/performance.py +10 -4
  65. agno/eval/reliability.py +22 -13
  66. agno/eval/utils.py +2 -1
  67. agno/exceptions.py +42 -0
  68. agno/hooks/__init__.py +3 -0
  69. agno/hooks/decorator.py +164 -0
  70. agno/integrations/discord/client.py +13 -2
  71. agno/knowledge/__init__.py +4 -0
  72. agno/knowledge/chunking/code.py +90 -0
  73. agno/knowledge/chunking/document.py +65 -4
  74. agno/knowledge/chunking/fixed.py +4 -1
  75. agno/knowledge/chunking/markdown.py +102 -11
  76. agno/knowledge/chunking/recursive.py +2 -2
  77. agno/knowledge/chunking/semantic.py +130 -48
  78. agno/knowledge/chunking/strategy.py +18 -0
  79. agno/knowledge/embedder/azure_openai.py +0 -1
  80. agno/knowledge/embedder/google.py +1 -1
  81. agno/knowledge/embedder/mistral.py +1 -1
  82. agno/knowledge/embedder/nebius.py +1 -1
  83. agno/knowledge/embedder/openai.py +16 -12
  84. agno/knowledge/filesystem.py +412 -0
  85. agno/knowledge/knowledge.py +4261 -1199
  86. agno/knowledge/protocol.py +134 -0
  87. agno/knowledge/reader/arxiv_reader.py +3 -2
  88. agno/knowledge/reader/base.py +9 -7
  89. agno/knowledge/reader/csv_reader.py +91 -42
  90. agno/knowledge/reader/docx_reader.py +9 -10
  91. agno/knowledge/reader/excel_reader.py +225 -0
  92. agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
  93. agno/knowledge/reader/firecrawl_reader.py +3 -2
  94. agno/knowledge/reader/json_reader.py +16 -22
  95. agno/knowledge/reader/markdown_reader.py +15 -14
  96. agno/knowledge/reader/pdf_reader.py +33 -28
  97. agno/knowledge/reader/pptx_reader.py +9 -10
  98. agno/knowledge/reader/reader_factory.py +135 -1
  99. agno/knowledge/reader/s3_reader.py +8 -16
  100. agno/knowledge/reader/tavily_reader.py +3 -3
  101. agno/knowledge/reader/text_reader.py +15 -14
  102. agno/knowledge/reader/utils/__init__.py +17 -0
  103. agno/knowledge/reader/utils/spreadsheet.py +114 -0
  104. agno/knowledge/reader/web_search_reader.py +8 -65
  105. agno/knowledge/reader/website_reader.py +16 -13
  106. agno/knowledge/reader/wikipedia_reader.py +36 -3
  107. agno/knowledge/reader/youtube_reader.py +3 -2
  108. agno/knowledge/remote_content/__init__.py +33 -0
  109. agno/knowledge/remote_content/config.py +266 -0
  110. agno/knowledge/remote_content/remote_content.py +105 -17
  111. agno/knowledge/utils.py +76 -22
  112. agno/learn/__init__.py +71 -0
  113. agno/learn/config.py +463 -0
  114. agno/learn/curate.py +185 -0
  115. agno/learn/machine.py +725 -0
  116. agno/learn/schemas.py +1114 -0
  117. agno/learn/stores/__init__.py +38 -0
  118. agno/learn/stores/decision_log.py +1156 -0
  119. agno/learn/stores/entity_memory.py +3275 -0
  120. agno/learn/stores/learned_knowledge.py +1583 -0
  121. agno/learn/stores/protocol.py +117 -0
  122. agno/learn/stores/session_context.py +1217 -0
  123. agno/learn/stores/user_memory.py +1495 -0
  124. agno/learn/stores/user_profile.py +1220 -0
  125. agno/learn/utils.py +209 -0
  126. agno/media.py +22 -6
  127. agno/memory/__init__.py +14 -1
  128. agno/memory/manager.py +223 -8
  129. agno/memory/strategies/__init__.py +15 -0
  130. agno/memory/strategies/base.py +66 -0
  131. agno/memory/strategies/summarize.py +196 -0
  132. agno/memory/strategies/types.py +37 -0
  133. agno/models/aimlapi/aimlapi.py +17 -0
  134. agno/models/anthropic/claude.py +434 -59
  135. agno/models/aws/bedrock.py +121 -20
  136. agno/models/aws/claude.py +131 -274
  137. agno/models/azure/ai_foundry.py +10 -6
  138. agno/models/azure/openai_chat.py +33 -10
  139. agno/models/base.py +1162 -561
  140. agno/models/cerebras/cerebras.py +120 -24
  141. agno/models/cerebras/cerebras_openai.py +21 -2
  142. agno/models/cohere/chat.py +65 -6
  143. agno/models/cometapi/cometapi.py +18 -1
  144. agno/models/dashscope/dashscope.py +2 -3
  145. agno/models/deepinfra/deepinfra.py +18 -1
  146. agno/models/deepseek/deepseek.py +69 -3
  147. agno/models/fireworks/fireworks.py +18 -1
  148. agno/models/google/gemini.py +959 -89
  149. agno/models/google/utils.py +22 -0
  150. agno/models/groq/groq.py +48 -18
  151. agno/models/huggingface/huggingface.py +17 -6
  152. agno/models/ibm/watsonx.py +16 -6
  153. agno/models/internlm/internlm.py +18 -1
  154. agno/models/langdb/langdb.py +13 -1
  155. agno/models/litellm/chat.py +88 -9
  156. agno/models/litellm/litellm_openai.py +18 -1
  157. agno/models/message.py +24 -5
  158. agno/models/meta/llama.py +40 -13
  159. agno/models/meta/llama_openai.py +22 -21
  160. agno/models/metrics.py +12 -0
  161. agno/models/mistral/mistral.py +8 -4
  162. agno/models/n1n/__init__.py +3 -0
  163. agno/models/n1n/n1n.py +57 -0
  164. agno/models/nebius/nebius.py +6 -7
  165. agno/models/nvidia/nvidia.py +20 -3
  166. agno/models/ollama/__init__.py +2 -0
  167. agno/models/ollama/chat.py +17 -6
  168. agno/models/ollama/responses.py +100 -0
  169. agno/models/openai/__init__.py +2 -0
  170. agno/models/openai/chat.py +117 -26
  171. agno/models/openai/open_responses.py +46 -0
  172. agno/models/openai/responses.py +110 -32
  173. agno/models/openrouter/__init__.py +2 -0
  174. agno/models/openrouter/openrouter.py +67 -2
  175. agno/models/openrouter/responses.py +146 -0
  176. agno/models/perplexity/perplexity.py +19 -1
  177. agno/models/portkey/portkey.py +7 -6
  178. agno/models/requesty/requesty.py +19 -2
  179. agno/models/response.py +20 -2
  180. agno/models/sambanova/sambanova.py +20 -3
  181. agno/models/siliconflow/siliconflow.py +19 -2
  182. agno/models/together/together.py +20 -3
  183. agno/models/vercel/v0.py +20 -3
  184. agno/models/vertexai/claude.py +124 -4
  185. agno/models/vllm/vllm.py +19 -14
  186. agno/models/xai/xai.py +19 -2
  187. agno/os/app.py +467 -137
  188. agno/os/auth.py +253 -5
  189. agno/os/config.py +22 -0
  190. agno/os/interfaces/a2a/a2a.py +7 -6
  191. agno/os/interfaces/a2a/router.py +635 -26
  192. agno/os/interfaces/a2a/utils.py +32 -33
  193. agno/os/interfaces/agui/agui.py +5 -3
  194. agno/os/interfaces/agui/router.py +26 -16
  195. agno/os/interfaces/agui/utils.py +97 -57
  196. agno/os/interfaces/base.py +7 -7
  197. agno/os/interfaces/slack/router.py +16 -7
  198. agno/os/interfaces/slack/slack.py +7 -7
  199. agno/os/interfaces/whatsapp/router.py +35 -7
  200. agno/os/interfaces/whatsapp/security.py +3 -1
  201. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  202. agno/os/managers.py +326 -0
  203. agno/os/mcp.py +652 -79
  204. agno/os/middleware/__init__.py +4 -0
  205. agno/os/middleware/jwt.py +718 -115
  206. agno/os/middleware/trailing_slash.py +27 -0
  207. agno/os/router.py +105 -1558
  208. agno/os/routers/agents/__init__.py +3 -0
  209. agno/os/routers/agents/router.py +655 -0
  210. agno/os/routers/agents/schema.py +288 -0
  211. agno/os/routers/components/__init__.py +3 -0
  212. agno/os/routers/components/components.py +475 -0
  213. agno/os/routers/database.py +155 -0
  214. agno/os/routers/evals/evals.py +111 -18
  215. agno/os/routers/evals/schemas.py +38 -5
  216. agno/os/routers/evals/utils.py +80 -11
  217. agno/os/routers/health.py +3 -3
  218. agno/os/routers/knowledge/knowledge.py +284 -35
  219. agno/os/routers/knowledge/schemas.py +14 -2
  220. agno/os/routers/memory/memory.py +274 -11
  221. agno/os/routers/memory/schemas.py +44 -3
  222. agno/os/routers/metrics/metrics.py +30 -15
  223. agno/os/routers/metrics/schemas.py +10 -6
  224. agno/os/routers/registry/__init__.py +3 -0
  225. agno/os/routers/registry/registry.py +337 -0
  226. agno/os/routers/session/session.py +143 -14
  227. agno/os/routers/teams/__init__.py +3 -0
  228. agno/os/routers/teams/router.py +550 -0
  229. agno/os/routers/teams/schema.py +280 -0
  230. agno/os/routers/traces/__init__.py +3 -0
  231. agno/os/routers/traces/schemas.py +414 -0
  232. agno/os/routers/traces/traces.py +549 -0
  233. agno/os/routers/workflows/__init__.py +3 -0
  234. agno/os/routers/workflows/router.py +757 -0
  235. agno/os/routers/workflows/schema.py +139 -0
  236. agno/os/schema.py +157 -584
  237. agno/os/scopes.py +469 -0
  238. agno/os/settings.py +3 -0
  239. agno/os/utils.py +574 -185
  240. agno/reasoning/anthropic.py +85 -1
  241. agno/reasoning/azure_ai_foundry.py +93 -1
  242. agno/reasoning/deepseek.py +102 -2
  243. agno/reasoning/default.py +6 -7
  244. agno/reasoning/gemini.py +87 -3
  245. agno/reasoning/groq.py +109 -2
  246. agno/reasoning/helpers.py +6 -7
  247. agno/reasoning/manager.py +1238 -0
  248. agno/reasoning/ollama.py +93 -1
  249. agno/reasoning/openai.py +115 -1
  250. agno/reasoning/vertexai.py +85 -1
  251. agno/registry/__init__.py +3 -0
  252. agno/registry/registry.py +68 -0
  253. agno/remote/__init__.py +3 -0
  254. agno/remote/base.py +581 -0
  255. agno/run/__init__.py +2 -4
  256. agno/run/agent.py +134 -19
  257. agno/run/base.py +49 -1
  258. agno/run/cancel.py +65 -52
  259. agno/run/cancellation_management/__init__.py +9 -0
  260. agno/run/cancellation_management/base.py +78 -0
  261. agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
  262. agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
  263. agno/run/requirement.py +181 -0
  264. agno/run/team.py +111 -19
  265. agno/run/workflow.py +2 -1
  266. agno/session/agent.py +57 -92
  267. agno/session/summary.py +1 -1
  268. agno/session/team.py +62 -115
  269. agno/session/workflow.py +353 -57
  270. agno/skills/__init__.py +17 -0
  271. agno/skills/agent_skills.py +377 -0
  272. agno/skills/errors.py +32 -0
  273. agno/skills/loaders/__init__.py +4 -0
  274. agno/skills/loaders/base.py +27 -0
  275. agno/skills/loaders/local.py +216 -0
  276. agno/skills/skill.py +65 -0
  277. agno/skills/utils.py +107 -0
  278. agno/skills/validator.py +277 -0
  279. agno/table.py +10 -0
  280. agno/team/__init__.py +5 -1
  281. agno/team/remote.py +447 -0
  282. agno/team/team.py +3769 -2202
  283. agno/tools/brandfetch.py +27 -18
  284. agno/tools/browserbase.py +225 -16
  285. agno/tools/crawl4ai.py +3 -0
  286. agno/tools/duckduckgo.py +25 -71
  287. agno/tools/exa.py +0 -21
  288. agno/tools/file.py +14 -13
  289. agno/tools/file_generation.py +12 -6
  290. agno/tools/firecrawl.py +15 -7
  291. agno/tools/function.py +94 -113
  292. agno/tools/google_bigquery.py +11 -2
  293. agno/tools/google_drive.py +4 -3
  294. agno/tools/knowledge.py +9 -4
  295. agno/tools/mcp/mcp.py +301 -18
  296. agno/tools/mcp/multi_mcp.py +269 -14
  297. agno/tools/mem0.py +11 -10
  298. agno/tools/memory.py +47 -46
  299. agno/tools/mlx_transcribe.py +10 -7
  300. agno/tools/models/nebius.py +5 -5
  301. agno/tools/models_labs.py +20 -10
  302. agno/tools/nano_banana.py +151 -0
  303. agno/tools/parallel.py +0 -7
  304. agno/tools/postgres.py +76 -36
  305. agno/tools/python.py +14 -6
  306. agno/tools/reasoning.py +30 -23
  307. agno/tools/redshift.py +406 -0
  308. agno/tools/shopify.py +1519 -0
  309. agno/tools/spotify.py +919 -0
  310. agno/tools/tavily.py +4 -1
  311. agno/tools/toolkit.py +253 -18
  312. agno/tools/websearch.py +93 -0
  313. agno/tools/website.py +1 -1
  314. agno/tools/wikipedia.py +1 -1
  315. agno/tools/workflow.py +56 -48
  316. agno/tools/yfinance.py +12 -11
  317. agno/tracing/__init__.py +12 -0
  318. agno/tracing/exporter.py +161 -0
  319. agno/tracing/schemas.py +276 -0
  320. agno/tracing/setup.py +112 -0
  321. agno/utils/agent.py +251 -10
  322. agno/utils/cryptography.py +22 -0
  323. agno/utils/dttm.py +33 -0
  324. agno/utils/events.py +264 -7
  325. agno/utils/hooks.py +111 -3
  326. agno/utils/http.py +161 -2
  327. agno/utils/mcp.py +49 -8
  328. agno/utils/media.py +22 -1
  329. agno/utils/models/ai_foundry.py +9 -2
  330. agno/utils/models/claude.py +20 -5
  331. agno/utils/models/cohere.py +9 -2
  332. agno/utils/models/llama.py +9 -2
  333. agno/utils/models/mistral.py +4 -2
  334. agno/utils/os.py +0 -0
  335. agno/utils/print_response/agent.py +99 -16
  336. agno/utils/print_response/team.py +223 -24
  337. agno/utils/print_response/workflow.py +0 -2
  338. agno/utils/prompts.py +8 -6
  339. agno/utils/remote.py +23 -0
  340. agno/utils/response.py +1 -13
  341. agno/utils/string.py +91 -2
  342. agno/utils/team.py +62 -12
  343. agno/utils/tokens.py +657 -0
  344. agno/vectordb/base.py +15 -2
  345. agno/vectordb/cassandra/cassandra.py +1 -1
  346. agno/vectordb/chroma/__init__.py +2 -1
  347. agno/vectordb/chroma/chromadb.py +468 -23
  348. agno/vectordb/clickhouse/clickhousedb.py +1 -1
  349. agno/vectordb/couchbase/couchbase.py +6 -2
  350. agno/vectordb/lancedb/lance_db.py +7 -38
  351. agno/vectordb/lightrag/lightrag.py +7 -6
  352. agno/vectordb/milvus/milvus.py +118 -84
  353. agno/vectordb/mongodb/__init__.py +2 -1
  354. agno/vectordb/mongodb/mongodb.py +14 -31
  355. agno/vectordb/pgvector/pgvector.py +120 -66
  356. agno/vectordb/pineconedb/pineconedb.py +2 -19
  357. agno/vectordb/qdrant/__init__.py +2 -1
  358. agno/vectordb/qdrant/qdrant.py +33 -56
  359. agno/vectordb/redis/__init__.py +2 -1
  360. agno/vectordb/redis/redisdb.py +19 -31
  361. agno/vectordb/singlestore/singlestore.py +17 -9
  362. agno/vectordb/surrealdb/surrealdb.py +2 -38
  363. agno/vectordb/weaviate/__init__.py +2 -1
  364. agno/vectordb/weaviate/weaviate.py +7 -3
  365. agno/workflow/__init__.py +5 -1
  366. agno/workflow/agent.py +2 -2
  367. agno/workflow/condition.py +12 -10
  368. agno/workflow/loop.py +28 -9
  369. agno/workflow/parallel.py +21 -13
  370. agno/workflow/remote.py +362 -0
  371. agno/workflow/router.py +12 -9
  372. agno/workflow/step.py +261 -36
  373. agno/workflow/steps.py +12 -8
  374. agno/workflow/types.py +40 -77
  375. agno/workflow/workflow.py +939 -213
  376. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
  377. agno-2.4.3.dist-info/RECORD +677 -0
  378. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
  379. agno/tools/googlesearch.py +0 -98
  380. agno/tools/memori.py +0 -339
  381. agno-2.2.13.dist-info/RECORD +0 -575
  382. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
  383. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
agno/run/agent.py CHANGED
@@ -11,6 +11,7 @@ from agno.models.metrics import Metrics
11
11
  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
+ from agno.run.requirement import RunRequirement
14
15
  from agno.utils.log import logger
15
16
  from agno.utils.media import (
16
17
  reconstruct_audio_list,
@@ -54,8 +55,11 @@ class RunInput:
54
55
  return self.input_content.model_dump_json(exclude_none=True)
55
56
  elif isinstance(self.input_content, Message):
56
57
  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])
58
+ elif isinstance(self.input_content, list):
59
+ try:
60
+ return json.dumps(self.to_dict().get("input_content"))
61
+ except Exception:
62
+ return str(self.input_content)
59
63
  else:
60
64
  return str(self.input_content)
61
65
 
@@ -70,22 +74,15 @@ class RunInput:
70
74
  result["input_content"] = self.input_content.model_dump(exclude_none=True)
71
75
  elif isinstance(self.input_content, Message):
72
76
  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):
77
+ elif isinstance(self.input_content, list):
78
+ serialized_items: List[Any] = []
79
+ for item in self.input_content:
80
+ if isinstance(item, Message):
81
+ serialized_items.append(item.to_dict())
82
+ elif isinstance(item, BaseModel):
83
+ serialized_items.append(item.model_dump(exclude_none=True))
84
+ elif isinstance(item, dict):
85
+ content = dict(item)
89
86
  if content.get("images"):
90
87
  content["images"] = [
91
88
  img.to_dict() if isinstance(img, Image) else img for img in content["images"]
@@ -102,7 +99,11 @@ class RunInput:
102
99
  content["files"] = [
103
100
  file.to_dict() if isinstance(file, File) else file for file in content["files"]
104
101
  ]
105
- result["input_content"] = self.input_content
102
+ serialized_items.append(content)
103
+ else:
104
+ serialized_items.append(item)
105
+
106
+ result["input_content"] = serialized_items
106
107
  else:
107
108
  result["input_content"] = self.input_content
108
109
 
@@ -152,9 +153,11 @@ class RunEvent(str, Enum):
152
153
 
153
154
  tool_call_started = "ToolCallStarted"
154
155
  tool_call_completed = "ToolCallCompleted"
156
+ tool_call_error = "ToolCallError"
155
157
 
156
158
  reasoning_started = "ReasoningStarted"
157
159
  reasoning_step = "ReasoningStep"
160
+ reasoning_content_delta = "ReasoningContentDelta"
158
161
  reasoning_completed = "ReasoningCompleted"
159
162
 
160
163
  memory_update_started = "MemoryUpdateStarted"
@@ -169,6 +172,12 @@ class RunEvent(str, Enum):
169
172
  output_model_response_started = "OutputModelResponseStarted"
170
173
  output_model_response_completed = "OutputModelResponseCompleted"
171
174
 
175
+ model_request_started = "ModelRequestStarted"
176
+ model_request_completed = "ModelRequestCompleted"
177
+
178
+ compression_started = "CompressionStarted"
179
+ compression_completed = "CompressionCompleted"
180
+
172
181
  custom_event = "CustomEvent"
173
182
 
174
183
 
@@ -273,11 +282,18 @@ class RunCompletedEvent(BaseAgentRunEvent):
273
282
  class RunPausedEvent(BaseAgentRunEvent):
274
283
  event: str = RunEvent.run_paused.value
275
284
  tools: Optional[List[ToolExecution]] = None
285
+ requirements: Optional[List[RunRequirement]] = None
276
286
 
277
287
  @property
278
288
  def is_paused(self):
279
289
  return True
280
290
 
291
+ @property
292
+ def active_requirements(self) -> List[RunRequirement]:
293
+ if not self.requirements:
294
+ return []
295
+ return [requirement for requirement in self.requirements if not requirement.is_resolved()]
296
+
281
297
 
282
298
  @dataclass
283
299
  class RunContinuedEvent(BaseAgentRunEvent):
@@ -339,6 +355,7 @@ class MemoryUpdateStartedEvent(BaseAgentRunEvent):
339
355
  @dataclass
340
356
  class MemoryUpdateCompletedEvent(BaseAgentRunEvent):
341
357
  event: str = RunEvent.memory_update_completed.value
358
+ memories: Optional[List[Any]] = None
342
359
 
343
360
 
344
361
  @dataclass
@@ -365,6 +382,14 @@ class ReasoningStepEvent(BaseAgentRunEvent):
365
382
  reasoning_content: str = ""
366
383
 
367
384
 
385
+ @dataclass
386
+ class ReasoningContentDeltaEvent(BaseAgentRunEvent):
387
+ """Event for streaming reasoning content chunks as they arrive."""
388
+
389
+ event: str = RunEvent.reasoning_content_delta.value
390
+ reasoning_content: str = "" # The delta/chunk of reasoning content
391
+
392
+
368
393
  @dataclass
369
394
  class ReasoningCompletedEvent(BaseAgentRunEvent):
370
395
  event: str = RunEvent.reasoning_completed.value
@@ -388,6 +413,13 @@ class ToolCallCompletedEvent(BaseAgentRunEvent):
388
413
  audio: Optional[List[Audio]] = None # Audio produced by the tool call
389
414
 
390
415
 
416
+ @dataclass
417
+ class ToolCallErrorEvent(BaseAgentRunEvent):
418
+ event: str = RunEvent.tool_call_error.value
419
+ tool: Optional[ToolExecution] = None
420
+ error: Optional[str] = None
421
+
422
+
391
423
  @dataclass
392
424
  class ParserModelResponseStartedEvent(BaseAgentRunEvent):
393
425
  event: str = RunEvent.parser_model_response_started.value
@@ -408,9 +440,53 @@ class OutputModelResponseCompletedEvent(BaseAgentRunEvent):
408
440
  event: str = RunEvent.output_model_response_completed.value
409
441
 
410
442
 
443
+ @dataclass
444
+ class ModelRequestStartedEvent(BaseAgentRunEvent):
445
+ """Event sent when a model request is about to be made"""
446
+
447
+ event: str = RunEvent.model_request_started.value
448
+ model: Optional[str] = None
449
+ model_provider: Optional[str] = None
450
+
451
+
452
+ @dataclass
453
+ class ModelRequestCompletedEvent(BaseAgentRunEvent):
454
+ """Event sent when a model request has completed"""
455
+
456
+ event: str = RunEvent.model_request_completed.value
457
+ model: Optional[str] = None
458
+ model_provider: Optional[str] = None
459
+ input_tokens: Optional[int] = None
460
+ output_tokens: Optional[int] = None
461
+ total_tokens: Optional[int] = None
462
+ time_to_first_token: Optional[float] = None
463
+ reasoning_tokens: Optional[int] = None
464
+ cache_read_tokens: Optional[int] = None
465
+ cache_write_tokens: Optional[int] = None
466
+
467
+
468
+ @dataclass
469
+ class CompressionStartedEvent(BaseAgentRunEvent):
470
+ """Event sent when tool result compression is about to start"""
471
+
472
+ event: str = RunEvent.compression_started.value
473
+
474
+
475
+ @dataclass
476
+ class CompressionCompletedEvent(BaseAgentRunEvent):
477
+ """Event sent when tool result compression has completed"""
478
+
479
+ event: str = RunEvent.compression_completed.value
480
+ tool_results_compressed: Optional[int] = None
481
+ original_size: Optional[int] = None
482
+ compressed_size: Optional[int] = None
483
+
484
+
411
485
  @dataclass
412
486
  class CustomEvent(BaseAgentRunEvent):
413
487
  event: str = RunEvent.custom_event.value
488
+ # tool_call_id for ToolExecution
489
+ tool_call_id: Optional[str] = None
414
490
 
415
491
  def __init__(self, **kwargs):
416
492
  # Store arbitrary attributes directly on the instance
@@ -434,6 +510,7 @@ RunOutputEvent = Union[
434
510
  PostHookCompletedEvent,
435
511
  ReasoningStartedEvent,
436
512
  ReasoningStepEvent,
513
+ ReasoningContentDeltaEvent,
437
514
  ReasoningCompletedEvent,
438
515
  MemoryUpdateStartedEvent,
439
516
  MemoryUpdateCompletedEvent,
@@ -441,10 +518,15 @@ RunOutputEvent = Union[
441
518
  SessionSummaryCompletedEvent,
442
519
  ToolCallStartedEvent,
443
520
  ToolCallCompletedEvent,
521
+ ToolCallErrorEvent,
444
522
  ParserModelResponseStartedEvent,
445
523
  ParserModelResponseCompletedEvent,
446
524
  OutputModelResponseStartedEvent,
447
525
  OutputModelResponseCompletedEvent,
526
+ ModelRequestStartedEvent,
527
+ ModelRequestCompletedEvent,
528
+ CompressionStartedEvent,
529
+ CompressionCompletedEvent,
448
530
  CustomEvent,
449
531
  ]
450
532
 
@@ -466,6 +548,7 @@ RUN_EVENT_TYPE_REGISTRY = {
466
548
  RunEvent.post_hook_completed.value: PostHookCompletedEvent,
467
549
  RunEvent.reasoning_started.value: ReasoningStartedEvent,
468
550
  RunEvent.reasoning_step.value: ReasoningStepEvent,
551
+ RunEvent.reasoning_content_delta.value: ReasoningContentDeltaEvent,
469
552
  RunEvent.reasoning_completed.value: ReasoningCompletedEvent,
470
553
  RunEvent.memory_update_started.value: MemoryUpdateStartedEvent,
471
554
  RunEvent.memory_update_completed.value: MemoryUpdateCompletedEvent,
@@ -473,10 +556,15 @@ RUN_EVENT_TYPE_REGISTRY = {
473
556
  RunEvent.session_summary_completed.value: SessionSummaryCompletedEvent,
474
557
  RunEvent.tool_call_started.value: ToolCallStartedEvent,
475
558
  RunEvent.tool_call_completed.value: ToolCallCompletedEvent,
559
+ RunEvent.tool_call_error.value: ToolCallErrorEvent,
476
560
  RunEvent.parser_model_response_started.value: ParserModelResponseStartedEvent,
477
561
  RunEvent.parser_model_response_completed.value: ParserModelResponseCompletedEvent,
478
562
  RunEvent.output_model_response_started.value: OutputModelResponseStartedEvent,
479
563
  RunEvent.output_model_response_completed.value: OutputModelResponseCompletedEvent,
564
+ RunEvent.model_request_started.value: ModelRequestStartedEvent,
565
+ RunEvent.model_request_completed.value: ModelRequestCompletedEvent,
566
+ RunEvent.compression_started.value: CompressionStartedEvent,
567
+ RunEvent.compression_completed.value: CompressionCompletedEvent,
480
568
  RunEvent.custom_event.value: CustomEvent,
481
569
  }
482
570
 
@@ -539,11 +627,20 @@ class RunOutput:
539
627
 
540
628
  status: RunStatus = RunStatus.running
541
629
 
630
+ # User control flow (HITL) requirements to continue a run when paused, in order of arrival
631
+ requirements: Optional[list[RunRequirement]] = None
632
+
542
633
  # === FOREIGN KEY RELATIONSHIPS ===
543
634
  # These fields establish relationships to parent workflow/step structures
544
635
  # and should be treated as foreign keys for data integrity
545
636
  workflow_step_id: Optional[str] = None # FK: Points to StepOutput.step_id
546
637
 
638
+ @property
639
+ def active_requirements(self) -> list[RunRequirement]:
640
+ if not self.requirements:
641
+ return []
642
+ return [requirement for requirement in self.requirements if not requirement.is_resolved()]
643
+
547
644
  @property
548
645
  def is_paused(self):
549
646
  return self.status == RunStatus.paused
@@ -572,6 +669,7 @@ class RunOutput:
572
669
  and k
573
670
  not in [
574
671
  "messages",
672
+ "metrics",
575
673
  "tools",
576
674
  "metadata",
577
675
  "images",
@@ -586,6 +684,7 @@ class RunOutput:
586
684
  "reasoning_steps",
587
685
  "reasoning_messages",
588
686
  "references",
687
+ "requirements",
589
688
  ]
590
689
  }
591
690
 
@@ -671,6 +770,9 @@ class RunOutput:
671
770
  else:
672
771
  _dict["tools"].append(tool)
673
772
 
773
+ if self.requirements is not None:
774
+ _dict["requirements"] = [req.to_dict() if hasattr(req, "to_dict") else req for req in self.requirements]
775
+
674
776
  if self.input is not None:
675
777
  _dict["input"] = self.input.to_dict()
676
778
 
@@ -717,6 +819,18 @@ class RunOutput:
717
819
  tools = data.pop("tools", [])
718
820
  tools = [ToolExecution.from_dict(tool) for tool in tools] if tools else None
719
821
 
822
+ # Handle requirements
823
+ requirements_data = data.pop("requirements", None)
824
+ requirements: Optional[List[RunRequirement]] = None
825
+ if requirements_data is not None:
826
+ requirements_list: List[RunRequirement] = []
827
+ for item in requirements_data:
828
+ if isinstance(item, RunRequirement):
829
+ requirements_list.append(item)
830
+ elif isinstance(item, dict):
831
+ requirements_list.append(RunRequirement.from_dict(item))
832
+ requirements = requirements_list if requirements_list else None
833
+
720
834
  images = reconstruct_images(data.pop("images", []))
721
835
  videos = reconstruct_videos(data.pop("videos", []))
722
836
  audio = reconstruct_audio_list(data.pop("audio", []))
@@ -771,6 +885,7 @@ class RunOutput:
771
885
  reasoning_steps=reasoning_steps,
772
886
  reasoning_messages=reasoning_messages,
773
887
  references=references,
888
+ requirements=requirements,
774
889
  **filtered_data,
775
890
  )
776
891
 
agno/run/base.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from dataclasses import asdict, dataclass
2
2
  from enum import Enum
3
- from typing import Any, Dict, List, Optional, Union
3
+ from typing import Any, Dict, List, Optional, Type, Union
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
@@ -18,10 +18,14 @@ class RunContext:
18
18
  session_id: str
19
19
  user_id: Optional[str] = None
20
20
 
21
+ workflow_id: Optional[str] = None
22
+ workflow_name: Optional[str] = None
23
+
21
24
  dependencies: Optional[Dict[str, Any]] = None
22
25
  knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
23
26
  metadata: Optional[Dict[str, Any]] = None
24
27
  session_state: Optional[Dict[str, Any]] = None
28
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None
25
29
 
26
30
 
27
31
  @dataclass
@@ -49,6 +53,9 @@ class BaseRunOutputEvent:
49
53
  "additional_input",
50
54
  "session_summary",
51
55
  "metrics",
56
+ "run_input",
57
+ "requirements",
58
+ "memories",
52
59
  ]
53
60
  }
54
61
 
@@ -133,6 +140,15 @@ class BaseRunOutputEvent:
133
140
  if hasattr(self, "session_summary") and self.session_summary is not None:
134
141
  _dict["session_summary"] = self.session_summary.to_dict()
135
142
 
143
+ if hasattr(self, "run_input") and self.run_input is not None:
144
+ _dict["run_input"] = self.run_input.to_dict()
145
+
146
+ if hasattr(self, "requirements") and self.requirements is not None:
147
+ _dict["requirements"] = [req.to_dict() if hasattr(req, "to_dict") else req for req in self.requirements]
148
+
149
+ if hasattr(self, "memories") and self.memories is not None:
150
+ _dict["memories"] = [mem.to_dict() if hasattr(mem, "to_dict") else mem for mem in self.memories]
151
+
136
152
  return _dict
137
153
 
138
154
  def to_json(self, separators=(", ", ": "), indent: Optional[int] = 2) -> str:
@@ -201,7 +217,39 @@ class BaseRunOutputEvent:
201
217
 
202
218
  data["session_summary"] = SessionSummary.from_dict(session_summary)
203
219
 
220
+ run_input = data.pop("run_input", None)
221
+ if run_input:
222
+ from agno.run.team import BaseTeamRunEvent
223
+
224
+ if issubclass(cls, BaseTeamRunEvent):
225
+ from agno.run.team import TeamRunInput
226
+
227
+ data["run_input"] = TeamRunInput.from_dict(run_input)
228
+ else:
229
+ from agno.run.agent import RunInput
230
+
231
+ data["run_input"] = RunInput.from_dict(run_input)
232
+
233
+ # Handle requirements
234
+
235
+ # Handle requirements
236
+ requirements_data = data.pop("requirements", None)
237
+ if requirements_data is not None:
238
+ from agno.run.requirement import RunRequirement
239
+
240
+ requirements_list: List[RunRequirement] = []
241
+ for item in requirements_data:
242
+ if isinstance(item, RunRequirement):
243
+ requirements_list.append(item)
244
+ elif isinstance(item, dict):
245
+ requirements_list.append(RunRequirement.from_dict(item))
246
+ data["requirements"] = requirements_list if requirements_list else None
247
+
204
248
  # Filter data to only include fields that are actually defined in the target class
249
+ # CustomEvent accepts arbitrary fields, so skip filtering for it
250
+ if cls.__name__ == "CustomEvent":
251
+ return cls(**data)
252
+
205
253
  from dataclasses import fields
206
254
 
207
255
  supported_fields = {f.name for f in fields(cls)}
agno/run/cancel.py CHANGED
@@ -1,64 +1,37 @@
1
1
  """Run cancellation management."""
2
2
 
3
- import threading
4
3
  from typing import Dict
5
4
 
6
- from agno.exceptions import RunCancelledException
5
+ from agno.run.cancellation_management.base import BaseRunCancellationManager
6
+ from agno.run.cancellation_management.in_memory_cancellation_manager import InMemoryRunCancellationManager
7
7
  from agno.utils.log import logger
8
8
 
9
+ # Global cancellation manager instance
10
+ _cancellation_manager: BaseRunCancellationManager = InMemoryRunCancellationManager()
9
11
 
10
- class RunCancellationManager:
11
- """Manages cancellation state for agent runs."""
12
-
13
- def __init__(self):
14
- self._cancelled_runs: Dict[str, bool] = {}
15
- self._lock = threading.Lock()
16
-
17
- def register_run(self, run_id: str) -> None:
18
- """Register a new run as not cancelled."""
19
- with self._lock:
20
- self._cancelled_runs[run_id] = False
21
-
22
- def cancel_run(self, run_id: str) -> bool:
23
- """Cancel a run by marking it as cancelled.
24
-
25
- Returns:
26
- bool: True if run was found and cancelled, False if run not found.
27
- """
28
- with self._lock:
29
- if run_id in self._cancelled_runs:
30
- self._cancelled_runs[run_id] = True
31
- logger.info(f"Run {run_id} marked for cancellation")
32
- return True
33
- else:
34
- logger.warning(f"Attempted to cancel unknown run {run_id}")
35
- return False
36
-
37
- def is_cancelled(self, run_id: str) -> bool:
38
- """Check if a run is cancelled."""
39
- with self._lock:
40
- return self._cancelled_runs.get(run_id, False)
41
-
42
- def cleanup_run(self, run_id: str) -> None:
43
- """Remove a run from tracking (called when run completes)."""
44
- with self._lock:
45
- if run_id in self._cancelled_runs:
46
- del self._cancelled_runs[run_id]
47
-
48
- def raise_if_cancelled(self, run_id: str) -> None:
49
- """Check if a run should be cancelled and raise exception if so."""
50
- if self.is_cancelled(run_id):
51
- logger.info(f"Cancelling run {run_id}")
52
- raise RunCancelledException(f"Run {run_id} was cancelled")
53
-
54
- def get_active_runs(self) -> Dict[str, bool]:
55
- """Get all currently tracked runs and their cancellation status."""
56
- with self._lock:
57
- return self._cancelled_runs.copy()
58
12
 
13
+ def set_cancellation_manager(manager: BaseRunCancellationManager) -> None:
14
+ """Set a custom cancellation manager.
59
15
 
60
- # Global cancellation manager instance
61
- _cancellation_manager = RunCancellationManager()
16
+ Args:
17
+ manager: A BaseRunCancellationManager instance or subclass.
18
+
19
+ Example:
20
+ ```python
21
+ class MyCustomManager(BaseRunCancellationManager):
22
+ ....
23
+
24
+ set_cancellation_manager(MyCustomManager())
25
+ ```
26
+ """
27
+ global _cancellation_manager
28
+ _cancellation_manager = manager
29
+ logger.info(f"Cancellation manager set to {type(manager).__name__}")
30
+
31
+
32
+ def get_cancellation_manager() -> BaseRunCancellationManager:
33
+ """Get the current cancellation manager instance."""
34
+ return _cancellation_manager
62
35
 
63
36
 
64
37
  def register_run(run_id: str) -> None:
@@ -66,16 +39,56 @@ def register_run(run_id: str) -> None:
66
39
  _cancellation_manager.register_run(run_id)
67
40
 
68
41
 
42
+ async def aregister_run(run_id: str) -> None:
43
+ """Register a new run for cancellation tracking (async version)."""
44
+ await _cancellation_manager.aregister_run(run_id)
45
+
46
+
69
47
  def cancel_run(run_id: str) -> bool:
70
48
  """Cancel a run."""
71
49
  return _cancellation_manager.cancel_run(run_id)
72
50
 
73
51
 
52
+ async def acancel_run(run_id: str) -> bool:
53
+ """Cancel a run (async version)."""
54
+ return await _cancellation_manager.acancel_run(run_id)
55
+
56
+
57
+ def is_cancelled(run_id: str) -> bool:
58
+ """Check if a run is cancelled."""
59
+ return _cancellation_manager.is_cancelled(run_id)
60
+
61
+
62
+ async def ais_cancelled(run_id: str) -> bool:
63
+ """Check if a run is cancelled (async version)."""
64
+ return await _cancellation_manager.ais_cancelled(run_id)
65
+
66
+
74
67
  def cleanup_run(run_id: str) -> None:
75
68
  """Clean up cancellation tracking for a completed run."""
76
69
  _cancellation_manager.cleanup_run(run_id)
77
70
 
78
71
 
72
+ async def acleanup_run(run_id: str) -> None:
73
+ """Clean up cancellation tracking for a completed run (async version)."""
74
+ await _cancellation_manager.acleanup_run(run_id)
75
+
76
+
79
77
  def raise_if_cancelled(run_id: str) -> None:
80
78
  """Check if a run should be cancelled and raise exception if so."""
81
79
  _cancellation_manager.raise_if_cancelled(run_id)
80
+
81
+
82
+ async def araise_if_cancelled(run_id: str) -> None:
83
+ """Check if a run should be cancelled and raise exception if so (async version)."""
84
+ await _cancellation_manager.araise_if_cancelled(run_id)
85
+
86
+
87
+ def get_active_runs() -> Dict[str, bool]:
88
+ """Get all currently tracked runs and their cancellation status."""
89
+ return _cancellation_manager.get_active_runs()
90
+
91
+
92
+ async def aget_active_runs() -> Dict[str, bool]:
93
+ """Get all currently tracked runs and their cancellation status (async version)."""
94
+ return await _cancellation_manager.aget_active_runs()
@@ -0,0 +1,9 @@
1
+ from agno.run.cancellation_management.base import BaseRunCancellationManager
2
+ from agno.run.cancellation_management.in_memory_cancellation_manager import InMemoryRunCancellationManager
3
+ from agno.run.cancellation_management.redis_cancellation_manager import RedisRunCancellationManager
4
+
5
+ __all__ = [
6
+ "BaseRunCancellationManager",
7
+ "InMemoryRunCancellationManager",
8
+ "RedisRunCancellationManager",
9
+ ]
@@ -0,0 +1,78 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Dict
3
+
4
+
5
+ class BaseRunCancellationManager(ABC):
6
+ """Manages cancellation state for agent runs.
7
+
8
+ This class can be extended to implement custom cancellation logic.
9
+ Use set_cancellation_manager() to replace the global instance with your own.
10
+ """
11
+
12
+ @abstractmethod
13
+ def register_run(self, run_id: str) -> None:
14
+ """Register a new run as not cancelled."""
15
+ pass
16
+
17
+ @abstractmethod
18
+ async def aregister_run(self, run_id: str) -> None:
19
+ """Register a new run as not cancelled (async version)."""
20
+ pass
21
+
22
+ @abstractmethod
23
+ def cancel_run(self, run_id: str) -> bool:
24
+ """Cancel a run by marking it as cancelled.
25
+
26
+ Returns:
27
+ bool: True if run was found and cancelled, False if run not found.
28
+ """
29
+ pass
30
+
31
+ @abstractmethod
32
+ async def acancel_run(self, run_id: str) -> bool:
33
+ """Cancel a run by marking it as cancelled (async version).
34
+
35
+ Returns:
36
+ bool: True if run was found and cancelled, False if run not found.
37
+ """
38
+ pass
39
+
40
+ @abstractmethod
41
+ def is_cancelled(self, run_id: str) -> bool:
42
+ """Check if a run is cancelled."""
43
+ pass
44
+
45
+ @abstractmethod
46
+ async def ais_cancelled(self, run_id: str) -> bool:
47
+ """Check if a run is cancelled (async version)."""
48
+ pass
49
+
50
+ @abstractmethod
51
+ def cleanup_run(self, run_id: str) -> None:
52
+ """Remove a run from tracking (called when run completes)."""
53
+ pass
54
+
55
+ @abstractmethod
56
+ async def acleanup_run(self, run_id: str) -> None:
57
+ """Remove a run from tracking (called when run completes) (async version)."""
58
+ pass
59
+
60
+ @abstractmethod
61
+ def raise_if_cancelled(self, run_id: str) -> None:
62
+ """Check if a run should be cancelled and raise exception if so."""
63
+ pass
64
+
65
+ @abstractmethod
66
+ async def araise_if_cancelled(self, run_id: str) -> None:
67
+ """Check if a run should be cancelled and raise exception if so (async version)."""
68
+ pass
69
+
70
+ @abstractmethod
71
+ def get_active_runs(self) -> Dict[str, bool]:
72
+ """Get all currently tracked runs and their cancellation status."""
73
+ pass
74
+
75
+ @abstractmethod
76
+ async def aget_active_runs(self) -> Dict[str, bool]:
77
+ """Get all currently tracked runs and their cancellation status (async version)."""
78
+ pass