agno 2.3.4__py3-none-any.whl → 2.3.6__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 (116) hide show
  1. agno/agent/agent.py +184 -45
  2. agno/culture/manager.py +2 -2
  3. agno/db/base.py +330 -8
  4. agno/db/dynamo/dynamo.py +722 -2
  5. agno/db/dynamo/schemas.py +127 -0
  6. agno/db/firestore/firestore.py +573 -1
  7. agno/db/firestore/schemas.py +40 -0
  8. agno/db/gcs_json/gcs_json_db.py +446 -1
  9. agno/db/in_memory/in_memory_db.py +143 -1
  10. agno/db/json/json_db.py +438 -1
  11. agno/db/mongo/async_mongo.py +522 -0
  12. agno/db/mongo/mongo.py +523 -1
  13. agno/db/mongo/schemas.py +29 -0
  14. agno/db/mysql/mysql.py +536 -3
  15. agno/db/mysql/schemas.py +38 -0
  16. agno/db/postgres/async_postgres.py +553 -15
  17. agno/db/postgres/postgres.py +544 -5
  18. agno/db/postgres/schemas.py +38 -0
  19. agno/db/redis/redis.py +468 -1
  20. agno/db/redis/schemas.py +32 -0
  21. agno/db/singlestore/schemas.py +38 -0
  22. agno/db/singlestore/singlestore.py +523 -1
  23. agno/db/sqlite/async_sqlite.py +549 -10
  24. agno/db/sqlite/schemas.py +38 -0
  25. agno/db/sqlite/sqlite.py +540 -9
  26. agno/db/sqlite/utils.py +6 -8
  27. agno/db/surrealdb/models.py +25 -0
  28. agno/db/surrealdb/surrealdb.py +548 -1
  29. agno/eval/accuracy.py +18 -8
  30. agno/eval/performance.py +10 -4
  31. agno/eval/reliability.py +22 -13
  32. agno/exceptions.py +11 -0
  33. agno/hooks/__init__.py +3 -0
  34. agno/hooks/decorator.py +164 -0
  35. agno/integrations/discord/client.py +1 -1
  36. agno/knowledge/chunking/semantic.py +2 -2
  37. agno/models/aimlapi/aimlapi.py +2 -3
  38. agno/models/anthropic/claude.py +18 -13
  39. agno/models/aws/bedrock.py +3 -4
  40. agno/models/aws/claude.py +5 -1
  41. agno/models/azure/ai_foundry.py +2 -2
  42. agno/models/azure/openai_chat.py +8 -0
  43. agno/models/cerebras/cerebras.py +62 -11
  44. agno/models/cerebras/cerebras_openai.py +2 -3
  45. agno/models/cohere/chat.py +1 -5
  46. agno/models/cometapi/cometapi.py +2 -3
  47. agno/models/dashscope/dashscope.py +2 -3
  48. agno/models/deepinfra/deepinfra.py +2 -3
  49. agno/models/deepseek/deepseek.py +2 -3
  50. agno/models/fireworks/fireworks.py +2 -3
  51. agno/models/google/gemini.py +9 -7
  52. agno/models/groq/groq.py +2 -3
  53. agno/models/huggingface/huggingface.py +1 -5
  54. agno/models/ibm/watsonx.py +1 -5
  55. agno/models/internlm/internlm.py +2 -3
  56. agno/models/langdb/langdb.py +6 -4
  57. agno/models/litellm/chat.py +2 -2
  58. agno/models/litellm/litellm_openai.py +2 -3
  59. agno/models/meta/llama.py +1 -5
  60. agno/models/meta/llama_openai.py +4 -5
  61. agno/models/mistral/mistral.py +1 -5
  62. agno/models/nebius/nebius.py +2 -3
  63. agno/models/nvidia/nvidia.py +4 -5
  64. agno/models/openai/chat.py +14 -3
  65. agno/models/openai/responses.py +14 -3
  66. agno/models/openrouter/openrouter.py +4 -5
  67. agno/models/perplexity/perplexity.py +2 -3
  68. agno/models/portkey/portkey.py +7 -6
  69. agno/models/requesty/requesty.py +4 -5
  70. agno/models/response.py +2 -1
  71. agno/models/sambanova/sambanova.py +4 -5
  72. agno/models/siliconflow/siliconflow.py +3 -4
  73. agno/models/together/together.py +4 -5
  74. agno/models/vercel/v0.py +4 -5
  75. agno/models/vllm/vllm.py +19 -14
  76. agno/models/xai/xai.py +4 -5
  77. agno/os/app.py +104 -0
  78. agno/os/config.py +13 -0
  79. agno/os/interfaces/whatsapp/router.py +0 -1
  80. agno/os/interfaces/whatsapp/security.py +3 -1
  81. agno/os/mcp.py +1 -0
  82. agno/os/router.py +31 -0
  83. agno/os/routers/traces/__init__.py +3 -0
  84. agno/os/routers/traces/schemas.py +414 -0
  85. agno/os/routers/traces/traces.py +499 -0
  86. agno/os/schema.py +12 -2
  87. agno/os/utils.py +57 -0
  88. agno/run/agent.py +1 -0
  89. agno/run/base.py +17 -0
  90. agno/run/team.py +4 -0
  91. agno/table.py +10 -0
  92. agno/team/team.py +221 -69
  93. agno/tools/function.py +10 -8
  94. agno/tools/google_drive.py +4 -3
  95. agno/tools/nano_banana.py +1 -1
  96. agno/tools/spotify.py +922 -0
  97. agno/tracing/__init__.py +12 -0
  98. agno/tracing/exporter.py +157 -0
  99. agno/tracing/schemas.py +276 -0
  100. agno/tracing/setup.py +111 -0
  101. agno/utils/agent.py +6 -6
  102. agno/utils/hooks.py +56 -1
  103. agno/utils/mcp.py +1 -1
  104. agno/vectordb/qdrant/qdrant.py +22 -22
  105. agno/workflow/condition.py +8 -0
  106. agno/workflow/loop.py +8 -0
  107. agno/workflow/parallel.py +8 -0
  108. agno/workflow/router.py +8 -0
  109. agno/workflow/step.py +20 -0
  110. agno/workflow/steps.py +8 -0
  111. agno/workflow/workflow.py +88 -19
  112. {agno-2.3.4.dist-info → agno-2.3.6.dist-info}/METADATA +38 -33
  113. {agno-2.3.4.dist-info → agno-2.3.6.dist-info}/RECORD +116 -105
  114. {agno-2.3.4.dist-info → agno-2.3.6.dist-info}/WHEEL +0 -0
  115. {agno-2.3.4.dist-info → agno-2.3.6.dist-info}/licenses/LICENSE +0 -0
  116. {agno-2.3.4.dist-info → agno-2.3.6.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,10 @@
1
- from dataclasses import dataclass, field
1
+ from dataclasses import dataclass
2
2
  from os import getenv
3
3
  from typing import Any, Dict, List, Optional, Type, Union
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
7
- from agno.exceptions import ModelProviderError
7
+ from agno.exceptions import ModelAuthenticationError
8
8
  from agno.models.openai.like import OpenAILike
9
9
  from agno.run.agent import RunOutput
10
10
 
@@ -30,7 +30,7 @@ class OpenRouter(OpenAILike):
30
30
  name: str = "OpenRouter"
31
31
  provider: str = "OpenRouter"
32
32
 
33
- api_key: Optional[str] = field(default_factory=lambda: getenv("OPENROUTER_API_KEY"))
33
+ api_key: Optional[str] = None
34
34
  base_url: str = "https://openrouter.ai/api/v1"
35
35
  max_tokens: int = 1024
36
36
  models: Optional[List[str]] = None # Dynamic model routing https://openrouter.ai/docs/features/model-routing
@@ -46,10 +46,9 @@ class OpenRouter(OpenAILike):
46
46
  if not self.api_key:
47
47
  self.api_key = getenv("OPENROUTER_API_KEY")
48
48
  if not self.api_key:
49
- raise ModelProviderError(
49
+ raise ModelAuthenticationError(
50
50
  message="OPENROUTER_API_KEY not set. Please set the OPENROUTER_API_KEY environment variable.",
51
51
  model_name=self.name,
52
- model_id=self.id,
53
52
  )
54
53
 
55
54
  return super()._get_client_params()
@@ -4,7 +4,7 @@ from typing import Any, Dict, Optional, Type, Union
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
7
- from agno.exceptions import ModelProviderError
7
+ from agno.exceptions import ModelAuthenticationError, ModelProviderError
8
8
  from agno.models.message import Citations, UrlCitation
9
9
  from agno.models.metrics import Metrics
10
10
  from agno.models.response import ModelResponse
@@ -60,10 +60,9 @@ class Perplexity(OpenAILike):
60
60
  if not self.api_key:
61
61
  self.api_key = getenv("PERPLEXITY_API_KEY")
62
62
  if not self.api_key:
63
- raise ModelProviderError(
63
+ raise ModelAuthenticationError(
64
64
  message="PERPLEXITY_API_KEY not set. Please set the PERPLEXITY_API_KEY environment variable.",
65
65
  model_name=self.name,
66
- model_id=self.id,
67
66
  )
68
67
  return super()._get_client_params()
69
68
 
@@ -1,8 +1,8 @@
1
- from dataclasses import dataclass, field
1
+ from dataclasses import dataclass
2
2
  from os import getenv
3
3
  from typing import Any, Dict, Optional, cast
4
4
 
5
- from agno.exceptions import ModelProviderError
5
+ from agno.exceptions import ModelAuthenticationError
6
6
  from agno.models.openai.like import OpenAILike
7
7
 
8
8
  try:
@@ -30,20 +30,21 @@ class Portkey(OpenAILike):
30
30
  name: str = "Portkey"
31
31
  provider: str = "Portkey"
32
32
 
33
- portkey_api_key: Optional[str] = field(default_factory=lambda: getenv("PORTKEY_API_KEY"))
34
- virtual_key: Optional[str] = field(default_factory=lambda: getenv("PORTKEY_VIRTUAL_KEY"))
33
+ portkey_api_key: Optional[str] = None
34
+ virtual_key: Optional[str] = None
35
35
  config: Optional[Dict[str, Any]] = None
36
36
  base_url: str = PORTKEY_GATEWAY_URL
37
37
 
38
38
  def _get_client_params(self) -> Dict[str, Any]:
39
39
  # Check for required keys
40
40
  if not self.portkey_api_key:
41
- raise ModelProviderError(
41
+ raise ModelAuthenticationError(
42
42
  message="PORTKEY_API_KEY not set. Please set the PORTKEY_API_KEY environment variable.",
43
43
  model_name=self.name,
44
- model_id=self.id,
45
44
  )
46
45
 
46
+ self.virtual_key = self.virtual_key or getenv("PORTKEY_VIRTUAL_KEY")
47
+
47
48
  # Create headers using Portkey's createHeaders function
48
49
  header_params: Dict[str, Any] = {
49
50
  "api_key": self.portkey_api_key,
@@ -1,10 +1,10 @@
1
- from dataclasses import dataclass, field
1
+ from dataclasses import dataclass
2
2
  from os import getenv
3
3
  from typing import Any, Dict, List, Optional, Type, Union
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
7
- from agno.exceptions import ModelProviderError
7
+ from agno.exceptions import ModelAuthenticationError
8
8
  from agno.models.openai.like import OpenAILike
9
9
  from agno.run.agent import RunOutput
10
10
  from agno.run.team import TeamRunOutput
@@ -27,7 +27,7 @@ class Requesty(OpenAILike):
27
27
  name: str = "Requesty"
28
28
  provider: str = "Requesty"
29
29
 
30
- api_key: Optional[str] = field(default_factory=lambda: getenv("REQUESTY_API_KEY"))
30
+ api_key: Optional[str] = None
31
31
  base_url: str = "https://router.requesty.ai/v1"
32
32
  max_tokens: int = 1024
33
33
 
@@ -41,10 +41,9 @@ class Requesty(OpenAILike):
41
41
  if not self.api_key:
42
42
  self.api_key = getenv("REQUESTY_API_KEY")
43
43
  if not self.api_key:
44
- raise ModelProviderError(
44
+ raise ModelAuthenticationError(
45
45
  message="REQUESTY_API_KEY not set. Please set the REQUESTY_API_KEY environment variable.",
46
46
  model_name=self.name,
47
- model_id=self.id,
48
47
  )
49
48
  return super()._get_client_params()
50
49
 
agno/models/response.py CHANGED
@@ -35,7 +35,7 @@ class ToolExecution:
35
35
  # If True, the agent will stop executing after this tool call.
36
36
  stop_after_tool_call: bool = False
37
37
 
38
- created_at: int = int(time())
38
+ created_at: int = field(default_factory=lambda: int(time()))
39
39
 
40
40
  # User control flow requirements
41
41
  requires_confirmation: Optional[bool] = None
@@ -81,6 +81,7 @@ class ToolExecution:
81
81
  else None,
82
82
  external_execution_required=data.get("external_execution_required"),
83
83
  metrics=Metrics(**(data.get("metrics", {}) or {})),
84
+ **{"created_at": data["created_at"]} if "created_at" in data else {},
84
85
  )
85
86
 
86
87
 
@@ -1,8 +1,8 @@
1
- from dataclasses import dataclass, field
1
+ from dataclasses import dataclass
2
2
  from os import getenv
3
3
  from typing import Any, Dict, Optional
4
4
 
5
- from agno.exceptions import ModelProviderError
5
+ from agno.exceptions import ModelAuthenticationError
6
6
  from agno.models.openai.like import OpenAILike
7
7
 
8
8
 
@@ -23,7 +23,7 @@ class Sambanova(OpenAILike):
23
23
  name: str = "Sambanova"
24
24
  provider: str = "Sambanova"
25
25
 
26
- api_key: Optional[str] = field(default_factory=lambda: getenv("SAMBANOVA_API_KEY"))
26
+ api_key: Optional[str] = None
27
27
  base_url: str = "https://api.sambanova.ai/v1"
28
28
 
29
29
  supports_native_structured_outputs: bool = False
@@ -38,9 +38,8 @@ class Sambanova(OpenAILike):
38
38
  if not self.api_key:
39
39
  self.api_key = getenv("SAMBANOVA_API_KEY")
40
40
  if not self.api_key:
41
- raise ModelProviderError(
41
+ raise ModelAuthenticationError(
42
42
  message="SAMBANOVA_API_KEY not set. Please set the SAMBANOVA_API_KEY environment variable.",
43
43
  model_name=self.name,
44
- model_id=self.id,
45
44
  )
46
45
  return super()._get_client_params()
@@ -2,7 +2,7 @@ from dataclasses import dataclass
2
2
  from os import getenv
3
3
  from typing import Any, Dict, Optional
4
4
 
5
- from agno.exceptions import ModelProviderError
5
+ from agno.exceptions import ModelAuthenticationError
6
6
  from agno.models.openai.like import OpenAILike
7
7
 
8
8
 
@@ -22,7 +22,7 @@ class Siliconflow(OpenAILike):
22
22
  id: str = "Qwen/QwQ-32B"
23
23
  name: str = "Siliconflow"
24
24
  provider: str = "Siliconflow"
25
- api_key: Optional[str] = getenv("SILICONFLOW_API_KEY")
25
+ api_key: Optional[str] = None
26
26
  base_url: str = "https://api.siliconflow.com/v1"
27
27
 
28
28
  def _get_client_params(self) -> Dict[str, Any]:
@@ -35,9 +35,8 @@ class Siliconflow(OpenAILike):
35
35
  if not self.api_key:
36
36
  self.api_key = getenv("SILICONFLOW_API_KEY")
37
37
  if not self.api_key:
38
- raise ModelProviderError(
38
+ raise ModelAuthenticationError(
39
39
  message="SILICONFLOW_API_KEY not set. Please set the SILICONFLOW_API_KEY environment variable.",
40
40
  model_name=self.name,
41
- model_id=self.id,
42
41
  )
43
42
  return super()._get_client_params()
@@ -1,8 +1,8 @@
1
- from dataclasses import dataclass, field
1
+ from dataclasses import dataclass
2
2
  from os import getenv
3
3
  from typing import Any, Dict, Optional
4
4
 
5
- from agno.exceptions import ModelProviderError
5
+ from agno.exceptions import ModelAuthenticationError
6
6
  from agno.models.openai.like import OpenAILike
7
7
 
8
8
 
@@ -22,7 +22,7 @@ class Together(OpenAILike):
22
22
  id: str = "mistralai/Mixtral-8x7B-Instruct-v0.1"
23
23
  name: str = "Together"
24
24
  provider: str = "Together"
25
- api_key: Optional[str] = field(default_factory=lambda: getenv("TOGETHER_API_KEY"))
25
+ api_key: Optional[str] = None
26
26
  base_url: str = "https://api.together.xyz/v1"
27
27
 
28
28
  def _get_client_params(self) -> Dict[str, Any]:
@@ -35,9 +35,8 @@ class Together(OpenAILike):
35
35
  if not self.api_key:
36
36
  self.api_key = getenv("TOGETHER_API_KEY")
37
37
  if not self.api_key:
38
- raise ModelProviderError(
38
+ raise ModelAuthenticationError(
39
39
  message="TOGETHER_API_KEY not set. Please set the TOGETHER_API_KEY environment variable.",
40
40
  model_name=self.name,
41
- model_id=self.id,
42
41
  )
43
42
  return super()._get_client_params()
agno/models/vercel/v0.py CHANGED
@@ -1,8 +1,8 @@
1
- from dataclasses import dataclass, field
1
+ from dataclasses import dataclass
2
2
  from os import getenv
3
3
  from typing import Any, Dict, Optional
4
4
 
5
- from agno.exceptions import ModelProviderError
5
+ from agno.exceptions import ModelAuthenticationError
6
6
  from agno.models.openai.like import OpenAILike
7
7
 
8
8
 
@@ -23,7 +23,7 @@ class V0(OpenAILike):
23
23
  name: str = "v0"
24
24
  provider: str = "Vercel"
25
25
 
26
- api_key: Optional[str] = field(default_factory=lambda: getenv("V0_API_KEY"))
26
+ api_key: Optional[str] = None
27
27
  base_url: str = "https://api.v0.dev/v1/"
28
28
 
29
29
  def _get_client_params(self) -> Dict[str, Any]:
@@ -36,9 +36,8 @@ class V0(OpenAILike):
36
36
  if not self.api_key:
37
37
  self.api_key = getenv("V0_API_KEY")
38
38
  if not self.api_key:
39
- raise ModelProviderError(
39
+ raise ModelAuthenticationError(
40
40
  message="V0_API_KEY not set. Please set the V0_API_KEY environment variable.",
41
41
  model_name=self.name,
42
- model_id=self.id,
43
42
  )
44
43
  return super()._get_client_params()
agno/models/vllm/vllm.py CHANGED
@@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional, Type, Union
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
7
+ from agno.exceptions import ModelAuthenticationError
7
8
  from agno.models.openai.like import OpenAILike
8
9
  from agno.utils.log import log_debug
9
10
 
@@ -29,8 +30,8 @@ class VLLM(OpenAILike):
29
30
  name: str = "VLLM"
30
31
  provider: str = "VLLM"
31
32
 
32
- api_key: Optional[str] = getenv("VLLM_API_KEY") or "EMPTY"
33
- base_url: Optional[str] = getenv("VLLM_BASE_URL", "http://localhost:8000/v1/")
33
+ api_key: Optional[str] = None
34
+ base_url: Optional[str] = None
34
35
 
35
36
  temperature: float = 0.7
36
37
  top_p: float = 0.8
@@ -38,19 +39,23 @@ class VLLM(OpenAILike):
38
39
  top_k: Optional[int] = None
39
40
  enable_thinking: Optional[bool] = None
40
41
 
41
- def __post_init__(self):
42
- """Validate required configuration"""
43
- if not self.base_url:
44
- raise ValueError("VLLM_BASE_URL must be set via environment variable or explicit initialization")
45
- if self.id == "not-set":
46
- raise ValueError("Model ID must be set via environment variable or explicit initialization")
42
+ def _get_client_params(self) -> Dict[str, Any]:
43
+ """
44
+ Returns client parameters for API requests, checking for VLLM_API_KEY.
47
45
 
48
- body: Dict[str, Any] = {}
49
- if self.top_k is not None:
50
- body["top_k"] = self.top_k
51
- if self.enable_thinking is not None:
52
- body["chat_template_kwargs"] = {"enable_thinking": self.enable_thinking}
53
- self.extra_body = body or None
46
+ Returns:
47
+ Dict[str, Any]: A dictionary of client parameters for API requests.
48
+ """
49
+ if not self.api_key:
50
+ self.api_key = getenv("VLLM_API_KEY")
51
+ if not self.api_key:
52
+ raise ModelAuthenticationError(
53
+ message="VLLM_API_KEY not set. Please set the VLLM_API_KEY environment variable.",
54
+ model_name=self.name,
55
+ )
56
+ if not self.base_url:
57
+ self.base_url = getenv("VLLM_BASE_URL", "http://localhost:8000/v1/")
58
+ return super()._get_client_params()
54
59
 
55
60
  def get_request_params(
56
61
  self,
agno/models/xai/xai.py CHANGED
@@ -1,10 +1,10 @@
1
- from dataclasses import dataclass, field
1
+ from dataclasses import dataclass
2
2
  from os import getenv
3
3
  from typing import Any, Dict, List, Optional, Type, Union
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
7
- from agno.exceptions import ModelProviderError
7
+ from agno.exceptions import ModelAuthenticationError
8
8
  from agno.models.message import Citations, UrlCitation
9
9
  from agno.models.openai.like import OpenAILike
10
10
  from agno.models.response import ModelResponse
@@ -35,7 +35,7 @@ class xAI(OpenAILike):
35
35
  name: str = "xAI"
36
36
  provider: str = "xAI"
37
37
 
38
- api_key: Optional[str] = field(default_factory=lambda: getenv("XAI_API_KEY"))
38
+ api_key: Optional[str] = None
39
39
  base_url: str = "https://api.x.ai/v1"
40
40
 
41
41
  search_parameters: Optional[Dict[str, Any]] = None
@@ -50,10 +50,9 @@ class xAI(OpenAILike):
50
50
  if not self.api_key:
51
51
  self.api_key = getenv("XAI_API_KEY")
52
52
  if not self.api_key:
53
- raise ModelProviderError(
53
+ raise ModelAuthenticationError(
54
54
  message="XAI_API_KEY not set. Please set the XAI_API_KEY environment variable.",
55
55
  model_name=self.name,
56
- model_id=self.id,
57
56
  )
58
57
  return super()._get_client_params()
59
58
 
agno/os/app.py CHANGED
@@ -27,6 +27,8 @@ from agno.os.config import (
27
27
  MetricsDomainConfig,
28
28
  SessionConfig,
29
29
  SessionDomainConfig,
30
+ TracesConfig,
31
+ TracesDomainConfig,
30
32
  )
31
33
  from agno.os.interfaces.base import BaseInterface
32
34
  from agno.os.router import get_base_router, get_websocket_router
@@ -37,12 +39,14 @@ from agno.os.routers.knowledge import get_knowledge_router
37
39
  from agno.os.routers.memory import get_memory_router
38
40
  from agno.os.routers.metrics import get_metrics_router
39
41
  from agno.os.routers.session import get_session_router
42
+ from agno.os.routers.traces import get_traces_router
40
43
  from agno.os.settings import AgnoAPISettings
41
44
  from agno.os.utils import (
42
45
  collect_mcp_tools_from_team,
43
46
  collect_mcp_tools_from_workflow,
44
47
  find_conflicting_routes,
45
48
  load_yaml_config,
49
+ setup_tracing_for_os,
46
50
  update_cors_middleware,
47
51
  )
48
52
  from agno.team.team import Team
@@ -109,7 +113,10 @@ class AgentOS:
109
113
  base_app: Optional[FastAPI] = None,
110
114
  on_route_conflict: Literal["preserve_agentos", "preserve_base_app", "error"] = "preserve_agentos",
111
115
  telemetry: bool = True,
116
+ tracing: bool = False,
117
+ tracing_db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
112
118
  auto_provision_dbs: bool = True,
119
+ run_hooks_in_background: bool = False,
113
120
  ):
114
121
  """Initialize AgentOS.
115
122
 
@@ -131,6 +138,10 @@ class AgentOS:
131
138
  base_app: Optional base FastAPI app to use for the AgentOS. All routes and middleware will be added to this app.
132
139
  on_route_conflict: What to do when a route conflict is detected in case a custom base_app is provided.
133
140
  telemetry: Whether to enable telemetry
141
+ tracing: If True, enables OpenTelemetry tracing for all agents and teams in the OS
142
+ tracing_db: Dedicated database for storing and reading traces. Recommended for multi-db setups.
143
+ If not provided and tracing=True, the first available db from agents/teams/workflows is used.
144
+ run_hooks_in_background: If True, run agent/team pre/post hooks as FastAPI background tasks (non-blocking)
134
145
 
135
146
  """
136
147
  if not agents and not workflows and not teams and not knowledge:
@@ -169,10 +180,15 @@ class AgentOS:
169
180
  self.description = description
170
181
 
171
182
  self.telemetry = telemetry
183
+ self.tracing = tracing
184
+ self.tracing_db = tracing_db
172
185
 
173
186
  self.enable_mcp_server = enable_mcp_server
174
187
  self.lifespan = lifespan
175
188
 
189
+ # If True, run agent/team hooks as FastAPI background tasks
190
+ self.run_hooks_in_background = run_hooks_in_background
191
+
176
192
  # List of all MCP tools used inside the AgentOS
177
193
  self.mcp_tools: List[Any] = []
178
194
  self._mcp_app: Optional[Any] = None
@@ -181,6 +197,9 @@ class AgentOS:
181
197
  self._initialize_teams()
182
198
  self._initialize_workflows()
183
199
 
200
+ if self.tracing:
201
+ self._setup_tracing()
202
+
184
203
  if self.telemetry:
185
204
  from agno.api.os import OSLaunch, log_os_telemetry
186
205
 
@@ -235,6 +254,7 @@ class AgentOS:
235
254
  get_session_router(dbs=self.dbs),
236
255
  get_metrics_router(dbs=self.dbs),
237
256
  get_knowledge_router(knowledge_instances=self.knowledge_instances),
257
+ get_traces_router(dbs=self.dbs),
238
258
  get_memory_router(dbs=self.dbs),
239
259
  get_eval_router(dbs=self.dbs, agents=self.agents, teams=self.teams),
240
260
  ]
@@ -332,6 +352,9 @@ class AgentOS:
332
352
  # Required for the built-in routes to work
333
353
  agent.store_events = True
334
354
 
355
+ # Propagate run_hooks_in_background setting from AgentOS to agents
356
+ agent._run_hooks_in_background = self.run_hooks_in_background
357
+
335
358
  def _initialize_teams(self) -> None:
336
359
  """Initialize and configure all teams for AgentOS usage."""
337
360
  if not self.teams:
@@ -353,6 +376,9 @@ class AgentOS:
353
376
  # Required for the built-in routes to work
354
377
  team.store_events = True
355
378
 
379
+ # Propagate run_hooks_in_background setting to team and all nested members
380
+ team.propagate_run_hooks_in_background(self.run_hooks_in_background)
381
+
356
382
  def _initialize_workflows(self) -> None:
357
383
  """Initialize and configure all workflows for AgentOS usage."""
358
384
  if not self.workflows:
@@ -369,6 +395,49 @@ class AgentOS:
369
395
  # Required for the built-in routes to work
370
396
  workflow.store_events = True
371
397
 
398
+ # Propagate run_hooks_in_background setting to workflow and all its step agents/teams
399
+ workflow.propagate_run_hooks_in_background(self.run_hooks_in_background)
400
+
401
+ def _setup_tracing(self) -> None:
402
+ """Set up OpenTelemetry tracing for this AgentOS.
403
+
404
+ Uses tracing_db if provided, otherwise falls back to the first available
405
+ database from agents/teams/workflows.
406
+ """
407
+ # Use tracing_db if explicitly provided
408
+ if self.tracing_db is not None:
409
+ setup_tracing_for_os(db=self.tracing_db)
410
+ return
411
+
412
+ # Fall back to finding the first available database
413
+ db: Optional[Union[BaseDb, AsyncBaseDb]] = None
414
+
415
+ for agent in self.agents or []:
416
+ if agent.db:
417
+ db = agent.db
418
+ break
419
+
420
+ if db is None:
421
+ for team in self.teams or []:
422
+ if team.db:
423
+ db = team.db
424
+ break
425
+
426
+ if db is None:
427
+ for workflow in self.workflows or []:
428
+ if workflow.db:
429
+ db = workflow.db
430
+ break
431
+
432
+ if db is None:
433
+ log_warning(
434
+ "tracing=True but no database found. "
435
+ "Provide 'tracing_db' parameter or 'db' parameter to at least one agent/team/workflow."
436
+ )
437
+ return
438
+
439
+ setup_tracing_for_os(db=db)
440
+
372
441
  def get_app(self) -> FastAPI:
373
442
  if self.base_app:
374
443
  fastapi_app = self.base_app
@@ -447,6 +516,7 @@ class AgentOS:
447
516
  get_eval_router(dbs=self.dbs, agents=self.agents, teams=self.teams),
448
517
  get_metrics_router(dbs=self.dbs),
449
518
  get_knowledge_router(knowledge_instances=self.knowledge_instances),
519
+ get_traces_router(dbs=self.dbs),
450
520
  ]
451
521
 
452
522
  for router in routers:
@@ -591,6 +661,10 @@ class AgentOS:
591
661
  elif interface.team and interface.team.db:
592
662
  self._register_db_with_validation(dbs, interface.team.db)
593
663
 
664
+ # Register tracing_db if provided (for traces reading)
665
+ if self.tracing_db is not None:
666
+ self._register_db_with_validation(dbs, self.tracing_db)
667
+
594
668
  self.dbs = dbs
595
669
  self.knowledge_dbs = knowledge_dbs
596
670
 
@@ -821,6 +895,36 @@ class AgentOS:
821
895
 
822
896
  return evals_config
823
897
 
898
+ def _get_traces_config(self) -> TracesConfig:
899
+ traces_config = self.config.traces if self.config and self.config.traces else TracesConfig()
900
+
901
+ if traces_config.dbs is None:
902
+ traces_config.dbs = []
903
+
904
+ dbs_with_specific_config = [db.db_id for db in traces_config.dbs]
905
+
906
+ # If tracing_db is explicitly set, only use that database for traces
907
+ if self.tracing_db is not None:
908
+ if self.tracing_db.id not in dbs_with_specific_config:
909
+ traces_config.dbs.append(
910
+ DatabaseConfig(
911
+ db_id=self.tracing_db.id,
912
+ domain_config=TracesDomainConfig(display_name=self.tracing_db.id),
913
+ )
914
+ )
915
+ else:
916
+ # Fall back to all discovered databases
917
+ for db_id in self.dbs.keys():
918
+ if db_id not in dbs_with_specific_config:
919
+ traces_config.dbs.append(
920
+ DatabaseConfig(
921
+ db_id=db_id,
922
+ domain_config=TracesDomainConfig(display_name=db_id),
923
+ )
924
+ )
925
+
926
+ return traces_config
927
+
824
928
  def serve(
825
929
  self,
826
930
  app: Union[str, FastAPI],
agno/os/config.py CHANGED
@@ -36,6 +36,12 @@ class MemoryDomainConfig(BaseModel):
36
36
  display_name: Optional[str] = None
37
37
 
38
38
 
39
+ class TracesDomainConfig(BaseModel):
40
+ """Configuration for the Traces domain of the AgentOS"""
41
+
42
+ display_name: Optional[str] = None
43
+
44
+
39
45
  DomainConfigType = TypeVar("DomainConfigType")
40
46
 
41
47
 
@@ -77,6 +83,12 @@ class MetricsConfig(MetricsDomainConfig):
77
83
  dbs: Optional[List[DatabaseConfig[MetricsDomainConfig]]] = None
78
84
 
79
85
 
86
+ class TracesConfig(TracesDomainConfig):
87
+ """Configuration for the Traces domain of the AgentOS"""
88
+
89
+ dbs: Optional[List[DatabaseConfig[TracesDomainConfig]]] = None
90
+
91
+
80
92
  class ChatConfig(BaseModel):
81
93
  """Configuration for the Chat page of the AgentOS"""
82
94
 
@@ -102,3 +114,4 @@ class AgentOSConfig(BaseModel):
102
114
  memory: Optional[MemoryConfig] = None
103
115
  session: Optional[SessionConfig] = None
104
116
  metrics: Optional[MetricsConfig] = None
117
+ traces: Optional[TracesConfig] = None
@@ -171,7 +171,6 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
171
171
  f"Could not process image content for user {phone_number}. Type: {type(image_content)}"
172
172
  )
173
173
  await _send_whatsapp_message(phone_number, response.content) # type: ignore
174
- await _send_whatsapp_message(phone_number, response.content) # type: ignore
175
174
  else:
176
175
  await _send_whatsapp_message(phone_number, response.content) # type: ignore
177
176
 
@@ -3,6 +3,8 @@ import hmac
3
3
  import os
4
4
  from typing import Optional
5
5
 
6
+ from agno.utils.log import log_warning
7
+
6
8
 
7
9
  def is_development_mode() -> bool:
8
10
  """Check if the application is running in development mode."""
@@ -36,7 +38,7 @@ def validate_webhook_signature(payload: bytes, signature_header: Optional[str])
36
38
  """
37
39
  # In development mode, we can bypass signature validation
38
40
  if is_development_mode():
39
- print("WARNING: Bypassing signature validation in development mode")
41
+ log_warning("Bypassing signature validation in development mode")
40
42
  return True
41
43
 
42
44
  if not signature_header or not signature_header.startswith("sha256="):
agno/os/mcp.py CHANGED
@@ -64,6 +64,7 @@ def get_mcp_server(
64
64
  knowledge=os._get_knowledge_config(),
65
65
  evals=os._get_evals_config(),
66
66
  metrics=os._get_metrics_config(),
67
+ traces=os._get_traces_config(),
67
68
  agents=[AgentSummaryResponse.from_agent(agent) for agent in os.agents] if os.agents else [],
68
69
  teams=[TeamSummaryResponse.from_team(team) for team in os.teams] if os.teams else [],
69
70
  workflows=[WorkflowSummaryResponse.from_workflow(w) for w in os.workflows] if os.workflows else [],