agno 2.3.16__py3-none-any.whl → 2.3.18__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 +2 -0
- agno/agent/agent.py +4 -53
- agno/agent/remote.py +351 -0
- agno/client/__init__.py +3 -0
- agno/client/os.py +2669 -0
- agno/db/base.py +20 -0
- agno/db/mongo/async_mongo.py +11 -0
- agno/db/mongo/mongo.py +10 -0
- agno/db/mysql/async_mysql.py +9 -0
- agno/db/mysql/mysql.py +9 -0
- agno/db/postgres/async_postgres.py +9 -0
- agno/db/postgres/postgres.py +9 -0
- agno/db/postgres/utils.py +3 -2
- agno/db/sqlite/async_sqlite.py +9 -0
- agno/db/sqlite/sqlite.py +11 -1
- agno/exceptions.py +23 -0
- agno/knowledge/chunking/semantic.py +123 -46
- agno/knowledge/reader/csv_reader.py +1 -1
- agno/knowledge/reader/field_labeled_csv_reader.py +1 -1
- agno/knowledge/reader/json_reader.py +1 -1
- agno/models/google/gemini.py +5 -0
- agno/os/app.py +108 -25
- agno/os/auth.py +25 -1
- agno/os/interfaces/a2a/a2a.py +7 -6
- agno/os/interfaces/a2a/router.py +13 -13
- agno/os/interfaces/agui/agui.py +5 -3
- agno/os/interfaces/agui/router.py +23 -16
- agno/os/interfaces/base.py +7 -7
- agno/os/interfaces/slack/router.py +6 -6
- agno/os/interfaces/slack/slack.py +7 -7
- agno/os/interfaces/whatsapp/router.py +29 -6
- agno/os/interfaces/whatsapp/whatsapp.py +11 -8
- agno/os/managers.py +326 -0
- agno/os/mcp.py +651 -79
- agno/os/router.py +125 -18
- agno/os/routers/agents/router.py +65 -22
- agno/os/routers/agents/schema.py +16 -4
- agno/os/routers/database.py +5 -0
- agno/os/routers/evals/evals.py +93 -11
- agno/os/routers/evals/utils.py +6 -6
- agno/os/routers/knowledge/knowledge.py +104 -16
- agno/os/routers/memory/memory.py +124 -7
- agno/os/routers/metrics/metrics.py +21 -4
- agno/os/routers/session/session.py +141 -12
- agno/os/routers/teams/router.py +40 -14
- agno/os/routers/teams/schema.py +12 -4
- agno/os/routers/traces/traces.py +54 -4
- agno/os/routers/workflows/router.py +223 -117
- agno/os/routers/workflows/schema.py +65 -1
- agno/os/schema.py +38 -12
- agno/os/utils.py +87 -166
- agno/remote/__init__.py +3 -0
- agno/remote/base.py +484 -0
- agno/run/workflow.py +1 -0
- agno/team/__init__.py +2 -0
- agno/team/remote.py +287 -0
- agno/team/team.py +25 -54
- agno/tracing/exporter.py +10 -6
- agno/tracing/setup.py +2 -1
- agno/utils/agent.py +58 -1
- agno/utils/http.py +68 -20
- agno/utils/os.py +0 -0
- agno/utils/remote.py +23 -0
- agno/vectordb/chroma/chromadb.py +452 -16
- agno/vectordb/pgvector/pgvector.py +7 -0
- agno/vectordb/redis/redisdb.py +1 -1
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +2 -2
- agno/workflow/remote.py +222 -0
- agno/workflow/types.py +0 -73
- agno/workflow/workflow.py +119 -68
- {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/METADATA +1 -1
- {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/RECORD +76 -66
- {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/WHEEL +0 -0
- {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/top_level.txt +0 -0
agno/os/app.py
CHANGED
|
@@ -5,13 +5,15 @@ from typing import Any, Dict, List, Literal, Optional, Union
|
|
|
5
5
|
from uuid import uuid4
|
|
6
6
|
|
|
7
7
|
from fastapi import APIRouter, FastAPI, HTTPException
|
|
8
|
+
from fastapi.exceptions import RequestValidationError
|
|
8
9
|
from fastapi.responses import JSONResponse
|
|
9
10
|
from fastapi.routing import APIRoute
|
|
11
|
+
from httpx import HTTPStatusError
|
|
10
12
|
from rich import box
|
|
11
13
|
from rich.panel import Panel
|
|
12
14
|
from starlette.requests import Request
|
|
13
15
|
|
|
14
|
-
from agno.agent
|
|
16
|
+
from agno.agent import Agent, RemoteAgent
|
|
15
17
|
from agno.db.base import AsyncBaseDb, BaseDb
|
|
16
18
|
from agno.knowledge.knowledge import Knowledge
|
|
17
19
|
from agno.os.config import (
|
|
@@ -32,7 +34,7 @@ from agno.os.config import (
|
|
|
32
34
|
TracesDomainConfig,
|
|
33
35
|
)
|
|
34
36
|
from agno.os.interfaces.base import BaseInterface
|
|
35
|
-
from agno.os.router import get_base_router
|
|
37
|
+
from agno.os.router import get_base_router, get_websocket_router
|
|
36
38
|
from agno.os.routers.agents import get_agent_router
|
|
37
39
|
from agno.os.routers.database import get_database_router
|
|
38
40
|
from agno.os.routers.evals import get_eval_router
|
|
@@ -44,7 +46,7 @@ from agno.os.routers.metrics import get_metrics_router
|
|
|
44
46
|
from agno.os.routers.session import get_session_router
|
|
45
47
|
from agno.os.routers.teams import get_team_router
|
|
46
48
|
from agno.os.routers.traces import get_traces_router
|
|
47
|
-
from agno.os.routers.workflows import
|
|
49
|
+
from agno.os.routers.workflows import get_workflow_router
|
|
48
50
|
from agno.os.settings import AgnoAPISettings
|
|
49
51
|
from agno.os.utils import (
|
|
50
52
|
collect_mcp_tools_from_team,
|
|
@@ -55,34 +57,46 @@ from agno.os.utils import (
|
|
|
55
57
|
setup_tracing_for_os,
|
|
56
58
|
update_cors_middleware,
|
|
57
59
|
)
|
|
58
|
-
from agno.
|
|
60
|
+
from agno.remote.base import RemoteDb, RemoteKnowledge
|
|
61
|
+
from agno.team import RemoteTeam, Team
|
|
59
62
|
from agno.utils.log import log_debug, log_error, log_info, log_warning
|
|
60
63
|
from agno.utils.string import generate_id, generate_id_from_name
|
|
61
|
-
from agno.workflow
|
|
64
|
+
from agno.workflow import RemoteWorkflow, Workflow
|
|
62
65
|
|
|
63
66
|
|
|
64
67
|
@asynccontextmanager
|
|
65
68
|
async def mcp_lifespan(_, mcp_tools):
|
|
66
69
|
"""Manage MCP connection lifecycle inside a FastAPI app"""
|
|
67
|
-
# Startup logic: connect to all contextual MCP servers
|
|
68
70
|
for tool in mcp_tools:
|
|
69
71
|
await tool.connect()
|
|
70
72
|
|
|
71
73
|
yield
|
|
72
74
|
|
|
73
|
-
# Shutdown logic: Close all contextual MCP connections
|
|
74
75
|
for tool in mcp_tools:
|
|
75
76
|
await tool.close()
|
|
76
77
|
|
|
77
78
|
|
|
79
|
+
@asynccontextmanager
|
|
80
|
+
async def http_client_lifespan(_):
|
|
81
|
+
"""Manage httpx client lifecycle for proper connection pool cleanup."""
|
|
82
|
+
from agno.utils.http import aclose_default_clients
|
|
83
|
+
|
|
84
|
+
yield
|
|
85
|
+
|
|
86
|
+
await aclose_default_clients()
|
|
87
|
+
|
|
88
|
+
|
|
78
89
|
@asynccontextmanager
|
|
79
90
|
async def db_lifespan(app: FastAPI, agent_os: "AgentOS"):
|
|
80
|
-
"""Initializes databases in the event loop"""
|
|
91
|
+
"""Initializes databases in the event loop and closes them on shutdown."""
|
|
81
92
|
if agent_os.auto_provision_dbs:
|
|
82
93
|
agent_os._initialize_sync_databases()
|
|
83
94
|
await agent_os._initialize_async_databases()
|
|
95
|
+
|
|
84
96
|
yield
|
|
85
97
|
|
|
98
|
+
await agent_os._close_databases()
|
|
99
|
+
|
|
86
100
|
|
|
87
101
|
def _combine_app_lifespans(lifespans: list) -> Any:
|
|
88
102
|
"""Combine multiple FastAPI app lifespan context managers into one."""
|
|
@@ -115,9 +129,9 @@ class AgentOS:
|
|
|
115
129
|
name: Optional[str] = None,
|
|
116
130
|
description: Optional[str] = None,
|
|
117
131
|
version: Optional[str] = None,
|
|
118
|
-
agents: Optional[List[Agent]] = None,
|
|
119
|
-
teams: Optional[List[Team]] = None,
|
|
120
|
-
workflows: Optional[List[Workflow]] = None,
|
|
132
|
+
agents: Optional[List[Union[Agent, RemoteAgent]]] = None,
|
|
133
|
+
teams: Optional[List[Union[Team, RemoteTeam]]] = None,
|
|
134
|
+
workflows: Optional[List[Union[Workflow, RemoteWorkflow]]] = None,
|
|
121
135
|
knowledge: Optional[List[Knowledge]] = None,
|
|
122
136
|
interfaces: Optional[List[BaseInterface]] = None,
|
|
123
137
|
a2a_interface: bool = False,
|
|
@@ -171,9 +185,9 @@ class AgentOS:
|
|
|
171
185
|
|
|
172
186
|
self.config = load_yaml_config(config) if isinstance(config, str) else config
|
|
173
187
|
|
|
174
|
-
self.agents: Optional[List[Agent]] = agents
|
|
175
|
-
self.workflows: Optional[List[Workflow]] = workflows
|
|
176
|
-
self.teams: Optional[List[Team]] = teams
|
|
188
|
+
self.agents: Optional[List[Union[Agent, RemoteAgent]]] = agents
|
|
189
|
+
self.workflows: Optional[List[Union[Workflow, RemoteWorkflow]]] = workflows
|
|
190
|
+
self.teams: Optional[List[Union[Team, RemoteTeam]]] = teams
|
|
177
191
|
self.interfaces = interfaces or []
|
|
178
192
|
self.a2a_interface = a2a_interface
|
|
179
193
|
self.knowledge = knowledge
|
|
@@ -286,12 +300,14 @@ class AgentOS:
|
|
|
286
300
|
def _reprovision_routers(self, app: FastAPI) -> None:
|
|
287
301
|
"""Re-provision all routes for the AgentOS."""
|
|
288
302
|
updated_routers = [
|
|
303
|
+
get_home_router(self),
|
|
289
304
|
get_session_router(dbs=self.dbs),
|
|
305
|
+
get_memory_router(dbs=self.dbs),
|
|
306
|
+
get_eval_router(dbs=self.dbs, agents=self.agents, teams=self.teams),
|
|
290
307
|
get_metrics_router(dbs=self.dbs),
|
|
291
308
|
get_knowledge_router(knowledge_instances=self.knowledge_instances),
|
|
292
309
|
get_traces_router(dbs=self.dbs),
|
|
293
|
-
|
|
294
|
-
get_eval_router(dbs=self.dbs, agents=self.agents, teams=self.teams),
|
|
310
|
+
get_database_router(self, settings=self.settings),
|
|
295
311
|
]
|
|
296
312
|
|
|
297
313
|
# Clear all previously existing routes
|
|
@@ -400,6 +416,8 @@ class AgentOS:
|
|
|
400
416
|
if not self.agents:
|
|
401
417
|
return
|
|
402
418
|
for agent in self.agents:
|
|
419
|
+
if isinstance(agent, RemoteAgent):
|
|
420
|
+
continue
|
|
403
421
|
# Track all MCP tools to later handle their connection
|
|
404
422
|
if agent.tools:
|
|
405
423
|
for tool in agent.tools:
|
|
@@ -424,6 +442,8 @@ class AgentOS:
|
|
|
424
442
|
return
|
|
425
443
|
|
|
426
444
|
for team in self.teams:
|
|
445
|
+
if isinstance(team, RemoteTeam):
|
|
446
|
+
continue
|
|
427
447
|
# Track all MCP tools recursively
|
|
428
448
|
collect_mcp_tools_from_team(team, self.mcp_tools)
|
|
429
449
|
|
|
@@ -449,6 +469,8 @@ class AgentOS:
|
|
|
449
469
|
|
|
450
470
|
if self.workflows:
|
|
451
471
|
for workflow in self.workflows:
|
|
472
|
+
if isinstance(workflow, RemoteWorkflow):
|
|
473
|
+
continue
|
|
452
474
|
# Track MCP tools recursively in workflow members
|
|
453
475
|
collect_mcp_tools_from_workflow(workflow, self.mcp_tools)
|
|
454
476
|
|
|
@@ -473,7 +495,7 @@ class AgentOS:
|
|
|
473
495
|
return
|
|
474
496
|
|
|
475
497
|
# Fall back to finding the first available database
|
|
476
|
-
db: Optional[Union[BaseDb, AsyncBaseDb]] = None
|
|
498
|
+
db: Optional[Union[BaseDb, AsyncBaseDb, RemoteDb]] = None
|
|
477
499
|
|
|
478
500
|
for agent in self.agents or []:
|
|
479
501
|
if agent.db:
|
|
@@ -535,6 +557,9 @@ class AgentOS:
|
|
|
535
557
|
# The async database lifespan
|
|
536
558
|
lifespans.append(partial(db_lifespan, agent_os=self))
|
|
537
559
|
|
|
560
|
+
# The httpx client cleanup lifespan (should be last to close after other lifespans)
|
|
561
|
+
lifespans.append(http_client_lifespan)
|
|
562
|
+
|
|
538
563
|
# Combine lifespans and set them in the app
|
|
539
564
|
if lifespans:
|
|
540
565
|
fastapi_app.router.lifespan_context = _combine_app_lifespans(lifespans)
|
|
@@ -560,6 +585,9 @@ class AgentOS:
|
|
|
560
585
|
# Async database initialization lifespan
|
|
561
586
|
lifespans.append(partial(db_lifespan, agent_os=self)) # type: ignore
|
|
562
587
|
|
|
588
|
+
# The httpx client cleanup lifespan (should be last to close after other lifespans)
|
|
589
|
+
lifespans.append(http_client_lifespan)
|
|
590
|
+
|
|
563
591
|
final_lifespan = _combine_app_lifespans(lifespans) if lifespans else None
|
|
564
592
|
fastapi_app = self._make_app(lifespan=final_lifespan)
|
|
565
593
|
|
|
@@ -587,6 +615,14 @@ class AgentOS:
|
|
|
587
615
|
|
|
588
616
|
if not self._app_set:
|
|
589
617
|
|
|
618
|
+
@fastapi_app.exception_handler(RequestValidationError)
|
|
619
|
+
async def validation_exception_handler(_: Request, exc: RequestValidationError) -> JSONResponse:
|
|
620
|
+
log_error(f"Validation error (422): {exc.errors()}")
|
|
621
|
+
return JSONResponse(
|
|
622
|
+
status_code=422,
|
|
623
|
+
content={"detail": exc.errors()},
|
|
624
|
+
)
|
|
625
|
+
|
|
590
626
|
@fastapi_app.exception_handler(HTTPException)
|
|
591
627
|
async def http_exception_handler(_, exc: HTTPException) -> JSONResponse:
|
|
592
628
|
log_error(f"HTTP exception: {exc.status_code} {exc.detail}")
|
|
@@ -595,6 +631,16 @@ class AgentOS:
|
|
|
595
631
|
content={"detail": str(exc.detail)},
|
|
596
632
|
)
|
|
597
633
|
|
|
634
|
+
@fastapi_app.exception_handler(HTTPStatusError)
|
|
635
|
+
async def http_status_error_handler(_: Request, exc: HTTPStatusError) -> JSONResponse:
|
|
636
|
+
status_code = exc.response.status_code
|
|
637
|
+
detail = exc.response.text
|
|
638
|
+
log_error(f"Downstream server returned HTTP status error: {status_code} {detail}")
|
|
639
|
+
return JSONResponse(
|
|
640
|
+
status_code=status_code,
|
|
641
|
+
content={"detail": detail},
|
|
642
|
+
)
|
|
643
|
+
|
|
598
644
|
@fastapi_app.exception_handler(Exception)
|
|
599
645
|
async def general_exception_handler(_: Request, exc: Exception) -> JSONResponse:
|
|
600
646
|
import traceback
|
|
@@ -734,19 +780,28 @@ class AgentOS:
|
|
|
734
780
|
|
|
735
781
|
def _get_telemetry_data(self) -> Dict[str, Any]:
|
|
736
782
|
"""Get the telemetry data for the OS"""
|
|
783
|
+
agent_ids = []
|
|
784
|
+
team_ids = []
|
|
785
|
+
workflow_ids = []
|
|
786
|
+
for agent in self.agents or []:
|
|
787
|
+
agent_ids.append(agent.id)
|
|
788
|
+
for team in self.teams or []:
|
|
789
|
+
team_ids.append(team.id)
|
|
790
|
+
for workflow in self.workflows or []:
|
|
791
|
+
workflow_ids.append(workflow.id)
|
|
737
792
|
return {
|
|
738
|
-
"agents":
|
|
739
|
-
"teams":
|
|
740
|
-
"workflows":
|
|
793
|
+
"agents": agent_ids,
|
|
794
|
+
"teams": team_ids,
|
|
795
|
+
"workflows": workflow_ids,
|
|
741
796
|
"interfaces": [interface.type for interface in self.interfaces] if self.interfaces else None,
|
|
742
797
|
}
|
|
743
798
|
|
|
744
799
|
def _auto_discover_databases(self) -> None:
|
|
745
800
|
"""Auto-discover and initialize the databases used by all contextual agents, teams and workflows."""
|
|
746
801
|
|
|
747
|
-
dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb]]] = {}
|
|
802
|
+
dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb, RemoteDb]]] = {}
|
|
748
803
|
knowledge_dbs: Dict[
|
|
749
|
-
str, List[Union[BaseDb, AsyncBaseDb]]
|
|
804
|
+
str, List[Union[BaseDb, AsyncBaseDb, RemoteDb]]
|
|
750
805
|
] = {} # Track databases specifically used for knowledge
|
|
751
806
|
|
|
752
807
|
for agent in self.agents or []:
|
|
@@ -833,6 +888,32 @@ class AgentOS:
|
|
|
833
888
|
except Exception as e:
|
|
834
889
|
log_warning(f"Failed to initialize async {db.__class__.__name__} (id: {db.id}): {e}")
|
|
835
890
|
|
|
891
|
+
async def _close_databases(self) -> None:
|
|
892
|
+
"""Close all database connections and release connection pools."""
|
|
893
|
+
from itertools import chain
|
|
894
|
+
|
|
895
|
+
if not hasattr(self, "dbs") or not hasattr(self, "knowledge_dbs"):
|
|
896
|
+
return
|
|
897
|
+
|
|
898
|
+
unique_dbs = list(
|
|
899
|
+
{
|
|
900
|
+
id(db): db
|
|
901
|
+
for db in chain(
|
|
902
|
+
chain.from_iterable(self.dbs.values()), chain.from_iterable(self.knowledge_dbs.values())
|
|
903
|
+
)
|
|
904
|
+
}.values()
|
|
905
|
+
)
|
|
906
|
+
|
|
907
|
+
for db in unique_dbs:
|
|
908
|
+
try:
|
|
909
|
+
if hasattr(db, "close") and callable(db.close):
|
|
910
|
+
if isinstance(db, AsyncBaseDb):
|
|
911
|
+
await db.close()
|
|
912
|
+
else:
|
|
913
|
+
db.close()
|
|
914
|
+
except Exception as e:
|
|
915
|
+
log_warning(f"Failed to close {db.__class__.__name__} (id: {db.id}): {e}")
|
|
916
|
+
|
|
836
917
|
def _get_db_table_names(self, db: BaseDb) -> Dict[str, str]:
|
|
837
918
|
"""Get the table names for a database"""
|
|
838
919
|
table_names = {
|
|
@@ -846,7 +927,9 @@ class AgentOS:
|
|
|
846
927
|
return {k: v for k, v in table_names.items() if v is not None}
|
|
847
928
|
|
|
848
929
|
def _register_db_with_validation(
|
|
849
|
-
self,
|
|
930
|
+
self,
|
|
931
|
+
registered_dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb, RemoteDb]]],
|
|
932
|
+
db: Union[BaseDb, AsyncBaseDb, RemoteDb],
|
|
850
933
|
) -> None:
|
|
851
934
|
"""Register a database in the contextual OS after validating it is not conflicting with registered databases"""
|
|
852
935
|
if db.id in registered_dbs:
|
|
@@ -857,9 +940,9 @@ class AgentOS:
|
|
|
857
940
|
def _auto_discover_knowledge_instances(self) -> None:
|
|
858
941
|
"""Auto-discover the knowledge instances used by all contextual agents, teams and workflows."""
|
|
859
942
|
seen_ids = set()
|
|
860
|
-
knowledge_instances: List[Knowledge] = []
|
|
943
|
+
knowledge_instances: List[Union[Knowledge, RemoteKnowledge]] = []
|
|
861
944
|
|
|
862
|
-
def _add_knowledge_if_not_duplicate(knowledge: "Knowledge") -> None:
|
|
945
|
+
def _add_knowledge_if_not_duplicate(knowledge: Union["Knowledge", RemoteKnowledge]) -> None:
|
|
863
946
|
"""Add knowledge instance if it's not already in the list (by object identity or db_id)."""
|
|
864
947
|
# Use database ID if available, otherwise use object ID as fallback
|
|
865
948
|
if not knowledge.contents_db:
|
agno/os/auth.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from os import getenv
|
|
2
|
-
from typing import List, Set
|
|
2
|
+
from typing import List, Optional, Set
|
|
3
3
|
|
|
4
4
|
from fastapi import Depends, HTTPException, Request
|
|
5
5
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
@@ -11,6 +11,30 @@ from agno.os.settings import AgnoAPISettings
|
|
|
11
11
|
security = HTTPBearer(auto_error=False)
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
def get_auth_token_from_request(request: Request) -> Optional[str]:
|
|
15
|
+
"""
|
|
16
|
+
Extract the JWT/Bearer token from the Authorization header.
|
|
17
|
+
|
|
18
|
+
This is used to forward the auth token to remote agents/teams/workflows
|
|
19
|
+
when making requests through the gateway.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
request: The FastAPI request object
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
The bearer token string if present, None otherwise
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
auth_token = get_auth_token_from_request(request)
|
|
29
|
+
if auth_token and isinstance(agent, RemoteAgent):
|
|
30
|
+
await agent.arun(message, auth_token=auth_token)
|
|
31
|
+
"""
|
|
32
|
+
auth_header = request.headers.get("Authorization")
|
|
33
|
+
if auth_header and auth_header.lower().startswith("bearer "):
|
|
34
|
+
return auth_header[7:] # Remove "Bearer " prefix
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
14
38
|
def _is_jwt_configured() -> bool:
|
|
15
39
|
"""Check if JWT authentication is configured via environment variables.
|
|
16
40
|
|
agno/os/interfaces/a2a/a2a.py
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
"""Main class for the A2A app, used to expose an Agno Agent, Team, or Workflow in an A2A compatible format."""
|
|
2
2
|
|
|
3
|
-
from typing import Optional
|
|
3
|
+
from typing import Optional, Union
|
|
4
4
|
|
|
5
5
|
from fastapi.routing import APIRouter
|
|
6
6
|
from typing_extensions import List
|
|
7
7
|
|
|
8
8
|
from agno.agent import Agent
|
|
9
|
+
from agno.agent.remote import RemoteAgent
|
|
9
10
|
from agno.os.interfaces.a2a.router import attach_routes
|
|
10
11
|
from agno.os.interfaces.base import BaseInterface
|
|
11
|
-
from agno.team import Team
|
|
12
|
-
from agno.workflow import Workflow
|
|
12
|
+
from agno.team import RemoteTeam, Team
|
|
13
|
+
from agno.workflow import RemoteWorkflow, Workflow
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class A2A(BaseInterface):
|
|
@@ -19,9 +20,9 @@ class A2A(BaseInterface):
|
|
|
19
20
|
|
|
20
21
|
def __init__(
|
|
21
22
|
self,
|
|
22
|
-
agents: Optional[List[Agent]] = None,
|
|
23
|
-
teams: Optional[List[Team]] = None,
|
|
24
|
-
workflows: Optional[List[Workflow]] = None,
|
|
23
|
+
agents: Optional[List[Union[Agent, RemoteAgent]]] = None,
|
|
24
|
+
teams: Optional[List[Union[Team, RemoteTeam]]] = None,
|
|
25
|
+
workflows: Optional[List[Union[Workflow, RemoteWorkflow]]] = None,
|
|
25
26
|
prefix: str = "/a2a",
|
|
26
27
|
tags: Optional[List[str]] = None,
|
|
27
28
|
):
|
agno/os/interfaces/a2a/router.py
CHANGED
|
@@ -23,22 +23,22 @@ except ImportError as e:
|
|
|
23
23
|
|
|
24
24
|
import warnings
|
|
25
25
|
|
|
26
|
-
from agno.agent import Agent
|
|
26
|
+
from agno.agent import Agent, RemoteAgent
|
|
27
27
|
from agno.os.interfaces.a2a.utils import (
|
|
28
28
|
map_a2a_request_to_run_input,
|
|
29
29
|
map_run_output_to_a2a_task,
|
|
30
30
|
stream_a2a_response_with_error_handling,
|
|
31
31
|
)
|
|
32
32
|
from agno.os.utils import get_agent_by_id, get_request_kwargs, get_team_by_id, get_workflow_by_id
|
|
33
|
-
from agno.team import Team
|
|
34
|
-
from agno.workflow import Workflow
|
|
33
|
+
from agno.team import RemoteTeam, Team
|
|
34
|
+
from agno.workflow import RemoteWorkflow, Workflow
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def attach_routes(
|
|
38
38
|
router: APIRouter,
|
|
39
|
-
agents: Optional[List[Agent]] = None,
|
|
40
|
-
teams: Optional[List[Team]] = None,
|
|
41
|
-
workflows: Optional[List[Workflow]] = None,
|
|
39
|
+
agents: Optional[List[Union[Agent, RemoteAgent]]] = None,
|
|
40
|
+
teams: Optional[List[Union[Team, RemoteTeam]]] = None,
|
|
41
|
+
workflows: Optional[List[Union[Workflow, RemoteWorkflow]]] = None,
|
|
42
42
|
) -> APIRouter:
|
|
43
43
|
if agents is None and teams is None and workflows is None:
|
|
44
44
|
raise ValueError("Agents, Teams, or Workflows are required to setup the A2A interface.")
|
|
@@ -687,7 +687,7 @@ def attach_routes(
|
|
|
687
687
|
status_code=400,
|
|
688
688
|
detail="Entity ID required. Provide it via 'agentId' in params.message or 'X-Agent-ID' header.",
|
|
689
689
|
)
|
|
690
|
-
entity: Optional[Union[Agent, Team, Workflow]] = None
|
|
690
|
+
entity: Optional[Union[Agent, RemoteAgent, Team, RemoteTeam, Workflow, RemoteWorkflow]] = None
|
|
691
691
|
if agents:
|
|
692
692
|
entity = get_agent_by_id(agent_id, agents)
|
|
693
693
|
if not entity and teams:
|
|
@@ -720,10 +720,10 @@ def attach_routes(
|
|
|
720
720
|
else:
|
|
721
721
|
response = entity.arun(
|
|
722
722
|
input=run_input.input_content,
|
|
723
|
-
images=run_input.images,
|
|
724
|
-
videos=run_input.videos,
|
|
725
|
-
audio=run_input.audios,
|
|
726
|
-
files=run_input.files,
|
|
723
|
+
images=run_input.images, # type: ignore
|
|
724
|
+
videos=run_input.videos, # type: ignore
|
|
725
|
+
audio=run_input.audios, # type: ignore
|
|
726
|
+
files=run_input.files, # type: ignore
|
|
727
727
|
session_id=context_id,
|
|
728
728
|
user_id=user_id,
|
|
729
729
|
**kwargs,
|
|
@@ -801,7 +801,7 @@ def attach_routes(
|
|
|
801
801
|
status_code=400,
|
|
802
802
|
detail="Entity ID required. Provide 'agentId' in params.message or 'X-Agent-ID' header.",
|
|
803
803
|
)
|
|
804
|
-
entity: Optional[Union[Agent, Team, Workflow]] = None
|
|
804
|
+
entity: Optional[Union[Agent, RemoteAgent, Team, RemoteTeam, Workflow, RemoteWorkflow]] = None
|
|
805
805
|
if agents:
|
|
806
806
|
entity = get_agent_by_id(agent_id, agents)
|
|
807
807
|
if not entity and teams:
|
|
@@ -834,7 +834,7 @@ def attach_routes(
|
|
|
834
834
|
**kwargs,
|
|
835
835
|
)
|
|
836
836
|
else:
|
|
837
|
-
event_stream = entity.arun( # type: ignore
|
|
837
|
+
event_stream = entity.arun( # type: ignore
|
|
838
838
|
input=run_input.input_content,
|
|
839
839
|
images=run_input.images,
|
|
840
840
|
videos=run_input.videos,
|
agno/os/interfaces/agui/agui.py
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"""Main class for the AG-UI app, used to expose an Agno Agent or Team in an AG-UI compatible format."""
|
|
2
2
|
|
|
3
|
-
from typing import List, Optional
|
|
3
|
+
from typing import List, Optional, Union
|
|
4
4
|
|
|
5
5
|
from fastapi.routing import APIRouter
|
|
6
6
|
|
|
7
7
|
from agno.agent import Agent
|
|
8
|
+
from agno.agent.remote import RemoteAgent
|
|
8
9
|
from agno.os.interfaces.agui.router import attach_routes
|
|
9
10
|
from agno.os.interfaces.base import BaseInterface
|
|
10
11
|
from agno.team import Team
|
|
12
|
+
from agno.team.remote import RemoteTeam
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
class AGUI(BaseInterface):
|
|
@@ -17,8 +19,8 @@ class AGUI(BaseInterface):
|
|
|
17
19
|
|
|
18
20
|
def __init__(
|
|
19
21
|
self,
|
|
20
|
-
agent: Optional[Agent] = None,
|
|
21
|
-
team: Optional[Team] = None,
|
|
22
|
+
agent: Optional[Union[Agent, RemoteAgent]] = None,
|
|
23
|
+
team: Optional[Union[Team, RemoteTeam]] = None,
|
|
22
24
|
prefix: str = "",
|
|
23
25
|
tags: Optional[List[str]] = None,
|
|
24
26
|
):
|
|
@@ -2,31 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import uuid
|
|
5
|
-
from typing import AsyncIterator, Optional
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
from typing import AsyncIterator, Optional, Union
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from ag_ui.core import (
|
|
9
|
+
BaseEvent,
|
|
10
|
+
EventType,
|
|
11
|
+
RunAgentInput,
|
|
12
|
+
RunErrorEvent,
|
|
13
|
+
RunStartedEvent,
|
|
14
|
+
)
|
|
15
|
+
from ag_ui.encoder import EventEncoder
|
|
16
|
+
except ImportError as e:
|
|
17
|
+
raise ImportError("`ag_ui` not installed. Please install it with `pip install -U ag-ui`") from e
|
|
18
|
+
|
|
15
19
|
from fastapi import APIRouter
|
|
16
20
|
from fastapi.responses import StreamingResponse
|
|
17
21
|
|
|
18
|
-
from agno.agent
|
|
22
|
+
from agno.agent import Agent, RemoteAgent
|
|
19
23
|
from agno.os.interfaces.agui.utils import (
|
|
20
24
|
async_stream_agno_response_as_agui_events,
|
|
21
25
|
convert_agui_messages_to_agno_messages,
|
|
22
26
|
validate_agui_state,
|
|
23
27
|
)
|
|
28
|
+
from agno.team.remote import RemoteTeam
|
|
24
29
|
from agno.team.team import Team
|
|
25
30
|
|
|
26
31
|
logger = logging.getLogger(__name__)
|
|
27
32
|
|
|
28
33
|
|
|
29
|
-
async def run_agent(agent: Agent, run_input: RunAgentInput) -> AsyncIterator[BaseEvent]:
|
|
34
|
+
async def run_agent(agent: Union[Agent, RemoteAgent], run_input: RunAgentInput) -> AsyncIterator[BaseEvent]:
|
|
30
35
|
"""Run the contextual Agent, mapping AG-UI input messages to Agno format, and streaming the response in AG-UI format."""
|
|
31
36
|
run_id = run_input.run_id or str(uuid.uuid4())
|
|
32
37
|
|
|
@@ -45,7 +50,7 @@ async def run_agent(agent: Agent, run_input: RunAgentInput) -> AsyncIterator[Bas
|
|
|
45
50
|
session_state = validate_agui_state(run_input.state, run_input.thread_id)
|
|
46
51
|
|
|
47
52
|
# Request streaming response from agent
|
|
48
|
-
response_stream = agent.arun(
|
|
53
|
+
response_stream = agent.arun( # type: ignore
|
|
49
54
|
input=messages,
|
|
50
55
|
session_id=run_input.thread_id,
|
|
51
56
|
stream=True,
|
|
@@ -69,7 +74,7 @@ async def run_agent(agent: Agent, run_input: RunAgentInput) -> AsyncIterator[Bas
|
|
|
69
74
|
yield RunErrorEvent(type=EventType.RUN_ERROR, message=str(e))
|
|
70
75
|
|
|
71
76
|
|
|
72
|
-
async def run_team(team: Team, input: RunAgentInput) -> AsyncIterator[BaseEvent]:
|
|
77
|
+
async def run_team(team: Union[Team, RemoteTeam], input: RunAgentInput) -> AsyncIterator[BaseEvent]:
|
|
73
78
|
"""Run the contextual Team, mapping AG-UI input messages to Agno format, and streaming the response in AG-UI format."""
|
|
74
79
|
run_id = input.run_id or str(uuid.uuid4())
|
|
75
80
|
try:
|
|
@@ -86,7 +91,7 @@ async def run_team(team: Team, input: RunAgentInput) -> AsyncIterator[BaseEvent]
|
|
|
86
91
|
session_state = validate_agui_state(input.state, input.thread_id)
|
|
87
92
|
|
|
88
93
|
# Request streaming response from team
|
|
89
|
-
response_stream = team.arun(
|
|
94
|
+
response_stream = team.arun( # type: ignore
|
|
90
95
|
input=messages,
|
|
91
96
|
session_id=input.thread_id,
|
|
92
97
|
stream=True,
|
|
@@ -107,7 +112,9 @@ async def run_team(team: Team, input: RunAgentInput) -> AsyncIterator[BaseEvent]
|
|
|
107
112
|
yield RunErrorEvent(type=EventType.RUN_ERROR, message=str(e))
|
|
108
113
|
|
|
109
114
|
|
|
110
|
-
def attach_routes(
|
|
115
|
+
def attach_routes(
|
|
116
|
+
router: APIRouter, agent: Optional[Union[Agent, RemoteAgent]] = None, team: Optional[Union[Team, RemoteTeam]] = None
|
|
117
|
+
) -> APIRouter:
|
|
111
118
|
if agent is None and team is None:
|
|
112
119
|
raise ValueError("Either agent or team must be provided.")
|
|
113
120
|
|
agno/os/interfaces/base.py
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import List, Optional
|
|
2
|
+
from typing import List, Optional, Union
|
|
3
3
|
|
|
4
4
|
from fastapi import APIRouter
|
|
5
5
|
|
|
6
|
-
from agno.agent import Agent
|
|
7
|
-
from agno.team import Team
|
|
8
|
-
from agno.workflow
|
|
6
|
+
from agno.agent import Agent, RemoteAgent
|
|
7
|
+
from agno.team import RemoteTeam, Team
|
|
8
|
+
from agno.workflow import RemoteWorkflow, Workflow
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class BaseInterface(ABC):
|
|
12
12
|
type: str
|
|
13
13
|
version: str = "1.0"
|
|
14
|
-
agent: Optional[Agent] = None
|
|
15
|
-
team: Optional[Team] = None
|
|
16
|
-
workflow: Optional[Workflow] = None
|
|
14
|
+
agent: Optional[Union[Agent, RemoteAgent]] = None
|
|
15
|
+
team: Optional[Union[Team, RemoteTeam]] = None
|
|
16
|
+
workflow: Optional[Union[Workflow, RemoteWorkflow]] = None
|
|
17
17
|
|
|
18
18
|
prefix: str
|
|
19
19
|
tags: List[str]
|
|
@@ -3,12 +3,12 @@ from typing import Optional, Union
|
|
|
3
3
|
from fastapi import APIRouter, BackgroundTasks, HTTPException, Request
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
6
|
-
from agno.agent
|
|
6
|
+
from agno.agent import Agent, RemoteAgent
|
|
7
7
|
from agno.os.interfaces.slack.security import verify_slack_signature
|
|
8
|
-
from agno.team
|
|
8
|
+
from agno.team import RemoteTeam, Team
|
|
9
9
|
from agno.tools.slack import SlackTools
|
|
10
10
|
from agno.utils.log import log_info
|
|
11
|
-
from agno.workflow
|
|
11
|
+
from agno.workflow import RemoteWorkflow, Workflow
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class SlackEventResponse(BaseModel):
|
|
@@ -25,9 +25,9 @@ class SlackChallengeResponse(BaseModel):
|
|
|
25
25
|
|
|
26
26
|
def attach_routes(
|
|
27
27
|
router: APIRouter,
|
|
28
|
-
agent: Optional[Agent] = None,
|
|
29
|
-
team: Optional[Team] = None,
|
|
30
|
-
workflow: Optional[Workflow] = None,
|
|
28
|
+
agent: Optional[Union[Agent, RemoteAgent]] = None,
|
|
29
|
+
team: Optional[Union[Team, RemoteTeam]] = None,
|
|
30
|
+
workflow: Optional[Union[Workflow, RemoteWorkflow]] = None,
|
|
31
31
|
reply_to_mentions_only: bool = True,
|
|
32
32
|
) -> APIRouter:
|
|
33
33
|
# Determine entity type for documentation
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
from typing import List, Optional
|
|
1
|
+
from typing import List, Optional, Union
|
|
2
2
|
|
|
3
3
|
from fastapi.routing import APIRouter
|
|
4
4
|
|
|
5
|
-
from agno.agent
|
|
5
|
+
from agno.agent import Agent, RemoteAgent
|
|
6
6
|
from agno.os.interfaces.base import BaseInterface
|
|
7
7
|
from agno.os.interfaces.slack.router import attach_routes
|
|
8
|
-
from agno.team
|
|
9
|
-
from agno.workflow
|
|
8
|
+
from agno.team import RemoteTeam, Team
|
|
9
|
+
from agno.workflow import RemoteWorkflow, Workflow
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class Slack(BaseInterface):
|
|
@@ -16,9 +16,9 @@ class Slack(BaseInterface):
|
|
|
16
16
|
|
|
17
17
|
def __init__(
|
|
18
18
|
self,
|
|
19
|
-
agent: Optional[Agent] = None,
|
|
20
|
-
team: Optional[Team] = None,
|
|
21
|
-
workflow: Optional[Workflow] = None,
|
|
19
|
+
agent: Optional[Union[Agent, RemoteAgent]] = None,
|
|
20
|
+
team: Optional[Union[Team, RemoteTeam]] = None,
|
|
21
|
+
workflow: Optional[Union[Workflow, RemoteWorkflow]] = None,
|
|
22
22
|
prefix: str = "/slack",
|
|
23
23
|
tags: Optional[List[str]] = None,
|
|
24
24
|
reply_to_mentions_only: bool = True,
|