agno 2.0.8__py3-none-any.whl → 2.0.9__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 (45) hide show
  1. agno/agent/agent.py +2 -2
  2. agno/db/base.py +14 -0
  3. agno/db/dynamo/dynamo.py +107 -27
  4. agno/db/firestore/firestore.py +109 -33
  5. agno/db/gcs_json/gcs_json_db.py +100 -20
  6. agno/db/in_memory/in_memory_db.py +95 -20
  7. agno/db/json/json_db.py +101 -21
  8. agno/db/migrations/v1_to_v2.py +181 -35
  9. agno/db/mongo/mongo.py +251 -26
  10. agno/db/mysql/mysql.py +307 -6
  11. agno/db/postgres/postgres.py +279 -33
  12. agno/db/redis/redis.py +99 -22
  13. agno/db/singlestore/singlestore.py +319 -38
  14. agno/db/sqlite/sqlite.py +339 -23
  15. agno/models/anthropic/claude.py +0 -20
  16. agno/models/huggingface/huggingface.py +2 -1
  17. agno/models/ollama/chat.py +28 -2
  18. agno/models/openai/chat.py +7 -0
  19. agno/models/openai/responses.py +8 -8
  20. agno/os/interfaces/base.py +2 -0
  21. agno/os/interfaces/slack/router.py +50 -10
  22. agno/os/interfaces/slack/slack.py +6 -4
  23. agno/os/interfaces/whatsapp/router.py +7 -4
  24. agno/os/router.py +18 -0
  25. agno/os/utils.py +2 -2
  26. agno/reasoning/azure_ai_foundry.py +2 -2
  27. agno/reasoning/deepseek.py +2 -2
  28. agno/reasoning/groq.py +2 -2
  29. agno/reasoning/ollama.py +2 -2
  30. agno/reasoning/openai.py +2 -2
  31. agno/run/base.py +15 -2
  32. agno/team/team.py +0 -7
  33. agno/tools/mcp_toolbox.py +284 -0
  34. agno/tools/scrapegraph.py +58 -31
  35. agno/tools/whatsapp.py +1 -1
  36. agno/utils/print_response/agent.py +2 -2
  37. agno/utils/print_response/team.py +6 -6
  38. agno/utils/reasoning.py +22 -1
  39. agno/utils/string.py +9 -0
  40. agno/workflow/workflow.py +0 -1
  41. {agno-2.0.8.dist-info → agno-2.0.9.dist-info}/METADATA +4 -1
  42. {agno-2.0.8.dist-info → agno-2.0.9.dist-info}/RECORD +45 -44
  43. {agno-2.0.8.dist-info → agno-2.0.9.dist-info}/WHEEL +0 -0
  44. {agno-2.0.8.dist-info → agno-2.0.9.dist-info}/licenses/LICENSE +0 -0
  45. {agno-2.0.8.dist-info → agno-2.0.9.dist-info}/top_level.txt +0 -0
agno/db/mongo/mongo.py CHANGED
@@ -223,7 +223,7 @@ class MongoDb(BaseDb):
223
223
 
224
224
  except Exception as e:
225
225
  log_error(f"Error deleting session: {e}")
226
- return False
226
+ raise e
227
227
 
228
228
  def delete_sessions(self, session_ids: List[str]) -> None:
229
229
  """Delete multiple sessions from the database.
@@ -241,6 +241,7 @@ class MongoDb(BaseDb):
241
241
 
242
242
  except Exception as e:
243
243
  log_error(f"Error deleting sessions: {e}")
244
+ raise e
244
245
 
245
246
  def get_session(
246
247
  self,
@@ -296,7 +297,7 @@ class MongoDb(BaseDb):
296
297
 
297
298
  except Exception as e:
298
299
  log_error(f"Exception reading session: {e}")
299
- return None
300
+ raise e
300
301
 
301
302
  def get_sessions(
302
303
  self,
@@ -409,7 +410,7 @@ class MongoDb(BaseDb):
409
410
 
410
411
  except Exception as e:
411
412
  log_error(f"Exception reading sessions: {e}")
412
- return [] if deserialize else ([], 0)
413
+ raise e
413
414
 
414
415
  def rename_session(
415
416
  self, session_id: str, session_type: SessionType, session_name: str, deserialize: Optional[bool] = True
@@ -467,7 +468,7 @@ class MongoDb(BaseDb):
467
468
 
468
469
  except Exception as e:
469
470
  log_error(f"Exception renaming session: {e}")
470
- return None
471
+ raise e
471
472
 
472
473
  def upsert_session(
473
474
  self, session: Session, deserialize: Optional[bool] = True
@@ -585,7 +586,145 @@ class MongoDb(BaseDb):
585
586
 
586
587
  except Exception as e:
587
588
  log_error(f"Exception upserting session: {e}")
588
- return None
589
+ raise e
590
+
591
+ def upsert_sessions(
592
+ self, sessions: List[Session], deserialize: Optional[bool] = True
593
+ ) -> List[Union[Session, Dict[str, Any]]]:
594
+ """
595
+ Bulk upsert multiple sessions for improved performance on large datasets.
596
+
597
+ Args:
598
+ sessions (List[Session]): List of sessions to upsert.
599
+ deserialize (Optional[bool]): Whether to deserialize the sessions. Defaults to True.
600
+
601
+ Returns:
602
+ List[Union[Session, Dict[str, Any]]]: List of upserted sessions.
603
+
604
+ Raises:
605
+ Exception: If an error occurs during bulk upsert.
606
+ """
607
+ if not sessions:
608
+ return []
609
+
610
+ try:
611
+ collection = self._get_collection(table_type="sessions", create_collection_if_not_found=True)
612
+ if collection is None:
613
+ log_info("Sessions collection not available, falling back to individual upserts")
614
+ return [
615
+ result
616
+ for session in sessions
617
+ if session is not None
618
+ for result in [self.upsert_session(session, deserialize=deserialize)]
619
+ if result is not None
620
+ ]
621
+
622
+ from pymongo import ReplaceOne
623
+
624
+ operations = []
625
+ results: List[Union[Session, Dict[str, Any]]] = []
626
+
627
+ for session in sessions:
628
+ if session is None:
629
+ continue
630
+
631
+ serialized_session_dict = serialize_session_json_fields(session.to_dict())
632
+
633
+ if isinstance(session, AgentSession):
634
+ record = {
635
+ "session_id": serialized_session_dict.get("session_id"),
636
+ "session_type": SessionType.AGENT.value,
637
+ "agent_id": serialized_session_dict.get("agent_id"),
638
+ "user_id": serialized_session_dict.get("user_id"),
639
+ "runs": serialized_session_dict.get("runs"),
640
+ "agent_data": serialized_session_dict.get("agent_data"),
641
+ "session_data": serialized_session_dict.get("session_data"),
642
+ "summary": serialized_session_dict.get("summary"),
643
+ "metadata": serialized_session_dict.get("metadata"),
644
+ "created_at": serialized_session_dict.get("created_at"),
645
+ "updated_at": int(time.time()),
646
+ }
647
+ elif isinstance(session, TeamSession):
648
+ record = {
649
+ "session_id": serialized_session_dict.get("session_id"),
650
+ "session_type": SessionType.TEAM.value,
651
+ "team_id": serialized_session_dict.get("team_id"),
652
+ "user_id": serialized_session_dict.get("user_id"),
653
+ "runs": serialized_session_dict.get("runs"),
654
+ "team_data": serialized_session_dict.get("team_data"),
655
+ "session_data": serialized_session_dict.get("session_data"),
656
+ "summary": serialized_session_dict.get("summary"),
657
+ "metadata": serialized_session_dict.get("metadata"),
658
+ "created_at": serialized_session_dict.get("created_at"),
659
+ "updated_at": int(time.time()),
660
+ }
661
+ elif isinstance(session, WorkflowSession):
662
+ record = {
663
+ "session_id": serialized_session_dict.get("session_id"),
664
+ "session_type": SessionType.WORKFLOW.value,
665
+ "workflow_id": serialized_session_dict.get("workflow_id"),
666
+ "user_id": serialized_session_dict.get("user_id"),
667
+ "runs": serialized_session_dict.get("runs"),
668
+ "workflow_data": serialized_session_dict.get("workflow_data"),
669
+ "session_data": serialized_session_dict.get("session_data"),
670
+ "summary": serialized_session_dict.get("summary"),
671
+ "metadata": serialized_session_dict.get("metadata"),
672
+ "created_at": serialized_session_dict.get("created_at"),
673
+ "updated_at": int(time.time()),
674
+ }
675
+ else:
676
+ continue
677
+
678
+ operations.append(
679
+ ReplaceOne(filter={"session_id": record["session_id"]}, replacement=record, upsert=True)
680
+ )
681
+
682
+ if operations:
683
+ # Execute bulk write
684
+ collection.bulk_write(operations)
685
+
686
+ # Fetch the results
687
+ session_ids = [session.session_id for session in sessions if session and session.session_id]
688
+ cursor = collection.find({"session_id": {"$in": session_ids}})
689
+
690
+ for doc in cursor:
691
+ session_dict = deserialize_session_json_fields(doc)
692
+
693
+ if deserialize:
694
+ session_type = doc.get("session_type")
695
+ if session_type == SessionType.AGENT.value:
696
+ deserialized_agent_session = AgentSession.from_dict(session_dict)
697
+ if deserialized_agent_session is None:
698
+ continue
699
+ results.append(deserialized_agent_session)
700
+
701
+ elif session_type == SessionType.TEAM.value:
702
+ deserialized_team_session = TeamSession.from_dict(session_dict)
703
+ if deserialized_team_session is None:
704
+ continue
705
+ results.append(deserialized_team_session)
706
+
707
+ elif session_type == SessionType.WORKFLOW.value:
708
+ deserialized_workflow_session = WorkflowSession.from_dict(session_dict)
709
+ if deserialized_workflow_session is None:
710
+ continue
711
+ results.append(deserialized_workflow_session)
712
+ else:
713
+ results.append(session_dict)
714
+
715
+ return results
716
+
717
+ except Exception as e:
718
+ log_error(f"Exception during bulk session upsert, falling back to individual upserts: {e}")
719
+
720
+ # Fallback to individual upserts
721
+ return [
722
+ result
723
+ for session in sessions
724
+ if session is not None
725
+ for result in [self.upsert_session(session, deserialize=deserialize)]
726
+ if result is not None
727
+ ]
589
728
 
590
729
  # -- Memory methods --
591
730
 
@@ -616,6 +755,7 @@ class MongoDb(BaseDb):
616
755
 
617
756
  except Exception as e:
618
757
  log_error(f"Error deleting memory: {e}")
758
+ raise e
619
759
 
620
760
  def delete_user_memories(self, memory_ids: List[str]) -> None:
621
761
  """Delete user memories from the database.
@@ -638,6 +778,7 @@ class MongoDb(BaseDb):
638
778
 
639
779
  except Exception as e:
640
780
  log_error(f"Error deleting memories: {e}")
781
+ raise e
641
782
 
642
783
  def get_all_memory_topics(self) -> List[str]:
643
784
  """Get all memory topics from the database.
@@ -658,7 +799,7 @@ class MongoDb(BaseDb):
658
799
 
659
800
  except Exception as e:
660
801
  log_error(f"Exception reading from collection: {e}")
661
- return []
802
+ raise e
662
803
 
663
804
  def get_user_memory(self, memory_id: str, deserialize: Optional[bool] = True) -> Optional[UserMemory]:
664
805
  """Get a memory from the database.
@@ -690,7 +831,7 @@ class MongoDb(BaseDb):
690
831
 
691
832
  except Exception as e:
692
833
  log_error(f"Exception reading from collection: {e}")
693
- return None
834
+ raise e
694
835
 
695
836
  def get_user_memories(
696
837
  self,
@@ -769,7 +910,7 @@ class MongoDb(BaseDb):
769
910
 
770
911
  except Exception as e:
771
912
  log_error(f"Exception reading from collection: {e}")
772
- return []
913
+ raise e
773
914
 
774
915
  def get_user_memory_stats(
775
916
  self,
@@ -831,7 +972,7 @@ class MongoDb(BaseDb):
831
972
 
832
973
  except Exception as e:
833
974
  log_error(f"Exception getting user memory stats: {e}")
834
- return [], 0
975
+ raise e
835
976
 
836
977
  def upsert_user_memory(
837
978
  self, memory: UserMemory, deserialize: Optional[bool] = True
@@ -882,7 +1023,92 @@ class MongoDb(BaseDb):
882
1023
 
883
1024
  except Exception as e:
884
1025
  log_error(f"Exception upserting user memory: {e}")
885
- return None
1026
+ raise e
1027
+
1028
+ def upsert_memories(
1029
+ self, memories: List[UserMemory], deserialize: Optional[bool] = True
1030
+ ) -> List[Union[UserMemory, Dict[str, Any]]]:
1031
+ """
1032
+ Bulk upsert multiple user memories for improved performance on large datasets.
1033
+
1034
+ Args:
1035
+ memories (List[UserMemory]): List of memories to upsert.
1036
+ deserialize (Optional[bool]): Whether to deserialize the memories. Defaults to True.
1037
+
1038
+ Returns:
1039
+ List[Union[UserMemory, Dict[str, Any]]]: List of upserted memories.
1040
+
1041
+ Raises:
1042
+ Exception: If an error occurs during bulk upsert.
1043
+ """
1044
+ if not memories:
1045
+ return []
1046
+
1047
+ try:
1048
+ collection = self._get_collection(table_type="memories", create_collection_if_not_found=True)
1049
+ if collection is None:
1050
+ log_info("Memories collection not available, falling back to individual upserts")
1051
+ return [
1052
+ result
1053
+ for memory in memories
1054
+ if memory is not None
1055
+ for result in [self.upsert_user_memory(memory, deserialize=deserialize)]
1056
+ if result is not None
1057
+ ]
1058
+
1059
+ from pymongo import ReplaceOne
1060
+
1061
+ operations = []
1062
+ results: List[Union[UserMemory, Dict[str, Any]]] = []
1063
+
1064
+ for memory in memories:
1065
+ if memory is None:
1066
+ continue
1067
+
1068
+ if memory.memory_id is None:
1069
+ memory.memory_id = str(uuid4())
1070
+
1071
+ record = {
1072
+ "user_id": memory.user_id,
1073
+ "agent_id": memory.agent_id,
1074
+ "team_id": memory.team_id,
1075
+ "memory_id": memory.memory_id,
1076
+ "memory": memory.memory,
1077
+ "topics": memory.topics,
1078
+ "updated_at": int(time.time()),
1079
+ }
1080
+
1081
+ operations.append(ReplaceOne(filter={"memory_id": memory.memory_id}, replacement=record, upsert=True))
1082
+
1083
+ if operations:
1084
+ # Execute bulk write
1085
+ collection.bulk_write(operations)
1086
+
1087
+ # Fetch the results
1088
+ memory_ids = [memory.memory_id for memory in memories if memory and memory.memory_id]
1089
+ cursor = collection.find({"memory_id": {"$in": memory_ids}})
1090
+
1091
+ for doc in cursor:
1092
+ if deserialize:
1093
+ # Remove MongoDB's _id field before creating UserMemory object
1094
+ doc_filtered = {k: v for k, v in doc.items() if k != "_id"}
1095
+ results.append(UserMemory.from_dict(doc_filtered))
1096
+ else:
1097
+ results.append(doc)
1098
+
1099
+ return results
1100
+
1101
+ except Exception as e:
1102
+ log_error(f"Exception during bulk memory upsert, falling back to individual upserts: {e}")
1103
+
1104
+ # Fallback to individual upserts
1105
+ return [
1106
+ result
1107
+ for memory in memories
1108
+ if memory is not None
1109
+ for result in [self.upsert_user_memory(memory, deserialize=deserialize)]
1110
+ if result is not None
1111
+ ]
886
1112
 
887
1113
  def clear_memories(self) -> None:
888
1114
  """Delete all memories from the database.
@@ -898,9 +1124,8 @@ class MongoDb(BaseDb):
898
1124
  collection.delete_many({})
899
1125
 
900
1126
  except Exception as e:
901
- from agno.utils.log import log_warning
902
-
903
- log_warning(f"Exception deleting all memories: {e}")
1127
+ log_error(f"Exception deleting all memories: {e}")
1128
+ raise e
904
1129
 
905
1130
  # -- Metrics methods --
906
1131
 
@@ -1052,7 +1277,7 @@ class MongoDb(BaseDb):
1052
1277
 
1053
1278
  except Exception as e:
1054
1279
  log_error(f"Error getting metrics: {e}")
1055
- return [], None
1280
+ raise e
1056
1281
 
1057
1282
  # -- Knowledge methods --
1058
1283
 
@@ -1076,7 +1301,7 @@ class MongoDb(BaseDb):
1076
1301
 
1077
1302
  except Exception as e:
1078
1303
  log_error(f"Error deleting knowledge content: {e}")
1079
- raise
1304
+ raise e
1080
1305
 
1081
1306
  def get_knowledge_content(self, id: str) -> Optional[KnowledgeRow]:
1082
1307
  """Get a knowledge row from the database.
@@ -1103,7 +1328,7 @@ class MongoDb(BaseDb):
1103
1328
 
1104
1329
  except Exception as e:
1105
1330
  log_error(f"Error getting knowledge content: {e}")
1106
- return None
1331
+ raise e
1107
1332
 
1108
1333
  def get_knowledge_contents(
1109
1334
  self,
@@ -1158,7 +1383,7 @@ class MongoDb(BaseDb):
1158
1383
 
1159
1384
  except Exception as e:
1160
1385
  log_error(f"Error getting knowledge contents: {e}")
1161
- return [], 0
1386
+ raise e
1162
1387
 
1163
1388
  def upsert_knowledge_content(self, knowledge_row: KnowledgeRow):
1164
1389
  """Upsert knowledge content in the database.
@@ -1184,7 +1409,7 @@ class MongoDb(BaseDb):
1184
1409
 
1185
1410
  except Exception as e:
1186
1411
  log_error(f"Error upserting knowledge content: {e}")
1187
- return None
1412
+ raise e
1188
1413
 
1189
1414
  # -- Eval methods --
1190
1415
 
@@ -1208,7 +1433,7 @@ class MongoDb(BaseDb):
1208
1433
 
1209
1434
  except Exception as e:
1210
1435
  log_error(f"Error creating eval run: {e}")
1211
- return None
1436
+ raise e
1212
1437
 
1213
1438
  def delete_eval_run(self, eval_run_id: str) -> None:
1214
1439
  """Delete an eval run from the database."""
@@ -1226,7 +1451,7 @@ class MongoDb(BaseDb):
1226
1451
 
1227
1452
  except Exception as e:
1228
1453
  log_error(f"Error deleting eval run {eval_run_id}: {e}")
1229
- raise
1454
+ raise e
1230
1455
 
1231
1456
  def delete_eval_runs(self, eval_run_ids: List[str]) -> None:
1232
1457
  """Delete multiple eval runs from the database."""
@@ -1244,7 +1469,7 @@ class MongoDb(BaseDb):
1244
1469
 
1245
1470
  except Exception as e:
1246
1471
  log_error(f"Error deleting eval runs {eval_run_ids}: {e}")
1247
- raise
1472
+ raise e
1248
1473
 
1249
1474
  def get_eval_run_raw(self, eval_run_id: str) -> Optional[Dict[str, Any]]:
1250
1475
  """Get an eval run from the database as a raw dictionary."""
@@ -1258,7 +1483,7 @@ class MongoDb(BaseDb):
1258
1483
 
1259
1484
  except Exception as e:
1260
1485
  log_error(f"Exception getting eval run {eval_run_id}: {e}")
1261
- return None
1486
+ raise e
1262
1487
 
1263
1488
  def get_eval_run(self, eval_run_id: str, deserialize: Optional[bool] = True) -> Optional[EvalRunRecord]:
1264
1489
  """Get an eval run from the database.
@@ -1292,7 +1517,7 @@ class MongoDb(BaseDb):
1292
1517
 
1293
1518
  except Exception as e:
1294
1519
  log_error(f"Exception getting eval run {eval_run_id}: {e}")
1295
- return None
1520
+ raise e
1296
1521
 
1297
1522
  def get_eval_runs(
1298
1523
  self,
@@ -1386,8 +1611,8 @@ class MongoDb(BaseDb):
1386
1611
  return [EvalRunRecord.model_validate(row) for row in records]
1387
1612
 
1388
1613
  except Exception as e:
1389
- log_debug(f"Exception getting eval runs: {e}")
1390
- return [] if deserialize else ([], 0)
1614
+ log_error(f"Exception getting eval runs: {e}")
1615
+ raise e
1391
1616
 
1392
1617
  def rename_eval_run(
1393
1618
  self, eval_run_id: str, name: str, deserialize: Optional[bool] = True
@@ -1425,7 +1650,7 @@ class MongoDb(BaseDb):
1425
1650
 
1426
1651
  except Exception as e:
1427
1652
  log_error(f"Error updating eval run name {eval_run_id}: {e}")
1428
- raise
1653
+ raise e
1429
1654
 
1430
1655
  def migrate_table_from_v1_to_v2(self, v1_db_schema: str, v1_table_name: str, v1_table_type: str):
1431
1656
  """Migrate all content in the given collection to the right v2 collection"""