agno 2.3.21__py3-none-any.whl → 2.3.23__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 +48 -2
- agno/agent/remote.py +234 -73
- agno/client/a2a/__init__.py +10 -0
- agno/client/a2a/client.py +554 -0
- agno/client/a2a/schemas.py +112 -0
- agno/client/a2a/utils.py +369 -0
- agno/db/migrations/utils.py +19 -0
- agno/db/migrations/v1_to_v2.py +54 -16
- agno/db/migrations/versions/v2_3_0.py +92 -53
- agno/db/mysql/async_mysql.py +5 -7
- agno/db/mysql/mysql.py +5 -7
- agno/db/mysql/schemas.py +39 -21
- agno/db/postgres/async_postgres.py +172 -42
- agno/db/postgres/postgres.py +186 -38
- agno/db/postgres/schemas.py +39 -21
- agno/db/postgres/utils.py +6 -2
- agno/db/singlestore/schemas.py +41 -21
- agno/db/singlestore/singlestore.py +14 -3
- agno/db/sqlite/async_sqlite.py +7 -2
- agno/db/sqlite/schemas.py +36 -21
- agno/db/sqlite/sqlite.py +3 -7
- agno/knowledge/chunking/document.py +3 -2
- agno/knowledge/chunking/markdown.py +8 -3
- agno/knowledge/chunking/recursive.py +2 -2
- agno/models/base.py +4 -0
- agno/models/google/gemini.py +27 -4
- agno/models/openai/chat.py +1 -1
- agno/models/openai/responses.py +14 -7
- agno/os/middleware/jwt.py +66 -27
- agno/os/routers/agents/router.py +3 -3
- agno/os/routers/evals/evals.py +2 -2
- agno/os/routers/knowledge/knowledge.py +5 -5
- agno/os/routers/knowledge/schemas.py +1 -1
- agno/os/routers/memory/memory.py +4 -4
- agno/os/routers/session/session.py +2 -2
- agno/os/routers/teams/router.py +4 -4
- agno/os/routers/traces/traces.py +3 -3
- agno/os/routers/workflows/router.py +3 -3
- agno/os/schema.py +1 -1
- agno/reasoning/deepseek.py +11 -1
- agno/reasoning/gemini.py +6 -2
- agno/reasoning/groq.py +8 -3
- agno/reasoning/openai.py +2 -0
- agno/remote/base.py +106 -9
- agno/skills/__init__.py +17 -0
- agno/skills/agent_skills.py +370 -0
- agno/skills/errors.py +32 -0
- agno/skills/loaders/__init__.py +4 -0
- agno/skills/loaders/base.py +27 -0
- agno/skills/loaders/local.py +216 -0
- agno/skills/skill.py +65 -0
- agno/skills/utils.py +107 -0
- agno/skills/validator.py +277 -0
- agno/team/remote.py +220 -60
- agno/team/team.py +41 -3
- agno/tools/brandfetch.py +27 -18
- agno/tools/browserbase.py +150 -13
- agno/tools/function.py +6 -1
- agno/tools/mcp/mcp.py +300 -17
- agno/tools/mcp/multi_mcp.py +269 -14
- agno/tools/toolkit.py +89 -21
- agno/utils/mcp.py +49 -8
- agno/utils/string.py +43 -1
- agno/workflow/condition.py +4 -2
- agno/workflow/loop.py +20 -1
- agno/workflow/remote.py +173 -33
- agno/workflow/router.py +4 -1
- agno/workflow/steps.py +4 -0
- agno/workflow/workflow.py +14 -0
- {agno-2.3.21.dist-info → agno-2.3.23.dist-info}/METADATA +13 -14
- {agno-2.3.21.dist-info → agno-2.3.23.dist-info}/RECORD +74 -60
- {agno-2.3.21.dist-info → agno-2.3.23.dist-info}/WHEEL +0 -0
- {agno-2.3.21.dist-info → agno-2.3.23.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.21.dist-info → agno-2.3.23.dist-info}/top_level.txt +0 -0
|
@@ -10,6 +10,7 @@ import time
|
|
|
10
10
|
from typing import Any, List, Tuple
|
|
11
11
|
|
|
12
12
|
from agno.db.base import AsyncBaseDb, BaseDb
|
|
13
|
+
from agno.db.migrations.utils import quote_db_identifier
|
|
13
14
|
from agno.utils.log import log_error, log_info, log_warning
|
|
14
15
|
|
|
15
16
|
try:
|
|
@@ -139,6 +140,9 @@ def _migrate_postgres(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
139
140
|
from sqlalchemy import text
|
|
140
141
|
|
|
141
142
|
db_schema = db.db_schema or "public" # type: ignore
|
|
143
|
+
db_type = type(db).__name__
|
|
144
|
+
quoted_schema = quote_db_identifier(db_type, db_schema)
|
|
145
|
+
quoted_table = quote_db_identifier(db_type, table_name)
|
|
142
146
|
|
|
143
147
|
with db.Session() as sess, sess.begin(): # type: ignore
|
|
144
148
|
# Check if table exists
|
|
@@ -181,7 +185,7 @@ def _migrate_postgres(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
181
185
|
sess.execute(
|
|
182
186
|
text(
|
|
183
187
|
f"""
|
|
184
|
-
ALTER TABLE {
|
|
188
|
+
ALTER TABLE {quoted_schema}.{quoted_table}
|
|
185
189
|
ADD COLUMN created_at BIGINT
|
|
186
190
|
"""
|
|
187
191
|
),
|
|
@@ -190,7 +194,7 @@ def _migrate_postgres(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
190
194
|
sess.execute(
|
|
191
195
|
text(
|
|
192
196
|
f"""
|
|
193
|
-
UPDATE {
|
|
197
|
+
UPDATE {quoted_schema}.{quoted_table}
|
|
194
198
|
SET created_at = COALESCE(updated_at, :default_time)
|
|
195
199
|
"""
|
|
196
200
|
),
|
|
@@ -200,7 +204,7 @@ def _migrate_postgres(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
200
204
|
sess.execute(
|
|
201
205
|
text(
|
|
202
206
|
f"""
|
|
203
|
-
ALTER TABLE {
|
|
207
|
+
ALTER TABLE {quoted_schema}.{quoted_table}
|
|
204
208
|
ALTER COLUMN created_at SET NOT NULL
|
|
205
209
|
"""
|
|
206
210
|
),
|
|
@@ -210,7 +214,7 @@ def _migrate_postgres(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
210
214
|
text(
|
|
211
215
|
f"""
|
|
212
216
|
CREATE INDEX IF NOT EXISTS idx_{table_name}_created_at
|
|
213
|
-
ON {
|
|
217
|
+
ON {quoted_schema}.{quoted_table}(created_at)
|
|
214
218
|
"""
|
|
215
219
|
)
|
|
216
220
|
)
|
|
@@ -221,7 +225,7 @@ def _migrate_postgres(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
221
225
|
sess.execute(
|
|
222
226
|
text(
|
|
223
227
|
f"""
|
|
224
|
-
ALTER TABLE {
|
|
228
|
+
ALTER TABLE {quoted_schema}.{quoted_table}
|
|
225
229
|
ADD COLUMN feedback TEXT
|
|
226
230
|
"""
|
|
227
231
|
)
|
|
@@ -231,7 +235,7 @@ def _migrate_postgres(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
231
235
|
("memory", table_name),
|
|
232
236
|
("topics", table_name),
|
|
233
237
|
]
|
|
234
|
-
_convert_json_to_jsonb(sess, db_schema, json_columns)
|
|
238
|
+
_convert_json_to_jsonb(sess, db_schema, json_columns, db_type)
|
|
235
239
|
|
|
236
240
|
if table_type == "sessions":
|
|
237
241
|
json_columns = [
|
|
@@ -243,37 +247,41 @@ def _migrate_postgres(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
243
247
|
("runs", table_name),
|
|
244
248
|
("summary", table_name),
|
|
245
249
|
]
|
|
246
|
-
_convert_json_to_jsonb(sess, db_schema, json_columns)
|
|
250
|
+
_convert_json_to_jsonb(sess, db_schema, json_columns, db_type)
|
|
247
251
|
if table_type == "evals":
|
|
248
252
|
json_columns = [
|
|
249
253
|
("eval_data", table_name),
|
|
250
254
|
("eval_input", table_name),
|
|
251
255
|
]
|
|
252
|
-
_convert_json_to_jsonb(sess, db_schema, json_columns)
|
|
256
|
+
_convert_json_to_jsonb(sess, db_schema, json_columns, db_type)
|
|
253
257
|
if table_type == "metrics":
|
|
254
258
|
json_columns = [
|
|
255
259
|
("token_metrics", table_name),
|
|
256
260
|
("model_metrics", table_name),
|
|
257
261
|
]
|
|
258
|
-
_convert_json_to_jsonb(sess, db_schema, json_columns)
|
|
262
|
+
_convert_json_to_jsonb(sess, db_schema, json_columns, db_type)
|
|
259
263
|
if table_type == "knowledge":
|
|
260
264
|
json_columns = [
|
|
261
265
|
("metadata", table_name),
|
|
262
266
|
]
|
|
263
|
-
_convert_json_to_jsonb(sess, db_schema, json_columns)
|
|
267
|
+
_convert_json_to_jsonb(sess, db_schema, json_columns, db_type)
|
|
264
268
|
if table_type == "culture":
|
|
265
269
|
json_columns = [
|
|
266
270
|
("metadata", table_name),
|
|
267
271
|
]
|
|
268
|
-
_convert_json_to_jsonb(sess, db_schema, json_columns)
|
|
272
|
+
_convert_json_to_jsonb(sess, db_schema, json_columns, db_type)
|
|
269
273
|
|
|
270
274
|
sess.commit()
|
|
271
275
|
return True
|
|
272
276
|
|
|
273
277
|
|
|
274
|
-
def _convert_json_to_jsonb(
|
|
278
|
+
def _convert_json_to_jsonb(
|
|
279
|
+
sess: Any, db_schema: str, json_columns: List[Tuple[str, str]], db_type: str = "PostgresDb"
|
|
280
|
+
) -> None:
|
|
281
|
+
quoted_schema = quote_db_identifier(db_type, db_schema) if db_schema else None
|
|
275
282
|
for column_name, table_name in json_columns:
|
|
276
|
-
|
|
283
|
+
quoted_table = quote_db_identifier(db_type, table_name)
|
|
284
|
+
table_full_name = f"{quoted_schema}.{quoted_table}" if quoted_schema else quoted_table
|
|
277
285
|
# Check current type
|
|
278
286
|
col_type = sess.execute(
|
|
279
287
|
text(
|
|
@@ -305,6 +313,9 @@ async def _migrate_async_postgres(db: AsyncBaseDb, table_type: str, table_name:
|
|
|
305
313
|
from sqlalchemy import text
|
|
306
314
|
|
|
307
315
|
db_schema = db.db_schema or "public" # type: ignore
|
|
316
|
+
db_type = type(db).__name__
|
|
317
|
+
quoted_schema = quote_db_identifier(db_type, db_schema)
|
|
318
|
+
quoted_table = quote_db_identifier(db_type, table_name)
|
|
308
319
|
|
|
309
320
|
async with db.async_session_factory() as sess, sess.begin(): # type: ignore
|
|
310
321
|
# Check if table exists
|
|
@@ -349,7 +360,7 @@ async def _migrate_async_postgres(db: AsyncBaseDb, table_type: str, table_name:
|
|
|
349
360
|
await sess.execute(
|
|
350
361
|
text(
|
|
351
362
|
f"""
|
|
352
|
-
ALTER TABLE {
|
|
363
|
+
ALTER TABLE {quoted_schema}.{quoted_table}
|
|
353
364
|
ADD COLUMN created_at BIGINT
|
|
354
365
|
"""
|
|
355
366
|
),
|
|
@@ -358,7 +369,7 @@ async def _migrate_async_postgres(db: AsyncBaseDb, table_type: str, table_name:
|
|
|
358
369
|
await sess.execute(
|
|
359
370
|
text(
|
|
360
371
|
f"""
|
|
361
|
-
UPDATE {
|
|
372
|
+
UPDATE {quoted_schema}.{quoted_table}
|
|
362
373
|
SET created_at = COALESCE(updated_at, :default_time)
|
|
363
374
|
"""
|
|
364
375
|
),
|
|
@@ -368,7 +379,7 @@ async def _migrate_async_postgres(db: AsyncBaseDb, table_type: str, table_name:
|
|
|
368
379
|
await sess.execute(
|
|
369
380
|
text(
|
|
370
381
|
f"""
|
|
371
|
-
ALTER TABLE {
|
|
382
|
+
ALTER TABLE {quoted_schema}.{quoted_table}
|
|
372
383
|
ALTER COLUMN created_at SET NOT NULL
|
|
373
384
|
"""
|
|
374
385
|
),
|
|
@@ -378,7 +389,7 @@ async def _migrate_async_postgres(db: AsyncBaseDb, table_type: str, table_name:
|
|
|
378
389
|
text(
|
|
379
390
|
f"""
|
|
380
391
|
CREATE INDEX IF NOT EXISTS idx_{table_name}_created_at
|
|
381
|
-
ON {
|
|
392
|
+
ON {quoted_schema}.{quoted_table}(created_at)
|
|
382
393
|
"""
|
|
383
394
|
)
|
|
384
395
|
)
|
|
@@ -389,7 +400,7 @@ async def _migrate_async_postgres(db: AsyncBaseDb, table_type: str, table_name:
|
|
|
389
400
|
await sess.execute(
|
|
390
401
|
text(
|
|
391
402
|
f"""
|
|
392
|
-
ALTER TABLE {
|
|
403
|
+
ALTER TABLE {quoted_schema}.{quoted_table}
|
|
393
404
|
ADD COLUMN feedback TEXT
|
|
394
405
|
"""
|
|
395
406
|
)
|
|
@@ -399,7 +410,7 @@ async def _migrate_async_postgres(db: AsyncBaseDb, table_type: str, table_name:
|
|
|
399
410
|
("memory", table_name),
|
|
400
411
|
("topics", table_name),
|
|
401
412
|
]
|
|
402
|
-
await _async_convert_json_to_jsonb(sess, db_schema, json_columns)
|
|
413
|
+
await _async_convert_json_to_jsonb(sess, db_schema, json_columns, db_type)
|
|
403
414
|
if table_type == "sessions":
|
|
404
415
|
json_columns = [
|
|
405
416
|
("session_data", table_name),
|
|
@@ -410,39 +421,43 @@ async def _migrate_async_postgres(db: AsyncBaseDb, table_type: str, table_name:
|
|
|
410
421
|
("runs", table_name),
|
|
411
422
|
("summary", table_name),
|
|
412
423
|
]
|
|
413
|
-
await _async_convert_json_to_jsonb(sess, db_schema, json_columns)
|
|
424
|
+
await _async_convert_json_to_jsonb(sess, db_schema, json_columns, db_type)
|
|
414
425
|
|
|
415
426
|
if table_type == "evals":
|
|
416
427
|
json_columns = [
|
|
417
428
|
("eval_data", table_name),
|
|
418
429
|
("eval_input", table_name),
|
|
419
430
|
]
|
|
420
|
-
await _async_convert_json_to_jsonb(sess, db_schema, json_columns)
|
|
431
|
+
await _async_convert_json_to_jsonb(sess, db_schema, json_columns, db_type)
|
|
421
432
|
if table_type == "metrics":
|
|
422
433
|
json_columns = [
|
|
423
434
|
("token_metrics", table_name),
|
|
424
435
|
("model_metrics", table_name),
|
|
425
436
|
]
|
|
426
|
-
await _async_convert_json_to_jsonb(sess, db_schema, json_columns)
|
|
437
|
+
await _async_convert_json_to_jsonb(sess, db_schema, json_columns, db_type)
|
|
427
438
|
if table_type == "knowledge":
|
|
428
439
|
json_columns = [
|
|
429
440
|
("metadata", table_name),
|
|
430
441
|
]
|
|
431
|
-
await _async_convert_json_to_jsonb(sess, db_schema, json_columns)
|
|
442
|
+
await _async_convert_json_to_jsonb(sess, db_schema, json_columns, db_type)
|
|
432
443
|
|
|
433
444
|
if table_type == "culture":
|
|
434
445
|
json_columns = [
|
|
435
446
|
("metadata", table_name),
|
|
436
447
|
]
|
|
437
|
-
await _async_convert_json_to_jsonb(sess, db_schema, json_columns)
|
|
448
|
+
await _async_convert_json_to_jsonb(sess, db_schema, json_columns, db_type)
|
|
438
449
|
|
|
439
450
|
await sess.commit()
|
|
440
451
|
return True
|
|
441
452
|
|
|
442
453
|
|
|
443
|
-
async def _async_convert_json_to_jsonb(
|
|
454
|
+
async def _async_convert_json_to_jsonb(
|
|
455
|
+
sess: Any, db_schema: str, json_columns: List[Tuple[str, str]], db_type: str = "AsyncPostgresDb"
|
|
456
|
+
) -> None:
|
|
457
|
+
quoted_schema = quote_db_identifier(db_type, db_schema) if db_schema else None
|
|
444
458
|
for column_name, table_name in json_columns:
|
|
445
|
-
|
|
459
|
+
quoted_table = quote_db_identifier(db_type, table_name)
|
|
460
|
+
table_full_name = f"{quoted_schema}.{quoted_table}" if quoted_schema else quoted_table
|
|
446
461
|
# Check current type
|
|
447
462
|
result = await sess.execute(
|
|
448
463
|
text(
|
|
@@ -475,6 +490,9 @@ def _migrate_mysql(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
475
490
|
from sqlalchemy import text
|
|
476
491
|
|
|
477
492
|
db_schema = db.db_schema or "agno" # type: ignore
|
|
493
|
+
db_type = type(db).__name__
|
|
494
|
+
quoted_schema = quote_db_identifier(db_type, db_schema)
|
|
495
|
+
quoted_table = quote_db_identifier(db_type, table_name)
|
|
478
496
|
|
|
479
497
|
with db.Session() as sess, sess.begin(): # type: ignore
|
|
480
498
|
# Check if table exists
|
|
@@ -517,7 +535,7 @@ def _migrate_mysql(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
517
535
|
sess.execute(
|
|
518
536
|
text(
|
|
519
537
|
f"""
|
|
520
|
-
ALTER TABLE
|
|
538
|
+
ALTER TABLE {quoted_schema}.{quoted_table}
|
|
521
539
|
ADD COLUMN `created_at` BIGINT,
|
|
522
540
|
ADD INDEX `idx_{table_name}_created_at` (`created_at`)
|
|
523
541
|
"""
|
|
@@ -527,7 +545,7 @@ def _migrate_mysql(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
527
545
|
sess.execute(
|
|
528
546
|
text(
|
|
529
547
|
f"""
|
|
530
|
-
UPDATE
|
|
548
|
+
UPDATE {quoted_schema}.{quoted_table}
|
|
531
549
|
SET `created_at` = COALESCE(`updated_at`, :default_time)
|
|
532
550
|
"""
|
|
533
551
|
),
|
|
@@ -537,7 +555,7 @@ def _migrate_mysql(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
537
555
|
sess.execute(
|
|
538
556
|
text(
|
|
539
557
|
f"""
|
|
540
|
-
ALTER TABLE
|
|
558
|
+
ALTER TABLE {quoted_schema}.{quoted_table}
|
|
541
559
|
MODIFY COLUMN `created_at` BIGINT NOT NULL
|
|
542
560
|
"""
|
|
543
561
|
)
|
|
@@ -549,7 +567,7 @@ def _migrate_mysql(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
549
567
|
sess.execute(
|
|
550
568
|
text(
|
|
551
569
|
f"""
|
|
552
|
-
ALTER TABLE
|
|
570
|
+
ALTER TABLE {quoted_schema}.{quoted_table}
|
|
553
571
|
ADD COLUMN `feedback` TEXT
|
|
554
572
|
"""
|
|
555
573
|
)
|
|
@@ -561,6 +579,8 @@ def _migrate_mysql(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
561
579
|
|
|
562
580
|
def _migrate_sqlite(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
563
581
|
"""Migrate SQLite database."""
|
|
582
|
+
db_type = type(db).__name__
|
|
583
|
+
quoted_table = quote_db_identifier(db_type, table_name)
|
|
564
584
|
|
|
565
585
|
with db.Session() as sess, sess.begin(): # type: ignore
|
|
566
586
|
# Check if table exists
|
|
@@ -581,7 +601,7 @@ def _migrate_sqlite(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
581
601
|
# SQLite doesn't support ALTER TABLE ADD COLUMN with constraints easily
|
|
582
602
|
# We'll use a simpler approach
|
|
583
603
|
# Check if columns already exist using PRAGMA
|
|
584
|
-
result = sess.execute(text(f"PRAGMA table_info({
|
|
604
|
+
result = sess.execute(text(f"PRAGMA table_info({quoted_table})"))
|
|
585
605
|
columns_info = result.fetchall()
|
|
586
606
|
existing_columns = {row[1] for row in columns_info} # row[1] contains column name
|
|
587
607
|
|
|
@@ -592,13 +612,13 @@ def _migrate_sqlite(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
592
612
|
# Add created_at column with NOT NULL constraint and default value
|
|
593
613
|
# SQLite doesn't support ALTER COLUMN, so we add NOT NULL directly
|
|
594
614
|
sess.execute(
|
|
595
|
-
text(f"ALTER TABLE {
|
|
615
|
+
text(f"ALTER TABLE {quoted_table} ADD COLUMN created_at BIGINT NOT NULL DEFAULT {current_time}"),
|
|
596
616
|
)
|
|
597
617
|
# Populate created_at for existing rows
|
|
598
618
|
sess.execute(
|
|
599
619
|
text(
|
|
600
620
|
f"""
|
|
601
|
-
UPDATE {
|
|
621
|
+
UPDATE {quoted_table}
|
|
602
622
|
SET created_at = COALESCE(updated_at, :default_time)
|
|
603
623
|
WHERE created_at = :default_time
|
|
604
624
|
"""
|
|
@@ -607,13 +627,13 @@ def _migrate_sqlite(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
607
627
|
)
|
|
608
628
|
# Add index
|
|
609
629
|
sess.execute(
|
|
610
|
-
text(f"CREATE INDEX IF NOT EXISTS idx_{table_name}_created_at ON {
|
|
630
|
+
text(f"CREATE INDEX IF NOT EXISTS idx_{table_name}_created_at ON {quoted_table}(created_at)")
|
|
611
631
|
)
|
|
612
632
|
|
|
613
633
|
# Add feedback if it doesn't exist
|
|
614
634
|
if "feedback" not in existing_columns:
|
|
615
635
|
log_info(f"-- Adding feedback column to {table_name}")
|
|
616
|
-
sess.execute(text(f"ALTER TABLE {
|
|
636
|
+
sess.execute(text(f"ALTER TABLE {quoted_table} ADD COLUMN feedback VARCHAR"))
|
|
617
637
|
|
|
618
638
|
sess.commit()
|
|
619
639
|
return True
|
|
@@ -621,6 +641,8 @@ def _migrate_sqlite(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
621
641
|
|
|
622
642
|
async def _migrate_async_sqlite(db: AsyncBaseDb, table_type: str, table_name: str) -> bool:
|
|
623
643
|
"""Migrate SQLite database."""
|
|
644
|
+
db_type = type(db).__name__
|
|
645
|
+
quoted_table = quote_db_identifier(db_type, table_name)
|
|
624
646
|
|
|
625
647
|
async with db.async_session_factory() as sess, sess.begin(): # type: ignore
|
|
626
648
|
# Check if table exists
|
|
@@ -642,7 +664,7 @@ async def _migrate_async_sqlite(db: AsyncBaseDb, table_type: str, table_name: st
|
|
|
642
664
|
# SQLite doesn't support ALTER TABLE ADD COLUMN with constraints easily
|
|
643
665
|
# We'll use a simpler approach
|
|
644
666
|
# Check if columns already exist using PRAGMA
|
|
645
|
-
result = await sess.execute(text(f"PRAGMA table_info({
|
|
667
|
+
result = await sess.execute(text(f"PRAGMA table_info({quoted_table})"))
|
|
646
668
|
columns_info = result.fetchall()
|
|
647
669
|
existing_columns = {row[1] for row in columns_info} # row[1] contains column name
|
|
648
670
|
|
|
@@ -653,13 +675,13 @@ async def _migrate_async_sqlite(db: AsyncBaseDb, table_type: str, table_name: st
|
|
|
653
675
|
# Add created_at column with NOT NULL constraint and default value
|
|
654
676
|
# SQLite doesn't support ALTER COLUMN, so we add NOT NULL directly
|
|
655
677
|
await sess.execute(
|
|
656
|
-
text(f"ALTER TABLE {
|
|
678
|
+
text(f"ALTER TABLE {quoted_table} ADD COLUMN created_at BIGINT NOT NULL DEFAULT {current_time}"),
|
|
657
679
|
)
|
|
658
680
|
# Populate created_at for existing rows
|
|
659
681
|
await sess.execute(
|
|
660
682
|
text(
|
|
661
683
|
f"""
|
|
662
|
-
UPDATE {
|
|
684
|
+
UPDATE {quoted_table}
|
|
663
685
|
SET created_at = COALESCE(updated_at, :default_time)
|
|
664
686
|
WHERE created_at = :default_time
|
|
665
687
|
"""
|
|
@@ -668,13 +690,13 @@ async def _migrate_async_sqlite(db: AsyncBaseDb, table_type: str, table_name: st
|
|
|
668
690
|
)
|
|
669
691
|
# Add index
|
|
670
692
|
await sess.execute(
|
|
671
|
-
text(f"CREATE INDEX IF NOT EXISTS idx_{table_name}_created_at ON {
|
|
693
|
+
text(f"CREATE INDEX IF NOT EXISTS idx_{table_name}_created_at ON {quoted_table}(created_at)")
|
|
672
694
|
)
|
|
673
695
|
|
|
674
696
|
# Add feedback if it doesn't exist
|
|
675
697
|
if "feedback" not in existing_columns:
|
|
676
698
|
log_info(f"-- Adding feedback column to {table_name}")
|
|
677
|
-
await sess.execute(text(f"ALTER TABLE {
|
|
699
|
+
await sess.execute(text(f"ALTER TABLE {quoted_table} ADD COLUMN feedback VARCHAR"))
|
|
678
700
|
|
|
679
701
|
await sess.commit()
|
|
680
702
|
return True
|
|
@@ -685,6 +707,9 @@ def _migrate_singlestore(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
685
707
|
from sqlalchemy import text
|
|
686
708
|
|
|
687
709
|
db_schema = db.db_schema or "agno" # type: ignore
|
|
710
|
+
db_type = type(db).__name__
|
|
711
|
+
quoted_schema = quote_db_identifier(db_type, db_schema)
|
|
712
|
+
quoted_table = quote_db_identifier(db_type, table_name)
|
|
688
713
|
|
|
689
714
|
with db.Session() as sess, sess.begin(): # type: ignore
|
|
690
715
|
# Check if table exists
|
|
@@ -727,7 +752,7 @@ def _migrate_singlestore(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
727
752
|
sess.execute(
|
|
728
753
|
text(
|
|
729
754
|
f"""
|
|
730
|
-
ALTER TABLE
|
|
755
|
+
ALTER TABLE {quoted_schema}.{quoted_table}
|
|
731
756
|
ADD COLUMN `created_at` BIGINT,
|
|
732
757
|
ADD INDEX `idx_{table_name}_created_at` (`created_at`)
|
|
733
758
|
"""
|
|
@@ -737,7 +762,7 @@ def _migrate_singlestore(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
737
762
|
sess.execute(
|
|
738
763
|
text(
|
|
739
764
|
f"""
|
|
740
|
-
UPDATE
|
|
765
|
+
UPDATE {quoted_schema}.{quoted_table}
|
|
741
766
|
SET `created_at` = COALESCE(`updated_at`, :default_time)
|
|
742
767
|
"""
|
|
743
768
|
),
|
|
@@ -750,7 +775,7 @@ def _migrate_singlestore(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
750
775
|
sess.execute(
|
|
751
776
|
text(
|
|
752
777
|
f"""
|
|
753
|
-
ALTER TABLE
|
|
778
|
+
ALTER TABLE {quoted_schema}.{quoted_table}
|
|
754
779
|
ADD COLUMN `feedback` TEXT
|
|
755
780
|
"""
|
|
756
781
|
)
|
|
@@ -765,6 +790,9 @@ def _revert_postgres(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
765
790
|
from sqlalchemy import text
|
|
766
791
|
|
|
767
792
|
db_schema = db.db_schema or "agno" # type: ignore
|
|
793
|
+
db_type = type(db).__name__
|
|
794
|
+
quoted_schema = quote_db_identifier(db_type, db_schema)
|
|
795
|
+
quoted_table = quote_db_identifier(db_type, table_name)
|
|
768
796
|
|
|
769
797
|
with db.Session() as sess, sess.begin(): # type: ignore
|
|
770
798
|
# Check if table exists
|
|
@@ -786,9 +814,9 @@ def _revert_postgres(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
786
814
|
return False
|
|
787
815
|
if table_type == "memories":
|
|
788
816
|
# Remove columns (in reverse order)
|
|
789
|
-
sess.execute(text(f"ALTER TABLE {
|
|
817
|
+
sess.execute(text(f"ALTER TABLE {quoted_schema}.{quoted_table} DROP COLUMN IF EXISTS feedback"))
|
|
790
818
|
sess.execute(text(f"DROP INDEX IF EXISTS idx_{table_name}_created_at"))
|
|
791
|
-
sess.execute(text(f"ALTER TABLE {
|
|
819
|
+
sess.execute(text(f"ALTER TABLE {quoted_schema}.{quoted_table} DROP COLUMN IF EXISTS created_at"))
|
|
792
820
|
sess.commit()
|
|
793
821
|
return True
|
|
794
822
|
|
|
@@ -798,6 +826,9 @@ async def _revert_async_postgres(db: AsyncBaseDb, table_type: str, table_name: s
|
|
|
798
826
|
from sqlalchemy import text
|
|
799
827
|
|
|
800
828
|
db_schema = db.db_schema or "agno" # type: ignore
|
|
829
|
+
db_type = type(db).__name__
|
|
830
|
+
quoted_schema = quote_db_identifier(db_type, db_schema)
|
|
831
|
+
quoted_table = quote_db_identifier(db_type, table_name)
|
|
801
832
|
|
|
802
833
|
async with db.async_session_factory() as sess, sess.begin(): # type: ignore
|
|
803
834
|
# Check if table exists
|
|
@@ -820,9 +851,9 @@ async def _revert_async_postgres(db: AsyncBaseDb, table_type: str, table_name: s
|
|
|
820
851
|
return False
|
|
821
852
|
if table_type == "memories":
|
|
822
853
|
# Remove columns (in reverse order)
|
|
823
|
-
await sess.execute(text(f"ALTER TABLE {
|
|
854
|
+
await sess.execute(text(f"ALTER TABLE {quoted_schema}.{quoted_table} DROP COLUMN IF EXISTS feedback"))
|
|
824
855
|
await sess.execute(text(f"DROP INDEX IF EXISTS idx_{table_name}_created_at"))
|
|
825
|
-
await sess.execute(text(f"ALTER TABLE {
|
|
856
|
+
await sess.execute(text(f"ALTER TABLE {quoted_schema}.{quoted_table} DROP COLUMN IF EXISTS created_at"))
|
|
826
857
|
await sess.commit()
|
|
827
858
|
return True
|
|
828
859
|
|
|
@@ -832,6 +863,9 @@ def _revert_mysql(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
832
863
|
from sqlalchemy import text
|
|
833
864
|
|
|
834
865
|
db_schema = db.db_schema or "agno" # type: ignore
|
|
866
|
+
db_type = type(db).__name__
|
|
867
|
+
quoted_schema = quote_db_identifier(db_type, db_schema)
|
|
868
|
+
quoted_table = quote_db_identifier(db_type, table_name)
|
|
835
869
|
|
|
836
870
|
with db.Session() as sess, sess.begin(): # type: ignore
|
|
837
871
|
# Check if table exists
|
|
@@ -867,7 +901,7 @@ def _revert_mysql(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
867
901
|
}
|
|
868
902
|
# Drop feedback column if it exists
|
|
869
903
|
if "feedback" in existing_columns:
|
|
870
|
-
sess.execute(text(f"ALTER TABLE
|
|
904
|
+
sess.execute(text(f"ALTER TABLE {quoted_schema}.{quoted_table} DROP COLUMN `feedback`"))
|
|
871
905
|
# Drop created_at index if it exists
|
|
872
906
|
index_exists = sess.execute(
|
|
873
907
|
text(
|
|
@@ -881,10 +915,12 @@ def _revert_mysql(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
881
915
|
{"schema": db_schema, "table_name": table_name, "index_name": f"idx_{table_name}_created_at"},
|
|
882
916
|
).scalar()
|
|
883
917
|
if index_exists:
|
|
884
|
-
sess.execute(
|
|
918
|
+
sess.execute(
|
|
919
|
+
text(f"ALTER TABLE {quoted_schema}.{quoted_table} DROP INDEX `idx_{table_name}_created_at`")
|
|
920
|
+
)
|
|
885
921
|
# Drop created_at column if it exists
|
|
886
922
|
if "created_at" in existing_columns:
|
|
887
|
-
sess.execute(text(f"ALTER TABLE
|
|
923
|
+
sess.execute(text(f"ALTER TABLE {quoted_schema}.{quoted_table} DROP COLUMN `created_at`"))
|
|
888
924
|
|
|
889
925
|
sess.commit()
|
|
890
926
|
return True
|
|
@@ -909,6 +945,9 @@ def _revert_singlestore(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
909
945
|
from sqlalchemy import text
|
|
910
946
|
|
|
911
947
|
db_schema = db.db_schema or "agno" # type: ignore
|
|
948
|
+
db_type = type(db).__name__
|
|
949
|
+
quoted_schema = quote_db_identifier(db_type, db_schema)
|
|
950
|
+
quoted_table = quote_db_identifier(db_type, table_name)
|
|
912
951
|
|
|
913
952
|
with db.Session() as sess, sess.begin(): # type: ignore
|
|
914
953
|
# Check if table exists
|
|
@@ -929,10 +968,10 @@ def _revert_singlestore(db: BaseDb, table_type: str, table_name: str) -> bool:
|
|
|
929
968
|
log_info(f"Table {table_name} does not exist, skipping revert")
|
|
930
969
|
return False
|
|
931
970
|
if table_type == "memories":
|
|
932
|
-
sess.execute(text(f"ALTER TABLE
|
|
971
|
+
sess.execute(text(f"ALTER TABLE {quoted_schema}.{quoted_table} DROP COLUMN IF EXISTS `feedback`"))
|
|
933
972
|
sess.execute(
|
|
934
|
-
text(f"ALTER TABLE
|
|
973
|
+
text(f"ALTER TABLE {quoted_schema}.{quoted_table} DROP INDEX IF EXISTS `idx_{table_name}_created_at`")
|
|
935
974
|
)
|
|
936
|
-
sess.execute(text(f"ALTER TABLE
|
|
975
|
+
sess.execute(text(f"ALTER TABLE {quoted_schema}.{quoted_table} DROP COLUMN IF EXISTS `created_at`"))
|
|
937
976
|
sess.commit()
|
|
938
977
|
return True
|
agno/db/mysql/async_mysql.py
CHANGED
|
@@ -158,7 +158,10 @@ class AsyncMySQLDb(AsyncBaseDb):
|
|
|
158
158
|
Table: SQLAlchemy Table object
|
|
159
159
|
"""
|
|
160
160
|
try:
|
|
161
|
-
|
|
161
|
+
# Pass traces_table_name and db_schema for spans table foreign key resolution
|
|
162
|
+
table_schema = get_table_schema_definition(
|
|
163
|
+
table_type, traces_table_name=self.trace_table_name, db_schema=self.db_schema
|
|
164
|
+
).copy()
|
|
162
165
|
|
|
163
166
|
log_debug(f"Creating table {self.db_schema}.{table_name} with schema: {table_schema}")
|
|
164
167
|
|
|
@@ -183,12 +186,7 @@ class AsyncMySQLDb(AsyncBaseDb):
|
|
|
183
186
|
|
|
184
187
|
# Handle foreign key constraint
|
|
185
188
|
if "foreign_key" in col_config:
|
|
186
|
-
|
|
187
|
-
# For spans table, dynamically replace the traces table reference
|
|
188
|
-
# with the actual trace table name configured for this db instance
|
|
189
|
-
if table_type == "spans" and "trace_id" in fk_ref:
|
|
190
|
-
fk_ref = f"{self.db_schema}.{self.trace_table_name}.trace_id"
|
|
191
|
-
column_args.append(ForeignKey(fk_ref))
|
|
189
|
+
column_args.append(ForeignKey(col_config["foreign_key"]))
|
|
192
190
|
|
|
193
191
|
columns.append(Column(*column_args, **column_kwargs)) # type: ignore
|
|
194
192
|
|
agno/db/mysql/mysql.py
CHANGED
|
@@ -155,7 +155,10 @@ class MySQLDb(BaseDb):
|
|
|
155
155
|
Table: SQLAlchemy Table object
|
|
156
156
|
"""
|
|
157
157
|
try:
|
|
158
|
-
|
|
158
|
+
# Pass traces_table_name and db_schema for spans table foreign key resolution
|
|
159
|
+
table_schema = get_table_schema_definition(
|
|
160
|
+
table_type, traces_table_name=self.trace_table_name, db_schema=self.db_schema
|
|
161
|
+
).copy()
|
|
159
162
|
|
|
160
163
|
columns: List[Column] = []
|
|
161
164
|
indexes: List[str] = []
|
|
@@ -178,12 +181,7 @@ class MySQLDb(BaseDb):
|
|
|
178
181
|
|
|
179
182
|
# Handle foreign key constraint
|
|
180
183
|
if "foreign_key" in col_config:
|
|
181
|
-
|
|
182
|
-
# For spans table, dynamically replace the traces table reference
|
|
183
|
-
# with the actual trace table name configured for this db instance
|
|
184
|
-
if table_type == "spans" and "trace_id" in fk_ref:
|
|
185
|
-
fk_ref = f"{self.db_schema}.{self.trace_table_name}.trace_id"
|
|
186
|
-
column_args.append(ForeignKey(fk_ref))
|
|
184
|
+
column_args.append(ForeignKey(col_config["foreign_key"]))
|
|
187
185
|
|
|
188
186
|
columns.append(Column(*column_args, **column_kwargs)) # type: ignore
|
|
189
187
|
|
agno/db/mysql/schemas.py
CHANGED
|
@@ -136,37 +136,56 @@ TRACE_TABLE_SCHEMA = {
|
|
|
136
136
|
"created_at": {"type": lambda: String(128), "nullable": False, "index": True}, # ISO 8601 datetime string
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
SPAN_TABLE_SCHEMA = {
|
|
140
|
-
"span_id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
|
|
141
|
-
"trace_id": {
|
|
142
|
-
"type": lambda: String(128),
|
|
143
|
-
"nullable": False,
|
|
144
|
-
"index": True,
|
|
145
|
-
"foreign_key": "agno_traces.trace_id", # Foreign key to traces table
|
|
146
|
-
},
|
|
147
|
-
"parent_span_id": {"type": lambda: String(128), "nullable": True, "index": True},
|
|
148
|
-
"name": {"type": lambda: String(255), "nullable": False},
|
|
149
|
-
"span_kind": {"type": lambda: String(50), "nullable": False},
|
|
150
|
-
"status_code": {"type": lambda: String(50), "nullable": False},
|
|
151
|
-
"status_message": {"type": Text, "nullable": True},
|
|
152
|
-
"start_time": {"type": lambda: String(128), "nullable": False, "index": True}, # ISO 8601 datetime string
|
|
153
|
-
"end_time": {"type": lambda: String(128), "nullable": False}, # ISO 8601 datetime string
|
|
154
|
-
"duration_ms": {"type": BigInteger, "nullable": False},
|
|
155
|
-
"attributes": {"type": JSON, "nullable": True},
|
|
156
|
-
"created_at": {"type": lambda: String(128), "nullable": False, "index": True}, # ISO 8601 datetime string
|
|
157
|
-
}
|
|
158
139
|
|
|
140
|
+
def _get_span_table_schema(traces_table_name: str = "agno_traces", db_schema: str = "agno") -> dict[str, Any]:
|
|
141
|
+
"""Get the span table schema with the correct foreign key reference.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
traces_table_name: The name of the traces table to reference in the foreign key.
|
|
145
|
+
db_schema: The database schema name.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
The span table schema dictionary.
|
|
149
|
+
"""
|
|
150
|
+
return {
|
|
151
|
+
"span_id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
|
|
152
|
+
"trace_id": {
|
|
153
|
+
"type": lambda: String(128),
|
|
154
|
+
"nullable": False,
|
|
155
|
+
"index": True,
|
|
156
|
+
"foreign_key": f"{db_schema}.{traces_table_name}.trace_id",
|
|
157
|
+
},
|
|
158
|
+
"parent_span_id": {"type": lambda: String(128), "nullable": True, "index": True},
|
|
159
|
+
"name": {"type": lambda: String(255), "nullable": False},
|
|
160
|
+
"span_kind": {"type": lambda: String(50), "nullable": False},
|
|
161
|
+
"status_code": {"type": lambda: String(50), "nullable": False},
|
|
162
|
+
"status_message": {"type": Text, "nullable": True},
|
|
163
|
+
"start_time": {"type": lambda: String(128), "nullable": False, "index": True}, # ISO 8601 datetime string
|
|
164
|
+
"end_time": {"type": lambda: String(128), "nullable": False}, # ISO 8601 datetime string
|
|
165
|
+
"duration_ms": {"type": BigInteger, "nullable": False},
|
|
166
|
+
"attributes": {"type": JSON, "nullable": True},
|
|
167
|
+
"created_at": {"type": lambda: String(128), "nullable": False, "index": True}, # ISO 8601 datetime string
|
|
168
|
+
}
|
|
159
169
|
|
|
160
|
-
|
|
170
|
+
|
|
171
|
+
def get_table_schema_definition(
|
|
172
|
+
table_type: str, traces_table_name: str = "agno_traces", db_schema: str = "agno"
|
|
173
|
+
) -> dict[str, Any]:
|
|
161
174
|
"""
|
|
162
175
|
Get the expected schema definition for the given table.
|
|
163
176
|
|
|
164
177
|
Args:
|
|
165
178
|
table_type (str): The type of table to get the schema for.
|
|
179
|
+
traces_table_name (str): The name of the traces table (used for spans foreign key).
|
|
180
|
+
db_schema (str): The database schema name (used for spans foreign key).
|
|
166
181
|
|
|
167
182
|
Returns:
|
|
168
183
|
Dict[str, Any]: Dictionary containing column definitions for the table
|
|
169
184
|
"""
|
|
185
|
+
# Handle spans table specially to resolve the foreign key reference
|
|
186
|
+
if table_type == "spans":
|
|
187
|
+
return _get_span_table_schema(traces_table_name, db_schema)
|
|
188
|
+
|
|
170
189
|
schemas = {
|
|
171
190
|
"sessions": SESSION_TABLE_SCHEMA,
|
|
172
191
|
"evals": EVAL_TABLE_SCHEMA,
|
|
@@ -176,7 +195,6 @@ def get_table_schema_definition(table_type: str) -> dict[str, Any]:
|
|
|
176
195
|
"culture": CULTURAL_KNOWLEDGE_TABLE_SCHEMA,
|
|
177
196
|
"versions": VERSIONS_TABLE_SCHEMA,
|
|
178
197
|
"traces": TRACE_TABLE_SCHEMA,
|
|
179
|
-
"spans": SPAN_TABLE_SCHEMA,
|
|
180
198
|
}
|
|
181
199
|
|
|
182
200
|
schema = schemas.get(table_type, {})
|