agno 2.1.10__py3-none-any.whl → 2.2.1__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 +1594 -1248
- agno/knowledge/knowledge.py +11 -0
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +14 -0
- agno/knowledge/types.py +1 -0
- agno/models/anthropic/claude.py +2 -2
- agno/models/base.py +4 -4
- agno/models/ollama/chat.py +7 -2
- agno/os/app.py +1 -1
- agno/os/interfaces/a2a/router.py +2 -2
- agno/os/interfaces/agui/router.py +2 -2
- agno/os/router.py +7 -7
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/health.py +6 -2
- agno/os/routers/knowledge/schemas.py +49 -47
- agno/os/routers/memory/schemas.py +16 -16
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +382 -7
- agno/os/schema.py +254 -231
- agno/os/utils.py +1 -1
- agno/run/agent.py +54 -1
- agno/run/team.py +48 -0
- agno/run/workflow.py +15 -5
- agno/session/summary.py +45 -13
- agno/session/team.py +90 -5
- agno/team/team.py +1130 -849
- agno/utils/agent.py +372 -0
- agno/utils/events.py +144 -2
- agno/utils/message.py +60 -0
- agno/utils/print_response/agent.py +10 -6
- agno/utils/print_response/team.py +6 -4
- agno/utils/print_response/workflow.py +7 -5
- agno/utils/team.py +9 -8
- agno/workflow/condition.py +17 -9
- agno/workflow/loop.py +18 -10
- agno/workflow/parallel.py +14 -6
- agno/workflow/router.py +16 -8
- agno/workflow/step.py +14 -6
- agno/workflow/steps.py +14 -6
- agno/workflow/workflow.py +331 -123
- {agno-2.1.10.dist-info → agno-2.2.1.dist-info}/METADATA +63 -23
- {agno-2.1.10.dist-info → agno-2.2.1.dist-info}/RECORD +45 -43
- {agno-2.1.10.dist-info → agno-2.2.1.dist-info}/WHEEL +0 -0
- {agno-2.1.10.dist-info → agno-2.2.1.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.10.dist-info → agno-2.2.1.dist-info}/top_level.txt +0 -0
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from typing import Any, Dict, List, Optional
|
|
3
3
|
|
|
4
|
-
from pydantic import BaseModel
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class DayAggregatedMetrics(BaseModel):
|
|
8
8
|
"""Aggregated metrics for a given day"""
|
|
9
9
|
|
|
10
|
-
id: str
|
|
10
|
+
id: str = Field(..., description="Unique identifier for the metrics record")
|
|
11
11
|
|
|
12
|
-
agent_runs_count: int
|
|
13
|
-
agent_sessions_count: int
|
|
14
|
-
team_runs_count: int
|
|
15
|
-
team_sessions_count: int
|
|
16
|
-
workflow_runs_count: int
|
|
17
|
-
workflow_sessions_count: int
|
|
18
|
-
users_count: int
|
|
19
|
-
token_metrics: Dict[str, Any]
|
|
20
|
-
model_metrics: List[Dict[str, Any]]
|
|
12
|
+
agent_runs_count: int = Field(..., description="Total number of agent runs", ge=0)
|
|
13
|
+
agent_sessions_count: int = Field(..., description="Total number of agent sessions", ge=0)
|
|
14
|
+
team_runs_count: int = Field(..., description="Total number of team runs", ge=0)
|
|
15
|
+
team_sessions_count: int = Field(..., description="Total number of team sessions", ge=0)
|
|
16
|
+
workflow_runs_count: int = Field(..., description="Total number of workflow runs", ge=0)
|
|
17
|
+
workflow_sessions_count: int = Field(..., description="Total number of workflow sessions", ge=0)
|
|
18
|
+
users_count: int = Field(..., description="Total number of unique users", ge=0)
|
|
19
|
+
token_metrics: Dict[str, Any] = Field(..., description="Token usage metrics (input, output, cached, etc.)")
|
|
20
|
+
model_metrics: List[Dict[str, Any]] = Field(..., description="Metrics grouped by model (model_id, provider, count)")
|
|
21
21
|
|
|
22
|
-
date: datetime
|
|
23
|
-
created_at: int
|
|
24
|
-
updated_at: int
|
|
22
|
+
date: datetime = Field(..., description="Date for which these metrics are aggregated")
|
|
23
|
+
created_at: int = Field(..., description="Unix timestamp when metrics were created", ge=0)
|
|
24
|
+
updated_at: int = Field(..., description="Unix timestamp when metrics were last updated", ge=0)
|
|
25
25
|
|
|
26
26
|
@classmethod
|
|
27
27
|
def from_dict(cls, metrics_dict: Dict[str, Any]) -> "DayAggregatedMetrics":
|
|
@@ -43,5 +43,5 @@ class DayAggregatedMetrics(BaseModel):
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
class MetricsResponse(BaseModel):
|
|
46
|
-
metrics: List[DayAggregatedMetrics]
|
|
47
|
-
updated_at: Optional[datetime]
|
|
46
|
+
metrics: List[DayAggregatedMetrics] = Field(..., description="List of daily aggregated metrics")
|
|
47
|
+
updated_at: Optional[datetime] = Field(None, description="Timestamp of the most recent metrics update")
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
|
|
2
|
+
import time
|
|
3
|
+
from typing import Any, List, Optional, Union, cast
|
|
4
|
+
from uuid import uuid4
|
|
3
5
|
|
|
4
6
|
from fastapi import APIRouter, Body, Depends, HTTPException, Path, Query, Request
|
|
5
7
|
|
|
@@ -8,6 +10,7 @@ from agno.os.auth import get_authentication_dependency
|
|
|
8
10
|
from agno.os.schema import (
|
|
9
11
|
AgentSessionDetailSchema,
|
|
10
12
|
BadRequestResponse,
|
|
13
|
+
CreateSessionRequest,
|
|
11
14
|
DeleteSessionRequest,
|
|
12
15
|
InternalServerErrorResponse,
|
|
13
16
|
NotFoundResponse,
|
|
@@ -19,12 +22,14 @@ from agno.os.schema import (
|
|
|
19
22
|
TeamRunSchema,
|
|
20
23
|
TeamSessionDetailSchema,
|
|
21
24
|
UnauthenticatedResponse,
|
|
25
|
+
UpdateSessionRequest,
|
|
22
26
|
ValidationErrorResponse,
|
|
23
27
|
WorkflowRunSchema,
|
|
24
28
|
WorkflowSessionDetailSchema,
|
|
25
29
|
)
|
|
26
30
|
from agno.os.settings import AgnoAPISettings
|
|
27
31
|
from agno.os.utils import get_db
|
|
32
|
+
from agno.session import AgentSession, TeamSession, WorkflowSession
|
|
28
33
|
|
|
29
34
|
logger = logging.getLogger(__name__)
|
|
30
35
|
|
|
@@ -146,6 +151,133 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
146
151
|
),
|
|
147
152
|
)
|
|
148
153
|
|
|
154
|
+
@router.post(
|
|
155
|
+
"/sessions",
|
|
156
|
+
response_model=Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema],
|
|
157
|
+
status_code=201,
|
|
158
|
+
operation_id="create_session",
|
|
159
|
+
summary="Create New Session",
|
|
160
|
+
description=(
|
|
161
|
+
"Create a new empty session with optional configuration. "
|
|
162
|
+
"Useful for pre-creating sessions with specific session_state, metadata, or other properties "
|
|
163
|
+
"before running any agent/team/workflow interactions. "
|
|
164
|
+
"The session can later be used by providing its session_id in run requests."
|
|
165
|
+
),
|
|
166
|
+
responses={
|
|
167
|
+
201: {
|
|
168
|
+
"description": "Session created successfully",
|
|
169
|
+
"content": {
|
|
170
|
+
"application/json": {
|
|
171
|
+
"examples": {
|
|
172
|
+
"agent_session_example": {
|
|
173
|
+
"summary": "Example created agent session",
|
|
174
|
+
"value": {
|
|
175
|
+
"user_id": "user-123",
|
|
176
|
+
"agent_session_id": "new-session-id",
|
|
177
|
+
"session_id": "new-session-id",
|
|
178
|
+
"session_name": "New Session",
|
|
179
|
+
"session_state": {"key": "value"},
|
|
180
|
+
"metadata": {"key": "value"},
|
|
181
|
+
"agent_id": "agent-1",
|
|
182
|
+
"created_at": "2025-10-21T12:00:00Z",
|
|
183
|
+
"updated_at": "2025-10-21T12:00:00Z",
|
|
184
|
+
},
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
400: {"description": "Invalid request parameters", "model": BadRequestResponse},
|
|
191
|
+
422: {"description": "Validation error", "model": ValidationErrorResponse},
|
|
192
|
+
500: {"description": "Failed to create session", "model": InternalServerErrorResponse},
|
|
193
|
+
},
|
|
194
|
+
)
|
|
195
|
+
async def create_session(
|
|
196
|
+
request: Request,
|
|
197
|
+
session_type: SessionType = Query(
|
|
198
|
+
default=SessionType.AGENT, alias="type", description="Type of session to create (agent, team, or workflow)"
|
|
199
|
+
),
|
|
200
|
+
create_session_request: CreateSessionRequest = Body(
|
|
201
|
+
default=CreateSessionRequest(), description="Session configuration data"
|
|
202
|
+
),
|
|
203
|
+
db_id: Optional[str] = Query(default=None, description="Database ID to create session in"),
|
|
204
|
+
) -> Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema]:
|
|
205
|
+
db = get_db(dbs, db_id)
|
|
206
|
+
|
|
207
|
+
# Get user_id from request state if available (from auth middleware)
|
|
208
|
+
user_id = create_session_request.user_id
|
|
209
|
+
if hasattr(request.state, "user_id"):
|
|
210
|
+
user_id = request.state.user_id
|
|
211
|
+
|
|
212
|
+
# Generate session_id if not provided
|
|
213
|
+
session_id = create_session_request.session_id or str(uuid4())
|
|
214
|
+
|
|
215
|
+
# Prepare session_data with session_state and session_name
|
|
216
|
+
session_data: dict[str, Any] = {}
|
|
217
|
+
if create_session_request.session_state is not None:
|
|
218
|
+
session_data["session_state"] = create_session_request.session_state
|
|
219
|
+
if create_session_request.session_name is not None:
|
|
220
|
+
session_data["session_name"] = create_session_request.session_name
|
|
221
|
+
|
|
222
|
+
current_time = int(time.time())
|
|
223
|
+
|
|
224
|
+
# Create the appropriate session type
|
|
225
|
+
session: Union[AgentSession, TeamSession, WorkflowSession]
|
|
226
|
+
if session_type == SessionType.AGENT:
|
|
227
|
+
session = AgentSession(
|
|
228
|
+
session_id=session_id,
|
|
229
|
+
agent_id=create_session_request.agent_id,
|
|
230
|
+
user_id=user_id,
|
|
231
|
+
session_data=session_data if session_data else None,
|
|
232
|
+
metadata=create_session_request.metadata,
|
|
233
|
+
created_at=current_time,
|
|
234
|
+
updated_at=current_time,
|
|
235
|
+
)
|
|
236
|
+
elif session_type == SessionType.TEAM:
|
|
237
|
+
session = TeamSession(
|
|
238
|
+
session_id=session_id,
|
|
239
|
+
team_id=create_session_request.team_id,
|
|
240
|
+
user_id=user_id,
|
|
241
|
+
session_data=session_data if session_data else None,
|
|
242
|
+
metadata=create_session_request.metadata,
|
|
243
|
+
created_at=current_time,
|
|
244
|
+
updated_at=current_time,
|
|
245
|
+
)
|
|
246
|
+
elif session_type == SessionType.WORKFLOW:
|
|
247
|
+
session = WorkflowSession(
|
|
248
|
+
session_id=session_id,
|
|
249
|
+
workflow_id=create_session_request.workflow_id,
|
|
250
|
+
user_id=user_id,
|
|
251
|
+
session_data=session_data if session_data else None,
|
|
252
|
+
metadata=create_session_request.metadata,
|
|
253
|
+
created_at=current_time,
|
|
254
|
+
updated_at=current_time,
|
|
255
|
+
)
|
|
256
|
+
else:
|
|
257
|
+
raise HTTPException(status_code=400, detail=f"Invalid session type: {session_type}")
|
|
258
|
+
|
|
259
|
+
# Upsert the session to the database
|
|
260
|
+
try:
|
|
261
|
+
if isinstance(db, AsyncBaseDb):
|
|
262
|
+
db = cast(AsyncBaseDb, db)
|
|
263
|
+
created_session = await db.upsert_session(session, deserialize=True)
|
|
264
|
+
else:
|
|
265
|
+
created_session = db.upsert_session(session, deserialize=True)
|
|
266
|
+
|
|
267
|
+
if not created_session:
|
|
268
|
+
raise HTTPException(status_code=500, detail="Failed to create session")
|
|
269
|
+
|
|
270
|
+
# Return appropriate schema based on session type
|
|
271
|
+
if session_type == SessionType.AGENT:
|
|
272
|
+
return AgentSessionDetailSchema.from_session(created_session) # type: ignore
|
|
273
|
+
elif session_type == SessionType.TEAM:
|
|
274
|
+
return TeamSessionDetailSchema.from_session(created_session) # type: ignore
|
|
275
|
+
else:
|
|
276
|
+
return WorkflowSessionDetailSchema.from_session(created_session) # type: ignore
|
|
277
|
+
except Exception as e:
|
|
278
|
+
logger.error(f"Error creating session: {e}")
|
|
279
|
+
raise HTTPException(status_code=500, detail=f"Failed to create session: {str(e)}")
|
|
280
|
+
|
|
149
281
|
@router.get(
|
|
150
282
|
"/sessions/{session_id}",
|
|
151
283
|
response_model=Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema],
|
|
@@ -271,8 +403,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
271
403
|
operation_id="get_session_runs",
|
|
272
404
|
summary="Get Session Runs",
|
|
273
405
|
description=(
|
|
274
|
-
"Retrieve all runs (executions) for a specific session
|
|
275
|
-
"interactions or executions within a session.
|
|
406
|
+
"Retrieve all runs (executions) for a specific session with optional timestamp filtering. "
|
|
407
|
+
"Runs represent individual interactions or executions within a session. "
|
|
408
|
+
"Response schema varies based on session type."
|
|
276
409
|
),
|
|
277
410
|
responses={
|
|
278
411
|
200: {
|
|
@@ -386,6 +519,14 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
386
519
|
default=SessionType.AGENT, description="Session type (agent, team, or workflow)", alias="type"
|
|
387
520
|
),
|
|
388
521
|
user_id: Optional[str] = Query(default=None, description="User ID to query runs from"),
|
|
522
|
+
created_after: Optional[int] = Query(
|
|
523
|
+
default=None,
|
|
524
|
+
description="Filter runs created after this Unix timestamp (epoch time in seconds)",
|
|
525
|
+
),
|
|
526
|
+
created_before: Optional[int] = Query(
|
|
527
|
+
default=None,
|
|
528
|
+
description="Filter runs created before this Unix timestamp (epoch time in seconds)",
|
|
529
|
+
),
|
|
389
530
|
db_id: Optional[str] = Query(default=None, description="Database ID to query runs from"),
|
|
390
531
|
) -> List[Union[RunSchema, TeamRunSchema, WorkflowRunSchema]]:
|
|
391
532
|
db = get_db(dbs, db_id)
|
|
@@ -393,6 +534,10 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
393
534
|
if hasattr(request.state, "user_id"):
|
|
394
535
|
user_id = request.state.user_id
|
|
395
536
|
|
|
537
|
+
# Use timestamp filters directly (already in epoch format)
|
|
538
|
+
start_timestamp = created_after
|
|
539
|
+
end_timestamp = created_before
|
|
540
|
+
|
|
396
541
|
if isinstance(db, AsyncBaseDb):
|
|
397
542
|
db = cast(AsyncBaseDb, db)
|
|
398
543
|
session = await db.get_session(
|
|
@@ -408,15 +553,33 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
408
553
|
|
|
409
554
|
runs = session.get("runs") # type: ignore
|
|
410
555
|
if not runs:
|
|
411
|
-
|
|
556
|
+
return []
|
|
557
|
+
|
|
558
|
+
# Filter runs by timestamp if specified
|
|
559
|
+
# TODO: Move this filtering into the DB layer
|
|
560
|
+
filtered_runs = []
|
|
561
|
+
for run in runs:
|
|
562
|
+
if start_timestamp or end_timestamp:
|
|
563
|
+
run_created_at = run.get("created_at")
|
|
564
|
+
if run_created_at:
|
|
565
|
+
# created_at is stored as epoch int
|
|
566
|
+
if start_timestamp and run_created_at < start_timestamp:
|
|
567
|
+
continue
|
|
568
|
+
if end_timestamp and run_created_at > end_timestamp:
|
|
569
|
+
continue
|
|
570
|
+
|
|
571
|
+
filtered_runs.append(run)
|
|
572
|
+
|
|
573
|
+
if not filtered_runs:
|
|
574
|
+
return []
|
|
412
575
|
|
|
413
576
|
run_responses: List[Union[RunSchema, TeamRunSchema, WorkflowRunSchema]] = []
|
|
414
577
|
|
|
415
578
|
if session_type == SessionType.AGENT:
|
|
416
|
-
return [RunSchema.from_dict(run) for run in
|
|
579
|
+
return [RunSchema.from_dict(run) for run in filtered_runs]
|
|
417
580
|
|
|
418
581
|
elif session_type == SessionType.TEAM:
|
|
419
|
-
for run in
|
|
582
|
+
for run in filtered_runs:
|
|
420
583
|
if run.get("agent_id") is not None:
|
|
421
584
|
run_responses.append(RunSchema.from_dict(run))
|
|
422
585
|
elif run.get("team_id") is not None:
|
|
@@ -424,7 +587,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
424
587
|
return run_responses
|
|
425
588
|
|
|
426
589
|
elif session_type == SessionType.WORKFLOW:
|
|
427
|
-
for run in
|
|
590
|
+
for run in filtered_runs:
|
|
428
591
|
if run.get("workflow_id") is not None:
|
|
429
592
|
run_responses.append(WorkflowRunSchema.from_dict(run))
|
|
430
593
|
elif run.get("team_id") is not None:
|
|
@@ -435,6 +598,93 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
435
598
|
else:
|
|
436
599
|
raise HTTPException(status_code=400, detail=f"Invalid session type: {session_type}")
|
|
437
600
|
|
|
601
|
+
@router.get(
|
|
602
|
+
"/sessions/{session_id}/runs/{run_id}",
|
|
603
|
+
response_model=Union[RunSchema, TeamRunSchema, WorkflowRunSchema],
|
|
604
|
+
status_code=200,
|
|
605
|
+
operation_id="get_session_run",
|
|
606
|
+
summary="Get Run by ID",
|
|
607
|
+
description=(
|
|
608
|
+
"Retrieve a specific run by its ID from a session. Response schema varies based on the "
|
|
609
|
+
"run type (agent run, team run, or workflow run)."
|
|
610
|
+
),
|
|
611
|
+
responses={
|
|
612
|
+
200: {
|
|
613
|
+
"description": "Run retrieved successfully",
|
|
614
|
+
"content": {
|
|
615
|
+
"application/json": {
|
|
616
|
+
"examples": {
|
|
617
|
+
"agent_run": {
|
|
618
|
+
"summary": "Example agent run",
|
|
619
|
+
"value": {
|
|
620
|
+
"run_id": "fcdf50f0-7c32-4593-b2ef-68a558774340",
|
|
621
|
+
"parent_run_id": "80056af0-c7a5-4d69-b6a2-c3eba9f040e0",
|
|
622
|
+
"agent_id": "basic-agent",
|
|
623
|
+
"user_id": "user_123",
|
|
624
|
+
"run_input": "Which tools do you have access to?",
|
|
625
|
+
"content": "I don't have access to external tools.",
|
|
626
|
+
"created_at": 1728499200,
|
|
627
|
+
},
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
},
|
|
633
|
+
404: {"description": "Session or run not found", "model": NotFoundResponse},
|
|
634
|
+
422: {"description": "Invalid session type", "model": ValidationErrorResponse},
|
|
635
|
+
},
|
|
636
|
+
)
|
|
637
|
+
async def get_session_run(
|
|
638
|
+
request: Request,
|
|
639
|
+
session_id: str = Path(description="Session ID to get run from"),
|
|
640
|
+
run_id: str = Path(description="Run ID to retrieve"),
|
|
641
|
+
session_type: SessionType = Query(
|
|
642
|
+
default=SessionType.AGENT, description="Session type (agent, team, or workflow)", alias="type"
|
|
643
|
+
),
|
|
644
|
+
user_id: Optional[str] = Query(default=None, description="User ID to query run from"),
|
|
645
|
+
db_id: Optional[str] = Query(default=None, description="Database ID to query run from"),
|
|
646
|
+
) -> Union[RunSchema, TeamRunSchema, WorkflowRunSchema]:
|
|
647
|
+
db = get_db(dbs, db_id)
|
|
648
|
+
|
|
649
|
+
if hasattr(request.state, "user_id"):
|
|
650
|
+
user_id = request.state.user_id
|
|
651
|
+
|
|
652
|
+
if isinstance(db, AsyncBaseDb):
|
|
653
|
+
db = cast(AsyncBaseDb, db)
|
|
654
|
+
session = await db.get_session(
|
|
655
|
+
session_id=session_id, session_type=session_type, user_id=user_id, deserialize=False
|
|
656
|
+
)
|
|
657
|
+
else:
|
|
658
|
+
session = db.get_session(
|
|
659
|
+
session_id=session_id, session_type=session_type, user_id=user_id, deserialize=False
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
if not session:
|
|
663
|
+
raise HTTPException(status_code=404, detail=f"Session with ID {session_id} not found")
|
|
664
|
+
|
|
665
|
+
runs = session.get("runs") # type: ignore
|
|
666
|
+
if not runs:
|
|
667
|
+
raise HTTPException(status_code=404, detail=f"Session with ID {session_id} has no runs")
|
|
668
|
+
|
|
669
|
+
# Find the specific run
|
|
670
|
+
# TODO: Move this filtering into the DB layer
|
|
671
|
+
target_run = None
|
|
672
|
+
for run in runs:
|
|
673
|
+
if run.get("run_id") == run_id:
|
|
674
|
+
target_run = run
|
|
675
|
+
break
|
|
676
|
+
|
|
677
|
+
if not target_run:
|
|
678
|
+
raise HTTPException(status_code=404, detail=f"Run with ID {run_id} not found in session {session_id}")
|
|
679
|
+
|
|
680
|
+
# Return the appropriate schema based on run type
|
|
681
|
+
if target_run.get("workflow_id") is not None:
|
|
682
|
+
return WorkflowRunSchema.from_dict(target_run)
|
|
683
|
+
elif target_run.get("team_id") is not None:
|
|
684
|
+
return TeamRunSchema.from_dict(target_run)
|
|
685
|
+
else:
|
|
686
|
+
return RunSchema.from_dict(target_run)
|
|
687
|
+
|
|
438
688
|
@router.delete(
|
|
439
689
|
"/sessions/{session_id}",
|
|
440
690
|
status_code=204,
|
|
@@ -609,4 +859,129 @@ def attach_routes(router: APIRouter, dbs: dict[str, Union[BaseDb, AsyncBaseDb]])
|
|
|
609
859
|
else:
|
|
610
860
|
return WorkflowSessionDetailSchema.from_session(session) # type: ignore
|
|
611
861
|
|
|
862
|
+
@router.patch(
|
|
863
|
+
"/sessions/{session_id}",
|
|
864
|
+
response_model=Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema],
|
|
865
|
+
status_code=200,
|
|
866
|
+
operation_id="update_session",
|
|
867
|
+
summary="Update Session",
|
|
868
|
+
description=(
|
|
869
|
+
"Update session properties such as session_name, session_state, metadata, or summary. "
|
|
870
|
+
"Use this endpoint to modify the session name, update state, add metadata, or update the session summary."
|
|
871
|
+
),
|
|
872
|
+
responses={
|
|
873
|
+
200: {
|
|
874
|
+
"description": "Session updated successfully",
|
|
875
|
+
"content": {
|
|
876
|
+
"application/json": {
|
|
877
|
+
"examples": {
|
|
878
|
+
"update_summary": {
|
|
879
|
+
"summary": "Update session summary",
|
|
880
|
+
"value": {
|
|
881
|
+
"summary": {
|
|
882
|
+
"summary": "The user discussed project planning with the agent.",
|
|
883
|
+
"updated_at": "2025-10-21T14:30:00Z",
|
|
884
|
+
}
|
|
885
|
+
},
|
|
886
|
+
},
|
|
887
|
+
"update_metadata": {
|
|
888
|
+
"summary": "Update session metadata",
|
|
889
|
+
"value": {
|
|
890
|
+
"metadata": {
|
|
891
|
+
"tags": ["planning", "project"],
|
|
892
|
+
"priority": "high",
|
|
893
|
+
}
|
|
894
|
+
},
|
|
895
|
+
},
|
|
896
|
+
"update_session_name": {
|
|
897
|
+
"summary": "Update session name",
|
|
898
|
+
"value": {"session_name": "Updated Session Name"},
|
|
899
|
+
},
|
|
900
|
+
"update_session_state": {
|
|
901
|
+
"summary": "Update session state",
|
|
902
|
+
"value": {
|
|
903
|
+
"session_state": {
|
|
904
|
+
"step": "completed",
|
|
905
|
+
"context": "Project planning finished",
|
|
906
|
+
"progress": 100,
|
|
907
|
+
}
|
|
908
|
+
},
|
|
909
|
+
},
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
},
|
|
913
|
+
},
|
|
914
|
+
404: {"description": "Session not found", "model": NotFoundResponse},
|
|
915
|
+
422: {"description": "Invalid request", "model": ValidationErrorResponse},
|
|
916
|
+
500: {"description": "Failed to update session", "model": InternalServerErrorResponse},
|
|
917
|
+
},
|
|
918
|
+
)
|
|
919
|
+
async def update_session(
|
|
920
|
+
request: Request,
|
|
921
|
+
session_id: str = Path(description="Session ID to update"),
|
|
922
|
+
session_type: SessionType = Query(
|
|
923
|
+
default=SessionType.AGENT, description="Session type (agent, team, or workflow)", alias="type"
|
|
924
|
+
),
|
|
925
|
+
update_data: UpdateSessionRequest = Body(description="Session update data"),
|
|
926
|
+
user_id: Optional[str] = Query(default=None, description="User ID"),
|
|
927
|
+
db_id: Optional[str] = Query(default=None, description="Database ID to use for update operation"),
|
|
928
|
+
) -> Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema]:
|
|
929
|
+
db = get_db(dbs, db_id)
|
|
930
|
+
|
|
931
|
+
if hasattr(request.state, "user_id"):
|
|
932
|
+
user_id = request.state.user_id
|
|
933
|
+
|
|
934
|
+
# Get the existing session
|
|
935
|
+
if isinstance(db, AsyncBaseDb):
|
|
936
|
+
db = cast(AsyncBaseDb, db)
|
|
937
|
+
existing_session = await db.get_session(
|
|
938
|
+
session_id=session_id, session_type=session_type, user_id=user_id, deserialize=True
|
|
939
|
+
)
|
|
940
|
+
else:
|
|
941
|
+
existing_session = db.get_session(
|
|
942
|
+
session_id=session_id, session_type=session_type, user_id=user_id, deserialize=True
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
if not existing_session:
|
|
946
|
+
raise HTTPException(status_code=404, detail=f"Session with id '{session_id}' not found")
|
|
947
|
+
|
|
948
|
+
# Update session properties
|
|
949
|
+
# Handle session_name - stored in session_data
|
|
950
|
+
if update_data.session_name is not None:
|
|
951
|
+
if existing_session.session_data is None: # type: ignore
|
|
952
|
+
existing_session.session_data = {} # type: ignore
|
|
953
|
+
existing_session.session_data["session_name"] = update_data.session_name # type: ignore
|
|
954
|
+
|
|
955
|
+
# Handle session_state - stored in session_data
|
|
956
|
+
if update_data.session_state is not None:
|
|
957
|
+
if existing_session.session_data is None: # type: ignore
|
|
958
|
+
existing_session.session_data = {} # type: ignore
|
|
959
|
+
existing_session.session_data["session_state"] = update_data.session_state # type: ignore
|
|
960
|
+
|
|
961
|
+
if update_data.metadata is not None:
|
|
962
|
+
existing_session.metadata = update_data.metadata # type: ignore
|
|
963
|
+
|
|
964
|
+
if update_data.summary is not None:
|
|
965
|
+
from agno.session.summary import SessionSummary
|
|
966
|
+
|
|
967
|
+
existing_session.summary = SessionSummary.from_dict(update_data.summary) # type: ignore
|
|
968
|
+
|
|
969
|
+
# Upsert the updated session
|
|
970
|
+
if isinstance(db, AsyncBaseDb):
|
|
971
|
+
db = cast(AsyncBaseDb, db)
|
|
972
|
+
updated_session = await db.upsert_session(existing_session, deserialize=True) # type: ignore
|
|
973
|
+
else:
|
|
974
|
+
updated_session = db.upsert_session(existing_session, deserialize=True) # type: ignore
|
|
975
|
+
|
|
976
|
+
if not updated_session:
|
|
977
|
+
raise HTTPException(status_code=500, detail="Failed to update session")
|
|
978
|
+
|
|
979
|
+
# Return appropriate schema based on session type
|
|
980
|
+
if session_type == SessionType.AGENT:
|
|
981
|
+
return AgentSessionDetailSchema.from_session(updated_session) # type: ignore
|
|
982
|
+
elif session_type == SessionType.TEAM:
|
|
983
|
+
return TeamSessionDetailSchema.from_session(updated_session) # type: ignore
|
|
984
|
+
else:
|
|
985
|
+
return WorkflowSessionDetailSchema.from_session(updated_session) # type: ignore
|
|
986
|
+
|
|
612
987
|
return router
|