agno 2.1.8__py3-none-any.whl → 2.1.10__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 (57) hide show
  1. agno/agent/agent.py +646 -133
  2. agno/culture/__init__.py +3 -0
  3. agno/culture/manager.py +954 -0
  4. agno/db/async_postgres/async_postgres.py +232 -0
  5. agno/db/async_postgres/schemas.py +15 -0
  6. agno/db/async_postgres/utils.py +58 -0
  7. agno/db/base.py +83 -6
  8. agno/db/dynamo/dynamo.py +162 -0
  9. agno/db/dynamo/schemas.py +44 -0
  10. agno/db/dynamo/utils.py +59 -0
  11. agno/db/firestore/firestore.py +231 -0
  12. agno/db/firestore/schemas.py +10 -0
  13. agno/db/firestore/utils.py +96 -0
  14. agno/db/gcs_json/gcs_json_db.py +190 -0
  15. agno/db/gcs_json/utils.py +58 -0
  16. agno/db/in_memory/in_memory_db.py +118 -0
  17. agno/db/in_memory/utils.py +58 -0
  18. agno/db/json/json_db.py +129 -0
  19. agno/db/json/utils.py +58 -0
  20. agno/db/mongo/mongo.py +222 -0
  21. agno/db/mongo/schemas.py +10 -0
  22. agno/db/mongo/utils.py +59 -0
  23. agno/db/mysql/mysql.py +232 -1
  24. agno/db/mysql/schemas.py +14 -0
  25. agno/db/mysql/utils.py +58 -0
  26. agno/db/postgres/postgres.py +242 -0
  27. agno/db/postgres/schemas.py +15 -0
  28. agno/db/postgres/utils.py +58 -0
  29. agno/db/redis/redis.py +181 -0
  30. agno/db/redis/schemas.py +14 -0
  31. agno/db/redis/utils.py +58 -0
  32. agno/db/schemas/__init__.py +2 -1
  33. agno/db/schemas/culture.py +120 -0
  34. agno/db/singlestore/schemas.py +14 -0
  35. agno/db/singlestore/singlestore.py +231 -0
  36. agno/db/singlestore/utils.py +58 -0
  37. agno/db/sqlite/schemas.py +14 -0
  38. agno/db/sqlite/sqlite.py +274 -7
  39. agno/db/sqlite/utils.py +62 -0
  40. agno/db/surrealdb/models.py +51 -1
  41. agno/db/surrealdb/surrealdb.py +154 -0
  42. agno/db/surrealdb/utils.py +61 -1
  43. agno/knowledge/knowledge.py +4 -0
  44. agno/knowledge/reader/field_labeled_csv_reader.py +0 -2
  45. agno/memory/manager.py +28 -11
  46. agno/models/message.py +4 -0
  47. agno/os/app.py +28 -6
  48. agno/team/team.py +9 -9
  49. agno/tools/gmail.py +59 -14
  50. agno/tools/googlecalendar.py +13 -20
  51. agno/workflow/condition.py +31 -9
  52. agno/workflow/router.py +31 -9
  53. {agno-2.1.8.dist-info → agno-2.1.10.dist-info}/METADATA +1 -1
  54. {agno-2.1.8.dist-info → agno-2.1.10.dist-info}/RECORD +57 -54
  55. {agno-2.1.8.dist-info → agno-2.1.10.dist-info}/WHEEL +0 -0
  56. {agno-2.1.8.dist-info → agno-2.1.10.dist-info}/licenses/LICENSE +0 -0
  57. {agno-2.1.8.dist-info → agno-2.1.10.dist-info}/top_level.txt +0 -0
agno/db/json/json_db.py CHANGED
@@ -10,10 +10,13 @@ from agno.db.base import BaseDb, SessionType
10
10
  from agno.db.json.utils import (
11
11
  apply_sorting,
12
12
  calculate_date_metrics,
13
+ deserialize_cultural_knowledge_from_db,
13
14
  fetch_all_sessions_data,
14
15
  get_dates_to_calculate_metrics_for,
15
16
  hydrate_session,
17
+ serialize_cultural_knowledge_for_db,
16
18
  )
19
+ from agno.db.schemas.culture import CulturalKnowledge
17
20
  from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
18
21
  from agno.db.schemas.knowledge import KnowledgeRow
19
22
  from agno.db.schemas.memory import UserMemory
@@ -27,6 +30,7 @@ class JsonDb(BaseDb):
27
30
  self,
28
31
  db_path: Optional[str] = None,
29
32
  session_table: Optional[str] = None,
33
+ culture_table: Optional[str] = None,
30
34
  memory_table: Optional[str] = None,
31
35
  metrics_table: Optional[str] = None,
32
36
  eval_table: Optional[str] = None,
@@ -39,6 +43,7 @@ class JsonDb(BaseDb):
39
43
  Args:
40
44
  db_path (Optional[str]): Path to the directory where JSON files will be stored.
41
45
  session_table (Optional[str]): Name of the JSON file to store sessions (without .json extension).
46
+ culture_table (Optional[str]): Name of the JSON file to store cultural knowledge.
42
47
  memory_table (Optional[str]): Name of the JSON file to store memories.
43
48
  metrics_table (Optional[str]): Name of the JSON file to store metrics.
44
49
  eval_table (Optional[str]): Name of the JSON file to store evaluation runs.
@@ -52,6 +57,7 @@ class JsonDb(BaseDb):
52
57
  super().__init__(
53
58
  id=id,
54
59
  session_table=session_table,
60
+ culture_table=culture_table,
55
61
  memory_table=memory_table,
56
62
  metrics_table=metrics_table,
57
63
  eval_table=eval_table,
@@ -1199,3 +1205,126 @@ class JsonDb(BaseDb):
1199
1205
  except Exception as e:
1200
1206
  log_error(f"Error renaming eval run {eval_run_id}: {e}")
1201
1207
  raise e
1208
+
1209
+ # -- Culture methods --
1210
+
1211
+ def clear_cultural_knowledge(self) -> None:
1212
+ """Delete all cultural knowledge from JSON file."""
1213
+ try:
1214
+ self._write_json_file(self.culture_table_name, [])
1215
+ except Exception as e:
1216
+ log_error(f"Error clearing cultural knowledge: {e}")
1217
+ raise e
1218
+
1219
+ def delete_cultural_knowledge(self, id: str) -> None:
1220
+ """Delete a cultural knowledge entry from JSON file."""
1221
+ try:
1222
+ cultural_knowledge = self._read_json_file(self.culture_table_name)
1223
+ cultural_knowledge = [ck for ck in cultural_knowledge if ck.get("id") != id]
1224
+ self._write_json_file(self.culture_table_name, cultural_knowledge)
1225
+ except Exception as e:
1226
+ log_error(f"Error deleting cultural knowledge: {e}")
1227
+ raise e
1228
+
1229
+ def get_cultural_knowledge(
1230
+ self, id: str, deserialize: Optional[bool] = True
1231
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
1232
+ """Get a cultural knowledge entry from JSON file."""
1233
+ try:
1234
+ cultural_knowledge = self._read_json_file(self.culture_table_name)
1235
+ for ck in cultural_knowledge:
1236
+ if ck.get("id") == id:
1237
+ if not deserialize:
1238
+ return ck
1239
+ return deserialize_cultural_knowledge_from_db(ck)
1240
+ return None
1241
+ except Exception as e:
1242
+ log_error(f"Error getting cultural knowledge: {e}")
1243
+ raise e
1244
+
1245
+ def get_all_cultural_knowledge(
1246
+ self,
1247
+ name: Optional[str] = None,
1248
+ agent_id: Optional[str] = None,
1249
+ team_id: Optional[str] = None,
1250
+ limit: Optional[int] = None,
1251
+ page: Optional[int] = None,
1252
+ sort_by: Optional[str] = None,
1253
+ sort_order: Optional[str] = None,
1254
+ deserialize: Optional[bool] = True,
1255
+ ) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
1256
+ """Get all cultural knowledge from JSON file."""
1257
+ try:
1258
+ cultural_knowledge = self._read_json_file(self.culture_table_name)
1259
+
1260
+ # Filter
1261
+ filtered = []
1262
+ for ck in cultural_knowledge:
1263
+ if name and ck.get("name") != name:
1264
+ continue
1265
+ if agent_id and ck.get("agent_id") != agent_id:
1266
+ continue
1267
+ if team_id and ck.get("team_id") != team_id:
1268
+ continue
1269
+ filtered.append(ck)
1270
+
1271
+ # Sort
1272
+ if sort_by:
1273
+ filtered = apply_sorting(filtered, sort_by, sort_order)
1274
+
1275
+ total_count = len(filtered)
1276
+
1277
+ # Paginate
1278
+ if limit and page:
1279
+ start = (page - 1) * limit
1280
+ filtered = filtered[start : start + limit]
1281
+ elif limit:
1282
+ filtered = filtered[:limit]
1283
+
1284
+ if not deserialize:
1285
+ return filtered, total_count
1286
+
1287
+ return [deserialize_cultural_knowledge_from_db(ck) for ck in filtered]
1288
+ except Exception as e:
1289
+ log_error(f"Error getting all cultural knowledge: {e}")
1290
+ raise e
1291
+
1292
+ def upsert_cultural_knowledge(
1293
+ self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
1294
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
1295
+ """Upsert a cultural knowledge entry into JSON file."""
1296
+ try:
1297
+ if not cultural_knowledge.id:
1298
+ cultural_knowledge.id = str(uuid4())
1299
+
1300
+ all_cultural_knowledge = self._read_json_file(self.culture_table_name, create_table_if_not_found=True)
1301
+
1302
+ # Serialize content, categories, and notes into a dict for DB storage
1303
+ content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
1304
+
1305
+ # Create the item dict with serialized content
1306
+ ck_dict = {
1307
+ "id": cultural_knowledge.id,
1308
+ "name": cultural_knowledge.name,
1309
+ "summary": cultural_knowledge.summary,
1310
+ "content": content_dict if content_dict else None,
1311
+ "metadata": cultural_knowledge.metadata,
1312
+ "input": cultural_knowledge.input,
1313
+ "created_at": cultural_knowledge.created_at,
1314
+ "updated_at": int(time.time()),
1315
+ "agent_id": cultural_knowledge.agent_id,
1316
+ "team_id": cultural_knowledge.team_id,
1317
+ }
1318
+
1319
+ # Remove existing entry
1320
+ all_cultural_knowledge = [ck for ck in all_cultural_knowledge if ck.get("id") != cultural_knowledge.id]
1321
+
1322
+ # Add new entry
1323
+ all_cultural_knowledge.append(ck_dict)
1324
+
1325
+ self._write_json_file(self.culture_table_name, all_cultural_knowledge)
1326
+
1327
+ return self.get_cultural_knowledge(cultural_knowledge.id, deserialize=deserialize)
1328
+ except Exception as e:
1329
+ log_error(f"Error upserting cultural knowledge: {e}")
1330
+ raise e
agno/db/json/utils.py CHANGED
@@ -6,6 +6,7 @@ from typing import Any, Dict, List, Optional
6
6
  from uuid import uuid4
7
7
 
8
8
  from agno.db.base import SessionType
9
+ from agno.db.schemas.culture import CulturalKnowledge
9
10
  from agno.run.agent import RunOutput
10
11
  from agno.run.team import TeamRunOutput
11
12
  from agno.session.summary import SessionSummary
@@ -194,3 +195,60 @@ def get_dates_to_calculate_metrics_for(starting_date: date) -> list[date]:
194
195
  if days_diff <= 0:
195
196
  return []
196
197
  return [starting_date + timedelta(days=x) for x in range(days_diff)]
198
+
199
+
200
+ # -- Cultural Knowledge util methods --
201
+ def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
202
+ """Serialize a CulturalKnowledge object for database storage.
203
+
204
+ Converts the model's separate content, categories, and notes fields
205
+ into a single dict for the database content column.
206
+
207
+ Args:
208
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
209
+
210
+ Returns:
211
+ Dict[str, Any]: A dictionary with the content field as a dict containing content, categories, and notes.
212
+ """
213
+ content_dict: Dict[str, Any] = {}
214
+ if cultural_knowledge.content is not None:
215
+ content_dict["content"] = cultural_knowledge.content
216
+ if cultural_knowledge.categories is not None:
217
+ content_dict["categories"] = cultural_knowledge.categories
218
+ if cultural_knowledge.notes is not None:
219
+ content_dict["notes"] = cultural_knowledge.notes
220
+
221
+ return content_dict if content_dict else {}
222
+
223
+
224
+ def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
225
+ """Deserialize a database row to a CulturalKnowledge object.
226
+
227
+ The database stores content as a dict containing content, categories, and notes.
228
+ This method extracts those fields and converts them back to the model format.
229
+
230
+ Args:
231
+ db_row (Dict[str, Any]): The database row as a dictionary.
232
+
233
+ Returns:
234
+ CulturalKnowledge: The cultural knowledge object.
235
+ """
236
+ # Extract content, categories, and notes from the content field
237
+ content_json = db_row.get("content", {}) or {}
238
+
239
+ return CulturalKnowledge.from_dict(
240
+ {
241
+ "id": db_row.get("id"),
242
+ "name": db_row.get("name"),
243
+ "summary": db_row.get("summary"),
244
+ "content": content_json.get("content"),
245
+ "categories": content_json.get("categories"),
246
+ "notes": content_json.get("notes"),
247
+ "metadata": db_row.get("metadata"),
248
+ "input": db_row.get("input"),
249
+ "created_at": db_row.get("created_at"),
250
+ "updated_at": db_row.get("updated_at"),
251
+ "agent_id": db_row.get("agent_id"),
252
+ "team_id": db_row.get("team_id"),
253
+ }
254
+ )
agno/db/mongo/mongo.py CHANGED
@@ -10,9 +10,12 @@ 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
@@ -41,6 +44,7 @@ class MongoDb(BaseDb):
41
44
  metrics_collection: Optional[str] = None,
42
45
  eval_collection: Optional[str] = None,
43
46
  knowledge_collection: Optional[str] = None,
47
+ culture_collection: Optional[str] = None,
44
48
  id: Optional[str] = None,
45
49
  ):
46
50
  """
@@ -55,6 +59,7 @@ class MongoDb(BaseDb):
55
59
  metrics_collection (Optional[str]): Name of the collection to store metrics.
56
60
  eval_collection (Optional[str]): Name of the collection to store evaluation runs.
57
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.
58
63
  id (Optional[str]): ID of the database.
59
64
 
60
65
  Raises:
@@ -73,6 +78,7 @@ class MongoDb(BaseDb):
73
78
  metrics_table=metrics_collection,
74
79
  eval_table=eval_collection,
75
80
  knowledge_table=knowledge_collection,
81
+ culture_table=culture_collection,
76
82
  )
77
83
 
78
84
  _client: Optional[MongoClient] = db_client
@@ -161,6 +167,17 @@ class MongoDb(BaseDb):
161
167
  )
162
168
  return self.knowledge_collection
163
169
 
170
+ if table_type == "culture":
171
+ if not hasattr(self, "culture_collection"):
172
+ if self.culture_table_name is None:
173
+ raise ValueError("Culture collection was not provided on initialization")
174
+ self.culture_collection = self._get_or_create_collection(
175
+ collection_name=self.culture_table_name,
176
+ collection_type="culture",
177
+ create_collection_if_not_found=create_collection_if_not_found,
178
+ )
179
+ return self.culture_collection
180
+
164
181
  raise ValueError(f"Unknown table type: {table_type}")
165
182
 
166
183
  def _get_or_create_collection(
@@ -1153,6 +1170,211 @@ class MongoDb(BaseDb):
1153
1170
  log_error(f"Exception deleting all memories: {e}")
1154
1171
  raise e
1155
1172
 
1173
+ # -- Cultural Knowledge methods --
1174
+ def clear_cultural_knowledge(self) -> None:
1175
+ """Delete all cultural knowledge from the database.
1176
+
1177
+ Raises:
1178
+ Exception: If an error occurs during deletion.
1179
+ """
1180
+ try:
1181
+ collection = self._get_collection(table_type="culture")
1182
+ if collection is None:
1183
+ return
1184
+
1185
+ collection.delete_many({})
1186
+
1187
+ except Exception as e:
1188
+ log_error(f"Exception deleting all cultural knowledge: {e}")
1189
+ raise e
1190
+
1191
+ def delete_cultural_knowledge(self, id: str) -> None:
1192
+ """Delete cultural knowledge by ID.
1193
+
1194
+ Args:
1195
+ id (str): The ID of the cultural knowledge to delete.
1196
+
1197
+ Raises:
1198
+ Exception: If an error occurs during deletion.
1199
+ """
1200
+ try:
1201
+ collection = self._get_collection(table_type="culture")
1202
+ if collection is None:
1203
+ return
1204
+
1205
+ collection.delete_one({"id": id})
1206
+ log_debug(f"Deleted cultural knowledge with ID: {id}")
1207
+
1208
+ except Exception as e:
1209
+ log_error(f"Error deleting cultural knowledge: {e}")
1210
+ raise e
1211
+
1212
+ def get_cultural_knowledge(
1213
+ self, id: str, deserialize: Optional[bool] = True
1214
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
1215
+ """Get cultural knowledge by ID.
1216
+
1217
+ Args:
1218
+ id (str): The ID of the cultural knowledge to retrieve.
1219
+ deserialize (Optional[bool]): Whether to deserialize to CulturalKnowledge object. Defaults to True.
1220
+
1221
+ Returns:
1222
+ Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The cultural knowledge if found, None otherwise.
1223
+
1224
+ Raises:
1225
+ Exception: If an error occurs during retrieval.
1226
+ """
1227
+ try:
1228
+ collection = self._get_collection(table_type="culture")
1229
+ if collection is None:
1230
+ return None
1231
+
1232
+ result = collection.find_one({"id": id})
1233
+ if result is None:
1234
+ return None
1235
+
1236
+ # Remove MongoDB's _id field
1237
+ result_filtered = {k: v for k, v in result.items() if k != "_id"}
1238
+
1239
+ if not deserialize:
1240
+ return result_filtered
1241
+
1242
+ return deserialize_cultural_knowledge_from_db(result_filtered)
1243
+
1244
+ except Exception as e:
1245
+ log_error(f"Error getting cultural knowledge: {e}")
1246
+ raise e
1247
+
1248
+ def get_all_cultural_knowledge(
1249
+ self,
1250
+ agent_id: Optional[str] = None,
1251
+ team_id: Optional[str] = None,
1252
+ name: Optional[str] = None,
1253
+ limit: Optional[int] = None,
1254
+ page: Optional[int] = None,
1255
+ sort_by: Optional[str] = None,
1256
+ sort_order: Optional[str] = None,
1257
+ deserialize: Optional[bool] = True,
1258
+ ) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
1259
+ """Get all cultural knowledge with filtering and pagination.
1260
+
1261
+ Args:
1262
+ agent_id (Optional[str]): Filter by agent ID.
1263
+ team_id (Optional[str]): Filter by team ID.
1264
+ name (Optional[str]): Filter by name (case-insensitive partial match).
1265
+ limit (Optional[int]): Maximum number of results to return.
1266
+ page (Optional[int]): Page number for pagination.
1267
+ sort_by (Optional[str]): Field to sort by.
1268
+ sort_order (Optional[str]): Sort order ('asc' or 'desc').
1269
+ deserialize (Optional[bool]): Whether to deserialize to CulturalKnowledge objects. Defaults to True.
1270
+
1271
+ Returns:
1272
+ Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
1273
+ - When deserialize=True: List of CulturalKnowledge objects
1274
+ - When deserialize=False: Tuple with list of dictionaries and total count
1275
+
1276
+ Raises:
1277
+ Exception: If an error occurs during retrieval.
1278
+ """
1279
+ try:
1280
+ collection = self._get_collection(table_type="culture")
1281
+ if collection is None:
1282
+ if not deserialize:
1283
+ return [], 0
1284
+ return []
1285
+
1286
+ # Build query
1287
+ query: Dict[str, Any] = {}
1288
+ if agent_id is not None:
1289
+ query["agent_id"] = agent_id
1290
+ if team_id is not None:
1291
+ query["team_id"] = team_id
1292
+ if name is not None:
1293
+ query["name"] = {"$regex": name, "$options": "i"}
1294
+
1295
+ # Get total count for pagination
1296
+ total_count = collection.count_documents(query)
1297
+
1298
+ # Apply sorting
1299
+ sort_criteria = apply_sorting({}, sort_by, sort_order)
1300
+
1301
+ # Apply pagination
1302
+ query_args = apply_pagination({}, limit, page)
1303
+
1304
+ cursor = collection.find(query)
1305
+ if sort_criteria:
1306
+ cursor = cursor.sort(sort_criteria)
1307
+ if query_args.get("skip"):
1308
+ cursor = cursor.skip(query_args["skip"])
1309
+ if query_args.get("limit"):
1310
+ cursor = cursor.limit(query_args["limit"])
1311
+
1312
+ # Remove MongoDB's _id field from all results
1313
+ results_filtered = [{k: v for k, v in item.items() if k != "_id"} for item in cursor]
1314
+
1315
+ if not deserialize:
1316
+ return results_filtered, total_count
1317
+
1318
+ return [deserialize_cultural_knowledge_from_db(item) for item in results_filtered]
1319
+
1320
+ except Exception as e:
1321
+ log_error(f"Error getting all cultural knowledge: {e}")
1322
+ raise e
1323
+
1324
+ def upsert_cultural_knowledge(
1325
+ self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
1326
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
1327
+ """Upsert cultural knowledge in MongoDB.
1328
+
1329
+ Args:
1330
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge to upsert.
1331
+ deserialize (Optional[bool]): Whether to deserialize the result. Defaults to True.
1332
+
1333
+ Returns:
1334
+ Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The upserted cultural knowledge.
1335
+
1336
+ Raises:
1337
+ Exception: If an error occurs during upsert.
1338
+ """
1339
+ try:
1340
+ collection = self._get_collection(table_type="culture", create_collection_if_not_found=True)
1341
+ if collection is None:
1342
+ return None
1343
+
1344
+ # Serialize content, categories, and notes into a dict for DB storage
1345
+ content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
1346
+
1347
+ # Create the document with serialized content
1348
+ update_doc = {
1349
+ "id": cultural_knowledge.id,
1350
+ "name": cultural_knowledge.name,
1351
+ "summary": cultural_knowledge.summary,
1352
+ "content": content_dict if content_dict else None,
1353
+ "metadata": cultural_knowledge.metadata,
1354
+ "input": cultural_knowledge.input,
1355
+ "created_at": cultural_knowledge.created_at,
1356
+ "updated_at": int(time.time()),
1357
+ "agent_id": cultural_knowledge.agent_id,
1358
+ "team_id": cultural_knowledge.team_id,
1359
+ }
1360
+
1361
+ result = collection.replace_one({"id": cultural_knowledge.id}, update_doc, upsert=True)
1362
+
1363
+ if result.upserted_id:
1364
+ update_doc["_id"] = result.upserted_id
1365
+
1366
+ # Remove MongoDB's _id field
1367
+ doc_filtered = {k: v for k, v in update_doc.items() if k != "_id"}
1368
+
1369
+ if not deserialize:
1370
+ return doc_filtered
1371
+
1372
+ return deserialize_cultural_knowledge_from_db(doc_filtered)
1373
+
1374
+ except Exception as e:
1375
+ log_error(f"Error upserting cultural knowledge: {e}")
1376
+ raise e
1377
+
1156
1378
  # -- Metrics methods --
1157
1379
 
1158
1380
  def _get_all_sessions_for_metrics_calculation(
agno/db/mongo/schemas.py CHANGED
@@ -59,6 +59,15 @@ METRICS_COLLECTION_SCHEMA = [
59
59
  {"key": [("date", 1), ("aggregation_period", 1)], "unique": True},
60
60
  ]
61
61
 
62
+ CULTURAL_KNOWLEDGE_COLLECTION_SCHEMA = [
63
+ {"key": "id", "unique": True},
64
+ {"key": "name"},
65
+ {"key": "agent_id"},
66
+ {"key": "team_id"},
67
+ {"key": "created_at"},
68
+ {"key": "updated_at"},
69
+ ]
70
+
62
71
 
63
72
  def get_collection_indexes(collection_type: str) -> List[Dict[str, Any]]:
64
73
  """Get the index definitions for a specific collection type."""
@@ -68,6 +77,7 @@ def get_collection_indexes(collection_type: str) -> List[Dict[str, Any]]:
68
77
  "metrics": METRICS_COLLECTION_SCHEMA,
69
78
  "evals": EVAL_COLLECTION_SCHEMA,
70
79
  "knowledge": KNOWLEDGE_COLLECTION_SCHEMA,
80
+ "culture": CULTURAL_KNOWLEDGE_COLLECTION_SCHEMA,
71
81
  }
72
82
 
73
83
  indexes = index_definitions.get(collection_type)
agno/db/mongo/utils.py CHANGED
@@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional
7
7
  from uuid import uuid4
8
8
 
9
9
  from agno.db.mongo.schemas import get_collection_indexes
10
+ from agno.db.schemas.culture import CulturalKnowledge
10
11
  from agno.utils.log import log_error, log_warning
11
12
 
12
13
  try:
@@ -198,3 +199,61 @@ def bulk_upsert_metrics(collection: Collection, metrics_records: List[Dict[str,
198
199
  continue
199
200
 
200
201
  return results
202
+
203
+
204
+ # -- Cultural Knowledge util methods --
205
+ def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
206
+ """Serialize a CulturalKnowledge object for database storage.
207
+
208
+ Converts the model's separate content, categories, and notes fields
209
+ into a single dict for the database content column.
210
+ MongoDB stores as BSON which natively supports nested documents.
211
+
212
+ Args:
213
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
214
+
215
+ Returns:
216
+ Dict[str, Any]: A dictionary with the content field as a dict containing content, categories, and notes.
217
+ """
218
+ content_dict: Dict[str, Any] = {}
219
+ if cultural_knowledge.content is not None:
220
+ content_dict["content"] = cultural_knowledge.content
221
+ if cultural_knowledge.categories is not None:
222
+ content_dict["categories"] = cultural_knowledge.categories
223
+ if cultural_knowledge.notes is not None:
224
+ content_dict["notes"] = cultural_knowledge.notes
225
+
226
+ return content_dict if content_dict else {}
227
+
228
+
229
+ def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
230
+ """Deserialize a database row to a CulturalKnowledge object.
231
+
232
+ The database stores content as a dict containing content, categories, and notes.
233
+ This method extracts those fields and converts them back to the model format.
234
+
235
+ Args:
236
+ db_row (Dict[str, Any]): The database row as a dictionary.
237
+
238
+ Returns:
239
+ CulturalKnowledge: The cultural knowledge object.
240
+ """
241
+ # Extract content, categories, and notes from the content field
242
+ content_json = db_row.get("content", {}) or {}
243
+
244
+ return CulturalKnowledge.from_dict(
245
+ {
246
+ "id": db_row.get("id"),
247
+ "name": db_row.get("name"),
248
+ "summary": db_row.get("summary"),
249
+ "content": content_json.get("content"),
250
+ "categories": content_json.get("categories"),
251
+ "notes": content_json.get("notes"),
252
+ "metadata": db_row.get("metadata"),
253
+ "input": db_row.get("input"),
254
+ "created_at": db_row.get("created_at"),
255
+ "updated_at": db_row.get("updated_at"),
256
+ "agent_id": db_row.get("agent_id"),
257
+ "team_id": db_row.get("team_id"),
258
+ }
259
+ )