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/db/mysql/schemas.py CHANGED
@@ -39,6 +39,8 @@ USER_MEMORY_TABLE_SCHEMA = {
39
39
  "team_id": {"type": lambda: String(128), "nullable": True},
40
40
  "user_id": {"type": lambda: String(128), "nullable": True, "index": True},
41
41
  "topics": {"type": JSON, "nullable": True},
42
+ "feedback": {"type": Text, "nullable": True},
43
+ "created_at": {"type": BigInteger, "nullable": False, "index": True},
42
44
  "updated_at": {"type": BigInteger, "nullable": True, "index": True},
43
45
  }
44
46
 
@@ -76,20 +78,20 @@ KNOWLEDGE_TABLE_SCHEMA = {
76
78
 
77
79
  METRICS_TABLE_SCHEMA = {
78
80
  "id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
79
- "agent_runs_count": {"type": BigInteger, "nullable": False},
80
- "team_runs_count": {"type": BigInteger, "nullable": False},
81
- "workflow_runs_count": {"type": BigInteger, "nullable": False},
82
- "agent_sessions_count": {"type": BigInteger, "nullable": False},
83
- "team_sessions_count": {"type": BigInteger, "nullable": False},
84
- "workflow_sessions_count": {"type": BigInteger, "nullable": False},
85
- "users_count": {"type": BigInteger, "nullable": False},
86
- "token_metrics": {"type": JSON, "nullable": False},
87
- "model_metrics": {"type": JSON, "nullable": False},
81
+ "agent_runs_count": {"type": BigInteger, "nullable": False, "default": 0},
82
+ "team_runs_count": {"type": BigInteger, "nullable": False, "default": 0},
83
+ "workflow_runs_count": {"type": BigInteger, "nullable": False, "default": 0},
84
+ "agent_sessions_count": {"type": BigInteger, "nullable": False, "default": 0},
85
+ "team_sessions_count": {"type": BigInteger, "nullable": False, "default": 0},
86
+ "workflow_sessions_count": {"type": BigInteger, "nullable": False, "default": 0},
87
+ "users_count": {"type": BigInteger, "nullable": False, "default": 0},
88
+ "token_metrics": {"type": JSON, "nullable": False, "default": {}},
89
+ "model_metrics": {"type": JSON, "nullable": False, "default": {}},
88
90
  "date": {"type": Date, "nullable": False, "index": True},
89
91
  "aggregation_period": {"type": lambda: String(20), "nullable": False},
90
92
  "created_at": {"type": BigInteger, "nullable": False},
91
93
  "updated_at": {"type": BigInteger, "nullable": True},
92
- "completed": {"type": Boolean, "nullable": False},
94
+ "completed": {"type": Boolean, "nullable": False, "default": False},
93
95
  "_unique_constraints": [
94
96
  {
95
97
  "name": "uq_metrics_date_period",
@@ -111,17 +113,79 @@ CULTURAL_KNOWLEDGE_TABLE_SCHEMA = {
111
113
  "team_id": {"type": lambda: String(128), "nullable": True},
112
114
  }
113
115
 
116
+ VERSIONS_TABLE_SCHEMA = {
117
+ "table_name": {"type": lambda: String(128), "nullable": False, "primary_key": True},
118
+ "version": {"type": lambda: String(10), "nullable": False},
119
+ "created_at": {"type": lambda: String(128), "nullable": False, "index": True},
120
+ "updated_at": {"type": lambda: String(128), "nullable": True},
121
+ }
114
122
 
115
- def get_table_schema_definition(table_type: str) -> dict[str, Any]:
123
+ TRACE_TABLE_SCHEMA = {
124
+ "trace_id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
125
+ "name": {"type": lambda: String(255), "nullable": False},
126
+ "status": {"type": lambda: String(50), "nullable": False, "index": True},
127
+ "start_time": {"type": lambda: String(128), "nullable": False, "index": True}, # ISO 8601 datetime string
128
+ "end_time": {"type": lambda: String(128), "nullable": False}, # ISO 8601 datetime string
129
+ "duration_ms": {"type": BigInteger, "nullable": False},
130
+ "run_id": {"type": lambda: String(128), "nullable": True, "index": True},
131
+ "session_id": {"type": lambda: String(128), "nullable": True, "index": True},
132
+ "user_id": {"type": lambda: String(128), "nullable": True, "index": True},
133
+ "agent_id": {"type": lambda: String(128), "nullable": True, "index": True},
134
+ "team_id": {"type": lambda: String(128), "nullable": True, "index": True},
135
+ "workflow_id": {"type": lambda: String(128), "nullable": True, "index": True},
136
+ "created_at": {"type": lambda: String(128), "nullable": False, "index": True}, # ISO 8601 datetime string
137
+ }
138
+
139
+
140
+ def _get_span_table_schema(traces_table_name: str = "agno_traces", db_schema: str = "agno") -> dict[str, Any]:
141
+ """Get the span table schema with the correct foreign key reference.
142
+
143
+ Args:
144
+ traces_table_name: The name of the traces table to reference in the foreign key.
145
+ db_schema: The database schema name.
146
+
147
+ Returns:
148
+ The span table schema dictionary.
149
+ """
150
+ return {
151
+ "span_id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
152
+ "trace_id": {
153
+ "type": lambda: String(128),
154
+ "nullable": False,
155
+ "index": True,
156
+ "foreign_key": f"{db_schema}.{traces_table_name}.trace_id",
157
+ },
158
+ "parent_span_id": {"type": lambda: String(128), "nullable": True, "index": True},
159
+ "name": {"type": lambda: String(255), "nullable": False},
160
+ "span_kind": {"type": lambda: String(50), "nullable": False},
161
+ "status_code": {"type": lambda: String(50), "nullable": False},
162
+ "status_message": {"type": Text, "nullable": True},
163
+ "start_time": {"type": lambda: String(128), "nullable": False, "index": True}, # ISO 8601 datetime string
164
+ "end_time": {"type": lambda: String(128), "nullable": False}, # ISO 8601 datetime string
165
+ "duration_ms": {"type": BigInteger, "nullable": False},
166
+ "attributes": {"type": JSON, "nullable": True},
167
+ "created_at": {"type": lambda: String(128), "nullable": False, "index": True}, # ISO 8601 datetime string
168
+ }
169
+
170
+
171
+ def get_table_schema_definition(
172
+ table_type: str, traces_table_name: str = "agno_traces", db_schema: str = "agno"
173
+ ) -> dict[str, Any]:
116
174
  """
117
175
  Get the expected schema definition for the given table.
118
176
 
119
177
  Args:
120
178
  table_type (str): The type of table to get the schema for.
179
+ traces_table_name (str): The name of the traces table (used for spans foreign key).
180
+ db_schema (str): The database schema name (used for spans foreign key).
121
181
 
122
182
  Returns:
123
183
  Dict[str, Any]: Dictionary containing column definitions for the table
124
184
  """
185
+ # Handle spans table specially to resolve the foreign key reference
186
+ if table_type == "spans":
187
+ return _get_span_table_schema(traces_table_name, db_schema)
188
+
125
189
  schemas = {
126
190
  "sessions": SESSION_TABLE_SCHEMA,
127
191
  "evals": EVAL_TABLE_SCHEMA,
@@ -129,6 +193,8 @@ def get_table_schema_definition(table_type: str) -> dict[str, Any]:
129
193
  "memories": USER_MEMORY_TABLE_SCHEMA,
130
194
  "knowledge": KNOWLEDGE_TABLE_SCHEMA,
131
195
  "culture": CULTURAL_KNOWLEDGE_TABLE_SCHEMA,
196
+ "versions": VERSIONS_TABLE_SCHEMA,
197
+ "traces": TRACE_TABLE_SCHEMA,
132
198
  }
133
199
 
134
200
  schema = schemas.get(table_type, {})
agno/db/mysql/utils.py CHANGED
@@ -5,15 +5,14 @@ from datetime import date, datetime, timedelta, timezone
5
5
  from typing import Any, Dict, List, Optional
6
6
  from uuid import uuid4
7
7
 
8
- from sqlalchemy import Engine
9
-
10
8
  from agno.db.mysql.schemas import get_table_schema_definition
11
9
  from agno.db.schemas.culture import CulturalKnowledge
12
10
  from agno.utils.log import log_debug, log_error, log_warning
13
11
 
14
12
  try:
15
- from sqlalchemy import Table
13
+ from sqlalchemy import Engine, Table, func
16
14
  from sqlalchemy.dialects import mysql
15
+ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
17
16
  from sqlalchemy.inspection import inspect
18
17
  from sqlalchemy.orm import Session
19
18
  from sqlalchemy.sql.expression import text
@@ -33,6 +32,11 @@ def apply_sorting(stmt, table: Table, sort_by: Optional[str] = None, sort_order:
33
32
 
34
33
  Returns:
35
34
  The modified statement with sorting applied
35
+
36
+ Note:
37
+ For 'updated_at' sorting, uses COALESCE(updated_at, created_at) to fall back
38
+ to created_at when updated_at is NULL. This ensures pre-2.0 records (which may
39
+ have NULL updated_at) are sorted correctly by their creation time.
36
40
  """
37
41
  if sort_by is None:
38
42
  return stmt
@@ -41,8 +45,13 @@ def apply_sorting(stmt, table: Table, sort_by: Optional[str] = None, sort_order:
41
45
  log_debug(f"Invalid sort field: '{sort_by}'. Will not apply any sorting.")
42
46
  return stmt
43
47
 
44
- # Apply the given sorting
45
- sort_column = getattr(table.c, sort_by)
48
+ # For updated_at, use COALESCE to fall back to created_at if updated_at is NULL
49
+ # This handles pre-2.0 records that may have NULL updated_at values
50
+ if sort_by == "updated_at" and hasattr(table.c, "created_at"):
51
+ sort_column = func.coalesce(table.c.updated_at, table.c.created_at)
52
+ else:
53
+ sort_column = getattr(table.c, sort_by)
54
+
46
55
  if sort_order and sort_order == "asc":
47
56
  return stmt.order_by(sort_column.asc())
48
57
  else:
@@ -91,8 +100,10 @@ def is_valid_table(db_engine: Engine, table_name: str, table_type: str, db_schem
91
100
  Check if the existing table has the expected column names.
92
101
 
93
102
  Args:
103
+ db_engine: Database engine
94
104
  table_name (str): Name of the table to validate
95
- schema (str): Database schema name
105
+ table_type (str): Type of table (for schema lookup)
106
+ db_schema (str): Database schema name
96
107
 
97
108
  Returns:
98
109
  bool: True if table has all expected columns, False otherwise
@@ -123,6 +134,7 @@ def bulk_upsert_metrics(session: Session, table: Table, metrics_records: list[di
123
134
  """Bulk upsert metrics into the database.
124
135
 
125
136
  Args:
137
+ session (Session): The SQLAlchemy session
126
138
  table (Table): The table to upsert into.
127
139
  metrics_records (list[dict]): The metrics records to upsert.
128
140
 
@@ -156,7 +168,10 @@ def bulk_upsert_metrics(session: Session, table: Table, metrics_records: list[di
156
168
 
157
169
  for record in metrics_records:
158
170
  select_stmt = select(table).where(
159
- and_(table.c.date == record["date"], table.c.aggregation_period == record["aggregation_period"])
171
+ and_(
172
+ table.c.date == record["date"],
173
+ table.c.aggregation_period == record["aggregation_period"],
174
+ )
160
175
  )
161
176
  result = session.execute(select_stmt).fetchone()
162
177
  if result:
@@ -165,6 +180,55 @@ def bulk_upsert_metrics(session: Session, table: Table, metrics_records: list[di
165
180
  return results # type: ignore
166
181
 
167
182
 
183
+ async def abulk_upsert_metrics(session: AsyncSession, table: Table, metrics_records: list[dict]) -> list[dict]:
184
+ """Async bulk upsert metrics into the database.
185
+
186
+ Args:
187
+ session (AsyncSession): The async SQLAlchemy session
188
+ table (Table): The table to upsert into.
189
+ metrics_records (list[dict]): The metrics records to upsert.
190
+
191
+ Returns:
192
+ list[dict]: The upserted metrics records.
193
+ """
194
+ if not metrics_records:
195
+ return []
196
+
197
+ results = []
198
+
199
+ # MySQL doesn't support returning in the same way as PostgreSQL
200
+ # We'll need to insert/update and then fetch the records
201
+ for record in metrics_records:
202
+ stmt = mysql.insert(table).values(record)
203
+
204
+ # Columns to update in case of conflict
205
+ update_dict = {
206
+ col.name: record.get(col.name)
207
+ for col in table.columns
208
+ if col.name not in ["id", "date", "created_at", "aggregation_period"] and col.name in record
209
+ }
210
+
211
+ stmt = stmt.on_duplicate_key_update(**update_dict)
212
+ await session.execute(stmt)
213
+
214
+ # Fetch the updated records
215
+ from sqlalchemy import and_, select
216
+
217
+ for record in metrics_records:
218
+ select_stmt = select(table).where(
219
+ and_(
220
+ table.c.date == record["date"],
221
+ table.c.aggregation_period == record["aggregation_period"],
222
+ )
223
+ )
224
+ result = await session.execute(select_stmt)
225
+ fetched_row = result.fetchone()
226
+ if fetched_row:
227
+ results.append(dict(fetched_row._mapping))
228
+
229
+ return results
230
+
231
+
168
232
  def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
169
233
  """Calculate metrics for the given single date.
170
234
 
@@ -299,7 +363,9 @@ def get_dates_to_calculate_metrics_for(starting_date: date) -> list[date]:
299
363
 
300
364
 
301
365
  # -- Cultural Knowledge util methods --
302
- def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
366
+ def serialize_cultural_knowledge_for_db(
367
+ cultural_knowledge: CulturalKnowledge,
368
+ ) -> Dict[str, Any]:
303
369
  """Serialize a CulturalKnowledge object for database storage.
304
370
 
305
371
  Converts the model's separate content, categories, and notes fields
@@ -353,3 +419,80 @@ def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKn
353
419
  "team_id": db_row.get("team_id"),
354
420
  }
355
421
  )
422
+
423
+
424
+ # -- Async DB util methods --
425
+ async def acreate_schema(session: AsyncSession, db_schema: str) -> None:
426
+ """Async version: Create the database schema if it doesn't exist.
427
+
428
+ Args:
429
+ session: The async SQLAlchemy session to use
430
+ db_schema (str): The definition of the database schema to create
431
+ """
432
+ try:
433
+ log_debug(f"Creating database if not exists: {db_schema}")
434
+ # MySQL uses CREATE DATABASE instead of CREATE SCHEMA
435
+ await session.execute(text(f"CREATE DATABASE IF NOT EXISTS `{db_schema}`;"))
436
+ except Exception as e:
437
+ log_warning(f"Could not create database {db_schema}: {e}")
438
+
439
+
440
+ async def ais_table_available(session: AsyncSession, table_name: str, db_schema: str) -> bool:
441
+ """Async version: Check if a table with the given name exists in the given schema.
442
+
443
+ Returns:
444
+ bool: True if the table exists, False otherwise.
445
+ """
446
+ try:
447
+ exists_query = text(
448
+ "SELECT 1 FROM information_schema.tables WHERE table_schema = :schema AND table_name = :table"
449
+ )
450
+ result = await session.execute(exists_query, {"schema": db_schema, "table": table_name})
451
+ exists = result.scalar() is not None
452
+ if not exists:
453
+ log_debug(f"Table {db_schema}.{table_name} {'exists' if exists else 'does not exist'}")
454
+
455
+ return exists
456
+
457
+ except Exception as e:
458
+ log_error(f"Error checking if table exists: {e}")
459
+ return False
460
+
461
+
462
+ async def ais_valid_table(db_engine: AsyncEngine, table_name: str, table_type: str, db_schema: str) -> bool:
463
+ """Async version: Check if the existing table has the expected column names.
464
+
465
+ Args:
466
+ db_engine: Async database engine
467
+ table_name (str): Name of the table to validate
468
+ table_type (str): Type of table (for schema lookup)
469
+ db_schema (str): Database schema name
470
+
471
+ Returns:
472
+ bool: True if table has all expected columns, False otherwise
473
+ """
474
+ try:
475
+ expected_table_schema = get_table_schema_definition(table_type)
476
+ expected_columns = {col_name for col_name in expected_table_schema.keys() if not col_name.startswith("_")}
477
+
478
+ # Get existing columns from the async engine
479
+ async with db_engine.connect() as conn:
480
+ existing_columns = await conn.run_sync(_get_table_columns, table_name, db_schema)
481
+
482
+ # Check if all expected columns exist
483
+ missing_columns = expected_columns - existing_columns
484
+ if missing_columns:
485
+ log_warning(f"Missing columns {missing_columns} in table {db_schema}.{table_name}")
486
+ return False
487
+
488
+ return True
489
+ except Exception as e:
490
+ log_error(f"Error validating table schema for {db_schema}.{table_name}: {e}")
491
+ return False
492
+
493
+
494
+ def _get_table_columns(connection, table_name: str, db_schema: str) -> set[str]:
495
+ """Helper function to get table columns using sync inspector."""
496
+ inspector = inspect(connection)
497
+ columns_info = inspector.get_columns(table_name, schema=db_schema)
498
+ return {col["name"] for col in columns_info}