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.
- agno/agent/agent.py +2466 -2048
- agno/db/dynamo/utils.py +26 -3
- agno/db/firestore/utils.py +25 -10
- agno/db/gcs_json/utils.py +14 -2
- agno/db/in_memory/utils.py +14 -2
- agno/db/json/utils.py +14 -2
- agno/db/mysql/utils.py +13 -3
- agno/db/postgres/utils.py +13 -3
- agno/db/redis/utils.py +26 -10
- agno/db/schemas/memory.py +15 -19
- agno/db/singlestore/utils.py +13 -3
- agno/db/sqlite/utils.py +15 -3
- agno/db/utils.py +22 -0
- agno/eval/agent_as_judge.py +24 -14
- agno/knowledge/embedder/mistral.py +1 -1
- agno/models/litellm/chat.py +6 -0
- agno/os/routers/evals/evals.py +0 -9
- agno/os/routers/evals/utils.py +6 -6
- agno/os/routers/knowledge/schemas.py +1 -1
- agno/os/routers/memory/schemas.py +14 -1
- agno/os/routers/metrics/schemas.py +1 -1
- agno/os/schema.py +11 -9
- agno/run/__init__.py +2 -4
- agno/run/agent.py +19 -19
- agno/run/cancel.py +65 -52
- agno/run/cancellation_management/__init__.py +9 -0
- agno/run/cancellation_management/base.py +78 -0
- agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
- agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
- agno/run/team.py +19 -19
- agno/team/team.py +1217 -1136
- agno/utils/response.py +1 -13
- agno/vectordb/weaviate/__init__.py +1 -1
- agno/workflow/workflow.py +23 -16
- {agno-2.3.19.dist-info → agno-2.3.21.dist-info}/METADATA +60 -129
- {agno-2.3.19.dist-info → agno-2.3.21.dist-info}/RECORD +39 -35
- {agno-2.3.19.dist-info → agno-2.3.21.dist-info}/WHEEL +0 -0
- {agno-2.3.19.dist-info → agno-2.3.21.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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
|
|
206
|
+
return sorted_records
|
|
184
207
|
|
|
185
208
|
|
|
186
209
|
# -- Session utils --
|
agno/db/firestore/utils.py
CHANGED
|
@@ -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
|
-
|
|
131
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
141
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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/in_memory/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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
#
|
|
44
|
-
|
|
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
|
-
#
|
|
46
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return
|
|
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
|
-
|
|
94
|
-
|
|
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
|
|
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
|
|
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
|
|
26
|
-
if self.created_at is None
|
|
27
|
-
|
|
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":
|
|
35
|
-
"updated_at":
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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)
|
agno/db/singlestore/utils.py
CHANGED
|
@@ -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
|
-
#
|
|
44
|
-
|
|
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
|
-
#
|
|
44
|
-
|
|
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
|
|
agno/eval/agent_as_judge.py
CHANGED
|
@@ -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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
"
|
|
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
|
|
agno/models/litellm/chat.py
CHANGED
|
@@ -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:
|
agno/os/routers/evals/evals.py
CHANGED
|
@@ -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(
|