agno 2.3.25__py3-none-any.whl → 2.4.0__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 +1428 -558
- 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 +1229 -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 +1173 -13
- agno/db/utils.py +37 -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 +2767 -2254
- 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 +5 -5
- agno/knowledge/reader/docx_reader.py +2 -2
- agno/knowledge/reader/field_labeled_csv_reader.py +2 -2
- 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 +110 -0
- 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/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 +48 -4
- agno/models/cohere/chat.py +25 -0
- agno/models/google/gemini.py +50 -5
- agno/models/litellm/chat.py +38 -0
- agno/models/openai/chat.py +7 -0
- agno/models/openrouter/openrouter.py +46 -0
- agno/models/response.py +16 -0
- agno/os/app.py +83 -44
- 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 +466 -0
- agno/os/routers/evals/schemas.py +4 -3
- agno/os/routers/health.py +3 -3
- agno/os/routers/knowledge/knowledge.py +3 -3
- 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 +133 -16
- 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 +57 -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 +1276 -326
- 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/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 +625 -63
- {agno-2.3.25.dist-info → agno-2.4.0.dist-info}/METADATA +46 -76
- {agno-2.3.25.dist-info → agno-2.4.0.dist-info}/RECORD +128 -117
- {agno-2.3.25.dist-info → agno-2.4.0.dist-info}/WHEEL +0 -0
- {agno-2.3.25.dist-info → agno-2.4.0.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.25.dist-info → agno-2.4.0.dist-info}/top_level.txt +0 -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)
|
|
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)
|
|
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(
|
|
@@ -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:
|