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.
Files changed (76) hide show
  1. agno/agent/__init__.py +2 -0
  2. agno/agent/agent.py +4 -53
  3. agno/agent/remote.py +351 -0
  4. agno/client/__init__.py +3 -0
  5. agno/client/os.py +2669 -0
  6. agno/db/base.py +20 -0
  7. agno/db/mongo/async_mongo.py +11 -0
  8. agno/db/mongo/mongo.py +10 -0
  9. agno/db/mysql/async_mysql.py +9 -0
  10. agno/db/mysql/mysql.py +9 -0
  11. agno/db/postgres/async_postgres.py +9 -0
  12. agno/db/postgres/postgres.py +9 -0
  13. agno/db/postgres/utils.py +3 -2
  14. agno/db/sqlite/async_sqlite.py +9 -0
  15. agno/db/sqlite/sqlite.py +11 -1
  16. agno/exceptions.py +23 -0
  17. agno/knowledge/chunking/semantic.py +123 -46
  18. agno/knowledge/reader/csv_reader.py +1 -1
  19. agno/knowledge/reader/field_labeled_csv_reader.py +1 -1
  20. agno/knowledge/reader/json_reader.py +1 -1
  21. agno/models/google/gemini.py +5 -0
  22. agno/os/app.py +108 -25
  23. agno/os/auth.py +25 -1
  24. agno/os/interfaces/a2a/a2a.py +7 -6
  25. agno/os/interfaces/a2a/router.py +13 -13
  26. agno/os/interfaces/agui/agui.py +5 -3
  27. agno/os/interfaces/agui/router.py +23 -16
  28. agno/os/interfaces/base.py +7 -7
  29. agno/os/interfaces/slack/router.py +6 -6
  30. agno/os/interfaces/slack/slack.py +7 -7
  31. agno/os/interfaces/whatsapp/router.py +29 -6
  32. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  33. agno/os/managers.py +326 -0
  34. agno/os/mcp.py +651 -79
  35. agno/os/router.py +125 -18
  36. agno/os/routers/agents/router.py +65 -22
  37. agno/os/routers/agents/schema.py +16 -4
  38. agno/os/routers/database.py +5 -0
  39. agno/os/routers/evals/evals.py +93 -11
  40. agno/os/routers/evals/utils.py +6 -6
  41. agno/os/routers/knowledge/knowledge.py +104 -16
  42. agno/os/routers/memory/memory.py +124 -7
  43. agno/os/routers/metrics/metrics.py +21 -4
  44. agno/os/routers/session/session.py +141 -12
  45. agno/os/routers/teams/router.py +40 -14
  46. agno/os/routers/teams/schema.py +12 -4
  47. agno/os/routers/traces/traces.py +54 -4
  48. agno/os/routers/workflows/router.py +223 -117
  49. agno/os/routers/workflows/schema.py +65 -1
  50. agno/os/schema.py +38 -12
  51. agno/os/utils.py +87 -166
  52. agno/remote/__init__.py +3 -0
  53. agno/remote/base.py +484 -0
  54. agno/run/workflow.py +1 -0
  55. agno/team/__init__.py +2 -0
  56. agno/team/remote.py +287 -0
  57. agno/team/team.py +25 -54
  58. agno/tracing/exporter.py +10 -6
  59. agno/tracing/setup.py +2 -1
  60. agno/utils/agent.py +58 -1
  61. agno/utils/http.py +68 -20
  62. agno/utils/os.py +0 -0
  63. agno/utils/remote.py +23 -0
  64. agno/vectordb/chroma/chromadb.py +452 -16
  65. agno/vectordb/pgvector/pgvector.py +7 -0
  66. agno/vectordb/redis/redisdb.py +1 -1
  67. agno/workflow/__init__.py +2 -0
  68. agno/workflow/agent.py +2 -2
  69. agno/workflow/remote.py +222 -0
  70. agno/workflow/types.py +0 -73
  71. agno/workflow/workflow.py +119 -68
  72. {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/METADATA +1 -1
  73. {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/RECORD +76 -66
  74. {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/WHEEL +0 -0
  75. {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/licenses/LICENSE +0 -0
  76. {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.agent import 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 get_websocket_router, get_workflow_router
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.team.team import Team
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.workflow import 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
- get_memory_router(dbs=self.dbs),
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": [agent.id for agent in self.agents] if self.agents else None,
739
- "teams": [team.id for team in self.teams] if self.teams else None,
740
- "workflows": [workflow.id for workflow in self.workflows] if self.workflows else None,
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, registered_dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb]]], db: Union[BaseDb, AsyncBaseDb]
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
 
@@ -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
  ):
@@ -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[assignment]
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,
@@ -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
- from ag_ui.core import (
8
- BaseEvent,
9
- EventType,
10
- RunAgentInput,
11
- RunErrorEvent,
12
- RunStartedEvent,
13
- )
14
- from ag_ui.encoder import EventEncoder
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.agent import 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(router: APIRouter, agent: Optional[Agent] = None, team: Optional[Team] = None) -> APIRouter:
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
 
@@ -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.workflow import 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.agent import Agent
6
+ from agno.agent import Agent, RemoteAgent
7
7
  from agno.os.interfaces.slack.security import verify_slack_signature
8
- from agno.team.team import 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.workflow import 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.agent import 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.team import Team
9
- from agno.workflow.workflow import 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,