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
@@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional
7
7
  from uuid import uuid4
8
8
 
9
9
  from agno.db.firestore.schemas import get_collection_indexes
10
+ from agno.db.schemas.culture import CulturalKnowledge
10
11
  from agno.utils.log import log_debug, log_error, log_info, log_warning
11
12
 
12
13
  try:
@@ -122,6 +123,42 @@ def apply_pagination(query, limit: Optional[int] = None, page: Optional[int] = N
122
123
  return query
123
124
 
124
125
 
126
+ def apply_sorting_to_records(
127
+ records: List[Dict[str, Any]], sort_by: Optional[str] = None, sort_order: Optional[str] = None
128
+ ) -> List[Dict[str, Any]]:
129
+ """Apply sorting to in-memory records (for cases where Firestore query sorting isn't possible)."""
130
+ if sort_by is None:
131
+ return records
132
+
133
+ def get_sort_key(record):
134
+ value = record.get(sort_by, 0)
135
+ if value is None:
136
+ return 0 if records and isinstance(records[0].get(sort_by, 0), (int, float)) else ""
137
+ return value
138
+
139
+ try:
140
+ is_reverse = sort_order == "desc"
141
+ return sorted(records, key=get_sort_key, reverse=is_reverse)
142
+ except Exception as e:
143
+ log_warning(f"Error sorting Firestore records: {e}")
144
+ return records
145
+
146
+
147
+ def apply_pagination_to_records(
148
+ records: List[Dict[str, Any]], limit: Optional[int] = None, page: Optional[int] = None
149
+ ) -> List[Dict[str, Any]]:
150
+ """Apply pagination to in-memory records (for cases where Firestore query pagination isn't possible)."""
151
+ if limit is None:
152
+ return records
153
+
154
+ if page is not None and page > 0:
155
+ start_idx = (page - 1) * limit
156
+ end_idx = start_idx + limit
157
+ return records[start_idx:end_idx]
158
+ else:
159
+ return records[:limit]
160
+
161
+
125
162
  # -- Metrics util methods --
126
163
 
127
164
 
@@ -278,3 +315,62 @@ def bulk_upsert_metrics(collection_ref, metrics_records: List[Dict[str, Any]]) -
278
315
  log_error(f"Error committing metrics batch: {e}")
279
316
 
280
317
  return results
318
+
319
+
320
+ # -- Cultural Knowledge util methods --
321
+
322
+
323
+ def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
324
+ """Serialize a CulturalKnowledge object for database storage.
325
+
326
+ Converts the model's separate content, categories, and notes fields
327
+ into a single dict for the database content field.
328
+
329
+ Args:
330
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
331
+
332
+ Returns:
333
+ Dict[str, Any]: A dictionary with content, categories, and notes.
334
+ """
335
+ content_dict: Dict[str, Any] = {}
336
+ if cultural_knowledge.content is not None:
337
+ content_dict["content"] = cultural_knowledge.content
338
+ if cultural_knowledge.categories is not None:
339
+ content_dict["categories"] = cultural_knowledge.categories
340
+ if cultural_knowledge.notes is not None:
341
+ content_dict["notes"] = cultural_knowledge.notes
342
+
343
+ return content_dict if content_dict else {}
344
+
345
+
346
+ def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
347
+ """Deserialize a database row to a CulturalKnowledge object.
348
+
349
+ The database stores content as a dict containing content, categories, and notes.
350
+ This method extracts those fields and converts them back to the model format.
351
+
352
+ Args:
353
+ db_row (Dict[str, Any]): The database row as a dictionary.
354
+
355
+ Returns:
356
+ CulturalKnowledge: The cultural knowledge object.
357
+ """
358
+ # Extract content, categories, and notes from the content field
359
+ content_json = db_row.get("content", {}) or {}
360
+
361
+ return CulturalKnowledge.from_dict(
362
+ {
363
+ "id": db_row.get("id"),
364
+ "name": db_row.get("name"),
365
+ "summary": db_row.get("summary"),
366
+ "content": content_json.get("content"),
367
+ "categories": content_json.get("categories"),
368
+ "notes": content_json.get("notes"),
369
+ "metadata": db_row.get("metadata"),
370
+ "input": db_row.get("input"),
371
+ "created_at": db_row.get("created_at"),
372
+ "updated_at": db_row.get("updated_at"),
373
+ "agent_id": db_row.get("agent_id"),
374
+ "team_id": db_row.get("team_id"),
375
+ }
376
+ )
@@ -8,9 +8,12 @@ from agno.db.base import BaseDb, SessionType
8
8
  from agno.db.gcs_json.utils import (
9
9
  apply_sorting,
10
10
  calculate_date_metrics,
11
+ deserialize_cultural_knowledge_from_db,
11
12
  fetch_all_sessions_data,
12
13
  get_dates_to_calculate_metrics_for,
14
+ serialize_cultural_knowledge_for_db,
13
15
  )
16
+ from agno.db.schemas.culture import CulturalKnowledge
14
17
  from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
15
18
  from agno.db.schemas.knowledge import KnowledgeRow
16
19
  from agno.db.schemas.memory import UserMemory
@@ -34,6 +37,7 @@ class GcsJsonDb(BaseDb):
34
37
  metrics_table: Optional[str] = None,
35
38
  eval_table: Optional[str] = None,
36
39
  knowledge_table: Optional[str] = None,
40
+ culture_table: Optional[str] = None,
37
41
  project: Optional[str] = None,
38
42
  credentials: Optional[Any] = None,
39
43
  id: Optional[str] = None,
@@ -49,6 +53,7 @@ class GcsJsonDb(BaseDb):
49
53
  metrics_table (Optional[str]): Name of the JSON file to store metrics.
50
54
  eval_table (Optional[str]): Name of the JSON file to store evaluation runs.
51
55
  knowledge_table (Optional[str]): Name of the JSON file to store knowledge content.
56
+ culture_table (Optional[str]): Name of the JSON file to store cultural knowledge.
52
57
  project (Optional[str]): GCP project ID. If None, uses default project.
53
58
  location (Optional[str]): GCS bucket location. If None, uses default location.
54
59
  credentials (Optional[Any]): GCP credentials. If None, uses default credentials.
@@ -66,6 +71,7 @@ class GcsJsonDb(BaseDb):
66
71
  metrics_table=metrics_table,
67
72
  eval_table=eval_table,
68
73
  knowledge_table=knowledge_table,
74
+ culture_table=culture_table,
69
75
  )
70
76
 
71
77
  self.bucket_name = bucket_name
@@ -1143,3 +1149,187 @@ class GcsJsonDb(BaseDb):
1143
1149
  except Exception as e:
1144
1150
  log_warning(f"Error renaming eval run {eval_run_id}: {e}")
1145
1151
  raise e
1152
+
1153
+ # -- Cultural Knowledge methods --
1154
+ def clear_cultural_knowledge(self) -> None:
1155
+ """Delete all cultural knowledge from the database.
1156
+
1157
+ Raises:
1158
+ Exception: If an error occurs during deletion.
1159
+ """
1160
+ try:
1161
+ self._write_json_file(self.culture_table_name, [])
1162
+ except Exception as e:
1163
+ log_warning(f"Exception deleting all cultural knowledge: {e}")
1164
+ raise e
1165
+
1166
+ def delete_cultural_knowledge(self, id: str) -> None:
1167
+ """Delete cultural knowledge by ID.
1168
+
1169
+ Args:
1170
+ id (str): The ID of the cultural knowledge to delete.
1171
+
1172
+ Raises:
1173
+ Exception: If an error occurs during deletion.
1174
+ """
1175
+ try:
1176
+ cultural_knowledge = self._read_json_file(self.culture_table_name)
1177
+ cultural_knowledge = [item for item in cultural_knowledge if item.get("id") != id]
1178
+ self._write_json_file(self.culture_table_name, cultural_knowledge)
1179
+ log_debug(f"Deleted cultural knowledge with ID: {id}")
1180
+ except Exception as e:
1181
+ log_warning(f"Error deleting cultural knowledge: {e}")
1182
+ raise e
1183
+
1184
+ def get_cultural_knowledge(
1185
+ self, id: str, deserialize: Optional[bool] = True
1186
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
1187
+ """Get cultural knowledge by ID.
1188
+
1189
+ Args:
1190
+ id (str): The ID of the cultural knowledge to retrieve.
1191
+ deserialize (Optional[bool]): Whether to deserialize to CulturalKnowledge object. Defaults to True.
1192
+
1193
+ Returns:
1194
+ Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The cultural knowledge if found, None otherwise.
1195
+
1196
+ Raises:
1197
+ Exception: If an error occurs during retrieval.
1198
+ """
1199
+ try:
1200
+ cultural_knowledge = self._read_json_file(self.culture_table_name)
1201
+
1202
+ for item in cultural_knowledge:
1203
+ if item.get("id") == id:
1204
+ if not deserialize:
1205
+ return item
1206
+ return deserialize_cultural_knowledge_from_db(item)
1207
+
1208
+ return None
1209
+ except Exception as e:
1210
+ log_warning(f"Error getting cultural knowledge: {e}")
1211
+ raise e
1212
+
1213
+ def get_all_cultural_knowledge(
1214
+ self,
1215
+ agent_id: Optional[str] = None,
1216
+ team_id: Optional[str] = None,
1217
+ name: Optional[str] = None,
1218
+ limit: Optional[int] = None,
1219
+ page: Optional[int] = None,
1220
+ sort_by: Optional[str] = None,
1221
+ sort_order: Optional[str] = None,
1222
+ deserialize: Optional[bool] = True,
1223
+ ) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
1224
+ """Get all cultural knowledge with filtering and pagination.
1225
+
1226
+ Args:
1227
+ agent_id (Optional[str]): Filter by agent ID.
1228
+ team_id (Optional[str]): Filter by team ID.
1229
+ name (Optional[str]): Filter by name (case-insensitive partial match).
1230
+ limit (Optional[int]): Maximum number of results to return.
1231
+ page (Optional[int]): Page number for pagination.
1232
+ sort_by (Optional[str]): Field to sort by.
1233
+ sort_order (Optional[str]): Sort order ('asc' or 'desc').
1234
+ deserialize (Optional[bool]): Whether to deserialize to CulturalKnowledge objects. Defaults to True.
1235
+
1236
+ Returns:
1237
+ Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
1238
+ - When deserialize=True: List of CulturalKnowledge objects
1239
+ - When deserialize=False: Tuple with list of dictionaries and total count
1240
+
1241
+ Raises:
1242
+ Exception: If an error occurs during retrieval.
1243
+ """
1244
+ try:
1245
+ cultural_knowledge = self._read_json_file(self.culture_table_name)
1246
+
1247
+ # Apply filters
1248
+ filtered_items = []
1249
+ for item in cultural_knowledge:
1250
+ if agent_id is not None and item.get("agent_id") != agent_id:
1251
+ continue
1252
+ if team_id is not None and item.get("team_id") != team_id:
1253
+ continue
1254
+ if name is not None and name.lower() not in item.get("name", "").lower():
1255
+ continue
1256
+
1257
+ filtered_items.append(item)
1258
+
1259
+ total_count = len(filtered_items)
1260
+
1261
+ # Apply sorting
1262
+ filtered_items = apply_sorting(filtered_items, sort_by, sort_order)
1263
+
1264
+ # Apply pagination
1265
+ if limit is not None:
1266
+ start_idx = 0
1267
+ if page is not None:
1268
+ start_idx = (page - 1) * limit
1269
+ filtered_items = filtered_items[start_idx : start_idx + limit]
1270
+
1271
+ if not deserialize:
1272
+ return filtered_items, total_count
1273
+
1274
+ return [deserialize_cultural_knowledge_from_db(item) for item in filtered_items]
1275
+
1276
+ except Exception as e:
1277
+ log_warning(f"Error getting all cultural knowledge: {e}")
1278
+ raise e
1279
+
1280
+ def upsert_cultural_knowledge(
1281
+ self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
1282
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
1283
+ """Upsert cultural knowledge in the GCS JSON file.
1284
+
1285
+ Args:
1286
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge to upsert.
1287
+ deserialize (Optional[bool]): Whether to deserialize the result. Defaults to True.
1288
+
1289
+ Returns:
1290
+ Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The upserted cultural knowledge.
1291
+
1292
+ Raises:
1293
+ Exception: If an error occurs during upsert.
1294
+ """
1295
+ try:
1296
+ cultural_knowledge_list = self._read_json_file(self.culture_table_name, create_table_if_not_found=True)
1297
+
1298
+ # Serialize content, categories, and notes into a dict for DB storage
1299
+ content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
1300
+
1301
+ # Create the item dict with serialized content
1302
+ cultural_knowledge_dict = {
1303
+ "id": cultural_knowledge.id,
1304
+ "name": cultural_knowledge.name,
1305
+ "summary": cultural_knowledge.summary,
1306
+ "content": content_dict if content_dict else None,
1307
+ "metadata": cultural_knowledge.metadata,
1308
+ "input": cultural_knowledge.input,
1309
+ "created_at": cultural_knowledge.created_at,
1310
+ "updated_at": int(time.time()),
1311
+ "agent_id": cultural_knowledge.agent_id,
1312
+ "team_id": cultural_knowledge.team_id,
1313
+ }
1314
+
1315
+ # Find existing item to update
1316
+ item_updated = False
1317
+ for i, existing_item in enumerate(cultural_knowledge_list):
1318
+ if existing_item.get("id") == cultural_knowledge.id:
1319
+ cultural_knowledge_list[i] = cultural_knowledge_dict
1320
+ item_updated = True
1321
+ break
1322
+
1323
+ if not item_updated:
1324
+ cultural_knowledge_list.append(cultural_knowledge_dict)
1325
+
1326
+ self._write_json_file(self.culture_table_name, cultural_knowledge_list)
1327
+
1328
+ if not deserialize:
1329
+ return cultural_knowledge_dict
1330
+
1331
+ return deserialize_cultural_knowledge_from_db(cultural_knowledge_dict)
1332
+
1333
+ except Exception as e:
1334
+ log_warning(f"Error upserting cultural knowledge: {e}")
1335
+ raise e
agno/db/gcs_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
@@ -192,3 +193,60 @@ def get_dates_to_calculate_metrics_for(starting_date: date) -> list[date]:
192
193
  if days_diff <= 0:
193
194
  return []
194
195
  return [starting_date + timedelta(days=x) for x in range(days_diff)]
196
+
197
+
198
+ # -- Cultural Knowledge util methods --
199
+ def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
200
+ """Serialize a CulturalKnowledge object for database storage.
201
+
202
+ Converts the model's separate content, categories, and notes fields
203
+ into a single dict for the database content column.
204
+
205
+ Args:
206
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
207
+
208
+ Returns:
209
+ Dict[str, Any]: A dictionary with the content field as a dict containing content, categories, and notes.
210
+ """
211
+ content_dict: Dict[str, Any] = {}
212
+ if cultural_knowledge.content is not None:
213
+ content_dict["content"] = cultural_knowledge.content
214
+ if cultural_knowledge.categories is not None:
215
+ content_dict["categories"] = cultural_knowledge.categories
216
+ if cultural_knowledge.notes is not None:
217
+ content_dict["notes"] = cultural_knowledge.notes
218
+
219
+ return content_dict if content_dict else {}
220
+
221
+
222
+ def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
223
+ """Deserialize a database row to a CulturalKnowledge object.
224
+
225
+ The database stores content as a dict containing content, categories, and notes.
226
+ This method extracts those fields and converts them back to the model format.
227
+
228
+ Args:
229
+ db_row (Dict[str, Any]): The database row as a dictionary.
230
+
231
+ Returns:
232
+ CulturalKnowledge: The cultural knowledge object.
233
+ """
234
+ # Extract content, categories, and notes from the content field
235
+ content_json = db_row.get("content", {}) or {}
236
+
237
+ return CulturalKnowledge.from_dict(
238
+ {
239
+ "id": db_row.get("id"),
240
+ "name": db_row.get("name"),
241
+ "summary": db_row.get("summary"),
242
+ "content": content_json.get("content"),
243
+ "categories": content_json.get("categories"),
244
+ "notes": content_json.get("notes"),
245
+ "metadata": db_row.get("metadata"),
246
+ "input": db_row.get("input"),
247
+ "created_at": db_row.get("created_at"),
248
+ "updated_at": db_row.get("updated_at"),
249
+ "agent_id": db_row.get("agent_id"),
250
+ "team_id": db_row.get("team_id"),
251
+ }
252
+ )
@@ -8,9 +8,12 @@ from agno.db.base import BaseDb, SessionType
8
8
  from agno.db.in_memory.utils import (
9
9
  apply_sorting,
10
10
  calculate_date_metrics,
11
+ deserialize_cultural_knowledge_from_db,
11
12
  fetch_all_sessions_data,
12
13
  get_dates_to_calculate_metrics_for,
14
+ serialize_cultural_knowledge_for_db,
13
15
  )
16
+ from agno.db.schemas.culture import CulturalKnowledge
14
17
  from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
15
18
  from agno.db.schemas.knowledge import KnowledgeRow
16
19
  from agno.db.schemas.memory import UserMemory
@@ -29,6 +32,7 @@ class InMemoryDb(BaseDb):
29
32
  self._metrics: List[Dict[str, Any]] = []
30
33
  self._eval_runs: List[Dict[str, Any]] = []
31
34
  self._knowledge: List[Dict[str, Any]] = []
35
+ self._cultural_knowledge: List[Dict[str, Any]] = []
32
36
 
33
37
  # -- Session methods --
34
38
 
@@ -1039,3 +1043,117 @@ class InMemoryDb(BaseDb):
1039
1043
  except Exception as e:
1040
1044
  log_error(f"Error renaming eval run {eval_run_id}: {e}")
1041
1045
  raise e
1046
+
1047
+ # -- Culture methods --
1048
+
1049
+ def clear_cultural_knowledge(self) -> None:
1050
+ """Delete all cultural knowledge from in-memory storage."""
1051
+ try:
1052
+ self._cultural_knowledge = []
1053
+ except Exception as e:
1054
+ log_error(f"Error clearing cultural knowledge: {e}")
1055
+ raise e
1056
+
1057
+ def delete_cultural_knowledge(self, id: str) -> None:
1058
+ """Delete a cultural knowledge entry from in-memory storage."""
1059
+ try:
1060
+ self._cultural_knowledge = [ck for ck in self._cultural_knowledge if ck.get("id") != id]
1061
+ except Exception as e:
1062
+ log_error(f"Error deleting cultural knowledge: {e}")
1063
+ raise e
1064
+
1065
+ def get_cultural_knowledge(
1066
+ self, id: str, deserialize: Optional[bool] = True
1067
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
1068
+ """Get a cultural knowledge entry from in-memory storage."""
1069
+ try:
1070
+ for ck_data in self._cultural_knowledge:
1071
+ if ck_data.get("id") == id:
1072
+ ck_data_copy = deepcopy(ck_data)
1073
+ if not deserialize:
1074
+ return ck_data_copy
1075
+ return deserialize_cultural_knowledge_from_db(ck_data_copy)
1076
+ return None
1077
+ except Exception as e:
1078
+ log_error(f"Error getting cultural knowledge: {e}")
1079
+ raise e
1080
+
1081
+ def get_all_cultural_knowledge(
1082
+ self,
1083
+ name: Optional[str] = None,
1084
+ agent_id: Optional[str] = None,
1085
+ team_id: Optional[str] = None,
1086
+ limit: Optional[int] = None,
1087
+ page: Optional[int] = None,
1088
+ sort_by: Optional[str] = None,
1089
+ sort_order: Optional[str] = None,
1090
+ deserialize: Optional[bool] = True,
1091
+ ) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
1092
+ """Get all cultural knowledge from in-memory storage."""
1093
+ try:
1094
+ filtered_ck = []
1095
+ for ck_data in self._cultural_knowledge:
1096
+ if name and ck_data.get("name") != name:
1097
+ continue
1098
+ if agent_id and ck_data.get("agent_id") != agent_id:
1099
+ continue
1100
+ if team_id and ck_data.get("team_id") != team_id:
1101
+ continue
1102
+ filtered_ck.append(ck_data)
1103
+
1104
+ # Apply sorting
1105
+ if sort_by:
1106
+ filtered_ck = apply_sorting(filtered_ck, sort_by, sort_order)
1107
+
1108
+ total_count = len(filtered_ck)
1109
+
1110
+ # Apply pagination
1111
+ if limit and page:
1112
+ start = (page - 1) * limit
1113
+ filtered_ck = filtered_ck[start : start + limit]
1114
+ elif limit:
1115
+ filtered_ck = filtered_ck[:limit]
1116
+
1117
+ if not deserialize:
1118
+ return [deepcopy(ck) for ck in filtered_ck], total_count
1119
+
1120
+ return [deserialize_cultural_knowledge_from_db(deepcopy(ck)) for ck in filtered_ck]
1121
+ except Exception as e:
1122
+ log_error(f"Error getting all cultural knowledge: {e}")
1123
+ raise e
1124
+
1125
+ def upsert_cultural_knowledge(
1126
+ self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
1127
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
1128
+ """Upsert a cultural knowledge entry into in-memory storage."""
1129
+ try:
1130
+ if not cultural_knowledge.id:
1131
+ cultural_knowledge.id = str(uuid4())
1132
+
1133
+ # Serialize content, categories, and notes into a dict for DB storage
1134
+ content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
1135
+
1136
+ # Create the item dict with serialized content
1137
+ ck_dict = {
1138
+ "id": cultural_knowledge.id,
1139
+ "name": cultural_knowledge.name,
1140
+ "summary": cultural_knowledge.summary,
1141
+ "content": content_dict if content_dict else None,
1142
+ "metadata": cultural_knowledge.metadata,
1143
+ "input": cultural_knowledge.input,
1144
+ "created_at": cultural_knowledge.created_at,
1145
+ "updated_at": int(time.time()),
1146
+ "agent_id": cultural_knowledge.agent_id,
1147
+ "team_id": cultural_knowledge.team_id,
1148
+ }
1149
+
1150
+ # Remove existing entry with same id
1151
+ self._cultural_knowledge = [ck for ck in self._cultural_knowledge if ck.get("id") != cultural_knowledge.id]
1152
+
1153
+ # Add new entry
1154
+ self._cultural_knowledge.append(ck_dict)
1155
+
1156
+ return self.get_cultural_knowledge(cultural_knowledge.id, deserialize=deserialize)
1157
+ except Exception as e:
1158
+ log_error(f"Error upserting cultural knowledge: {e}")
1159
+ raise e
@@ -5,6 +5,7 @@ from datetime import date, datetime, timedelta, timezone
5
5
  from typing import Any, Dict, List, Optional
6
6
  from uuid import uuid4
7
7
 
8
+ from agno.db.schemas.culture import CulturalKnowledge
8
9
  from agno.utils.log import log_debug
9
10
 
10
11
 
@@ -170,3 +171,60 @@ def get_dates_to_calculate_metrics_for(starting_date: date) -> list[date]:
170
171
  if days_diff <= 0:
171
172
  return []
172
173
  return [starting_date + timedelta(days=x) for x in range(days_diff)]
174
+
175
+
176
+ # -- Cultural Knowledge util methods --
177
+ def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
178
+ """Serialize a CulturalKnowledge object for database storage.
179
+
180
+ Converts the model's separate content, categories, and notes fields
181
+ into a single dict for the database content column.
182
+
183
+ Args:
184
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
185
+
186
+ Returns:
187
+ Dict[str, Any]: A dictionary with the content field as a dict containing content, categories, and notes.
188
+ """
189
+ content_dict: Dict[str, Any] = {}
190
+ if cultural_knowledge.content is not None:
191
+ content_dict["content"] = cultural_knowledge.content
192
+ if cultural_knowledge.categories is not None:
193
+ content_dict["categories"] = cultural_knowledge.categories
194
+ if cultural_knowledge.notes is not None:
195
+ content_dict["notes"] = cultural_knowledge.notes
196
+
197
+ return content_dict if content_dict else {}
198
+
199
+
200
+ def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
201
+ """Deserialize a database row to a CulturalKnowledge object.
202
+
203
+ The database stores content as a dict containing content, categories, and notes.
204
+ This method extracts those fields and converts them back to the model format.
205
+
206
+ Args:
207
+ db_row (Dict[str, Any]): The database row as a dictionary.
208
+
209
+ Returns:
210
+ CulturalKnowledge: The cultural knowledge object.
211
+ """
212
+ # Extract content, categories, and notes from the content field
213
+ content_json = db_row.get("content", {}) or {}
214
+
215
+ return CulturalKnowledge.from_dict(
216
+ {
217
+ "id": db_row.get("id"),
218
+ "name": db_row.get("name"),
219
+ "summary": db_row.get("summary"),
220
+ "content": content_json.get("content"),
221
+ "categories": content_json.get("categories"),
222
+ "notes": content_json.get("notes"),
223
+ "metadata": db_row.get("metadata"),
224
+ "input": db_row.get("input"),
225
+ "created_at": db_row.get("created_at"),
226
+ "updated_at": db_row.get("updated_at"),
227
+ "agent_id": db_row.get("agent_id"),
228
+ "team_id": db_row.get("team_id"),
229
+ }
230
+ )