agno 2.0.0a1__py3-none-any.whl → 2.0.0rc1__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 +390 -33
- agno/api/agent.py +2 -2
- agno/api/evals.py +2 -2
- agno/api/os.py +1 -1
- agno/api/settings.py +2 -2
- agno/api/team.py +2 -2
- agno/db/dynamo/dynamo.py +0 -6
- agno/db/firestore/firestore.py +0 -6
- agno/db/in_memory/in_memory_db.py +0 -6
- agno/db/json/json_db.py +0 -6
- agno/db/mongo/mongo.py +0 -6
- agno/db/mysql/utils.py +0 -1
- agno/db/postgres/postgres.py +0 -10
- agno/db/postgres/utils.py +0 -1
- agno/db/redis/redis.py +0 -4
- agno/db/singlestore/singlestore.py +0 -10
- agno/db/singlestore/utils.py +0 -1
- agno/db/sqlite/sqlite.py +0 -4
- agno/db/sqlite/utils.py +0 -1
- agno/integrations/discord/client.py +5 -1
- agno/knowledge/embedder/aws_bedrock.py +2 -2
- agno/models/anthropic/claude.py +2 -49
- agno/models/message.py +7 -6
- agno/os/app.py +158 -62
- agno/os/interfaces/agui/agui.py +1 -1
- agno/os/interfaces/agui/utils.py +16 -9
- agno/os/interfaces/slack/slack.py +2 -3
- agno/os/interfaces/whatsapp/whatsapp.py +2 -3
- agno/os/mcp.py +255 -0
- agno/os/router.py +33 -7
- agno/os/routers/evals/evals.py +9 -5
- agno/os/routers/knowledge/knowledge.py +30 -7
- agno/os/routers/memory/memory.py +17 -8
- agno/os/routers/metrics/metrics.py +4 -2
- agno/os/routers/session/session.py +8 -3
- agno/os/settings.py +0 -1
- agno/run/agent.py +87 -2
- agno/run/cancel.py +0 -2
- agno/team/team.py +2 -2
- agno/tools/function.py +65 -7
- agno/tools/linear.py +1 -1
- agno/utils/gemini.py +31 -1
- agno/utils/models/claude.py +49 -0
- agno/utils/streamlit.py +454 -0
- agno/workflow/workflow.py +8 -1
- {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/METADATA +1 -1
- {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/RECORD +50 -48
- {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/WHEEL +0 -0
- {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/top_level.txt +0 -0
agno/os/router.py
CHANGED
|
@@ -311,7 +311,7 @@ def get_base_router(
|
|
|
311
311
|
|
|
312
312
|
# -- Main Routes ---
|
|
313
313
|
|
|
314
|
-
@router.get("/health", tags=["Core"])
|
|
314
|
+
@router.get("/health", tags=["Core"], operation_id="health_check")
|
|
315
315
|
async def health_check():
|
|
316
316
|
return JSONResponse(content={"status": "ok"})
|
|
317
317
|
|
|
@@ -320,6 +320,7 @@ def get_base_router(
|
|
|
320
320
|
response_model=ConfigResponse,
|
|
321
321
|
response_model_exclude_none=True,
|
|
322
322
|
tags=["Core"],
|
|
323
|
+
operation_id="get_config",
|
|
323
324
|
)
|
|
324
325
|
async def config() -> ConfigResponse:
|
|
325
326
|
return ConfigResponse(
|
|
@@ -347,6 +348,7 @@ def get_base_router(
|
|
|
347
348
|
response_model=List[Model],
|
|
348
349
|
response_model_exclude_none=True,
|
|
349
350
|
tags=["Core"],
|
|
351
|
+
operation_id="get_models",
|
|
350
352
|
)
|
|
351
353
|
async def get_models():
|
|
352
354
|
"""Return the list of all models used by agents and teams in the contextual OS"""
|
|
@@ -368,7 +370,9 @@ def get_base_router(
|
|
|
368
370
|
|
|
369
371
|
# -- Agent routes ---
|
|
370
372
|
|
|
371
|
-
@router.post(
|
|
373
|
+
@router.post(
|
|
374
|
+
"/agents/{agent_id}/runs", tags=["Agents"], operation_id="create_agent_run", response_model_exclude_none=True
|
|
375
|
+
)
|
|
372
376
|
async def create_agent_run(
|
|
373
377
|
agent_id: str,
|
|
374
378
|
message: str = Form(...),
|
|
@@ -475,6 +479,8 @@ def get_base_router(
|
|
|
475
479
|
@router.post(
|
|
476
480
|
"/agents/{agent_id}/runs/{run_id}/cancel",
|
|
477
481
|
tags=["Agents"],
|
|
482
|
+
operation_id="cancel_agent_run",
|
|
483
|
+
response_model_exclude_none=True,
|
|
478
484
|
)
|
|
479
485
|
async def cancel_agent_run(
|
|
480
486
|
agent_id: str,
|
|
@@ -492,6 +498,8 @@ def get_base_router(
|
|
|
492
498
|
@router.post(
|
|
493
499
|
"/agents/{agent_id}/runs/{run_id}/continue",
|
|
494
500
|
tags=["Agents"],
|
|
501
|
+
operation_id="continue_agent_run",
|
|
502
|
+
response_model_exclude_none=True,
|
|
495
503
|
)
|
|
496
504
|
async def continue_agent_run(
|
|
497
505
|
agent_id: str,
|
|
@@ -555,6 +563,7 @@ def get_base_router(
|
|
|
555
563
|
response_model=List[AgentResponse],
|
|
556
564
|
response_model_exclude_none=True,
|
|
557
565
|
tags=["Agents"],
|
|
566
|
+
operation_id="get_agents",
|
|
558
567
|
)
|
|
559
568
|
async def get_agents():
|
|
560
569
|
"""Return the list of all Agents present in the contextual OS"""
|
|
@@ -572,6 +581,7 @@ def get_base_router(
|
|
|
572
581
|
response_model=AgentResponse,
|
|
573
582
|
response_model_exclude_none=True,
|
|
574
583
|
tags=["Agents"],
|
|
584
|
+
operation_id="get_agent",
|
|
575
585
|
)
|
|
576
586
|
async def get_agent(agent_id: str):
|
|
577
587
|
agent = get_agent_by_id(agent_id, os.agents)
|
|
@@ -582,7 +592,9 @@ def get_base_router(
|
|
|
582
592
|
|
|
583
593
|
# -- Team routes ---
|
|
584
594
|
|
|
585
|
-
@router.post(
|
|
595
|
+
@router.post(
|
|
596
|
+
"/teams/{team_id}/runs", tags=["Teams"], operation_id="create_team_run", response_model_exclude_none=True
|
|
597
|
+
)
|
|
586
598
|
async def create_team_run(
|
|
587
599
|
team_id: str,
|
|
588
600
|
message: str = Form(...),
|
|
@@ -686,6 +698,8 @@ def get_base_router(
|
|
|
686
698
|
@router.post(
|
|
687
699
|
"/teams/{team_id}/runs/{run_id}/cancel",
|
|
688
700
|
tags=["Teams"],
|
|
701
|
+
operation_id="cancel_team_run",
|
|
702
|
+
response_model_exclude_none=True,
|
|
689
703
|
)
|
|
690
704
|
async def cancel_team_run(
|
|
691
705
|
team_id: str,
|
|
@@ -705,6 +719,7 @@ def get_base_router(
|
|
|
705
719
|
response_model=List[TeamResponse],
|
|
706
720
|
response_model_exclude_none=True,
|
|
707
721
|
tags=["Teams"],
|
|
722
|
+
operation_id="get_teams",
|
|
708
723
|
)
|
|
709
724
|
async def get_teams():
|
|
710
725
|
"""Return the list of all Teams present in the contextual OS"""
|
|
@@ -722,6 +737,7 @@ def get_base_router(
|
|
|
722
737
|
response_model=TeamResponse,
|
|
723
738
|
response_model_exclude_none=True,
|
|
724
739
|
tags=["Teams"],
|
|
740
|
+
operation_id="get_team",
|
|
725
741
|
)
|
|
726
742
|
async def get_team(team_id: str):
|
|
727
743
|
team = get_team_by_id(team_id, os.teams)
|
|
@@ -749,9 +765,10 @@ def get_base_router(
|
|
|
749
765
|
elif action == "start-workflow":
|
|
750
766
|
# Handle workflow execution directly via WebSocket
|
|
751
767
|
await handle_workflow_via_websocket(websocket, message, os)
|
|
752
|
-
|
|
753
768
|
except Exception as e:
|
|
754
|
-
|
|
769
|
+
if "1012" not in str(e):
|
|
770
|
+
logger.error(f"WebSocket error: {e}")
|
|
771
|
+
finally:
|
|
755
772
|
# Clean up any run_ids associated with this websocket
|
|
756
773
|
runs_to_remove = [run_id for run_id, ws in websocket_manager.active_connections.items() if ws == websocket]
|
|
757
774
|
for run_id in runs_to_remove:
|
|
@@ -762,6 +779,7 @@ def get_base_router(
|
|
|
762
779
|
response_model=List[WorkflowResponse],
|
|
763
780
|
response_model_exclude_none=True,
|
|
764
781
|
tags=["Workflows"],
|
|
782
|
+
operation_id="get_workflows",
|
|
765
783
|
)
|
|
766
784
|
async def get_workflows():
|
|
767
785
|
if os.workflows is None:
|
|
@@ -774,6 +792,7 @@ def get_base_router(
|
|
|
774
792
|
response_model=WorkflowResponse,
|
|
775
793
|
response_model_exclude_none=True,
|
|
776
794
|
tags=["Workflows"],
|
|
795
|
+
operation_id="get_workflow",
|
|
777
796
|
)
|
|
778
797
|
async def get_workflow(workflow_id: str):
|
|
779
798
|
workflow = get_workflow_by_id(workflow_id, os.workflows)
|
|
@@ -782,7 +801,12 @@ def get_base_router(
|
|
|
782
801
|
|
|
783
802
|
return WorkflowResponse.from_workflow(workflow)
|
|
784
803
|
|
|
785
|
-
@router.post(
|
|
804
|
+
@router.post(
|
|
805
|
+
"/workflows/{workflow_id}/runs",
|
|
806
|
+
tags=["Workflows"],
|
|
807
|
+
operation_id="create_workflow_run",
|
|
808
|
+
response_model_exclude_none=True,
|
|
809
|
+
)
|
|
786
810
|
async def create_workflow_run(
|
|
787
811
|
workflow_id: str,
|
|
788
812
|
message: str = Form(...),
|
|
@@ -828,7 +852,9 @@ def get_base_router(
|
|
|
828
852
|
# Handle unexpected runtime errors
|
|
829
853
|
raise HTTPException(status_code=500, detail=f"Error running workflow: {str(e)}")
|
|
830
854
|
|
|
831
|
-
@router.post(
|
|
855
|
+
@router.post(
|
|
856
|
+
"/workflows/{workflow_id}/runs/{run_id}/cancel", tags=["Workflows"], operation_id="cancel_workflow_run"
|
|
857
|
+
)
|
|
832
858
|
async def cancel_workflow_run(workflow_id: str, run_id: str):
|
|
833
859
|
workflow = get_workflow_by_id(workflow_id, os.workflows)
|
|
834
860
|
|
agno/os/routers/evals/evals.py
CHANGED
|
@@ -37,7 +37,9 @@ def get_eval_router(
|
|
|
37
37
|
def attach_routes(
|
|
38
38
|
router: APIRouter, dbs: dict[str, BaseDb], agents: Optional[List[Agent]] = None, teams: Optional[List[Team]] = None
|
|
39
39
|
) -> APIRouter:
|
|
40
|
-
@router.get(
|
|
40
|
+
@router.get(
|
|
41
|
+
"/eval-runs", response_model=PaginatedResponse[EvalSchema], status_code=200, operation_id="get_eval_runs"
|
|
42
|
+
)
|
|
41
43
|
async def get_eval_runs(
|
|
42
44
|
agent_id: Optional[str] = Query(default=None, description="Agent ID"),
|
|
43
45
|
team_id: Optional[str] = Query(default=None, description="Team ID"),
|
|
@@ -76,7 +78,7 @@ def attach_routes(
|
|
|
76
78
|
),
|
|
77
79
|
)
|
|
78
80
|
|
|
79
|
-
@router.get("/eval-runs/{eval_run_id}", response_model=EvalSchema, status_code=200)
|
|
81
|
+
@router.get("/eval-runs/{eval_run_id}", response_model=EvalSchema, status_code=200, operation_id="get_eval_run")
|
|
80
82
|
async def get_eval_run(
|
|
81
83
|
eval_run_id: str,
|
|
82
84
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
@@ -88,7 +90,7 @@ def attach_routes(
|
|
|
88
90
|
|
|
89
91
|
return EvalSchema.from_dict(eval_run) # type: ignore
|
|
90
92
|
|
|
91
|
-
@router.delete("/eval-runs", status_code=204)
|
|
93
|
+
@router.delete("/eval-runs", status_code=204, operation_id="delete_eval_runs")
|
|
92
94
|
async def delete_eval_runs(
|
|
93
95
|
request: DeleteEvalRunsRequest,
|
|
94
96
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
@@ -99,7 +101,9 @@ def attach_routes(
|
|
|
99
101
|
except Exception as e:
|
|
100
102
|
raise HTTPException(status_code=500, detail=f"Failed to delete eval runs: {e}")
|
|
101
103
|
|
|
102
|
-
@router.patch(
|
|
104
|
+
@router.patch(
|
|
105
|
+
"/eval-runs/{eval_run_id}", response_model=EvalSchema, status_code=200, operation_id="update_eval_run"
|
|
106
|
+
)
|
|
103
107
|
async def update_eval_run(
|
|
104
108
|
eval_run_id: str,
|
|
105
109
|
request: UpdateEvalRunRequest,
|
|
@@ -116,7 +120,7 @@ def attach_routes(
|
|
|
116
120
|
|
|
117
121
|
return EvalSchema.from_dict(eval_run) # type: ignore
|
|
118
122
|
|
|
119
|
-
@router.post("/eval-runs", response_model=EvalSchema, status_code=200)
|
|
123
|
+
@router.post("/eval-runs", response_model=EvalSchema, status_code=200, operation_id="run_eval")
|
|
120
124
|
async def run_eval(
|
|
121
125
|
eval_run_input: EvalRunInput,
|
|
122
126
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
@@ -37,7 +37,9 @@ def get_knowledge_router(
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> APIRouter:
|
|
40
|
-
@router.post(
|
|
40
|
+
@router.post(
|
|
41
|
+
"/knowledge/content", response_model=ContentResponseSchema, status_code=202, operation_id="upload_content"
|
|
42
|
+
)
|
|
41
43
|
async def upload_content(
|
|
42
44
|
background_tasks: BackgroundTasks,
|
|
43
45
|
name: Optional[str] = Form(None),
|
|
@@ -126,7 +128,12 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
126
128
|
)
|
|
127
129
|
return response
|
|
128
130
|
|
|
129
|
-
@router.patch(
|
|
131
|
+
@router.patch(
|
|
132
|
+
"/knowledge/content/{content_id}",
|
|
133
|
+
response_model=ContentResponseSchema,
|
|
134
|
+
status_code=200,
|
|
135
|
+
operation_id="update_content",
|
|
136
|
+
)
|
|
130
137
|
async def update_content(
|
|
131
138
|
content_id: str = Path(..., description="Content ID"),
|
|
132
139
|
name: Optional[str] = Form(None, description="Content name"),
|
|
@@ -172,7 +179,12 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
172
179
|
|
|
173
180
|
return ContentResponseSchema.from_dict(updated_content_dict)
|
|
174
181
|
|
|
175
|
-
@router.get(
|
|
182
|
+
@router.get(
|
|
183
|
+
"/knowledge/content",
|
|
184
|
+
response_model=PaginatedResponse[ContentResponseSchema],
|
|
185
|
+
status_code=200,
|
|
186
|
+
operation_id="get_content",
|
|
187
|
+
)
|
|
176
188
|
def get_content(
|
|
177
189
|
limit: Optional[int] = Query(default=20, description="Number of content entries to return"),
|
|
178
190
|
page: Optional[int] = Query(default=1, description="Page number"),
|
|
@@ -209,7 +221,12 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
209
221
|
),
|
|
210
222
|
)
|
|
211
223
|
|
|
212
|
-
@router.get(
|
|
224
|
+
@router.get(
|
|
225
|
+
"/knowledge/content/{content_id}",
|
|
226
|
+
response_model=ContentResponseSchema,
|
|
227
|
+
status_code=200,
|
|
228
|
+
operation_id="get_content_by_id",
|
|
229
|
+
)
|
|
213
230
|
def get_content_by_id(
|
|
214
231
|
content_id: str,
|
|
215
232
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
@@ -241,6 +258,7 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
241
258
|
response_model=ContentResponseSchema,
|
|
242
259
|
status_code=200,
|
|
243
260
|
response_model_exclude_none=True,
|
|
261
|
+
operation_id="delete_content_by_id",
|
|
244
262
|
)
|
|
245
263
|
def delete_content_by_id(
|
|
246
264
|
content_id: str,
|
|
@@ -254,7 +272,7 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
254
272
|
id=content_id,
|
|
255
273
|
)
|
|
256
274
|
|
|
257
|
-
@router.delete("/knowledge/content", status_code=200)
|
|
275
|
+
@router.delete("/knowledge/content", status_code=200, operation_id="delete_all_content")
|
|
258
276
|
def delete_all_content(
|
|
259
277
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
260
278
|
):
|
|
@@ -263,7 +281,12 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
263
281
|
knowledge.remove_all_content()
|
|
264
282
|
return "success"
|
|
265
283
|
|
|
266
|
-
@router.get(
|
|
284
|
+
@router.get(
|
|
285
|
+
"/knowledge/content/{content_id}/status",
|
|
286
|
+
status_code=200,
|
|
287
|
+
response_model=ContentStatusResponse,
|
|
288
|
+
operation_id="get_content_status",
|
|
289
|
+
)
|
|
267
290
|
def get_content_status(
|
|
268
291
|
content_id: str,
|
|
269
292
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
@@ -301,7 +324,7 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
301
324
|
|
|
302
325
|
return ContentStatusResponse(status=status, status_message=status_message or "")
|
|
303
326
|
|
|
304
|
-
@router.get("/knowledge/config", status_code=200)
|
|
327
|
+
@router.get("/knowledge/config", status_code=200, operation_id="get_knowledge_config")
|
|
305
328
|
def get_config(
|
|
306
329
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
307
330
|
) -> ConfigResponseSchema:
|
agno/os/routers/memory/memory.py
CHANGED
|
@@ -28,7 +28,7 @@ def get_memory_router(dbs: dict[str, BaseDb], settings: AgnoAPISettings = AgnoAP
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
31
|
-
@router.post("/memories", response_model=UserMemorySchema, status_code=200)
|
|
31
|
+
@router.post("/memories", response_model=UserMemorySchema, status_code=200, operation_id="create_memory")
|
|
32
32
|
async def create_memory(
|
|
33
33
|
payload: UserMemoryCreateSchema,
|
|
34
34
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
@@ -48,14 +48,14 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
48
48
|
|
|
49
49
|
return UserMemorySchema.from_dict(user_memory) # type: ignore
|
|
50
50
|
|
|
51
|
-
@router.delete("/memories/{memory_id}", status_code=204)
|
|
51
|
+
@router.delete("/memories/{memory_id}", status_code=204, operation_id="delete_memory")
|
|
52
52
|
async def delete_memory(
|
|
53
53
|
memory_id: str = Path(), db_id: Optional[str] = Query(default=None, description="The ID of the database to use")
|
|
54
54
|
) -> None:
|
|
55
55
|
db = get_db(dbs, db_id)
|
|
56
56
|
db.delete_user_memory(memory_id=memory_id)
|
|
57
57
|
|
|
58
|
-
@router.delete("/memories", status_code=204)
|
|
58
|
+
@router.delete("/memories", status_code=204, operation_id="delete_memories")
|
|
59
59
|
async def delete_memories(
|
|
60
60
|
request: DeleteMemoriesRequest,
|
|
61
61
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
@@ -63,7 +63,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
63
63
|
db = get_db(dbs, db_id)
|
|
64
64
|
db.delete_user_memories(memory_ids=request.memory_ids)
|
|
65
65
|
|
|
66
|
-
@router.get(
|
|
66
|
+
@router.get(
|
|
67
|
+
"/memories", response_model=PaginatedResponse[UserMemorySchema], status_code=200, operation_id="get_memories"
|
|
68
|
+
)
|
|
67
69
|
async def get_memories(
|
|
68
70
|
user_id: Optional[str] = Query(default=None, description="Filter memories by user ID"),
|
|
69
71
|
agent_id: Optional[str] = Query(default=None, description="Filter memories by agent ID"),
|
|
@@ -99,7 +101,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
99
101
|
),
|
|
100
102
|
)
|
|
101
103
|
|
|
102
|
-
@router.get("/memories/{memory_id}", response_model=UserMemorySchema, status_code=200)
|
|
104
|
+
@router.get("/memories/{memory_id}", response_model=UserMemorySchema, status_code=200, operation_id="get_memory")
|
|
103
105
|
async def get_memory(
|
|
104
106
|
memory_id: str = Path(),
|
|
105
107
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
@@ -111,14 +113,16 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
111
113
|
|
|
112
114
|
return UserMemorySchema.from_dict(user_memory) # type: ignore
|
|
113
115
|
|
|
114
|
-
@router.get("/memories/topics", response_model=List[str], status_code=200)
|
|
116
|
+
@router.get("/memories/topics", response_model=List[str], status_code=200, operation_id="get_memory_topics")
|
|
115
117
|
async def get_topics(
|
|
116
118
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
117
119
|
) -> List[str]:
|
|
118
120
|
db = get_db(dbs, db_id)
|
|
119
121
|
return db.get_all_memory_topics()
|
|
120
122
|
|
|
121
|
-
@router.patch(
|
|
123
|
+
@router.patch(
|
|
124
|
+
"/memories/{memory_id}", response_model=UserMemorySchema, status_code=200, operation_id="update_memory"
|
|
125
|
+
)
|
|
122
126
|
async def update_memory(
|
|
123
127
|
payload: UserMemoryCreateSchema,
|
|
124
128
|
memory_id: str = Path(),
|
|
@@ -139,7 +143,12 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
139
143
|
|
|
140
144
|
return UserMemorySchema.from_dict(user_memory) # type: ignore
|
|
141
145
|
|
|
142
|
-
@router.get(
|
|
146
|
+
@router.get(
|
|
147
|
+
"/user_memory_stats",
|
|
148
|
+
response_model=PaginatedResponse[UserStatsSchema],
|
|
149
|
+
status_code=200,
|
|
150
|
+
operation_id="get_user_memory_stats",
|
|
151
|
+
)
|
|
143
152
|
async def get_user_memory_stats(
|
|
144
153
|
limit: Optional[int] = Query(default=20, description="Number of items to return"),
|
|
145
154
|
page: Optional[int] = Query(default=1, description="Page number"),
|
|
@@ -20,7 +20,7 @@ def get_metrics_router(dbs: dict[str, BaseDb], settings: AgnoAPISettings = AgnoA
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
23
|
-
@router.get("/metrics", response_model=MetricsResponse, status_code=200)
|
|
23
|
+
@router.get("/metrics", response_model=MetricsResponse, status_code=200, operation_id="get_metrics")
|
|
24
24
|
async def get_metrics(
|
|
25
25
|
starting_date: Optional[date] = Query(default=None, description="Starting date to filter metrics (YYYY-MM-DD)"),
|
|
26
26
|
ending_date: Optional[date] = Query(default=None, description="Ending date to filter metrics (YYYY-MM-DD)"),
|
|
@@ -40,7 +40,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
40
40
|
except Exception as e:
|
|
41
41
|
raise HTTPException(status_code=500, detail=f"Error getting metrics: {str(e)}")
|
|
42
42
|
|
|
43
|
-
@router.post(
|
|
43
|
+
@router.post(
|
|
44
|
+
"/metrics/refresh", response_model=List[DayAggregatedMetrics], status_code=200, operation_id="refresh_metrics"
|
|
45
|
+
)
|
|
44
46
|
async def calculate_metrics(
|
|
45
47
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
46
48
|
) -> List[DayAggregatedMetrics]:
|
|
@@ -30,7 +30,9 @@ def get_session_router(dbs: dict[str, BaseDb], settings: AgnoAPISettings = AgnoA
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
33
|
-
@router.get(
|
|
33
|
+
@router.get(
|
|
34
|
+
"/sessions", response_model=PaginatedResponse[SessionSchema], status_code=200, operation_id="get_sessions"
|
|
35
|
+
)
|
|
34
36
|
async def get_sessions(
|
|
35
37
|
session_type: SessionType = Query(default=SessionType.AGENT, alias="type"),
|
|
36
38
|
component_id: Optional[str] = Query(default=None, description="Filter sessions by component ID"),
|
|
@@ -69,6 +71,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
69
71
|
"/sessions/{session_id}",
|
|
70
72
|
response_model=Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema],
|
|
71
73
|
status_code=200,
|
|
74
|
+
operation_id="get_session_by_id",
|
|
72
75
|
)
|
|
73
76
|
async def get_session_by_id(
|
|
74
77
|
session_id: str = Path(...),
|
|
@@ -91,6 +94,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
91
94
|
"/sessions/{session_id}/runs",
|
|
92
95
|
response_model=Union[List[RunSchema], List[TeamRunSchema], List[WorkflowRunSchema]],
|
|
93
96
|
status_code=200,
|
|
97
|
+
operation_id="get_session_runs",
|
|
94
98
|
)
|
|
95
99
|
async def get_session_runs(
|
|
96
100
|
session_id: str = Path(..., description="Session ID", alias="session_id"),
|
|
@@ -118,7 +122,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
118
122
|
else:
|
|
119
123
|
return [RunSchema.from_dict(run) for run in runs]
|
|
120
124
|
|
|
121
|
-
@router.delete("/sessions/{session_id}", status_code=204)
|
|
125
|
+
@router.delete("/sessions/{session_id}", status_code=204, operation_id="delete_session")
|
|
122
126
|
async def delete_session(
|
|
123
127
|
session_id: str = Path(...),
|
|
124
128
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
@@ -126,7 +130,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
126
130
|
db = get_db(dbs, db_id)
|
|
127
131
|
db.delete_session(session_id=session_id)
|
|
128
132
|
|
|
129
|
-
@router.delete("/sessions", status_code=204)
|
|
133
|
+
@router.delete("/sessions", status_code=204, operation_id="delete_sessions")
|
|
130
134
|
async def delete_sessions(
|
|
131
135
|
request: DeleteSessionRequest,
|
|
132
136
|
session_type: SessionType = Query(default=SessionType.AGENT, description="Session type filter", alias="type"),
|
|
@@ -141,6 +145,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
141
145
|
@router.post(
|
|
142
146
|
"/sessions/{session_id}/rename",
|
|
143
147
|
response_model=Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema],
|
|
148
|
+
operation_id="rename_session",
|
|
144
149
|
)
|
|
145
150
|
async def rename_session(
|
|
146
151
|
session_id: str = Path(...),
|
agno/os/settings.py
CHANGED
agno/run/agent.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
from dataclasses import asdict, dataclass, field
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from time import time
|
|
4
|
-
from typing import Any, Dict, List, Optional, Union
|
|
4
|
+
from typing import Any, Dict, List, Optional, Sequence, Union
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
|
-
from agno.media import AudioArtifact, AudioResponse, ImageArtifact, VideoArtifact
|
|
8
|
+
from agno.media import AudioArtifact, AudioResponse, File, ImageArtifact, VideoArtifact
|
|
9
9
|
from agno.models.message import Citations, Message
|
|
10
10
|
from agno.models.metrics import Metrics
|
|
11
11
|
from agno.models.response import ToolExecution
|
|
@@ -281,6 +281,78 @@ def run_output_event_from_dict(data: dict) -> BaseRunOutputEvent:
|
|
|
281
281
|
return cls.from_dict(data) # type: ignore
|
|
282
282
|
|
|
283
283
|
|
|
284
|
+
@dataclass
|
|
285
|
+
class RunInput:
|
|
286
|
+
"""Container for the raw input data passed to Agent.run().
|
|
287
|
+
|
|
288
|
+
This captures the original input exactly as provided by the user,
|
|
289
|
+
separate from the processed messages that go to the model.
|
|
290
|
+
|
|
291
|
+
Attributes:
|
|
292
|
+
input_content: The literal input message/content passed to run()
|
|
293
|
+
images: Images directly passed to run()
|
|
294
|
+
videos: Videos directly passed to run()
|
|
295
|
+
audios: Audio files directly passed to run()
|
|
296
|
+
files: Files directly passed to run()
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
input_content: Optional[Union[str, List, Dict, Message, BaseModel, List[Message]]] = None
|
|
300
|
+
images: Optional[Sequence[ImageArtifact]] = None
|
|
301
|
+
videos: Optional[Sequence[VideoArtifact]] = None
|
|
302
|
+
audios: Optional[Sequence[AudioArtifact]] = None
|
|
303
|
+
files: Optional[Sequence[File]] = None
|
|
304
|
+
|
|
305
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
306
|
+
"""Convert to dictionary representation"""
|
|
307
|
+
result: Dict[str, Any] = {}
|
|
308
|
+
|
|
309
|
+
if self.input_content is not None:
|
|
310
|
+
if isinstance(self.input_content, (str)):
|
|
311
|
+
result["input_content"] = self.input_content
|
|
312
|
+
elif isinstance(self.input_content, BaseModel):
|
|
313
|
+
result["input_content"] = self.input_content.model_dump(exclude_none=True)
|
|
314
|
+
elif isinstance(self.input_content, Message):
|
|
315
|
+
result["input_content"] = self.input_content.to_dict()
|
|
316
|
+
elif (
|
|
317
|
+
isinstance(self.input_content, list)
|
|
318
|
+
and self.input_content
|
|
319
|
+
and isinstance(self.input_content[0], Message)
|
|
320
|
+
):
|
|
321
|
+
result["input_content"] = [m.to_dict() for m in self.input_content]
|
|
322
|
+
else:
|
|
323
|
+
result["input_content"] = self.input_content
|
|
324
|
+
|
|
325
|
+
if self.images:
|
|
326
|
+
result["images"] = [img.to_dict() for img in self.images]
|
|
327
|
+
if self.videos:
|
|
328
|
+
result["videos"] = [vid.to_dict() for vid in self.videos]
|
|
329
|
+
if self.audios:
|
|
330
|
+
result["audios"] = [aud.to_dict() for aud in self.audios]
|
|
331
|
+
|
|
332
|
+
return result
|
|
333
|
+
|
|
334
|
+
@classmethod
|
|
335
|
+
def from_dict(cls, data: Dict[str, Any]) -> "RunInput":
|
|
336
|
+
"""Create RunInput from dictionary"""
|
|
337
|
+
images = None
|
|
338
|
+
if data.get("images"):
|
|
339
|
+
images = [ImageArtifact.model_validate(img_data) for img_data in data["images"]]
|
|
340
|
+
|
|
341
|
+
videos = None
|
|
342
|
+
if data.get("videos"):
|
|
343
|
+
videos = [VideoArtifact.model_validate(vid_data) for vid_data in data["videos"]]
|
|
344
|
+
|
|
345
|
+
audios = None
|
|
346
|
+
if data.get("audios"):
|
|
347
|
+
audios = [AudioArtifact.model_validate(aud_data) for aud_data in data["audios"]]
|
|
348
|
+
|
|
349
|
+
files = None
|
|
350
|
+
if data.get("files"):
|
|
351
|
+
files = [File.model_validate(file_data) for file_data in data["files"]]
|
|
352
|
+
|
|
353
|
+
return cls(input_content=data.get("input_content"), images=images, videos=videos, audios=audios, files=files)
|
|
354
|
+
|
|
355
|
+
|
|
284
356
|
@dataclass
|
|
285
357
|
class RunOutput:
|
|
286
358
|
"""Response returned by Agent.run() or Workflow.run() functions"""
|
|
@@ -313,6 +385,9 @@ class RunOutput:
|
|
|
313
385
|
audio: Optional[List[AudioArtifact]] = None # Audio attached to the response
|
|
314
386
|
response_audio: Optional[AudioResponse] = None # Model audio response
|
|
315
387
|
|
|
388
|
+
# Input media and messages from user
|
|
389
|
+
input: Optional[RunInput] = None
|
|
390
|
+
|
|
316
391
|
citations: Optional[Citations] = None
|
|
317
392
|
references: Optional[List[MessageReferences]] = None
|
|
318
393
|
|
|
@@ -363,6 +438,7 @@ class RunOutput:
|
|
|
363
438
|
"videos",
|
|
364
439
|
"audio",
|
|
365
440
|
"response_audio",
|
|
441
|
+
"input",
|
|
366
442
|
"citations",
|
|
367
443
|
"events",
|
|
368
444
|
"additional_input",
|
|
@@ -446,6 +522,9 @@ class RunOutput:
|
|
|
446
522
|
else:
|
|
447
523
|
_dict["tools"].append(tool)
|
|
448
524
|
|
|
525
|
+
if self.input is not None:
|
|
526
|
+
_dict["input"] = self.input.to_dict()
|
|
527
|
+
|
|
449
528
|
return _dict
|
|
450
529
|
|
|
451
530
|
def to_json(self) -> str:
|
|
@@ -488,6 +567,11 @@ class RunOutput:
|
|
|
488
567
|
response_audio = data.pop("response_audio", None)
|
|
489
568
|
response_audio = AudioResponse.model_validate(response_audio) if response_audio else None
|
|
490
569
|
|
|
570
|
+
input_data = data.pop("input", None)
|
|
571
|
+
input_obj = None
|
|
572
|
+
if input_data:
|
|
573
|
+
input_obj = RunInput.from_dict(input_data)
|
|
574
|
+
|
|
491
575
|
metrics = data.pop("metrics", None)
|
|
492
576
|
if metrics:
|
|
493
577
|
metrics = Metrics(**metrics)
|
|
@@ -518,6 +602,7 @@ class RunOutput:
|
|
|
518
602
|
audio=audio,
|
|
519
603
|
videos=videos,
|
|
520
604
|
response_audio=response_audio,
|
|
605
|
+
input=input_obj,
|
|
521
606
|
events=events,
|
|
522
607
|
additional_input=additional_input,
|
|
523
608
|
reasoning_steps=reasoning_steps,
|
agno/run/cancel.py
CHANGED
|
@@ -18,7 +18,6 @@ class RunCancellationManager:
|
|
|
18
18
|
"""Register a new run as not cancelled."""
|
|
19
19
|
with self._lock:
|
|
20
20
|
self._cancelled_runs[run_id] = False
|
|
21
|
-
logger.debug(f"Registered run {run_id} for cancellation tracking")
|
|
22
21
|
|
|
23
22
|
def cancel_run(self, run_id: str) -> bool:
|
|
24
23
|
"""Cancel a run by marking it as cancelled.
|
|
@@ -45,7 +44,6 @@ class RunCancellationManager:
|
|
|
45
44
|
with self._lock:
|
|
46
45
|
if run_id in self._cancelled_runs:
|
|
47
46
|
del self._cancelled_runs[run_id]
|
|
48
|
-
logger.debug(f"Cleaned up cancellation tracking for run {run_id}")
|
|
49
47
|
|
|
50
48
|
def raise_if_cancelled(self, run_id: str) -> None:
|
|
51
49
|
"""Check if a run should be cancelled and raise exception if so."""
|
agno/team/team.py
CHANGED
|
@@ -25,7 +25,7 @@ from typing import (
|
|
|
25
25
|
get_args,
|
|
26
26
|
overload,
|
|
27
27
|
)
|
|
28
|
-
from uuid import
|
|
28
|
+
from uuid import uuid4
|
|
29
29
|
|
|
30
30
|
from pydantic import BaseModel
|
|
31
31
|
|
|
@@ -563,7 +563,7 @@ class Team:
|
|
|
563
563
|
"""
|
|
564
564
|
if self.id is None:
|
|
565
565
|
if self.name is not None:
|
|
566
|
-
self.id =
|
|
566
|
+
self.id = self.name.lower().replace(" ", "-")
|
|
567
567
|
else:
|
|
568
568
|
self.id = str(uuid4())
|
|
569
569
|
|