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
agno/db/mongo/mongo.py CHANGED
@@ -10,15 +10,19 @@ from agno.db.mongo.utils import (
10
10
  bulk_upsert_metrics,
11
11
  calculate_date_metrics,
12
12
  create_collection_indexes,
13
+ deserialize_cultural_knowledge_from_db,
13
14
  fetch_all_sessions_data,
14
15
  get_dates_to_calculate_metrics_for,
16
+ serialize_cultural_knowledge_for_db,
15
17
  )
18
+ from agno.db.schemas.culture import CulturalKnowledge
16
19
  from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
17
20
  from agno.db.schemas.knowledge import KnowledgeRow
18
21
  from agno.db.schemas.memory import UserMemory
19
- from agno.db.utils import deserialize_session_json_fields, serialize_session_json_fields
22
+ from agno.db.utils import deserialize_session_json_fields
20
23
  from agno.session import AgentSession, Session, TeamSession, WorkflowSession
21
24
  from agno.utils.log import log_debug, log_error, log_info
25
+ from agno.utils.string import generate_id
22
26
 
23
27
  try:
24
28
  from pymongo import MongoClient, ReturnDocument
@@ -40,6 +44,8 @@ class MongoDb(BaseDb):
40
44
  metrics_collection: Optional[str] = None,
41
45
  eval_collection: Optional[str] = None,
42
46
  knowledge_collection: Optional[str] = None,
47
+ culture_collection: Optional[str] = None,
48
+ id: Optional[str] = None,
43
49
  ):
44
50
  """
45
51
  Interface for interacting with a MongoDB database.
@@ -53,16 +59,26 @@ class MongoDb(BaseDb):
53
59
  metrics_collection (Optional[str]): Name of the collection to store metrics.
54
60
  eval_collection (Optional[str]): Name of the collection to store evaluation runs.
55
61
  knowledge_collection (Optional[str]): Name of the collection to store knowledge documents.
62
+ culture_collection (Optional[str]): Name of the collection to store cultural knowledge.
63
+ id (Optional[str]): ID of the database.
56
64
 
57
65
  Raises:
58
66
  ValueError: If neither db_url nor db_client is provided.
59
67
  """
68
+ if id is None:
69
+ base_seed = db_url or str(db_client)
70
+ db_name_suffix = db_name if db_name is not None else "agno"
71
+ seed = f"{base_seed}#{db_name_suffix}"
72
+ id = generate_id(seed)
73
+
60
74
  super().__init__(
75
+ id=id,
61
76
  session_table=session_collection,
62
77
  memory_table=memory_collection,
63
78
  metrics_table=metrics_collection,
64
79
  eval_table=eval_collection,
65
80
  knowledge_table=knowledge_collection,
81
+ culture_table=culture_collection,
66
82
  )
67
83
 
68
84
  _client: Optional[MongoClient] = db_client
@@ -84,6 +100,31 @@ class MongoDb(BaseDb):
84
100
  return self._database
85
101
 
86
102
  # -- DB methods --
103
+ def table_exists(self, table_name: str) -> bool:
104
+ """Check if a collection with the given name exists in the MongoDB database.
105
+
106
+ Args:
107
+ table_name: Name of the collection to check
108
+
109
+ Returns:
110
+ bool: True if the collection exists in the database, False otherwise
111
+ """
112
+ return table_name in self.database.list_collection_names()
113
+
114
+ def _create_all_tables(self):
115
+ """Create all configured MongoDB collections if they don't exist."""
116
+ collections_to_create = [
117
+ ("sessions", self.session_table_name),
118
+ ("memories", self.memory_table_name),
119
+ ("metrics", self.metrics_table_name),
120
+ ("evals", self.eval_table_name),
121
+ ("knowledge", self.knowledge_table_name),
122
+ ("culture", self.culture_table_name),
123
+ ]
124
+
125
+ for collection_type, collection_name in collections_to_create:
126
+ if collection_name and not self.table_exists(collection_name):
127
+ self._get_collection(collection_type, create_collection_if_not_found=True)
87
128
 
88
129
  def _get_collection(
89
130
  self, table_type: str, create_collection_if_not_found: Optional[bool] = True
@@ -151,6 +192,17 @@ class MongoDb(BaseDb):
151
192
  )
152
193
  return self.knowledge_collection
153
194
 
195
+ if table_type == "culture":
196
+ if not hasattr(self, "culture_collection"):
197
+ if self.culture_table_name is None:
198
+ raise ValueError("Culture collection was not provided on initialization")
199
+ self.culture_collection = self._get_or_create_collection(
200
+ collection_name=self.culture_table_name,
201
+ collection_type="culture",
202
+ create_collection_if_not_found=create_collection_if_not_found,
203
+ )
204
+ return self.culture_collection
205
+
154
206
  raise ValueError(f"Unknown table type: {table_type}")
155
207
 
156
208
  def _get_or_create_collection(
@@ -184,6 +236,14 @@ class MongoDb(BaseDb):
184
236
  log_error(f"Error getting collection {collection_name}: {e}")
185
237
  raise
186
238
 
239
+ def get_latest_schema_version(self):
240
+ """Get the latest version of the database schema."""
241
+ pass
242
+
243
+ def upsert_schema_version(self, version: str) -> None:
244
+ """Upsert the schema version into the database."""
245
+ pass
246
+
187
247
  # -- Session methods --
188
248
 
189
249
  def delete_session(self, session_id: str) -> bool:
@@ -213,7 +273,7 @@ class MongoDb(BaseDb):
213
273
 
214
274
  except Exception as e:
215
275
  log_error(f"Error deleting session: {e}")
216
- return False
276
+ raise e
217
277
 
218
278
  def delete_sessions(self, session_ids: List[str]) -> None:
219
279
  """Delete multiple sessions from the database.
@@ -231,6 +291,7 @@ class MongoDb(BaseDb):
231
291
 
232
292
  except Exception as e:
233
293
  log_error(f"Error deleting sessions: {e}")
294
+ raise e
234
295
 
235
296
  def get_session(
236
297
  self,
@@ -243,8 +304,8 @@ class MongoDb(BaseDb):
243
304
 
244
305
  Args:
245
306
  session_id (str): The ID of the session to get.
307
+ session_type (SessionType): The type of session to get.
246
308
  user_id (Optional[str]): The ID of the user to get the session for.
247
- session_type (Optional[SessionType]): The type of session to get.
248
309
  deserialize (Optional[bool]): Whether to serialize the session. Defaults to True.
249
310
 
250
311
  Returns:
@@ -263,28 +324,27 @@ class MongoDb(BaseDb):
263
324
  query = {"session_id": session_id}
264
325
  if user_id is not None:
265
326
  query["user_id"] = user_id
266
- if session_type is not None:
267
- query["session_type"] = session_type
268
327
 
269
328
  result = collection.find_one(query)
270
329
  if result is None:
271
330
  return None
272
331
 
273
332
  session = deserialize_session_json_fields(result)
274
-
275
333
  if not deserialize:
276
334
  return session
277
335
 
278
- if session_type == SessionType.AGENT.value:
336
+ if session_type == SessionType.AGENT:
279
337
  return AgentSession.from_dict(session)
280
- elif session_type == SessionType.TEAM.value:
338
+ elif session_type == SessionType.TEAM:
281
339
  return TeamSession.from_dict(session)
282
- else:
340
+ elif session_type == SessionType.WORKFLOW:
283
341
  return WorkflowSession.from_dict(session)
342
+ else:
343
+ raise ValueError(f"Invalid session type: {session_type}")
284
344
 
285
345
  except Exception as e:
286
346
  log_error(f"Exception reading session: {e}")
287
- return None
347
+ raise e
288
348
 
289
349
  def get_sessions(
290
350
  self,
@@ -372,7 +432,6 @@ class MongoDb(BaseDb):
372
432
  records = list(cursor)
373
433
  if records is None:
374
434
  return [] if deserialize else ([], 0)
375
-
376
435
  sessions_raw = [deserialize_session_json_fields(record) for record in records]
377
436
 
378
437
  if not deserialize:
@@ -397,7 +456,7 @@ class MongoDb(BaseDb):
397
456
 
398
457
  except Exception as e:
399
458
  log_error(f"Exception reading sessions: {e}")
400
- return [] if deserialize else ([], 0)
459
+ raise e
401
460
 
402
461
  def rename_session(
403
462
  self, session_id: str, session_type: SessionType, session_name: str, deserialize: Optional[bool] = True
@@ -455,7 +514,7 @@ class MongoDb(BaseDb):
455
514
 
456
515
  except Exception as e:
457
516
  log_error(f"Exception renaming session: {e}")
458
- return None
517
+ raise e
459
518
 
460
519
  def upsert_session(
461
520
  self, session: Session, deserialize: Optional[bool] = True
@@ -476,25 +535,25 @@ class MongoDb(BaseDb):
476
535
  if collection is None:
477
536
  return None
478
537
 
479
- serialized_session_dict = serialize_session_json_fields(session.to_dict())
538
+ session_dict = session.to_dict()
480
539
 
481
540
  if isinstance(session, AgentSession):
482
541
  record = {
483
- "session_id": serialized_session_dict.get("session_id"),
542
+ "session_id": session_dict.get("session_id"),
484
543
  "session_type": SessionType.AGENT.value,
485
- "agent_id": serialized_session_dict.get("agent_id"),
486
- "user_id": serialized_session_dict.get("user_id"),
487
- "runs": serialized_session_dict.get("runs"),
488
- "agent_data": serialized_session_dict.get("agent_data"),
489
- "session_data": serialized_session_dict.get("session_data"),
490
- "summary": serialized_session_dict.get("summary"),
491
- "metadata": serialized_session_dict.get("metadata"),
492
- "created_at": serialized_session_dict.get("created_at"),
544
+ "agent_id": session_dict.get("agent_id"),
545
+ "user_id": session_dict.get("user_id"),
546
+ "runs": session_dict.get("runs"),
547
+ "agent_data": session_dict.get("agent_data"),
548
+ "session_data": session_dict.get("session_data"),
549
+ "summary": session_dict.get("summary"),
550
+ "metadata": session_dict.get("metadata"),
551
+ "created_at": session_dict.get("created_at"),
493
552
  "updated_at": int(time.time()),
494
553
  }
495
554
 
496
555
  result = collection.find_one_and_replace(
497
- filter={"session_id": serialized_session_dict.get("session_id")},
556
+ filter={"session_id": session_dict.get("session_id")},
498
557
  replacement=record,
499
558
  upsert=True,
500
559
  return_document=ReturnDocument.AFTER,
@@ -502,7 +561,7 @@ class MongoDb(BaseDb):
502
561
  if not result:
503
562
  return None
504
563
 
505
- session = deserialize_session_json_fields(result) # type: ignore
564
+ session = result # type: ignore
506
565
 
507
566
  if not deserialize:
508
567
  return session
@@ -511,21 +570,21 @@ class MongoDb(BaseDb):
511
570
 
512
571
  elif isinstance(session, TeamSession):
513
572
  record = {
514
- "session_id": serialized_session_dict.get("session_id"),
573
+ "session_id": session_dict.get("session_id"),
515
574
  "session_type": SessionType.TEAM.value,
516
- "team_id": serialized_session_dict.get("team_id"),
517
- "user_id": serialized_session_dict.get("user_id"),
518
- "runs": serialized_session_dict.get("runs"),
519
- "team_data": serialized_session_dict.get("team_data"),
520
- "session_data": serialized_session_dict.get("session_data"),
521
- "summary": serialized_session_dict.get("summary"),
522
- "metadata": serialized_session_dict.get("metadata"),
523
- "created_at": serialized_session_dict.get("created_at"),
575
+ "team_id": session_dict.get("team_id"),
576
+ "user_id": session_dict.get("user_id"),
577
+ "runs": session_dict.get("runs"),
578
+ "team_data": session_dict.get("team_data"),
579
+ "session_data": session_dict.get("session_data"),
580
+ "summary": session_dict.get("summary"),
581
+ "metadata": session_dict.get("metadata"),
582
+ "created_at": session_dict.get("created_at"),
524
583
  "updated_at": int(time.time()),
525
584
  }
526
585
 
527
586
  result = collection.find_one_and_replace(
528
- filter={"session_id": serialized_session_dict.get("session_id")},
587
+ filter={"session_id": session_dict.get("session_id")},
529
588
  replacement=record,
530
589
  upsert=True,
531
590
  return_document=ReturnDocument.AFTER,
@@ -533,7 +592,8 @@ class MongoDb(BaseDb):
533
592
  if not result:
534
593
  return None
535
594
 
536
- session = deserialize_session_json_fields(result) # type: ignore
595
+ # MongoDB stores native objects, no deserialization needed for document fields
596
+ session = result # type: ignore
537
597
 
538
598
  if not deserialize:
539
599
  return session
@@ -542,21 +602,21 @@ class MongoDb(BaseDb):
542
602
 
543
603
  else:
544
604
  record = {
545
- "session_id": serialized_session_dict.get("session_id"),
605
+ "session_id": session_dict.get("session_id"),
546
606
  "session_type": SessionType.WORKFLOW.value,
547
- "workflow_id": serialized_session_dict.get("workflow_id"),
548
- "user_id": serialized_session_dict.get("user_id"),
549
- "runs": serialized_session_dict.get("runs"),
550
- "workflow_data": serialized_session_dict.get("workflow_data"),
551
- "session_data": serialized_session_dict.get("session_data"),
552
- "summary": serialized_session_dict.get("summary"),
553
- "metadata": serialized_session_dict.get("metadata"),
554
- "created_at": serialized_session_dict.get("created_at"),
607
+ "workflow_id": session_dict.get("workflow_id"),
608
+ "user_id": session_dict.get("user_id"),
609
+ "runs": session_dict.get("runs"),
610
+ "workflow_data": session_dict.get("workflow_data"),
611
+ "session_data": session_dict.get("session_data"),
612
+ "summary": session_dict.get("summary"),
613
+ "metadata": session_dict.get("metadata"),
614
+ "created_at": session_dict.get("created_at"),
555
615
  "updated_at": int(time.time()),
556
616
  }
557
617
 
558
618
  result = collection.find_one_and_replace(
559
- filter={"session_id": serialized_session_dict.get("session_id")},
619
+ filter={"session_id": session_dict.get("session_id")},
560
620
  replacement=record,
561
621
  upsert=True,
562
622
  return_document=ReturnDocument.AFTER,
@@ -564,7 +624,7 @@ class MongoDb(BaseDb):
564
624
  if not result:
565
625
  return None
566
626
 
567
- session = deserialize_session_json_fields(result) # type: ignore
627
+ session = result # type: ignore
568
628
 
569
629
  if not deserialize:
570
630
  return session
@@ -573,15 +633,158 @@ class MongoDb(BaseDb):
573
633
 
574
634
  except Exception as e:
575
635
  log_error(f"Exception upserting session: {e}")
576
- return None
636
+ raise e
637
+
638
+ def upsert_sessions(
639
+ self, sessions: List[Session], deserialize: Optional[bool] = True, preserve_updated_at: bool = False
640
+ ) -> List[Union[Session, Dict[str, Any]]]:
641
+ """
642
+ Bulk upsert multiple sessions for improved performance on large datasets.
643
+
644
+ Args:
645
+ sessions (List[Session]): List of sessions to upsert.
646
+ deserialize (Optional[bool]): Whether to deserialize the sessions. Defaults to True.
647
+ preserve_updated_at (bool): If True, preserve the updated_at from the session object.
648
+
649
+ Returns:
650
+ List[Union[Session, Dict[str, Any]]]: List of upserted sessions.
651
+
652
+ Raises:
653
+ Exception: If an error occurs during bulk upsert.
654
+ """
655
+ if not sessions:
656
+ return []
657
+
658
+ try:
659
+ collection = self._get_collection(table_type="sessions", create_collection_if_not_found=True)
660
+ if collection is None:
661
+ log_info("Sessions collection not available, falling back to individual upserts")
662
+ return [
663
+ result
664
+ for session in sessions
665
+ if session is not None
666
+ for result in [self.upsert_session(session, deserialize=deserialize)]
667
+ if result is not None
668
+ ]
669
+
670
+ from pymongo import ReplaceOne
671
+
672
+ operations = []
673
+ results: List[Union[Session, Dict[str, Any]]] = []
674
+
675
+ for session in sessions:
676
+ if session is None:
677
+ continue
678
+
679
+ session_dict = session.to_dict()
680
+
681
+ # Use preserved updated_at if flag is set and value exists, otherwise use current time
682
+ updated_at = session_dict.get("updated_at") if preserve_updated_at else int(time.time())
683
+
684
+ if isinstance(session, AgentSession):
685
+ record = {
686
+ "session_id": session_dict.get("session_id"),
687
+ "session_type": SessionType.AGENT.value,
688
+ "agent_id": session_dict.get("agent_id"),
689
+ "user_id": session_dict.get("user_id"),
690
+ "runs": session_dict.get("runs"),
691
+ "agent_data": session_dict.get("agent_data"),
692
+ "session_data": session_dict.get("session_data"),
693
+ "summary": session_dict.get("summary"),
694
+ "metadata": session_dict.get("metadata"),
695
+ "created_at": session_dict.get("created_at"),
696
+ "updated_at": updated_at,
697
+ }
698
+ elif isinstance(session, TeamSession):
699
+ record = {
700
+ "session_id": session_dict.get("session_id"),
701
+ "session_type": SessionType.TEAM.value,
702
+ "team_id": session_dict.get("team_id"),
703
+ "user_id": session_dict.get("user_id"),
704
+ "runs": session_dict.get("runs"),
705
+ "team_data": session_dict.get("team_data"),
706
+ "session_data": session_dict.get("session_data"),
707
+ "summary": session_dict.get("summary"),
708
+ "metadata": session_dict.get("metadata"),
709
+ "created_at": session_dict.get("created_at"),
710
+ "updated_at": updated_at,
711
+ }
712
+ elif isinstance(session, WorkflowSession):
713
+ record = {
714
+ "session_id": session_dict.get("session_id"),
715
+ "session_type": SessionType.WORKFLOW.value,
716
+ "workflow_id": session_dict.get("workflow_id"),
717
+ "user_id": session_dict.get("user_id"),
718
+ "runs": session_dict.get("runs"),
719
+ "workflow_data": session_dict.get("workflow_data"),
720
+ "session_data": session_dict.get("session_data"),
721
+ "summary": session_dict.get("summary"),
722
+ "metadata": session_dict.get("metadata"),
723
+ "created_at": session_dict.get("created_at"),
724
+ "updated_at": updated_at,
725
+ }
726
+ else:
727
+ continue
728
+
729
+ operations.append(
730
+ ReplaceOne(filter={"session_id": record["session_id"]}, replacement=record, upsert=True)
731
+ )
732
+
733
+ if operations:
734
+ # Execute bulk write
735
+ collection.bulk_write(operations)
736
+
737
+ # Fetch the results
738
+ session_ids = [session.session_id for session in sessions if session and session.session_id]
739
+ cursor = collection.find({"session_id": {"$in": session_ids}})
740
+
741
+ for doc in cursor:
742
+ session_dict = doc
743
+
744
+ if deserialize:
745
+ session_type = doc.get("session_type")
746
+ if session_type == SessionType.AGENT.value:
747
+ deserialized_agent_session = AgentSession.from_dict(session_dict)
748
+ if deserialized_agent_session is None:
749
+ continue
750
+ results.append(deserialized_agent_session)
751
+
752
+ elif session_type == SessionType.TEAM.value:
753
+ deserialized_team_session = TeamSession.from_dict(session_dict)
754
+ if deserialized_team_session is None:
755
+ continue
756
+ results.append(deserialized_team_session)
757
+
758
+ elif session_type == SessionType.WORKFLOW.value:
759
+ deserialized_workflow_session = WorkflowSession.from_dict(session_dict)
760
+ if deserialized_workflow_session is None:
761
+ continue
762
+ results.append(deserialized_workflow_session)
763
+ else:
764
+ results.append(session_dict)
765
+
766
+ return results
767
+
768
+ except Exception as e:
769
+ log_error(f"Exception during bulk session upsert, falling back to individual upserts: {e}")
770
+
771
+ # Fallback to individual upserts
772
+ return [
773
+ result
774
+ for session in sessions
775
+ if session is not None
776
+ for result in [self.upsert_session(session, deserialize=deserialize)]
777
+ if result is not None
778
+ ]
577
779
 
578
780
  # -- Memory methods --
579
781
 
580
- def delete_user_memory(self, memory_id: str):
782
+ def delete_user_memory(self, memory_id: str, user_id: Optional[str] = None):
581
783
  """Delete a user memory from the database.
582
784
 
583
785
  Args:
584
786
  memory_id (str): The ID of the memory to delete.
787
+ user_id (Optional[str]): The ID of the user to verify ownership. If provided, only delete if the memory belongs to this user.
585
788
 
586
789
  Returns:
587
790
  bool: True if the memory was deleted, False otherwise.
@@ -594,7 +797,11 @@ class MongoDb(BaseDb):
594
797
  if collection is None:
595
798
  return
596
799
 
597
- result = collection.delete_one({"memory_id": memory_id})
800
+ query = {"memory_id": memory_id}
801
+ if user_id is not None:
802
+ query["user_id"] = user_id
803
+
804
+ result = collection.delete_one(query)
598
805
 
599
806
  success = result.deleted_count > 0
600
807
  if success:
@@ -604,12 +811,14 @@ class MongoDb(BaseDb):
604
811
 
605
812
  except Exception as e:
606
813
  log_error(f"Error deleting memory: {e}")
814
+ raise e
607
815
 
608
- def delete_user_memories(self, memory_ids: List[str]) -> None:
816
+ def delete_user_memories(self, memory_ids: List[str], user_id: Optional[str] = None) -> None:
609
817
  """Delete user memories from the database.
610
818
 
611
819
  Args:
612
820
  memory_ids (List[str]): The IDs of the memories to delete.
821
+ user_id (Optional[str]): The ID of the user to verify ownership. If provided, only delete memories that belong to this user.
613
822
 
614
823
  Raises:
615
824
  Exception: If there is an error deleting the memories.
@@ -619,13 +828,18 @@ class MongoDb(BaseDb):
619
828
  if collection is None:
620
829
  return
621
830
 
622
- result = collection.delete_many({"memory_id": {"$in": memory_ids}})
831
+ query: Dict[str, Any] = {"memory_id": {"$in": memory_ids}}
832
+ if user_id is not None:
833
+ query["user_id"] = user_id
834
+
835
+ result = collection.delete_many(query)
623
836
 
624
837
  if result.deleted_count == 0:
625
838
  log_debug(f"No memories found with ids: {memory_ids}")
626
839
 
627
840
  except Exception as e:
628
841
  log_error(f"Error deleting memories: {e}")
842
+ raise e
629
843
 
630
844
  def get_all_memory_topics(self) -> List[str]:
631
845
  """Get all memory topics from the database.
@@ -641,19 +855,22 @@ class MongoDb(BaseDb):
641
855
  if collection is None:
642
856
  return []
643
857
 
644
- topics = collection.distinct("topics")
858
+ topics = collection.distinct("topics", {})
645
859
  return [topic for topic in topics if topic]
646
860
 
647
861
  except Exception as e:
648
862
  log_error(f"Exception reading from collection: {e}")
649
- return []
863
+ raise e
650
864
 
651
- def get_user_memory(self, memory_id: str, deserialize: Optional[bool] = True) -> Optional[UserMemory]:
865
+ def get_user_memory(
866
+ self, memory_id: str, deserialize: Optional[bool] = True, user_id: Optional[str] = None
867
+ ) -> Optional[UserMemory]:
652
868
  """Get a memory from the database.
653
869
 
654
870
  Args:
655
871
  memory_id (str): The ID of the memory to get.
656
872
  deserialize (Optional[bool]): Whether to serialize the memory. Defaults to True.
873
+ user_id (Optional[str]): The ID of the user to verify ownership. If provided, only return the memory if it belongs to this user.
657
874
 
658
875
  Returns:
659
876
  Optional[UserMemory]:
@@ -668,7 +885,11 @@ class MongoDb(BaseDb):
668
885
  if collection is None:
669
886
  return None
670
887
 
671
- result = collection.find_one({"memory_id": memory_id})
888
+ query = {"memory_id": memory_id}
889
+ if user_id is not None:
890
+ query["user_id"] = user_id
891
+
892
+ result = collection.find_one(query)
672
893
  if result is None or not deserialize:
673
894
  return result
674
895
 
@@ -678,7 +899,7 @@ class MongoDb(BaseDb):
678
899
 
679
900
  except Exception as e:
680
901
  log_error(f"Exception reading from collection: {e}")
681
- return None
902
+ raise e
682
903
 
683
904
  def get_user_memories(
684
905
  self,
@@ -757,7 +978,7 @@ class MongoDb(BaseDb):
757
978
 
758
979
  except Exception as e:
759
980
  log_error(f"Exception reading from collection: {e}")
760
- return []
981
+ raise e
761
982
 
762
983
  def get_user_memory_stats(
763
984
  self,
@@ -781,8 +1002,10 @@ class MongoDb(BaseDb):
781
1002
  if collection is None:
782
1003
  return [], 0
783
1004
 
1005
+ match_stage = {"user_id": {"$ne": None}}
1006
+
784
1007
  pipeline = [
785
- {"$match": {"user_id": {"$ne": None}}},
1008
+ {"$match": match_stage},
786
1009
  {
787
1010
  "$group": {
788
1011
  "_id": "$user_id",
@@ -819,7 +1042,7 @@ class MongoDb(BaseDb):
819
1042
 
820
1043
  except Exception as e:
821
1044
  log_error(f"Exception getting user memory stats: {e}")
822
- return [], 0
1045
+ raise e
823
1046
 
824
1047
  def upsert_user_memory(
825
1048
  self, memory: UserMemory, deserialize: Optional[bool] = True
@@ -870,7 +1093,99 @@ class MongoDb(BaseDb):
870
1093
 
871
1094
  except Exception as e:
872
1095
  log_error(f"Exception upserting user memory: {e}")
873
- return None
1096
+ raise e
1097
+
1098
+ def upsert_memories(
1099
+ self, memories: List[UserMemory], deserialize: Optional[bool] = True, preserve_updated_at: bool = False
1100
+ ) -> List[Union[UserMemory, Dict[str, Any]]]:
1101
+ """
1102
+ Bulk upsert multiple user memories for improved performance on large datasets.
1103
+
1104
+ Args:
1105
+ memories (List[UserMemory]): List of memories to upsert.
1106
+ deserialize (Optional[bool]): Whether to deserialize the memories. Defaults to True.
1107
+
1108
+ Returns:
1109
+ List[Union[UserMemory, Dict[str, Any]]]: List of upserted memories.
1110
+
1111
+ Raises:
1112
+ Exception: If an error occurs during bulk upsert.
1113
+ """
1114
+ if not memories:
1115
+ return []
1116
+
1117
+ try:
1118
+ collection = self._get_collection(table_type="memories", create_collection_if_not_found=True)
1119
+ if collection is None:
1120
+ log_info("Memories collection not available, falling back to individual upserts")
1121
+ return [
1122
+ result
1123
+ for memory in memories
1124
+ if memory is not None
1125
+ for result in [self.upsert_user_memory(memory, deserialize=deserialize)]
1126
+ if result is not None
1127
+ ]
1128
+
1129
+ from pymongo import ReplaceOne
1130
+
1131
+ operations = []
1132
+ results: List[Union[UserMemory, Dict[str, Any]]] = []
1133
+
1134
+ current_time = int(time.time())
1135
+ for memory in memories:
1136
+ if memory is None:
1137
+ continue
1138
+
1139
+ if memory.memory_id is None:
1140
+ memory.memory_id = str(uuid4())
1141
+
1142
+ # Use preserved updated_at if flag is set and value exists, otherwise use current time
1143
+ updated_at = memory.updated_at if preserve_updated_at else current_time
1144
+
1145
+ record = {
1146
+ "user_id": memory.user_id,
1147
+ "agent_id": memory.agent_id,
1148
+ "team_id": memory.team_id,
1149
+ "memory_id": memory.memory_id,
1150
+ "memory": memory.memory,
1151
+ "input": memory.input,
1152
+ "feedback": memory.feedback,
1153
+ "topics": memory.topics,
1154
+ "created_at": memory.created_at,
1155
+ "updated_at": updated_at,
1156
+ }
1157
+
1158
+ operations.append(ReplaceOne(filter={"memory_id": memory.memory_id}, replacement=record, upsert=True))
1159
+
1160
+ if operations:
1161
+ # Execute bulk write
1162
+ collection.bulk_write(operations)
1163
+
1164
+ # Fetch the results
1165
+ memory_ids = [memory.memory_id for memory in memories if memory and memory.memory_id]
1166
+ cursor = collection.find({"memory_id": {"$in": memory_ids}})
1167
+
1168
+ for doc in cursor:
1169
+ if deserialize:
1170
+ # Remove MongoDB's _id field before creating UserMemory object
1171
+ doc_filtered = {k: v for k, v in doc.items() if k != "_id"}
1172
+ results.append(UserMemory.from_dict(doc_filtered))
1173
+ else:
1174
+ results.append(doc)
1175
+
1176
+ return results
1177
+
1178
+ except Exception as e:
1179
+ log_error(f"Exception during bulk memory upsert, falling back to individual upserts: {e}")
1180
+
1181
+ # Fallback to individual upserts
1182
+ return [
1183
+ result
1184
+ for memory in memories
1185
+ if memory is not None
1186
+ for result in [self.upsert_user_memory(memory, deserialize=deserialize)]
1187
+ if result is not None
1188
+ ]
874
1189
 
875
1190
  def clear_memories(self) -> None:
876
1191
  """Delete all memories from the database.
@@ -886,9 +1201,213 @@ class MongoDb(BaseDb):
886
1201
  collection.delete_many({})
887
1202
 
888
1203
  except Exception as e:
889
- from agno.utils.log import log_warning
1204
+ log_error(f"Exception deleting all memories: {e}")
1205
+ raise e
1206
+
1207
+ # -- Cultural Knowledge methods --
1208
+ def clear_cultural_knowledge(self) -> None:
1209
+ """Delete all cultural knowledge from the database.
1210
+
1211
+ Raises:
1212
+ Exception: If an error occurs during deletion.
1213
+ """
1214
+ try:
1215
+ collection = self._get_collection(table_type="culture")
1216
+ if collection is None:
1217
+ return
1218
+
1219
+ collection.delete_many({})
1220
+
1221
+ except Exception as e:
1222
+ log_error(f"Exception deleting all cultural knowledge: {e}")
1223
+ raise e
1224
+
1225
+ def delete_cultural_knowledge(self, id: str) -> None:
1226
+ """Delete cultural knowledge by ID.
1227
+
1228
+ Args:
1229
+ id (str): The ID of the cultural knowledge to delete.
1230
+
1231
+ Raises:
1232
+ Exception: If an error occurs during deletion.
1233
+ """
1234
+ try:
1235
+ collection = self._get_collection(table_type="culture")
1236
+ if collection is None:
1237
+ return
1238
+
1239
+ collection.delete_one({"id": id})
1240
+ log_debug(f"Deleted cultural knowledge with ID: {id}")
1241
+
1242
+ except Exception as e:
1243
+ log_error(f"Error deleting cultural knowledge: {e}")
1244
+ raise e
1245
+
1246
+ def get_cultural_knowledge(
1247
+ self, id: str, deserialize: Optional[bool] = True
1248
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
1249
+ """Get cultural knowledge by ID.
1250
+
1251
+ Args:
1252
+ id (str): The ID of the cultural knowledge to retrieve.
1253
+ deserialize (Optional[bool]): Whether to deserialize to CulturalKnowledge object. Defaults to True.
1254
+
1255
+ Returns:
1256
+ Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The cultural knowledge if found, None otherwise.
1257
+
1258
+ Raises:
1259
+ Exception: If an error occurs during retrieval.
1260
+ """
1261
+ try:
1262
+ collection = self._get_collection(table_type="culture")
1263
+ if collection is None:
1264
+ return None
1265
+
1266
+ result = collection.find_one({"id": id})
1267
+ if result is None:
1268
+ return None
1269
+
1270
+ # Remove MongoDB's _id field
1271
+ result_filtered = {k: v for k, v in result.items() if k != "_id"}
1272
+
1273
+ if not deserialize:
1274
+ return result_filtered
1275
+
1276
+ return deserialize_cultural_knowledge_from_db(result_filtered)
1277
+
1278
+ except Exception as e:
1279
+ log_error(f"Error getting cultural knowledge: {e}")
1280
+ raise e
1281
+
1282
+ def get_all_cultural_knowledge(
1283
+ self,
1284
+ agent_id: Optional[str] = None,
1285
+ team_id: Optional[str] = None,
1286
+ name: Optional[str] = None,
1287
+ limit: Optional[int] = None,
1288
+ page: Optional[int] = None,
1289
+ sort_by: Optional[str] = None,
1290
+ sort_order: Optional[str] = None,
1291
+ deserialize: Optional[bool] = True,
1292
+ ) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
1293
+ """Get all cultural knowledge with filtering and pagination.
1294
+
1295
+ Args:
1296
+ agent_id (Optional[str]): Filter by agent ID.
1297
+ team_id (Optional[str]): Filter by team ID.
1298
+ name (Optional[str]): Filter by name (case-insensitive partial match).
1299
+ limit (Optional[int]): Maximum number of results to return.
1300
+ page (Optional[int]): Page number for pagination.
1301
+ sort_by (Optional[str]): Field to sort by.
1302
+ sort_order (Optional[str]): Sort order ('asc' or 'desc').
1303
+ deserialize (Optional[bool]): Whether to deserialize to CulturalKnowledge objects. Defaults to True.
1304
+
1305
+ Returns:
1306
+ Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
1307
+ - When deserialize=True: List of CulturalKnowledge objects
1308
+ - When deserialize=False: Tuple with list of dictionaries and total count
1309
+
1310
+ Raises:
1311
+ Exception: If an error occurs during retrieval.
1312
+ """
1313
+ try:
1314
+ collection = self._get_collection(table_type="culture")
1315
+ if collection is None:
1316
+ if not deserialize:
1317
+ return [], 0
1318
+ return []
890
1319
 
891
- log_warning(f"Exception deleting all memories: {e}")
1320
+ # Build query
1321
+ query: Dict[str, Any] = {}
1322
+ if agent_id is not None:
1323
+ query["agent_id"] = agent_id
1324
+ if team_id is not None:
1325
+ query["team_id"] = team_id
1326
+ if name is not None:
1327
+ query["name"] = {"$regex": name, "$options": "i"}
1328
+
1329
+ # Get total count for pagination
1330
+ total_count = collection.count_documents(query)
1331
+
1332
+ # Apply sorting
1333
+ sort_criteria = apply_sorting({}, sort_by, sort_order)
1334
+
1335
+ # Apply pagination
1336
+ query_args = apply_pagination({}, limit, page)
1337
+
1338
+ cursor = collection.find(query)
1339
+ if sort_criteria:
1340
+ cursor = cursor.sort(sort_criteria)
1341
+ if query_args.get("skip"):
1342
+ cursor = cursor.skip(query_args["skip"])
1343
+ if query_args.get("limit"):
1344
+ cursor = cursor.limit(query_args["limit"])
1345
+
1346
+ # Remove MongoDB's _id field from all results
1347
+ results_filtered = [{k: v for k, v in item.items() if k != "_id"} for item in cursor]
1348
+
1349
+ if not deserialize:
1350
+ return results_filtered, total_count
1351
+
1352
+ return [deserialize_cultural_knowledge_from_db(item) for item in results_filtered]
1353
+
1354
+ except Exception as e:
1355
+ log_error(f"Error getting all cultural knowledge: {e}")
1356
+ raise e
1357
+
1358
+ def upsert_cultural_knowledge(
1359
+ self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
1360
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
1361
+ """Upsert cultural knowledge in MongoDB.
1362
+
1363
+ Args:
1364
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge to upsert.
1365
+ deserialize (Optional[bool]): Whether to deserialize the result. Defaults to True.
1366
+
1367
+ Returns:
1368
+ Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The upserted cultural knowledge.
1369
+
1370
+ Raises:
1371
+ Exception: If an error occurs during upsert.
1372
+ """
1373
+ try:
1374
+ collection = self._get_collection(table_type="culture", create_collection_if_not_found=True)
1375
+ if collection is None:
1376
+ return None
1377
+
1378
+ # Serialize content, categories, and notes into a dict for DB storage
1379
+ content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
1380
+
1381
+ # Create the document with serialized content
1382
+ update_doc = {
1383
+ "id": cultural_knowledge.id,
1384
+ "name": cultural_knowledge.name,
1385
+ "summary": cultural_knowledge.summary,
1386
+ "content": content_dict if content_dict else None,
1387
+ "metadata": cultural_knowledge.metadata,
1388
+ "input": cultural_knowledge.input,
1389
+ "created_at": cultural_knowledge.created_at,
1390
+ "updated_at": int(time.time()),
1391
+ "agent_id": cultural_knowledge.agent_id,
1392
+ "team_id": cultural_knowledge.team_id,
1393
+ }
1394
+
1395
+ result = collection.replace_one({"id": cultural_knowledge.id}, update_doc, upsert=True)
1396
+
1397
+ if result.upserted_id:
1398
+ update_doc["_id"] = result.upserted_id
1399
+
1400
+ # Remove MongoDB's _id field
1401
+ doc_filtered = {k: v for k, v in update_doc.items() if k != "_id"}
1402
+
1403
+ if not deserialize:
1404
+ return doc_filtered
1405
+
1406
+ return deserialize_cultural_knowledge_from_db(doc_filtered)
1407
+
1408
+ except Exception as e:
1409
+ log_error(f"Error upserting cultural knowledge: {e}")
1410
+ raise e
892
1411
 
893
1412
  # -- Metrics methods --
894
1413
 
@@ -1040,7 +1559,7 @@ class MongoDb(BaseDb):
1040
1559
 
1041
1560
  except Exception as e:
1042
1561
  log_error(f"Error getting metrics: {e}")
1043
- return [], None
1562
+ raise e
1044
1563
 
1045
1564
  # -- Knowledge methods --
1046
1565
 
@@ -1064,7 +1583,7 @@ class MongoDb(BaseDb):
1064
1583
 
1065
1584
  except Exception as e:
1066
1585
  log_error(f"Error deleting knowledge content: {e}")
1067
- raise
1586
+ raise e
1068
1587
 
1069
1588
  def get_knowledge_content(self, id: str) -> Optional[KnowledgeRow]:
1070
1589
  """Get a knowledge row from the database.
@@ -1091,7 +1610,7 @@ class MongoDb(BaseDb):
1091
1610
 
1092
1611
  except Exception as e:
1093
1612
  log_error(f"Error getting knowledge content: {e}")
1094
- return None
1613
+ raise e
1095
1614
 
1096
1615
  def get_knowledge_contents(
1097
1616
  self,
@@ -1146,7 +1665,7 @@ class MongoDb(BaseDb):
1146
1665
 
1147
1666
  except Exception as e:
1148
1667
  log_error(f"Error getting knowledge contents: {e}")
1149
- return [], 0
1668
+ raise e
1150
1669
 
1151
1670
  def upsert_knowledge_content(self, knowledge_row: KnowledgeRow):
1152
1671
  """Upsert knowledge content in the database.
@@ -1172,7 +1691,7 @@ class MongoDb(BaseDb):
1172
1691
 
1173
1692
  except Exception as e:
1174
1693
  log_error(f"Error upserting knowledge content: {e}")
1175
- return None
1694
+ raise e
1176
1695
 
1177
1696
  # -- Eval methods --
1178
1697
 
@@ -1196,7 +1715,7 @@ class MongoDb(BaseDb):
1196
1715
 
1197
1716
  except Exception as e:
1198
1717
  log_error(f"Error creating eval run: {e}")
1199
- return None
1718
+ raise e
1200
1719
 
1201
1720
  def delete_eval_run(self, eval_run_id: str) -> None:
1202
1721
  """Delete an eval run from the database."""
@@ -1214,7 +1733,7 @@ class MongoDb(BaseDb):
1214
1733
 
1215
1734
  except Exception as e:
1216
1735
  log_error(f"Error deleting eval run {eval_run_id}: {e}")
1217
- raise
1736
+ raise e
1218
1737
 
1219
1738
  def delete_eval_runs(self, eval_run_ids: List[str]) -> None:
1220
1739
  """Delete multiple eval runs from the database."""
@@ -1232,7 +1751,7 @@ class MongoDb(BaseDb):
1232
1751
 
1233
1752
  except Exception as e:
1234
1753
  log_error(f"Error deleting eval runs {eval_run_ids}: {e}")
1235
- raise
1754
+ raise e
1236
1755
 
1237
1756
  def get_eval_run_raw(self, eval_run_id: str) -> Optional[Dict[str, Any]]:
1238
1757
  """Get an eval run from the database as a raw dictionary."""
@@ -1246,7 +1765,7 @@ class MongoDb(BaseDb):
1246
1765
 
1247
1766
  except Exception as e:
1248
1767
  log_error(f"Exception getting eval run {eval_run_id}: {e}")
1249
- return None
1768
+ raise e
1250
1769
 
1251
1770
  def get_eval_run(self, eval_run_id: str, deserialize: Optional[bool] = True) -> Optional[EvalRunRecord]:
1252
1771
  """Get an eval run from the database.
@@ -1280,7 +1799,7 @@ class MongoDb(BaseDb):
1280
1799
 
1281
1800
  except Exception as e:
1282
1801
  log_error(f"Exception getting eval run {eval_run_id}: {e}")
1283
- return None
1802
+ raise e
1284
1803
 
1285
1804
  def get_eval_runs(
1286
1805
  self,
@@ -1374,8 +1893,8 @@ class MongoDb(BaseDb):
1374
1893
  return [EvalRunRecord.model_validate(row) for row in records]
1375
1894
 
1376
1895
  except Exception as e:
1377
- log_debug(f"Exception getting eval runs: {e}")
1378
- return [] if deserialize else ([], 0)
1896
+ log_error(f"Exception getting eval runs: {e}")
1897
+ raise e
1379
1898
 
1380
1899
  def rename_eval_run(
1381
1900
  self, eval_run_id: str, name: str, deserialize: Optional[bool] = True
@@ -1413,4 +1932,62 @@ class MongoDb(BaseDb):
1413
1932
 
1414
1933
  except Exception as e:
1415
1934
  log_error(f"Error updating eval run name {eval_run_id}: {e}")
1416
- raise
1935
+ raise e
1936
+
1937
+ def migrate_table_from_v1_to_v2(self, v1_db_schema: str, v1_table_name: str, v1_table_type: str):
1938
+ """Migrate all content in the given collection to the right v2 collection"""
1939
+
1940
+ from typing import List, Sequence, Union
1941
+
1942
+ from agno.db.migrations.v1_to_v2 import (
1943
+ get_all_table_content,
1944
+ parse_agent_sessions,
1945
+ parse_memories,
1946
+ parse_team_sessions,
1947
+ parse_workflow_sessions,
1948
+ )
1949
+
1950
+ # Get all content from the old collection
1951
+ old_content: list[dict[str, Any]] = get_all_table_content(
1952
+ db=self,
1953
+ db_schema=v1_db_schema,
1954
+ table_name=v1_table_name,
1955
+ )
1956
+ if not old_content:
1957
+ log_info(f"No content to migrate from collection {v1_table_name}")
1958
+ return
1959
+
1960
+ # Parse the content into the new format
1961
+ memories: List[UserMemory] = []
1962
+ sessions: Sequence[Union[AgentSession, TeamSession, WorkflowSession]] = []
1963
+ if v1_table_type == "agent_sessions":
1964
+ sessions = parse_agent_sessions(old_content)
1965
+ elif v1_table_type == "team_sessions":
1966
+ sessions = parse_team_sessions(old_content)
1967
+ elif v1_table_type == "workflow_sessions":
1968
+ sessions = parse_workflow_sessions(old_content)
1969
+ elif v1_table_type == "memories":
1970
+ memories = parse_memories(old_content)
1971
+ else:
1972
+ raise ValueError(f"Invalid table type: {v1_table_type}")
1973
+
1974
+ # Insert the new content into the new collection
1975
+ if v1_table_type == "agent_sessions":
1976
+ for session in sessions:
1977
+ self.upsert_session(session)
1978
+ log_info(f"Migrated {len(sessions)} Agent sessions to collection: {self.session_table_name}")
1979
+
1980
+ elif v1_table_type == "team_sessions":
1981
+ for session in sessions:
1982
+ self.upsert_session(session)
1983
+ log_info(f"Migrated {len(sessions)} Team sessions to collection: {self.session_table_name}")
1984
+
1985
+ elif v1_table_type == "workflow_sessions":
1986
+ for session in sessions:
1987
+ self.upsert_session(session)
1988
+ log_info(f"Migrated {len(sessions)} Workflow sessions to collection: {self.session_table_name}")
1989
+
1990
+ elif v1_table_type == "memories":
1991
+ for memory in memories:
1992
+ self.upsert_user_memory(memory)
1993
+ log_info(f"Migrated {len(memories)} memories to collection: {self.memory_table_name}")