agno 2.3.19__py3-none-any.whl → 2.3.21__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 (39) hide show
  1. agno/agent/agent.py +2466 -2048
  2. agno/db/dynamo/utils.py +26 -3
  3. agno/db/firestore/utils.py +25 -10
  4. agno/db/gcs_json/utils.py +14 -2
  5. agno/db/in_memory/utils.py +14 -2
  6. agno/db/json/utils.py +14 -2
  7. agno/db/mysql/utils.py +13 -3
  8. agno/db/postgres/utils.py +13 -3
  9. agno/db/redis/utils.py +26 -10
  10. agno/db/schemas/memory.py +15 -19
  11. agno/db/singlestore/utils.py +13 -3
  12. agno/db/sqlite/utils.py +15 -3
  13. agno/db/utils.py +22 -0
  14. agno/eval/agent_as_judge.py +24 -14
  15. agno/knowledge/embedder/mistral.py +1 -1
  16. agno/models/litellm/chat.py +6 -0
  17. agno/os/routers/evals/evals.py +0 -9
  18. agno/os/routers/evals/utils.py +6 -6
  19. agno/os/routers/knowledge/schemas.py +1 -1
  20. agno/os/routers/memory/schemas.py +14 -1
  21. agno/os/routers/metrics/schemas.py +1 -1
  22. agno/os/schema.py +11 -9
  23. agno/run/__init__.py +2 -4
  24. agno/run/agent.py +19 -19
  25. agno/run/cancel.py +65 -52
  26. agno/run/cancellation_management/__init__.py +9 -0
  27. agno/run/cancellation_management/base.py +78 -0
  28. agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
  29. agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
  30. agno/run/team.py +19 -19
  31. agno/team/team.py +1217 -1136
  32. agno/utils/response.py +1 -13
  33. agno/vectordb/weaviate/__init__.py +1 -1
  34. agno/workflow/workflow.py +23 -16
  35. {agno-2.3.19.dist-info → agno-2.3.21.dist-info}/METADATA +60 -129
  36. {agno-2.3.19.dist-info → agno-2.3.21.dist-info}/RECORD +39 -35
  37. {agno-2.3.19.dist-info → agno-2.3.21.dist-info}/WHEEL +0 -0
  38. {agno-2.3.19.dist-info → agno-2.3.21.dist-info}/licenses/LICENSE +0 -0
  39. {agno-2.3.19.dist-info → agno-2.3.21.dist-info}/top_level.txt +0 -0
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
 
@@ -518,8 +518,11 @@ class AgentAsJudgeEval(BaseEval):
518
518
  if self.print_summary or print_summary:
519
519
  result.print_summary(console)
520
520
 
521
+ # evaluator model info
522
+ model_id = self.model.id if self.model is not None else None
523
+ model_provider = self.model.provider if self.model is not None else None
521
524
  # Log to DB
522
- self._log_eval_to_db(run_id=run_id, result=result)
525
+ self._log_eval_to_db(run_id=run_id, result=result, model_id=model_id, model_provider=model_provider)
523
526
 
524
527
  if self.telemetry:
525
528
  from agno.api.evals import EvalRunCreate, create_eval_run_telemetry
@@ -613,8 +616,11 @@ class AgentAsJudgeEval(BaseEval):
613
616
  if self.print_summary or print_summary:
614
617
  result.print_summary(console)
615
618
 
619
+ # evaluator model info
620
+ model_id = self.model.id if self.model is not None else None
621
+ model_provider = self.model.provider if self.model is not None else None
616
622
  # Log to DB
617
- await self._async_log_eval_to_db(run_id=run_id, result=result)
623
+ await self._async_log_eval_to_db(run_id=run_id, result=result, model_id=model_id, model_provider=model_provider)
618
624
 
619
625
  if self.telemetry:
620
626
  from agno.api.evals import EvalRunCreate, async_create_eval_run_telemetry
@@ -681,8 +687,11 @@ class AgentAsJudgeEval(BaseEval):
681
687
  if self.print_summary or print_summary:
682
688
  result.print_summary(console)
683
689
 
690
+ # evaluator model info
691
+ model_id = self.model.id if self.model is not None else None
692
+ model_provider = self.model.provider if self.model is not None else None
684
693
  # Log to DB
685
- self._log_eval_to_db(run_id=run_id, result=result)
694
+ self._log_eval_to_db(run_id=run_id, result=result, model_id=model_id, model_provider=model_provider)
686
695
 
687
696
  if self.telemetry:
688
697
  from agno.api.evals import EvalRunCreate, create_eval_run_telemetry
@@ -748,8 +757,11 @@ class AgentAsJudgeEval(BaseEval):
748
757
  if self.print_summary or print_summary:
749
758
  result.print_summary(console)
750
759
 
760
+ # evaluator model info
761
+ model_id = self.model.id if self.model is not None else None
762
+ model_provider = self.model.provider if self.model is not None else None
751
763
  # Log to DB
752
- await self._async_log_eval_to_db(run_id=run_id, result=result)
764
+ await self._async_log_eval_to_db(run_id=run_id, result=result, model_id=model_id, model_provider=model_provider)
753
765
 
754
766
  if self.telemetry:
755
767
  from agno.api.evals import EvalRunCreate, async_create_eval_run_telemetry
@@ -801,15 +813,14 @@ class AgentAsJudgeEval(BaseEval):
801
813
  if isinstance(run_output, RunOutput):
802
814
  agent_id = run_output.agent_id
803
815
  team_id = None
804
- model_id = run_output.model
805
- model_provider = run_output.model_provider
806
816
  elif isinstance(run_output, TeamRunOutput):
807
817
  agent_id = None
808
818
  team_id = run_output.team_id
809
- model_id = run_output.model
810
- model_provider = run_output.model_provider
811
819
 
812
- # Log to DB if we have a valid result (use run_id from result)
820
+ # evaluator model info
821
+ model_id = self.model.id if self.model is not None else None
822
+ model_provider = self.model.provider if self.model is not None else None
823
+ # Log to DB if we have a valid result
813
824
  if result:
814
825
  self._log_eval_to_db(
815
826
  run_id=result.run_id,
@@ -841,15 +852,14 @@ class AgentAsJudgeEval(BaseEval):
841
852
  if isinstance(run_output, RunOutput):
842
853
  agent_id = run_output.agent_id
843
854
  team_id = None
844
- model_id = run_output.model
845
- model_provider = run_output.model_provider
846
855
  elif isinstance(run_output, TeamRunOutput):
847
856
  agent_id = None
848
857
  team_id = run_output.team_id
849
- model_id = run_output.model
850
- model_provider = run_output.model_provider
851
858
 
852
- # Log to DB if we have a valid result (use run_id from result)
859
+ # evaluator model info
860
+ model_id = self.model.id if self.model is not None else None
861
+ model_provider = self.model.provider if self.model is not None else None
862
+ # Log to DB if we have a valid result
853
863
  if result:
854
864
  await self._async_log_eval_to_db(
855
865
  run_id=result.run_id,
@@ -37,7 +37,7 @@ class MistralEmbedder(Embedder):
37
37
  "api_key": self.api_key,
38
38
  "endpoint": self.endpoint,
39
39
  "max_retries": self.max_retries,
40
- "timeout": self.timeout,
40
+ "timeout_ms": self.timeout * 1000 if self.timeout else None,
41
41
  }
42
42
  _client_params = {k: v for k, v in _client_params.items() if v is not None}
43
43
 
@@ -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:
@@ -144,15 +144,6 @@ def attach_routes(
144
144
  headers=headers,
145
145
  )
146
146
 
147
- # TODO: Delete me:
148
- # Filtering out agent-as-judge by default for now,
149
- # as they are not supported yet in the AgentOS UI.
150
- eval_types = eval_types or [
151
- EvalType.ACCURACY,
152
- EvalType.PERFORMANCE,
153
- EvalType.RELIABILITY,
154
- ]
155
-
156
147
  if isinstance(db, AsyncBaseDb):
157
148
  db = cast(AsyncBaseDb, db)
158
149
  eval_runs, total_count = await db.get_eval_runs(