agno 2.3.16__py3-none-any.whl → 2.3.17__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 (75) 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/os/app.py +104 -23
  22. agno/os/auth.py +25 -1
  23. agno/os/interfaces/a2a/a2a.py +7 -6
  24. agno/os/interfaces/a2a/router.py +13 -13
  25. agno/os/interfaces/agui/agui.py +5 -3
  26. agno/os/interfaces/agui/router.py +23 -16
  27. agno/os/interfaces/base.py +7 -7
  28. agno/os/interfaces/slack/router.py +6 -6
  29. agno/os/interfaces/slack/slack.py +7 -7
  30. agno/os/interfaces/whatsapp/router.py +29 -6
  31. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  32. agno/os/managers.py +326 -0
  33. agno/os/mcp.py +651 -79
  34. agno/os/router.py +125 -18
  35. agno/os/routers/agents/router.py +65 -22
  36. agno/os/routers/agents/schema.py +16 -4
  37. agno/os/routers/database.py +5 -0
  38. agno/os/routers/evals/evals.py +93 -11
  39. agno/os/routers/evals/utils.py +6 -6
  40. agno/os/routers/knowledge/knowledge.py +104 -16
  41. agno/os/routers/memory/memory.py +124 -7
  42. agno/os/routers/metrics/metrics.py +21 -4
  43. agno/os/routers/session/session.py +141 -12
  44. agno/os/routers/teams/router.py +40 -14
  45. agno/os/routers/teams/schema.py +12 -4
  46. agno/os/routers/traces/traces.py +54 -4
  47. agno/os/routers/workflows/router.py +223 -117
  48. agno/os/routers/workflows/schema.py +65 -1
  49. agno/os/schema.py +38 -12
  50. agno/os/utils.py +87 -166
  51. agno/remote/__init__.py +3 -0
  52. agno/remote/base.py +484 -0
  53. agno/run/workflow.py +1 -0
  54. agno/team/__init__.py +2 -0
  55. agno/team/remote.py +287 -0
  56. agno/team/team.py +25 -54
  57. agno/tracing/exporter.py +10 -6
  58. agno/tracing/setup.py +2 -1
  59. agno/utils/agent.py +58 -1
  60. agno/utils/http.py +68 -20
  61. agno/utils/os.py +0 -0
  62. agno/utils/remote.py +23 -0
  63. agno/vectordb/chroma/chromadb.py +452 -16
  64. agno/vectordb/pgvector/pgvector.py +7 -0
  65. agno/vectordb/redis/redisdb.py +1 -1
  66. agno/workflow/__init__.py +2 -0
  67. agno/workflow/agent.py +2 -2
  68. agno/workflow/remote.py +222 -0
  69. agno/workflow/types.py +0 -73
  70. agno/workflow/workflow.py +119 -68
  71. {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/METADATA +1 -1
  72. {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/RECORD +75 -65
  73. {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/WHEEL +0 -0
  74. {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/licenses/LICENSE +0 -0
  75. {agno-2.3.16.dist-info → agno-2.3.17.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
@@ -400,6 +414,8 @@ class AgentOS:
400
414
  if not self.agents:
401
415
  return
402
416
  for agent in self.agents:
417
+ if isinstance(agent, RemoteAgent):
418
+ continue
403
419
  # Track all MCP tools to later handle their connection
404
420
  if agent.tools:
405
421
  for tool in agent.tools:
@@ -424,6 +440,8 @@ class AgentOS:
424
440
  return
425
441
 
426
442
  for team in self.teams:
443
+ if isinstance(team, RemoteTeam):
444
+ continue
427
445
  # Track all MCP tools recursively
428
446
  collect_mcp_tools_from_team(team, self.mcp_tools)
429
447
 
@@ -449,6 +467,8 @@ class AgentOS:
449
467
 
450
468
  if self.workflows:
451
469
  for workflow in self.workflows:
470
+ if isinstance(workflow, RemoteWorkflow):
471
+ continue
452
472
  # Track MCP tools recursively in workflow members
453
473
  collect_mcp_tools_from_workflow(workflow, self.mcp_tools)
454
474
 
@@ -473,7 +493,7 @@ class AgentOS:
473
493
  return
474
494
 
475
495
  # Fall back to finding the first available database
476
- db: Optional[Union[BaseDb, AsyncBaseDb]] = None
496
+ db: Optional[Union[BaseDb, AsyncBaseDb, RemoteDb]] = None
477
497
 
478
498
  for agent in self.agents or []:
479
499
  if agent.db:
@@ -535,6 +555,9 @@ class AgentOS:
535
555
  # The async database lifespan
536
556
  lifespans.append(partial(db_lifespan, agent_os=self))
537
557
 
558
+ # The httpx client cleanup lifespan (should be last to close after other lifespans)
559
+ lifespans.append(http_client_lifespan)
560
+
538
561
  # Combine lifespans and set them in the app
539
562
  if lifespans:
540
563
  fastapi_app.router.lifespan_context = _combine_app_lifespans(lifespans)
@@ -560,6 +583,9 @@ class AgentOS:
560
583
  # Async database initialization lifespan
561
584
  lifespans.append(partial(db_lifespan, agent_os=self)) # type: ignore
562
585
 
586
+ # The httpx client cleanup lifespan (should be last to close after other lifespans)
587
+ lifespans.append(http_client_lifespan)
588
+
563
589
  final_lifespan = _combine_app_lifespans(lifespans) if lifespans else None
564
590
  fastapi_app = self._make_app(lifespan=final_lifespan)
565
591
 
@@ -587,6 +613,14 @@ class AgentOS:
587
613
 
588
614
  if not self._app_set:
589
615
 
616
+ @fastapi_app.exception_handler(RequestValidationError)
617
+ async def validation_exception_handler(_: Request, exc: RequestValidationError) -> JSONResponse:
618
+ log_error(f"Validation error (422): {exc.errors()}")
619
+ return JSONResponse(
620
+ status_code=422,
621
+ content={"detail": exc.errors()},
622
+ )
623
+
590
624
  @fastapi_app.exception_handler(HTTPException)
591
625
  async def http_exception_handler(_, exc: HTTPException) -> JSONResponse:
592
626
  log_error(f"HTTP exception: {exc.status_code} {exc.detail}")
@@ -595,6 +629,16 @@ class AgentOS:
595
629
  content={"detail": str(exc.detail)},
596
630
  )
597
631
 
632
+ @fastapi_app.exception_handler(HTTPStatusError)
633
+ async def http_status_error_handler(_: Request, exc: HTTPStatusError) -> JSONResponse:
634
+ status_code = exc.response.status_code
635
+ detail = exc.response.text
636
+ log_error(f"Downstream server returned HTTP status error: {status_code} {detail}")
637
+ return JSONResponse(
638
+ status_code=status_code,
639
+ content={"detail": detail},
640
+ )
641
+
598
642
  @fastapi_app.exception_handler(Exception)
599
643
  async def general_exception_handler(_: Request, exc: Exception) -> JSONResponse:
600
644
  import traceback
@@ -734,19 +778,28 @@ class AgentOS:
734
778
 
735
779
  def _get_telemetry_data(self) -> Dict[str, Any]:
736
780
  """Get the telemetry data for the OS"""
781
+ agent_ids = []
782
+ team_ids = []
783
+ workflow_ids = []
784
+ for agent in self.agents or []:
785
+ agent_ids.append(agent.id)
786
+ for team in self.teams or []:
787
+ team_ids.append(team.id)
788
+ for workflow in self.workflows or []:
789
+ workflow_ids.append(workflow.id)
737
790
  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,
791
+ "agents": agent_ids,
792
+ "teams": team_ids,
793
+ "workflows": workflow_ids,
741
794
  "interfaces": [interface.type for interface in self.interfaces] if self.interfaces else None,
742
795
  }
743
796
 
744
797
  def _auto_discover_databases(self) -> None:
745
798
  """Auto-discover and initialize the databases used by all contextual agents, teams and workflows."""
746
799
 
747
- dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb]]] = {}
800
+ dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb, RemoteDb]]] = {}
748
801
  knowledge_dbs: Dict[
749
- str, List[Union[BaseDb, AsyncBaseDb]]
802
+ str, List[Union[BaseDb, AsyncBaseDb, RemoteDb]]
750
803
  ] = {} # Track databases specifically used for knowledge
751
804
 
752
805
  for agent in self.agents or []:
@@ -833,6 +886,32 @@ class AgentOS:
833
886
  except Exception as e:
834
887
  log_warning(f"Failed to initialize async {db.__class__.__name__} (id: {db.id}): {e}")
835
888
 
889
+ async def _close_databases(self) -> None:
890
+ """Close all database connections and release connection pools."""
891
+ from itertools import chain
892
+
893
+ if not hasattr(self, "dbs") or not hasattr(self, "knowledge_dbs"):
894
+ return
895
+
896
+ unique_dbs = list(
897
+ {
898
+ id(db): db
899
+ for db in chain(
900
+ chain.from_iterable(self.dbs.values()), chain.from_iterable(self.knowledge_dbs.values())
901
+ )
902
+ }.values()
903
+ )
904
+
905
+ for db in unique_dbs:
906
+ try:
907
+ if hasattr(db, "close") and callable(db.close):
908
+ if isinstance(db, AsyncBaseDb):
909
+ await db.close()
910
+ else:
911
+ db.close()
912
+ except Exception as e:
913
+ log_warning(f"Failed to close {db.__class__.__name__} (id: {db.id}): {e}")
914
+
836
915
  def _get_db_table_names(self, db: BaseDb) -> Dict[str, str]:
837
916
  """Get the table names for a database"""
838
917
  table_names = {
@@ -846,7 +925,9 @@ class AgentOS:
846
925
  return {k: v for k, v in table_names.items() if v is not None}
847
926
 
848
927
  def _register_db_with_validation(
849
- self, registered_dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb]]], db: Union[BaseDb, AsyncBaseDb]
928
+ self,
929
+ registered_dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb, RemoteDb]]],
930
+ db: Union[BaseDb, AsyncBaseDb, RemoteDb],
850
931
  ) -> None:
851
932
  """Register a database in the contextual OS after validating it is not conflicting with registered databases"""
852
933
  if db.id in registered_dbs:
@@ -857,9 +938,9 @@ class AgentOS:
857
938
  def _auto_discover_knowledge_instances(self) -> None:
858
939
  """Auto-discover the knowledge instances used by all contextual agents, teams and workflows."""
859
940
  seen_ids = set()
860
- knowledge_instances: List[Knowledge] = []
941
+ knowledge_instances: List[Union[Knowledge, RemoteKnowledge]] = []
861
942
 
862
- def _add_knowledge_if_not_duplicate(knowledge: "Knowledge") -> None:
943
+ def _add_knowledge_if_not_duplicate(knowledge: Union["Knowledge", RemoteKnowledge]) -> None:
863
944
  """Add knowledge instance if it's not already in the list (by object identity or db_id)."""
864
945
  # Use database ID if available, otherwise use object ID as fallback
865
946
  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,