agno 2.1.0__py3-none-any.whl → 2.1.2__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 (53) hide show
  1. agno/agent/agent.py +13 -1
  2. agno/db/base.py +8 -4
  3. agno/db/dynamo/dynamo.py +69 -17
  4. agno/db/firestore/firestore.py +68 -29
  5. agno/db/gcs_json/gcs_json_db.py +68 -17
  6. agno/db/in_memory/in_memory_db.py +83 -14
  7. agno/db/json/json_db.py +79 -15
  8. agno/db/mongo/mongo.py +92 -74
  9. agno/db/mysql/mysql.py +17 -3
  10. agno/db/postgres/postgres.py +21 -3
  11. agno/db/redis/redis.py +38 -11
  12. agno/db/singlestore/singlestore.py +14 -3
  13. agno/db/sqlite/sqlite.py +34 -46
  14. agno/db/utils.py +50 -22
  15. agno/knowledge/knowledge.py +6 -0
  16. agno/knowledge/reader/field_labeled_csv_reader.py +294 -0
  17. agno/knowledge/reader/pdf_reader.py +28 -52
  18. agno/knowledge/reader/reader_factory.py +12 -0
  19. agno/memory/manager.py +12 -4
  20. agno/models/anthropic/claude.py +4 -1
  21. agno/models/aws/bedrock.py +52 -112
  22. agno/models/openai/responses.py +1 -1
  23. agno/os/app.py +24 -30
  24. agno/os/interfaces/__init__.py +1 -0
  25. agno/os/interfaces/a2a/__init__.py +3 -0
  26. agno/os/interfaces/a2a/a2a.py +42 -0
  27. agno/os/interfaces/a2a/router.py +252 -0
  28. agno/os/interfaces/a2a/utils.py +924 -0
  29. agno/os/interfaces/agui/agui.py +21 -5
  30. agno/os/interfaces/agui/router.py +12 -0
  31. agno/os/interfaces/base.py +4 -2
  32. agno/os/interfaces/slack/slack.py +13 -8
  33. agno/os/interfaces/whatsapp/whatsapp.py +12 -5
  34. agno/os/mcp.py +1 -1
  35. agno/os/router.py +39 -9
  36. agno/os/routers/memory/memory.py +5 -3
  37. agno/os/routers/memory/schemas.py +1 -0
  38. agno/os/utils.py +36 -10
  39. agno/run/base.py +2 -13
  40. agno/team/team.py +13 -1
  41. agno/tools/mcp.py +46 -1
  42. agno/utils/merge_dict.py +22 -1
  43. agno/utils/serialize.py +32 -0
  44. agno/utils/streamlit.py +1 -1
  45. agno/workflow/parallel.py +90 -14
  46. agno/workflow/step.py +30 -27
  47. agno/workflow/types.py +4 -6
  48. agno/workflow/workflow.py +5 -3
  49. {agno-2.1.0.dist-info → agno-2.1.2.dist-info}/METADATA +16 -14
  50. {agno-2.1.0.dist-info → agno-2.1.2.dist-info}/RECORD +53 -47
  51. {agno-2.1.0.dist-info → agno-2.1.2.dist-info}/WHEEL +0 -0
  52. {agno-2.1.0.dist-info → agno-2.1.2.dist-info}/licenses/LICENSE +0 -0
  53. {agno-2.1.0.dist-info → agno-2.1.2.dist-info}/top_level.txt +0 -0
agno/db/mysql/mysql.py CHANGED
@@ -917,9 +917,13 @@ class MySQLDb(BaseDb):
917
917
  ]
918
918
 
919
919
  # -- Memory methods --
920
- def delete_user_memory(self, memory_id: str):
920
+ def delete_user_memory(self, memory_id: str, user_id: Optional[str] = None):
921
921
  """Delete a user memory from the database.
922
922
 
923
+ Args:
924
+ memory_id (str): The ID of the memory to delete.
925
+ user_id (Optional[str]): The user ID to filter by. Defaults to None.
926
+
923
927
  Returns:
924
928
  bool: True if deletion was successful, False otherwise.
925
929
 
@@ -933,6 +937,8 @@ class MySQLDb(BaseDb):
933
937
 
934
938
  with self.Session() as sess, sess.begin():
935
939
  delete_stmt = table.delete().where(table.c.memory_id == memory_id)
940
+ if user_id is not None:
941
+ delete_stmt = delete_stmt.where(table.c.user_id == user_id)
936
942
  result = sess.execute(delete_stmt)
937
943
 
938
944
  success = result.rowcount > 0
@@ -944,11 +950,12 @@ class MySQLDb(BaseDb):
944
950
  except Exception as e:
945
951
  log_error(f"Error deleting user memory: {e}")
946
952
 
947
- def delete_user_memories(self, memory_ids: List[str]) -> None:
953
+ def delete_user_memories(self, memory_ids: List[str], user_id: Optional[str] = None) -> None:
948
954
  """Delete user memories from the database.
949
955
 
950
956
  Args:
951
957
  memory_ids (List[str]): The IDs of the memories to delete.
958
+ user_id (Optional[str]): The user ID to filter by. Defaults to None.
952
959
 
953
960
  Raises:
954
961
  Exception: If an error occurs during deletion.
@@ -960,6 +967,8 @@ class MySQLDb(BaseDb):
960
967
 
961
968
  with self.Session() as sess, sess.begin():
962
969
  delete_stmt = table.delete().where(table.c.memory_id.in_(memory_ids))
970
+ if user_id is not None:
971
+ delete_stmt = delete_stmt.where(table.c.user_id == user_id)
963
972
  result = sess.execute(delete_stmt)
964
973
  if result.rowcount == 0:
965
974
  log_debug(f"No user memories found with ids: {memory_ids}")
@@ -1002,12 +1011,15 @@ class MySQLDb(BaseDb):
1002
1011
  log_error(f"Exception reading from memory table: {e}")
1003
1012
  raise e
1004
1013
 
1005
- def get_user_memory(self, memory_id: str, deserialize: Optional[bool] = True) -> Optional[UserMemory]:
1014
+ def get_user_memory(
1015
+ self, memory_id: str, deserialize: Optional[bool] = True, user_id: Optional[str] = None
1016
+ ) -> Optional[UserMemory]:
1006
1017
  """Get a memory from the database.
1007
1018
 
1008
1019
  Args:
1009
1020
  memory_id (str): The ID of the memory to get.
1010
1021
  deserialize (Optional[bool]): Whether to serialize the memory. Defaults to True.
1022
+ user_id (Optional[str]): The user ID to filter by. Defaults to None.
1011
1023
 
1012
1024
  Returns:
1013
1025
  Union[UserMemory, Dict[str, Any], None]:
@@ -1024,6 +1036,8 @@ class MySQLDb(BaseDb):
1024
1036
 
1025
1037
  with self.Session() as sess, sess.begin():
1026
1038
  stmt = select(table).where(table.c.memory_id == memory_id)
1039
+ if user_id is not None:
1040
+ stmt = stmt.where(table.c.user_id == user_id)
1027
1041
 
1028
1042
  result = sess.execute(stmt).fetchone()
1029
1043
  if not result:
@@ -870,9 +870,13 @@ class PostgresDb(BaseDb):
870
870
  return []
871
871
 
872
872
  # -- Memory methods --
873
- def delete_user_memory(self, memory_id: str):
873
+ def delete_user_memory(self, memory_id: str, user_id: Optional[str] = None):
874
874
  """Delete a user memory from the database.
875
875
 
876
+ Args:
877
+ memory_id (str): The ID of the memory to delete.
878
+ user_id (Optional[str]): The ID of the user to filter by. Defaults to None.
879
+
876
880
  Returns:
877
881
  bool: True if deletion was successful, False otherwise.
878
882
 
@@ -886,6 +890,10 @@ class PostgresDb(BaseDb):
886
890
 
887
891
  with self.Session() as sess, sess.begin():
888
892
  delete_stmt = table.delete().where(table.c.memory_id == memory_id)
893
+
894
+ if user_id is not None:
895
+ delete_stmt = delete_stmt.where(table.c.user_id == user_id)
896
+
889
897
  result = sess.execute(delete_stmt)
890
898
 
891
899
  success = result.rowcount > 0
@@ -898,11 +906,12 @@ class PostgresDb(BaseDb):
898
906
  log_error(f"Error deleting user memory: {e}")
899
907
  raise e
900
908
 
901
- def delete_user_memories(self, memory_ids: List[str]) -> None:
909
+ def delete_user_memories(self, memory_ids: List[str], user_id: Optional[str] = None) -> None:
902
910
  """Delete user memories from the database.
903
911
 
904
912
  Args:
905
913
  memory_ids (List[str]): The IDs of the memories to delete.
914
+ user_id (Optional[str]): The ID of the user to filter by. Defaults to None.
906
915
 
907
916
  Raises:
908
917
  Exception: If an error occurs during deletion.
@@ -914,6 +923,10 @@ class PostgresDb(BaseDb):
914
923
 
915
924
  with self.Session() as sess, sess.begin():
916
925
  delete_stmt = table.delete().where(table.c.memory_id.in_(memory_ids))
926
+
927
+ if user_id is not None:
928
+ delete_stmt = delete_stmt.where(table.c.user_id == user_id)
929
+
917
930
  result = sess.execute(delete_stmt)
918
931
 
919
932
  if result.rowcount == 0:
@@ -938,6 +951,7 @@ class PostgresDb(BaseDb):
938
951
 
939
952
  with self.Session() as sess, sess.begin():
940
953
  stmt = select(func.json_array_elements_text(table.c.topics))
954
+
941
955
  result = sess.execute(stmt).fetchall()
942
956
 
943
957
  return list(set([record[0] for record in result]))
@@ -947,13 +961,14 @@ class PostgresDb(BaseDb):
947
961
  return []
948
962
 
949
963
  def get_user_memory(
950
- self, memory_id: str, deserialize: Optional[bool] = True
964
+ self, memory_id: str, deserialize: Optional[bool] = True, user_id: Optional[str] = None
951
965
  ) -> Optional[Union[UserMemory, Dict[str, Any]]]:
952
966
  """Get a memory from the database.
953
967
 
954
968
  Args:
955
969
  memory_id (str): The ID of the memory to get.
956
970
  deserialize (Optional[bool]): Whether to serialize the memory. Defaults to True.
971
+ user_id (Optional[str]): The ID of the user to filter by. Defaults to None.
957
972
 
958
973
  Returns:
959
974
  Union[UserMemory, Dict[str, Any], None]:
@@ -971,6 +986,9 @@ class PostgresDb(BaseDb):
971
986
  with self.Session() as sess, sess.begin():
972
987
  stmt = select(table).where(table.c.memory_id == memory_id)
973
988
 
989
+ if user_id is not None:
990
+ stmt = stmt.where(table.c.user_id == user_id)
991
+
974
992
  result = sess.execute(stmt).fetchone()
975
993
  if not result:
976
994
  return None
agno/db/redis/redis.py CHANGED
@@ -627,11 +627,12 @@ class RedisDb(BaseDb):
627
627
 
628
628
  # -- Memory methods --
629
629
 
630
- def delete_user_memory(self, memory_id: str):
630
+ def delete_user_memory(self, memory_id: str, user_id: Optional[str] = None):
631
631
  """Delete a user memory from Redis.
632
632
 
633
633
  Args:
634
634
  memory_id (str): The ID of the memory to delete.
635
+ user_id (Optional[str]): The ID of the user. If provided, verifies the memory belongs to this user before deleting.
635
636
 
636
637
  Returns:
637
638
  bool: True if the memory was deleted, False otherwise.
@@ -640,6 +641,16 @@ class RedisDb(BaseDb):
640
641
  Exception: If any error occurs while deleting the memory.
641
642
  """
642
643
  try:
644
+ # If user_id is provided, verify ownership before deleting
645
+ if user_id is not None:
646
+ memory = self._get_record("memories", memory_id)
647
+ if memory is None:
648
+ log_debug(f"No user memory found with id: {memory_id}")
649
+ return
650
+ if memory.get("user_id") != user_id:
651
+ log_debug(f"Memory {memory_id} does not belong to user {user_id}")
652
+ return
653
+
643
654
  if self._delete_record(
644
655
  "memories", memory_id, index_fields=["user_id", "agent_id", "team_id", "workflow_id"]
645
656
  ):
@@ -651,15 +662,25 @@ class RedisDb(BaseDb):
651
662
  log_error(f"Error deleting user memory: {e}")
652
663
  raise e
653
664
 
654
- def delete_user_memories(self, memory_ids: List[str]) -> None:
665
+ def delete_user_memories(self, memory_ids: List[str], user_id: Optional[str] = None) -> None:
655
666
  """Delete user memories from Redis.
656
667
 
657
668
  Args:
658
669
  memory_ids (List[str]): The IDs of the memories to delete.
670
+ user_id (Optional[str]): The ID of the user. If provided, only deletes memories belonging to this user.
659
671
  """
660
672
  try:
661
673
  # TODO: cant we optimize this?
662
674
  for memory_id in memory_ids:
675
+ # If user_id is provided, verify ownership before deleting
676
+ if user_id is not None:
677
+ memory = self._get_record("memories", memory_id)
678
+ if memory is None:
679
+ continue
680
+ if memory.get("user_id") != user_id:
681
+ log_debug(f"Memory {memory_id} does not belong to user {user_id}, skipping deletion")
682
+ continue
683
+
663
684
  self._delete_record(
664
685
  "memories",
665
686
  memory_id,
@@ -692,12 +713,14 @@ class RedisDb(BaseDb):
692
713
  raise e
693
714
 
694
715
  def get_user_memory(
695
- self, memory_id: str, deserialize: Optional[bool] = True
716
+ self, memory_id: str, deserialize: Optional[bool] = True, user_id: Optional[str] = None
696
717
  ) -> Optional[Union[UserMemory, Dict[str, Any]]]:
697
718
  """Get a memory from Redis.
698
719
 
699
720
  Args:
700
721
  memory_id (str): The ID of the memory to get.
722
+ deserialize (Optional[bool]): Whether to deserialize the memory. Defaults to True.
723
+ user_id (Optional[str]): The ID of the user. If provided, only returns the memory if it belongs to this user.
701
724
 
702
725
  Returns:
703
726
  Optional[UserMemory]: The memory data if found, None otherwise.
@@ -707,6 +730,10 @@ class RedisDb(BaseDb):
707
730
  if memory_raw is None:
708
731
  return None
709
732
 
733
+ # Filter by user_id if provided
734
+ if user_id is not None and memory_raw.get("user_id") != user_id:
735
+ return None
736
+
710
737
  if not deserialize:
711
738
  return memory_raw
712
739
 
@@ -812,21 +839,21 @@ class RedisDb(BaseDb):
812
839
  # Group by user_id
813
840
  user_stats = {}
814
841
  for memory in all_memories:
815
- user_id = memory.get("user_id")
816
- if user_id is None:
842
+ memory_user_id = memory.get("user_id")
843
+ if memory_user_id is None:
817
844
  continue
818
845
 
819
- if user_id not in user_stats:
820
- user_stats[user_id] = {
821
- "user_id": user_id,
846
+ if memory_user_id not in user_stats:
847
+ user_stats[memory_user_id] = {
848
+ "user_id": memory_user_id,
822
849
  "total_memories": 0,
823
850
  "last_memory_updated_at": 0,
824
851
  }
825
852
 
826
- user_stats[user_id]["total_memories"] += 1
853
+ user_stats[memory_user_id]["total_memories"] += 1
827
854
  updated_at = memory.get("updated_at", 0)
828
- if updated_at > user_stats[user_id]["last_memory_updated_at"]:
829
- user_stats[user_id]["last_memory_updated_at"] = updated_at
855
+ if updated_at > user_stats[memory_user_id]["last_memory_updated_at"]:
856
+ user_stats[memory_user_id]["last_memory_updated_at"] = updated_at
830
857
 
831
858
  stats_list = list(user_stats.values())
832
859
 
@@ -990,11 +990,12 @@ class SingleStoreDb(BaseDb):
990
990
  return []
991
991
 
992
992
  # -- Memory methods --
993
- def delete_user_memory(self, memory_id: str):
993
+ def delete_user_memory(self, memory_id: str, user_id: Optional[str] = None):
994
994
  """Delete a user memory from the database.
995
995
 
996
996
  Args:
997
997
  memory_id (str): The ID of the memory to delete.
998
+ user_id (Optional[str]): The ID of the user to filter by. Defaults to None.
998
999
 
999
1000
  Returns:
1000
1001
  bool: True if deletion was successful, False otherwise.
@@ -1009,6 +1010,8 @@ class SingleStoreDb(BaseDb):
1009
1010
 
1010
1011
  with self.Session() as sess, sess.begin():
1011
1012
  delete_stmt = table.delete().where(table.c.memory_id == memory_id)
1013
+ if user_id is not None:
1014
+ delete_stmt = delete_stmt.where(table.c.user_id == user_id)
1012
1015
  result = sess.execute(delete_stmt)
1013
1016
 
1014
1017
  success = result.rowcount > 0
@@ -1021,11 +1024,12 @@ class SingleStoreDb(BaseDb):
1021
1024
  log_error(f"Error deleting memory: {e}")
1022
1025
  raise e
1023
1026
 
1024
- def delete_user_memories(self, memory_ids: List[str]) -> None:
1027
+ def delete_user_memories(self, memory_ids: List[str], user_id: Optional[str] = None) -> None:
1025
1028
  """Delete user memories from the database.
1026
1029
 
1027
1030
  Args:
1028
1031
  memory_ids (List[str]): The IDs of the memories to delete.
1032
+ user_id (Optional[str]): The ID of the user to filter by. Defaults to None.
1029
1033
 
1030
1034
  Raises:
1031
1035
  Exception: If an error occurs during deletion.
@@ -1037,6 +1041,8 @@ class SingleStoreDb(BaseDb):
1037
1041
 
1038
1042
  with self.Session() as sess, sess.begin():
1039
1043
  delete_stmt = table.delete().where(table.c.memory_id.in_(memory_ids))
1044
+ if user_id is not None:
1045
+ delete_stmt = delete_stmt.where(table.c.user_id == user_id)
1040
1046
  result = sess.execute(delete_stmt)
1041
1047
  if result.rowcount == 0:
1042
1048
  log_debug(f"No memories found with ids: {memory_ids}")
@@ -1073,12 +1079,15 @@ class SingleStoreDb(BaseDb):
1073
1079
  log_error(f"Exception reading from memory table: {e}")
1074
1080
  raise e
1075
1081
 
1076
- def get_user_memory(self, memory_id: str, deserialize: Optional[bool] = True) -> Optional[UserMemory]:
1082
+ def get_user_memory(
1083
+ self, memory_id: str, deserialize: Optional[bool] = True, user_id: Optional[str] = None
1084
+ ) -> Optional[UserMemory]:
1077
1085
  """Get a memory from the database.
1078
1086
 
1079
1087
  Args:
1080
1088
  memory_id (str): The ID of the memory to get.
1081
1089
  deserialize (Optional[bool]): Whether to serialize the memory. Defaults to True.
1090
+ user_id (Optional[str]): The ID of the user to filter by. Defaults to None.
1082
1091
 
1083
1092
  Returns:
1084
1093
  Union[UserMemory, Dict[str, Any], None]:
@@ -1095,6 +1104,8 @@ class SingleStoreDb(BaseDb):
1095
1104
 
1096
1105
  with self.Session() as sess, sess.begin():
1097
1106
  stmt = select(table).where(table.c.memory_id == memory_id)
1107
+ if user_id is not None:
1108
+ stmt = stmt.where(table.c.user_id == user_id)
1098
1109
 
1099
1110
  result = sess.execute(stmt).fetchone()
1100
1111
  if not result:
agno/db/sqlite/sqlite.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import time
2
2
  from datetime import date, datetime, timedelta, timezone
3
3
  from pathlib import Path
4
- from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
4
+ from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, cast
5
5
  from uuid import uuid4
6
6
 
7
7
  from agno.db.base import BaseDb, SessionType
@@ -24,7 +24,7 @@ from agno.utils.log import log_debug, log_error, log_info, log_warning
24
24
  from agno.utils.string import generate_id
25
25
 
26
26
  try:
27
- from sqlalchemy import Column, MetaData, Table, and_, func, select, text, update
27
+ from sqlalchemy import Column, MetaData, Table, and_, func, select, text
28
28
  from sqlalchemy.dialects import sqlite
29
29
  from sqlalchemy.engine import Engine, create_engine
30
30
  from sqlalchemy.orm import scoped_session, sessionmaker
@@ -442,11 +442,7 @@ class SqliteDb(BaseDb):
442
442
  if end_timestamp is not None:
443
443
  stmt = stmt.where(table.c.created_at <= end_timestamp)
444
444
  if session_name is not None:
445
- stmt = stmt.where(
446
- func.coalesce(func.json_extract(table.c.session_data, "$.session_name"), "").like(
447
- f"%{session_name}%"
448
- )
449
- )
445
+ stmt = stmt.where(table.c.session_data.like(f"%{session_name}%"))
450
446
  if session_type is not None:
451
447
  stmt = stmt.where(table.c.session_type == session_type.value)
452
448
 
@@ -468,8 +464,10 @@ class SqliteDb(BaseDb):
468
464
  return [] if deserialize else ([], 0)
469
465
 
470
466
  sessions_raw = [deserialize_session_json_fields(dict(record._mapping)) for record in records]
471
- if not sessions_raw or not deserialize:
467
+ if not deserialize:
472
468
  return sessions_raw, total_count
469
+ if not sessions_raw:
470
+ return []
473
471
 
474
472
  if session_type == SessionType.AGENT:
475
473
  return [AgentSession.from_dict(record) for record in sessions_raw] # type: ignore
@@ -505,43 +503,20 @@ class SqliteDb(BaseDb):
505
503
  Exception: If an error occurs during renaming.
506
504
  """
507
505
  try:
508
- table = self._get_table(table_type="sessions")
509
- if table is None:
506
+ # Get the current session as a deserialized object
507
+ # Get the session record
508
+ session = self.get_session(session_id, session_type, deserialize=True)
509
+ if session is None:
510
510
  return None
511
511
 
512
- with self.Session() as sess, sess.begin():
513
- # Update session_name inside the session_data JSON field
514
- stmt = (
515
- update(table)
516
- .where(table.c.session_id == session_id)
517
- .values(session_data=func.json_set(table.c.session_data, "$.session_name", session_name))
518
- )
519
- result = sess.execute(stmt)
520
-
521
- # Check if any rows were affected
522
- if result.rowcount == 0:
523
- return None
524
-
525
- # Fetch the updated row
526
- select_stmt = select(table).where(table.c.session_id == session_id)
527
- row = sess.execute(select_stmt).fetchone()
528
-
529
- if not row:
530
- return None
531
-
532
- session_raw = deserialize_session_json_fields(dict(row._mapping))
533
- if not session_raw or not deserialize:
534
- return session_raw
512
+ session = cast(Session, session)
513
+ # Update the session name
514
+ if session.session_data is None:
515
+ session.session_data = {}
516
+ session.session_data["session_name"] = session_name
535
517
 
536
- # Return the appropriate session type
537
- if session_type == SessionType.AGENT:
538
- return AgentSession.from_dict(session_raw)
539
- elif session_type == SessionType.TEAM:
540
- return TeamSession.from_dict(session_raw)
541
- elif session_type == SessionType.WORKFLOW:
542
- return WorkflowSession.from_dict(session_raw)
543
- else:
544
- raise ValueError(f"Invalid session type: {session_type}")
518
+ # Upsert the updated session back to the database
519
+ return self.upsert_session(session, deserialize=deserialize)
545
520
 
546
521
  except Exception as e:
547
522
  log_error(f"Exception renaming session: {e}")
@@ -909,9 +884,13 @@ class SqliteDb(BaseDb):
909
884
 
910
885
  # -- Memory methods --
911
886
 
912
- def delete_user_memory(self, memory_id: str):
887
+ def delete_user_memory(self, memory_id: str, user_id: Optional[str] = None):
913
888
  """Delete a user memory from the database.
914
889
 
890
+ Args:
891
+ memory_id (str): The ID of the memory to delete.
892
+ user_id (Optional[str]): The user ID to filter by. Defaults to None.
893
+
915
894
  Returns:
916
895
  bool: True if deletion was successful, False otherwise.
917
896
 
@@ -925,6 +904,8 @@ class SqliteDb(BaseDb):
925
904
 
926
905
  with self.Session() as sess, sess.begin():
927
906
  delete_stmt = table.delete().where(table.c.memory_id == memory_id)
907
+ if user_id is not None:
908
+ delete_stmt = delete_stmt.where(table.c.user_id == user_id)
928
909
  result = sess.execute(delete_stmt)
929
910
 
930
911
  success = result.rowcount > 0
@@ -937,11 +918,12 @@ class SqliteDb(BaseDb):
937
918
  log_error(f"Error deleting user memory: {e}")
938
919
  raise e
939
920
 
940
- def delete_user_memories(self, memory_ids: List[str]) -> None:
921
+ def delete_user_memories(self, memory_ids: List[str], user_id: Optional[str] = None) -> None:
941
922
  """Delete user memories from the database.
942
923
 
943
924
  Args:
944
925
  memory_ids (List[str]): The IDs of the memories to delete.
926
+ user_id (Optional[str]): The user ID to filter by. Defaults to None.
945
927
 
946
928
  Raises:
947
929
  Exception: If an error occurs during deletion.
@@ -953,6 +935,8 @@ class SqliteDb(BaseDb):
953
935
 
954
936
  with self.Session() as sess, sess.begin():
955
937
  delete_stmt = table.delete().where(table.c.memory_id.in_(memory_ids))
938
+ if user_id is not None:
939
+ delete_stmt = delete_stmt.where(table.c.user_id == user_id)
956
940
  result = sess.execute(delete_stmt)
957
941
  if result.rowcount == 0:
958
942
  log_debug(f"No user memories found with ids: {memory_ids}")
@@ -973,7 +957,8 @@ class SqliteDb(BaseDb):
973
957
  return []
974
958
 
975
959
  with self.Session() as sess, sess.begin():
976
- stmt = select(func.json_array_elements_text(table.c.topics))
960
+ # Select topics from all results
961
+ stmt = select(func.json_array_elements_text(table.c.topics)).select_from(table)
977
962
  result = sess.execute(stmt).fetchall()
978
963
 
979
964
  return list(set([record[0] for record in result]))
@@ -983,13 +968,14 @@ class SqliteDb(BaseDb):
983
968
  raise e
984
969
 
985
970
  def get_user_memory(
986
- self, memory_id: str, deserialize: Optional[bool] = True
971
+ self, memory_id: str, deserialize: Optional[bool] = True, user_id: Optional[str] = None
987
972
  ) -> Optional[Union[UserMemory, Dict[str, Any]]]:
988
973
  """Get a memory from the database.
989
974
 
990
975
  Args:
991
976
  memory_id (str): The ID of the memory to get.
992
977
  deserialize (Optional[bool]): Whether to serialize the memory. Defaults to True.
978
+ user_id (Optional[str]): The user ID to filter by. Defaults to None.
993
979
 
994
980
  Returns:
995
981
  Optional[Union[UserMemory, Dict[str, Any]]]:
@@ -1006,6 +992,8 @@ class SqliteDb(BaseDb):
1006
992
 
1007
993
  with self.Session() as sess, sess.begin():
1008
994
  stmt = select(table).where(table.c.memory_id == memory_id)
995
+ if user_id is not None:
996
+ stmt = stmt.where(table.c.user_id == user_id)
1009
997
  result = sess.execute(stmt).fetchone()
1010
998
  if result is None:
1011
999
  return None
agno/db/utils.py CHANGED
@@ -4,7 +4,6 @@ import json
4
4
  from datetime import date, datetime
5
5
  from uuid import UUID
6
6
 
7
- from agno.db.base import SessionType
8
7
  from agno.models.message import Message
9
8
  from agno.models.metrics import Metrics
10
9
 
@@ -55,34 +54,63 @@ def serialize_session_json_fields(session: dict) -> dict:
55
54
 
56
55
 
57
56
  def deserialize_session_json_fields(session: dict) -> dict:
58
- """Deserialize all JSON fields in the given Session dictionary.
57
+ """Deserialize JSON fields in the given Session dictionary.
59
58
 
60
59
  Args:
61
60
  session (dict): The dictionary to deserialize.
62
61
 
63
62
  Returns:
64
- dict: The dictionary with JSON fields deserialized.
63
+ dict: The dictionary with JSON string fields deserialized to objects.
65
64
  """
66
- if session.get("agent_data") is not None:
67
- session["agent_data"] = json.loads(session["agent_data"])
68
- if session.get("team_data") is not None:
69
- session["team_data"] = json.loads(session["team_data"])
70
- if session.get("workflow_data") is not None:
71
- session["workflow_data"] = json.loads(session["workflow_data"])
72
- if session.get("metadata") is not None:
73
- session["metadata"] = json.loads(session["metadata"])
74
- if session.get("chat_history") is not None:
75
- session["chat_history"] = json.loads(session["chat_history"])
76
- if session.get("summary") is not None:
77
- session["summary"] = json.loads(session["summary"])
65
+ from agno.utils.log import log_warning
66
+
67
+ if session.get("agent_data") is not None and isinstance(session["agent_data"], str):
68
+ try:
69
+ session["agent_data"] = json.loads(session["agent_data"])
70
+ except (json.JSONDecodeError, TypeError) as e:
71
+ log_warning(f"Warning: Could not parse agent_data as JSON, keeping as string: {e}")
72
+
73
+ if session.get("team_data") is not None and isinstance(session["team_data"], str):
74
+ try:
75
+ session["team_data"] = json.loads(session["team_data"])
76
+ except (json.JSONDecodeError, TypeError) as e:
77
+ log_warning(f"Warning: Could not parse team_data as JSON, keeping as string: {e}")
78
+
79
+ if session.get("workflow_data") is not None and isinstance(session["workflow_data"], str):
80
+ try:
81
+ session["workflow_data"] = json.loads(session["workflow_data"])
82
+ except (json.JSONDecodeError, TypeError) as e:
83
+ log_warning(f"Warning: Could not parse workflow_data as JSON, keeping as string: {e}")
84
+
85
+ if session.get("metadata") is not None and isinstance(session["metadata"], str):
86
+ try:
87
+ session["metadata"] = json.loads(session["metadata"])
88
+ except (json.JSONDecodeError, TypeError) as e:
89
+ log_warning(f"Warning: Could not parse metadata as JSON, keeping as string: {e}")
90
+
91
+ if session.get("chat_history") is not None and isinstance(session["chat_history"], str):
92
+ try:
93
+ session["chat_history"] = json.loads(session["chat_history"])
94
+ except (json.JSONDecodeError, TypeError) as e:
95
+ log_warning(f"Warning: Could not parse chat_history as JSON, keeping as string: {e}")
96
+
97
+ if session.get("summary") is not None and isinstance(session["summary"], str):
98
+ try:
99
+ session["summary"] = json.loads(session["summary"])
100
+ except (json.JSONDecodeError, TypeError) as e:
101
+ log_warning(f"Warning: Could not parse summary as JSON, keeping as string: {e}")
102
+
78
103
  if session.get("session_data") is not None and isinstance(session["session_data"], str):
79
- session["session_data"] = json.loads(session["session_data"])
80
- if session.get("runs") is not None:
81
- if session["session_type"] == SessionType.AGENT.value:
82
- session["runs"] = json.loads(session["runs"])
83
- if session["session_type"] == SessionType.TEAM.value:
84
- session["runs"] = json.loads(session["runs"])
85
- if session["session_type"] == SessionType.WORKFLOW.value:
104
+ try:
105
+ session["session_data"] = json.loads(session["session_data"])
106
+ except (json.JSONDecodeError, TypeError) as e:
107
+ log_warning(f"Warning: Could not parse session_data as JSON, keeping as string: {e}")
108
+
109
+ # Handle runs field with session type checking
110
+ if session.get("runs") is not None and isinstance(session["runs"], str):
111
+ try:
86
112
  session["runs"] = json.loads(session["runs"])
113
+ except (json.JSONDecodeError, TypeError) as e:
114
+ log_warning(f"Warning: Could not parse runs as JSON, keeping as string: {e}")
87
115
 
88
116
  return session
@@ -1357,6 +1357,12 @@ class Knowledge:
1357
1357
  log_error(f"Error searching for documents: {e}")
1358
1358
  return []
1359
1359
 
1360
+ def get_valid_filters(self) -> Set[str]:
1361
+ if self.valid_metadata_filters is None:
1362
+ self.valid_metadata_filters = set()
1363
+ self.valid_metadata_filters.update(self._get_filters_from_db)
1364
+ return self.valid_metadata_filters
1365
+
1360
1366
  def validate_filters(self, filters: Optional[Dict[str, Any]]) -> Tuple[Dict[str, Any], List[str]]:
1361
1367
  if self.valid_metadata_filters is None:
1362
1368
  self.valid_metadata_filters = set()