agno 2.2.13__py3-none-any.whl → 2.3.1__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.
- agno/agent/agent.py +197 -110
- agno/api/api.py +2 -0
- agno/db/base.py +26 -0
- agno/db/dynamo/dynamo.py +8 -0
- agno/db/dynamo/schemas.py +1 -0
- agno/db/firestore/firestore.py +8 -0
- agno/db/firestore/schemas.py +1 -0
- agno/db/gcs_json/gcs_json_db.py +8 -0
- agno/db/in_memory/in_memory_db.py +8 -1
- agno/db/json/json_db.py +8 -0
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/async_mongo.py +16 -6
- agno/db/mongo/mongo.py +11 -0
- agno/db/mongo/schemas.py +3 -0
- agno/db/mongo/utils.py +17 -0
- agno/db/mysql/mysql.py +76 -3
- agno/db/mysql/schemas.py +20 -10
- agno/db/postgres/async_postgres.py +99 -25
- agno/db/postgres/postgres.py +75 -6
- agno/db/postgres/schemas.py +30 -20
- agno/db/redis/redis.py +15 -2
- agno/db/redis/schemas.py +4 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +11 -0
- agno/db/singlestore/singlestore.py +79 -5
- agno/db/sqlite/async_sqlite.py +97 -19
- agno/db/sqlite/schemas.py +10 -0
- agno/db/sqlite/sqlite.py +79 -2
- agno/db/surrealdb/surrealdb.py +8 -0
- agno/knowledge/chunking/semantic.py +7 -2
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/knowledge.py +57 -86
- agno/knowledge/reader/csv_reader.py +7 -9
- agno/knowledge/reader/docx_reader.py +5 -5
- agno/knowledge/reader/field_labeled_csv_reader.py +16 -18
- agno/knowledge/reader/json_reader.py +5 -4
- agno/knowledge/reader/markdown_reader.py +8 -8
- agno/knowledge/reader/pdf_reader.py +11 -11
- agno/knowledge/reader/pptx_reader.py +5 -5
- agno/knowledge/reader/s3_reader.py +3 -3
- agno/knowledge/reader/text_reader.py +8 -8
- agno/knowledge/reader/web_search_reader.py +1 -48
- agno/knowledge/reader/website_reader.py +10 -10
- agno/models/anthropic/claude.py +319 -28
- agno/models/aws/claude.py +32 -0
- agno/models/azure/openai_chat.py +19 -10
- agno/models/base.py +612 -545
- agno/models/cerebras/cerebras.py +8 -11
- agno/models/cohere/chat.py +27 -1
- agno/models/google/gemini.py +39 -7
- agno/models/groq/groq.py +25 -11
- agno/models/meta/llama.py +20 -9
- agno/models/meta/llama_openai.py +3 -19
- agno/models/nebius/nebius.py +4 -4
- agno/models/openai/chat.py +30 -14
- agno/models/openai/responses.py +10 -13
- agno/models/response.py +1 -0
- agno/models/vertexai/claude.py +26 -0
- agno/os/app.py +8 -19
- agno/os/router.py +54 -0
- agno/os/routers/knowledge/knowledge.py +2 -2
- agno/os/schema.py +2 -2
- agno/session/agent.py +57 -92
- agno/session/summary.py +1 -1
- agno/session/team.py +62 -112
- agno/session/workflow.py +353 -57
- agno/team/team.py +227 -125
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/http.py +111 -0
- agno/utils/media.py +11 -0
- agno/utils/models/claude.py +8 -0
- agno/utils/print_response/agent.py +33 -12
- agno/utils/print_response/team.py +22 -12
- agno/vectordb/couchbase/couchbase.py +6 -2
- agno/workflow/condition.py +13 -0
- agno/workflow/loop.py +13 -0
- agno/workflow/parallel.py +13 -0
- agno/workflow/router.py +13 -0
- agno/workflow/step.py +120 -20
- agno/workflow/steps.py +13 -0
- agno/workflow/workflow.py +76 -63
- {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/METADATA +6 -2
- {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/RECORD +91 -88
- agno/tools/googlesearch.py +0 -98
- {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/WHEEL +0 -0
- {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/top_level.txt +0 -0
agno/db/mongo/async_mongo.py
CHANGED
|
@@ -9,7 +9,7 @@ from agno.db.mongo.utils import (
|
|
|
9
9
|
apply_sorting,
|
|
10
10
|
bulk_upsert_metrics,
|
|
11
11
|
calculate_date_metrics,
|
|
12
|
-
|
|
12
|
+
create_collection_indexes_async,
|
|
13
13
|
deserialize_cultural_knowledge_from_db,
|
|
14
14
|
fetch_all_sessions_data,
|
|
15
15
|
get_dates_to_calculate_metrics_for,
|
|
@@ -167,9 +167,9 @@ class AsyncMongoDb(AsyncBaseDb):
|
|
|
167
167
|
|
|
168
168
|
self._event_loop = current_loop
|
|
169
169
|
self._database = None # Reset database reference
|
|
170
|
-
# Clear collection caches when switching event loops
|
|
170
|
+
# Clear collection caches and initialization flags when switching event loops
|
|
171
171
|
for attr in list(vars(self).keys()):
|
|
172
|
-
if attr.endswith("_collection"):
|
|
172
|
+
if attr.endswith("_collection") or attr.endswith("_initialized"):
|
|
173
173
|
delattr(self, attr)
|
|
174
174
|
|
|
175
175
|
return self._client # type: ignore
|
|
@@ -307,9 +307,8 @@ class AsyncMongoDb(AsyncBaseDb):
|
|
|
307
307
|
if not hasattr(self, f"_{collection_name}_initialized"):
|
|
308
308
|
if not create_collection_if_not_found:
|
|
309
309
|
return None
|
|
310
|
-
#
|
|
311
|
-
|
|
312
|
-
create_collection_indexes(collection, collection_type) # type: ignore
|
|
310
|
+
# Create indexes asynchronously for Motor collections
|
|
311
|
+
await create_collection_indexes_async(collection, collection_type)
|
|
313
312
|
setattr(self, f"_{collection_name}_initialized", True)
|
|
314
313
|
log_debug(f"Initialized collection '{collection_name}'")
|
|
315
314
|
else:
|
|
@@ -321,6 +320,14 @@ class AsyncMongoDb(AsyncBaseDb):
|
|
|
321
320
|
log_error(f"Error getting collection {collection_name}: {e}")
|
|
322
321
|
raise
|
|
323
322
|
|
|
323
|
+
def get_latest_schema_version(self):
|
|
324
|
+
"""Get the latest version of the database schema."""
|
|
325
|
+
pass
|
|
326
|
+
|
|
327
|
+
def upsert_schema_version(self, version: str) -> None:
|
|
328
|
+
"""Upsert the schema version into the database."""
|
|
329
|
+
pass
|
|
330
|
+
|
|
324
331
|
# -- Session methods --
|
|
325
332
|
|
|
326
333
|
async def delete_session(self, session_id: str) -> bool:
|
|
@@ -1241,6 +1248,9 @@ class AsyncMongoDb(AsyncBaseDb):
|
|
|
1241
1248
|
"memory_id": memory.memory_id,
|
|
1242
1249
|
"memory": memory.memory,
|
|
1243
1250
|
"topics": memory.topics,
|
|
1251
|
+
"input": memory.input,
|
|
1252
|
+
"feedback": memory.feedback,
|
|
1253
|
+
"created_at": memory.created_at,
|
|
1244
1254
|
"updated_at": updated_at,
|
|
1245
1255
|
}
|
|
1246
1256
|
|
agno/db/mongo/mongo.py
CHANGED
|
@@ -236,6 +236,14 @@ class MongoDb(BaseDb):
|
|
|
236
236
|
log_error(f"Error getting collection {collection_name}: {e}")
|
|
237
237
|
raise
|
|
238
238
|
|
|
239
|
+
def get_latest_schema_version(self):
|
|
240
|
+
"""Get the latest version of the database schema."""
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
def upsert_schema_version(self, version: str) -> None:
|
|
244
|
+
"""Upsert the schema version into the database."""
|
|
245
|
+
pass
|
|
246
|
+
|
|
239
247
|
# -- Session methods --
|
|
240
248
|
|
|
241
249
|
def delete_session(self, session_id: str) -> bool:
|
|
@@ -1140,7 +1148,10 @@ class MongoDb(BaseDb):
|
|
|
1140
1148
|
"team_id": memory.team_id,
|
|
1141
1149
|
"memory_id": memory.memory_id,
|
|
1142
1150
|
"memory": memory.memory,
|
|
1151
|
+
"input": memory.input,
|
|
1152
|
+
"feedback": memory.feedback,
|
|
1143
1153
|
"topics": memory.topics,
|
|
1154
|
+
"created_at": memory.created_at,
|
|
1144
1155
|
"updated_at": updated_at,
|
|
1145
1156
|
}
|
|
1146
1157
|
|
agno/db/mongo/schemas.py
CHANGED
agno/db/mongo/utils.py
CHANGED
|
@@ -34,6 +34,23 @@ def create_collection_indexes(collection: Collection, collection_type: str) -> N
|
|
|
34
34
|
log_warning(f"Error creating indexes for {collection_type} collection: {e}")
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
async def create_collection_indexes_async(collection: Any, collection_type: str) -> None:
|
|
38
|
+
"""Create all required indexes for a collection (async version for Motor)"""
|
|
39
|
+
try:
|
|
40
|
+
indexes = get_collection_indexes(collection_type)
|
|
41
|
+
for index_spec in indexes:
|
|
42
|
+
key = index_spec["key"]
|
|
43
|
+
unique = index_spec.get("unique", False)
|
|
44
|
+
|
|
45
|
+
if isinstance(key, list):
|
|
46
|
+
await collection.create_index(key, unique=unique)
|
|
47
|
+
else:
|
|
48
|
+
await collection.create_index([(key, 1)], unique=unique)
|
|
49
|
+
|
|
50
|
+
except Exception as e:
|
|
51
|
+
log_warning(f"Error creating indexes for {collection_type} collection: {e}")
|
|
52
|
+
|
|
53
|
+
|
|
37
54
|
def apply_sorting(
|
|
38
55
|
query_args: Dict[str, Any], sort_by: Optional[str] = None, sort_order: Optional[str] = None
|
|
39
56
|
) -> List[tuple]:
|
agno/db/mysql/mysql.py
CHANGED
|
@@ -6,6 +6,7 @@ from uuid import uuid4
|
|
|
6
6
|
from sqlalchemy import Index, UniqueConstraint
|
|
7
7
|
|
|
8
8
|
from agno.db.base import BaseDb, SessionType
|
|
9
|
+
from agno.db.migrations.manager import MigrationManager
|
|
9
10
|
from agno.db.mysql.schemas import get_table_schema_definition
|
|
10
11
|
from agno.db.mysql.utils import (
|
|
11
12
|
apply_sorting,
|
|
@@ -50,6 +51,7 @@ class MySQLDb(BaseDb):
|
|
|
50
51
|
metrics_table: Optional[str] = None,
|
|
51
52
|
eval_table: Optional[str] = None,
|
|
52
53
|
knowledge_table: Optional[str] = None,
|
|
54
|
+
versions_table: Optional[str] = None,
|
|
53
55
|
id: Optional[str] = None,
|
|
54
56
|
):
|
|
55
57
|
"""
|
|
@@ -70,6 +72,7 @@ class MySQLDb(BaseDb):
|
|
|
70
72
|
metrics_table (Optional[str]): Name of the table to store metrics.
|
|
71
73
|
eval_table (Optional[str]): Name of the table to store evaluation runs data.
|
|
72
74
|
knowledge_table (Optional[str]): Name of the table to store knowledge content.
|
|
75
|
+
versions_table (Optional[str]): Name of the table to store schema versions.
|
|
73
76
|
id (Optional[str]): ID of the database.
|
|
74
77
|
|
|
75
78
|
Raises:
|
|
@@ -90,6 +93,7 @@ class MySQLDb(BaseDb):
|
|
|
90
93
|
metrics_table=metrics_table,
|
|
91
94
|
eval_table=eval_table,
|
|
92
95
|
knowledge_table=knowledge_table,
|
|
96
|
+
versions_table=versions_table,
|
|
93
97
|
)
|
|
94
98
|
|
|
95
99
|
_engine: Optional[Engine] = db_engine
|
|
@@ -218,9 +222,15 @@ class MySQLDb(BaseDb):
|
|
|
218
222
|
(self.metrics_table_name, "metrics"),
|
|
219
223
|
(self.eval_table_name, "evals"),
|
|
220
224
|
(self.knowledge_table_name, "knowledge"),
|
|
225
|
+
(self.versions_table_name, "versions"),
|
|
221
226
|
]
|
|
222
227
|
|
|
223
228
|
for table_name, table_type in tables_to_create:
|
|
229
|
+
if table_name != self.versions_table_name:
|
|
230
|
+
# Also store the schema version for the created table
|
|
231
|
+
latest_schema_version = MigrationManager(self).latest_schema_version
|
|
232
|
+
self.upsert_schema_version(table_name=table_name, version=latest_schema_version.public)
|
|
233
|
+
|
|
224
234
|
self._create_table(table_name=table_name, table_type=table_type, db_schema=self.db_schema)
|
|
225
235
|
|
|
226
236
|
def _get_table(self, table_type: str, create_table_if_not_found: Optional[bool] = False) -> Optional[Table]:
|
|
@@ -278,6 +288,15 @@ class MySQLDb(BaseDb):
|
|
|
278
288
|
)
|
|
279
289
|
return self.culture_table
|
|
280
290
|
|
|
291
|
+
if table_type == "versions":
|
|
292
|
+
self.versions_table = self._get_or_create_table(
|
|
293
|
+
table_name=self.versions_table_name,
|
|
294
|
+
table_type="versions",
|
|
295
|
+
db_schema=self.db_schema,
|
|
296
|
+
create_table_if_not_found=create_table_if_not_found,
|
|
297
|
+
)
|
|
298
|
+
return self.versions_table
|
|
299
|
+
|
|
281
300
|
raise ValueError(f"Unknown table type: {table_type}")
|
|
282
301
|
|
|
283
302
|
def _get_or_create_table(
|
|
@@ -302,7 +321,14 @@ class MySQLDb(BaseDb):
|
|
|
302
321
|
if not create_table_if_not_found:
|
|
303
322
|
return None
|
|
304
323
|
|
|
305
|
-
|
|
324
|
+
created_table = self._create_table(table_name=table_name, table_type=table_type, db_schema=db_schema)
|
|
325
|
+
|
|
326
|
+
if table_name != self.versions_table_name:
|
|
327
|
+
# Also store the schema version for the created table
|
|
328
|
+
latest_schema_version = MigrationManager(self).latest_schema_version
|
|
329
|
+
self.upsert_schema_version(table_name=table_name, version=latest_schema_version.public)
|
|
330
|
+
|
|
331
|
+
return created_table
|
|
306
332
|
|
|
307
333
|
if not is_valid_table(
|
|
308
334
|
db_engine=self.db_engine,
|
|
@@ -321,6 +347,39 @@ class MySQLDb(BaseDb):
|
|
|
321
347
|
log_error(f"Error loading existing table {db_schema}.{table_name}: {e}")
|
|
322
348
|
raise
|
|
323
349
|
|
|
350
|
+
def get_latest_schema_version(self, table_name: str) -> str:
|
|
351
|
+
"""Get the latest version of the database schema."""
|
|
352
|
+
table = self._get_table(table_type="versions", create_table_if_not_found=True)
|
|
353
|
+
with self.Session() as sess:
|
|
354
|
+
# Latest version for the given table
|
|
355
|
+
stmt = select(table).where(table.c.table_name == table_name).order_by(table.c.version.desc()).limit(1) # type: ignore
|
|
356
|
+
result = sess.execute(stmt).fetchone()
|
|
357
|
+
if result is None:
|
|
358
|
+
return "2.0.0"
|
|
359
|
+
version_dict = dict(result._mapping)
|
|
360
|
+
return version_dict.get("version") or "2.0.0"
|
|
361
|
+
|
|
362
|
+
def upsert_schema_version(self, table_name: str, version: str) -> None:
|
|
363
|
+
"""Upsert the schema version into the database."""
|
|
364
|
+
table = self._get_table(table_type="versions", create_table_if_not_found=True)
|
|
365
|
+
if table is None:
|
|
366
|
+
return
|
|
367
|
+
current_datetime = datetime.now().isoformat()
|
|
368
|
+
with self.Session() as sess, sess.begin():
|
|
369
|
+
stmt = mysql.insert(table).values( # type: ignore
|
|
370
|
+
table_name=table_name,
|
|
371
|
+
version=version,
|
|
372
|
+
created_at=current_datetime, # Store as ISO format string
|
|
373
|
+
updated_at=current_datetime,
|
|
374
|
+
)
|
|
375
|
+
# Update version if table_name already exists
|
|
376
|
+
stmt = stmt.on_duplicate_key_update(
|
|
377
|
+
version=version,
|
|
378
|
+
created_at=current_datetime,
|
|
379
|
+
updated_at=current_datetime,
|
|
380
|
+
)
|
|
381
|
+
sess.execute(stmt)
|
|
382
|
+
|
|
324
383
|
# -- Session methods --
|
|
325
384
|
def delete_session(self, session_id: str) -> bool:
|
|
326
385
|
"""
|
|
@@ -1287,6 +1346,8 @@ class MySQLDb(BaseDb):
|
|
|
1287
1346
|
if memory.memory_id is None:
|
|
1288
1347
|
memory.memory_id = str(uuid4())
|
|
1289
1348
|
|
|
1349
|
+
current_time = int(time.time())
|
|
1350
|
+
|
|
1290
1351
|
stmt = mysql.insert(table).values(
|
|
1291
1352
|
memory_id=memory.memory_id,
|
|
1292
1353
|
memory=memory.memory,
|
|
@@ -1295,7 +1356,9 @@ class MySQLDb(BaseDb):
|
|
|
1295
1356
|
agent_id=memory.agent_id,
|
|
1296
1357
|
team_id=memory.team_id,
|
|
1297
1358
|
topics=memory.topics,
|
|
1298
|
-
|
|
1359
|
+
feedback=memory.feedback,
|
|
1360
|
+
created_at=memory.created_at,
|
|
1361
|
+
updated_at=memory.created_at,
|
|
1299
1362
|
)
|
|
1300
1363
|
stmt = stmt.on_duplicate_key_update(
|
|
1301
1364
|
memory=memory.memory,
|
|
@@ -1303,7 +1366,10 @@ class MySQLDb(BaseDb):
|
|
|
1303
1366
|
input=memory.input,
|
|
1304
1367
|
agent_id=memory.agent_id,
|
|
1305
1368
|
team_id=memory.team_id,
|
|
1306
|
-
|
|
1369
|
+
feedback=memory.feedback,
|
|
1370
|
+
updated_at=current_time,
|
|
1371
|
+
# Preserve created_at on update - don't overwrite existing value
|
|
1372
|
+
created_at=table.c.created_at,
|
|
1307
1373
|
)
|
|
1308
1374
|
sess.execute(stmt)
|
|
1309
1375
|
|
|
@@ -1358,12 +1424,14 @@ class MySQLDb(BaseDb):
|
|
|
1358
1424
|
# Prepare bulk data
|
|
1359
1425
|
bulk_data = []
|
|
1360
1426
|
current_time = int(time.time())
|
|
1427
|
+
|
|
1361
1428
|
for memory in memories:
|
|
1362
1429
|
if memory.memory_id is None:
|
|
1363
1430
|
memory.memory_id = str(uuid4())
|
|
1364
1431
|
|
|
1365
1432
|
# Use preserved updated_at if flag is set and value exists, otherwise use current time
|
|
1366
1433
|
updated_at = memory.updated_at if preserve_updated_at else current_time
|
|
1434
|
+
|
|
1367
1435
|
bulk_data.append(
|
|
1368
1436
|
{
|
|
1369
1437
|
"memory_id": memory.memory_id,
|
|
@@ -1373,6 +1441,8 @@ class MySQLDb(BaseDb):
|
|
|
1373
1441
|
"agent_id": memory.agent_id,
|
|
1374
1442
|
"team_id": memory.team_id,
|
|
1375
1443
|
"topics": memory.topics,
|
|
1444
|
+
"feedback": memory.feedback,
|
|
1445
|
+
"created_at": memory.created_at,
|
|
1376
1446
|
"updated_at": updated_at,
|
|
1377
1447
|
}
|
|
1378
1448
|
)
|
|
@@ -1388,7 +1458,10 @@ class MySQLDb(BaseDb):
|
|
|
1388
1458
|
input=stmt.inserted.input,
|
|
1389
1459
|
agent_id=stmt.inserted.agent_id,
|
|
1390
1460
|
team_id=stmt.inserted.team_id,
|
|
1461
|
+
feedback=stmt.inserted.feedback,
|
|
1391
1462
|
updated_at=stmt.inserted.updated_at,
|
|
1463
|
+
# Preserve created_at on update
|
|
1464
|
+
created_at=table.c.created_at,
|
|
1392
1465
|
)
|
|
1393
1466
|
sess.execute(stmt, bulk_data)
|
|
1394
1467
|
|
agno/db/mysql/schemas.py
CHANGED
|
@@ -39,6 +39,8 @@ USER_MEMORY_TABLE_SCHEMA = {
|
|
|
39
39
|
"team_id": {"type": lambda: String(128), "nullable": True},
|
|
40
40
|
"user_id": {"type": lambda: String(128), "nullable": True, "index": True},
|
|
41
41
|
"topics": {"type": JSON, "nullable": True},
|
|
42
|
+
"feedback": {"type": Text, "nullable": True},
|
|
43
|
+
"created_at": {"type": BigInteger, "nullable": False, "index": True},
|
|
42
44
|
"updated_at": {"type": BigInteger, "nullable": True, "index": True},
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -76,20 +78,20 @@ KNOWLEDGE_TABLE_SCHEMA = {
|
|
|
76
78
|
|
|
77
79
|
METRICS_TABLE_SCHEMA = {
|
|
78
80
|
"id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
|
|
79
|
-
"agent_runs_count": {"type": BigInteger, "nullable": False},
|
|
80
|
-
"team_runs_count": {"type": BigInteger, "nullable": False},
|
|
81
|
-
"workflow_runs_count": {"type": BigInteger, "nullable": False},
|
|
82
|
-
"agent_sessions_count": {"type": BigInteger, "nullable": False},
|
|
83
|
-
"team_sessions_count": {"type": BigInteger, "nullable": False},
|
|
84
|
-
"workflow_sessions_count": {"type": BigInteger, "nullable": False},
|
|
85
|
-
"users_count": {"type": BigInteger, "nullable": False},
|
|
86
|
-
"token_metrics": {"type": JSON, "nullable": False},
|
|
87
|
-
"model_metrics": {"type": JSON, "nullable": False},
|
|
81
|
+
"agent_runs_count": {"type": BigInteger, "nullable": False, "default": 0},
|
|
82
|
+
"team_runs_count": {"type": BigInteger, "nullable": False, "default": 0},
|
|
83
|
+
"workflow_runs_count": {"type": BigInteger, "nullable": False, "default": 0},
|
|
84
|
+
"agent_sessions_count": {"type": BigInteger, "nullable": False, "default": 0},
|
|
85
|
+
"team_sessions_count": {"type": BigInteger, "nullable": False, "default": 0},
|
|
86
|
+
"workflow_sessions_count": {"type": BigInteger, "nullable": False, "default": 0},
|
|
87
|
+
"users_count": {"type": BigInteger, "nullable": False, "default": 0},
|
|
88
|
+
"token_metrics": {"type": JSON, "nullable": False, "default": {}},
|
|
89
|
+
"model_metrics": {"type": JSON, "nullable": False, "default": {}},
|
|
88
90
|
"date": {"type": Date, "nullable": False, "index": True},
|
|
89
91
|
"aggregation_period": {"type": lambda: String(20), "nullable": False},
|
|
90
92
|
"created_at": {"type": BigInteger, "nullable": False},
|
|
91
93
|
"updated_at": {"type": BigInteger, "nullable": True},
|
|
92
|
-
"completed": {"type": Boolean, "nullable": False},
|
|
94
|
+
"completed": {"type": Boolean, "nullable": False, "default": False},
|
|
93
95
|
"_unique_constraints": [
|
|
94
96
|
{
|
|
95
97
|
"name": "uq_metrics_date_period",
|
|
@@ -111,6 +113,13 @@ CULTURAL_KNOWLEDGE_TABLE_SCHEMA = {
|
|
|
111
113
|
"team_id": {"type": lambda: String(128), "nullable": True},
|
|
112
114
|
}
|
|
113
115
|
|
|
116
|
+
VERSIONS_TABLE_SCHEMA = {
|
|
117
|
+
"table_name": {"type": lambda: String(128), "nullable": False, "primary_key": True},
|
|
118
|
+
"version": {"type": lambda: String(10), "nullable": False},
|
|
119
|
+
"created_at": {"type": lambda: String(128), "nullable": False, "index": True},
|
|
120
|
+
"updated_at": {"type": lambda: String(128), "nullable": True},
|
|
121
|
+
}
|
|
122
|
+
|
|
114
123
|
|
|
115
124
|
def get_table_schema_definition(table_type: str) -> dict[str, Any]:
|
|
116
125
|
"""
|
|
@@ -129,6 +138,7 @@ def get_table_schema_definition(table_type: str) -> dict[str, Any]:
|
|
|
129
138
|
"memories": USER_MEMORY_TABLE_SCHEMA,
|
|
130
139
|
"knowledge": KNOWLEDGE_TABLE_SCHEMA,
|
|
131
140
|
"culture": CULTURAL_KNOWLEDGE_TABLE_SCHEMA,
|
|
141
|
+
"versions": VERSIONS_TABLE_SCHEMA,
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
schema = schemas.get(table_type, {})
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import time
|
|
2
|
+
import warnings
|
|
2
3
|
from datetime import date, datetime, timedelta, timezone
|
|
3
4
|
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
|
|
4
5
|
from uuid import uuid4
|
|
5
6
|
|
|
6
7
|
from agno.db.base import AsyncBaseDb, SessionType
|
|
8
|
+
from agno.db.migrations.manager import MigrationManager
|
|
7
9
|
from agno.db.postgres.schemas import get_table_schema_definition
|
|
8
10
|
from agno.db.postgres.utils import (
|
|
9
11
|
abulk_upsert_metrics,
|
|
@@ -47,6 +49,7 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
47
49
|
eval_table: Optional[str] = None,
|
|
48
50
|
knowledge_table: Optional[str] = None,
|
|
49
51
|
culture_table: Optional[str] = None,
|
|
52
|
+
versions_table: Optional[str] = None,
|
|
50
53
|
db_id: Optional[str] = None, # Deprecated, use id instead.
|
|
51
54
|
):
|
|
52
55
|
"""
|
|
@@ -68,6 +71,7 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
68
71
|
eval_table (Optional[str]): Name of the table to store evaluation runs data.
|
|
69
72
|
knowledge_table (Optional[str]): Name of the table to store knowledge content.
|
|
70
73
|
culture_table (Optional[str]): Name of the table to store cultural knowledge.
|
|
74
|
+
versions_table (Optional[str]): Name of the table to store schema versions.
|
|
71
75
|
db_id: Deprecated, use id instead.
|
|
72
76
|
|
|
73
77
|
Raises:
|
|
@@ -75,7 +79,11 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
75
79
|
ValueError: If none of the tables are provided.
|
|
76
80
|
"""
|
|
77
81
|
if db_id is not None:
|
|
78
|
-
|
|
82
|
+
warnings.warn(
|
|
83
|
+
"The 'db_id' parameter is deprecated and will be removed in future versions. Use 'id' instead.",
|
|
84
|
+
DeprecationWarning,
|
|
85
|
+
stacklevel=2,
|
|
86
|
+
)
|
|
79
87
|
|
|
80
88
|
super().__init__(
|
|
81
89
|
id=id or db_id,
|
|
@@ -85,6 +93,7 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
85
93
|
eval_table=eval_table,
|
|
86
94
|
knowledge_table=knowledge_table,
|
|
87
95
|
culture_table=culture_table,
|
|
96
|
+
versions_table=versions_table,
|
|
88
97
|
)
|
|
89
98
|
|
|
90
99
|
_engine: Optional[AsyncEngine] = db_engine
|
|
@@ -122,9 +131,14 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
122
131
|
(self.metrics_table_name, "metrics"),
|
|
123
132
|
(self.eval_table_name, "evals"),
|
|
124
133
|
(self.knowledge_table_name, "knowledge"),
|
|
134
|
+
(self.versions_table_name, "versions"),
|
|
125
135
|
]
|
|
126
136
|
|
|
127
137
|
for table_name, table_type in tables_to_create:
|
|
138
|
+
# Also store the schema version for the created table
|
|
139
|
+
latest_schema_version = MigrationManager(self).latest_schema_version
|
|
140
|
+
await self.upsert_schema_version(table_name=table_name, version=latest_schema_version.public)
|
|
141
|
+
|
|
128
142
|
await self._create_table(table_name=table_name, table_type=table_type, db_schema=self.db_schema)
|
|
129
143
|
|
|
130
144
|
async def _create_table(self, table_name: str, table_type: str, db_schema: str) -> Table:
|
|
@@ -255,6 +269,13 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
255
269
|
)
|
|
256
270
|
return self.culture_table
|
|
257
271
|
|
|
272
|
+
if table_type == "versions":
|
|
273
|
+
if not hasattr(self, "versions_table"):
|
|
274
|
+
self.versions_table = await self._get_or_create_table(
|
|
275
|
+
table_name=self.versions_table_name, table_type="versions", db_schema=self.db_schema
|
|
276
|
+
)
|
|
277
|
+
return self.versions_table
|
|
278
|
+
|
|
258
279
|
raise ValueError(f"Unknown table type: {table_type}")
|
|
259
280
|
|
|
260
281
|
async def _get_or_create_table(self, table_name: str, table_type: str, db_schema: str) -> Table:
|
|
@@ -274,6 +295,11 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
274
295
|
table_is_available = await ais_table_available(session=sess, table_name=table_name, db_schema=db_schema)
|
|
275
296
|
|
|
276
297
|
if not table_is_available:
|
|
298
|
+
if table_name != self.versions_table_name:
|
|
299
|
+
# Also store the schema version for the created table
|
|
300
|
+
latest_schema_version = MigrationManager(self).latest_schema_version
|
|
301
|
+
await self.upsert_schema_version(table_name=table_name, version=latest_schema_version.public)
|
|
302
|
+
|
|
277
303
|
return await self._create_table(table_name=table_name, table_type=table_type, db_schema=db_schema)
|
|
278
304
|
|
|
279
305
|
if not await ais_valid_table(
|
|
@@ -291,12 +317,52 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
291
317
|
return Table(table_name, self.metadata, schema=db_schema, autoload_with=connection)
|
|
292
318
|
|
|
293
319
|
table = await conn.run_sync(create_table)
|
|
320
|
+
|
|
294
321
|
return table
|
|
295
322
|
|
|
296
323
|
except Exception as e:
|
|
297
324
|
log_error(f"Error loading existing table {db_schema}.{table_name}: {e}")
|
|
298
325
|
raise
|
|
299
326
|
|
|
327
|
+
async def get_latest_schema_version(self, table_name: str) -> str:
|
|
328
|
+
"""Get the latest version of the database schema."""
|
|
329
|
+
table = await self._get_table(table_type="versions")
|
|
330
|
+
if table is None:
|
|
331
|
+
return "2.0.0"
|
|
332
|
+
|
|
333
|
+
async with self.async_session_factory() as sess:
|
|
334
|
+
stmt = select(table)
|
|
335
|
+
# Latest version for the given table
|
|
336
|
+
stmt = stmt.where(table.c.table_name == table_name)
|
|
337
|
+
stmt = stmt.order_by(table.c.version.desc()).limit(1)
|
|
338
|
+
result = await sess.execute(stmt)
|
|
339
|
+
row = result.fetchone()
|
|
340
|
+
if row is None:
|
|
341
|
+
return "2.0.0"
|
|
342
|
+
|
|
343
|
+
version_dict = dict(row._mapping)
|
|
344
|
+
return version_dict.get("version") or "2.0.0"
|
|
345
|
+
|
|
346
|
+
async def upsert_schema_version(self, table_name: str, version: str) -> None:
|
|
347
|
+
"""Upsert the schema version into the database."""
|
|
348
|
+
table = await self._get_table(table_type="versions")
|
|
349
|
+
if table is None:
|
|
350
|
+
return
|
|
351
|
+
current_datetime = datetime.now().isoformat()
|
|
352
|
+
async with self.async_session_factory() as sess, sess.begin():
|
|
353
|
+
stmt = postgresql.insert(table).values(
|
|
354
|
+
table_name=table_name,
|
|
355
|
+
version=version,
|
|
356
|
+
created_at=current_datetime, # Store as ISO format string
|
|
357
|
+
updated_at=current_datetime,
|
|
358
|
+
)
|
|
359
|
+
# Update version if table_name already exists
|
|
360
|
+
stmt = stmt.on_conflict_do_update(
|
|
361
|
+
index_elements=["table_name"],
|
|
362
|
+
set_=dict(version=version, updated_at=current_datetime),
|
|
363
|
+
)
|
|
364
|
+
await sess.execute(stmt)
|
|
365
|
+
|
|
300
366
|
# -- Session methods --
|
|
301
367
|
async def delete_session(self, session_id: str) -> bool:
|
|
302
368
|
"""
|
|
@@ -1231,36 +1297,44 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
1231
1297
|
try:
|
|
1232
1298
|
table = await self._get_table(table_type="memories")
|
|
1233
1299
|
|
|
1234
|
-
|
|
1235
|
-
if memory.memory_id is None:
|
|
1236
|
-
memory.memory_id = str(uuid4())
|
|
1300
|
+
current_time = int(time.time())
|
|
1237
1301
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
memory
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
topics=memory.topics,
|
|
1246
|
-
updated_at=int(time.time()),
|
|
1247
|
-
)
|
|
1248
|
-
stmt = stmt.on_conflict_do_update( # type: ignore
|
|
1249
|
-
index_elements=["memory_id"],
|
|
1250
|
-
set_=dict(
|
|
1302
|
+
async with self.async_session_factory() as sess:
|
|
1303
|
+
async with sess.begin():
|
|
1304
|
+
if memory.memory_id is None:
|
|
1305
|
+
memory.memory_id = str(uuid4())
|
|
1306
|
+
|
|
1307
|
+
stmt = postgresql.insert(table).values(
|
|
1308
|
+
memory_id=memory.memory_id,
|
|
1251
1309
|
memory=memory.memory,
|
|
1252
|
-
topics=memory.topics,
|
|
1253
1310
|
input=memory.input,
|
|
1311
|
+
user_id=memory.user_id,
|
|
1254
1312
|
agent_id=memory.agent_id,
|
|
1255
1313
|
team_id=memory.team_id,
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1314
|
+
topics=memory.topics,
|
|
1315
|
+
feedback=memory.feedback,
|
|
1316
|
+
created_at=memory.created_at,
|
|
1317
|
+
updated_at=memory.created_at,
|
|
1318
|
+
)
|
|
1319
|
+
stmt = stmt.on_conflict_do_update( # type: ignore
|
|
1320
|
+
index_elements=["memory_id"],
|
|
1321
|
+
set_=dict(
|
|
1322
|
+
memory=memory.memory,
|
|
1323
|
+
topics=memory.topics,
|
|
1324
|
+
input=memory.input,
|
|
1325
|
+
agent_id=memory.agent_id,
|
|
1326
|
+
team_id=memory.team_id,
|
|
1327
|
+
feedback=memory.feedback,
|
|
1328
|
+
updated_at=current_time,
|
|
1329
|
+
# Preserve created_at on update - don't overwrite existing value
|
|
1330
|
+
created_at=table.c.created_at,
|
|
1331
|
+
),
|
|
1332
|
+
).returning(table)
|
|
1259
1333
|
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1334
|
+
result = await sess.execute(stmt)
|
|
1335
|
+
row = result.fetchone()
|
|
1336
|
+
if row is None:
|
|
1337
|
+
return None
|
|
1264
1338
|
|
|
1265
1339
|
memory_raw = dict(row._mapping)
|
|
1266
1340
|
|