agno 2.0.1__py3-none-any.whl → 2.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. agno/agent/agent.py +6015 -2823
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +594 -186
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +2 -8
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +72 -0
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +999 -519
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +103 -31
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +139 -0
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +59 -5
  142. agno/models/openai/chat.py +69 -29
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +77 -1
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -178
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +248 -94
  205. agno/run/base.py +44 -5
  206. agno/run/team.py +238 -97
  207. agno/run/workflow.py +144 -33
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1610
  213. agno/tools/dalle.py +2 -4
  214. agno/tools/decorator.py +4 -2
  215. agno/tools/duckduckgo.py +15 -11
  216. agno/tools/e2b.py +14 -7
  217. agno/tools/eleven_labs.py +23 -25
  218. agno/tools/exa.py +21 -16
  219. agno/tools/file.py +153 -23
  220. agno/tools/file_generation.py +350 -0
  221. agno/tools/firecrawl.py +4 -4
  222. agno/tools/function.py +250 -30
  223. agno/tools/gmail.py +238 -14
  224. agno/tools/google_drive.py +270 -0
  225. agno/tools/googlecalendar.py +36 -8
  226. agno/tools/googlesheets.py +20 -5
  227. agno/tools/jira.py +20 -0
  228. agno/tools/knowledge.py +3 -3
  229. agno/tools/mcp/__init__.py +10 -0
  230. agno/tools/mcp/mcp.py +331 -0
  231. agno/tools/mcp/multi_mcp.py +347 -0
  232. agno/tools/mcp/params.py +24 -0
  233. agno/tools/mcp_toolbox.py +284 -0
  234. agno/tools/mem0.py +11 -17
  235. agno/tools/memori.py +1 -53
  236. agno/tools/memory.py +419 -0
  237. agno/tools/models/nebius.py +5 -5
  238. agno/tools/models_labs.py +20 -10
  239. agno/tools/notion.py +204 -0
  240. agno/tools/parallel.py +314 -0
  241. agno/tools/scrapegraph.py +58 -31
  242. agno/tools/searxng.py +2 -2
  243. agno/tools/serper.py +2 -2
  244. agno/tools/slack.py +18 -3
  245. agno/tools/spider.py +2 -2
  246. agno/tools/tavily.py +146 -0
  247. agno/tools/whatsapp.py +1 -1
  248. agno/tools/workflow.py +278 -0
  249. agno/tools/yfinance.py +12 -11
  250. agno/utils/agent.py +820 -0
  251. agno/utils/audio.py +27 -0
  252. agno/utils/common.py +90 -1
  253. agno/utils/events.py +217 -2
  254. agno/utils/gemini.py +180 -22
  255. agno/utils/hooks.py +57 -0
  256. agno/utils/http.py +111 -0
  257. agno/utils/knowledge.py +12 -5
  258. agno/utils/log.py +1 -0
  259. agno/utils/mcp.py +92 -2
  260. agno/utils/media.py +188 -10
  261. agno/utils/merge_dict.py +22 -1
  262. agno/utils/message.py +60 -0
  263. agno/utils/models/claude.py +40 -11
  264. agno/utils/print_response/agent.py +105 -21
  265. agno/utils/print_response/team.py +103 -38
  266. agno/utils/print_response/workflow.py +251 -34
  267. agno/utils/reasoning.py +22 -1
  268. agno/utils/serialize.py +32 -0
  269. agno/utils/streamlit.py +16 -10
  270. agno/utils/string.py +41 -0
  271. agno/utils/team.py +98 -9
  272. agno/utils/tools.py +1 -1
  273. agno/vectordb/base.py +23 -4
  274. agno/vectordb/cassandra/cassandra.py +65 -9
  275. agno/vectordb/chroma/chromadb.py +182 -38
  276. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  277. agno/vectordb/couchbase/couchbase.py +105 -10
  278. agno/vectordb/lancedb/lance_db.py +124 -133
  279. agno/vectordb/langchaindb/langchaindb.py +25 -7
  280. agno/vectordb/lightrag/lightrag.py +17 -3
  281. agno/vectordb/llamaindex/__init__.py +3 -0
  282. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  283. agno/vectordb/milvus/milvus.py +126 -9
  284. agno/vectordb/mongodb/__init__.py +7 -1
  285. agno/vectordb/mongodb/mongodb.py +112 -7
  286. agno/vectordb/pgvector/pgvector.py +142 -21
  287. agno/vectordb/pineconedb/pineconedb.py +80 -8
  288. agno/vectordb/qdrant/qdrant.py +125 -39
  289. agno/vectordb/redis/__init__.py +9 -0
  290. agno/vectordb/redis/redisdb.py +694 -0
  291. agno/vectordb/singlestore/singlestore.py +111 -25
  292. agno/vectordb/surrealdb/surrealdb.py +31 -5
  293. agno/vectordb/upstashdb/upstashdb.py +76 -8
  294. agno/vectordb/weaviate/weaviate.py +86 -15
  295. agno/workflow/__init__.py +2 -0
  296. agno/workflow/agent.py +299 -0
  297. agno/workflow/condition.py +112 -18
  298. agno/workflow/loop.py +69 -10
  299. agno/workflow/parallel.py +266 -118
  300. agno/workflow/router.py +110 -17
  301. agno/workflow/step.py +638 -129
  302. agno/workflow/steps.py +65 -6
  303. agno/workflow/types.py +61 -23
  304. agno/workflow/workflow.py +2085 -272
  305. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
  306. agno-2.3.0.dist-info/RECORD +577 -0
  307. agno/knowledge/reader/url_reader.py +0 -128
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -610
  310. agno/utils/models/aws_claude.py +0 -170
  311. agno-2.0.1.dist-info/RECORD +0 -515
  312. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  313. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,8 @@
3
3
  from typing import Any
4
4
 
5
5
  try:
6
- from sqlalchemy.types import JSON, BigInteger, Boolean, Date, String
6
+ from sqlalchemy.dialects.postgresql import JSONB
7
+ from sqlalchemy.types import BigInteger, Boolean, Date, String, Text
7
8
  except ImportError:
8
9
  raise ImportError("`sqlalchemy` not installed. Please install it using `pip install sqlalchemy`")
9
10
 
@@ -14,13 +15,13 @@ SESSION_TABLE_SCHEMA = {
14
15
  "team_id": {"type": String, "nullable": True},
15
16
  "workflow_id": {"type": String, "nullable": True},
16
17
  "user_id": {"type": String, "nullable": True},
17
- "session_data": {"type": JSON, "nullable": True},
18
- "agent_data": {"type": JSON, "nullable": True},
19
- "team_data": {"type": JSON, "nullable": True},
20
- "workflow_data": {"type": JSON, "nullable": True},
21
- "metadata": {"type": JSON, "nullable": True},
22
- "runs": {"type": JSON, "nullable": True},
23
- "summary": {"type": JSON, "nullable": True},
18
+ "session_data": {"type": JSONB, "nullable": True},
19
+ "agent_data": {"type": JSONB, "nullable": True},
20
+ "team_data": {"type": JSONB, "nullable": True},
21
+ "workflow_data": {"type": JSONB, "nullable": True},
22
+ "metadata": {"type": JSONB, "nullable": True},
23
+ "runs": {"type": JSONB, "nullable": True},
24
+ "summary": {"type": JSONB, "nullable": True},
24
25
  "created_at": {"type": BigInteger, "nullable": False, "index": True},
25
26
  "updated_at": {"type": BigInteger, "nullable": True},
26
27
  "_unique_constraints": [
@@ -33,20 +34,22 @@ SESSION_TABLE_SCHEMA = {
33
34
 
34
35
  MEMORY_TABLE_SCHEMA = {
35
36
  "memory_id": {"type": String, "primary_key": True, "nullable": False},
36
- "memory": {"type": JSON, "nullable": False},
37
- "input": {"type": String, "nullable": True},
37
+ "memory": {"type": JSONB, "nullable": False},
38
+ "feedback": {"type": Text, "nullable": True},
39
+ "input": {"type": Text, "nullable": True},
38
40
  "agent_id": {"type": String, "nullable": True},
39
41
  "team_id": {"type": String, "nullable": True},
40
42
  "user_id": {"type": String, "nullable": True, "index": True},
41
- "topics": {"type": JSON, "nullable": True},
43
+ "topics": {"type": JSONB, "nullable": True},
44
+ "created_at": {"type": BigInteger, "nullable": False, "index": True},
42
45
  "updated_at": {"type": BigInteger, "nullable": True, "index": True},
43
46
  }
44
47
 
45
48
  EVAL_TABLE_SCHEMA = {
46
49
  "run_id": {"type": String, "primary_key": True, "nullable": False},
47
50
  "eval_type": {"type": String, "nullable": False},
48
- "eval_data": {"type": JSON, "nullable": False},
49
- "eval_input": {"type": JSON, "nullable": False},
51
+ "eval_data": {"type": JSONB, "nullable": False},
52
+ "eval_input": {"type": JSONB, "nullable": False},
50
53
  "name": {"type": String, "nullable": True},
51
54
  "agent_id": {"type": String, "nullable": True},
52
55
  "team_id": {"type": String, "nullable": True},
@@ -61,14 +64,14 @@ EVAL_TABLE_SCHEMA = {
61
64
  KNOWLEDGE_TABLE_SCHEMA = {
62
65
  "id": {"type": String, "primary_key": True, "nullable": False},
63
66
  "name": {"type": String, "nullable": False},
64
- "description": {"type": String, "nullable": False},
65
- "metadata": {"type": JSON, "nullable": True},
67
+ "description": {"type": Text, "nullable": False},
68
+ "metadata": {"type": JSONB, "nullable": True},
66
69
  "type": {"type": String, "nullable": True},
67
70
  "size": {"type": BigInteger, "nullable": True},
68
71
  "linked_to": {"type": String, "nullable": True},
69
72
  "access_count": {"type": BigInteger, "nullable": True},
70
73
  "status": {"type": String, "nullable": True},
71
- "status_message": {"type": String, "nullable": True},
74
+ "status_message": {"type": Text, "nullable": True},
72
75
  "created_at": {"type": BigInteger, "nullable": True},
73
76
  "updated_at": {"type": BigInteger, "nullable": True},
74
77
  "external_id": {"type": String, "nullable": True},
@@ -83,8 +86,8 @@ METRICS_TABLE_SCHEMA = {
83
86
  "team_sessions_count": {"type": BigInteger, "nullable": False, "default": 0},
84
87
  "workflow_sessions_count": {"type": BigInteger, "nullable": False, "default": 0},
85
88
  "users_count": {"type": BigInteger, "nullable": False, "default": 0},
86
- "token_metrics": {"type": JSON, "nullable": False, "default": {}},
87
- "model_metrics": {"type": JSON, "nullable": False, "default": {}},
89
+ "token_metrics": {"type": JSONB, "nullable": False, "default": {}},
90
+ "model_metrics": {"type": JSONB, "nullable": False, "default": {}},
88
91
  "date": {"type": Date, "nullable": False, "index": True},
89
92
  "aggregation_period": {"type": String, "nullable": False},
90
93
  "created_at": {"type": BigInteger, "nullable": False},
@@ -98,6 +101,26 @@ METRICS_TABLE_SCHEMA = {
98
101
  ],
99
102
  }
100
103
 
104
+ CULTURAL_KNOWLEDGE_TABLE_SCHEMA = {
105
+ "id": {"type": String, "primary_key": True, "nullable": False},
106
+ "name": {"type": String, "nullable": False, "index": True},
107
+ "summary": {"type": Text, "nullable": True},
108
+ "content": {"type": JSONB, "nullable": True},
109
+ "metadata": {"type": JSONB, "nullable": True},
110
+ "input": {"type": Text, "nullable": True},
111
+ "created_at": {"type": BigInteger, "nullable": True},
112
+ "updated_at": {"type": BigInteger, "nullable": True},
113
+ "agent_id": {"type": String, "nullable": True},
114
+ "team_id": {"type": String, "nullable": True},
115
+ }
116
+
117
+ VERSIONS_TABLE_SCHEMA = {
118
+ "table_name": {"type": String, "nullable": False, "primary_key": True},
119
+ "version": {"type": String, "nullable": False},
120
+ "created_at": {"type": String, "nullable": False, "index": True},
121
+ "updated_at": {"type": String, "nullable": True},
122
+ }
123
+
101
124
 
102
125
  def get_table_schema_definition(table_type: str) -> dict[str, Any]:
103
126
  """
@@ -115,6 +138,8 @@ def get_table_schema_definition(table_type: str) -> dict[str, Any]:
115
138
  "metrics": METRICS_TABLE_SCHEMA,
116
139
  "memories": MEMORY_TABLE_SCHEMA,
117
140
  "knowledge": KNOWLEDGE_TABLE_SCHEMA,
141
+ "culture": CULTURAL_KNOWLEDGE_TABLE_SCHEMA,
142
+ "versions": VERSIONS_TABLE_SCHEMA,
118
143
  }
119
144
 
120
145
  schema = schemas.get(table_type, {})
agno/db/postgres/utils.py CHANGED
@@ -6,8 +6,10 @@ from typing import Any, Dict, List, Optional
6
6
  from uuid import uuid4
7
7
 
8
8
  from sqlalchemy import Engine
9
+ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
9
10
 
10
11
  from agno.db.postgres.schemas import get_table_schema_definition
12
+ from agno.db.schemas.culture import CulturalKnowledge
11
13
  from agno.utils.log import log_debug, log_error, log_warning
12
14
 
13
15
  try:
@@ -62,6 +64,20 @@ def create_schema(session: Session, db_schema: str) -> None:
62
64
  log_warning(f"Could not create schema {db_schema}: {e}")
63
65
 
64
66
 
67
+ async def acreate_schema(session: AsyncSession, db_schema: str) -> None:
68
+ """Create the database schema if it doesn't exist.
69
+
70
+ Args:
71
+ session: The SQLAlchemy session to use
72
+ db_schema (str): The definition of the database schema to create
73
+ """
74
+ try:
75
+ log_debug(f"Creating schema if not exists: {db_schema}")
76
+ await session.execute(text(f"CREATE SCHEMA IF NOT EXISTS {db_schema};"))
77
+ except Exception as e:
78
+ log_warning(f"Could not create schema {db_schema}: {e}")
79
+
80
+
65
81
  def is_table_available(session: Session, table_name: str, db_schema: str) -> bool:
66
82
  """
67
83
  Check if a table with the given name exists in the given schema.
@@ -81,6 +97,24 @@ def is_table_available(session: Session, table_name: str, db_schema: str) -> boo
81
97
  return False
82
98
 
83
99
 
100
+ async def ais_table_available(session: AsyncSession, table_name: str, db_schema: str) -> bool:
101
+ """
102
+ Check if a table with the given name exists in the given schema.
103
+
104
+ Returns:
105
+ bool: True if the table exists, False otherwise.
106
+ """
107
+ try:
108
+ exists_query = text(
109
+ "SELECT 1 FROM information_schema.tables WHERE table_schema = :schema AND table_name = :table"
110
+ )
111
+ exists = (await session.execute(exists_query, {"schema": db_schema, "table": table_name})).scalar() is not None
112
+ return exists
113
+ except Exception as e:
114
+ log_error(f"Error checking if table exists: {e}")
115
+ return False
116
+
117
+
84
118
  def is_valid_table(db_engine: Engine, table_name: str, table_type: str, db_schema: str) -> bool:
85
119
  """
86
120
  Check if the existing table has the expected column names.
@@ -113,6 +147,44 @@ def is_valid_table(db_engine: Engine, table_name: str, table_type: str, db_schem
113
147
  return False
114
148
 
115
149
 
150
+ async def ais_valid_table(db_engine: AsyncEngine, table_name: str, table_type: str, db_schema: str) -> bool:
151
+ """
152
+ Check if the existing table has the expected column names.
153
+
154
+ Args:
155
+ table_name (str): Name of the table to validate
156
+ schema (str): Database schema name
157
+
158
+ Returns:
159
+ bool: True if table has all expected columns, False otherwise
160
+ """
161
+ try:
162
+ expected_table_schema = get_table_schema_definition(table_type)
163
+ expected_columns = {col_name for col_name in expected_table_schema.keys() if not col_name.startswith("_")}
164
+
165
+ # Get existing columns from the async engine
166
+ async with db_engine.connect() as conn:
167
+ existing_columns = await conn.run_sync(_get_table_columns, table_name, db_schema)
168
+
169
+ # Check if all expected columns exist
170
+ missing_columns = expected_columns - existing_columns
171
+ if missing_columns:
172
+ log_warning(f"Missing columns {missing_columns} in table {db_schema}.{table_name}")
173
+ return False
174
+
175
+ return True
176
+ except Exception as e:
177
+ log_error(f"Error validating table schema for {db_schema}.{table_name}: {e}")
178
+ return False
179
+
180
+
181
+ def _get_table_columns(conn, table_name: str, db_schema: str) -> set[str]:
182
+ """Helper function to get table columns using sync inspector."""
183
+ inspector = inspect(conn)
184
+ columns_info = inspector.get_columns(table_name, schema=db_schema)
185
+ return {col["name"] for col in columns_info}
186
+
187
+
116
188
  # -- Metrics util methods --
117
189
  def bulk_upsert_metrics(session: Session, table: Table, metrics_records: list[dict]) -> list[dict]:
118
190
  """Bulk upsert metrics into the database.
@@ -147,6 +219,39 @@ def bulk_upsert_metrics(session: Session, table: Table, metrics_records: list[di
147
219
  return results # type: ignore
148
220
 
149
221
 
222
+ async def abulk_upsert_metrics(session: AsyncSession, table: Table, metrics_records: list[dict]) -> list[dict]:
223
+ """Bulk upsert metrics into the database.
224
+
225
+ Args:
226
+ table (Table): The table to upsert into.
227
+ metrics_records (list[dict]): The metrics records to upsert.
228
+
229
+ Returns:
230
+ list[dict]: The upserted metrics records.
231
+ """
232
+ if not metrics_records:
233
+ return []
234
+
235
+ results = []
236
+ stmt = postgresql.insert(table)
237
+
238
+ # Columns to update in case of conflict
239
+ update_columns = {
240
+ col.name: stmt.excluded[col.name]
241
+ for col in table.columns
242
+ if col.name not in ["id", "date", "created_at", "aggregation_period"]
243
+ }
244
+
245
+ stmt = stmt.on_conflict_do_update(index_elements=["date", "aggregation_period"], set_=update_columns).returning( # type: ignore
246
+ table
247
+ )
248
+ result = await session.execute(stmt, metrics_records)
249
+ results = [row._mapping for row in result.fetchall()]
250
+ await session.commit()
251
+
252
+ return results # type: ignore
253
+
254
+
150
255
  def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
151
256
  """Calculate metrics for the given single date.
152
257
 
@@ -187,7 +292,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
187
292
  all_user_ids = set()
188
293
 
189
294
  for session_type, sessions_count_key, runs_count_key in session_types:
190
- sessions = sessions_data.get(session_type, [])
295
+ sessions = sessions_data.get(session_type, []) or []
191
296
  metrics[sessions_count_key] = len(sessions)
192
297
 
193
298
  for session in sessions:
@@ -208,7 +313,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
208
313
 
209
314
  model_metrics = []
210
315
  for model, count in model_counts.items():
211
- model_id, model_provider = model.split(":")
316
+ model_id, model_provider = model.rsplit(":", 1)
212
317
  model_metrics.append({"model_id": model_id, "model_provider": model_provider, "count": count})
213
318
 
214
319
  metrics["users_count"] = len(all_user_ids)
@@ -278,3 +383,60 @@ def get_dates_to_calculate_metrics_for(starting_date: date) -> list[date]:
278
383
  if days_diff <= 0:
279
384
  return []
280
385
  return [starting_date + timedelta(days=x) for x in range(days_diff)]
386
+
387
+
388
+ # -- Cultural Knowledge util methods --
389
+ def serialize_cultural_knowledge(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
390
+ """Serialize a CulturalKnowledge object for database storage.
391
+
392
+ Converts the model's separate content, categories, and notes fields
393
+ into a single JSON dict for the database content column.
394
+
395
+ Args:
396
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
397
+
398
+ Returns:
399
+ Dict[str, Any]: A dictionary with the content field as JSON containing content, categories, and notes.
400
+ """
401
+ content_dict: Dict[str, Any] = {}
402
+ if cultural_knowledge.content is not None:
403
+ content_dict["content"] = cultural_knowledge.content
404
+ if cultural_knowledge.categories is not None:
405
+ content_dict["categories"] = cultural_knowledge.categories
406
+ if cultural_knowledge.notes is not None:
407
+ content_dict["notes"] = cultural_knowledge.notes
408
+
409
+ return content_dict if content_dict else {}
410
+
411
+
412
+ def deserialize_cultural_knowledge(db_row: Dict[str, Any]) -> CulturalKnowledge:
413
+ """Deserialize a database row to a CulturalKnowledge object.
414
+
415
+ The database stores content as a JSON dict containing content, categories, and notes.
416
+ This method extracts those fields and converts them back to the model format.
417
+
418
+ Args:
419
+ db_row (Dict[str, Any]): The database row as a dictionary.
420
+
421
+ Returns:
422
+ CulturalKnowledge: The cultural knowledge object.
423
+ """
424
+ # Extract content, categories, and notes from the JSON content field
425
+ content_json = db_row.get("content", {}) or {}
426
+
427
+ return CulturalKnowledge.from_dict(
428
+ {
429
+ "id": db_row.get("id"),
430
+ "name": db_row.get("name"),
431
+ "summary": db_row.get("summary"),
432
+ "content": content_json.get("content"),
433
+ "categories": content_json.get("categories"),
434
+ "notes": content_json.get("notes"),
435
+ "metadata": db_row.get("metadata"),
436
+ "input": db_row.get("input"),
437
+ "created_at": db_row.get("created_at"),
438
+ "updated_at": db_row.get("updated_at"),
439
+ "agent_id": db_row.get("agent_id"),
440
+ "team_id": db_row.get("team_id"),
441
+ }
442
+ )