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
|
@@ -170,9 +170,21 @@ class VectorSearchRequestSchema(BaseModel):
|
|
|
170
170
|
)
|
|
171
171
|
|
|
172
172
|
|
|
173
|
+
class RemoteContentSourceSchema(BaseModel):
|
|
174
|
+
"""Schema for remote content source configuration."""
|
|
175
|
+
|
|
176
|
+
id: str = Field(..., description="Unique identifier for the content source")
|
|
177
|
+
name: str = Field(..., description="Display name for the content source")
|
|
178
|
+
type: str = Field(..., description="Type of content source (s3, gcs, sharepoint, github)")
|
|
179
|
+
metadata: Optional[Dict[str, Any]] = Field(None, description="Custom metadata for the content source")
|
|
180
|
+
|
|
181
|
+
|
|
173
182
|
class ConfigResponseSchema(BaseModel):
|
|
174
183
|
readers: Optional[Dict[str, ReaderSchema]] = Field(None, description="Available content readers")
|
|
175
184
|
readersForType: Optional[Dict[str, List[str]]] = Field(None, description="Mapping of content types to reader IDs")
|
|
176
185
|
chunkers: Optional[Dict[str, ChunkerSchema]] = Field(None, description="Available chunking strategies")
|
|
177
186
|
filters: Optional[List[str]] = Field(None, description="Available filter tags")
|
|
178
187
|
vector_dbs: Optional[List[VectorDbSchema]] = Field(None, description="Configured vector databases")
|
|
188
|
+
remote_content_sources: Optional[List[RemoteContentSourceSchema]] = Field(
|
|
189
|
+
None, description="Configured remote content sources (S3, GCS, SharePoint, GitHub)"
|
|
190
|
+
)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from datetime import datetime
|
|
2
|
+
from datetime import datetime
|
|
3
3
|
from typing import Any, Dict, List, Optional
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel, Field
|
|
6
6
|
|
|
7
|
+
from agno.os.utils import to_utc_datetime
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
class DeleteMemoriesRequest(BaseModel):
|
|
9
11
|
memory_ids: List[str] = Field(..., description="List of memory IDs to delete", min_length=1)
|
|
@@ -71,7 +73,7 @@ class UserStatsSchema(BaseModel):
|
|
|
71
73
|
return cls(
|
|
72
74
|
user_id=str(user_stats_dict["user_id"]),
|
|
73
75
|
total_memories=user_stats_dict["total_memories"],
|
|
74
|
-
last_memory_updated_at=
|
|
76
|
+
last_memory_updated_at=to_utc_datetime(updated_at),
|
|
75
77
|
)
|
|
76
78
|
|
|
77
79
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from datetime import date
|
|
2
|
+
from datetime import date
|
|
3
3
|
from typing import List, Optional, Union, cast
|
|
4
4
|
|
|
5
5
|
from fastapi import Depends, HTTPException, Query, Request
|
|
@@ -16,7 +16,7 @@ from agno.os.schema import (
|
|
|
16
16
|
ValidationErrorResponse,
|
|
17
17
|
)
|
|
18
18
|
from agno.os.settings import AgnoAPISettings
|
|
19
|
-
from agno.os.utils import get_db
|
|
19
|
+
from agno.os.utils import get_db, to_utc_datetime
|
|
20
20
|
from agno.remote.base import RemoteDb
|
|
21
21
|
|
|
22
22
|
logger = logging.getLogger(__name__)
|
|
@@ -79,9 +79,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
79
79
|
"reasoning_tokens": 0,
|
|
80
80
|
},
|
|
81
81
|
"model_metrics": [{"model_id": "gpt-4o", "model_provider": "OpenAI", "count": 5}],
|
|
82
|
-
"date": "2025-07-31T00:00:
|
|
83
|
-
"created_at":
|
|
84
|
-
"updated_at":
|
|
82
|
+
"date": "2025-07-31T00:00:00Z",
|
|
83
|
+
"created_at": "2025-07-31T12:38:52Z",
|
|
84
|
+
"updated_at": "2025-07-31T12:49:01Z",
|
|
85
85
|
}
|
|
86
86
|
]
|
|
87
87
|
}
|
|
@@ -121,9 +121,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
121
121
|
|
|
122
122
|
return MetricsResponse(
|
|
123
123
|
metrics=[DayAggregatedMetrics.from_dict(metric) for metric in metrics],
|
|
124
|
-
updated_at=
|
|
125
|
-
if latest_updated_at is not None
|
|
126
|
-
else None,
|
|
124
|
+
updated_at=to_utc_datetime(latest_updated_at),
|
|
127
125
|
)
|
|
128
126
|
|
|
129
127
|
except Exception as e:
|
|
@@ -167,9 +165,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
167
165
|
"reasoning_tokens": 0,
|
|
168
166
|
},
|
|
169
167
|
"model_metrics": [{"model_id": "gpt-4o", "model_provider": "OpenAI", "count": 2}],
|
|
170
|
-
"date": "2025-08-12T00:00:
|
|
171
|
-
"created_at":
|
|
172
|
-
"updated_at":
|
|
168
|
+
"date": "2025-08-12T00:00:00Z",
|
|
169
|
+
"created_at": "2025-08-12T08:01:47Z",
|
|
170
|
+
"updated_at": "2025-08-12T08:01:47Z",
|
|
173
171
|
}
|
|
174
172
|
]
|
|
175
173
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
from datetime import datetime
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
2
|
from typing import Any, Dict, List, Optional
|
|
3
3
|
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
6
|
+
from agno.os.utils import to_utc_datetime
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
class DayAggregatedMetrics(BaseModel):
|
|
8
10
|
"""Aggregated metrics for a given day"""
|
|
@@ -20,22 +22,24 @@ class DayAggregatedMetrics(BaseModel):
|
|
|
20
22
|
model_metrics: List[Dict[str, Any]] = Field(..., description="Metrics grouped by model (model_id, provider, count)")
|
|
21
23
|
|
|
22
24
|
date: datetime = Field(..., description="Date for which these metrics are aggregated")
|
|
23
|
-
created_at:
|
|
24
|
-
updated_at:
|
|
25
|
+
created_at: datetime = Field(..., description="Timestamp when metrics were created")
|
|
26
|
+
updated_at: datetime = Field(..., description="Timestamp when metrics were last updated")
|
|
25
27
|
|
|
26
28
|
@classmethod
|
|
27
29
|
def from_dict(cls, metrics_dict: Dict[str, Any]) -> "DayAggregatedMetrics":
|
|
30
|
+
created_at = to_utc_datetime(metrics_dict.get("created_at")) or datetime.now(timezone.utc)
|
|
31
|
+
updated_at = to_utc_datetime(metrics_dict.get("updated_at", created_at)) or created_at
|
|
28
32
|
return cls(
|
|
29
33
|
agent_runs_count=metrics_dict.get("agent_runs_count", 0),
|
|
30
34
|
agent_sessions_count=metrics_dict.get("agent_sessions_count", 0),
|
|
31
|
-
|
|
32
|
-
date=metrics_dict.get("date", datetime.now()),
|
|
35
|
+
date=metrics_dict.get("date", datetime.now(timezone.utc)),
|
|
33
36
|
id=metrics_dict.get("id", ""),
|
|
34
37
|
model_metrics=metrics_dict.get("model_metrics", {}),
|
|
35
38
|
team_runs_count=metrics_dict.get("team_runs_count", 0),
|
|
36
39
|
team_sessions_count=metrics_dict.get("team_sessions_count", 0),
|
|
37
40
|
token_metrics=metrics_dict.get("token_metrics", {}),
|
|
38
|
-
|
|
41
|
+
created_at=created_at,
|
|
42
|
+
updated_at=updated_at,
|
|
39
43
|
users_count=metrics_dict.get("users_count", 0),
|
|
40
44
|
workflow_runs_count=metrics_dict.get("workflow_runs_count", 0),
|
|
41
45
|
workflow_sessions_count=metrics_dict.get("workflow_sessions_count", 0),
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from agno.os.auth import get_authentication_dependency
|
|
8
|
+
from agno.os.schema import (
|
|
9
|
+
BadRequestResponse,
|
|
10
|
+
InternalServerErrorResponse,
|
|
11
|
+
NotFoundResponse,
|
|
12
|
+
PaginatedResponse,
|
|
13
|
+
PaginationInfo,
|
|
14
|
+
UnauthenticatedResponse,
|
|
15
|
+
ValidationErrorResponse,
|
|
16
|
+
)
|
|
17
|
+
from agno.os.settings import AgnoAPISettings
|
|
18
|
+
from agno.registry import Registry
|
|
19
|
+
from agno.tools.function import Function
|
|
20
|
+
from agno.tools.toolkit import Toolkit
|
|
21
|
+
from agno.utils.log import log_error
|
|
22
|
+
|
|
23
|
+
ComponentType = Literal["tool", "toolkit", "model", "db", "vector_db", "schema"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ============================================
|
|
27
|
+
# Response Schema
|
|
28
|
+
# ============================================
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ComponentResponse(BaseModel):
|
|
32
|
+
name: str
|
|
33
|
+
component_type: ComponentType
|
|
34
|
+
description: Optional[str] = None
|
|
35
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
36
|
+
# Tool-specific fields (matching config format)
|
|
37
|
+
parameters: Optional[Dict[str, Any]] = None
|
|
38
|
+
requires_confirmation: Optional[bool] = None
|
|
39
|
+
external_execution: Optional[bool] = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ============================================
|
|
43
|
+
# Router
|
|
44
|
+
# ============================================
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_registry_router(registry: Registry, settings: AgnoAPISettings = AgnoAPISettings()) -> APIRouter:
|
|
48
|
+
router = APIRouter(
|
|
49
|
+
dependencies=[Depends(get_authentication_dependency(settings))],
|
|
50
|
+
tags=["Registry"],
|
|
51
|
+
responses={
|
|
52
|
+
400: {"description": "Bad Request", "model": BadRequestResponse},
|
|
53
|
+
401: {"description": "Unauthorized", "model": UnauthenticatedResponse},
|
|
54
|
+
404: {"description": "Not Found", "model": NotFoundResponse},
|
|
55
|
+
422: {"description": "Validation Error", "model": ValidationErrorResponse},
|
|
56
|
+
500: {"description": "Internal Server Error", "model": InternalServerErrorResponse},
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
return attach_routes(router=router, registry=registry)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def attach_routes(router: APIRouter, registry: Registry) -> APIRouter:
|
|
63
|
+
def _safe_str(v: Any) -> Optional[str]:
|
|
64
|
+
if v is None:
|
|
65
|
+
return None
|
|
66
|
+
if isinstance(v, str):
|
|
67
|
+
s = v.strip()
|
|
68
|
+
return s or None
|
|
69
|
+
return str(v)
|
|
70
|
+
|
|
71
|
+
def _safe_name(obj: Any, fallback: str) -> str:
|
|
72
|
+
n = getattr(obj, "name", None)
|
|
73
|
+
n = _safe_str(n)
|
|
74
|
+
return n or fallback
|
|
75
|
+
|
|
76
|
+
def _class_path(obj: Any) -> str:
|
|
77
|
+
cls = obj.__class__
|
|
78
|
+
return f"{cls.__module__}.{cls.__name__}"
|
|
79
|
+
|
|
80
|
+
def _maybe_jsonable(value: Any) -> Any:
|
|
81
|
+
# Best-effort: keep only data that is likely JSON serializable
|
|
82
|
+
# If your Function.parameters is a Pydantic model or custom object, this avoids 500s.
|
|
83
|
+
if value is None:
|
|
84
|
+
return None
|
|
85
|
+
if isinstance(value, (str, int, float, bool)):
|
|
86
|
+
return value
|
|
87
|
+
if isinstance(value, list):
|
|
88
|
+
return [_maybe_jsonable(x) for x in value]
|
|
89
|
+
if isinstance(value, dict):
|
|
90
|
+
out: Dict[str, Any] = {}
|
|
91
|
+
for k, v in value.items():
|
|
92
|
+
out[str(k)] = _maybe_jsonable(v)
|
|
93
|
+
return out
|
|
94
|
+
# Fallback to string to avoid serialization errors
|
|
95
|
+
return str(value)
|
|
96
|
+
|
|
97
|
+
def _get_all_components(include_schema: bool) -> List[ComponentResponse]:
|
|
98
|
+
components: List[ComponentResponse] = []
|
|
99
|
+
|
|
100
|
+
# Tools
|
|
101
|
+
for tool in getattr(registry, "tools", []) or []:
|
|
102
|
+
if isinstance(tool, Toolkit):
|
|
103
|
+
toolkit_name = _safe_name(tool, fallback=tool.__class__.__name__)
|
|
104
|
+
functions = getattr(tool, "functions", {}) or {}
|
|
105
|
+
|
|
106
|
+
components.append(
|
|
107
|
+
ComponentResponse(
|
|
108
|
+
name=toolkit_name,
|
|
109
|
+
component_type="toolkit",
|
|
110
|
+
description=_safe_str(getattr(tool, "description", None)),
|
|
111
|
+
metadata={
|
|
112
|
+
"class_path": _class_path(tool),
|
|
113
|
+
"functions": sorted(list(functions.keys())),
|
|
114
|
+
},
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Also expose individual functions within toolkit
|
|
119
|
+
for func in functions.values():
|
|
120
|
+
func_name = _safe_name(func, fallback=func.__class__.__name__)
|
|
121
|
+
# Check if function requires confirmation or external execution
|
|
122
|
+
# First check function-level settings, then toolkit-level settings
|
|
123
|
+
requires_confirmation = getattr(func, "requires_confirmation", None)
|
|
124
|
+
external_execution = getattr(func, "external_execution", None)
|
|
125
|
+
|
|
126
|
+
# If not set on function, check toolkit settings
|
|
127
|
+
if requires_confirmation is None and hasattr(tool, "requires_confirmation_tools"):
|
|
128
|
+
requires_confirmation = func_name in (tool.requires_confirmation_tools or [])
|
|
129
|
+
if external_execution is None and hasattr(tool, "external_execution_required_tools"):
|
|
130
|
+
external_execution = func_name in (tool.external_execution_required_tools or [])
|
|
131
|
+
|
|
132
|
+
# Get parameters - ensure they're processed if needed
|
|
133
|
+
func_params = func.parameters
|
|
134
|
+
# If parameters are empty/default and function has entrypoint, try to process it
|
|
135
|
+
default_params = {"type": "object", "properties": {}, "required": []}
|
|
136
|
+
if func_params == default_params and func.entrypoint and not func.skip_entrypoint_processing:
|
|
137
|
+
try:
|
|
138
|
+
# Create a copy to avoid modifying the original
|
|
139
|
+
func_copy = func.model_copy(deep=False)
|
|
140
|
+
func_copy.process_entrypoint(strict=False)
|
|
141
|
+
func_params = func_copy.parameters
|
|
142
|
+
except Exception:
|
|
143
|
+
# If processing fails, use original parameters
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
components.append(
|
|
147
|
+
ComponentResponse(
|
|
148
|
+
name=func_name,
|
|
149
|
+
component_type="tool",
|
|
150
|
+
description=_safe_str(getattr(func, "description", None)),
|
|
151
|
+
parameters=_maybe_jsonable(func_params),
|
|
152
|
+
requires_confirmation=requires_confirmation,
|
|
153
|
+
external_execution=external_execution,
|
|
154
|
+
metadata={
|
|
155
|
+
"class_path": _class_path(func),
|
|
156
|
+
"toolkit": toolkit_name,
|
|
157
|
+
"has_entrypoint": bool(getattr(func, "entrypoint", None)),
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
elif isinstance(tool, Function):
|
|
163
|
+
func_name = _safe_name(tool, fallback=tool.__class__.__name__)
|
|
164
|
+
requires_confirmation = getattr(tool, "requires_confirmation", None)
|
|
165
|
+
external_execution = getattr(tool, "external_execution", None)
|
|
166
|
+
|
|
167
|
+
# Get parameters - ensure they're processed if needed
|
|
168
|
+
func_params = tool.parameters
|
|
169
|
+
# If parameters are empty/default and function has entrypoint, try to process it
|
|
170
|
+
default_params = {"type": "object", "properties": {}, "required": []}
|
|
171
|
+
if func_params == default_params and tool.entrypoint and not tool.skip_entrypoint_processing:
|
|
172
|
+
try:
|
|
173
|
+
# Create a copy to avoid modifying the original
|
|
174
|
+
tool_copy = tool.model_copy(deep=False)
|
|
175
|
+
tool_copy.process_entrypoint(strict=False)
|
|
176
|
+
func_params = tool_copy.parameters
|
|
177
|
+
except Exception:
|
|
178
|
+
# If processing fails, use original parameters
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
components.append(
|
|
182
|
+
ComponentResponse(
|
|
183
|
+
name=func_name,
|
|
184
|
+
component_type="tool",
|
|
185
|
+
description=_safe_str(getattr(tool, "description", None)),
|
|
186
|
+
parameters=_maybe_jsonable(func_params),
|
|
187
|
+
requires_confirmation=requires_confirmation,
|
|
188
|
+
external_execution=external_execution,
|
|
189
|
+
metadata={
|
|
190
|
+
"class_path": _class_path(tool),
|
|
191
|
+
"has_entrypoint": bool(getattr(tool, "entrypoint", None)),
|
|
192
|
+
},
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
elif callable(tool):
|
|
197
|
+
call_name = getattr(tool, "__name__", None) or tool.__class__.__name__
|
|
198
|
+
components.append(
|
|
199
|
+
ComponentResponse(
|
|
200
|
+
name=str(call_name),
|
|
201
|
+
component_type="tool",
|
|
202
|
+
description=_safe_str(getattr(tool, "__doc__", None)),
|
|
203
|
+
metadata={"class_path": _class_path(tool)},
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Models
|
|
208
|
+
for model in getattr(registry, "models", []) or []:
|
|
209
|
+
model_name = (
|
|
210
|
+
_safe_str(getattr(model, "id", None))
|
|
211
|
+
or _safe_str(getattr(model, "name", None))
|
|
212
|
+
or model.__class__.__name__
|
|
213
|
+
)
|
|
214
|
+
components.append(
|
|
215
|
+
ComponentResponse(
|
|
216
|
+
name=model_name,
|
|
217
|
+
component_type="model",
|
|
218
|
+
description=_safe_str(getattr(model, "description", None)),
|
|
219
|
+
metadata={
|
|
220
|
+
"class_path": _class_path(model),
|
|
221
|
+
"provider": _safe_str(getattr(model, "provider", None)),
|
|
222
|
+
"model_id": _safe_str(getattr(model, "id", None)),
|
|
223
|
+
},
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Databases
|
|
228
|
+
for db in getattr(registry, "dbs", []) or []:
|
|
229
|
+
db_name = (
|
|
230
|
+
_safe_str(getattr(db, "name", None))
|
|
231
|
+
or _safe_str(getattr(db, "id", None))
|
|
232
|
+
or _safe_str(getattr(db, "table_name", None))
|
|
233
|
+
or db.__class__.__name__
|
|
234
|
+
)
|
|
235
|
+
components.append(
|
|
236
|
+
ComponentResponse(
|
|
237
|
+
name=db_name,
|
|
238
|
+
component_type="db",
|
|
239
|
+
metadata={
|
|
240
|
+
"class_path": _class_path(db),
|
|
241
|
+
"db_id": _safe_str(getattr(db, "id", None)),
|
|
242
|
+
"table_name": _safe_str(getattr(db, "table_name", None)),
|
|
243
|
+
},
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Vector databases
|
|
248
|
+
for vdb in getattr(registry, "vector_dbs", []) or []:
|
|
249
|
+
vdb_name = (
|
|
250
|
+
_safe_str(getattr(vdb, "name", None))
|
|
251
|
+
or _safe_str(getattr(vdb, "id", None))
|
|
252
|
+
or _safe_str(getattr(vdb, "collection", None))
|
|
253
|
+
or _safe_str(getattr(vdb, "table_name", None))
|
|
254
|
+
or vdb.__class__.__name__
|
|
255
|
+
)
|
|
256
|
+
components.append(
|
|
257
|
+
ComponentResponse(
|
|
258
|
+
name=vdb_name,
|
|
259
|
+
component_type="vector_db",
|
|
260
|
+
metadata={
|
|
261
|
+
"class_path": _class_path(vdb),
|
|
262
|
+
"vector_db_id": _safe_str(getattr(vdb, "id", None)),
|
|
263
|
+
"collection": _safe_str(getattr(vdb, "collection", None)),
|
|
264
|
+
"table_name": _safe_str(getattr(vdb, "table_name", None)),
|
|
265
|
+
},
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Schemas
|
|
270
|
+
for schema in getattr(registry, "schemas", []) or []:
|
|
271
|
+
schema_name = schema.__name__
|
|
272
|
+
meta: Dict[str, Any] = {"class_path": _class_path(schema)}
|
|
273
|
+
if include_schema:
|
|
274
|
+
try:
|
|
275
|
+
meta["schema"] = schema.model_json_schema() if hasattr(schema, "model_json_schema") else {}
|
|
276
|
+
except Exception as e:
|
|
277
|
+
meta["schema_error"] = str(e)
|
|
278
|
+
|
|
279
|
+
components.append(
|
|
280
|
+
ComponentResponse(
|
|
281
|
+
name=schema_name,
|
|
282
|
+
component_type="schema",
|
|
283
|
+
metadata=meta,
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Stable ordering helps pagination
|
|
288
|
+
components.sort(key=lambda c: (c.component_type, c.name))
|
|
289
|
+
return components
|
|
290
|
+
|
|
291
|
+
@router.get(
|
|
292
|
+
"/registry",
|
|
293
|
+
response_model=PaginatedResponse[ComponentResponse],
|
|
294
|
+
response_model_exclude_none=True,
|
|
295
|
+
status_code=200,
|
|
296
|
+
operation_id="list_registry",
|
|
297
|
+
summary="List Registry",
|
|
298
|
+
description="List all components in the registry with optional filtering.",
|
|
299
|
+
)
|
|
300
|
+
async def list_registry(
|
|
301
|
+
component_type: Optional[ComponentType] = Query(None, description="Filter by component type"),
|
|
302
|
+
name: Optional[str] = Query(None, description="Filter by name (partial match)"),
|
|
303
|
+
include_schema: bool = Query(False, description="Include JSON schema for schema components"),
|
|
304
|
+
page: int = Query(1, ge=1, description="Page number"),
|
|
305
|
+
limit: int = Query(20, ge=1, le=100, description="Items per page"),
|
|
306
|
+
) -> PaginatedResponse[ComponentResponse]:
|
|
307
|
+
try:
|
|
308
|
+
start_time_ms = time.time() * 1000
|
|
309
|
+
components = _get_all_components(include_schema=include_schema)
|
|
310
|
+
|
|
311
|
+
if component_type:
|
|
312
|
+
components = [c for c in components if c.component_type == component_type]
|
|
313
|
+
|
|
314
|
+
if name:
|
|
315
|
+
needle = name.lower().strip()
|
|
316
|
+
components = [c for c in components if needle in c.name.lower()]
|
|
317
|
+
|
|
318
|
+
total_count = len(components)
|
|
319
|
+
total_pages = (total_count + limit - 1) // limit if limit > 0 else 0
|
|
320
|
+
start_idx = (page - 1) * limit
|
|
321
|
+
paginated = components[start_idx : start_idx + limit]
|
|
322
|
+
|
|
323
|
+
return PaginatedResponse(
|
|
324
|
+
data=paginated,
|
|
325
|
+
meta=PaginationInfo(
|
|
326
|
+
page=page,
|
|
327
|
+
limit=limit,
|
|
328
|
+
total_pages=total_pages,
|
|
329
|
+
total_count=total_count,
|
|
330
|
+
search_time_ms=round(time.time() * 1000 - start_time_ms, 2),
|
|
331
|
+
),
|
|
332
|
+
)
|
|
333
|
+
except Exception as e:
|
|
334
|
+
log_error(f"Error listing components: {e}")
|
|
335
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
336
|
+
|
|
337
|
+
return router
|
agno/os/routers/teams/router.py
CHANGED
|
@@ -13,6 +13,7 @@ from fastapi import (
|
|
|
13
13
|
)
|
|
14
14
|
from fastapi.responses import JSONResponse, StreamingResponse
|
|
15
15
|
|
|
16
|
+
from agno.db.base import BaseDb
|
|
16
17
|
from agno.exceptions import InputCheckError, OutputCheckError
|
|
17
18
|
from agno.media import Audio, Image, Video
|
|
18
19
|
from agno.media import File as FileMedia
|
|
@@ -35,6 +36,7 @@ from agno.os.utils import (
|
|
|
35
36
|
process_image,
|
|
36
37
|
process_video,
|
|
37
38
|
)
|
|
39
|
+
from agno.registry import Registry
|
|
38
40
|
from agno.run.team import RunErrorEvent as TeamRunErrorEvent
|
|
39
41
|
from agno.team.remote import RemoteTeam
|
|
40
42
|
from agno.team.team import Team
|
|
@@ -111,6 +113,7 @@ async def team_response_streamer(
|
|
|
111
113
|
def get_team_router(
|
|
112
114
|
os: "AgentOS",
|
|
113
115
|
settings: AgnoAPISettings = AgnoAPISettings(),
|
|
116
|
+
registry: Optional[Registry] = None,
|
|
114
117
|
) -> APIRouter:
|
|
115
118
|
"""Create the team router with comprehensive OpenAPI documentation."""
|
|
116
119
|
router = APIRouter(
|
|
@@ -165,6 +168,7 @@ def get_team_router(
|
|
|
165
168
|
session_id: Optional[str] = Form(None),
|
|
166
169
|
user_id: Optional[str] = Form(None),
|
|
167
170
|
files: Optional[List[UploadFile]] = File(None),
|
|
171
|
+
version: Optional[int] = Form(None),
|
|
168
172
|
):
|
|
169
173
|
kwargs = await get_request_kwargs(request, create_team_run)
|
|
170
174
|
|
|
@@ -194,7 +198,9 @@ def get_team_router(
|
|
|
194
198
|
|
|
195
199
|
logger.debug(f"Creating team run: {message=} {session_id=} {monitor=} {user_id=} {team_id=} {files=} {kwargs=}")
|
|
196
200
|
|
|
197
|
-
team = get_team_by_id(
|
|
201
|
+
team = get_team_by_id(
|
|
202
|
+
team_id=team_id, teams=os.teams, db=os.db, version=version, registry=registry, create_fresh=True
|
|
203
|
+
)
|
|
198
204
|
if team is None:
|
|
199
205
|
raise HTTPException(status_code=404, detail="Team not found")
|
|
200
206
|
|
|
@@ -321,7 +327,7 @@ def get_team_router(
|
|
|
321
327
|
team_id: str,
|
|
322
328
|
run_id: str,
|
|
323
329
|
):
|
|
324
|
-
team = get_team_by_id(team_id, os.teams, create_fresh=True)
|
|
330
|
+
team = get_team_by_id(team_id=team_id, teams=os.teams, db=os.db, registry=registry, create_fresh=True)
|
|
325
331
|
if team is None:
|
|
326
332
|
raise HTTPException(status_code=404, detail="Team not found")
|
|
327
333
|
|
|
@@ -414,9 +420,6 @@ def get_team_router(
|
|
|
414
420
|
)
|
|
415
421
|
async def get_teams(request: Request) -> List[TeamResponse]:
|
|
416
422
|
"""Return the list of all Teams present in the contextual OS"""
|
|
417
|
-
if os.teams is None:
|
|
418
|
-
return []
|
|
419
|
-
|
|
420
423
|
# Filter teams based on user's scopes (only if authorization is enabled)
|
|
421
424
|
if getattr(request.state, "authorization_enabled", False):
|
|
422
425
|
from agno.os.auth import filter_resources_by_access, get_accessible_resources
|
|
@@ -426,9 +429,9 @@ def get_team_router(
|
|
|
426
429
|
if not accessible_ids:
|
|
427
430
|
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
|
428
431
|
|
|
429
|
-
accessible_teams = filter_resources_by_access(request, os.teams, "teams")
|
|
432
|
+
accessible_teams = filter_resources_by_access(request, os.teams or [], "teams")
|
|
430
433
|
else:
|
|
431
|
-
accessible_teams = os.teams
|
|
434
|
+
accessible_teams = os.teams or []
|
|
432
435
|
|
|
433
436
|
teams = []
|
|
434
437
|
for team in accessible_teams:
|
|
@@ -438,6 +441,15 @@ def get_team_router(
|
|
|
438
441
|
team_response = await TeamResponse.from_team(team=team)
|
|
439
442
|
teams.append(team_response)
|
|
440
443
|
|
|
444
|
+
# Also load teams from database
|
|
445
|
+
if os.db and isinstance(os.db, BaseDb):
|
|
446
|
+
from agno.team.team import get_teams
|
|
447
|
+
|
|
448
|
+
db_teams = get_teams(db=os.db, registry=registry)
|
|
449
|
+
for db_team in db_teams:
|
|
450
|
+
team_response = await TeamResponse.from_team(team=db_team)
|
|
451
|
+
teams.append(team_response)
|
|
452
|
+
|
|
441
453
|
return teams
|
|
442
454
|
|
|
443
455
|
@router.get(
|
|
@@ -526,7 +538,7 @@ def get_team_router(
|
|
|
526
538
|
dependencies=[Depends(require_resource_access("teams", "read", "team_id"))],
|
|
527
539
|
)
|
|
528
540
|
async def get_team(team_id: str, request: Request) -> TeamResponse:
|
|
529
|
-
team = get_team_by_id(team_id, os.teams, create_fresh=True)
|
|
541
|
+
team = get_team_by_id(team_id=team_id, teams=os.teams, db=os.db, registry=registry, create_fresh=True)
|
|
530
542
|
if team is None:
|
|
531
543
|
raise HTTPException(status_code=404, detail="Team not found")
|
|
532
544
|
|
agno/os/routers/teams/schema.py
CHANGED
|
@@ -65,7 +65,7 @@ class TeamResponse(BaseModel):
|
|
|
65
65
|
"enable_agentic_knowledge_filters": False,
|
|
66
66
|
# Memory defaults
|
|
67
67
|
"enable_agentic_memory": False,
|
|
68
|
-
"
|
|
68
|
+
"update_memory_on_run": False,
|
|
69
69
|
# Reasoning defaults
|
|
70
70
|
"reasoning": False,
|
|
71
71
|
"reasoning_min_steps": 1,
|
|
@@ -137,8 +137,9 @@ class TeamResponse(BaseModel):
|
|
|
137
137
|
"cache_session": team.cache_session,
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
contents_db = getattr(team.knowledge, "contents_db", None) if team.knowledge else None
|
|
140
141
|
knowledge_info = {
|
|
141
|
-
"db_id":
|
|
142
|
+
"db_id": contents_db.id if contents_db else None,
|
|
142
143
|
"knowledge_table": knowledge_table,
|
|
143
144
|
"enable_agentic_knowledge_filters": team.enable_agentic_knowledge_filters,
|
|
144
145
|
"knowledge_filters": team.knowledge_filters,
|
|
@@ -149,9 +150,10 @@ class TeamResponse(BaseModel):
|
|
|
149
150
|
if team.memory_manager is not None:
|
|
150
151
|
memory_info = {
|
|
151
152
|
"enable_agentic_memory": team.enable_agentic_memory,
|
|
152
|
-
"
|
|
153
|
+
"update_memory_on_run": team.update_memory_on_run,
|
|
154
|
+
"enable_user_memories": team.enable_user_memories, # Soon to be deprecated. Use update_memory_on_run
|
|
153
155
|
"metadata": team.metadata,
|
|
154
|
-
"memory_table": team.db.memory_table_name if team.db and team.
|
|
156
|
+
"memory_table": team.db.memory_table_name if team.db and team.update_memory_on_run else None,
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
if team.memory_manager.model is not None:
|
agno/os/routers/traces/traces.py
CHANGED
|
@@ -22,7 +22,7 @@ from agno.os.schema import (
|
|
|
22
22
|
ValidationErrorResponse,
|
|
23
23
|
)
|
|
24
24
|
from agno.os.settings import AgnoAPISettings
|
|
25
|
-
from agno.os.utils import get_db,
|
|
25
|
+
from agno.os.utils import get_db, timestamp_to_datetime
|
|
26
26
|
from agno.remote.base import RemoteDb
|
|
27
27
|
from agno.utils.log import log_error
|
|
28
28
|
|
|
@@ -159,8 +159,8 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
159
159
|
start_time_ms = time_module.time() * 1000
|
|
160
160
|
|
|
161
161
|
# Convert ISO datetime strings to UTC datetime objects
|
|
162
|
-
start_time_dt =
|
|
163
|
-
end_time_dt =
|
|
162
|
+
start_time_dt = timestamp_to_datetime(start_time, "start_time") if start_time else None
|
|
163
|
+
end_time_dt = timestamp_to_datetime(end_time, "end_time") if end_time else None
|
|
164
164
|
|
|
165
165
|
if isinstance(db, AsyncBaseDb):
|
|
166
166
|
traces, total_count = await db.get_traces(
|
|
@@ -484,8 +484,8 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
484
484
|
start_time_ms = time_module.time() * 1000
|
|
485
485
|
|
|
486
486
|
# Convert ISO datetime strings to UTC datetime objects
|
|
487
|
-
start_time_dt =
|
|
488
|
-
end_time_dt =
|
|
487
|
+
start_time_dt = timestamp_to_datetime(start_time, "start_time") if start_time else None
|
|
488
|
+
end_time_dt = timestamp_to_datetime(end_time, "end_time") if end_time else None
|
|
489
489
|
|
|
490
490
|
if isinstance(db, AsyncBaseDb):
|
|
491
491
|
stats_list, total_count = await db.get_trace_stats(
|