agno 2.3.8__py3-none-any.whl → 2.3.9__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 (62) hide show
  1. agno/agent/agent.py +134 -82
  2. agno/db/mysql/__init__.py +2 -1
  3. agno/db/mysql/async_mysql.py +2888 -0
  4. agno/db/mysql/mysql.py +17 -8
  5. agno/db/mysql/utils.py +139 -6
  6. agno/db/postgres/async_postgres.py +10 -5
  7. agno/db/postgres/postgres.py +7 -2
  8. agno/db/schemas/evals.py +1 -0
  9. agno/db/singlestore/singlestore.py +5 -1
  10. agno/db/sqlite/async_sqlite.py +2 -2
  11. agno/eval/__init__.py +10 -0
  12. agno/eval/agent_as_judge.py +860 -0
  13. agno/eval/base.py +29 -0
  14. agno/eval/utils.py +2 -1
  15. agno/exceptions.py +7 -0
  16. agno/knowledge/embedder/openai.py +8 -8
  17. agno/knowledge/knowledge.py +1142 -176
  18. agno/media.py +22 -6
  19. agno/models/aws/claude.py +8 -7
  20. agno/models/base.py +27 -1
  21. agno/models/deepseek/deepseek.py +67 -0
  22. agno/models/google/gemini.py +65 -11
  23. agno/models/google/utils.py +22 -0
  24. agno/models/message.py +2 -0
  25. agno/models/openai/chat.py +4 -0
  26. agno/os/app.py +64 -74
  27. agno/os/interfaces/a2a/router.py +3 -4
  28. agno/os/interfaces/agui/router.py +2 -0
  29. agno/os/router.py +3 -1607
  30. agno/os/routers/agents/__init__.py +3 -0
  31. agno/os/routers/agents/router.py +581 -0
  32. agno/os/routers/agents/schema.py +261 -0
  33. agno/os/routers/evals/evals.py +26 -6
  34. agno/os/routers/evals/schemas.py +34 -2
  35. agno/os/routers/evals/utils.py +101 -20
  36. agno/os/routers/knowledge/knowledge.py +1 -1
  37. agno/os/routers/teams/__init__.py +3 -0
  38. agno/os/routers/teams/router.py +496 -0
  39. agno/os/routers/teams/schema.py +257 -0
  40. agno/os/routers/workflows/__init__.py +3 -0
  41. agno/os/routers/workflows/router.py +545 -0
  42. agno/os/routers/workflows/schema.py +75 -0
  43. agno/os/schema.py +1 -559
  44. agno/os/utils.py +139 -2
  45. agno/team/team.py +73 -16
  46. agno/tools/file_generation.py +12 -6
  47. agno/tools/firecrawl.py +15 -7
  48. agno/utils/hooks.py +64 -5
  49. agno/utils/http.py +2 -2
  50. agno/utils/media.py +11 -1
  51. agno/utils/print_response/agent.py +8 -0
  52. agno/utils/print_response/team.py +8 -0
  53. agno/vectordb/pgvector/pgvector.py +88 -51
  54. agno/workflow/parallel.py +3 -3
  55. agno/workflow/step.py +14 -2
  56. agno/workflow/types.py +38 -2
  57. agno/workflow/workflow.py +12 -4
  58. {agno-2.3.8.dist-info → agno-2.3.9.dist-info}/METADATA +7 -2
  59. {agno-2.3.8.dist-info → agno-2.3.9.dist-info}/RECORD +62 -49
  60. {agno-2.3.8.dist-info → agno-2.3.9.dist-info}/WHEEL +0 -0
  61. {agno-2.3.8.dist-info → agno-2.3.9.dist-info}/licenses/LICENSE +0 -0
  62. {agno-2.3.8.dist-info → agno-2.3.9.dist-info}/top_level.txt +0 -0
agno/os/app.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from contextlib import asynccontextmanager
2
2
  from functools import partial
3
3
  from os import getenv
4
- from typing import Any, Dict, List, Literal, Optional, Tuple, Union
4
+ 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
@@ -31,7 +31,8 @@ from agno.os.config import (
31
31
  TracesDomainConfig,
32
32
  )
33
33
  from agno.os.interfaces.base import BaseInterface
34
- from agno.os.router import get_base_router, get_websocket_router
34
+ from agno.os.router import get_base_router
35
+ from agno.os.routers.agents import get_agent_router
35
36
  from agno.os.routers.evals import get_eval_router
36
37
  from agno.os.routers.health import get_health_router
37
38
  from agno.os.routers.home import get_home_router
@@ -39,7 +40,9 @@ from agno.os.routers.knowledge import get_knowledge_router
39
40
  from agno.os.routers.memory import get_memory_router
40
41
  from agno.os.routers.metrics import get_metrics_router
41
42
  from agno.os.routers.session import get_session_router
43
+ from agno.os.routers.teams import get_team_router
42
44
  from agno.os.routers.traces import get_traces_router
45
+ from agno.os.routers.workflows import get_websocket_router, get_workflow_router
43
46
  from agno.os.settings import AgnoAPISettings
44
47
  from agno.os.utils import (
45
48
  collect_mcp_tools_from_team,
@@ -69,6 +72,15 @@ async def mcp_lifespan(_, mcp_tools):
69
72
  await tool.close()
70
73
 
71
74
 
75
+ @asynccontextmanager
76
+ async def db_lifespan(app: FastAPI, agent_os: "AgentOS"):
77
+ """Initializes databases in the event loop"""
78
+ if agent_os.auto_provision_dbs:
79
+ agent_os._initialize_sync_databases()
80
+ await agent_os._initialize_async_databases()
81
+ yield
82
+
83
+
72
84
  def _combine_app_lifespans(lifespans: list) -> Any:
73
85
  """Combine multiple FastAPI app lifespan context managers into one."""
74
86
  if len(lifespans) == 1:
@@ -287,6 +299,9 @@ class AgentOS:
287
299
 
288
300
  self._add_router(app, get_health_router(health_endpoint="/health"))
289
301
  self._add_router(app, get_base_router(self, settings=self.settings))
302
+ self._add_router(app, get_agent_router(self, settings=self.settings))
303
+ self._add_router(app, get_team_router(self, settings=self.settings))
304
+ self._add_router(app, get_workflow_router(self, settings=self.settings))
290
305
  self._add_router(app, get_websocket_router(self, settings=self.settings))
291
306
 
292
307
  # Add A2A interface if relevant
@@ -469,41 +484,36 @@ class AgentOS:
469
484
  if self.enable_mcp_server and self._mcp_app:
470
485
  lifespans.append(self._mcp_app.lifespan)
471
486
 
487
+ # The async database lifespan
488
+ lifespans.append(partial(db_lifespan, agent_os=self))
489
+
472
490
  # Combine lifespans and set them in the app
473
491
  if lifespans:
474
492
  fastapi_app.router.lifespan_context = _combine_app_lifespans(lifespans)
475
493
 
476
494
  else:
477
- if self.enable_mcp_server:
478
- from contextlib import asynccontextmanager
479
-
480
- from agno.os.mcp import get_mcp_server
495
+ lifespans = []
481
496
 
482
- self._mcp_app = get_mcp_server(self)
497
+ # User provided lifespan
498
+ if self.lifespan:
499
+ lifespans.append(self._add_agent_os_to_lifespan_function(self.lifespan))
483
500
 
484
- final_lifespan = self._mcp_app.lifespan # type: ignore
485
- if self.lifespan is not None:
486
- # Wrap the user lifespan with agent_os parameter
487
- wrapped_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
501
+ # MCP tools lifespan
502
+ if self.mcp_tools:
503
+ lifespans.append(partial(mcp_lifespan, mcp_tools=self.mcp_tools))
488
504
 
489
- # Combine both lifespans
490
- @asynccontextmanager
491
- async def combined_lifespan(app: FastAPI):
492
- # Run both lifespans
493
- async with wrapped_lifespan(app): # type: ignore
494
- async with self._mcp_app.lifespan(app): # type: ignore
495
- yield
505
+ # MCP server lifespan
506
+ if self.enable_mcp_server:
507
+ from agno.os.mcp import get_mcp_server
496
508
 
497
- final_lifespan = combined_lifespan # type: ignore
509
+ self._mcp_app = get_mcp_server(self)
510
+ lifespans.append(self._mcp_app.lifespan)
498
511
 
499
- fastapi_app = self._make_app(lifespan=final_lifespan)
500
- else:
501
- # Wrap the user lifespan with agent_os parameter
502
- wrapped_user_lifespan = None
503
- if self.lifespan is not None:
504
- wrapped_user_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
512
+ # Async database initialization lifespan
513
+ lifespans.append(partial(db_lifespan, agent_os=self)) # type: ignore
505
514
 
506
- fastapi_app = self._make_app(lifespan=wrapped_user_lifespan)
515
+ final_lifespan = _combine_app_lifespans(lifespans) if lifespans else None
516
+ fastapi_app = self._make_app(lifespan=final_lifespan)
507
517
 
508
518
  self._add_built_in_routes(app=fastapi_app)
509
519
 
@@ -668,36 +678,14 @@ class AgentOS:
668
678
  self.dbs = dbs
669
679
  self.knowledge_dbs = knowledge_dbs
670
680
 
671
- # Initialize/scaffold all discovered databases
681
+ # Initialize all discovered databases
672
682
  if self.auto_provision_dbs:
673
- import asyncio
674
- import concurrent.futures
683
+ self._pending_async_db_init = True
675
684
 
676
- try:
677
- # If we're already in an event loop, run in a separate thread
678
- asyncio.get_running_loop()
679
-
680
- def run_in_new_loop():
681
- new_loop = asyncio.new_event_loop()
682
- asyncio.set_event_loop(new_loop)
683
- try:
684
- return new_loop.run_until_complete(self._initialize_databases())
685
- finally:
686
- new_loop.close()
687
-
688
- with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
689
- future = executor.submit(run_in_new_loop)
690
- future.result() # Wait for completion
691
-
692
- except RuntimeError:
693
- # No event loop running, use asyncio.run
694
- asyncio.run(self._initialize_databases())
695
-
696
- async def _initialize_databases(self) -> None:
697
- """Initialize all discovered databases and create all Agno tables that don't exist yet."""
685
+ def _initialize_sync_databases(self) -> None:
686
+ """Initialize sync databases."""
698
687
  from itertools import chain
699
688
 
700
- # Collect all database instances and remove duplicates by identity
701
689
  unique_dbs = list(
702
690
  {
703
691
  id(db): db
@@ -707,37 +695,39 @@ class AgentOS:
707
695
  }.values()
708
696
  )
709
697
 
710
- # Separate sync and async databases
711
- sync_dbs: List[Tuple[str, BaseDb]] = []
712
- async_dbs: List[Tuple[str, AsyncBaseDb]] = []
713
-
714
698
  for db in unique_dbs:
715
- target = async_dbs if isinstance(db, AsyncBaseDb) else sync_dbs
716
- target.append((db.id, db)) # type: ignore
699
+ if isinstance(db, AsyncBaseDb):
700
+ continue # Skip async dbs
717
701
 
718
- # Initialize sync databases
719
- for db_id, db in sync_dbs:
720
702
  try:
721
- if hasattr(db, "_create_all_tables") and callable(getattr(db, "_create_all_tables")):
703
+ if hasattr(db, "_create_all_tables") and callable(db._create_all_tables):
722
704
  db._create_all_tables()
723
- else:
724
- log_debug(f"No table initialization needed for {db.__class__.__name__}")
725
-
726
705
  except Exception as e:
727
- log_warning(f"Failed to initialize {db.__class__.__name__} (id: {db_id}): {e}")
706
+ log_warning(f"Failed to initialize {db.__class__.__name__} (id: {db.id}): {e}")
728
707
 
729
- # Initialize async databases
730
- for db_id, db in async_dbs:
731
- try:
732
- log_debug(f"Initializing async {db.__class__.__name__} (id: {db_id})")
708
+ async def _initialize_async_databases(self) -> None:
709
+ """Initialize async databases."""
733
710
 
734
- if hasattr(db, "_create_all_tables") and callable(getattr(db, "_create_all_tables")):
735
- await db._create_all_tables()
736
- else:
737
- log_debug(f"No table initialization needed for async {db.__class__.__name__}")
711
+ from itertools import chain
738
712
 
713
+ unique_dbs = list(
714
+ {
715
+ id(db): db
716
+ for db in chain(
717
+ chain.from_iterable(self.dbs.values()), chain.from_iterable(self.knowledge_dbs.values())
718
+ )
719
+ }.values()
720
+ )
721
+
722
+ for db in unique_dbs:
723
+ if not isinstance(db, AsyncBaseDb):
724
+ continue # Skip sync dbs
725
+
726
+ try:
727
+ if hasattr(db, "_create_all_tables") and callable(db._create_all_tables):
728
+ await db._create_all_tables()
739
729
  except Exception as e:
740
- log_warning(f"Failed to initialize async database {db.__class__.__name__} (id: {db_id}): {e}")
730
+ log_warning(f"Failed to initialize async {db.__class__.__name__} (id: {db.id}): {e}")
741
731
 
742
732
  def _get_db_table_names(self, db: BaseDb) -> Dict[str, str]:
743
733
  """Get the table names for a database"""
@@ -19,8 +19,7 @@ from agno.os.interfaces.a2a.utils import (
19
19
  map_run_output_to_a2a_task,
20
20
  stream_a2a_response_with_error_handling,
21
21
  )
22
- from agno.os.router import _get_request_kwargs
23
- from agno.os.utils import get_agent_by_id, get_team_by_id, get_workflow_by_id
22
+ from agno.os.utils import get_agent_by_id, get_request_kwargs, get_team_by_id, get_workflow_by_id
24
23
  from agno.team import Team
25
24
  from agno.workflow import Workflow
26
25
 
@@ -75,7 +74,7 @@ def attach_routes(
75
74
  )
76
75
  async def a2a_send_message(request: Request):
77
76
  request_body = await request.json()
78
- kwargs = await _get_request_kwargs(request, a2a_send_message)
77
+ kwargs = await get_request_kwargs(request, a2a_send_message)
79
78
 
80
79
  # 1. Get the Agent, Team, or Workflow to run
81
80
  agent_id = request_body.get("params", {}).get("message", {}).get("agentId") or request.headers.get("X-Agent-ID")
@@ -181,7 +180,7 @@ def attach_routes(
181
180
  )
182
181
  async def a2a_stream_message(request: Request):
183
182
  request_body = await request.json()
184
- kwargs = await _get_request_kwargs(request, a2a_stream_message)
183
+ kwargs = await get_request_kwargs(request, a2a_stream_message)
185
184
 
186
185
  # 1. Get the Agent, Team, or Workflow to run
187
186
  agent_id = request_body.get("params", {}).get("message", {}).get("agentId")
@@ -52,6 +52,7 @@ async def run_agent(agent: Agent, run_input: RunAgentInput) -> AsyncIterator[Bas
52
52
  stream_events=True,
53
53
  user_id=user_id,
54
54
  session_state=session_state,
55
+ run_id=run_id,
55
56
  )
56
57
 
57
58
  # Stream the response content in AG-UI format
@@ -92,6 +93,7 @@ async def run_team(team: Team, input: RunAgentInput) -> AsyncIterator[BaseEvent]
92
93
  stream_steps=True,
93
94
  user_id=user_id,
94
95
  session_state=session_state,
96
+ run_id=run_id,
95
97
  )
96
98
 
97
99
  # Stream the response content in AG-UI format