agno 2.3.26__py3-none-any.whl → 2.4.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/__init__.py +4 -0
- agno/agent/agent.py +1368 -541
- agno/agent/remote.py +13 -0
- agno/db/base.py +339 -0
- agno/db/postgres/async_postgres.py +116 -12
- agno/db/postgres/postgres.py +1242 -25
- agno/db/postgres/schemas.py +48 -1
- agno/db/sqlite/async_sqlite.py +119 -4
- agno/db/sqlite/schemas.py +51 -0
- agno/db/sqlite/sqlite.py +1186 -13
- agno/db/utils.py +37 -1
- agno/integrations/discord/client.py +12 -1
- agno/knowledge/__init__.py +4 -0
- agno/knowledge/chunking/code.py +1 -1
- agno/knowledge/chunking/semantic.py +1 -1
- agno/knowledge/chunking/strategy.py +4 -0
- agno/knowledge/filesystem.py +412 -0
- agno/knowledge/knowledge.py +3722 -2182
- agno/knowledge/protocol.py +134 -0
- agno/knowledge/reader/arxiv_reader.py +2 -2
- agno/knowledge/reader/base.py +9 -7
- agno/knowledge/reader/csv_reader.py +236 -13
- agno/knowledge/reader/docx_reader.py +2 -2
- agno/knowledge/reader/field_labeled_csv_reader.py +169 -5
- agno/knowledge/reader/firecrawl_reader.py +2 -2
- agno/knowledge/reader/json_reader.py +2 -2
- agno/knowledge/reader/markdown_reader.py +2 -2
- agno/knowledge/reader/pdf_reader.py +5 -4
- agno/knowledge/reader/pptx_reader.py +2 -2
- agno/knowledge/reader/reader_factory.py +118 -1
- agno/knowledge/reader/s3_reader.py +2 -2
- agno/knowledge/reader/tavily_reader.py +2 -2
- agno/knowledge/reader/text_reader.py +2 -2
- agno/knowledge/reader/web_search_reader.py +2 -2
- agno/knowledge/reader/website_reader.py +5 -3
- agno/knowledge/reader/wikipedia_reader.py +2 -2
- agno/knowledge/reader/youtube_reader.py +2 -2
- agno/knowledge/remote_content/__init__.py +29 -0
- agno/knowledge/remote_content/config.py +204 -0
- agno/knowledge/remote_content/remote_content.py +74 -17
- agno/knowledge/utils.py +37 -29
- agno/learn/__init__.py +6 -0
- agno/learn/machine.py +35 -0
- agno/learn/schemas.py +82 -11
- agno/learn/stores/__init__.py +3 -0
- agno/learn/stores/decision_log.py +1156 -0
- agno/learn/stores/learned_knowledge.py +6 -6
- agno/models/anthropic/claude.py +24 -0
- agno/models/aws/bedrock.py +20 -0
- agno/models/base.py +60 -6
- agno/models/cerebras/cerebras.py +34 -2
- agno/models/cohere/chat.py +25 -0
- agno/models/google/gemini.py +50 -5
- agno/models/litellm/chat.py +38 -0
- agno/models/n1n/__init__.py +3 -0
- agno/models/n1n/n1n.py +57 -0
- agno/models/openai/chat.py +25 -1
- agno/models/openrouter/openrouter.py +46 -0
- agno/models/perplexity/perplexity.py +2 -0
- agno/models/response.py +16 -0
- agno/os/app.py +83 -44
- agno/os/interfaces/slack/router.py +10 -1
- agno/os/interfaces/whatsapp/router.py +6 -0
- agno/os/middleware/__init__.py +2 -0
- agno/os/middleware/trailing_slash.py +27 -0
- agno/os/router.py +1 -0
- agno/os/routers/agents/router.py +29 -16
- agno/os/routers/agents/schema.py +6 -4
- agno/os/routers/components/__init__.py +3 -0
- agno/os/routers/components/components.py +475 -0
- agno/os/routers/evals/schemas.py +4 -3
- agno/os/routers/health.py +3 -3
- agno/os/routers/knowledge/knowledge.py +128 -3
- agno/os/routers/knowledge/schemas.py +12 -0
- agno/os/routers/memory/schemas.py +4 -2
- agno/os/routers/metrics/metrics.py +9 -11
- agno/os/routers/metrics/schemas.py +10 -6
- agno/os/routers/registry/__init__.py +3 -0
- agno/os/routers/registry/registry.py +337 -0
- agno/os/routers/teams/router.py +20 -8
- agno/os/routers/teams/schema.py +6 -4
- agno/os/routers/traces/traces.py +5 -5
- agno/os/routers/workflows/router.py +38 -11
- agno/os/routers/workflows/schema.py +1 -1
- agno/os/schema.py +92 -26
- agno/os/utils.py +84 -19
- agno/reasoning/anthropic.py +2 -2
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +6 -7
- agno/reasoning/gemini.py +2 -2
- agno/reasoning/helpers.py +6 -7
- agno/reasoning/manager.py +4 -10
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +2 -2
- agno/reasoning/vertexai.py +2 -2
- agno/registry/__init__.py +3 -0
- agno/registry/registry.py +68 -0
- agno/run/agent.py +59 -0
- agno/run/base.py +7 -0
- agno/run/team.py +57 -0
- agno/skills/agent_skills.py +10 -3
- agno/team/__init__.py +3 -1
- agno/team/team.py +1165 -330
- agno/tools/duckduckgo.py +25 -71
- agno/tools/exa.py +0 -21
- agno/tools/function.py +35 -83
- agno/tools/knowledge.py +9 -4
- agno/tools/mem0.py +11 -10
- agno/tools/memory.py +47 -46
- agno/tools/parallel.py +0 -7
- agno/tools/reasoning.py +30 -23
- agno/tools/tavily.py +4 -1
- agno/tools/websearch.py +93 -0
- agno/tools/website.py +1 -1
- agno/tools/wikipedia.py +1 -1
- agno/tools/workflow.py +48 -47
- agno/utils/agent.py +42 -5
- agno/utils/events.py +160 -2
- agno/utils/print_response/agent.py +0 -31
- agno/utils/print_response/team.py +0 -2
- agno/utils/print_response/workflow.py +0 -2
- agno/utils/team.py +61 -11
- agno/vectordb/lancedb/lance_db.py +4 -1
- agno/vectordb/mongodb/mongodb.py +1 -1
- agno/vectordb/pgvector/pgvector.py +3 -3
- agno/vectordb/qdrant/qdrant.py +4 -4
- agno/workflow/__init__.py +3 -1
- agno/workflow/condition.py +0 -21
- agno/workflow/loop.py +0 -21
- agno/workflow/parallel.py +0 -21
- agno/workflow/router.py +0 -21
- agno/workflow/step.py +117 -24
- agno/workflow/steps.py +0 -21
- agno/workflow/workflow.py +427 -63
- {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/METADATA +49 -76
- {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/RECORD +140 -126
- {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/WHEEL +1 -1
- {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/top_level.txt +0 -0
|
@@ -14,6 +14,7 @@ from fastapi import (
|
|
|
14
14
|
from fastapi.responses import JSONResponse, StreamingResponse
|
|
15
15
|
from pydantic import BaseModel
|
|
16
16
|
|
|
17
|
+
from agno.db.base import BaseDb
|
|
17
18
|
from agno.exceptions import InputCheckError, OutputCheckError
|
|
18
19
|
from agno.os.auth import (
|
|
19
20
|
get_auth_token_from_request,
|
|
@@ -61,7 +62,9 @@ async def handle_workflow_via_websocket(websocket: WebSocket, message: dict, os:
|
|
|
61
62
|
return
|
|
62
63
|
|
|
63
64
|
# Get workflow from OS
|
|
64
|
-
workflow = get_workflow_by_id(
|
|
65
|
+
workflow = get_workflow_by_id(
|
|
66
|
+
workflow_id=workflow_id, workflows=os.workflows, db=os.db, registry=os.registry, create_fresh=True
|
|
67
|
+
)
|
|
65
68
|
if not workflow:
|
|
66
69
|
await websocket.send_text(json.dumps({"event": "error", "error": f"Workflow {workflow_id} not found"}))
|
|
67
70
|
return
|
|
@@ -141,7 +144,9 @@ async def handle_workflow_subscription(websocket: WebSocket, message: dict, os:
|
|
|
141
144
|
if buffer_status is None:
|
|
142
145
|
# Run not in buffer - check database
|
|
143
146
|
if workflow_id and session_id:
|
|
144
|
-
workflow = get_workflow_by_id(
|
|
147
|
+
workflow = get_workflow_by_id(
|
|
148
|
+
workflow_id=workflow_id, workflows=os.workflows, db=os.db, registry=os.registry, create_fresh=True
|
|
149
|
+
)
|
|
145
150
|
if workflow and isinstance(workflow, Workflow):
|
|
146
151
|
workflow_run = await workflow.aget_run_output(run_id, session_id)
|
|
147
152
|
|
|
@@ -526,9 +531,6 @@ def get_workflow_router(
|
|
|
526
531
|
},
|
|
527
532
|
)
|
|
528
533
|
async def get_workflows(request: Request) -> List[WorkflowSummaryResponse]:
|
|
529
|
-
if os.workflows is None:
|
|
530
|
-
return []
|
|
531
|
-
|
|
532
534
|
# Filter workflows based on user's scopes (only if authorization is enabled)
|
|
533
535
|
if getattr(request.state, "authorization_enabled", False):
|
|
534
536
|
from agno.os.auth import filter_resources_by_access, get_accessible_resources
|
|
@@ -538,11 +540,24 @@ def get_workflow_router(
|
|
|
538
540
|
if not accessible_ids:
|
|
539
541
|
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
|
540
542
|
|
|
541
|
-
accessible_workflows = filter_resources_by_access(request, os.workflows, "workflows")
|
|
543
|
+
accessible_workflows = filter_resources_by_access(request, os.workflows or [], "workflows")
|
|
542
544
|
else:
|
|
543
|
-
accessible_workflows = os.workflows
|
|
545
|
+
accessible_workflows = os.workflows or []
|
|
546
|
+
|
|
547
|
+
workflows: List[WorkflowSummaryResponse] = []
|
|
548
|
+
if accessible_workflows:
|
|
549
|
+
for workflow in accessible_workflows:
|
|
550
|
+
workflows.append(WorkflowSummaryResponse.from_workflow(workflow=workflow))
|
|
551
|
+
|
|
552
|
+
if os.db and isinstance(os.db, BaseDb):
|
|
553
|
+
from agno.workflow.workflow import get_workflows
|
|
544
554
|
|
|
545
|
-
|
|
555
|
+
db_workflows = get_workflows(db=os.db, registry=os.registry)
|
|
556
|
+
if db_workflows:
|
|
557
|
+
for db_workflow in db_workflows:
|
|
558
|
+
workflows.append(WorkflowSummaryResponse.from_workflow(workflow=db_workflow))
|
|
559
|
+
|
|
560
|
+
return workflows
|
|
546
561
|
|
|
547
562
|
@router.get(
|
|
548
563
|
"/workflows/{workflow_id}",
|
|
@@ -571,7 +586,9 @@ def get_workflow_router(
|
|
|
571
586
|
dependencies=[Depends(require_resource_access("workflows", "read", "workflow_id"))],
|
|
572
587
|
)
|
|
573
588
|
async def get_workflow(workflow_id: str, request: Request) -> WorkflowResponse:
|
|
574
|
-
workflow = get_workflow_by_id(
|
|
589
|
+
workflow = get_workflow_by_id(
|
|
590
|
+
workflow_id=workflow_id, workflows=os.workflows, db=os.db, registry=os.registry, create_fresh=True
|
|
591
|
+
)
|
|
575
592
|
if workflow is None:
|
|
576
593
|
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
577
594
|
if isinstance(workflow, RemoteWorkflow):
|
|
@@ -622,6 +639,7 @@ def get_workflow_router(
|
|
|
622
639
|
stream: bool = Form(True),
|
|
623
640
|
session_id: Optional[str] = Form(None),
|
|
624
641
|
user_id: Optional[str] = Form(None),
|
|
642
|
+
version: Optional[int] = Form(None),
|
|
625
643
|
):
|
|
626
644
|
kwargs = await get_request_kwargs(request, create_workflow_run)
|
|
627
645
|
|
|
@@ -650,7 +668,14 @@ def get_workflow_router(
|
|
|
650
668
|
kwargs["metadata"] = metadata
|
|
651
669
|
|
|
652
670
|
# Retrieve the workflow by ID
|
|
653
|
-
workflow = get_workflow_by_id(
|
|
671
|
+
workflow = get_workflow_by_id(
|
|
672
|
+
workflow_id=workflow_id,
|
|
673
|
+
workflows=os.workflows,
|
|
674
|
+
db=os.db,
|
|
675
|
+
version=version,
|
|
676
|
+
registry=os.registry,
|
|
677
|
+
create_fresh=True,
|
|
678
|
+
)
|
|
654
679
|
if workflow is None:
|
|
655
680
|
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
656
681
|
|
|
@@ -716,7 +741,9 @@ def get_workflow_router(
|
|
|
716
741
|
dependencies=[Depends(require_resource_access("workflows", "run", "workflow_id"))],
|
|
717
742
|
)
|
|
718
743
|
async def cancel_workflow_run(workflow_id: str, run_id: str):
|
|
719
|
-
workflow = get_workflow_by_id(
|
|
744
|
+
workflow = get_workflow_by_id(
|
|
745
|
+
workflow_id=workflow_id, workflows=os.workflows, db=os.db, registry=os.registry, create_fresh=True
|
|
746
|
+
)
|
|
720
747
|
|
|
721
748
|
if workflow is None:
|
|
722
749
|
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
@@ -121,7 +121,7 @@ class WorkflowResponse(BaseModel):
|
|
|
121
121
|
|
|
122
122
|
@classmethod
|
|
123
123
|
async def from_workflow(cls, workflow: Workflow) -> "WorkflowResponse":
|
|
124
|
-
workflow_dict = workflow.
|
|
124
|
+
workflow_dict = workflow.to_dict_for_steps()
|
|
125
125
|
steps = workflow_dict.get("steps")
|
|
126
126
|
|
|
127
127
|
if steps:
|
agno/os/schema.py
CHANGED
|
@@ -16,11 +16,7 @@ from agno.os.config import (
|
|
|
16
16
|
SessionConfig,
|
|
17
17
|
TracesConfig,
|
|
18
18
|
)
|
|
19
|
-
from agno.os.utils import
|
|
20
|
-
extract_input_media,
|
|
21
|
-
get_run_input,
|
|
22
|
-
get_session_name,
|
|
23
|
-
)
|
|
19
|
+
from agno.os.utils import extract_input_media, get_run_input, get_session_name, to_utc_datetime
|
|
24
20
|
from agno.session import AgentSession, TeamSession, WorkflowSession
|
|
25
21
|
from agno.team.remote import RemoteTeam
|
|
26
22
|
from agno.team.team import Team
|
|
@@ -79,10 +75,12 @@ class InternalServerErrorResponse(BaseModel):
|
|
|
79
75
|
|
|
80
76
|
|
|
81
77
|
class HealthResponse(BaseModel):
|
|
82
|
-
model_config = ConfigDict(
|
|
78
|
+
model_config = ConfigDict(
|
|
79
|
+
json_schema_extra={"example": {"status": "ok", "instantiated_at": "2025-06-10T12:00:00Z"}}
|
|
80
|
+
)
|
|
83
81
|
|
|
84
82
|
status: str = Field(..., description="Health status of the service")
|
|
85
|
-
instantiated_at:
|
|
83
|
+
instantiated_at: datetime = Field(..., description="Timestamp when service was instantiated")
|
|
86
84
|
|
|
87
85
|
|
|
88
86
|
class InterfaceResponse(BaseModel):
|
|
@@ -145,7 +143,8 @@ class ConfigResponse(BaseModel):
|
|
|
145
143
|
name: Optional[str] = Field(None, description="Name of the OS instance")
|
|
146
144
|
description: Optional[str] = Field(None, description="Description of the OS instance")
|
|
147
145
|
available_models: Optional[List[str]] = Field(None, description="List of available models")
|
|
148
|
-
|
|
146
|
+
os_database: Optional[str] = Field(None, description="ID of the database used for the OS instance")
|
|
147
|
+
databases: List[str] = Field(..., description="List of database IDs used by the components of the OS instance")
|
|
149
148
|
chat: Optional[ChatConfig] = Field(None, description="Chat configuration")
|
|
150
149
|
|
|
151
150
|
session: Optional[SessionConfig] = Field(None, description="Session configuration")
|
|
@@ -212,8 +211,8 @@ class SessionSchema(BaseModel):
|
|
|
212
211
|
return None
|
|
213
212
|
return None
|
|
214
213
|
|
|
215
|
-
created_at =
|
|
216
|
-
updated_at =
|
|
214
|
+
created_at = to_utc_datetime(session.get("created_at", 0))
|
|
215
|
+
updated_at = to_utc_datetime(session.get("updated_at", created_at))
|
|
217
216
|
return cls(
|
|
218
217
|
session_id=session.get("session_id", ""),
|
|
219
218
|
session_name=session_name,
|
|
@@ -282,8 +281,8 @@ class AgentSessionDetailSchema(BaseModel):
|
|
|
282
281
|
metrics=session.session_data.get("session_metrics", {}) if session.session_data else None, # type: ignore
|
|
283
282
|
metadata=session.metadata,
|
|
284
283
|
chat_history=[message.to_dict() for message in session.get_chat_history()],
|
|
285
|
-
created_at=created_at,
|
|
286
|
-
updated_at=updated_at,
|
|
284
|
+
created_at=to_utc_datetime(created_at),
|
|
285
|
+
updated_at=to_utc_datetime(updated_at),
|
|
287
286
|
)
|
|
288
287
|
|
|
289
288
|
|
|
@@ -322,8 +321,8 @@ class TeamSessionDetailSchema(BaseModel):
|
|
|
322
321
|
metrics=session.session_data.get("session_metrics", {}) if session.session_data else None,
|
|
323
322
|
metadata=session.metadata,
|
|
324
323
|
chat_history=[message.to_dict() for message in session.get_chat_history()],
|
|
325
|
-
created_at=created_at,
|
|
326
|
-
updated_at=updated_at,
|
|
324
|
+
created_at=to_utc_datetime(created_at),
|
|
325
|
+
updated_at=to_utc_datetime(updated_at),
|
|
327
326
|
)
|
|
328
327
|
|
|
329
328
|
|
|
@@ -339,13 +338,15 @@ class WorkflowSessionDetailSchema(BaseModel):
|
|
|
339
338
|
workflow_data: Optional[dict] = Field(None, description="Workflow-specific data")
|
|
340
339
|
metadata: Optional[dict] = Field(None, description="Additional metadata")
|
|
341
340
|
|
|
342
|
-
created_at: Optional[
|
|
343
|
-
updated_at: Optional[
|
|
341
|
+
created_at: Optional[datetime] = Field(None, description="Session creation timestamp")
|
|
342
|
+
updated_at: Optional[datetime] = Field(None, description="Last update timestamp")
|
|
344
343
|
|
|
345
344
|
@classmethod
|
|
346
345
|
def from_session(cls, session: WorkflowSession) -> "WorkflowSessionDetailSchema":
|
|
347
346
|
session_dict = session.to_dict()
|
|
348
347
|
session_name = get_session_name({**session_dict, "session_type": "workflow"})
|
|
348
|
+
created_at = datetime.fromtimestamp(session.created_at, tz=timezone.utc) if session.created_at else None
|
|
349
|
+
updated_at = datetime.fromtimestamp(session.updated_at, tz=timezone.utc) if session.updated_at else created_at
|
|
349
350
|
return cls(
|
|
350
351
|
session_id=session.session_id,
|
|
351
352
|
user_id=session.user_id,
|
|
@@ -356,8 +357,8 @@ class WorkflowSessionDetailSchema(BaseModel):
|
|
|
356
357
|
session_state=session.session_data.get("session_state", None) if session.session_data else None,
|
|
357
358
|
workflow_data=session.workflow_data,
|
|
358
359
|
metadata=session.metadata,
|
|
359
|
-
created_at=
|
|
360
|
-
updated_at=
|
|
360
|
+
created_at=to_utc_datetime(created_at),
|
|
361
|
+
updated_at=to_utc_datetime(updated_at),
|
|
361
362
|
)
|
|
362
363
|
|
|
363
364
|
|
|
@@ -418,9 +419,7 @@ class RunSchema(BaseModel):
|
|
|
418
419
|
files=run_dict.get("files", []),
|
|
419
420
|
response_audio=run_dict.get("response_audio", None),
|
|
420
421
|
input_media=extract_input_media(run_dict),
|
|
421
|
-
created_at=
|
|
422
|
-
if run_dict.get("created_at") is not None
|
|
423
|
-
else None,
|
|
422
|
+
created_at=to_utc_datetime(run_dict.get("created_at")),
|
|
424
423
|
)
|
|
425
424
|
|
|
426
425
|
|
|
@@ -468,9 +467,7 @@ class TeamRunSchema(BaseModel):
|
|
|
468
467
|
messages=[message for message in run_dict.get("messages", [])] if run_dict.get("messages") else None,
|
|
469
468
|
tools=[tool for tool in run_dict.get("tools", [])] if run_dict.get("tools") else None,
|
|
470
469
|
events=[event for event in run_dict["events"]] if run_dict.get("events") else None,
|
|
471
|
-
created_at=
|
|
472
|
-
if run_dict.get("created_at") is not None
|
|
473
|
-
else None,
|
|
470
|
+
created_at=to_utc_datetime(run_dict.get("created_at")),
|
|
474
471
|
references=run_dict.get("references", []),
|
|
475
472
|
citations=run_dict.get("citations", None),
|
|
476
473
|
reasoning_messages=run_dict.get("reasoning_messages", []),
|
|
@@ -496,7 +493,7 @@ class WorkflowRunSchema(BaseModel):
|
|
|
496
493
|
step_results: Optional[list[dict]] = Field(None, description="Results from each workflow step")
|
|
497
494
|
step_executor_runs: Optional[list[dict]] = Field(None, description="Executor runs for each step")
|
|
498
495
|
metrics: Optional[dict] = Field(None, description="Performance and usage metrics")
|
|
499
|
-
created_at: Optional[
|
|
496
|
+
created_at: Optional[datetime] = Field(None, description="Run creation timestamp")
|
|
500
497
|
reasoning_content: Optional[str] = Field(None, description="Reasoning content if reasoning was enabled")
|
|
501
498
|
reasoning_steps: Optional[List[dict]] = Field(None, description="List of reasoning steps")
|
|
502
499
|
references: Optional[List[dict]] = Field(None, description="References cited in the workflow")
|
|
@@ -525,7 +522,7 @@ class WorkflowRunSchema(BaseModel):
|
|
|
525
522
|
metrics=run_response.get("metrics", {}),
|
|
526
523
|
step_results=run_response.get("step_results", []),
|
|
527
524
|
step_executor_runs=run_response.get("step_executor_runs", []),
|
|
528
|
-
created_at=run_response
|
|
525
|
+
created_at=to_utc_datetime(run_response.get("created_at")),
|
|
529
526
|
reasoning_content=run_response.get("reasoning_content", ""),
|
|
530
527
|
reasoning_steps=run_response.get("reasoning_steps", []),
|
|
531
528
|
references=run_response.get("references", []),
|
|
@@ -560,3 +557,72 @@ class PaginatedResponse(BaseModel, Generic[T]):
|
|
|
560
557
|
|
|
561
558
|
data: List[T] = Field(..., description="List of items for the current page")
|
|
562
559
|
meta: PaginationInfo = Field(..., description="Pagination metadata")
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
class ComponentType(str, Enum):
|
|
563
|
+
AGENT = "agent"
|
|
564
|
+
TEAM = "team"
|
|
565
|
+
WORKFLOW = "workflow"
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
class ComponentCreate(BaseModel):
|
|
569
|
+
name: str = Field(..., description="Display name")
|
|
570
|
+
component_id: Optional[str] = Field(
|
|
571
|
+
None, description="Unique identifier for the entity. Auto-generated from name if not provided."
|
|
572
|
+
)
|
|
573
|
+
component_type: ComponentType = Field(..., description="Type of entity: agent, team, or workflow")
|
|
574
|
+
description: Optional[str] = Field(None, description="Optional description")
|
|
575
|
+
metadata: Optional[Dict[str, Any]] = Field(None, description="Optional metadata")
|
|
576
|
+
# Config parameters are optional, but if provided, they will be used to create the initial config
|
|
577
|
+
config: Optional[Dict[str, Any]] = Field(None, description="Optional configuration")
|
|
578
|
+
label: Optional[str] = Field(None, description="Optional label (e.g., 'stable')")
|
|
579
|
+
stage: str = Field("draft", description="Stage: 'draft' or 'published'")
|
|
580
|
+
notes: Optional[str] = Field(None, description="Optional notes")
|
|
581
|
+
set_current: bool = Field(True, description="Set as current version")
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
class ComponentResponse(BaseModel):
|
|
585
|
+
component_id: str
|
|
586
|
+
component_type: ComponentType
|
|
587
|
+
name: Optional[str] = None
|
|
588
|
+
description: Optional[str] = None
|
|
589
|
+
current_version: Optional[int] = None
|
|
590
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
591
|
+
created_at: int
|
|
592
|
+
updated_at: Optional[int] = None
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
class ConfigCreate(BaseModel):
|
|
596
|
+
config: Dict[str, Any] = Field(..., description="The configuration data")
|
|
597
|
+
version: Optional[int] = Field(None, description="Optional version number")
|
|
598
|
+
label: Optional[str] = Field(None, description="Optional label (e.g., 'stable')")
|
|
599
|
+
stage: str = Field("draft", description="Stage: 'draft' or 'published'")
|
|
600
|
+
notes: Optional[str] = Field(None, description="Optional notes")
|
|
601
|
+
links: Optional[List[Dict[str, Any]]] = Field(None, description="Optional links to child components")
|
|
602
|
+
set_current: bool = Field(True, description="Set as current version")
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
class ComponentConfigResponse(BaseModel):
|
|
606
|
+
component_id: str
|
|
607
|
+
version: int
|
|
608
|
+
label: Optional[str] = None
|
|
609
|
+
stage: str
|
|
610
|
+
config: Dict[str, Any]
|
|
611
|
+
notes: Optional[str] = None
|
|
612
|
+
created_at: int
|
|
613
|
+
updated_at: Optional[int] = None
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
class ComponentUpdate(BaseModel):
|
|
617
|
+
name: Optional[str] = None
|
|
618
|
+
description: Optional[str] = None
|
|
619
|
+
component_type: Optional[str] = None
|
|
620
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
class ConfigUpdate(BaseModel):
|
|
624
|
+
config: Optional[Dict[str, Any]] = None
|
|
625
|
+
label: Optional[str] = None
|
|
626
|
+
stage: Optional[str] = None
|
|
627
|
+
notes: Optional[str] = None
|
|
628
|
+
links: Optional[List[Dict[str, Any]]] = None
|
agno/os/utils.py
CHANGED
|
@@ -14,6 +14,7 @@ from agno.media import Audio, Image, Video
|
|
|
14
14
|
from agno.media import File as FileMedia
|
|
15
15
|
from agno.models.message import Message
|
|
16
16
|
from agno.os.config import AgentOSConfig
|
|
17
|
+
from agno.registry import Registry
|
|
17
18
|
from agno.remote.base import RemoteDb, RemoteKnowledge
|
|
18
19
|
from agno.run.agent import RunOutputEvent
|
|
19
20
|
from agno.run.team import TeamRunOutputEvent
|
|
@@ -24,6 +25,20 @@ from agno.utils.log import log_warning, logger
|
|
|
24
25
|
from agno.workflow import RemoteWorkflow, Workflow
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
def to_utc_datetime(value: Optional[Union[int, float, datetime]]) -> Optional[datetime]:
|
|
29
|
+
"""Convert a timestamp to a UTC datetime."""
|
|
30
|
+
if value is None:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
if isinstance(value, datetime):
|
|
34
|
+
# If already a datetime, make sure the timezone is UTC
|
|
35
|
+
if value.tzinfo is None:
|
|
36
|
+
return value.replace(tzinfo=timezone.utc)
|
|
37
|
+
return value
|
|
38
|
+
|
|
39
|
+
return datetime.fromtimestamp(value, tz=timezone.utc)
|
|
40
|
+
|
|
41
|
+
|
|
27
42
|
async def get_request_kwargs(request: Request, endpoint_func: Callable) -> Dict[str, Any]:
|
|
28
43
|
"""Given a Request and an endpoint function, return a dictionary with all extra form data fields.
|
|
29
44
|
|
|
@@ -416,6 +431,9 @@ def extract_format(file: UploadFile) -> Optional[str]:
|
|
|
416
431
|
def get_agent_by_id(
|
|
417
432
|
agent_id: str,
|
|
418
433
|
agents: Optional[List[Union[Agent, RemoteAgent]]] = None,
|
|
434
|
+
db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
|
|
435
|
+
registry: Optional[Registry] = None,
|
|
436
|
+
version: Optional[int] = None,
|
|
419
437
|
create_fresh: bool = False,
|
|
420
438
|
) -> Optional[Union[Agent, RemoteAgent]]:
|
|
421
439
|
"""Get an agent by ID, optionally creating a fresh instance for request isolation.
|
|
@@ -432,14 +450,28 @@ def get_agent_by_id(
|
|
|
432
450
|
Returns:
|
|
433
451
|
The agent instance (shared or fresh copy based on create_fresh)
|
|
434
452
|
"""
|
|
435
|
-
if agent_id is None
|
|
453
|
+
if agent_id is None:
|
|
436
454
|
return None
|
|
437
455
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
456
|
+
# Try to get the agent from the list of agents
|
|
457
|
+
if agents:
|
|
458
|
+
for agent in agents:
|
|
459
|
+
if agent.id == agent_id:
|
|
460
|
+
if create_fresh and isinstance(agent, Agent):
|
|
461
|
+
return agent.deep_copy()
|
|
462
|
+
return agent
|
|
463
|
+
|
|
464
|
+
# Try to get the agent from the database
|
|
465
|
+
if db and isinstance(db, BaseDb):
|
|
466
|
+
from agno.agent.agent import get_agent_by_id as get_agent_by_id_db
|
|
467
|
+
|
|
468
|
+
try:
|
|
469
|
+
db_agent = get_agent_by_id_db(db=db, id=agent_id, version=version, registry=registry)
|
|
470
|
+
return db_agent
|
|
471
|
+
except Exception as e:
|
|
472
|
+
logger.error(f"Error getting agent {agent_id} from database: {e}")
|
|
473
|
+
return None
|
|
474
|
+
|
|
443
475
|
return None
|
|
444
476
|
|
|
445
477
|
|
|
@@ -447,6 +479,9 @@ def get_team_by_id(
|
|
|
447
479
|
team_id: str,
|
|
448
480
|
teams: Optional[List[Union[Team, RemoteTeam]]] = None,
|
|
449
481
|
create_fresh: bool = False,
|
|
482
|
+
db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
|
|
483
|
+
version: Optional[int] = None,
|
|
484
|
+
registry: Optional[Registry] = None,
|
|
450
485
|
) -> Optional[Union[Team, RemoteTeam]]:
|
|
451
486
|
"""Get a team by ID, optionally creating a fresh instance for request isolation.
|
|
452
487
|
|
|
@@ -461,14 +496,26 @@ def get_team_by_id(
|
|
|
461
496
|
Returns:
|
|
462
497
|
The team instance (shared or fresh copy based on create_fresh)
|
|
463
498
|
"""
|
|
464
|
-
if team_id is None
|
|
499
|
+
if team_id is None:
|
|
465
500
|
return None
|
|
466
501
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if
|
|
470
|
-
|
|
471
|
-
|
|
502
|
+
if teams:
|
|
503
|
+
for team in teams:
|
|
504
|
+
if team.id == team_id:
|
|
505
|
+
if create_fresh and isinstance(team, Team):
|
|
506
|
+
return team.deep_copy()
|
|
507
|
+
return team
|
|
508
|
+
|
|
509
|
+
if db and isinstance(db, BaseDb):
|
|
510
|
+
from agno.team.team import get_team_by_id as get_team_by_id_db
|
|
511
|
+
|
|
512
|
+
try:
|
|
513
|
+
db_team = get_team_by_id_db(db=db, id=team_id, version=version, registry=registry)
|
|
514
|
+
return db_team
|
|
515
|
+
except Exception as e:
|
|
516
|
+
logger.error(f"Error getting team {team_id} from database: {e}")
|
|
517
|
+
return None
|
|
518
|
+
|
|
472
519
|
return None
|
|
473
520
|
|
|
474
521
|
|
|
@@ -476,6 +523,9 @@ def get_workflow_by_id(
|
|
|
476
523
|
workflow_id: str,
|
|
477
524
|
workflows: Optional[List[Union[Workflow, RemoteWorkflow]]] = None,
|
|
478
525
|
create_fresh: bool = False,
|
|
526
|
+
db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
|
|
527
|
+
version: Optional[int] = None,
|
|
528
|
+
registry: Optional[Registry] = None,
|
|
479
529
|
) -> Optional[Union[Workflow, RemoteWorkflow]]:
|
|
480
530
|
"""Get a workflow by ID, optionally creating a fresh instance for request isolation.
|
|
481
531
|
|
|
@@ -486,18 +536,33 @@ def get_workflow_by_id(
|
|
|
486
536
|
workflow_id: The workflow ID to look up
|
|
487
537
|
workflows: List of workflows to search
|
|
488
538
|
create_fresh: If True, creates a new instance using deep_copy()
|
|
539
|
+
db: Optional database interface
|
|
540
|
+
version: Workflow version, if needed
|
|
541
|
+
registry: Optional Registry instance
|
|
489
542
|
|
|
490
543
|
Returns:
|
|
491
544
|
The workflow instance (shared or fresh copy based on create_fresh)
|
|
492
545
|
"""
|
|
493
|
-
if workflow_id is None
|
|
546
|
+
if workflow_id is None:
|
|
494
547
|
return None
|
|
495
548
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if
|
|
499
|
-
|
|
500
|
-
|
|
549
|
+
if workflows:
|
|
550
|
+
for workflow in workflows:
|
|
551
|
+
if workflow.id == workflow_id:
|
|
552
|
+
if create_fresh and isinstance(workflow, Workflow):
|
|
553
|
+
return workflow.deep_copy()
|
|
554
|
+
return workflow
|
|
555
|
+
|
|
556
|
+
if db and isinstance(db, BaseDb):
|
|
557
|
+
from agno.workflow.workflow import get_workflow_by_id as get_workflow_by_id_db
|
|
558
|
+
|
|
559
|
+
try:
|
|
560
|
+
db_workflow = get_workflow_by_id_db(db=db, id=workflow_id, version=version, registry=registry)
|
|
561
|
+
return db_workflow
|
|
562
|
+
except Exception as e:
|
|
563
|
+
logger.error(f"Error getting workflow {workflow_id} from database: {e}")
|
|
564
|
+
return None
|
|
565
|
+
|
|
501
566
|
return None
|
|
502
567
|
|
|
503
568
|
|
|
@@ -871,7 +936,7 @@ def format_duration_ms(duration_ms: Optional[int]) -> str:
|
|
|
871
936
|
return f"{duration_ms / 1000:.2f}s"
|
|
872
937
|
|
|
873
938
|
|
|
874
|
-
def
|
|
939
|
+
def timestamp_to_datetime(datetime_str: str, param_name: str = "datetime") -> "datetime":
|
|
875
940
|
"""Parse an ISO 8601 datetime string and convert to UTC.
|
|
876
941
|
|
|
877
942
|
Args:
|
agno/reasoning/anthropic.py
CHANGED
|
@@ -69,7 +69,7 @@ def get_anthropic_reasoning_stream(
|
|
|
69
69
|
redacted_reasoning_content: Optional[str] = None
|
|
70
70
|
|
|
71
71
|
try:
|
|
72
|
-
for event in reasoning_agent.run(input=messages, stream=True,
|
|
72
|
+
for event in reasoning_agent.run(input=messages, stream=True, stream_events=True):
|
|
73
73
|
if hasattr(event, "event"):
|
|
74
74
|
if event.event == RunEvent.run_content:
|
|
75
75
|
# Stream reasoning content as it arrives
|
|
@@ -140,7 +140,7 @@ async def aget_anthropic_reasoning_stream(
|
|
|
140
140
|
redacted_reasoning_content: Optional[str] = None
|
|
141
141
|
|
|
142
142
|
try:
|
|
143
|
-
async for event in reasoning_agent.arun(input=messages, stream=True,
|
|
143
|
+
async for event in reasoning_agent.arun(input=messages, stream=True, stream_events=True):
|
|
144
144
|
if hasattr(event, "event"):
|
|
145
145
|
if event.event == RunEvent.run_content:
|
|
146
146
|
# Stream reasoning content as it arrives
|
|
@@ -86,7 +86,7 @@ def get_ai_foundry_reasoning_stream(
|
|
|
86
86
|
reasoning_content: str = ""
|
|
87
87
|
|
|
88
88
|
try:
|
|
89
|
-
for event in reasoning_agent.run(input=messages, stream=True,
|
|
89
|
+
for event in reasoning_agent.run(input=messages, stream=True, stream_events=True):
|
|
90
90
|
if hasattr(event, "event"):
|
|
91
91
|
if event.event == RunEvent.run_content:
|
|
92
92
|
# Check for reasoning_content attribute first (native reasoning)
|
|
@@ -132,7 +132,7 @@ async def aget_ai_foundry_reasoning_stream(
|
|
|
132
132
|
reasoning_content: str = ""
|
|
133
133
|
|
|
134
134
|
try:
|
|
135
|
-
async for event in reasoning_agent.arun(input=messages, stream=True,
|
|
135
|
+
async for event in reasoning_agent.arun(input=messages, stream=True, stream_events=True):
|
|
136
136
|
if hasattr(event, "event"):
|
|
137
137
|
if event.event == RunEvent.run_content:
|
|
138
138
|
# Check for reasoning_content attribute first (native reasoning)
|
agno/reasoning/deepseek.py
CHANGED
|
@@ -69,7 +69,7 @@ def get_deepseek_reasoning_stream(
|
|
|
69
69
|
reasoning_content: str = ""
|
|
70
70
|
|
|
71
71
|
try:
|
|
72
|
-
for event in reasoning_agent.run(input=messages, stream=True,
|
|
72
|
+
for event in reasoning_agent.run(input=messages, stream=True, stream_events=True):
|
|
73
73
|
if hasattr(event, "event"):
|
|
74
74
|
if event.event == RunEvent.run_content:
|
|
75
75
|
# Stream reasoning content as it arrives
|
|
@@ -140,7 +140,7 @@ async def aget_deepseek_reasoning_stream(
|
|
|
140
140
|
reasoning_content: str = ""
|
|
141
141
|
|
|
142
142
|
try:
|
|
143
|
-
async for event in reasoning_agent.arun(input=messages, stream=True,
|
|
143
|
+
async for event in reasoning_agent.arun(input=messages, stream=True, stream_events=True):
|
|
144
144
|
if hasattr(event, "event"):
|
|
145
145
|
if event.event == RunEvent.run_content:
|
|
146
146
|
# Stream reasoning content as it arrives
|
agno/reasoning/default.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from textwrap import dedent
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Callable, Dict, List, Literal, Optional, Union
|
|
5
5
|
|
|
6
6
|
from agno.models.base import Model
|
|
7
7
|
from agno.reasoning.step import ReasoningSteps
|
|
8
|
+
from agno.run.base import RunContext
|
|
8
9
|
from agno.tools import Toolkit
|
|
9
10
|
from agno.tools.function import Function
|
|
10
11
|
|
|
@@ -19,9 +20,7 @@ def get_default_reasoning_agent(
|
|
|
19
20
|
telemetry: bool = True,
|
|
20
21
|
debug_mode: bool = False,
|
|
21
22
|
debug_level: Literal[1, 2] = 1,
|
|
22
|
-
|
|
23
|
-
dependencies: Optional[Dict[str, Any]] = None,
|
|
24
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
23
|
+
run_context: Optional[RunContext] = None,
|
|
25
24
|
) -> Optional["Agent"]: # type: ignore # noqa: F821
|
|
26
25
|
from agno.agent import Agent
|
|
27
26
|
|
|
@@ -89,9 +88,9 @@ def get_default_reasoning_agent(
|
|
|
89
88
|
telemetry=telemetry,
|
|
90
89
|
debug_mode=debug_mode,
|
|
91
90
|
debug_level=debug_level,
|
|
92
|
-
session_state=session_state,
|
|
93
|
-
dependencies=dependencies,
|
|
94
|
-
metadata=metadata,
|
|
91
|
+
session_state=run_context.session_state if run_context else None,
|
|
92
|
+
dependencies=run_context.dependencies if run_context else None,
|
|
93
|
+
metadata=run_context.metadata if run_context else None,
|
|
95
94
|
)
|
|
96
95
|
|
|
97
96
|
return agent
|
agno/reasoning/gemini.py
CHANGED
|
@@ -94,7 +94,7 @@ def get_gemini_reasoning_stream(
|
|
|
94
94
|
reasoning_content: str = ""
|
|
95
95
|
|
|
96
96
|
try:
|
|
97
|
-
for event in reasoning_agent.run(input=messages, stream=True,
|
|
97
|
+
for event in reasoning_agent.run(input=messages, stream=True, stream_events=True):
|
|
98
98
|
if hasattr(event, "event"):
|
|
99
99
|
if event.event == RunEvent.run_content:
|
|
100
100
|
# Stream reasoning content as it arrives
|
|
@@ -134,7 +134,7 @@ async def aget_gemini_reasoning_stream(
|
|
|
134
134
|
reasoning_content: str = ""
|
|
135
135
|
|
|
136
136
|
try:
|
|
137
|
-
async for event in reasoning_agent.arun(input=messages, stream=True,
|
|
137
|
+
async for event in reasoning_agent.arun(input=messages, stream=True, stream_events=True):
|
|
138
138
|
if hasattr(event, "event"):
|
|
139
139
|
if event.event == RunEvent.run_content:
|
|
140
140
|
# Stream reasoning content as it arrives
|
agno/reasoning/helpers.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import List, Literal, Optional
|
|
2
2
|
|
|
3
3
|
from agno.models.base import Model
|
|
4
4
|
from agno.models.message import Message
|
|
5
5
|
from agno.reasoning.step import NextAction, ReasoningStep
|
|
6
|
+
from agno.run.base import RunContext
|
|
6
7
|
from agno.run.messages import RunMessages
|
|
7
8
|
from agno.utils.log import logger
|
|
8
9
|
|
|
@@ -12,9 +13,7 @@ def get_reasoning_agent(
|
|
|
12
13
|
telemetry: bool = False,
|
|
13
14
|
debug_mode: bool = False,
|
|
14
15
|
debug_level: Literal[1, 2] = 1,
|
|
15
|
-
|
|
16
|
-
dependencies: Optional[Dict[str, Any]] = None,
|
|
17
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
16
|
+
run_context: Optional[RunContext] = None,
|
|
18
17
|
) -> "Agent": # type: ignore # noqa: F821
|
|
19
18
|
from agno.agent import Agent
|
|
20
19
|
|
|
@@ -23,9 +22,9 @@ def get_reasoning_agent(
|
|
|
23
22
|
telemetry=telemetry,
|
|
24
23
|
debug_mode=debug_mode,
|
|
25
24
|
debug_level=debug_level,
|
|
26
|
-
session_state=session_state,
|
|
27
|
-
dependencies=dependencies,
|
|
28
|
-
metadata=metadata,
|
|
25
|
+
session_state=run_context.session_state if run_context else None,
|
|
26
|
+
dependencies=run_context.dependencies if run_context else None,
|
|
27
|
+
metadata=run_context.metadata if run_context else None,
|
|
29
28
|
)
|
|
30
29
|
|
|
31
30
|
|