agno 2.3.19__py3-none-any.whl → 2.3.20__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/db/dynamo/utils.py CHANGED
@@ -8,6 +8,7 @@ from agno.db.base import SessionType
8
8
  from agno.db.schemas.culture import CulturalKnowledge
9
9
  from agno.db.schemas.evals import EvalRunRecord
10
10
  from agno.db.schemas.knowledge import KnowledgeRow
11
+ from agno.db.utils import get_sort_value
11
12
  from agno.session import Session
12
13
  from agno.utils.log import log_debug, log_error, log_info
13
14
 
@@ -174,13 +175,35 @@ def apply_pagination(
174
175
  def apply_sorting(
175
176
  items: List[Dict[str, Any]], sort_by: Optional[str] = None, sort_order: Optional[str] = None
176
177
  ) -> List[Dict[str, Any]]:
177
- """Apply sorting to a list of items."""
178
+ """Apply sorting to a list of items.
179
+
180
+ Args:
181
+ items: The list of dictionaries to sort
182
+ sort_by: The field to sort by (defaults to 'created_at')
183
+ sort_order: The sort order ('asc' or 'desc')
184
+
185
+ Returns:
186
+ The sorted list
187
+
188
+ Note:
189
+ If sorting by "updated_at", will fallback to "created_at" in case of None.
190
+ """
191
+ if not items:
192
+ return items
193
+
178
194
  if sort_by is None:
179
195
  sort_by = "created_at"
180
196
 
181
- reverse = sort_order == "desc"
197
+ is_descending = sort_order == "desc"
198
+
199
+ # Sort using the helper function that handles updated_at -> created_at fallback
200
+ sorted_records = sorted(
201
+ items,
202
+ key=lambda x: (get_sort_value(x, sort_by) is None, get_sort_value(x, sort_by)),
203
+ reverse=is_descending,
204
+ )
182
205
 
183
- return sorted(items, key=lambda x: x.get(sort_by, ""), reverse=reverse)
206
+ return sorted_records
184
207
 
185
208
 
186
209
  # -- Session utils --
@@ -8,6 +8,7 @@ from uuid import uuid4
8
8
 
9
9
  from agno.db.firestore.schemas import get_collection_indexes
10
10
  from agno.db.schemas.culture import CulturalKnowledge
11
+ from agno.db.utils import get_sort_value
11
12
  from agno.utils.log import log_debug, log_error, log_info, log_warning
12
13
 
13
14
  try:
@@ -126,19 +127,33 @@ def apply_pagination(query, limit: Optional[int] = None, page: Optional[int] = N
126
127
  def apply_sorting_to_records(
127
128
  records: List[Dict[str, Any]], sort_by: Optional[str] = None, sort_order: Optional[str] = None
128
129
  ) -> List[Dict[str, Any]]:
129
- """Apply sorting to in-memory records (for cases where Firestore query sorting isn't possible)."""
130
- if sort_by is None:
131
- return records
130
+ """Apply sorting to in-memory records (for cases where Firestore query sorting isn't possible).
131
+
132
+ Args:
133
+ records: The list of dictionaries to sort
134
+ sort_by: The field to sort by
135
+ sort_order: The sort order ('asc' or 'desc')
132
136
 
133
- def get_sort_key(record):
134
- value = record.get(sort_by, 0)
135
- if value is None:
136
- return 0 if records and isinstance(records[0].get(sort_by, 0), (int, float)) else ""
137
- return value
137
+ Returns:
138
+ The sorted list
139
+
140
+ Note:
141
+ If sorting by "updated_at", will fallback to "created_at" in case of None.
142
+ """
143
+ if sort_by is None or not records:
144
+ return records
138
145
 
139
146
  try:
140
- is_reverse = sort_order == "desc"
141
- return sorted(records, key=get_sort_key, reverse=is_reverse)
147
+ is_descending = sort_order == "desc"
148
+
149
+ # Sort using the helper function that handles updated_at -> created_at fallback
150
+ sorted_records = sorted(
151
+ records,
152
+ key=lambda x: (get_sort_value(x, sort_by) is None, get_sort_value(x, sort_by)),
153
+ reverse=is_descending,
154
+ )
155
+
156
+ return sorted_records
142
157
  except Exception as e:
143
158
  log_warning(f"Error sorting Firestore records: {e}")
144
159
  return records
agno/db/gcs_json/utils.py CHANGED
@@ -6,6 +6,7 @@ from typing import Any, Dict, List, Optional
6
6
  from uuid import uuid4
7
7
 
8
8
  from agno.db.schemas.culture import CulturalKnowledge
9
+ from agno.db.utils import get_sort_value
9
10
  from agno.utils.log import log_debug
10
11
 
11
12
 
@@ -21,6 +22,9 @@ def apply_sorting(
21
22
 
22
23
  Returns:
23
24
  The sorted list
25
+
26
+ Note:
27
+ If sorting by "updated_at", will fallback to "created_at" in case of None.
24
28
  """
25
29
  if sort_by is None or not data:
26
30
  return data
@@ -31,8 +35,16 @@ def apply_sorting(
31
35
  return data
32
36
 
33
37
  try:
34
- reverse_order = sort_order != "asc" if sort_order else True
35
- return sorted(data, key=lambda x: x.get(sort_by, 0), reverse=reverse_order)
38
+ is_descending = sort_order != "asc" if sort_order else True
39
+
40
+ # Sort using the helper function that handles updated_at -> created_at fallback
41
+ sorted_records = sorted(
42
+ data,
43
+ key=lambda x: (get_sort_value(x, sort_by) is None, get_sort_value(x, sort_by)),
44
+ reverse=is_descending,
45
+ )
46
+
47
+ return sorted_records
36
48
  except Exception as e:
37
49
  log_debug(f"Error sorting data by '{sort_by}': {e}")
38
50
  return data
@@ -6,6 +6,7 @@ from typing import Any, Dict, List, Optional
6
6
  from uuid import uuid4
7
7
 
8
8
  from agno.db.schemas.culture import CulturalKnowledge
9
+ from agno.db.utils import get_sort_value
9
10
  from agno.utils.log import log_debug
10
11
 
11
12
 
@@ -21,6 +22,9 @@ def apply_sorting(
21
22
 
22
23
  Returns:
23
24
  The sorted list
25
+
26
+ Note:
27
+ If sorting by "updated_at", will fallback to "created_at" in case of None.
24
28
  """
25
29
  if sort_by is None or not data:
26
30
  return data
@@ -31,8 +35,16 @@ def apply_sorting(
31
35
  return data
32
36
 
33
37
  try:
34
- reverse_order = sort_order != "asc" if sort_order else True
35
- return sorted(data, key=lambda x: x.get(sort_by, 0), reverse=reverse_order)
38
+ is_descending = sort_order != "asc" if sort_order else True
39
+
40
+ # Sort using the helper function that handles updated_at -> created_at fallback
41
+ sorted_records = sorted(
42
+ data,
43
+ key=lambda x: (get_sort_value(x, sort_by) is None, get_sort_value(x, sort_by)),
44
+ reverse=is_descending,
45
+ )
46
+
47
+ return sorted_records
36
48
  except Exception as e:
37
49
  log_debug(f"Error sorting data by '{sort_by}': {e}")
38
50
  return data
agno/db/json/utils.py CHANGED
@@ -6,6 +6,7 @@ from typing import Any, Dict, List, Optional
6
6
  from uuid import uuid4
7
7
 
8
8
  from agno.db.schemas.culture import CulturalKnowledge
9
+ from agno.db.utils import get_sort_value
9
10
  from agno.utils.log import log_debug
10
11
 
11
12
 
@@ -21,6 +22,9 @@ def apply_sorting(
21
22
 
22
23
  Returns:
23
24
  The sorted list
25
+
26
+ Note:
27
+ If sorting by "updated_at", will fallback to "created_at" in case of None.
24
28
  """
25
29
  if sort_by is None or not data:
26
30
  return data
@@ -31,8 +35,16 @@ def apply_sorting(
31
35
  return data
32
36
 
33
37
  try:
34
- reverse_order = sort_order != "asc" if sort_order else True
35
- return sorted(data, key=lambda x: x.get(sort_by, 0), reverse=reverse_order)
38
+ is_descending = sort_order != "asc" if sort_order else True
39
+
40
+ # Sort using the helper function that handles updated_at -> created_at fallback
41
+ sorted_records = sorted(
42
+ data,
43
+ key=lambda x: (get_sort_value(x, sort_by) is None, get_sort_value(x, sort_by)),
44
+ reverse=is_descending,
45
+ )
46
+
47
+ return sorted_records
36
48
  except Exception as e:
37
49
  log_debug(f"Error sorting data by '{sort_by}': {e}")
38
50
  return data
agno/db/mysql/utils.py CHANGED
@@ -10,7 +10,7 @@ from agno.db.schemas.culture import CulturalKnowledge
10
10
  from agno.utils.log import log_debug, log_error, log_warning
11
11
 
12
12
  try:
13
- from sqlalchemy import Engine, Table
13
+ from sqlalchemy import Engine, Table, func
14
14
  from sqlalchemy.dialects import mysql
15
15
  from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
16
16
  from sqlalchemy.inspection import inspect
@@ -32,6 +32,11 @@ def apply_sorting(stmt, table: Table, sort_by: Optional[str] = None, sort_order:
32
32
 
33
33
  Returns:
34
34
  The modified statement with sorting applied
35
+
36
+ Note:
37
+ For 'updated_at' sorting, uses COALESCE(updated_at, created_at) to fall back
38
+ to created_at when updated_at is NULL. This ensures pre-2.0 records (which may
39
+ have NULL updated_at) are sorted correctly by their creation time.
35
40
  """
36
41
  if sort_by is None:
37
42
  return stmt
@@ -40,8 +45,13 @@ def apply_sorting(stmt, table: Table, sort_by: Optional[str] = None, sort_order:
40
45
  log_debug(f"Invalid sort field: '{sort_by}'. Will not apply any sorting.")
41
46
  return stmt
42
47
 
43
- # Apply the given sorting
44
- sort_column = getattr(table.c, sort_by)
48
+ # For updated_at, use COALESCE to fall back to created_at if updated_at is NULL
49
+ # This handles pre-2.0 records that may have NULL updated_at values
50
+ if sort_by == "updated_at" and hasattr(table.c, "created_at"):
51
+ sort_column = func.coalesce(table.c.updated_at, table.c.created_at)
52
+ else:
53
+ sort_column = getattr(table.c, sort_by)
54
+
45
55
  if sort_order and sort_order == "asc":
46
56
  return stmt.order_by(sort_column.asc())
47
57
  else:
agno/db/postgres/utils.py CHANGED
@@ -13,7 +13,7 @@ from agno.db.schemas.culture import CulturalKnowledge
13
13
  from agno.utils.log import log_debug, log_error, log_warning
14
14
 
15
15
  try:
16
- from sqlalchemy import Table
16
+ from sqlalchemy import Table, func
17
17
  from sqlalchemy.dialects import postgresql
18
18
  from sqlalchemy.inspection import inspect
19
19
  from sqlalchemy.orm import Session
@@ -34,6 +34,11 @@ def apply_sorting(stmt, table: Table, sort_by: Optional[str] = None, sort_order:
34
34
 
35
35
  Returns:
36
36
  The modified statement with sorting applied
37
+
38
+ Note:
39
+ For 'updated_at' sorting, uses COALESCE(updated_at, created_at) to fall back
40
+ to created_at when updated_at is NULL. This ensures pre-2.0 records (which may
41
+ have NULL updated_at) are sorted correctly by their creation time.
37
42
  """
38
43
  if sort_by is None:
39
44
  return stmt
@@ -42,8 +47,13 @@ def apply_sorting(stmt, table: Table, sort_by: Optional[str] = None, sort_order:
42
47
  log_debug(f"Invalid sort field: '{sort_by}'. Will not apply any sorting.")
43
48
  return stmt
44
49
 
45
- # Apply the given sorting
46
- sort_column = getattr(table.c, sort_by)
50
+ # For updated_at, use COALESCE to fall back to created_at if updated_at is NULL
51
+ # This handles pre-2.0 records that may have NULL updated_at values
52
+ if sort_by == "updated_at" and hasattr(table.c, "created_at"):
53
+ sort_column = func.coalesce(table.c.updated_at, table.c.created_at)
54
+ else:
55
+ sort_column = getattr(table.c, sort_by)
56
+
47
57
  if sort_order and sort_order == "asc":
48
58
  return stmt.order_by(sort_column.asc())
49
59
  else:
agno/db/redis/utils.py CHANGED
@@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional, Union
7
7
  from uuid import UUID
8
8
 
9
9
  from agno.db.schemas.culture import CulturalKnowledge
10
+ from agno.db.utils import get_sort_value
10
11
  from agno.utils.log import log_warning
11
12
 
12
13
  try:
@@ -80,21 +81,36 @@ def get_all_keys_for_table(redis_client: Union[Redis, RedisCluster], prefix: str
80
81
  def apply_sorting(
81
82
  records: List[Dict[str, Any]], sort_by: Optional[str] = None, sort_order: Optional[str] = None
82
83
  ) -> List[Dict[str, Any]]:
83
- if sort_by is None:
84
- return records
84
+ """Apply sorting to the given records list.
85
+
86
+ Args:
87
+ records: The list of dictionaries to sort
88
+ sort_by: The field to sort by
89
+ sort_order: The sort order ('asc' or 'desc')
90
+
91
+ Returns:
92
+ The sorted list
85
93
 
86
- def get_sort_key(record):
87
- value = record.get(sort_by, 0)
88
- if value is None:
89
- return 0 if isinstance(records[0].get(sort_by, 0), (int, float)) else ""
90
- return value
94
+ Note:
95
+ If sorting by "updated_at", will fallback to "created_at" in case of None.
96
+ """
97
+ if sort_by is None or not records:
98
+ return records
91
99
 
92
100
  try:
93
- is_reverse = sort_order == "desc"
94
- return sorted(records, key=get_sort_key, reverse=is_reverse)
101
+ is_descending = sort_order == "desc"
102
+
103
+ # Sort using the helper function that handles updated_at -> created_at fallback
104
+ sorted_records = sorted(
105
+ records,
106
+ key=lambda x: (get_sort_value(x, sort_by) is None, get_sort_value(x, sort_by)),
107
+ reverse=is_descending,
108
+ )
109
+
110
+ return sorted_records
95
111
 
96
112
  except Exception as e:
97
- log_warning(f"Error sorting Redisrecords: {e}")
113
+ log_warning(f"Error sorting Redis records: {e}")
98
114
  return records
99
115
 
100
116
 
agno/db/schemas/memory.py CHANGED
@@ -1,8 +1,8 @@
1
1
  from dataclasses import dataclass
2
- from datetime import datetime, timezone
2
+ from datetime import datetime
3
3
  from typing import Any, Dict, List, Optional
4
4
 
5
- from agno.utils.dttm import now_epoch_s
5
+ from agno.utils.dttm import now_epoch_s, to_epoch_s
6
6
 
7
7
 
8
8
  @dataclass
@@ -22,17 +22,20 @@ class UserMemory:
22
22
  team_id: Optional[str] = None
23
23
 
24
24
  def __post_init__(self) -> None:
25
- """Automatically set created_at if not provided."""
26
- if self.created_at is None:
27
- self.created_at = now_epoch_s()
25
+ """Automatically set/normalize created_at and updated_at."""
26
+ self.created_at = now_epoch_s() if self.created_at is None else to_epoch_s(self.created_at)
27
+ if self.updated_at is not None:
28
+ self.updated_at = to_epoch_s(self.updated_at)
28
29
 
29
30
  def to_dict(self) -> Dict[str, Any]:
31
+ created_at = datetime.fromtimestamp(self.created_at).isoformat() if self.created_at is not None else None
32
+ updated_at = datetime.fromtimestamp(self.updated_at).isoformat() if self.updated_at is not None else created_at
30
33
  _dict = {
31
34
  "memory_id": self.memory_id,
32
35
  "memory": self.memory,
33
36
  "topics": self.topics,
34
- "created_at": datetime.fromtimestamp(self.created_at).isoformat() if self.created_at else None,
35
- "updated_at": datetime.fromtimestamp(self.updated_at).isoformat() if self.updated_at else None,
37
+ "created_at": created_at,
38
+ "updated_at": updated_at,
36
39
  "input": self.input,
37
40
  "user_id": self.user_id,
38
41
  "agent_id": self.agent_id,
@@ -45,17 +48,10 @@ class UserMemory:
45
48
  def from_dict(cls, data: Dict[str, Any]) -> "UserMemory":
46
49
  data = dict(data)
47
50
 
48
- if created_at := data.get("created_at"):
49
- if isinstance(created_at, (int, float)):
50
- data["created_at"] = datetime.fromtimestamp(created_at, tz=timezone.utc)
51
- else:
52
- data["created_at"] = datetime.fromisoformat(created_at)
53
-
54
- # Convert updated_at to datetime
55
- if updated_at := data.get("updated_at"):
56
- if isinstance(updated_at, (int, float)):
57
- data["updated_at"] = datetime.fromtimestamp(updated_at, tz=timezone.utc)
58
- else:
59
- data["updated_at"] = datetime.fromisoformat(updated_at)
51
+ # Preserve 0 and None explicitly; only process if key exists
52
+ if "created_at" in data and data["created_at"] is not None:
53
+ data["created_at"] = to_epoch_s(data["created_at"])
54
+ if "updated_at" in data and data["updated_at"] is not None:
55
+ data["updated_at"] = to_epoch_s(data["updated_at"])
60
56
 
61
57
  return cls(**data)
@@ -12,7 +12,7 @@ from agno.db.singlestore.schemas import get_table_schema_definition
12
12
  from agno.utils.log import log_debug, log_error, log_warning
13
13
 
14
14
  try:
15
- from sqlalchemy import Table
15
+ from sqlalchemy import Table, func
16
16
  from sqlalchemy.inspection import inspect
17
17
  from sqlalchemy.orm import Session
18
18
  from sqlalchemy.sql.expression import text
@@ -32,6 +32,11 @@ def apply_sorting(stmt, table: Table, sort_by: Optional[str] = None, sort_order:
32
32
 
33
33
  Returns:
34
34
  The modified statement with sorting applied
35
+
36
+ Note:
37
+ For 'updated_at' sorting, uses COALESCE(updated_at, created_at) to fall back
38
+ to created_at when updated_at is NULL. This ensures pre-2.0 records (which may
39
+ have NULL updated_at) are sorted correctly by their creation time.
35
40
  """
36
41
  if sort_by is None:
37
42
  return stmt
@@ -40,8 +45,13 @@ def apply_sorting(stmt, table: Table, sort_by: Optional[str] = None, sort_order:
40
45
  log_debug(f"Invalid sort field: '{sort_by}'. Will not apply any sorting.")
41
46
  return stmt
42
47
 
43
- # Apply the given sorting
44
- sort_column = getattr(table.c, sort_by)
48
+ # For updated_at, use COALESCE to fall back to created_at if updated_at is NULL
49
+ # This handles pre-2.0 records that may have NULL updated_at values
50
+ if sort_by == "updated_at" and hasattr(table.c, "created_at"):
51
+ sort_column = func.coalesce(table.c.updated_at, table.c.created_at)
52
+ else:
53
+ sort_column = getattr(table.c, sort_by)
54
+
45
55
  if sort_order and sort_order == "asc":
46
56
  return stmt.order_by(sort_column.asc())
47
57
  else:
agno/db/sqlite/utils.py CHANGED
@@ -11,7 +11,7 @@ from agno.db.sqlite.schemas import get_table_schema_definition
11
11
  from agno.utils.log import log_debug, log_error, log_warning
12
12
 
13
13
  try:
14
- from sqlalchemy import Table
14
+ from sqlalchemy import Table, func
15
15
  from sqlalchemy.dialects import sqlite
16
16
  from sqlalchemy.engine import Engine
17
17
  from sqlalchemy.inspection import inspect
@@ -26,13 +26,20 @@ except ImportError:
26
26
 
27
27
  def apply_sorting(stmt, table: Table, sort_by: Optional[str] = None, sort_order: Optional[str] = None):
28
28
  """Apply sorting to the given SQLAlchemy statement.
29
+
29
30
  Args:
30
31
  stmt: The SQLAlchemy statement to modify
31
32
  table: The table being queried
32
33
  sort_by: The field to sort by
33
34
  sort_order: The sort order ('asc' or 'desc')
35
+
34
36
  Returns:
35
37
  The modified statement with sorting applied
38
+
39
+ Note:
40
+ For 'updated_at' sorting, uses COALESCE(updated_at, created_at) to fall back
41
+ to created_at when updated_at is NULL. This ensures pre-2.0 records (which may
42
+ have NULL updated_at) are sorted correctly by their creation time.
36
43
  """
37
44
  if sort_by is None:
38
45
  return stmt
@@ -40,8 +47,13 @@ def apply_sorting(stmt, table: Table, sort_by: Optional[str] = None, sort_order:
40
47
  log_debug(f"Invalid sort field: '{sort_by}'. Will not apply any sorting.")
41
48
  return stmt
42
49
 
43
- # Apply the given sorting
44
- sort_column = getattr(table.c, sort_by)
50
+ # For updated_at, use COALESCE to fall back to created_at if updated_at is NULL
51
+ # This handles pre-2.0 records that may have NULL updated_at values
52
+ if sort_by == "updated_at" and hasattr(table.c, "created_at"):
53
+ sort_column = func.coalesce(table.c.updated_at, table.c.created_at)
54
+ else:
55
+ sort_column = getattr(table.c, sort_by)
56
+
45
57
  if sort_order and sort_order == "asc":
46
58
  return stmt.order_by(sort_column.asc())
47
59
  else:
agno/db/utils.py CHANGED
@@ -2,12 +2,34 @@
2
2
 
3
3
  import json
4
4
  from datetime import date, datetime
5
+ from typing import Any, Dict
5
6
  from uuid import UUID
6
7
 
7
8
  from agno.models.message import Message
8
9
  from agno.models.metrics import Metrics
9
10
 
10
11
 
12
+ def get_sort_value(record: Dict[str, Any], sort_by: str) -> Any:
13
+ """Get the sort value for a record, with fallback to created_at for updated_at.
14
+
15
+ When sorting by 'updated_at', this function falls back to 'created_at' if
16
+ 'updated_at' is None. This ensures pre-2.0 records (which may have NULL
17
+ updated_at values) are sorted correctly by their creation time.
18
+
19
+ Args:
20
+ record: The record dictionary to get the sort value from
21
+ sort_by: The field to sort by
22
+
23
+ Returns:
24
+ The value to use for sorting
25
+ """
26
+ value = record.get(sort_by)
27
+ # For updated_at, fall back to created_at if updated_at is None
28
+ if value is None and sort_by == "updated_at":
29
+ value = record.get("created_at")
30
+ return value
31
+
32
+
11
33
  class CustomJSONEncoder(json.JSONEncoder):
12
34
  """Custom encoder to handle non JSON serializable types."""
13
35
 
@@ -307,6 +307,9 @@ class LiteLLM(Model):
307
307
  if response_message.content is not None:
308
308
  model_response.content = response_message.content
309
309
 
310
+ if hasattr(response_message, "reasoning_content") and response_message.reasoning_content is not None:
311
+ model_response.reasoning_content = response_message.reasoning_content
312
+
310
313
  if hasattr(response_message, "tool_calls") and response_message.tool_calls:
311
314
  model_response.tool_calls = []
312
315
  for tool_call in response_message.tool_calls:
@@ -334,6 +337,9 @@ class LiteLLM(Model):
334
337
  if hasattr(choice_delta, "content") and choice_delta.content is not None:
335
338
  model_response.content = choice_delta.content
336
339
 
340
+ if hasattr(choice_delta, "reasoning_content") and choice_delta.reasoning_content is not None:
341
+ model_response.reasoning_content = choice_delta.reasoning_content
342
+
337
343
  if hasattr(choice_delta, "tool_calls") and choice_delta.tool_calls:
338
344
  processed_tool_calls = []
339
345
  for tool_call in choice_delta.tool_calls:
@@ -82,7 +82,7 @@ class ContentResponseSchema(BaseModel):
82
82
  status=status,
83
83
  status_message=content.get("status_message"),
84
84
  created_at=parse_timestamp(content.get("created_at")),
85
- updated_at=parse_timestamp(content.get("updated_at")),
85
+ updated_at=parse_timestamp(content.get("updated_at", content.get("created_at", 0))),
86
86
  # TODO: These fields are not available in the Content class. Fix the inconsistency
87
87
  access_count=None,
88
88
  linked_to=None,
@@ -1,3 +1,4 @@
1
+ import json
1
2
  from datetime import datetime, timezone
2
3
  from typing import Any, Dict, List, Optional
3
4
 
@@ -25,12 +26,24 @@ class UserMemorySchema(BaseModel):
25
26
  if memory_dict["memory"] == "":
26
27
  return None
27
28
 
29
+ # Handle nested memory content (relevant for some memories migrated from v1)
30
+ if isinstance(memory_dict["memory"], dict):
31
+ if memory_dict["memory"].get("memory") is not None:
32
+ memory = str(memory_dict["memory"]["memory"])
33
+ else:
34
+ try:
35
+ memory = json.dumps(memory_dict["memory"])
36
+ except json.JSONDecodeError:
37
+ memory = str(memory_dict["memory"])
38
+ else:
39
+ memory = memory_dict["memory"]
40
+
28
41
  return cls(
29
42
  memory_id=memory_dict["memory_id"],
30
43
  user_id=str(memory_dict["user_id"]),
31
44
  agent_id=memory_dict.get("agent_id"),
32
45
  team_id=memory_dict.get("team_id"),
33
- memory=memory_dict["memory"],
46
+ memory=memory,
34
47
  topics=memory_dict.get("topics", []),
35
48
  updated_at=memory_dict["updated_at"],
36
49
  )
@@ -35,7 +35,7 @@ class DayAggregatedMetrics(BaseModel):
35
35
  team_runs_count=metrics_dict.get("team_runs_count", 0),
36
36
  team_sessions_count=metrics_dict.get("team_sessions_count", 0),
37
37
  token_metrics=metrics_dict.get("token_metrics", {}),
38
- updated_at=metrics_dict.get("updated_at", 0),
38
+ updated_at=metrics_dict.get("updated_at", metrics_dict.get("created_at", 0)),
39
39
  users_count=metrics_dict.get("users_count", 0),
40
40
  workflow_runs_count=metrics_dict.get("workflow_runs_count", 0),
41
41
  workflow_sessions_count=metrics_dict.get("workflow_sessions_count", 0),
agno/os/schema.py CHANGED
@@ -193,7 +193,7 @@ class SessionSchema(BaseModel):
193
193
  session_data = session.get("session_data", {}) or {}
194
194
 
195
195
  created_at = session.get("created_at", 0)
196
- updated_at = session.get("updated_at", 0)
196
+ updated_at = session.get("updated_at", created_at)
197
197
 
198
198
  # Handle created_at and updated_at as either ISO 8601 string or timestamp
199
199
  def parse_datetime(val):
@@ -213,7 +213,7 @@ class SessionSchema(BaseModel):
213
213
  return None
214
214
 
215
215
  created_at = parse_datetime(session.get("created_at", 0))
216
- updated_at = parse_datetime(session.get("updated_at", 0))
216
+ updated_at = parse_datetime(session.get("updated_at", created_at))
217
217
  return cls(
218
218
  session_id=session.get("session_id", ""),
219
219
  session_name=session_name,
@@ -265,6 +265,8 @@ class AgentSessionDetailSchema(BaseModel):
265
265
  @classmethod
266
266
  def from_session(cls, session: AgentSession) -> "AgentSessionDetailSchema":
267
267
  session_name = get_session_name({**session.to_dict(), "session_type": "agent"})
268
+ created_at = datetime.fromtimestamp(session.created_at, tz=timezone.utc) if session.created_at else None
269
+ updated_at = datetime.fromtimestamp(session.updated_at, tz=timezone.utc) if session.updated_at else created_at
268
270
  return cls(
269
271
  user_id=session.user_id,
270
272
  agent_session_id=session.session_id,
@@ -280,8 +282,8 @@ class AgentSessionDetailSchema(BaseModel):
280
282
  metrics=session.session_data.get("session_metrics", {}) if session.session_data else None, # type: ignore
281
283
  metadata=session.metadata,
282
284
  chat_history=[message.to_dict() for message in session.get_chat_history()],
283
- created_at=datetime.fromtimestamp(session.created_at, tz=timezone.utc) if session.created_at else None,
284
- updated_at=datetime.fromtimestamp(session.updated_at, tz=timezone.utc) if session.updated_at else None,
285
+ created_at=created_at,
286
+ updated_at=updated_at,
285
287
  )
286
288
 
287
289
 
@@ -304,7 +306,8 @@ class TeamSessionDetailSchema(BaseModel):
304
306
  def from_session(cls, session: TeamSession) -> "TeamSessionDetailSchema":
305
307
  session_dict = session.to_dict()
306
308
  session_name = get_session_name({**session_dict, "session_type": "team"})
307
-
309
+ created_at = datetime.fromtimestamp(session.created_at, tz=timezone.utc) if session.created_at else None
310
+ updated_at = datetime.fromtimestamp(session.updated_at, tz=timezone.utc) if session.updated_at else created_at
308
311
  return cls(
309
312
  session_id=session.session_id,
310
313
  team_id=session.team_id,
@@ -319,8 +322,8 @@ class TeamSessionDetailSchema(BaseModel):
319
322
  metrics=session.session_data.get("session_metrics", {}) if session.session_data else None,
320
323
  metadata=session.metadata,
321
324
  chat_history=[message.to_dict() for message in session.get_chat_history()],
322
- created_at=datetime.fromtimestamp(session.created_at, tz=timezone.utc) if session.created_at else None,
323
- updated_at=datetime.fromtimestamp(session.updated_at, tz=timezone.utc) if session.updated_at else None,
325
+ created_at=created_at,
326
+ updated_at=updated_at,
324
327
  )
325
328
 
326
329
 
@@ -343,7 +346,6 @@ class WorkflowSessionDetailSchema(BaseModel):
343
346
  def from_session(cls, session: WorkflowSession) -> "WorkflowSessionDetailSchema":
344
347
  session_dict = session.to_dict()
345
348
  session_name = get_session_name({**session_dict, "session_type": "workflow"})
346
-
347
349
  return cls(
348
350
  session_id=session.session_id,
349
351
  user_id=session.user_id,
@@ -355,7 +357,7 @@ class WorkflowSessionDetailSchema(BaseModel):
355
357
  workflow_data=session.workflow_data,
356
358
  metadata=session.metadata,
357
359
  created_at=session.created_at,
358
- updated_at=session.updated_at,
360
+ updated_at=session.updated_at or session.created_at,
359
361
  )
360
362
 
361
363