agno 2.1.9__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 (54) 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/reader/field_labeled_csv_reader.py +0 -2
  44. agno/memory/manager.py +28 -11
  45. agno/models/message.py +0 -1
  46. agno/os/app.py +28 -6
  47. agno/team/team.py +1 -1
  48. agno/tools/gmail.py +59 -14
  49. agno/workflow/router.py +1 -1
  50. {agno-2.1.9.dist-info → agno-2.1.10.dist-info}/METADATA +1 -1
  51. {agno-2.1.9.dist-info → agno-2.1.10.dist-info}/RECORD +54 -51
  52. {agno-2.1.9.dist-info → agno-2.1.10.dist-info}/WHEEL +0 -0
  53. {agno-2.1.9.dist-info → agno-2.1.10.dist-info}/licenses/LICENSE +0 -0
  54. {agno-2.1.9.dist-info → agno-2.1.10.dist-info}/top_level.txt +0 -0
agno/db/mysql/mysql.py CHANGED
@@ -12,16 +12,19 @@ from agno.db.mysql.utils import (
12
12
  bulk_upsert_metrics,
13
13
  calculate_date_metrics,
14
14
  create_schema,
15
+ deserialize_cultural_knowledge_from_db,
15
16
  fetch_all_sessions_data,
16
17
  get_dates_to_calculate_metrics_for,
17
18
  is_table_available,
18
19
  is_valid_table,
20
+ serialize_cultural_knowledge_for_db,
19
21
  )
22
+ from agno.db.schemas.culture import CulturalKnowledge
20
23
  from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
21
24
  from agno.db.schemas.knowledge import KnowledgeRow
22
25
  from agno.db.schemas.memory import UserMemory
23
26
  from agno.session import AgentSession, Session, TeamSession, WorkflowSession
24
- from agno.utils.log import log_debug, log_error, log_info
27
+ from agno.utils.log import log_debug, log_error, log_info, log_warning
25
28
  from agno.utils.string import generate_id
26
29
 
27
30
  try:
@@ -42,6 +45,7 @@ class MySQLDb(BaseDb):
42
45
  db_schema: Optional[str] = None,
43
46
  db_url: Optional[str] = None,
44
47
  session_table: Optional[str] = None,
48
+ culture_table: Optional[str] = None,
45
49
  memory_table: Optional[str] = None,
46
50
  metrics_table: Optional[str] = None,
47
51
  eval_table: Optional[str] = None,
@@ -61,6 +65,7 @@ class MySQLDb(BaseDb):
61
65
  db_engine (Optional[Engine]): The SQLAlchemy database engine to use.
62
66
  db_schema (Optional[str]): The database schema to use.
63
67
  session_table (Optional[str]): Name of the table to store Agent, Team and Workflow sessions.
68
+ culture_table (Optional[str]): Name of the table to store cultural knowledge.
64
69
  memory_table (Optional[str]): Name of the table to store memories.
65
70
  metrics_table (Optional[str]): Name of the table to store metrics.
66
71
  eval_table (Optional[str]): Name of the table to store evaluation runs data.
@@ -80,6 +85,7 @@ class MySQLDb(BaseDb):
80
85
  super().__init__(
81
86
  id=id,
82
87
  session_table=session_table,
88
+ culture_table=culture_table,
83
89
  memory_table=memory_table,
84
90
  metrics_table=metrics_table,
85
91
  eval_table=eval_table,
@@ -238,6 +244,15 @@ class MySQLDb(BaseDb):
238
244
  )
239
245
  return self.knowledge_table
240
246
 
247
+ if table_type == "culture":
248
+ self.culture_table = self._get_or_create_table(
249
+ table_name=self.culture_table_name,
250
+ table_type="culture",
251
+ db_schema=self.db_schema,
252
+ create_table_if_not_found=create_table_if_not_found,
253
+ )
254
+ return self.culture_table
255
+
241
256
  raise ValueError(f"Unknown table type: {table_type}")
242
257
 
243
258
  def _get_or_create_table(
@@ -1996,6 +2011,222 @@ class MySQLDb(BaseDb):
1996
2011
  log_error(f"Error upserting eval run name {eval_run_id}: {e}")
1997
2012
  return None
1998
2013
 
2014
+ # -- Culture methods --
2015
+
2016
+ def clear_cultural_knowledge(self) -> None:
2017
+ """Delete all cultural knowledge from the database.
2018
+
2019
+ Raises:
2020
+ Exception: If an error occurs during deletion.
2021
+ """
2022
+ try:
2023
+ table = self._get_table(table_type="culture")
2024
+ if table is None:
2025
+ return
2026
+
2027
+ with self.Session() as sess, sess.begin():
2028
+ sess.execute(table.delete())
2029
+
2030
+ except Exception as e:
2031
+ log_warning(f"Exception deleting all cultural knowledge: {e}")
2032
+ raise e
2033
+
2034
+ def delete_cultural_knowledge(self, id: str) -> None:
2035
+ """Delete a cultural knowledge entry from the database.
2036
+
2037
+ Args:
2038
+ id (str): The ID of the cultural knowledge to delete.
2039
+
2040
+ Raises:
2041
+ Exception: If an error occurs during deletion.
2042
+ """
2043
+ try:
2044
+ table = self._get_table(table_type="culture")
2045
+ if table is None:
2046
+ return
2047
+
2048
+ with self.Session() as sess, sess.begin():
2049
+ delete_stmt = table.delete().where(table.c.id == id)
2050
+ result = sess.execute(delete_stmt)
2051
+
2052
+ success = result.rowcount > 0
2053
+ if success:
2054
+ log_debug(f"Successfully deleted cultural knowledge id: {id}")
2055
+ else:
2056
+ log_debug(f"No cultural knowledge found with id: {id}")
2057
+
2058
+ except Exception as e:
2059
+ log_error(f"Error deleting cultural knowledge: {e}")
2060
+ raise e
2061
+
2062
+ def get_cultural_knowledge(
2063
+ self, id: str, deserialize: Optional[bool] = True
2064
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
2065
+ """Get a cultural knowledge entry from the database.
2066
+
2067
+ Args:
2068
+ id (str): The ID of the cultural knowledge to get.
2069
+ deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
2070
+
2071
+ Returns:
2072
+ Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The cultural knowledge entry, or None if it doesn't exist.
2073
+
2074
+ Raises:
2075
+ Exception: If an error occurs during retrieval.
2076
+ """
2077
+ try:
2078
+ table = self._get_table(table_type="culture")
2079
+ if table is None:
2080
+ return None
2081
+
2082
+ with self.Session() as sess, sess.begin():
2083
+ stmt = select(table).where(table.c.id == id)
2084
+ result = sess.execute(stmt).fetchone()
2085
+ if result is None:
2086
+ return None
2087
+
2088
+ db_row = dict(result._mapping)
2089
+ if not db_row or not deserialize:
2090
+ return db_row
2091
+
2092
+ return deserialize_cultural_knowledge_from_db(db_row)
2093
+
2094
+ except Exception as e:
2095
+ log_error(f"Exception reading from cultural knowledge table: {e}")
2096
+ raise e
2097
+
2098
+ def get_all_cultural_knowledge(
2099
+ self,
2100
+ name: Optional[str] = None,
2101
+ agent_id: Optional[str] = None,
2102
+ team_id: Optional[str] = None,
2103
+ limit: Optional[int] = None,
2104
+ page: Optional[int] = None,
2105
+ sort_by: Optional[str] = None,
2106
+ sort_order: Optional[str] = None,
2107
+ deserialize: Optional[bool] = True,
2108
+ ) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
2109
+ """Get all cultural knowledge from the database as CulturalKnowledge objects.
2110
+
2111
+ Args:
2112
+ name (Optional[str]): The name of the cultural knowledge to filter by.
2113
+ agent_id (Optional[str]): The ID of the agent to filter by.
2114
+ team_id (Optional[str]): The ID of the team to filter by.
2115
+ limit (Optional[int]): The maximum number of cultural knowledge entries to return.
2116
+ page (Optional[int]): The page number.
2117
+ sort_by (Optional[str]): The column to sort by.
2118
+ sort_order (Optional[str]): The order to sort by.
2119
+ deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
2120
+
2121
+ Returns:
2122
+ Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
2123
+ - When deserialize=True: List of CulturalKnowledge objects
2124
+ - When deserialize=False: List of CulturalKnowledge dictionaries and total count
2125
+
2126
+ Raises:
2127
+ Exception: If an error occurs during retrieval.
2128
+ """
2129
+ try:
2130
+ table = self._get_table(table_type="culture")
2131
+ if table is None:
2132
+ return [] if deserialize else ([], 0)
2133
+
2134
+ with self.Session() as sess, sess.begin():
2135
+ stmt = select(table)
2136
+
2137
+ # Filtering
2138
+ if name is not None:
2139
+ stmt = stmt.where(table.c.name == name)
2140
+ if agent_id is not None:
2141
+ stmt = stmt.where(table.c.agent_id == agent_id)
2142
+ if team_id is not None:
2143
+ stmt = stmt.where(table.c.team_id == team_id)
2144
+
2145
+ # Get total count after applying filtering
2146
+ count_stmt = select(func.count()).select_from(stmt.alias())
2147
+ total_count = sess.execute(count_stmt).scalar()
2148
+
2149
+ # Sorting
2150
+ stmt = apply_sorting(stmt, table, sort_by, sort_order)
2151
+ # Paginating
2152
+ if limit is not None:
2153
+ stmt = stmt.limit(limit)
2154
+ if page is not None:
2155
+ stmt = stmt.offset((page - 1) * limit)
2156
+
2157
+ result = sess.execute(stmt).fetchall()
2158
+ if not result:
2159
+ return [] if deserialize else ([], 0)
2160
+
2161
+ db_rows = [dict(record._mapping) for record in result]
2162
+
2163
+ if not deserialize:
2164
+ return db_rows, total_count
2165
+
2166
+ return [deserialize_cultural_knowledge_from_db(row) for row in db_rows]
2167
+
2168
+ except Exception as e:
2169
+ log_error(f"Error reading from cultural knowledge table: {e}")
2170
+ raise e
2171
+
2172
+ def upsert_cultural_knowledge(
2173
+ self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
2174
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
2175
+ """Upsert a cultural knowledge entry into the database.
2176
+
2177
+ Args:
2178
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge to upsert.
2179
+ deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
2180
+
2181
+ Returns:
2182
+ Optional[CulturalKnowledge]: The upserted cultural knowledge entry.
2183
+
2184
+ Raises:
2185
+ Exception: If an error occurs during upsert.
2186
+ """
2187
+ try:
2188
+ table = self._get_table(table_type="culture", create_table_if_not_found=True)
2189
+ if table is None:
2190
+ return None
2191
+
2192
+ if cultural_knowledge.id is None:
2193
+ cultural_knowledge.id = str(uuid4())
2194
+
2195
+ # Serialize content, categories, and notes into a JSON dict for DB storage
2196
+ content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
2197
+
2198
+ with self.Session() as sess, sess.begin():
2199
+ stmt = mysql.insert(table).values(
2200
+ id=cultural_knowledge.id,
2201
+ name=cultural_knowledge.name,
2202
+ summary=cultural_knowledge.summary,
2203
+ content=content_dict if content_dict else None,
2204
+ metadata=cultural_knowledge.metadata,
2205
+ input=cultural_knowledge.input,
2206
+ created_at=cultural_knowledge.created_at,
2207
+ updated_at=int(time.time()),
2208
+ agent_id=cultural_knowledge.agent_id,
2209
+ team_id=cultural_knowledge.team_id,
2210
+ )
2211
+ stmt = stmt.on_duplicate_key_update(
2212
+ name=cultural_knowledge.name,
2213
+ summary=cultural_knowledge.summary,
2214
+ content=content_dict if content_dict else None,
2215
+ metadata=cultural_knowledge.metadata,
2216
+ input=cultural_knowledge.input,
2217
+ updated_at=int(time.time()),
2218
+ agent_id=cultural_knowledge.agent_id,
2219
+ team_id=cultural_knowledge.team_id,
2220
+ )
2221
+ sess.execute(stmt)
2222
+
2223
+ # Fetch the inserted/updated row
2224
+ return self.get_cultural_knowledge(id=cultural_knowledge.id, deserialize=deserialize)
2225
+
2226
+ except Exception as e:
2227
+ log_error(f"Error upserting cultural knowledge: {e}")
2228
+ raise e
2229
+
1999
2230
  # -- Migrations --
2000
2231
 
2001
2232
  def migrate_table_from_v1_to_v2(self, v1_db_schema: str, v1_table_name: str, v1_table_type: str):
agno/db/mysql/schemas.py CHANGED
@@ -98,6 +98,19 @@ METRICS_TABLE_SCHEMA = {
98
98
  ],
99
99
  }
100
100
 
101
+ CULTURAL_KNOWLEDGE_TABLE_SCHEMA = {
102
+ "id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
103
+ "name": {"type": lambda: String(255), "nullable": False, "index": True},
104
+ "summary": {"type": Text, "nullable": True},
105
+ "content": {"type": JSON, "nullable": True},
106
+ "metadata": {"type": JSON, "nullable": True},
107
+ "input": {"type": Text, "nullable": True},
108
+ "created_at": {"type": BigInteger, "nullable": True},
109
+ "updated_at": {"type": BigInteger, "nullable": True},
110
+ "agent_id": {"type": lambda: String(128), "nullable": True},
111
+ "team_id": {"type": lambda: String(128), "nullable": True},
112
+ }
113
+
101
114
 
102
115
  def get_table_schema_definition(table_type: str) -> dict[str, Any]:
103
116
  """
@@ -115,6 +128,7 @@ def get_table_schema_definition(table_type: str) -> dict[str, Any]:
115
128
  "metrics": METRICS_TABLE_SCHEMA,
116
129
  "memories": USER_MEMORY_TABLE_SCHEMA,
117
130
  "knowledge": KNOWLEDGE_TABLE_SCHEMA,
131
+ "culture": CULTURAL_KNOWLEDGE_TABLE_SCHEMA,
118
132
  }
119
133
 
120
134
  schema = schemas.get(table_type, {})
agno/db/mysql/utils.py CHANGED
@@ -8,6 +8,7 @@ from uuid import uuid4
8
8
  from sqlalchemy import Engine
9
9
 
10
10
  from agno.db.mysql.schemas import get_table_schema_definition
11
+ from agno.db.schemas.culture import CulturalKnowledge
11
12
  from agno.utils.log import log_debug, log_error, log_warning
12
13
 
13
14
  try:
@@ -295,3 +296,60 @@ def get_dates_to_calculate_metrics_for(starting_date: date) -> list[date]:
295
296
  if days_diff <= 0:
296
297
  return []
297
298
  return [starting_date + timedelta(days=x) for x in range(days_diff)]
299
+
300
+
301
+ # -- Cultural Knowledge util methods --
302
+ def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
303
+ """Serialize a CulturalKnowledge object for database storage.
304
+
305
+ Converts the model's separate content, categories, and notes fields
306
+ into a single JSON dict for the database content column.
307
+
308
+ Args:
309
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
310
+
311
+ Returns:
312
+ Dict[str, Any]: A dictionary with the content field as JSON containing content, categories, and notes.
313
+ """
314
+ content_dict: Dict[str, Any] = {}
315
+ if cultural_knowledge.content is not None:
316
+ content_dict["content"] = cultural_knowledge.content
317
+ if cultural_knowledge.categories is not None:
318
+ content_dict["categories"] = cultural_knowledge.categories
319
+ if cultural_knowledge.notes is not None:
320
+ content_dict["notes"] = cultural_knowledge.notes
321
+
322
+ return content_dict if content_dict else {}
323
+
324
+
325
+ def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
326
+ """Deserialize a database row to a CulturalKnowledge object.
327
+
328
+ The database stores content as a JSON dict containing content, categories, and notes.
329
+ This method extracts those fields and converts them back to the model format.
330
+
331
+ Args:
332
+ db_row (Dict[str, Any]): The database row as a dictionary.
333
+
334
+ Returns:
335
+ CulturalKnowledge: The cultural knowledge object.
336
+ """
337
+ # Extract content, categories, and notes from the JSON content field
338
+ content_json = db_row.get("content", {}) or {}
339
+
340
+ return CulturalKnowledge.from_dict(
341
+ {
342
+ "id": db_row.get("id"),
343
+ "name": db_row.get("name"),
344
+ "summary": db_row.get("summary"),
345
+ "content": content_json.get("content"),
346
+ "categories": content_json.get("categories"),
347
+ "notes": content_json.get("notes"),
348
+ "metadata": db_row.get("metadata"),
349
+ "input": db_row.get("input"),
350
+ "created_at": db_row.get("created_at"),
351
+ "updated_at": db_row.get("updated_at"),
352
+ "agent_id": db_row.get("agent_id"),
353
+ "team_id": db_row.get("team_id"),
354
+ }
355
+ )
@@ -10,11 +10,14 @@ from agno.db.postgres.utils import (
10
10
  bulk_upsert_metrics,
11
11
  calculate_date_metrics,
12
12
  create_schema,
13
+ deserialize_cultural_knowledge_from_db,
13
14
  fetch_all_sessions_data,
14
15
  get_dates_to_calculate_metrics_for,
15
16
  is_table_available,
16
17
  is_valid_table,
18
+ serialize_cultural_knowledge_for_db,
17
19
  )
20
+ from agno.db.schemas.culture import CulturalKnowledge
18
21
  from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
19
22
  from agno.db.schemas.knowledge import KnowledgeRow
20
23
  from agno.db.schemas.memory import UserMemory
@@ -40,6 +43,7 @@ class PostgresDb(BaseDb):
40
43
  db_engine: Optional[Engine] = None,
41
44
  db_schema: Optional[str] = None,
42
45
  session_table: Optional[str] = None,
46
+ culture_table: Optional[str] = None,
43
47
  memory_table: Optional[str] = None,
44
48
  metrics_table: Optional[str] = None,
45
49
  eval_table: Optional[str] = None,
@@ -63,6 +67,7 @@ class PostgresDb(BaseDb):
63
67
  metrics_table (Optional[str]): Name of the table to store metrics.
64
68
  eval_table (Optional[str]): Name of the table to store evaluation runs data.
65
69
  knowledge_table (Optional[str]): Name of the table to store knowledge content.
70
+ culture_table (Optional[str]): Name of the table to store cultural knowledge.
66
71
  id (Optional[str]): ID of the database.
67
72
 
68
73
  Raises:
@@ -91,6 +96,7 @@ class PostgresDb(BaseDb):
91
96
  metrics_table=metrics_table,
92
97
  eval_table=eval_table,
93
98
  knowledge_table=knowledge_table,
99
+ culture_table=culture_table,
94
100
  )
95
101
 
96
102
  self.db_schema: str = db_schema if db_schema is not None else "ai"
@@ -231,6 +237,15 @@ class PostgresDb(BaseDb):
231
237
  )
232
238
  return self.knowledge_table
233
239
 
240
+ if table_type == "culture":
241
+ self.culture_table = self._get_or_create_table(
242
+ table_name=self.culture_table_name,
243
+ table_type="culture",
244
+ db_schema=self.db_schema,
245
+ create_table_if_not_found=create_table_if_not_found,
246
+ )
247
+ return self.culture_table
248
+
234
249
  raise ValueError(f"Unknown table type: {table_type}")
235
250
 
236
251
  def _get_or_create_table(
@@ -1937,6 +1952,233 @@ class PostgresDb(BaseDb):
1937
1952
  log_error(f"Error upserting eval run name {eval_run_id}: {e}")
1938
1953
  raise e
1939
1954
 
1955
+ # -- Culture methods --
1956
+
1957
+ def clear_cultural_knowledge(self) -> None:
1958
+ """Delete all cultural knowledge from the database.
1959
+
1960
+ Raises:
1961
+ Exception: If an error occurs during deletion.
1962
+ """
1963
+ try:
1964
+ table = self._get_table(table_type="culture")
1965
+ if table is None:
1966
+ return
1967
+
1968
+ with self.Session() as sess, sess.begin():
1969
+ sess.execute(table.delete())
1970
+
1971
+ except Exception as e:
1972
+ log_warning(f"Exception deleting all cultural knowledge: {e}")
1973
+ raise e
1974
+
1975
+ def delete_cultural_knowledge(self, id: str) -> None:
1976
+ """Delete a cultural knowledge entry from the database.
1977
+
1978
+ Args:
1979
+ id (str): The ID of the cultural knowledge to delete.
1980
+
1981
+ Raises:
1982
+ Exception: If an error occurs during deletion.
1983
+ """
1984
+ try:
1985
+ table = self._get_table(table_type="culture")
1986
+ if table is None:
1987
+ return
1988
+
1989
+ with self.Session() as sess, sess.begin():
1990
+ delete_stmt = table.delete().where(table.c.id == id)
1991
+ result = sess.execute(delete_stmt)
1992
+
1993
+ success = result.rowcount > 0
1994
+ if success:
1995
+ log_debug(f"Successfully deleted cultural knowledge id: {id}")
1996
+ else:
1997
+ log_debug(f"No cultural knowledge found with id: {id}")
1998
+
1999
+ except Exception as e:
2000
+ log_error(f"Error deleting cultural knowledge: {e}")
2001
+ raise e
2002
+
2003
+ def get_cultural_knowledge(
2004
+ self, id: str, deserialize: Optional[bool] = True
2005
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
2006
+ """Get a cultural knowledge entry from the database.
2007
+
2008
+ Args:
2009
+ id (str): The ID of the cultural knowledge to get.
2010
+ deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
2011
+
2012
+ Returns:
2013
+ Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The cultural knowledge entry, or None if it doesn't exist.
2014
+
2015
+ Raises:
2016
+ Exception: If an error occurs during retrieval.
2017
+ """
2018
+ try:
2019
+ table = self._get_table(table_type="culture")
2020
+ if table is None:
2021
+ return None
2022
+
2023
+ with self.Session() as sess, sess.begin():
2024
+ stmt = select(table).where(table.c.id == id)
2025
+ result = sess.execute(stmt).fetchone()
2026
+ if result is None:
2027
+ return None
2028
+
2029
+ db_row = dict(result._mapping)
2030
+ if not db_row or not deserialize:
2031
+ return db_row
2032
+
2033
+ return deserialize_cultural_knowledge_from_db(db_row)
2034
+
2035
+ except Exception as e:
2036
+ log_error(f"Exception reading from cultural knowledge table: {e}")
2037
+ raise e
2038
+
2039
+ def get_all_cultural_knowledge(
2040
+ self,
2041
+ name: Optional[str] = None,
2042
+ agent_id: Optional[str] = None,
2043
+ team_id: Optional[str] = None,
2044
+ limit: Optional[int] = None,
2045
+ page: Optional[int] = None,
2046
+ sort_by: Optional[str] = None,
2047
+ sort_order: Optional[str] = None,
2048
+ deserialize: Optional[bool] = True,
2049
+ ) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
2050
+ """Get all cultural knowledge from the database as CulturalKnowledge objects.
2051
+
2052
+ Args:
2053
+ name (Optional[str]): The name of the cultural knowledge to filter by.
2054
+ agent_id (Optional[str]): The ID of the agent to filter by.
2055
+ team_id (Optional[str]): The ID of the team to filter by.
2056
+ limit (Optional[int]): The maximum number of cultural knowledge entries to return.
2057
+ page (Optional[int]): The page number.
2058
+ sort_by (Optional[str]): The column to sort by.
2059
+ sort_order (Optional[str]): The order to sort by.
2060
+ deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
2061
+
2062
+ Returns:
2063
+ Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
2064
+ - When deserialize=True: List of CulturalKnowledge objects
2065
+ - When deserialize=False: List of CulturalKnowledge dictionaries and total count
2066
+
2067
+ Raises:
2068
+ Exception: If an error occurs during retrieval.
2069
+ """
2070
+ try:
2071
+ table = self._get_table(table_type="culture")
2072
+ if table is None:
2073
+ return [] if deserialize else ([], 0)
2074
+
2075
+ with self.Session() as sess, sess.begin():
2076
+ stmt = select(table)
2077
+
2078
+ # Filtering
2079
+ if name is not None:
2080
+ stmt = stmt.where(table.c.name == name)
2081
+ if agent_id is not None:
2082
+ stmt = stmt.where(table.c.agent_id == agent_id)
2083
+ if team_id is not None:
2084
+ stmt = stmt.where(table.c.team_id == team_id)
2085
+
2086
+ # Get total count after applying filtering
2087
+ count_stmt = select(func.count()).select_from(stmt.alias())
2088
+ total_count = sess.execute(count_stmt).scalar()
2089
+
2090
+ # Sorting
2091
+ stmt = apply_sorting(stmt, table, sort_by, sort_order)
2092
+ # Paginating
2093
+ if limit is not None:
2094
+ stmt = stmt.limit(limit)
2095
+ if page is not None:
2096
+ stmt = stmt.offset((page - 1) * limit)
2097
+
2098
+ result = sess.execute(stmt).fetchall()
2099
+ if not result:
2100
+ return [] if deserialize else ([], 0)
2101
+
2102
+ db_rows = [dict(record._mapping) for record in result]
2103
+
2104
+ if not deserialize:
2105
+ return db_rows, total_count
2106
+
2107
+ return [deserialize_cultural_knowledge_from_db(row) for row in db_rows]
2108
+
2109
+ except Exception as e:
2110
+ log_error(f"Error reading from cultural knowledge table: {e}")
2111
+ raise e
2112
+
2113
+ def upsert_cultural_knowledge(
2114
+ self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
2115
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
2116
+ """Upsert a cultural knowledge entry into the database.
2117
+
2118
+ Args:
2119
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge to upsert.
2120
+ deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
2121
+
2122
+ Returns:
2123
+ Optional[CulturalKnowledge]: The upserted cultural knowledge entry.
2124
+
2125
+ Raises:
2126
+ Exception: If an error occurs during upsert.
2127
+ """
2128
+ try:
2129
+ table = self._get_table(table_type="culture", create_table_if_not_found=True)
2130
+ if table is None:
2131
+ return None
2132
+
2133
+ if cultural_knowledge.id is None:
2134
+ cultural_knowledge.id = str(uuid4())
2135
+
2136
+ # Serialize content, categories, and notes into a JSON dict for DB storage
2137
+ content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
2138
+
2139
+ with self.Session() as sess, sess.begin():
2140
+ stmt = postgresql.insert(table).values(
2141
+ id=cultural_knowledge.id,
2142
+ name=cultural_knowledge.name,
2143
+ summary=cultural_knowledge.summary,
2144
+ content=content_dict if content_dict else None,
2145
+ metadata=cultural_knowledge.metadata,
2146
+ input=cultural_knowledge.input,
2147
+ created_at=cultural_knowledge.created_at,
2148
+ updated_at=int(time.time()),
2149
+ agent_id=cultural_knowledge.agent_id,
2150
+ team_id=cultural_knowledge.team_id,
2151
+ )
2152
+ stmt = stmt.on_conflict_do_update(
2153
+ index_elements=["id"],
2154
+ set_=dict(
2155
+ name=cultural_knowledge.name,
2156
+ summary=cultural_knowledge.summary,
2157
+ content=content_dict if content_dict else None,
2158
+ metadata=cultural_knowledge.metadata,
2159
+ input=cultural_knowledge.input,
2160
+ updated_at=int(time.time()),
2161
+ agent_id=cultural_knowledge.agent_id,
2162
+ team_id=cultural_knowledge.team_id,
2163
+ ),
2164
+ ).returning(table)
2165
+
2166
+ result = sess.execute(stmt)
2167
+ row = result.fetchone()
2168
+
2169
+ if row is None:
2170
+ return None
2171
+
2172
+ db_row = dict(row._mapping)
2173
+ if not db_row or not deserialize:
2174
+ return db_row
2175
+
2176
+ return deserialize_cultural_knowledge_from_db(db_row)
2177
+
2178
+ except Exception as e:
2179
+ log_error(f"Error upserting cultural knowledge: {e}")
2180
+ raise e
2181
+
1940
2182
  # -- Migrations --
1941
2183
 
1942
2184
  def migrate_table_from_v1_to_v2(self, v1_db_schema: str, v1_table_name: str, v1_table_type: str):