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.
Files changed (140) hide show
  1. agno/agent/__init__.py +4 -0
  2. agno/agent/agent.py +1368 -541
  3. agno/agent/remote.py +13 -0
  4. agno/db/base.py +339 -0
  5. agno/db/postgres/async_postgres.py +116 -12
  6. agno/db/postgres/postgres.py +1242 -25
  7. agno/db/postgres/schemas.py +48 -1
  8. agno/db/sqlite/async_sqlite.py +119 -4
  9. agno/db/sqlite/schemas.py +51 -0
  10. agno/db/sqlite/sqlite.py +1186 -13
  11. agno/db/utils.py +37 -1
  12. agno/integrations/discord/client.py +12 -1
  13. agno/knowledge/__init__.py +4 -0
  14. agno/knowledge/chunking/code.py +1 -1
  15. agno/knowledge/chunking/semantic.py +1 -1
  16. agno/knowledge/chunking/strategy.py +4 -0
  17. agno/knowledge/filesystem.py +412 -0
  18. agno/knowledge/knowledge.py +3722 -2182
  19. agno/knowledge/protocol.py +134 -0
  20. agno/knowledge/reader/arxiv_reader.py +2 -2
  21. agno/knowledge/reader/base.py +9 -7
  22. agno/knowledge/reader/csv_reader.py +236 -13
  23. agno/knowledge/reader/docx_reader.py +2 -2
  24. agno/knowledge/reader/field_labeled_csv_reader.py +169 -5
  25. agno/knowledge/reader/firecrawl_reader.py +2 -2
  26. agno/knowledge/reader/json_reader.py +2 -2
  27. agno/knowledge/reader/markdown_reader.py +2 -2
  28. agno/knowledge/reader/pdf_reader.py +5 -4
  29. agno/knowledge/reader/pptx_reader.py +2 -2
  30. agno/knowledge/reader/reader_factory.py +118 -1
  31. agno/knowledge/reader/s3_reader.py +2 -2
  32. agno/knowledge/reader/tavily_reader.py +2 -2
  33. agno/knowledge/reader/text_reader.py +2 -2
  34. agno/knowledge/reader/web_search_reader.py +2 -2
  35. agno/knowledge/reader/website_reader.py +5 -3
  36. agno/knowledge/reader/wikipedia_reader.py +2 -2
  37. agno/knowledge/reader/youtube_reader.py +2 -2
  38. agno/knowledge/remote_content/__init__.py +29 -0
  39. agno/knowledge/remote_content/config.py +204 -0
  40. agno/knowledge/remote_content/remote_content.py +74 -17
  41. agno/knowledge/utils.py +37 -29
  42. agno/learn/__init__.py +6 -0
  43. agno/learn/machine.py +35 -0
  44. agno/learn/schemas.py +82 -11
  45. agno/learn/stores/__init__.py +3 -0
  46. agno/learn/stores/decision_log.py +1156 -0
  47. agno/learn/stores/learned_knowledge.py +6 -6
  48. agno/models/anthropic/claude.py +24 -0
  49. agno/models/aws/bedrock.py +20 -0
  50. agno/models/base.py +60 -6
  51. agno/models/cerebras/cerebras.py +34 -2
  52. agno/models/cohere/chat.py +25 -0
  53. agno/models/google/gemini.py +50 -5
  54. agno/models/litellm/chat.py +38 -0
  55. agno/models/n1n/__init__.py +3 -0
  56. agno/models/n1n/n1n.py +57 -0
  57. agno/models/openai/chat.py +25 -1
  58. agno/models/openrouter/openrouter.py +46 -0
  59. agno/models/perplexity/perplexity.py +2 -0
  60. agno/models/response.py +16 -0
  61. agno/os/app.py +83 -44
  62. agno/os/interfaces/slack/router.py +10 -1
  63. agno/os/interfaces/whatsapp/router.py +6 -0
  64. agno/os/middleware/__init__.py +2 -0
  65. agno/os/middleware/trailing_slash.py +27 -0
  66. agno/os/router.py +1 -0
  67. agno/os/routers/agents/router.py +29 -16
  68. agno/os/routers/agents/schema.py +6 -4
  69. agno/os/routers/components/__init__.py +3 -0
  70. agno/os/routers/components/components.py +475 -0
  71. agno/os/routers/evals/schemas.py +4 -3
  72. agno/os/routers/health.py +3 -3
  73. agno/os/routers/knowledge/knowledge.py +128 -3
  74. agno/os/routers/knowledge/schemas.py +12 -0
  75. agno/os/routers/memory/schemas.py +4 -2
  76. agno/os/routers/metrics/metrics.py +9 -11
  77. agno/os/routers/metrics/schemas.py +10 -6
  78. agno/os/routers/registry/__init__.py +3 -0
  79. agno/os/routers/registry/registry.py +337 -0
  80. agno/os/routers/teams/router.py +20 -8
  81. agno/os/routers/teams/schema.py +6 -4
  82. agno/os/routers/traces/traces.py +5 -5
  83. agno/os/routers/workflows/router.py +38 -11
  84. agno/os/routers/workflows/schema.py +1 -1
  85. agno/os/schema.py +92 -26
  86. agno/os/utils.py +84 -19
  87. agno/reasoning/anthropic.py +2 -2
  88. agno/reasoning/azure_ai_foundry.py +2 -2
  89. agno/reasoning/deepseek.py +2 -2
  90. agno/reasoning/default.py +6 -7
  91. agno/reasoning/gemini.py +2 -2
  92. agno/reasoning/helpers.py +6 -7
  93. agno/reasoning/manager.py +4 -10
  94. agno/reasoning/ollama.py +2 -2
  95. agno/reasoning/openai.py +2 -2
  96. agno/reasoning/vertexai.py +2 -2
  97. agno/registry/__init__.py +3 -0
  98. agno/registry/registry.py +68 -0
  99. agno/run/agent.py +59 -0
  100. agno/run/base.py +7 -0
  101. agno/run/team.py +57 -0
  102. agno/skills/agent_skills.py +10 -3
  103. agno/team/__init__.py +3 -1
  104. agno/team/team.py +1165 -330
  105. agno/tools/duckduckgo.py +25 -71
  106. agno/tools/exa.py +0 -21
  107. agno/tools/function.py +35 -83
  108. agno/tools/knowledge.py +9 -4
  109. agno/tools/mem0.py +11 -10
  110. agno/tools/memory.py +47 -46
  111. agno/tools/parallel.py +0 -7
  112. agno/tools/reasoning.py +30 -23
  113. agno/tools/tavily.py +4 -1
  114. agno/tools/websearch.py +93 -0
  115. agno/tools/website.py +1 -1
  116. agno/tools/wikipedia.py +1 -1
  117. agno/tools/workflow.py +48 -47
  118. agno/utils/agent.py +42 -5
  119. agno/utils/events.py +160 -2
  120. agno/utils/print_response/agent.py +0 -31
  121. agno/utils/print_response/team.py +0 -2
  122. agno/utils/print_response/workflow.py +0 -2
  123. agno/utils/team.py +61 -11
  124. agno/vectordb/lancedb/lance_db.py +4 -1
  125. agno/vectordb/mongodb/mongodb.py +1 -1
  126. agno/vectordb/pgvector/pgvector.py +3 -3
  127. agno/vectordb/qdrant/qdrant.py +4 -4
  128. agno/workflow/__init__.py +3 -1
  129. agno/workflow/condition.py +0 -21
  130. agno/workflow/loop.py +0 -21
  131. agno/workflow/parallel.py +0 -21
  132. agno/workflow/router.py +0 -21
  133. agno/workflow/step.py +117 -24
  134. agno/workflow/steps.py +0 -21
  135. agno/workflow/workflow.py +427 -63
  136. {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/METADATA +49 -76
  137. {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/RECORD +140 -126
  138. {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/WHEEL +1 -1
  139. {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/licenses/LICENSE +0 -0
  140. {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(workflow_id, os.workflows, create_fresh=True)
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(workflow_id, os.workflows, create_fresh=True)
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
- return [WorkflowSummaryResponse.from_workflow(workflow) for workflow in accessible_workflows]
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(workflow_id, os.workflows, create_fresh=True)
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(workflow_id, os.workflows, create_fresh=True)
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(workflow_id, os.workflows, create_fresh=True)
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.to_dict()
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(json_schema_extra={"example": {"status": "ok", "instantiated_at": "1760169236.778903"}})
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: str = Field(..., description="Unix timestamp when service was instantiated")
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
- databases: List[str] = Field(..., description="List of database IDs")
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 = parse_datetime(session.get("created_at", 0))
216
- updated_at = parse_datetime(session.get("updated_at", created_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[int] = Field(None, description="Unix timestamp of session creation")
343
- updated_at: Optional[int] = Field(None, description="Unix timestamp of last update")
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=session.created_at,
360
- updated_at=session.updated_at or session.created_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=datetime.fromtimestamp(run_dict.get("created_at", 0), tz=timezone.utc)
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=datetime.fromtimestamp(run_dict.get("created_at", 0), tz=timezone.utc)
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[int] = Field(None, description="Unix timestamp of run creation")
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["created_at"],
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 or agents is None:
453
+ if agent_id is None:
436
454
  return None
437
455
 
438
- for agent in agents:
439
- if agent.id == agent_id:
440
- if create_fresh and isinstance(agent, Agent):
441
- return agent.deep_copy()
442
- return agent
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 or teams is None:
499
+ if team_id is None:
465
500
  return None
466
501
 
467
- for team in teams:
468
- if team.id == team_id:
469
- if create_fresh and isinstance(team, Team):
470
- return team.deep_copy()
471
- return team
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 or workflows is None:
546
+ if workflow_id is None:
494
547
  return None
495
548
 
496
- for workflow in workflows:
497
- if workflow.id == workflow_id:
498
- if create_fresh and isinstance(workflow, Workflow):
499
- return workflow.deep_copy()
500
- return workflow
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 parse_datetime_to_utc(datetime_str: str, param_name: str = "datetime") -> "datetime":
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:
@@ -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, stream_intermediate_steps=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, stream_intermediate_steps=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, stream_intermediate_steps=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, stream_intermediate_steps=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)
@@ -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, stream_intermediate_steps=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, stream_intermediate_steps=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 Any, Callable, Dict, List, Literal, Optional, Union
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
- session_state: Optional[Dict[str, Any]] = None,
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, stream_intermediate_steps=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, stream_intermediate_steps=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 Any, Dict, List, Literal, Optional
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
- session_state: Optional[Dict[str, Any]] = None,
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