agno 2.1.9__py3-none-any.whl → 2.2.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 (83) hide show
  1. agno/agent/agent.py +2048 -1204
  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/anthropic/claude.py +2 -2
  46. agno/models/message.py +0 -1
  47. agno/models/ollama/chat.py +7 -2
  48. agno/os/app.py +29 -7
  49. agno/os/interfaces/a2a/router.py +2 -2
  50. agno/os/interfaces/agui/router.py +2 -2
  51. agno/os/router.py +7 -7
  52. agno/os/routers/evals/schemas.py +31 -31
  53. agno/os/routers/health.py +6 -2
  54. agno/os/routers/knowledge/schemas.py +49 -47
  55. agno/os/routers/memory/schemas.py +16 -16
  56. agno/os/routers/metrics/schemas.py +16 -16
  57. agno/os/routers/session/session.py +382 -7
  58. agno/os/schema.py +254 -231
  59. agno/os/utils.py +1 -1
  60. agno/run/agent.py +49 -1
  61. agno/run/team.py +43 -0
  62. agno/session/summary.py +45 -13
  63. agno/session/team.py +90 -5
  64. agno/team/team.py +1118 -857
  65. agno/tools/gmail.py +59 -14
  66. agno/utils/agent.py +372 -0
  67. agno/utils/events.py +144 -2
  68. agno/utils/print_response/agent.py +10 -6
  69. agno/utils/print_response/team.py +6 -4
  70. agno/utils/print_response/workflow.py +7 -5
  71. agno/utils/team.py +9 -8
  72. agno/workflow/condition.py +17 -9
  73. agno/workflow/loop.py +18 -10
  74. agno/workflow/parallel.py +14 -6
  75. agno/workflow/router.py +17 -9
  76. agno/workflow/step.py +14 -6
  77. agno/workflow/steps.py +14 -6
  78. agno/workflow/workflow.py +245 -122
  79. {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/METADATA +60 -23
  80. {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/RECORD +83 -79
  81. {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/WHEEL +0 -0
  82. {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/licenses/LICENSE +0 -0
  83. {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union
5
5
  from uuid import uuid4
6
6
 
7
7
  from agno.db.base import BaseDb, SessionType
8
+ from agno.db.schemas.culture import CulturalKnowledge
8
9
  from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
9
10
  from agno.db.schemas.knowledge import KnowledgeRow
10
11
  from agno.db.schemas.memory import UserMemory
@@ -14,10 +15,12 @@ from agno.db.singlestore.utils import (
14
15
  bulk_upsert_metrics,
15
16
  calculate_date_metrics,
16
17
  create_schema,
18
+ deserialize_cultural_knowledge_from_db,
17
19
  fetch_all_sessions_data,
18
20
  get_dates_to_calculate_metrics_for,
19
21
  is_table_available,
20
22
  is_valid_table,
23
+ serialize_cultural_knowledge_for_db,
21
24
  )
22
25
  from agno.session import AgentSession, Session, TeamSession, WorkflowSession
23
26
  from agno.utils.log import log_debug, log_error, log_info, log_warning
@@ -42,6 +45,7 @@ class SingleStoreDb(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 SingleStoreDb(BaseDb):
61
65
  db_schema (Optional[str]): The database schema to use.
62
66
  db_url (Optional[str]): The database URL to connect to.
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.
@@ -79,6 +84,7 @@ class SingleStoreDb(BaseDb):
79
84
  super().__init__(
80
85
  id=id,
81
86
  session_table=session_table,
87
+ culture_table=culture_table,
82
88
  memory_table=memory_table,
83
89
  metrics_table=metrics_table,
84
90
  eval_table=eval_table,
@@ -316,6 +322,15 @@ class SingleStoreDb(BaseDb):
316
322
  )
317
323
  return self.knowledge_table
318
324
 
325
+ if table_type == "culture":
326
+ self.culture_table = self._get_or_create_table(
327
+ table_name=self.culture_table_name,
328
+ table_type="culture",
329
+ db_schema=self.db_schema,
330
+ create_table_if_not_found=create_table_if_not_found,
331
+ )
332
+ return self.culture_table
333
+
319
334
  raise ValueError(f"Unknown table type: {table_type}")
320
335
 
321
336
  def _get_or_create_table(
@@ -2018,3 +2033,219 @@ class SingleStoreDb(BaseDb):
2018
2033
  except Exception as e:
2019
2034
  log_error(f"Error renaming eval run {eval_run_id}: {e}")
2020
2035
  raise e
2036
+
2037
+ # -- Culture methods --
2038
+
2039
+ def clear_cultural_knowledge(self) -> None:
2040
+ """Delete all cultural knowledge from the database.
2041
+
2042
+ Raises:
2043
+ Exception: If an error occurs during deletion.
2044
+ """
2045
+ try:
2046
+ table = self._get_table(table_type="culture")
2047
+ if table is None:
2048
+ return
2049
+
2050
+ with self.Session() as sess, sess.begin():
2051
+ sess.execute(table.delete())
2052
+
2053
+ except Exception as e:
2054
+ log_warning(f"Exception deleting all cultural knowledge: {e}")
2055
+ raise e
2056
+
2057
+ def delete_cultural_knowledge(self, id: str) -> None:
2058
+ """Delete a cultural knowledge entry from the database.
2059
+
2060
+ Args:
2061
+ id (str): The ID of the cultural knowledge to delete.
2062
+
2063
+ Raises:
2064
+ Exception: If an error occurs during deletion.
2065
+ """
2066
+ try:
2067
+ table = self._get_table(table_type="culture")
2068
+ if table is None:
2069
+ return
2070
+
2071
+ with self.Session() as sess, sess.begin():
2072
+ delete_stmt = table.delete().where(table.c.id == id)
2073
+ result = sess.execute(delete_stmt)
2074
+
2075
+ success = result.rowcount > 0
2076
+ if success:
2077
+ log_debug(f"Successfully deleted cultural knowledge id: {id}")
2078
+ else:
2079
+ log_debug(f"No cultural knowledge found with id: {id}")
2080
+
2081
+ except Exception as e:
2082
+ log_error(f"Error deleting cultural knowledge: {e}")
2083
+ raise e
2084
+
2085
+ def get_cultural_knowledge(
2086
+ self, id: str, deserialize: Optional[bool] = True
2087
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
2088
+ """Get a cultural knowledge entry from the database.
2089
+
2090
+ Args:
2091
+ id (str): The ID of the cultural knowledge to get.
2092
+ deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
2093
+
2094
+ Returns:
2095
+ Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The cultural knowledge entry, or None if it doesn't exist.
2096
+
2097
+ Raises:
2098
+ Exception: If an error occurs during retrieval.
2099
+ """
2100
+ try:
2101
+ table = self._get_table(table_type="culture")
2102
+ if table is None:
2103
+ return None
2104
+
2105
+ with self.Session() as sess, sess.begin():
2106
+ stmt = select(table).where(table.c.id == id)
2107
+ result = sess.execute(stmt).fetchone()
2108
+ if result is None:
2109
+ return None
2110
+
2111
+ db_row = dict(result._mapping)
2112
+ if not db_row or not deserialize:
2113
+ return db_row
2114
+
2115
+ return deserialize_cultural_knowledge_from_db(db_row)
2116
+
2117
+ except Exception as e:
2118
+ log_error(f"Exception reading from cultural knowledge table: {e}")
2119
+ raise e
2120
+
2121
+ def get_all_cultural_knowledge(
2122
+ self,
2123
+ name: Optional[str] = None,
2124
+ agent_id: Optional[str] = None,
2125
+ team_id: Optional[str] = None,
2126
+ limit: Optional[int] = None,
2127
+ page: Optional[int] = None,
2128
+ sort_by: Optional[str] = None,
2129
+ sort_order: Optional[str] = None,
2130
+ deserialize: Optional[bool] = True,
2131
+ ) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
2132
+ """Get all cultural knowledge from the database as CulturalKnowledge objects.
2133
+
2134
+ Args:
2135
+ name (Optional[str]): The name of the cultural knowledge to filter by.
2136
+ agent_id (Optional[str]): The ID of the agent to filter by.
2137
+ team_id (Optional[str]): The ID of the team to filter by.
2138
+ limit (Optional[int]): The maximum number of cultural knowledge entries to return.
2139
+ page (Optional[int]): The page number.
2140
+ sort_by (Optional[str]): The column to sort by.
2141
+ sort_order (Optional[str]): The order to sort by.
2142
+ deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
2143
+
2144
+ Returns:
2145
+ Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
2146
+ - When deserialize=True: List of CulturalKnowledge objects
2147
+ - When deserialize=False: List of CulturalKnowledge dictionaries and total count
2148
+
2149
+ Raises:
2150
+ Exception: If an error occurs during retrieval.
2151
+ """
2152
+ try:
2153
+ table = self._get_table(table_type="culture")
2154
+ if table is None:
2155
+ return [] if deserialize else ([], 0)
2156
+
2157
+ with self.Session() as sess, sess.begin():
2158
+ stmt = select(table)
2159
+
2160
+ # Filtering
2161
+ if name is not None:
2162
+ stmt = stmt.where(table.c.name == name)
2163
+ if agent_id is not None:
2164
+ stmt = stmt.where(table.c.agent_id == agent_id)
2165
+ if team_id is not None:
2166
+ stmt = stmt.where(table.c.team_id == team_id)
2167
+
2168
+ # Get total count after applying filtering
2169
+ count_stmt = select(func.count()).select_from(stmt.alias())
2170
+ total_count = sess.execute(count_stmt).scalar()
2171
+
2172
+ # Sorting
2173
+ stmt = apply_sorting(stmt, table, sort_by, sort_order)
2174
+ # Paginating
2175
+ if limit is not None:
2176
+ stmt = stmt.limit(limit)
2177
+ if page is not None:
2178
+ stmt = stmt.offset((page - 1) * limit)
2179
+
2180
+ result = sess.execute(stmt).fetchall()
2181
+ if not result:
2182
+ return [] if deserialize else ([], 0)
2183
+
2184
+ db_rows = [dict(record._mapping) for record in result]
2185
+
2186
+ if not deserialize:
2187
+ return db_rows, total_count
2188
+
2189
+ return [deserialize_cultural_knowledge_from_db(row) for row in db_rows]
2190
+
2191
+ except Exception as e:
2192
+ log_error(f"Error reading from cultural knowledge table: {e}")
2193
+ raise e
2194
+
2195
+ def upsert_cultural_knowledge(
2196
+ self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
2197
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
2198
+ """Upsert a cultural knowledge entry into the database.
2199
+
2200
+ Args:
2201
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge to upsert.
2202
+ deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
2203
+
2204
+ Returns:
2205
+ Optional[CulturalKnowledge]: The upserted cultural knowledge entry.
2206
+
2207
+ Raises:
2208
+ Exception: If an error occurs during upsert.
2209
+ """
2210
+ try:
2211
+ table = self._get_table(table_type="culture", create_table_if_not_found=True)
2212
+ if table is None:
2213
+ return None
2214
+
2215
+ if cultural_knowledge.id is None:
2216
+ cultural_knowledge.id = str(uuid4())
2217
+
2218
+ # Serialize content, categories, and notes into a JSON dict for DB storage
2219
+ content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
2220
+
2221
+ with self.Session() as sess, sess.begin():
2222
+ stmt = mysql.insert(table).values(
2223
+ id=cultural_knowledge.id,
2224
+ name=cultural_knowledge.name,
2225
+ summary=cultural_knowledge.summary,
2226
+ content=content_dict if content_dict else None,
2227
+ metadata=cultural_knowledge.metadata,
2228
+ input=cultural_knowledge.input,
2229
+ created_at=cultural_knowledge.created_at,
2230
+ updated_at=int(time.time()),
2231
+ agent_id=cultural_knowledge.agent_id,
2232
+ team_id=cultural_knowledge.team_id,
2233
+ )
2234
+ stmt = stmt.on_duplicate_key_update(
2235
+ name=cultural_knowledge.name,
2236
+ summary=cultural_knowledge.summary,
2237
+ content=content_dict if content_dict else None,
2238
+ metadata=cultural_knowledge.metadata,
2239
+ input=cultural_knowledge.input,
2240
+ updated_at=int(time.time()),
2241
+ agent_id=cultural_knowledge.agent_id,
2242
+ team_id=cultural_knowledge.team_id,
2243
+ )
2244
+ sess.execute(stmt)
2245
+
2246
+ # Fetch the inserted/updated row
2247
+ return self.get_cultural_knowledge(id=cultural_knowledge.id, deserialize=deserialize)
2248
+
2249
+ except Exception as e:
2250
+ log_error(f"Error upserting cultural knowledge: {e}")
2251
+ raise e
@@ -7,6 +7,7 @@ from uuid import uuid4
7
7
 
8
8
  from sqlalchemy import Engine
9
9
 
10
+ from agno.db.schemas.culture import CulturalKnowledge
10
11
  from agno.db.singlestore.schemas import get_table_schema_definition
11
12
  from agno.utils.log import log_debug, log_error, log_warning
12
13
 
@@ -324,3 +325,60 @@ def get_dates_to_calculate_metrics_for(starting_date: date) -> list[date]:
324
325
  if days_diff <= 0:
325
326
  return []
326
327
  return [starting_date + timedelta(days=x) for x in range(days_diff)]
328
+
329
+
330
+ # -- Cultural Knowledge util methods --
331
+ def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
332
+ """Serialize a CulturalKnowledge object for database storage.
333
+
334
+ Converts the model's separate content, categories, and notes fields
335
+ into a single JSON dict for the database content column.
336
+
337
+ Args:
338
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
339
+
340
+ Returns:
341
+ Dict[str, Any]: A dictionary with the content field as JSON containing content, categories, and notes.
342
+ """
343
+ content_dict: Dict[str, Any] = {}
344
+ if cultural_knowledge.content is not None:
345
+ content_dict["content"] = cultural_knowledge.content
346
+ if cultural_knowledge.categories is not None:
347
+ content_dict["categories"] = cultural_knowledge.categories
348
+ if cultural_knowledge.notes is not None:
349
+ content_dict["notes"] = cultural_knowledge.notes
350
+
351
+ return content_dict if content_dict else {}
352
+
353
+
354
+ def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
355
+ """Deserialize a database row to a CulturalKnowledge object.
356
+
357
+ The database stores content as a JSON dict containing content, categories, and notes.
358
+ This method extracts those fields and converts them back to the model format.
359
+
360
+ Args:
361
+ db_row (Dict[str, Any]): The database row as a dictionary.
362
+
363
+ Returns:
364
+ CulturalKnowledge: The cultural knowledge object.
365
+ """
366
+ # Extract content, categories, and notes from the JSON content field
367
+ content_json = db_row.get("content", {}) or {}
368
+
369
+ return CulturalKnowledge.from_dict(
370
+ {
371
+ "id": db_row.get("id"),
372
+ "name": db_row.get("name"),
373
+ "summary": db_row.get("summary"),
374
+ "content": content_json.get("content"),
375
+ "categories": content_json.get("categories"),
376
+ "notes": content_json.get("notes"),
377
+ "metadata": db_row.get("metadata"),
378
+ "input": db_row.get("input"),
379
+ "created_at": db_row.get("created_at"),
380
+ "updated_at": db_row.get("updated_at"),
381
+ "agent_id": db_row.get("agent_id"),
382
+ "team_id": db_row.get("team_id"),
383
+ }
384
+ )
agno/db/sqlite/schemas.py CHANGED
@@ -93,6 +93,19 @@ METRICS_TABLE_SCHEMA = {
93
93
  ],
94
94
  }
95
95
 
96
+ CULTURAL_KNOWLEDGE_TABLE_SCHEMA = {
97
+ "id": {"type": String, "primary_key": True, "nullable": False},
98
+ "name": {"type": String, "nullable": False, "index": True},
99
+ "summary": {"type": String, "nullable": True},
100
+ "content": {"type": JSON, "nullable": True},
101
+ "metadata": {"type": JSON, "nullable": True},
102
+ "input": {"type": String, "nullable": True},
103
+ "created_at": {"type": BigInteger, "nullable": True},
104
+ "updated_at": {"type": BigInteger, "nullable": True},
105
+ "agent_id": {"type": String, "nullable": True},
106
+ "team_id": {"type": String, "nullable": True},
107
+ }
108
+
96
109
 
97
110
  def get_table_schema_definition(table_type: str) -> dict[str, Any]:
98
111
  """
@@ -110,6 +123,7 @@ def get_table_schema_definition(table_type: str) -> dict[str, Any]:
110
123
  "metrics": METRICS_TABLE_SCHEMA,
111
124
  "memories": USER_MEMORY_TABLE_SCHEMA,
112
125
  "knowledge": KNOWLEDGE_TABLE_SCHEMA,
126
+ "culture": CULTURAL_KNOWLEDGE_TABLE_SCHEMA,
113
127
  }
114
128
  schema = schemas.get(table_type, {})
115
129