agno 2.0.4__py3-none-any.whl → 2.0.6__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 +127 -102
- agno/db/dynamo/dynamo.py +9 -7
- agno/db/firestore/firestore.py +7 -4
- agno/db/gcs_json/gcs_json_db.py +6 -4
- agno/db/json/json_db.py +10 -6
- agno/db/migrations/v1_to_v2.py +191 -23
- agno/db/mongo/mongo.py +67 -6
- agno/db/mysql/mysql.py +7 -6
- agno/db/mysql/schemas.py +27 -27
- agno/db/postgres/postgres.py +7 -6
- agno/db/redis/redis.py +3 -3
- agno/db/singlestore/singlestore.py +4 -4
- agno/db/sqlite/sqlite.py +7 -6
- agno/db/utils.py +0 -14
- agno/integrations/discord/client.py +1 -0
- agno/knowledge/embedder/openai.py +19 -11
- agno/knowledge/knowledge.py +11 -10
- agno/knowledge/reader/reader_factory.py +7 -3
- agno/knowledge/reader/web_search_reader.py +12 -6
- agno/knowledge/reader/website_reader.py +33 -16
- agno/media.py +70 -0
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/base.py +31 -4
- agno/models/cerebras/cerebras_openai.py +2 -2
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/message.py +135 -0
- agno/models/meta/llama_openai.py +2 -2
- agno/models/nebius/nebius.py +2 -2
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +25 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/openai/responses.py +6 -0
- agno/models/openrouter/openrouter.py +2 -2
- agno/models/perplexity/perplexity.py +2 -2
- agno/models/portkey/portkey.py +3 -3
- agno/models/response.py +2 -1
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/together/together.py +2 -2
- agno/models/vercel/v0.py +2 -2
- agno/models/xai/xai.py +2 -2
- agno/os/app.py +162 -42
- agno/os/interfaces/agui/utils.py +98 -134
- agno/os/router.py +3 -1
- agno/os/routers/health.py +0 -1
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +2 -2
- agno/os/schema.py +21 -0
- agno/os/utils.py +1 -9
- agno/run/agent.py +19 -3
- agno/run/team.py +18 -3
- agno/run/workflow.py +10 -0
- agno/team/team.py +70 -45
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +14 -7
- agno/tools/file_generation.py +350 -0
- agno/tools/function.py +2 -0
- agno/tools/googlesearch.py +1 -1
- agno/utils/gemini.py +24 -4
- agno/utils/string.py +32 -0
- agno/utils/tools.py +1 -1
- agno/vectordb/chroma/chromadb.py +66 -25
- agno/vectordb/lancedb/lance_db.py +15 -4
- agno/vectordb/milvus/milvus.py +6 -0
- agno/workflow/step.py +4 -3
- agno/workflow/workflow.py +4 -0
- {agno-2.0.4.dist-info → agno-2.0.6.dist-info}/METADATA +9 -5
- {agno-2.0.4.dist-info → agno-2.0.6.dist-info}/RECORD +75 -72
- agno/knowledge/reader/url_reader.py +0 -128
- {agno-2.0.4.dist-info → agno-2.0.6.dist-info}/WHEEL +0 -0
- {agno-2.0.4.dist-info → agno-2.0.6.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.4.dist-info → agno-2.0.6.dist-info}/top_level.txt +0 -0
agno/db/json/json_db.py
CHANGED
|
@@ -17,9 +17,9 @@ from agno.db.json.utils import (
|
|
|
17
17
|
from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
|
|
18
18
|
from agno.db.schemas.knowledge import KnowledgeRow
|
|
19
19
|
from agno.db.schemas.memory import UserMemory
|
|
20
|
-
from agno.db.utils import generate_deterministic_id
|
|
21
20
|
from agno.session import AgentSession, Session, TeamSession, WorkflowSession
|
|
22
21
|
from agno.utils.log import log_debug, log_error, log_info, log_warning
|
|
22
|
+
from agno.utils.string import generate_id
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class JsonDb(BaseDb):
|
|
@@ -47,7 +47,7 @@ class JsonDb(BaseDb):
|
|
|
47
47
|
"""
|
|
48
48
|
if id is None:
|
|
49
49
|
seed = db_path or "agno_json_db"
|
|
50
|
-
id =
|
|
50
|
+
id = generate_id(seed)
|
|
51
51
|
|
|
52
52
|
super().__init__(
|
|
53
53
|
id=id,
|
|
@@ -168,7 +168,7 @@ class JsonDb(BaseDb):
|
|
|
168
168
|
def get_session(
|
|
169
169
|
self,
|
|
170
170
|
session_id: str,
|
|
171
|
-
session_type:
|
|
171
|
+
session_type: SessionType,
|
|
172
172
|
user_id: Optional[str] = None,
|
|
173
173
|
deserialize: Optional[bool] = True,
|
|
174
174
|
) -> Optional[Union[AgentSession, TeamSession, WorkflowSession, Dict[str, Any]]]:
|
|
@@ -176,7 +176,7 @@ class JsonDb(BaseDb):
|
|
|
176
176
|
|
|
177
177
|
Args:
|
|
178
178
|
session_id (str): The ID of the session to read.
|
|
179
|
-
session_type (
|
|
179
|
+
session_type (SessionType): The type of the session to read.
|
|
180
180
|
user_id (Optional[str]): The ID of the user to read the session for.
|
|
181
181
|
deserialize (Optional[bool]): Whether to deserialize the session.
|
|
182
182
|
|
|
@@ -208,8 +208,10 @@ class JsonDb(BaseDb):
|
|
|
208
208
|
return AgentSession.from_dict(session)
|
|
209
209
|
elif session_type == SessionType.TEAM:
|
|
210
210
|
return TeamSession.from_dict(session)
|
|
211
|
-
|
|
211
|
+
elif session_type == SessionType.WORKFLOW:
|
|
212
212
|
return WorkflowSession.from_dict(session)
|
|
213
|
+
else:
|
|
214
|
+
raise ValueError(f"Invalid session type: {session_type}")
|
|
213
215
|
|
|
214
216
|
return None
|
|
215
217
|
|
|
@@ -338,8 +340,10 @@ class JsonDb(BaseDb):
|
|
|
338
340
|
return AgentSession.from_dict(session)
|
|
339
341
|
elif session_type == SessionType.TEAM:
|
|
340
342
|
return TeamSession.from_dict(session)
|
|
341
|
-
|
|
343
|
+
elif session_type == SessionType.WORKFLOW:
|
|
342
344
|
return WorkflowSession.from_dict(session)
|
|
345
|
+
else:
|
|
346
|
+
raise ValueError(f"Invalid session type: {session_type}")
|
|
343
347
|
|
|
344
348
|
return None
|
|
345
349
|
|
agno/db/migrations/v1_to_v2.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"""Migration utility to migrate your Agno tables from v1 to v2"""
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from typing import Any, Dict, List, Optional, Union
|
|
4
5
|
|
|
5
6
|
from sqlalchemy import text
|
|
6
7
|
|
|
8
|
+
from agno.db.mongo.mongo import MongoDb
|
|
7
9
|
from agno.db.mysql.mysql import MySQLDb
|
|
8
10
|
from agno.db.postgres.postgres import PostgresDb
|
|
9
11
|
from agno.db.schemas.memory import UserMemory
|
|
@@ -12,24 +14,171 @@ from agno.session import AgentSession, TeamSession, WorkflowSession
|
|
|
12
14
|
from agno.utils.log import log_error
|
|
13
15
|
|
|
14
16
|
|
|
17
|
+
def convert_v1_metrics_to_v2(metrics_dict: Dict[str, Any]) -> Dict[str, Any]:
|
|
18
|
+
"""Convert v1 metrics dictionary to v2 format by mapping old field names to new ones."""
|
|
19
|
+
if not isinstance(metrics_dict, dict):
|
|
20
|
+
return metrics_dict
|
|
21
|
+
|
|
22
|
+
# Create a copy to avoid modifying the original
|
|
23
|
+
v2_metrics = metrics_dict.copy()
|
|
24
|
+
|
|
25
|
+
# Map v1 field names to v2 field names
|
|
26
|
+
field_mappings = {
|
|
27
|
+
"time": "duration",
|
|
28
|
+
"audio_tokens": "audio_total_tokens",
|
|
29
|
+
"input_audio_tokens": "audio_input_tokens",
|
|
30
|
+
"output_audio_tokens": "audio_output_tokens",
|
|
31
|
+
"cached_tokens": "cache_read_tokens",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Fields to remove (deprecated in v2)
|
|
35
|
+
deprecated_fields = ["prompt_tokens", "completion_tokens", "prompt_tokens_details", "completion_tokens_details"]
|
|
36
|
+
|
|
37
|
+
# Apply field mappings
|
|
38
|
+
for old_field, new_field in field_mappings.items():
|
|
39
|
+
if old_field in v2_metrics:
|
|
40
|
+
v2_metrics[new_field] = v2_metrics.pop(old_field)
|
|
41
|
+
|
|
42
|
+
# Remove deprecated fields
|
|
43
|
+
for field in deprecated_fields:
|
|
44
|
+
v2_metrics.pop(field, None)
|
|
45
|
+
|
|
46
|
+
return v2_metrics
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def convert_any_metrics_in_data(data: Any) -> Any:
|
|
50
|
+
"""Recursively find and convert any metrics dictionaries in the data structure."""
|
|
51
|
+
if isinstance(data, dict):
|
|
52
|
+
# First filter out deprecated v1 fields
|
|
53
|
+
data = filter_deprecated_v1_fields(data)
|
|
54
|
+
|
|
55
|
+
# Check if this looks like a metrics dictionary
|
|
56
|
+
if _is_metrics_dict(data):
|
|
57
|
+
return convert_v1_metrics_to_v2(data)
|
|
58
|
+
|
|
59
|
+
# Otherwise, recursively process all values
|
|
60
|
+
converted_dict = {}
|
|
61
|
+
for key, value in data.items():
|
|
62
|
+
# Special handling for 'metrics' keys - always convert their values
|
|
63
|
+
if key == "metrics" and isinstance(value, dict):
|
|
64
|
+
converted_dict[key] = convert_v1_metrics_to_v2(value)
|
|
65
|
+
else:
|
|
66
|
+
converted_dict[key] = convert_any_metrics_in_data(value)
|
|
67
|
+
return converted_dict
|
|
68
|
+
|
|
69
|
+
elif isinstance(data, list):
|
|
70
|
+
return [convert_any_metrics_in_data(item) for item in data]
|
|
71
|
+
|
|
72
|
+
else:
|
|
73
|
+
# Not a dict or list, return as-is
|
|
74
|
+
return data
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _is_metrics_dict(data: Dict[str, Any]) -> bool:
|
|
78
|
+
"""Check if a dictionary looks like a metrics dictionary based on common field names."""
|
|
79
|
+
if not isinstance(data, dict):
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
# Common metrics field names (both v1 and v2)
|
|
83
|
+
metrics_indicators = {
|
|
84
|
+
"input_tokens",
|
|
85
|
+
"output_tokens",
|
|
86
|
+
"total_tokens",
|
|
87
|
+
"time",
|
|
88
|
+
"duration",
|
|
89
|
+
"audio_tokens",
|
|
90
|
+
"audio_total_tokens",
|
|
91
|
+
"audio_input_tokens",
|
|
92
|
+
"audio_output_tokens",
|
|
93
|
+
"cached_tokens",
|
|
94
|
+
"cache_read_tokens",
|
|
95
|
+
"cache_write_tokens",
|
|
96
|
+
"reasoning_tokens",
|
|
97
|
+
"prompt_tokens",
|
|
98
|
+
"completion_tokens",
|
|
99
|
+
"time_to_first_token",
|
|
100
|
+
"provider_metrics",
|
|
101
|
+
"additional_metrics",
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Deprecated v1 fields that are strong indicators this is a metrics dict
|
|
105
|
+
deprecated_v1_indicators = {"time", "audio_tokens", "cached_tokens", "prompt_tokens", "completion_tokens"}
|
|
106
|
+
|
|
107
|
+
# If we find any deprecated v1 field, it's definitely a metrics dict that needs conversion
|
|
108
|
+
if any(field in data for field in deprecated_v1_indicators):
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
# Otherwise, if the dict has at least 2 metrics-related fields, consider it a metrics dict
|
|
112
|
+
matching_fields = sum(1 for field in data.keys() if field in metrics_indicators)
|
|
113
|
+
return matching_fields >= 2
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def convert_session_data_comprehensively(session_data: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
117
|
+
"""Comprehensively convert any metrics found anywhere in session_data from v1 to v2 format."""
|
|
118
|
+
if not session_data:
|
|
119
|
+
return session_data
|
|
120
|
+
|
|
121
|
+
# Use the recursive converter to find and fix all metrics
|
|
122
|
+
return convert_any_metrics_in_data(session_data)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def safe_get_runs_from_memory(memory_data: Any) -> Any:
|
|
126
|
+
"""Safely extract runs data from memory field, handling various data types."""
|
|
127
|
+
if memory_data is None:
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
# If memory_data is a string, try to parse it as JSON
|
|
131
|
+
if isinstance(memory_data, str):
|
|
132
|
+
try:
|
|
133
|
+
memory_dict = json.loads(memory_data)
|
|
134
|
+
if isinstance(memory_dict, dict):
|
|
135
|
+
return memory_dict.get("runs")
|
|
136
|
+
except (json.JSONDecodeError, AttributeError):
|
|
137
|
+
# If JSON parsing fails, memory_data might just be a string value
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
# If memory_data is already a dict, access runs directly
|
|
141
|
+
elif isinstance(memory_data, dict):
|
|
142
|
+
return memory_data.get("runs")
|
|
143
|
+
|
|
144
|
+
# For any other type, return None
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def filter_deprecated_v1_fields(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
149
|
+
"""Remove v1-only fields that don't exist in v2 models."""
|
|
150
|
+
if not isinstance(data, dict):
|
|
151
|
+
return data
|
|
152
|
+
|
|
153
|
+
# Fields that existed in v1 but were removed in v2
|
|
154
|
+
deprecated_fields = {
|
|
155
|
+
"team_session_id", # RunOutput v1 field, removed in v2
|
|
156
|
+
"formatted_tool_calls", # RunOutput v1 field, removed in v2
|
|
157
|
+
# Add other deprecated fields here as needed
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# Create a copy and remove deprecated fields
|
|
161
|
+
filtered_data = {k: v for k, v in data.items() if k not in deprecated_fields}
|
|
162
|
+
return filtered_data
|
|
163
|
+
|
|
164
|
+
|
|
15
165
|
def migrate(
|
|
16
|
-
db: Union[PostgresDb, MySQLDb, SqliteDb],
|
|
166
|
+
db: Union[PostgresDb, MySQLDb, SqliteDb, MongoDb],
|
|
17
167
|
v1_db_schema: str,
|
|
18
168
|
agent_sessions_table_name: Optional[str] = None,
|
|
19
169
|
team_sessions_table_name: Optional[str] = None,
|
|
20
170
|
workflow_sessions_table_name: Optional[str] = None,
|
|
21
171
|
memories_table_name: Optional[str] = None,
|
|
22
172
|
):
|
|
23
|
-
"""Given a
|
|
173
|
+
"""Given a database connection and table/collection names, parse and migrate the content to corresponding v2 tables/collections.
|
|
24
174
|
|
|
25
175
|
Args:
|
|
26
|
-
db: The database to migrate
|
|
27
|
-
v1_db_schema: The schema of the v1 tables
|
|
28
|
-
agent_sessions_table_name: The name of the agent sessions table. If not provided,
|
|
29
|
-
team_sessions_table_name: The name of the team sessions table. If not provided,
|
|
30
|
-
workflow_sessions_table_name: The name of the workflow sessions table. If not provided,
|
|
31
|
-
|
|
32
|
-
memories_table_name: The name of the memories table. If not provided, the memories table will not be migrated.
|
|
176
|
+
db: The database to migrate (PostgresDb, MySQLDb, SqliteDb, or MongoDb)
|
|
177
|
+
v1_db_schema: The schema of the v1 tables (leave empty for SQLite and MongoDB)
|
|
178
|
+
agent_sessions_table_name: The name of the agent sessions table/collection. If not provided, agent sessions will not be migrated.
|
|
179
|
+
team_sessions_table_name: The name of the team sessions table/collection. If not provided, team sessions will not be migrated.
|
|
180
|
+
workflow_sessions_table_name: The name of the workflow sessions table/collection. If not provided, workflow sessions will not be migrated.
|
|
181
|
+
memories_table_name: The name of the memories table/collection. If not provided, memories will not be migrated.
|
|
33
182
|
"""
|
|
34
183
|
if agent_sessions_table_name:
|
|
35
184
|
db.migrate_table_from_v1_to_v2(
|
|
@@ -61,14 +210,33 @@ def migrate(
|
|
|
61
210
|
|
|
62
211
|
|
|
63
212
|
def get_all_table_content(db, db_schema: str, table_name: str) -> list[dict[str, Any]]:
|
|
64
|
-
"""Get all content from the given table"""
|
|
213
|
+
"""Get all content from the given table/collection"""
|
|
65
214
|
try:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
215
|
+
# Check if this is a MongoDB instance
|
|
216
|
+
if hasattr(db, "database") and hasattr(db, "db_client"):
|
|
217
|
+
# MongoDB implementation
|
|
218
|
+
collection = db.database[table_name]
|
|
219
|
+
# Convert MongoDB documents to dictionaries and handle ObjectId
|
|
220
|
+
documents = list(collection.find({}))
|
|
221
|
+
# Convert ObjectId to string for compatibility
|
|
222
|
+
for doc in documents:
|
|
223
|
+
if "_id" in doc:
|
|
224
|
+
doc["_id"] = str(doc["_id"])
|
|
225
|
+
return documents
|
|
226
|
+
else:
|
|
227
|
+
# SQL database implementation (PostgreSQL, MySQL, SQLite)
|
|
228
|
+
with db.Session() as sess:
|
|
229
|
+
# Handle empty schema by omitting the schema prefix (needed for SQLite)
|
|
230
|
+
if db_schema and db_schema.strip():
|
|
231
|
+
sql_query = f"SELECT * FROM {db_schema}.{table_name}"
|
|
232
|
+
else:
|
|
233
|
+
sql_query = f"SELECT * FROM {table_name}"
|
|
234
|
+
|
|
235
|
+
result = sess.execute(text(sql_query))
|
|
236
|
+
return [row._asdict() for row in result]
|
|
69
237
|
|
|
70
238
|
except Exception as e:
|
|
71
|
-
log_error(f"Error getting all content from table {table_name}: {e}")
|
|
239
|
+
log_error(f"Error getting all content from table/collection {table_name}: {e}")
|
|
72
240
|
return []
|
|
73
241
|
|
|
74
242
|
|
|
@@ -82,9 +250,9 @@ def parse_agent_sessions(v1_content: List[Dict[str, Any]]) -> List[AgentSession]
|
|
|
82
250
|
"agent_data": item.get("agent_data"),
|
|
83
251
|
"session_id": item.get("session_id"),
|
|
84
252
|
"user_id": item.get("user_id"),
|
|
85
|
-
"session_data": item.get("session_data"),
|
|
86
|
-
"metadata": item.get("extra_data"),
|
|
87
|
-
"runs": item.get("memory"
|
|
253
|
+
"session_data": convert_session_data_comprehensively(item.get("session_data")),
|
|
254
|
+
"metadata": convert_any_metrics_in_data(item.get("extra_data")),
|
|
255
|
+
"runs": convert_any_metrics_in_data(safe_get_runs_from_memory(item.get("memory"))),
|
|
88
256
|
"created_at": item.get("created_at"),
|
|
89
257
|
"updated_at": item.get("updated_at"),
|
|
90
258
|
}
|
|
@@ -105,9 +273,9 @@ def parse_team_sessions(v1_content: List[Dict[str, Any]]) -> List[TeamSession]:
|
|
|
105
273
|
"team_data": item.get("team_data"),
|
|
106
274
|
"session_id": item.get("session_id"),
|
|
107
275
|
"user_id": item.get("user_id"),
|
|
108
|
-
"session_data": item.get("session_data"),
|
|
109
|
-
"metadata": item.get("extra_data"),
|
|
110
|
-
"runs": item.get("memory"
|
|
276
|
+
"session_data": convert_session_data_comprehensively(item.get("session_data")),
|
|
277
|
+
"metadata": convert_any_metrics_in_data(item.get("extra_data")),
|
|
278
|
+
"runs": convert_any_metrics_in_data(safe_get_runs_from_memory(item.get("memory"))),
|
|
111
279
|
"created_at": item.get("created_at"),
|
|
112
280
|
"updated_at": item.get("updated_at"),
|
|
113
281
|
}
|
|
@@ -128,13 +296,13 @@ def parse_workflow_sessions(v1_content: List[Dict[str, Any]]) -> List[WorkflowSe
|
|
|
128
296
|
"workflow_data": item.get("workflow_data"),
|
|
129
297
|
"session_id": item.get("session_id"),
|
|
130
298
|
"user_id": item.get("user_id"),
|
|
131
|
-
"session_data": item.get("session_data"),
|
|
132
|
-
"metadata": item.get("extra_data"),
|
|
299
|
+
"session_data": convert_session_data_comprehensively(item.get("session_data")),
|
|
300
|
+
"metadata": convert_any_metrics_in_data(item.get("extra_data")),
|
|
133
301
|
"created_at": item.get("created_at"),
|
|
134
302
|
"updated_at": item.get("updated_at"),
|
|
135
303
|
# Workflow v2 specific fields
|
|
136
304
|
"workflow_name": item.get("workflow_name"),
|
|
137
|
-
"runs": item.get("runs"),
|
|
305
|
+
"runs": convert_any_metrics_in_data(item.get("runs")),
|
|
138
306
|
}
|
|
139
307
|
workflow_session = WorkflowSession.from_dict(session)
|
|
140
308
|
if workflow_session is not None:
|
agno/db/mongo/mongo.py
CHANGED
|
@@ -16,9 +16,10 @@ from agno.db.mongo.utils import (
|
|
|
16
16
|
from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
|
|
17
17
|
from agno.db.schemas.knowledge import KnowledgeRow
|
|
18
18
|
from agno.db.schemas.memory import UserMemory
|
|
19
|
-
from agno.db.utils import deserialize_session_json_fields,
|
|
19
|
+
from agno.db.utils import deserialize_session_json_fields, serialize_session_json_fields
|
|
20
20
|
from agno.session import AgentSession, Session, TeamSession, WorkflowSession
|
|
21
21
|
from agno.utils.log import log_debug, log_error, log_info
|
|
22
|
+
from agno.utils.string import generate_id
|
|
22
23
|
|
|
23
24
|
try:
|
|
24
25
|
from pymongo import MongoClient, ReturnDocument
|
|
@@ -63,7 +64,7 @@ class MongoDb(BaseDb):
|
|
|
63
64
|
base_seed = db_url or str(db_client)
|
|
64
65
|
db_name_suffix = db_name if db_name is not None else "agno"
|
|
65
66
|
seed = f"{base_seed}#{db_name_suffix}"
|
|
66
|
-
id =
|
|
67
|
+
id = generate_id(seed)
|
|
67
68
|
|
|
68
69
|
super().__init__(
|
|
69
70
|
id=id,
|
|
@@ -252,8 +253,8 @@ class MongoDb(BaseDb):
|
|
|
252
253
|
|
|
253
254
|
Args:
|
|
254
255
|
session_id (str): The ID of the session to get.
|
|
256
|
+
session_type (SessionType): The type of session to get.
|
|
255
257
|
user_id (Optional[str]): The ID of the user to get the session for.
|
|
256
|
-
session_type (Optional[SessionType]): The type of session to get.
|
|
257
258
|
deserialize (Optional[bool]): Whether to serialize the session. Defaults to True.
|
|
258
259
|
|
|
259
260
|
Returns:
|
|
@@ -284,12 +285,14 @@ class MongoDb(BaseDb):
|
|
|
284
285
|
if not deserialize:
|
|
285
286
|
return session
|
|
286
287
|
|
|
287
|
-
if session_type == SessionType.AGENT
|
|
288
|
+
if session_type == SessionType.AGENT:
|
|
288
289
|
return AgentSession.from_dict(session)
|
|
289
|
-
elif session_type == SessionType.TEAM
|
|
290
|
+
elif session_type == SessionType.TEAM:
|
|
290
291
|
return TeamSession.from_dict(session)
|
|
291
|
-
|
|
292
|
+
elif session_type == SessionType.WORKFLOW:
|
|
292
293
|
return WorkflowSession.from_dict(session)
|
|
294
|
+
else:
|
|
295
|
+
raise ValueError(f"Invalid session type: {session_type}")
|
|
293
296
|
|
|
294
297
|
except Exception as e:
|
|
295
298
|
log_error(f"Exception reading session: {e}")
|
|
@@ -1423,3 +1426,61 @@ class MongoDb(BaseDb):
|
|
|
1423
1426
|
except Exception as e:
|
|
1424
1427
|
log_error(f"Error updating eval run name {eval_run_id}: {e}")
|
|
1425
1428
|
raise
|
|
1429
|
+
|
|
1430
|
+
def migrate_table_from_v1_to_v2(self, v1_db_schema: str, v1_table_name: str, v1_table_type: str):
|
|
1431
|
+
"""Migrate all content in the given collection to the right v2 collection"""
|
|
1432
|
+
|
|
1433
|
+
from typing import List, Sequence, Union
|
|
1434
|
+
|
|
1435
|
+
from agno.db.migrations.v1_to_v2 import (
|
|
1436
|
+
get_all_table_content,
|
|
1437
|
+
parse_agent_sessions,
|
|
1438
|
+
parse_memories,
|
|
1439
|
+
parse_team_sessions,
|
|
1440
|
+
parse_workflow_sessions,
|
|
1441
|
+
)
|
|
1442
|
+
|
|
1443
|
+
# Get all content from the old collection
|
|
1444
|
+
old_content: list[dict[str, Any]] = get_all_table_content(
|
|
1445
|
+
db=self,
|
|
1446
|
+
db_schema=v1_db_schema,
|
|
1447
|
+
table_name=v1_table_name,
|
|
1448
|
+
)
|
|
1449
|
+
if not old_content:
|
|
1450
|
+
log_info(f"No content to migrate from collection {v1_table_name}")
|
|
1451
|
+
return
|
|
1452
|
+
|
|
1453
|
+
# Parse the content into the new format
|
|
1454
|
+
memories: List[UserMemory] = []
|
|
1455
|
+
sessions: Sequence[Union[AgentSession, TeamSession, WorkflowSession]] = []
|
|
1456
|
+
if v1_table_type == "agent_sessions":
|
|
1457
|
+
sessions = parse_agent_sessions(old_content)
|
|
1458
|
+
elif v1_table_type == "team_sessions":
|
|
1459
|
+
sessions = parse_team_sessions(old_content)
|
|
1460
|
+
elif v1_table_type == "workflow_sessions":
|
|
1461
|
+
sessions = parse_workflow_sessions(old_content)
|
|
1462
|
+
elif v1_table_type == "memories":
|
|
1463
|
+
memories = parse_memories(old_content)
|
|
1464
|
+
else:
|
|
1465
|
+
raise ValueError(f"Invalid table type: {v1_table_type}")
|
|
1466
|
+
|
|
1467
|
+
# Insert the new content into the new collection
|
|
1468
|
+
if v1_table_type == "agent_sessions":
|
|
1469
|
+
for session in sessions:
|
|
1470
|
+
self.upsert_session(session)
|
|
1471
|
+
log_info(f"Migrated {len(sessions)} Agent sessions to collection: {self.session_table_name}")
|
|
1472
|
+
|
|
1473
|
+
elif v1_table_type == "team_sessions":
|
|
1474
|
+
for session in sessions:
|
|
1475
|
+
self.upsert_session(session)
|
|
1476
|
+
log_info(f"Migrated {len(sessions)} Team sessions to collection: {self.session_table_name}")
|
|
1477
|
+
|
|
1478
|
+
elif v1_table_type == "workflow_sessions":
|
|
1479
|
+
for session in sessions:
|
|
1480
|
+
self.upsert_session(session)
|
|
1481
|
+
log_info(f"Migrated {len(sessions)} Workflow sessions to collection: {self.session_table_name}")
|
|
1482
|
+
|
|
1483
|
+
elif v1_table_type == "memories":
|
|
1484
|
+
for memory in memories:
|
|
1485
|
+
self.upsert_user_memory(memory)
|
|
1486
|
+
log_info(f"Migrated {len(memories)} memories to collection: {self.memory_table_name}")
|
agno/db/mysql/mysql.py
CHANGED
|
@@ -20,9 +20,9 @@ from agno.db.mysql.utils import (
|
|
|
20
20
|
from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
|
|
21
21
|
from agno.db.schemas.knowledge import KnowledgeRow
|
|
22
22
|
from agno.db.schemas.memory import UserMemory
|
|
23
|
-
from agno.db.utils import generate_deterministic_id
|
|
24
23
|
from agno.session import AgentSession, Session, TeamSession, WorkflowSession
|
|
25
24
|
from agno.utils.log import log_debug, log_error, log_info
|
|
25
|
+
from agno.utils.string import generate_id
|
|
26
26
|
|
|
27
27
|
try:
|
|
28
28
|
from sqlalchemy import TEXT, and_, cast, func, update
|
|
@@ -75,7 +75,7 @@ class MySQLDb(BaseDb):
|
|
|
75
75
|
base_seed = db_url or str(db_engine.url) # type: ignore
|
|
76
76
|
schema_suffix = db_schema if db_schema is not None else "ai"
|
|
77
77
|
seed = f"{base_seed}#{schema_suffix}"
|
|
78
|
-
id =
|
|
78
|
+
id = generate_id(seed)
|
|
79
79
|
|
|
80
80
|
super().__init__(
|
|
81
81
|
id=id,
|
|
@@ -350,8 +350,8 @@ class MySQLDb(BaseDb):
|
|
|
350
350
|
|
|
351
351
|
Args:
|
|
352
352
|
session_id (str): ID of the session to read.
|
|
353
|
+
session_type (SessionType): Type of session to get.
|
|
353
354
|
user_id (Optional[str]): User ID to filter by. Defaults to None.
|
|
354
|
-
session_type (Optional[SessionType]): Type of session to read. Defaults to None.
|
|
355
355
|
deserialize (Optional[bool]): Whether to serialize the session. Defaults to True.
|
|
356
356
|
|
|
357
357
|
Returns:
|
|
@@ -415,6 +415,7 @@ class MySQLDb(BaseDb):
|
|
|
415
415
|
Get all sessions in the given table. Can filter by user_id and entity_id.
|
|
416
416
|
|
|
417
417
|
Args:
|
|
418
|
+
session_type (Optional[SessionType]): The type of sessions to get.
|
|
418
419
|
user_id (Optional[str]): The ID of the user to filter by.
|
|
419
420
|
entity_id (Optional[str]): The ID of the agent / workflow to filter by.
|
|
420
421
|
start_timestamp (Optional[int]): The start timestamp to filter by.
|
|
@@ -1711,17 +1712,17 @@ class MySQLDb(BaseDb):
|
|
|
1711
1712
|
if v1_table_type == "agent_sessions":
|
|
1712
1713
|
for session in sessions:
|
|
1713
1714
|
self.upsert_session(session)
|
|
1714
|
-
log_info(f"Migrated {len(sessions)} Agent sessions to table: {self.
|
|
1715
|
+
log_info(f"Migrated {len(sessions)} Agent sessions to table: {self.session_table_name}")
|
|
1715
1716
|
|
|
1716
1717
|
elif v1_table_type == "team_sessions":
|
|
1717
1718
|
for session in sessions:
|
|
1718
1719
|
self.upsert_session(session)
|
|
1719
|
-
log_info(f"Migrated {len(sessions)} Team sessions to table: {self.
|
|
1720
|
+
log_info(f"Migrated {len(sessions)} Team sessions to table: {self.session_table_name}")
|
|
1720
1721
|
|
|
1721
1722
|
elif v1_table_type == "workflow_sessions":
|
|
1722
1723
|
for session in sessions:
|
|
1723
1724
|
self.upsert_session(session)
|
|
1724
|
-
log_info(f"Migrated {len(sessions)} Workflow sessions to table: {self.
|
|
1725
|
+
log_info(f"Migrated {len(sessions)} Workflow sessions to table: {self.session_table_name}")
|
|
1725
1726
|
|
|
1726
1727
|
elif v1_table_type == "memories":
|
|
1727
1728
|
for memory in memories:
|
agno/db/mysql/schemas.py
CHANGED
|
@@ -8,12 +8,12 @@ except ImportError:
|
|
|
8
8
|
raise ImportError("`sqlalchemy` not installed. Please install it using `pip install sqlalchemy`")
|
|
9
9
|
|
|
10
10
|
SESSION_TABLE_SCHEMA = {
|
|
11
|
-
"session_id": {"type": String, "nullable": False},
|
|
12
|
-
"session_type": {"type": String, "nullable": False, "index": True},
|
|
13
|
-
"agent_id": {"type": String, "nullable": True},
|
|
14
|
-
"team_id": {"type": String, "nullable": True},
|
|
15
|
-
"workflow_id": {"type": String, "nullable": True},
|
|
16
|
-
"user_id": {"type": String, "nullable": True},
|
|
11
|
+
"session_id": {"type": lambda: String(128), "nullable": False},
|
|
12
|
+
"session_type": {"type": lambda: String(20), "nullable": False, "index": True},
|
|
13
|
+
"agent_id": {"type": lambda: String(128), "nullable": True},
|
|
14
|
+
"team_id": {"type": lambda: String(128), "nullable": True},
|
|
15
|
+
"workflow_id": {"type": lambda: String(128), "nullable": True},
|
|
16
|
+
"user_id": {"type": lambda: String(128), "nullable": True},
|
|
17
17
|
"session_data": {"type": JSON, "nullable": True},
|
|
18
18
|
"agent_data": {"type": JSON, "nullable": True},
|
|
19
19
|
"team_data": {"type": JSON, "nullable": True},
|
|
@@ -32,50 +32,50 @@ SESSION_TABLE_SCHEMA = {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
USER_MEMORY_TABLE_SCHEMA = {
|
|
35
|
-
"memory_id": {"type": String, "primary_key": True, "nullable": False},
|
|
35
|
+
"memory_id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
|
|
36
36
|
"memory": {"type": JSON, "nullable": False},
|
|
37
37
|
"input": {"type": Text, "nullable": True},
|
|
38
|
-
"agent_id": {"type": String, "nullable": True},
|
|
39
|
-
"team_id": {"type": String, "nullable": True},
|
|
40
|
-
"user_id": {"type": String, "nullable": True, "index": True},
|
|
38
|
+
"agent_id": {"type": lambda: String(128), "nullable": True},
|
|
39
|
+
"team_id": {"type": lambda: String(128), "nullable": True},
|
|
40
|
+
"user_id": {"type": lambda: String(128), "nullable": True, "index": True},
|
|
41
41
|
"topics": {"type": JSON, "nullable": True},
|
|
42
42
|
"updated_at": {"type": BigInteger, "nullable": True, "index": True},
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
EVAL_TABLE_SCHEMA = {
|
|
46
|
-
"run_id": {"type": String, "primary_key": True, "nullable": False},
|
|
47
|
-
"eval_type": {"type": String, "nullable": False},
|
|
46
|
+
"run_id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
|
|
47
|
+
"eval_type": {"type": lambda: String(50), "nullable": False},
|
|
48
48
|
"eval_data": {"type": JSON, "nullable": False},
|
|
49
49
|
"eval_input": {"type": JSON, "nullable": False},
|
|
50
|
-
"name": {"type": String, "nullable": True},
|
|
51
|
-
"agent_id": {"type": String, "nullable": True},
|
|
52
|
-
"team_id": {"type": String, "nullable": True},
|
|
53
|
-
"workflow_id": {"type": String, "nullable": True},
|
|
54
|
-
"model_id": {"type": String, "nullable": True},
|
|
55
|
-
"model_provider": {"type": String, "nullable": True},
|
|
56
|
-
"evaluated_component_name": {"type": String, "nullable": True},
|
|
50
|
+
"name": {"type": lambda: String(255), "nullable": True},
|
|
51
|
+
"agent_id": {"type": lambda: String(128), "nullable": True},
|
|
52
|
+
"team_id": {"type": lambda: String(128), "nullable": True},
|
|
53
|
+
"workflow_id": {"type": lambda: String(128), "nullable": True},
|
|
54
|
+
"model_id": {"type": lambda: String(128), "nullable": True},
|
|
55
|
+
"model_provider": {"type": lambda: String(128), "nullable": True},
|
|
56
|
+
"evaluated_component_name": {"type": lambda: String(255), "nullable": True},
|
|
57
57
|
"created_at": {"type": BigInteger, "nullable": False, "index": True},
|
|
58
58
|
"updated_at": {"type": BigInteger, "nullable": True},
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
KNOWLEDGE_TABLE_SCHEMA = {
|
|
62
|
-
"id": {"type": String, "primary_key": True, "nullable": False},
|
|
63
|
-
"name": {"type": String, "nullable": False},
|
|
62
|
+
"id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
|
|
63
|
+
"name": {"type": lambda: String(255), "nullable": False},
|
|
64
64
|
"description": {"type": Text, "nullable": False},
|
|
65
65
|
"metadata": {"type": JSON, "nullable": True},
|
|
66
|
-
"type": {"type": String, "nullable": True},
|
|
66
|
+
"type": {"type": lambda: String(50), "nullable": True},
|
|
67
67
|
"size": {"type": BigInteger, "nullable": True},
|
|
68
|
-
"linked_to": {"type": String, "nullable": True},
|
|
68
|
+
"linked_to": {"type": lambda: String(128), "nullable": True},
|
|
69
69
|
"access_count": {"type": BigInteger, "nullable": True},
|
|
70
70
|
"created_at": {"type": BigInteger, "nullable": True},
|
|
71
71
|
"updated_at": {"type": BigInteger, "nullable": True},
|
|
72
|
-
"status": {"type": String, "nullable": True},
|
|
72
|
+
"status": {"type": lambda: String(50), "nullable": True},
|
|
73
73
|
"status_message": {"type": Text, "nullable": True},
|
|
74
|
-
"external_id": {"type": String, "nullable": True},
|
|
74
|
+
"external_id": {"type": lambda: String(128), "nullable": True},
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
METRICS_TABLE_SCHEMA = {
|
|
78
|
-
"id": {"type": String, "primary_key": True, "nullable": False},
|
|
78
|
+
"id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
|
|
79
79
|
"agent_runs_count": {"type": BigInteger, "nullable": False},
|
|
80
80
|
"team_runs_count": {"type": BigInteger, "nullable": False},
|
|
81
81
|
"workflow_runs_count": {"type": BigInteger, "nullable": False},
|
|
@@ -86,7 +86,7 @@ METRICS_TABLE_SCHEMA = {
|
|
|
86
86
|
"token_metrics": {"type": JSON, "nullable": False},
|
|
87
87
|
"model_metrics": {"type": JSON, "nullable": False},
|
|
88
88
|
"date": {"type": Date, "nullable": False, "index": True},
|
|
89
|
-
"aggregation_period": {"type": String, "nullable": False},
|
|
89
|
+
"aggregation_period": {"type": lambda: String(20), "nullable": False},
|
|
90
90
|
"created_at": {"type": BigInteger, "nullable": False},
|
|
91
91
|
"updated_at": {"type": BigInteger, "nullable": True},
|
|
92
92
|
"completed": {"type": Boolean, "nullable": False},
|
agno/db/postgres/postgres.py
CHANGED
|
@@ -18,9 +18,9 @@ from agno.db.postgres.utils import (
|
|
|
18
18
|
from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
|
|
19
19
|
from agno.db.schemas.knowledge import KnowledgeRow
|
|
20
20
|
from agno.db.schemas.memory import UserMemory
|
|
21
|
-
from agno.db.utils import generate_deterministic_id
|
|
22
21
|
from agno.session import AgentSession, Session, TeamSession, WorkflowSession
|
|
23
22
|
from agno.utils.log import log_debug, log_error, log_info, log_warning
|
|
23
|
+
from agno.utils.string import generate_id
|
|
24
24
|
|
|
25
25
|
try:
|
|
26
26
|
from sqlalchemy import Index, String, UniqueConstraint, func, update
|
|
@@ -82,7 +82,7 @@ class PostgresDb(BaseDb):
|
|
|
82
82
|
base_seed = db_url or str(db_engine.url) # type: ignore
|
|
83
83
|
schema_suffix = db_schema if db_schema is not None else "ai"
|
|
84
84
|
seed = f"{base_seed}#{schema_suffix}"
|
|
85
|
-
id =
|
|
85
|
+
id = generate_id(seed)
|
|
86
86
|
|
|
87
87
|
super().__init__(
|
|
88
88
|
id=id,
|
|
@@ -345,8 +345,8 @@ class PostgresDb(BaseDb):
|
|
|
345
345
|
|
|
346
346
|
Args:
|
|
347
347
|
session_id (str): ID of the session to read.
|
|
348
|
+
session_type (SessionType): Type of session to get.
|
|
348
349
|
user_id (Optional[str]): User ID to filter by. Defaults to None.
|
|
349
|
-
session_type (Optional[SessionType]): Type of session to read. Defaults to None.
|
|
350
350
|
deserialize (Optional[bool]): Whether to serialize the session. Defaults to True.
|
|
351
351
|
|
|
352
352
|
Returns:
|
|
@@ -410,6 +410,7 @@ class PostgresDb(BaseDb):
|
|
|
410
410
|
Get all sessions in the given table. Can filter by user_id and entity_id.
|
|
411
411
|
|
|
412
412
|
Args:
|
|
413
|
+
session_type (Optional[SessionType]): The type of session to get.
|
|
413
414
|
user_id (Optional[str]): The ID of the user to filter by.
|
|
414
415
|
entity_id (Optional[str]): The ID of the agent / workflow to filter by.
|
|
415
416
|
start_timestamp (Optional[int]): The start timestamp to filter by.
|
|
@@ -1700,17 +1701,17 @@ class PostgresDb(BaseDb):
|
|
|
1700
1701
|
if v1_table_type == "agent_sessions":
|
|
1701
1702
|
for session in sessions:
|
|
1702
1703
|
self.upsert_session(session)
|
|
1703
|
-
log_info(f"Migrated {len(sessions)} Agent sessions to table: {self.
|
|
1704
|
+
log_info(f"Migrated {len(sessions)} Agent sessions to table: {self.session_table_name}")
|
|
1704
1705
|
|
|
1705
1706
|
elif v1_table_type == "team_sessions":
|
|
1706
1707
|
for session in sessions:
|
|
1707
1708
|
self.upsert_session(session)
|
|
1708
|
-
log_info(f"Migrated {len(sessions)} Team sessions to table: {self.
|
|
1709
|
+
log_info(f"Migrated {len(sessions)} Team sessions to table: {self.session_table_name}")
|
|
1709
1710
|
|
|
1710
1711
|
elif v1_table_type == "workflow_sessions":
|
|
1711
1712
|
for session in sessions:
|
|
1712
1713
|
self.upsert_session(session)
|
|
1713
|
-
log_info(f"Migrated {len(sessions)} Workflow sessions to table: {self.
|
|
1714
|
+
log_info(f"Migrated {len(sessions)} Workflow sessions to table: {self.session_table_name}")
|
|
1714
1715
|
|
|
1715
1716
|
elif v1_table_type == "memories":
|
|
1716
1717
|
for memory in memories:
|