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/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",
@@ -98,6 +100,62 @@ METRICS_TABLE_SCHEMA = {
98
100
  ],
99
101
  }
100
102
 
103
+ CULTURAL_KNOWLEDGE_TABLE_SCHEMA = {
104
+ "id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
105
+ "name": {"type": lambda: String(255), "nullable": False, "index": True},
106
+ "summary": {"type": Text, "nullable": True},
107
+ "content": {"type": JSON, "nullable": True},
108
+ "metadata": {"type": JSON, "nullable": True},
109
+ "input": {"type": Text, "nullable": True},
110
+ "created_at": {"type": BigInteger, "nullable": True},
111
+ "updated_at": {"type": BigInteger, "nullable": True},
112
+ "agent_id": {"type": lambda: String(128), "nullable": True},
113
+ "team_id": {"type": lambda: String(128), "nullable": True},
114
+ }
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
+ }
122
+
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
+ SPAN_TABLE_SCHEMA = {
140
+ "span_id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
141
+ "trace_id": {
142
+ "type": lambda: String(128),
143
+ "nullable": False,
144
+ "index": True,
145
+ "foreign_key": "agno_traces.trace_id", # Foreign key to traces table
146
+ },
147
+ "parent_span_id": {"type": lambda: String(128), "nullable": True, "index": True},
148
+ "name": {"type": lambda: String(255), "nullable": False},
149
+ "span_kind": {"type": lambda: String(50), "nullable": False},
150
+ "status_code": {"type": lambda: String(50), "nullable": False},
151
+ "status_message": {"type": Text, "nullable": True},
152
+ "start_time": {"type": lambda: String(128), "nullable": False, "index": True}, # ISO 8601 datetime string
153
+ "end_time": {"type": lambda: String(128), "nullable": False}, # ISO 8601 datetime string
154
+ "duration_ms": {"type": BigInteger, "nullable": False},
155
+ "attributes": {"type": JSON, "nullable": True},
156
+ "created_at": {"type": lambda: String(128), "nullable": False, "index": True}, # ISO 8601 datetime string
157
+ }
158
+
101
159
 
102
160
  def get_table_schema_definition(table_type: str) -> dict[str, Any]:
103
161
  """
@@ -115,6 +173,10 @@ def get_table_schema_definition(table_type: str) -> dict[str, Any]:
115
173
  "metrics": METRICS_TABLE_SCHEMA,
116
174
  "memories": USER_MEMORY_TABLE_SCHEMA,
117
175
  "knowledge": KNOWLEDGE_TABLE_SCHEMA,
176
+ "culture": CULTURAL_KNOWLEDGE_TABLE_SCHEMA,
177
+ "versions": VERSIONS_TABLE_SCHEMA,
178
+ "traces": TRACE_TABLE_SCHEMA,
179
+ "spans": SPAN_TABLE_SCHEMA,
118
180
  }
119
181
 
120
182
  schema = schemas.get(table_type, {})
agno/db/mysql/utils.py CHANGED
@@ -5,14 +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
9
+ from agno.db.schemas.culture import CulturalKnowledge
11
10
  from agno.utils.log import log_debug, log_error, log_warning
12
11
 
13
12
  try:
14
- from sqlalchemy import Table
13
+ from sqlalchemy import Engine, Table
15
14
  from sqlalchemy.dialects import mysql
15
+ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
16
16
  from sqlalchemy.inspection import inspect
17
17
  from sqlalchemy.orm import Session
18
18
  from sqlalchemy.sql.expression import text
@@ -90,8 +90,10 @@ def is_valid_table(db_engine: Engine, table_name: str, table_type: str, db_schem
90
90
  Check if the existing table has the expected column names.
91
91
 
92
92
  Args:
93
+ db_engine: Database engine
93
94
  table_name (str): Name of the table to validate
94
- schema (str): Database schema name
95
+ table_type (str): Type of table (for schema lookup)
96
+ db_schema (str): Database schema name
95
97
 
96
98
  Returns:
97
99
  bool: True if table has all expected columns, False otherwise
@@ -122,6 +124,7 @@ def bulk_upsert_metrics(session: Session, table: Table, metrics_records: list[di
122
124
  """Bulk upsert metrics into the database.
123
125
 
124
126
  Args:
127
+ session (Session): The SQLAlchemy session
125
128
  table (Table): The table to upsert into.
126
129
  metrics_records (list[dict]): The metrics records to upsert.
127
130
 
@@ -155,7 +158,10 @@ def bulk_upsert_metrics(session: Session, table: Table, metrics_records: list[di
155
158
 
156
159
  for record in metrics_records:
157
160
  select_stmt = select(table).where(
158
- and_(table.c.date == record["date"], table.c.aggregation_period == record["aggregation_period"])
161
+ and_(
162
+ table.c.date == record["date"],
163
+ table.c.aggregation_period == record["aggregation_period"],
164
+ )
159
165
  )
160
166
  result = session.execute(select_stmt).fetchone()
161
167
  if result:
@@ -164,6 +170,55 @@ def bulk_upsert_metrics(session: Session, table: Table, metrics_records: list[di
164
170
  return results # type: ignore
165
171
 
166
172
 
173
+ async def abulk_upsert_metrics(session: AsyncSession, table: Table, metrics_records: list[dict]) -> list[dict]:
174
+ """Async bulk upsert metrics into the database.
175
+
176
+ Args:
177
+ session (AsyncSession): The async SQLAlchemy session
178
+ table (Table): The table to upsert into.
179
+ metrics_records (list[dict]): The metrics records to upsert.
180
+
181
+ Returns:
182
+ list[dict]: The upserted metrics records.
183
+ """
184
+ if not metrics_records:
185
+ return []
186
+
187
+ results = []
188
+
189
+ # MySQL doesn't support returning in the same way as PostgreSQL
190
+ # We'll need to insert/update and then fetch the records
191
+ for record in metrics_records:
192
+ stmt = mysql.insert(table).values(record)
193
+
194
+ # Columns to update in case of conflict
195
+ update_dict = {
196
+ col.name: record.get(col.name)
197
+ for col in table.columns
198
+ if col.name not in ["id", "date", "created_at", "aggregation_period"] and col.name in record
199
+ }
200
+
201
+ stmt = stmt.on_duplicate_key_update(**update_dict)
202
+ await session.execute(stmt)
203
+
204
+ # Fetch the updated records
205
+ from sqlalchemy import and_, select
206
+
207
+ for record in metrics_records:
208
+ select_stmt = select(table).where(
209
+ and_(
210
+ table.c.date == record["date"],
211
+ table.c.aggregation_period == record["aggregation_period"],
212
+ )
213
+ )
214
+ result = await session.execute(select_stmt)
215
+ fetched_row = result.fetchone()
216
+ if fetched_row:
217
+ results.append(dict(fetched_row._mapping))
218
+
219
+ return results
220
+
221
+
167
222
  def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
168
223
  """Calculate metrics for the given single date.
169
224
 
@@ -204,7 +259,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
204
259
  all_user_ids = set()
205
260
 
206
261
  for session_type, sessions_count_key, runs_count_key in session_types:
207
- sessions = sessions_data.get(session_type, [])
262
+ sessions = sessions_data.get(session_type, []) or []
208
263
  metrics[sessions_count_key] = len(sessions)
209
264
 
210
265
  for session in sessions:
@@ -225,7 +280,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
225
280
 
226
281
  model_metrics = []
227
282
  for model, count in model_counts.items():
228
- model_id, model_provider = model.split(":")
283
+ model_id, model_provider = model.rsplit(":", 1)
229
284
  model_metrics.append({"model_id": model_id, "model_provider": model_provider, "count": count})
230
285
 
231
286
  metrics["users_count"] = len(all_user_ids)
@@ -295,3 +350,139 @@ def get_dates_to_calculate_metrics_for(starting_date: date) -> list[date]:
295
350
  if days_diff <= 0:
296
351
  return []
297
352
  return [starting_date + timedelta(days=x) for x in range(days_diff)]
353
+
354
+
355
+ # -- Cultural Knowledge util methods --
356
+ def serialize_cultural_knowledge_for_db(
357
+ cultural_knowledge: CulturalKnowledge,
358
+ ) -> Dict[str, Any]:
359
+ """Serialize a CulturalKnowledge object for database storage.
360
+
361
+ Converts the model's separate content, categories, and notes fields
362
+ into a single JSON dict for the database content column.
363
+
364
+ Args:
365
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
366
+
367
+ Returns:
368
+ Dict[str, Any]: A dictionary with the content field as JSON containing content, categories, and notes.
369
+ """
370
+ content_dict: Dict[str, Any] = {}
371
+ if cultural_knowledge.content is not None:
372
+ content_dict["content"] = cultural_knowledge.content
373
+ if cultural_knowledge.categories is not None:
374
+ content_dict["categories"] = cultural_knowledge.categories
375
+ if cultural_knowledge.notes is not None:
376
+ content_dict["notes"] = cultural_knowledge.notes
377
+
378
+ return content_dict if content_dict else {}
379
+
380
+
381
+ def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
382
+ """Deserialize a database row to a CulturalKnowledge object.
383
+
384
+ The database stores content as a JSON dict containing content, categories, and notes.
385
+ This method extracts those fields and converts them back to the model format.
386
+
387
+ Args:
388
+ db_row (Dict[str, Any]): The database row as a dictionary.
389
+
390
+ Returns:
391
+ CulturalKnowledge: The cultural knowledge object.
392
+ """
393
+ # Extract content, categories, and notes from the JSON content field
394
+ content_json = db_row.get("content", {}) or {}
395
+
396
+ return CulturalKnowledge.from_dict(
397
+ {
398
+ "id": db_row.get("id"),
399
+ "name": db_row.get("name"),
400
+ "summary": db_row.get("summary"),
401
+ "content": content_json.get("content"),
402
+ "categories": content_json.get("categories"),
403
+ "notes": content_json.get("notes"),
404
+ "metadata": db_row.get("metadata"),
405
+ "input": db_row.get("input"),
406
+ "created_at": db_row.get("created_at"),
407
+ "updated_at": db_row.get("updated_at"),
408
+ "agent_id": db_row.get("agent_id"),
409
+ "team_id": db_row.get("team_id"),
410
+ }
411
+ )
412
+
413
+
414
+ # -- Async DB util methods --
415
+ async def acreate_schema(session: AsyncSession, db_schema: str) -> None:
416
+ """Async version: Create the database schema if it doesn't exist.
417
+
418
+ Args:
419
+ session: The async SQLAlchemy session to use
420
+ db_schema (str): The definition of the database schema to create
421
+ """
422
+ try:
423
+ log_debug(f"Creating database if not exists: {db_schema}")
424
+ # MySQL uses CREATE DATABASE instead of CREATE SCHEMA
425
+ await session.execute(text(f"CREATE DATABASE IF NOT EXISTS `{db_schema}`;"))
426
+ except Exception as e:
427
+ log_warning(f"Could not create database {db_schema}: {e}")
428
+
429
+
430
+ async def ais_table_available(session: AsyncSession, table_name: str, db_schema: str) -> bool:
431
+ """Async version: Check if a table with the given name exists in the given schema.
432
+
433
+ Returns:
434
+ bool: True if the table exists, False otherwise.
435
+ """
436
+ try:
437
+ exists_query = text(
438
+ "SELECT 1 FROM information_schema.tables WHERE table_schema = :schema AND table_name = :table"
439
+ )
440
+ result = await session.execute(exists_query, {"schema": db_schema, "table": table_name})
441
+ exists = result.scalar() is not None
442
+ if not exists:
443
+ log_debug(f"Table {db_schema}.{table_name} {'exists' if exists else 'does not exist'}")
444
+
445
+ return exists
446
+
447
+ except Exception as e:
448
+ log_error(f"Error checking if table exists: {e}")
449
+ return False
450
+
451
+
452
+ async def ais_valid_table(db_engine: AsyncEngine, table_name: str, table_type: str, db_schema: str) -> bool:
453
+ """Async version: Check if the existing table has the expected column names.
454
+
455
+ Args:
456
+ db_engine: Async database engine
457
+ table_name (str): Name of the table to validate
458
+ table_type (str): Type of table (for schema lookup)
459
+ db_schema (str): Database schema name
460
+
461
+ Returns:
462
+ bool: True if table has all expected columns, False otherwise
463
+ """
464
+ try:
465
+ expected_table_schema = get_table_schema_definition(table_type)
466
+ expected_columns = {col_name for col_name in expected_table_schema.keys() if not col_name.startswith("_")}
467
+
468
+ # Get existing columns from the async engine
469
+ async with db_engine.connect() as conn:
470
+ existing_columns = await conn.run_sync(_get_table_columns, table_name, db_schema)
471
+
472
+ # Check if all expected columns exist
473
+ missing_columns = expected_columns - existing_columns
474
+ if missing_columns:
475
+ log_warning(f"Missing columns {missing_columns} in table {db_schema}.{table_name}")
476
+ return False
477
+
478
+ return True
479
+ except Exception as e:
480
+ log_error(f"Error validating table schema for {db_schema}.{table_name}: {e}")
481
+ return False
482
+
483
+
484
+ def _get_table_columns(connection, table_name: str, db_schema: str) -> set[str]:
485
+ """Helper function to get table columns using sync inspector."""
486
+ inspector = inspect(connection)
487
+ columns_info = inspector.get_columns(table_name, schema=db_schema)
488
+ return {col["name"] for col in columns_info}
@@ -1,3 +1,4 @@
1
+ from agno.db.postgres.async_postgres import AsyncPostgresDb
1
2
  from agno.db.postgres.postgres import PostgresDb
2
3
 
3
- __all__ = ["PostgresDb"]
4
+ __all__ = ["PostgresDb", "AsyncPostgresDb"]