agno 2.2.8__py3-none-any.whl → 2.2.10__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 +37 -19
- agno/db/base.py +23 -0
- agno/db/dynamo/dynamo.py +20 -25
- agno/db/dynamo/schemas.py +1 -0
- agno/db/firestore/firestore.py +11 -0
- agno/db/gcs_json/gcs_json_db.py +4 -0
- agno/db/in_memory/in_memory_db.py +4 -0
- agno/db/json/json_db.py +4 -0
- agno/db/mongo/async_mongo.py +27 -0
- agno/db/mongo/mongo.py +25 -0
- agno/db/mysql/mysql.py +26 -1
- agno/db/postgres/async_postgres.py +26 -1
- agno/db/postgres/postgres.py +26 -1
- agno/db/redis/redis.py +4 -0
- agno/db/singlestore/singlestore.py +24 -0
- agno/db/sqlite/async_sqlite.py +25 -1
- agno/db/sqlite/sqlite.py +25 -1
- agno/db/surrealdb/surrealdb.py +13 -1
- agno/knowledge/reader/docx_reader.py +0 -1
- agno/models/azure/ai_foundry.py +2 -1
- agno/models/cerebras/cerebras.py +3 -2
- agno/models/openai/chat.py +2 -1
- agno/models/openai/responses.py +2 -1
- agno/os/app.py +127 -65
- agno/os/config.py +1 -0
- agno/os/interfaces/agui/router.py +9 -0
- agno/os/interfaces/agui/utils.py +49 -3
- agno/os/mcp.py +8 -8
- agno/os/router.py +27 -9
- agno/os/routers/evals/evals.py +12 -7
- agno/os/routers/memory/memory.py +18 -10
- agno/os/routers/metrics/metrics.py +6 -4
- agno/os/routers/session/session.py +21 -11
- agno/os/utils.py +57 -11
- agno/team/team.py +33 -23
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/redis/__init__.py +4 -0
- agno/workflow/agent.py +2 -2
- agno/workflow/condition.py +26 -4
- agno/workflow/loop.py +9 -0
- agno/workflow/parallel.py +39 -16
- agno/workflow/router.py +25 -4
- agno/workflow/step.py +162 -91
- agno/workflow/steps.py +9 -0
- agno/workflow/workflow.py +26 -22
- {agno-2.2.8.dist-info → agno-2.2.10.dist-info}/METADATA +11 -13
- {agno-2.2.8.dist-info → agno-2.2.10.dist-info}/RECORD +50 -50
- {agno-2.2.8.dist-info → agno-2.2.10.dist-info}/WHEEL +0 -0
- {agno-2.2.8.dist-info → agno-2.2.10.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.8.dist-info → agno-2.2.10.dist-info}/top_level.txt +0 -0
agno/os/mcp.py
CHANGED
|
@@ -57,7 +57,7 @@ def get_mcp_server(
|
|
|
57
57
|
os_id=os.id or "AgentOS",
|
|
58
58
|
description=os.description,
|
|
59
59
|
available_models=os.config.available_models if os.config else [],
|
|
60
|
-
databases=[db.id for
|
|
60
|
+
databases=[db.id for db_list in os.dbs.values() for db in db_list],
|
|
61
61
|
chat=os.config.chat if os.config else None,
|
|
62
62
|
session=os._get_session_config(),
|
|
63
63
|
memory=os._get_memory_config(),
|
|
@@ -103,7 +103,7 @@ def get_mcp_server(
|
|
|
103
103
|
sort_by: str = "created_at",
|
|
104
104
|
sort_order: str = "desc",
|
|
105
105
|
):
|
|
106
|
-
db = get_db(os.dbs, db_id)
|
|
106
|
+
db = await get_db(os.dbs, db_id)
|
|
107
107
|
if isinstance(db, AsyncBaseDb):
|
|
108
108
|
db = cast(AsyncBaseDb, db)
|
|
109
109
|
sessions = await db.get_sessions(
|
|
@@ -136,7 +136,7 @@ def get_mcp_server(
|
|
|
136
136
|
sort_by: str = "created_at",
|
|
137
137
|
sort_order: str = "desc",
|
|
138
138
|
):
|
|
139
|
-
db = get_db(os.dbs, db_id)
|
|
139
|
+
db = await get_db(os.dbs, db_id)
|
|
140
140
|
if isinstance(db, AsyncBaseDb):
|
|
141
141
|
db = cast(AsyncBaseDb, db)
|
|
142
142
|
sessions = await db.get_sessions(
|
|
@@ -169,7 +169,7 @@ def get_mcp_server(
|
|
|
169
169
|
sort_by: str = "created_at",
|
|
170
170
|
sort_order: str = "desc",
|
|
171
171
|
):
|
|
172
|
-
db = get_db(os.dbs, db_id)
|
|
172
|
+
db = await get_db(os.dbs, db_id)
|
|
173
173
|
if isinstance(db, AsyncBaseDb):
|
|
174
174
|
db = cast(AsyncBaseDb, db)
|
|
175
175
|
sessions = await db.get_sessions(
|
|
@@ -202,7 +202,7 @@ def get_mcp_server(
|
|
|
202
202
|
user_id: str,
|
|
203
203
|
topics: Optional[List[str]] = None,
|
|
204
204
|
) -> UserMemorySchema:
|
|
205
|
-
db = get_db(os.dbs, db_id)
|
|
205
|
+
db = await get_db(os.dbs, db_id)
|
|
206
206
|
user_memory = db.upsert_user_memory(
|
|
207
207
|
memory=UserMemory(
|
|
208
208
|
memory_id=str(uuid4()),
|
|
@@ -224,7 +224,7 @@ def get_mcp_server(
|
|
|
224
224
|
sort_order: str = "desc",
|
|
225
225
|
db_id: Optional[str] = None,
|
|
226
226
|
):
|
|
227
|
-
db = get_db(os.dbs, db_id)
|
|
227
|
+
db = await get_db(os.dbs, db_id)
|
|
228
228
|
if isinstance(db, AsyncBaseDb):
|
|
229
229
|
db = cast(AsyncBaseDb, db)
|
|
230
230
|
user_memories = await db.get_user_memories(
|
|
@@ -251,7 +251,7 @@ def get_mcp_server(
|
|
|
251
251
|
memory: str,
|
|
252
252
|
user_id: str,
|
|
253
253
|
) -> UserMemorySchema:
|
|
254
|
-
db = get_db(os.dbs, db_id)
|
|
254
|
+
db = await get_db(os.dbs, db_id)
|
|
255
255
|
if isinstance(db, AsyncBaseDb):
|
|
256
256
|
db = cast(AsyncBaseDb, db)
|
|
257
257
|
user_memory = await db.upsert_user_memory(
|
|
@@ -281,7 +281,7 @@ def get_mcp_server(
|
|
|
281
281
|
db_id: str,
|
|
282
282
|
memory_id: str,
|
|
283
283
|
) -> None:
|
|
284
|
-
db = get_db(os.dbs, db_id)
|
|
284
|
+
db = await get_db(os.dbs, db_id)
|
|
285
285
|
if isinstance(db, AsyncBaseDb):
|
|
286
286
|
db = cast(AsyncBaseDb, db)
|
|
287
287
|
await db.delete_user_memory(memory_id=memory_id)
|
agno/os/router.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from itertools import chain
|
|
3
2
|
from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Dict, List, Optional, Union, cast
|
|
4
3
|
from uuid import uuid4
|
|
5
4
|
|
|
@@ -73,33 +72,52 @@ async def _get_request_kwargs(request: Request, endpoint_func: Callable) -> Dict
|
|
|
73
72
|
form_data = await request.form()
|
|
74
73
|
sig = inspect.signature(endpoint_func)
|
|
75
74
|
known_fields = set(sig.parameters.keys())
|
|
76
|
-
kwargs = {key: value for key, value in form_data.items() if key not in known_fields}
|
|
75
|
+
kwargs: Dict[str, Any] = {key: value for key, value in form_data.items() if key not in known_fields}
|
|
77
76
|
|
|
78
77
|
# Handle JSON parameters. They are passed as strings and need to be deserialized.
|
|
79
78
|
if session_state := kwargs.get("session_state"):
|
|
80
79
|
try:
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
if isinstance(session_state, str):
|
|
81
|
+
session_state_dict = json.loads(session_state) # type: ignore
|
|
82
|
+
kwargs["session_state"] = session_state_dict
|
|
83
83
|
except json.JSONDecodeError:
|
|
84
84
|
kwargs.pop("session_state")
|
|
85
85
|
log_warning(f"Invalid session_state parameter couldn't be loaded: {session_state}")
|
|
86
86
|
|
|
87
87
|
if dependencies := kwargs.get("dependencies"):
|
|
88
88
|
try:
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
if isinstance(dependencies, str):
|
|
90
|
+
dependencies_dict = json.loads(dependencies) # type: ignore
|
|
91
|
+
kwargs["dependencies"] = dependencies_dict
|
|
91
92
|
except json.JSONDecodeError:
|
|
92
93
|
kwargs.pop("dependencies")
|
|
93
94
|
log_warning(f"Invalid dependencies parameter couldn't be loaded: {dependencies}")
|
|
94
95
|
|
|
95
96
|
if metadata := kwargs.get("metadata"):
|
|
96
97
|
try:
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
if isinstance(metadata, str):
|
|
99
|
+
metadata_dict = json.loads(metadata) # type: ignore
|
|
100
|
+
kwargs["metadata"] = metadata_dict
|
|
99
101
|
except json.JSONDecodeError:
|
|
100
102
|
kwargs.pop("metadata")
|
|
101
103
|
log_warning(f"Invalid metadata parameter couldn't be loaded: {metadata}")
|
|
102
104
|
|
|
105
|
+
if knowledge_filters := kwargs.get("knowledge_filters"):
|
|
106
|
+
try:
|
|
107
|
+
if isinstance(knowledge_filters, str):
|
|
108
|
+
knowledge_filters_dict = json.loads(knowledge_filters) # type: ignore
|
|
109
|
+
kwargs["knowledge_filters"] = knowledge_filters_dict
|
|
110
|
+
except json.JSONDecodeError:
|
|
111
|
+
kwargs.pop("knowledge_filters")
|
|
112
|
+
log_warning(f"Invalid knowledge_filters parameter couldn't be loaded: {knowledge_filters}")
|
|
113
|
+
|
|
114
|
+
# Parse boolean and null values
|
|
115
|
+
for key, value in kwargs.items():
|
|
116
|
+
if isinstance(value, str) and value.lower() in ["true", "false"]:
|
|
117
|
+
kwargs[key] = value.lower() == "true"
|
|
118
|
+
elif isinstance(value, str) and value.lower() in ["null", "none"]:
|
|
119
|
+
kwargs[key] = None
|
|
120
|
+
|
|
103
121
|
return kwargs
|
|
104
122
|
|
|
105
123
|
|
|
@@ -652,7 +670,7 @@ def get_base_router(
|
|
|
652
670
|
os_id=os.id or "Unnamed OS",
|
|
653
671
|
description=os.description,
|
|
654
672
|
available_models=os.config.available_models if os.config else [],
|
|
655
|
-
databases=list({db.id for
|
|
673
|
+
databases=list({db.id for db_id, dbs in os.dbs.items() for db in dbs}),
|
|
656
674
|
chat=os.config.chat if os.config else None,
|
|
657
675
|
session=os._get_session_config(),
|
|
658
676
|
memory=os._get_memory_config(),
|
agno/os/routers/evals/evals.py
CHANGED
|
@@ -34,7 +34,7 @@ logger = logging.getLogger(__name__)
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def get_eval_router(
|
|
37
|
-
dbs: dict[str, Union[BaseDb, AsyncBaseDb]],
|
|
37
|
+
dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]],
|
|
38
38
|
agents: Optional[List[Agent]] = None,
|
|
39
39
|
teams: Optional[List[Team]] = None,
|
|
40
40
|
settings: AgnoAPISettings = AgnoAPISettings(),
|
|
@@ -56,7 +56,7 @@ def get_eval_router(
|
|
|
56
56
|
|
|
57
57
|
def attach_routes(
|
|
58
58
|
router: APIRouter,
|
|
59
|
-
dbs: dict[str, Union[BaseDb, AsyncBaseDb]],
|
|
59
|
+
dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]],
|
|
60
60
|
agents: Optional[List[Agent]] = None,
|
|
61
61
|
teams: Optional[List[Team]] = None,
|
|
62
62
|
) -> APIRouter:
|
|
@@ -115,8 +115,9 @@ def attach_routes(
|
|
|
115
115
|
sort_by: Optional[str] = Query(default="created_at", description="Field to sort by"),
|
|
116
116
|
sort_order: Optional[SortOrder] = Query(default="desc", description="Sort order (asc or desc)"),
|
|
117
117
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
118
|
+
table: Optional[str] = Query(default=None, description="The database table to use"),
|
|
118
119
|
) -> PaginatedResponse[EvalSchema]:
|
|
119
|
-
db = get_db(dbs, db_id)
|
|
120
|
+
db = await get_db(dbs, db_id, table)
|
|
120
121
|
|
|
121
122
|
if isinstance(db, AsyncBaseDb):
|
|
122
123
|
db = cast(AsyncBaseDb, db)
|
|
@@ -198,8 +199,9 @@ def attach_routes(
|
|
|
198
199
|
async def get_eval_run(
|
|
199
200
|
eval_run_id: str,
|
|
200
201
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
202
|
+
table: Optional[str] = Query(default=None, description="Table to query eval run from"),
|
|
201
203
|
) -> EvalSchema:
|
|
202
|
-
db = get_db(dbs, db_id)
|
|
204
|
+
db = await get_db(dbs, db_id, table)
|
|
203
205
|
if isinstance(db, AsyncBaseDb):
|
|
204
206
|
db = cast(AsyncBaseDb, db)
|
|
205
207
|
eval_run = await db.get_eval_run(eval_run_id=eval_run_id, deserialize=False)
|
|
@@ -224,9 +226,10 @@ def attach_routes(
|
|
|
224
226
|
async def delete_eval_runs(
|
|
225
227
|
request: DeleteEvalRunsRequest,
|
|
226
228
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for deletion"),
|
|
229
|
+
table: Optional[str] = Query(default=None, description="Table to use for deletion"),
|
|
227
230
|
) -> None:
|
|
228
231
|
try:
|
|
229
|
-
db = get_db(dbs, db_id)
|
|
232
|
+
db = await get_db(dbs, db_id, table)
|
|
230
233
|
if isinstance(db, AsyncBaseDb):
|
|
231
234
|
db = cast(AsyncBaseDb, db)
|
|
232
235
|
await db.delete_eval_runs(eval_run_ids=request.eval_run_ids)
|
|
@@ -277,9 +280,10 @@ def attach_routes(
|
|
|
277
280
|
eval_run_id: str,
|
|
278
281
|
request: UpdateEvalRunRequest,
|
|
279
282
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
283
|
+
table: Optional[str] = Query(default=None, description="Table to use for rename operation"),
|
|
280
284
|
) -> EvalSchema:
|
|
281
285
|
try:
|
|
282
|
-
db = get_db(dbs, db_id)
|
|
286
|
+
db = await get_db(dbs, db_id, table)
|
|
283
287
|
if isinstance(db, AsyncBaseDb):
|
|
284
288
|
db = cast(AsyncBaseDb, db)
|
|
285
289
|
eval_run = await db.rename_eval_run(eval_run_id=eval_run_id, name=request.name, deserialize=False)
|
|
@@ -336,8 +340,9 @@ def attach_routes(
|
|
|
336
340
|
async def run_eval(
|
|
337
341
|
eval_run_input: EvalRunInput,
|
|
338
342
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for evaluation"),
|
|
343
|
+
table: Optional[str] = Query(default=None, description="Table to use for evaluation"),
|
|
339
344
|
) -> Optional[EvalSchema]:
|
|
340
|
-
db = get_db(dbs, db_id)
|
|
345
|
+
db = await get_db(dbs, db_id, table)
|
|
341
346
|
|
|
342
347
|
if eval_run_input.agent_id and eval_run_input.team_id:
|
|
343
348
|
raise HTTPException(status_code=400, detail="Only one of agent_id or team_id must be provided")
|
agno/os/routers/memory/memory.py
CHANGED
|
@@ -32,7 +32,7 @@ logger = logging.getLogger(__name__)
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
def get_memory_router(
|
|
35
|
-
dbs: dict[str, Union[BaseDb, AsyncBaseDb]], settings: AgnoAPISettings = AgnoAPISettings(), **kwargs
|
|
35
|
+
dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]], settings: AgnoAPISettings = AgnoAPISettings(), **kwargs
|
|
36
36
|
) -> APIRouter:
|
|
37
37
|
"""Create memory router with comprehensive OpenAPI documentation for user memory management endpoints."""
|
|
38
38
|
router = APIRouter(
|
|
@@ -49,7 +49,7 @@ def get_memory_router(
|
|
|
49
49
|
return attach_routes(router=router, dbs=dbs)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]]) -> APIRouter:
|
|
52
|
+
def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]]) -> APIRouter:
|
|
53
53
|
@router.post(
|
|
54
54
|
"/memories",
|
|
55
55
|
response_model=UserMemorySchema,
|
|
@@ -85,6 +85,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
85
85
|
request: Request,
|
|
86
86
|
payload: UserMemoryCreateSchema,
|
|
87
87
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for memory storage"),
|
|
88
|
+
table: Optional[str] = Query(default=None, description="Table to use for memory storage"),
|
|
88
89
|
) -> UserMemorySchema:
|
|
89
90
|
if hasattr(request.state, "user_id"):
|
|
90
91
|
user_id = request.state.user_id
|
|
@@ -93,7 +94,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
93
94
|
if payload.user_id is None:
|
|
94
95
|
raise HTTPException(status_code=400, detail="User ID is required")
|
|
95
96
|
|
|
96
|
-
db = get_db(dbs, db_id)
|
|
97
|
+
db = await get_db(dbs, db_id, table)
|
|
97
98
|
|
|
98
99
|
if isinstance(db, AsyncBaseDb):
|
|
99
100
|
db = cast(AsyncBaseDb, db)
|
|
@@ -138,8 +139,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
138
139
|
memory_id: str = Path(description="Memory ID to delete"),
|
|
139
140
|
user_id: Optional[str] = Query(default=None, description="User ID to delete memory for"),
|
|
140
141
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for deletion"),
|
|
142
|
+
table: Optional[str] = Query(default=None, description="Table to use for deletion"),
|
|
141
143
|
) -> None:
|
|
142
|
-
db = get_db(dbs, db_id)
|
|
144
|
+
db = await get_db(dbs, db_id, table)
|
|
143
145
|
if isinstance(db, AsyncBaseDb):
|
|
144
146
|
db = cast(AsyncBaseDb, db)
|
|
145
147
|
await db.delete_user_memory(memory_id=memory_id, user_id=user_id)
|
|
@@ -164,8 +166,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
164
166
|
async def delete_memories(
|
|
165
167
|
request: DeleteMemoriesRequest,
|
|
166
168
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for deletion"),
|
|
169
|
+
table: Optional[str] = Query(default=None, description="Table to use for deletion"),
|
|
167
170
|
) -> None:
|
|
168
|
-
db = get_db(dbs, db_id)
|
|
171
|
+
db = await get_db(dbs, db_id, table)
|
|
169
172
|
if isinstance(db, AsyncBaseDb):
|
|
170
173
|
db = cast(AsyncBaseDb, db)
|
|
171
174
|
await db.delete_user_memories(memory_ids=request.memory_ids, user_id=request.user_id)
|
|
@@ -217,8 +220,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
217
220
|
sort_by: Optional[str] = Query(default="updated_at", description="Field to sort memories by"),
|
|
218
221
|
sort_order: Optional[SortOrder] = Query(default="desc", description="Sort order (asc or desc)"),
|
|
219
222
|
db_id: Optional[str] = Query(default=None, description="Database ID to query memories from"),
|
|
223
|
+
table: Optional[str] = Query(default=None, description="The database table to use"),
|
|
220
224
|
) -> PaginatedResponse[UserMemorySchema]:
|
|
221
|
-
db = get_db(dbs, db_id)
|
|
225
|
+
db = await get_db(dbs, db_id, table)
|
|
222
226
|
|
|
223
227
|
if hasattr(request.state, "user_id"):
|
|
224
228
|
user_id = request.state.user_id
|
|
@@ -294,8 +298,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
294
298
|
memory_id: str = Path(description="Memory ID to retrieve"),
|
|
295
299
|
user_id: Optional[str] = Query(default=None, description="User ID to query memory for"),
|
|
296
300
|
db_id: Optional[str] = Query(default=None, description="Database ID to query memory from"),
|
|
301
|
+
table: Optional[str] = Query(default=None, description="Table to query memory from"),
|
|
297
302
|
) -> UserMemorySchema:
|
|
298
|
-
db = get_db(dbs, db_id)
|
|
303
|
+
db = await get_db(dbs, db_id, table)
|
|
299
304
|
|
|
300
305
|
if hasattr(request.state, "user_id"):
|
|
301
306
|
user_id = request.state.user_id
|
|
@@ -343,8 +348,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
343
348
|
)
|
|
344
349
|
async def get_topics(
|
|
345
350
|
db_id: Optional[str] = Query(default=None, description="Database ID to query topics from"),
|
|
351
|
+
table: Optional[str] = Query(default=None, description="Table to query topics from"),
|
|
346
352
|
) -> List[str]:
|
|
347
|
-
db = get_db(dbs, db_id)
|
|
353
|
+
db = await get_db(dbs, db_id, table)
|
|
348
354
|
if isinstance(db, AsyncBaseDb):
|
|
349
355
|
db = cast(AsyncBaseDb, db)
|
|
350
356
|
return await db.get_all_memory_topics()
|
|
@@ -389,6 +395,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
389
395
|
payload: UserMemoryCreateSchema,
|
|
390
396
|
memory_id: str = Path(description="Memory ID to update"),
|
|
391
397
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for update"),
|
|
398
|
+
table: Optional[str] = Query(default=None, description="Table to use for update"),
|
|
392
399
|
) -> UserMemorySchema:
|
|
393
400
|
if hasattr(request.state, "user_id"):
|
|
394
401
|
user_id = request.state.user_id
|
|
@@ -397,7 +404,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
397
404
|
if payload.user_id is None:
|
|
398
405
|
raise HTTPException(status_code=400, detail="User ID is required")
|
|
399
406
|
|
|
400
|
-
db = get_db(dbs, db_id)
|
|
407
|
+
db = await get_db(dbs, db_id, table)
|
|
401
408
|
|
|
402
409
|
if isinstance(db, AsyncBaseDb):
|
|
403
410
|
db = cast(AsyncBaseDb, db)
|
|
@@ -459,8 +466,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
459
466
|
limit: Optional[int] = Query(default=20, description="Number of user statistics to return per page"),
|
|
460
467
|
page: Optional[int] = Query(default=1, description="Page number for pagination"),
|
|
461
468
|
db_id: Optional[str] = Query(default=None, description="Database ID to query statistics from"),
|
|
469
|
+
table: Optional[str] = Query(default=None, description="Table to query statistics from"),
|
|
462
470
|
) -> PaginatedResponse[UserStatsSchema]:
|
|
463
|
-
db = get_db(dbs, db_id)
|
|
471
|
+
db = await get_db(dbs, db_id, table)
|
|
464
472
|
try:
|
|
465
473
|
if isinstance(db, AsyncBaseDb):
|
|
466
474
|
db = cast(AsyncBaseDb, db)
|
|
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def get_metrics_router(
|
|
25
|
-
dbs: dict[str, Union[BaseDb, AsyncBaseDb]], settings: AgnoAPISettings = AgnoAPISettings(), **kwargs
|
|
25
|
+
dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]], settings: AgnoAPISettings = AgnoAPISettings(), **kwargs
|
|
26
26
|
) -> APIRouter:
|
|
27
27
|
"""Create metrics router with comprehensive OpenAPI documentation for system metrics and analytics endpoints."""
|
|
28
28
|
router = APIRouter(
|
|
@@ -39,7 +39,7 @@ def get_metrics_router(
|
|
|
39
39
|
return attach_routes(router=router, dbs=dbs)
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]]) -> APIRouter:
|
|
42
|
+
def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]]) -> APIRouter:
|
|
43
43
|
@router.get(
|
|
44
44
|
"/metrics",
|
|
45
45
|
response_model=MetricsResponse,
|
|
@@ -99,9 +99,10 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
99
99
|
default=None, description="Ending date for metrics range (YYYY-MM-DD format)"
|
|
100
100
|
),
|
|
101
101
|
db_id: Optional[str] = Query(default=None, description="Database ID to query metrics from"),
|
|
102
|
+
table: Optional[str] = Query(default=None, description="The database table to use"),
|
|
102
103
|
) -> MetricsResponse:
|
|
103
104
|
try:
|
|
104
|
-
db = get_db(dbs, db_id)
|
|
105
|
+
db = await get_db(dbs, db_id, table)
|
|
105
106
|
if isinstance(db, AsyncBaseDb):
|
|
106
107
|
db = cast(AsyncBaseDb, db)
|
|
107
108
|
metrics, latest_updated_at = await db.get_metrics(starting_date=starting_date, ending_date=ending_date)
|
|
@@ -169,9 +170,10 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
169
170
|
)
|
|
170
171
|
async def calculate_metrics(
|
|
171
172
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for metrics calculation"),
|
|
173
|
+
table: Optional[str] = Query(default=None, description="Table to use for metrics calculation"),
|
|
172
174
|
) -> List[DayAggregatedMetrics]:
|
|
173
175
|
try:
|
|
174
|
-
db = get_db(dbs, db_id)
|
|
176
|
+
db = await get_db(dbs, db_id, table)
|
|
175
177
|
if isinstance(db, AsyncBaseDb):
|
|
176
178
|
db = cast(AsyncBaseDb, db)
|
|
177
179
|
result = await db.calculate_metrics()
|
|
@@ -35,7 +35,7 @@ logger = logging.getLogger(__name__)
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def get_session_router(
|
|
38
|
-
dbs: dict[str, Union[BaseDb, AsyncBaseDb]], settings: AgnoAPISettings = AgnoAPISettings()
|
|
38
|
+
dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]], settings: AgnoAPISettings = AgnoAPISettings()
|
|
39
39
|
) -> APIRouter:
|
|
40
40
|
"""Create session router with comprehensive OpenAPI documentation for session management endpoints."""
|
|
41
41
|
session_router = APIRouter(
|
|
@@ -52,7 +52,7 @@ def get_session_router(
|
|
|
52
52
|
return attach_routes(router=session_router, dbs=dbs)
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]]) -> APIRouter:
|
|
55
|
+
def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]]) -> APIRouter:
|
|
56
56
|
@router.get(
|
|
57
57
|
"/sessions",
|
|
58
58
|
response_model=PaginatedResponse[SessionSchema],
|
|
@@ -89,6 +89,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
89
89
|
},
|
|
90
90
|
},
|
|
91
91
|
400: {"description": "Invalid session type or filter parameters", "model": BadRequestResponse},
|
|
92
|
+
404: {"description": "Not found", "model": NotFoundResponse},
|
|
92
93
|
422: {"description": "Validation error in query parameters", "model": ValidationErrorResponse},
|
|
93
94
|
},
|
|
94
95
|
)
|
|
@@ -109,8 +110,12 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
109
110
|
sort_by: Optional[str] = Query(default="created_at", description="Field to sort sessions by"),
|
|
110
111
|
sort_order: Optional[SortOrder] = Query(default="desc", description="Sort order (asc or desc)"),
|
|
111
112
|
db_id: Optional[str] = Query(default=None, description="Database ID to query sessions from"),
|
|
113
|
+
table: Optional[str] = Query(default=None, description="The database table to use"),
|
|
112
114
|
) -> PaginatedResponse[SessionSchema]:
|
|
113
|
-
|
|
115
|
+
try:
|
|
116
|
+
db = await get_db(dbs, db_id, table)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
raise HTTPException(status_code=404, detail=f"{e}")
|
|
114
119
|
|
|
115
120
|
if hasattr(request.state, "user_id"):
|
|
116
121
|
user_id = request.state.user_id
|
|
@@ -202,7 +207,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
202
207
|
),
|
|
203
208
|
db_id: Optional[str] = Query(default=None, description="Database ID to create session in"),
|
|
204
209
|
) -> Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema]:
|
|
205
|
-
db = get_db(dbs, db_id)
|
|
210
|
+
db = await get_db(dbs, db_id)
|
|
206
211
|
|
|
207
212
|
# Get user_id from request state if available (from auth middleware)
|
|
208
213
|
user_id = create_session_request.user_id
|
|
@@ -373,8 +378,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
373
378
|
),
|
|
374
379
|
user_id: Optional[str] = Query(default=None, description="User ID to query session from"),
|
|
375
380
|
db_id: Optional[str] = Query(default=None, description="Database ID to query session from"),
|
|
381
|
+
table: Optional[str] = Query(default=None, description="Table to query session from"),
|
|
376
382
|
) -> Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema]:
|
|
377
|
-
db = get_db(dbs, db_id)
|
|
383
|
+
db = await get_db(dbs, db_id, table)
|
|
378
384
|
|
|
379
385
|
if hasattr(request.state, "user_id"):
|
|
380
386
|
user_id = request.state.user_id
|
|
@@ -528,8 +534,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
528
534
|
description="Filter runs created before this Unix timestamp (epoch time in seconds)",
|
|
529
535
|
),
|
|
530
536
|
db_id: Optional[str] = Query(default=None, description="Database ID to query runs from"),
|
|
537
|
+
table: Optional[str] = Query(default=None, description="Table to query runs from"),
|
|
531
538
|
) -> List[Union[RunSchema, TeamRunSchema, WorkflowRunSchema]]:
|
|
532
|
-
db = get_db(dbs, db_id)
|
|
539
|
+
db = await get_db(dbs, db_id, table)
|
|
533
540
|
|
|
534
541
|
if hasattr(request.state, "user_id"):
|
|
535
542
|
user_id = request.state.user_id
|
|
@@ -644,7 +651,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
644
651
|
user_id: Optional[str] = Query(default=None, description="User ID to query run from"),
|
|
645
652
|
db_id: Optional[str] = Query(default=None, description="Database ID to query run from"),
|
|
646
653
|
) -> Union[RunSchema, TeamRunSchema, WorkflowRunSchema]:
|
|
647
|
-
db = get_db(dbs, db_id)
|
|
654
|
+
db = await get_db(dbs, db_id)
|
|
648
655
|
|
|
649
656
|
if hasattr(request.state, "user_id"):
|
|
650
657
|
user_id = request.state.user_id
|
|
@@ -702,8 +709,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
702
709
|
async def delete_session(
|
|
703
710
|
session_id: str = Path(description="Session ID to delete"),
|
|
704
711
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for deletion"),
|
|
712
|
+
table: Optional[str] = Query(default=None, description="Table to use for deletion"),
|
|
705
713
|
) -> None:
|
|
706
|
-
db = get_db(dbs, db_id)
|
|
714
|
+
db = await get_db(dbs, db_id, table)
|
|
707
715
|
if isinstance(db, AsyncBaseDb):
|
|
708
716
|
db = cast(AsyncBaseDb, db)
|
|
709
717
|
await db.delete_session(session_id=session_id)
|
|
@@ -734,11 +742,12 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
734
742
|
default=SessionType.AGENT, description="Default session type filter", alias="type"
|
|
735
743
|
),
|
|
736
744
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for deletion"),
|
|
745
|
+
table: Optional[str] = Query(default=None, description="Table to use for deletion"),
|
|
737
746
|
) -> None:
|
|
738
747
|
if len(request.session_ids) != len(request.session_types):
|
|
739
748
|
raise HTTPException(status_code=400, detail="Session IDs and session types must have the same length")
|
|
740
749
|
|
|
741
|
-
db = get_db(dbs, db_id)
|
|
750
|
+
db = await get_db(dbs, db_id, table)
|
|
742
751
|
if isinstance(db, AsyncBaseDb):
|
|
743
752
|
db = cast(AsyncBaseDb, db)
|
|
744
753
|
await db.delete_sessions(session_ids=request.session_ids)
|
|
@@ -840,8 +849,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
840
849
|
),
|
|
841
850
|
session_name: str = Body(embed=True, description="New name for the session"),
|
|
842
851
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for rename operation"),
|
|
852
|
+
table: Optional[str] = Query(default=None, description="Table to use for rename operation"),
|
|
843
853
|
) -> Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema]:
|
|
844
|
-
db = get_db(dbs, db_id)
|
|
854
|
+
db = await get_db(dbs, db_id, table)
|
|
845
855
|
if isinstance(db, AsyncBaseDb):
|
|
846
856
|
db = cast(AsyncBaseDb, db)
|
|
847
857
|
session = await db.rename_session(
|
|
@@ -926,7 +936,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
926
936
|
user_id: Optional[str] = Query(default=None, description="User ID"),
|
|
927
937
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for update operation"),
|
|
928
938
|
) -> Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema]:
|
|
929
|
-
db = get_db(dbs, db_id)
|
|
939
|
+
db = await get_db(dbs, db_id)
|
|
930
940
|
|
|
931
941
|
if hasattr(request.state, "user_id"):
|
|
932
942
|
user_id = request.state.user_id
|
agno/os/utils.py
CHANGED
|
@@ -19,23 +19,69 @@ from agno.utils.log import logger
|
|
|
19
19
|
from agno.workflow.workflow import Workflow
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def get_db(
|
|
23
|
-
|
|
22
|
+
async def get_db(
|
|
23
|
+
dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]], db_id: Optional[str] = None, table: Optional[str] = None
|
|
24
|
+
) -> Union[BaseDb, AsyncBaseDb]:
|
|
25
|
+
"""Return the database with the given ID and/or table, or the first database if no ID/table is provided."""
|
|
26
|
+
|
|
27
|
+
if table and not db_id:
|
|
28
|
+
raise HTTPException(status_code=400, detail="The db_id query parameter is required when passing a table")
|
|
29
|
+
|
|
30
|
+
async def _has_table(db: Union[BaseDb, AsyncBaseDb], table_name: str) -> bool:
|
|
31
|
+
"""Check if this database has the specified table (configured and actually exists)."""
|
|
32
|
+
# First check if table name is configured
|
|
33
|
+
is_configured = (
|
|
34
|
+
hasattr(db, "session_table_name")
|
|
35
|
+
and db.session_table_name == table_name
|
|
36
|
+
or hasattr(db, "memory_table_name")
|
|
37
|
+
and db.memory_table_name == table_name
|
|
38
|
+
or hasattr(db, "metrics_table_name")
|
|
39
|
+
and db.metrics_table_name == table_name
|
|
40
|
+
or hasattr(db, "eval_table_name")
|
|
41
|
+
and db.eval_table_name == table_name
|
|
42
|
+
or hasattr(db, "knowledge_table_name")
|
|
43
|
+
and db.knowledge_table_name == table_name
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if not is_configured:
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
# Then check if table actually exists in the database
|
|
50
|
+
try:
|
|
51
|
+
if isinstance(db, AsyncBaseDb):
|
|
52
|
+
# For async databases, await the check
|
|
53
|
+
return await db.table_exists(table_name)
|
|
54
|
+
else:
|
|
55
|
+
# For sync databases, call directly
|
|
56
|
+
return db.table_exists(table_name)
|
|
57
|
+
except (NotImplementedError, AttributeError):
|
|
58
|
+
# If table_exists not implemented, fall back to configuration check
|
|
59
|
+
return is_configured
|
|
60
|
+
|
|
61
|
+
# If db_id is provided, first find the database with that ID
|
|
62
|
+
if db_id:
|
|
63
|
+
target_db_list = dbs.get(db_id)
|
|
64
|
+
if not target_db_list:
|
|
65
|
+
raise HTTPException(status_code=404, detail=f"No database found with id '{db_id}'")
|
|
66
|
+
|
|
67
|
+
# If table is also specified, search through all databases with this ID to find one with the table
|
|
68
|
+
if table:
|
|
69
|
+
for db in target_db_list:
|
|
70
|
+
if await _has_table(db, table):
|
|
71
|
+
return db
|
|
72
|
+
raise HTTPException(status_code=404, detail=f"No database with id '{db_id}' has table '{table}'")
|
|
73
|
+
|
|
74
|
+
# If no table specified, return the first database with this ID
|
|
75
|
+
return target_db_list[0]
|
|
24
76
|
|
|
25
77
|
# Raise if multiple databases are provided but no db_id is provided
|
|
26
|
-
if
|
|
78
|
+
if len(dbs) > 1:
|
|
27
79
|
raise HTTPException(
|
|
28
80
|
status_code=400, detail="The db_id query parameter is required when using multiple databases"
|
|
29
81
|
)
|
|
30
82
|
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
db = dbs.get(db_id)
|
|
34
|
-
if not db:
|
|
35
|
-
raise HTTPException(status_code=404, detail=f"Database with id '{db_id}' not found")
|
|
36
|
-
else:
|
|
37
|
-
db = next(iter(dbs.values()))
|
|
38
|
-
return db
|
|
83
|
+
# Return the first (and only) database
|
|
84
|
+
return next(db for dbs in dbs.values() for db in dbs)
|
|
39
85
|
|
|
40
86
|
|
|
41
87
|
def get_knowledge_instance_by_db_id(knowledge_instances: List[Knowledge], db_id: Optional[str] = None) -> Knowledge:
|