agno 2.2.1__py3-none-any.whl → 2.2.3__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 (69) hide show
  1. agno/agent/agent.py +735 -574
  2. agno/culture/manager.py +22 -24
  3. agno/db/async_postgres/__init__.py +1 -1
  4. agno/db/dynamo/dynamo.py +0 -2
  5. agno/db/firestore/firestore.py +0 -2
  6. agno/db/gcs_json/gcs_json_db.py +0 -4
  7. agno/db/gcs_json/utils.py +0 -24
  8. agno/db/in_memory/in_memory_db.py +0 -3
  9. agno/db/json/json_db.py +4 -10
  10. agno/db/json/utils.py +0 -24
  11. agno/db/mongo/__init__.py +15 -1
  12. agno/db/mongo/async_mongo.py +1999 -0
  13. agno/db/mongo/mongo.py +0 -2
  14. agno/db/mysql/mysql.py +0 -3
  15. agno/db/postgres/__init__.py +1 -1
  16. agno/db/{async_postgres → postgres}/async_postgres.py +19 -22
  17. agno/db/postgres/postgres.py +7 -10
  18. agno/db/postgres/utils.py +106 -2
  19. agno/db/redis/redis.py +0 -2
  20. agno/db/singlestore/singlestore.py +0 -3
  21. agno/db/sqlite/__init__.py +2 -1
  22. agno/db/sqlite/async_sqlite.py +2269 -0
  23. agno/db/sqlite/sqlite.py +0 -2
  24. agno/db/sqlite/utils.py +96 -0
  25. agno/db/surrealdb/surrealdb.py +0 -6
  26. agno/knowledge/knowledge.py +3 -3
  27. agno/knowledge/reader/reader_factory.py +16 -0
  28. agno/knowledge/reader/tavily_reader.py +194 -0
  29. agno/memory/manager.py +28 -25
  30. agno/models/anthropic/claude.py +63 -6
  31. agno/models/base.py +251 -32
  32. agno/models/response.py +69 -0
  33. agno/os/router.py +7 -5
  34. agno/os/routers/memory/memory.py +2 -1
  35. agno/os/routers/memory/schemas.py +5 -2
  36. agno/os/schema.py +25 -20
  37. agno/os/utils.py +9 -2
  38. agno/run/agent.py +23 -30
  39. agno/run/base.py +17 -1
  40. agno/run/team.py +23 -29
  41. agno/run/workflow.py +17 -12
  42. agno/session/agent.py +3 -0
  43. agno/session/summary.py +4 -1
  44. agno/session/team.py +1 -1
  45. agno/team/team.py +599 -367
  46. agno/tools/dalle.py +2 -4
  47. agno/tools/eleven_labs.py +23 -25
  48. agno/tools/function.py +40 -0
  49. agno/tools/mcp/__init__.py +10 -0
  50. agno/tools/mcp/mcp.py +324 -0
  51. agno/tools/mcp/multi_mcp.py +347 -0
  52. agno/tools/mcp/params.py +24 -0
  53. agno/tools/slack.py +18 -3
  54. agno/tools/tavily.py +146 -0
  55. agno/utils/agent.py +366 -1
  56. agno/utils/mcp.py +92 -2
  57. agno/utils/media.py +166 -1
  58. agno/utils/print_response/workflow.py +17 -1
  59. agno/utils/team.py +89 -1
  60. agno/workflow/step.py +0 -1
  61. agno/workflow/types.py +10 -15
  62. {agno-2.2.1.dist-info → agno-2.2.3.dist-info}/METADATA +28 -25
  63. {agno-2.2.1.dist-info → agno-2.2.3.dist-info}/RECORD +66 -62
  64. agno/db/async_postgres/schemas.py +0 -139
  65. agno/db/async_postgres/utils.py +0 -347
  66. agno/tools/mcp.py +0 -679
  67. {agno-2.2.1.dist-info → agno-2.2.3.dist-info}/WHEEL +0 -0
  68. {agno-2.2.1.dist-info → agno-2.2.3.dist-info}/licenses/LICENSE +0 -0
  69. {agno-2.2.1.dist-info → agno-2.2.3.dist-info}/top_level.txt +0 -0
agno/db/mongo/mongo.py CHANGED
@@ -291,8 +291,6 @@ class MongoDb(BaseDb):
291
291
  query = {"session_id": session_id}
292
292
  if user_id is not None:
293
293
  query["user_id"] = user_id
294
- if session_type is not None:
295
- query["session_type"] = session_type
296
294
 
297
295
  result = collection.find_one(query)
298
296
  if result is None:
agno/db/mysql/mysql.py CHANGED
@@ -387,9 +387,6 @@ class MySQLDb(BaseDb):
387
387
 
388
388
  if user_id is not None:
389
389
  stmt = stmt.where(table.c.user_id == user_id)
390
- if session_type is not None:
391
- session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
392
- stmt = stmt.where(table.c.session_type == session_type_value)
393
390
  result = sess.execute(stmt).fetchone()
394
391
  if result is None:
395
392
  return None
@@ -1,4 +1,4 @@
1
- from agno.db.async_postgres import AsyncPostgresDb
1
+ from agno.db.postgres.async_postgres import AsyncPostgresDb
2
2
  from agno.db.postgres.postgres import PostgresDb
3
3
 
4
4
  __all__ = ["PostgresDb", "AsyncPostgresDb"]
@@ -3,20 +3,20 @@ from datetime import date, datetime, timedelta, timezone
3
3
  from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
4
4
  from uuid import uuid4
5
5
 
6
- from agno.db.async_postgres.schemas import get_table_schema_definition
7
- from agno.db.async_postgres.utils import (
6
+ from agno.db.base import AsyncBaseDb, SessionType
7
+ from agno.db.postgres.schemas import get_table_schema_definition
8
+ from agno.db.postgres.utils import (
9
+ abulk_upsert_metrics,
10
+ acreate_schema,
11
+ ais_table_available,
12
+ ais_valid_table,
8
13
  apply_sorting,
9
- bulk_upsert_metrics,
10
14
  calculate_date_metrics,
11
- create_schema,
12
15
  deserialize_cultural_knowledge,
13
16
  fetch_all_sessions_data,
14
17
  get_dates_to_calculate_metrics_for,
15
- is_table_available,
16
- is_valid_table,
17
18
  serialize_cultural_knowledge,
18
19
  )
19
- from agno.db.base import AsyncBaseDb, SessionType
20
20
  from agno.db.schemas.culture import CulturalKnowledge
21
21
  from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
22
22
  from agno.db.schemas.knowledge import KnowledgeRow
@@ -148,7 +148,7 @@ class AsyncPostgresDb(AsyncBaseDb):
148
148
  table.append_constraint(Index(idx_name, idx_col))
149
149
 
150
150
  async with self.async_session_factory() as sess, sess.begin():
151
- await create_schema(session=sess, db_schema=db_schema)
151
+ await acreate_schema(session=sess, db_schema=db_schema)
152
152
 
153
153
  # Create table
154
154
  async with self.db_engine.begin() as conn:
@@ -241,12 +241,12 @@ class AsyncPostgresDb(AsyncBaseDb):
241
241
  """
242
242
 
243
243
  async with self.async_session_factory() as sess, sess.begin():
244
- table_is_available = await is_table_available(session=sess, table_name=table_name, db_schema=db_schema)
244
+ table_is_available = await ais_table_available(session=sess, table_name=table_name, db_schema=db_schema)
245
245
 
246
246
  if not table_is_available:
247
247
  return await self._create_table(table_name=table_name, table_type=table_type, db_schema=db_schema)
248
248
 
249
- if not await is_valid_table(
249
+ if not await ais_valid_table(
250
250
  db_engine=self.db_engine,
251
251
  table_name=table_name,
252
252
  table_type=table_type,
@@ -288,7 +288,7 @@ class AsyncPostgresDb(AsyncBaseDb):
288
288
  delete_stmt = table.delete().where(table.c.session_id == session_id)
289
289
  result = await sess.execute(delete_stmt)
290
290
 
291
- if result.rowcount == 0:
291
+ if result.rowcount == 0: # type: ignore
292
292
  log_debug(f"No session found to delete with session_id: {session_id} in table {table.name}")
293
293
  return False
294
294
 
@@ -317,7 +317,7 @@ class AsyncPostgresDb(AsyncBaseDb):
317
317
  delete_stmt = table.delete().where(table.c.session_id.in_(session_ids))
318
318
  result = await sess.execute(delete_stmt)
319
319
 
320
- log_debug(f"Successfully deleted {result.rowcount} sessions")
320
+ log_debug(f"Successfully deleted {result.rowcount} sessions") # type: ignore
321
321
 
322
322
  except Exception as e:
323
323
  log_error(f"Error deleting sessions: {e}")
@@ -354,9 +354,6 @@ class AsyncPostgresDb(AsyncBaseDb):
354
354
 
355
355
  if user_id is not None:
356
356
  stmt = stmt.where(table.c.user_id == user_id)
357
- if session_type is not None:
358
- session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
359
- stmt = stmt.where(table.c.session_type == session_type_value)
360
357
  result = await sess.execute(stmt)
361
358
  row = result.fetchone()
362
359
  if row is None:
@@ -712,7 +709,7 @@ class AsyncPostgresDb(AsyncBaseDb):
712
709
  delete_stmt = table.delete().where(table.c.memory_id == memory_id)
713
710
  result = await sess.execute(delete_stmt)
714
711
 
715
- success = result.rowcount > 0
712
+ success = result.rowcount > 0 # type: ignore
716
713
  if success:
717
714
  log_debug(f"Successfully deleted user memory id: {memory_id}")
718
715
  else:
@@ -737,10 +734,10 @@ class AsyncPostgresDb(AsyncBaseDb):
737
734
  delete_stmt = table.delete().where(table.c.memory_id.in_(memory_ids))
738
735
  result = await sess.execute(delete_stmt)
739
736
 
740
- if result.rowcount == 0:
737
+ if result.rowcount == 0: # type: ignore
741
738
  log_debug(f"No user memories found with ids: {memory_ids}")
742
739
  else:
743
- log_debug(f"Successfully deleted {result.rowcount} user memories")
740
+ log_debug(f"Successfully deleted {result.rowcount} user memories") # type: ignore
744
741
 
745
742
  except Exception as e:
746
743
  log_error(f"Error deleting user memories: {e}")
@@ -1387,7 +1384,7 @@ class AsyncPostgresDb(AsyncBaseDb):
1387
1384
 
1388
1385
  if metrics_records:
1389
1386
  async with self.async_session_factory() as sess, sess.begin():
1390
- results = await bulk_upsert_metrics(session=sess, table=table, metrics_records=metrics_records)
1387
+ results = await abulk_upsert_metrics(session=sess, table=table, metrics_records=metrics_records)
1391
1388
 
1392
1389
  log_debug("Updated metrics calculations")
1393
1390
 
@@ -1649,7 +1646,7 @@ class AsyncPostgresDb(AsyncBaseDb):
1649
1646
  stmt = table.delete().where(table.c.run_id == eval_run_id)
1650
1647
  result = await sess.execute(stmt)
1651
1648
 
1652
- if result.rowcount == 0:
1649
+ if result.rowcount == 0: # type: ignore
1653
1650
  log_warning(f"No eval run found with ID: {eval_run_id}")
1654
1651
  else:
1655
1652
  log_debug(f"Deleted eval run with ID: {eval_run_id}")
@@ -1670,10 +1667,10 @@ class AsyncPostgresDb(AsyncBaseDb):
1670
1667
  stmt = table.delete().where(table.c.run_id.in_(eval_run_ids))
1671
1668
  result = await sess.execute(stmt)
1672
1669
 
1673
- if result.rowcount == 0:
1670
+ if result.rowcount == 0: # type: ignore
1674
1671
  log_warning(f"No eval runs found with IDs: {eval_run_ids}")
1675
1672
  else:
1676
- log_debug(f"Deleted {result.rowcount} eval runs")
1673
+ log_debug(f"Deleted {result.rowcount} eval runs") # type: ignore
1677
1674
 
1678
1675
  except Exception as e:
1679
1676
  log_error(f"Error deleting eval runs {eval_run_ids}: {e}")
@@ -10,12 +10,12 @@ 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
+ deserialize_cultural_knowledge,
14
14
  fetch_all_sessions_data,
15
15
  get_dates_to_calculate_metrics_for,
16
16
  is_table_available,
17
17
  is_valid_table,
18
- serialize_cultural_knowledge_for_db,
18
+ serialize_cultural_knowledge,
19
19
  )
20
20
  from agno.db.schemas.culture import CulturalKnowledge
21
21
  from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
@@ -383,9 +383,6 @@ class PostgresDb(BaseDb):
383
383
 
384
384
  if user_id is not None:
385
385
  stmt = stmt.where(table.c.user_id == user_id)
386
- if session_type is not None:
387
- session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
388
- stmt = stmt.where(table.c.session_type == session_type_value)
389
386
  result = sess.execute(stmt).fetchone()
390
387
  if result is None:
391
388
  return None
@@ -2030,7 +2027,7 @@ class PostgresDb(BaseDb):
2030
2027
  if not db_row or not deserialize:
2031
2028
  return db_row
2032
2029
 
2033
- return deserialize_cultural_knowledge_from_db(db_row)
2030
+ return deserialize_cultural_knowledge(db_row)
2034
2031
 
2035
2032
  except Exception as e:
2036
2033
  log_error(f"Exception reading from cultural knowledge table: {e}")
@@ -2104,7 +2101,7 @@ class PostgresDb(BaseDb):
2104
2101
  if not deserialize:
2105
2102
  return db_rows, total_count
2106
2103
 
2107
- return [deserialize_cultural_knowledge_from_db(row) for row in db_rows]
2104
+ return [deserialize_cultural_knowledge(row) for row in db_rows]
2108
2105
 
2109
2106
  except Exception as e:
2110
2107
  log_error(f"Error reading from cultural knowledge table: {e}")
@@ -2134,7 +2131,7 @@ class PostgresDb(BaseDb):
2134
2131
  cultural_knowledge.id = str(uuid4())
2135
2132
 
2136
2133
  # Serialize content, categories, and notes into a JSON dict for DB storage
2137
- content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
2134
+ content_dict = serialize_cultural_knowledge(cultural_knowledge)
2138
2135
 
2139
2136
  with self.Session() as sess, sess.begin():
2140
2137
  stmt = postgresql.insert(table).values(
@@ -2149,7 +2146,7 @@ class PostgresDb(BaseDb):
2149
2146
  agent_id=cultural_knowledge.agent_id,
2150
2147
  team_id=cultural_knowledge.team_id,
2151
2148
  )
2152
- stmt = stmt.on_conflict_do_update(
2149
+ stmt = stmt.on_conflict_do_update( # type: ignore
2153
2150
  index_elements=["id"],
2154
2151
  set_=dict(
2155
2152
  name=cultural_knowledge.name,
@@ -2173,7 +2170,7 @@ class PostgresDb(BaseDb):
2173
2170
  if not db_row or not deserialize:
2174
2171
  return db_row
2175
2172
 
2176
- return deserialize_cultural_knowledge_from_db(db_row)
2173
+ return deserialize_cultural_knowledge(db_row)
2177
2174
 
2178
2175
  except Exception as e:
2179
2176
  log_error(f"Error upserting cultural knowledge: {e}")
agno/db/postgres/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 sqlalchemy import Engine
9
+ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
9
10
 
10
11
  from agno.db.postgres.schemas import get_table_schema_definition
11
12
  from agno.db.schemas.culture import CulturalKnowledge
@@ -63,6 +64,20 @@ def create_schema(session: Session, db_schema: str) -> None:
63
64
  log_warning(f"Could not create schema {db_schema}: {e}")
64
65
 
65
66
 
67
+ async def acreate_schema(session: AsyncSession, db_schema: str) -> None:
68
+ """Create the database schema if it doesn't exist.
69
+
70
+ Args:
71
+ session: The SQLAlchemy session to use
72
+ db_schema (str): The definition of the database schema to create
73
+ """
74
+ try:
75
+ log_debug(f"Creating schema if not exists: {db_schema}")
76
+ await session.execute(text(f"CREATE SCHEMA IF NOT EXISTS {db_schema};"))
77
+ except Exception as e:
78
+ log_warning(f"Could not create schema {db_schema}: {e}")
79
+
80
+
66
81
  def is_table_available(session: Session, table_name: str, db_schema: str) -> bool:
67
82
  """
68
83
  Check if a table with the given name exists in the given schema.
@@ -82,6 +97,24 @@ def is_table_available(session: Session, table_name: str, db_schema: str) -> boo
82
97
  return False
83
98
 
84
99
 
100
+ async def ais_table_available(session: AsyncSession, table_name: str, db_schema: str) -> bool:
101
+ """
102
+ Check if a table with the given name exists in the given schema.
103
+
104
+ Returns:
105
+ bool: True if the table exists, False otherwise.
106
+ """
107
+ try:
108
+ exists_query = text(
109
+ "SELECT 1 FROM information_schema.tables WHERE table_schema = :schema AND table_name = :table"
110
+ )
111
+ exists = (await session.execute(exists_query, {"schema": db_schema, "table": table_name})).scalar() is not None
112
+ return exists
113
+ except Exception as e:
114
+ log_error(f"Error checking if table exists: {e}")
115
+ return False
116
+
117
+
85
118
  def is_valid_table(db_engine: Engine, table_name: str, table_type: str, db_schema: str) -> bool:
86
119
  """
87
120
  Check if the existing table has the expected column names.
@@ -114,6 +147,44 @@ def is_valid_table(db_engine: Engine, table_name: str, table_type: str, db_schem
114
147
  return False
115
148
 
116
149
 
150
+ async def ais_valid_table(db_engine: AsyncEngine, table_name: str, table_type: str, db_schema: str) -> bool:
151
+ """
152
+ Check if the existing table has the expected column names.
153
+
154
+ Args:
155
+ table_name (str): Name of the table to validate
156
+ schema (str): Database schema name
157
+
158
+ Returns:
159
+ bool: True if table has all expected columns, False otherwise
160
+ """
161
+ try:
162
+ expected_table_schema = get_table_schema_definition(table_type)
163
+ expected_columns = {col_name for col_name in expected_table_schema.keys() if not col_name.startswith("_")}
164
+
165
+ # Get existing columns from the async engine
166
+ async with db_engine.connect() as conn:
167
+ existing_columns = await conn.run_sync(_get_table_columns, table_name, db_schema)
168
+
169
+ # Check if all expected columns exist
170
+ missing_columns = expected_columns - existing_columns
171
+ if missing_columns:
172
+ log_warning(f"Missing columns {missing_columns} in table {db_schema}.{table_name}")
173
+ return False
174
+
175
+ return True
176
+ except Exception as e:
177
+ log_error(f"Error validating table schema for {db_schema}.{table_name}: {e}")
178
+ return False
179
+
180
+
181
+ def _get_table_columns(conn, table_name: str, db_schema: str) -> set[str]:
182
+ """Helper function to get table columns using sync inspector."""
183
+ inspector = inspect(conn)
184
+ columns_info = inspector.get_columns(table_name, schema=db_schema)
185
+ return {col["name"] for col in columns_info}
186
+
187
+
117
188
  # -- Metrics util methods --
118
189
  def bulk_upsert_metrics(session: Session, table: Table, metrics_records: list[dict]) -> list[dict]:
119
190
  """Bulk upsert metrics into the database.
@@ -148,6 +219,39 @@ def bulk_upsert_metrics(session: Session, table: Table, metrics_records: list[di
148
219
  return results # type: ignore
149
220
 
150
221
 
222
+ async def abulk_upsert_metrics(session: AsyncSession, table: Table, metrics_records: list[dict]) -> list[dict]:
223
+ """Bulk upsert metrics into the database.
224
+
225
+ Args:
226
+ table (Table): The table to upsert into.
227
+ metrics_records (list[dict]): The metrics records to upsert.
228
+
229
+ Returns:
230
+ list[dict]: The upserted metrics records.
231
+ """
232
+ if not metrics_records:
233
+ return []
234
+
235
+ results = []
236
+ stmt = postgresql.insert(table)
237
+
238
+ # Columns to update in case of conflict
239
+ update_columns = {
240
+ col.name: stmt.excluded[col.name]
241
+ for col in table.columns
242
+ if col.name not in ["id", "date", "created_at", "aggregation_period"]
243
+ }
244
+
245
+ stmt = stmt.on_conflict_do_update(index_elements=["date", "aggregation_period"], set_=update_columns).returning( # type: ignore
246
+ table
247
+ )
248
+ result = await session.execute(stmt, metrics_records)
249
+ results = [row._mapping for row in result.fetchall()]
250
+ await session.commit()
251
+
252
+ return results # type: ignore
253
+
254
+
151
255
  def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
152
256
  """Calculate metrics for the given single date.
153
257
 
@@ -282,7 +386,7 @@ def get_dates_to_calculate_metrics_for(starting_date: date) -> list[date]:
282
386
 
283
387
 
284
388
  # -- Cultural Knowledge util methods --
285
- def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
389
+ def serialize_cultural_knowledge(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
286
390
  """Serialize a CulturalKnowledge object for database storage.
287
391
 
288
392
  Converts the model's separate content, categories, and notes fields
@@ -305,7 +409,7 @@ def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -
305
409
  return content_dict if content_dict else {}
306
410
 
307
411
 
308
- def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
412
+ def deserialize_cultural_knowledge(db_row: Dict[str, Any]) -> CulturalKnowledge:
309
413
  """Deserialize a database row to a CulturalKnowledge object.
310
414
 
311
415
  The database stores content as a JSON dict containing content, categories, and notes.
agno/db/redis/redis.py CHANGED
@@ -327,8 +327,6 @@ class RedisDb(BaseDb):
327
327
  # Apply filters
328
328
  if user_id is not None and session.get("user_id") != user_id:
329
329
  return None
330
- if session_type is not None and session.get("session_type") != session_type:
331
- return None
332
330
 
333
331
  if not deserialize:
334
332
  return session
@@ -469,9 +469,6 @@ class SingleStoreDb(BaseDb):
469
469
 
470
470
  if user_id is not None:
471
471
  stmt = stmt.where(table.c.user_id == user_id)
472
- if session_type is not None:
473
- session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
474
- stmt = stmt.where(table.c.session_type == session_type_value)
475
472
  result = sess.execute(stmt).fetchone()
476
473
  if result is None:
477
474
  return None
@@ -1,3 +1,4 @@
1
+ from agno.db.sqlite.async_sqlite import AsyncSqliteDb
1
2
  from agno.db.sqlite.sqlite import SqliteDb
2
3
 
3
- __all__ = ["SqliteDb"]
4
+ __all__ = ["SqliteDb", "AsyncSqliteDb"]