agno 2.1.2__py3-none-any.whl → 2.3.13__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 (314) hide show
  1. agno/agent/agent.py +5540 -2273
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/compression/__init__.py +3 -0
  5. agno/compression/manager.py +247 -0
  6. agno/culture/__init__.py +3 -0
  7. agno/culture/manager.py +956 -0
  8. agno/db/async_postgres/__init__.py +3 -0
  9. agno/db/base.py +689 -6
  10. agno/db/dynamo/dynamo.py +933 -37
  11. agno/db/dynamo/schemas.py +174 -10
  12. agno/db/dynamo/utils.py +63 -4
  13. agno/db/firestore/firestore.py +831 -9
  14. agno/db/firestore/schemas.py +51 -0
  15. agno/db/firestore/utils.py +102 -4
  16. agno/db/gcs_json/gcs_json_db.py +660 -12
  17. agno/db/gcs_json/utils.py +60 -26
  18. agno/db/in_memory/in_memory_db.py +287 -14
  19. agno/db/in_memory/utils.py +60 -2
  20. agno/db/json/json_db.py +590 -14
  21. agno/db/json/utils.py +60 -26
  22. agno/db/migrations/manager.py +199 -0
  23. agno/db/migrations/v1_to_v2.py +43 -13
  24. agno/db/migrations/versions/__init__.py +0 -0
  25. agno/db/migrations/versions/v2_3_0.py +938 -0
  26. agno/db/mongo/__init__.py +15 -1
  27. agno/db/mongo/async_mongo.py +2760 -0
  28. agno/db/mongo/mongo.py +879 -11
  29. agno/db/mongo/schemas.py +42 -0
  30. agno/db/mongo/utils.py +80 -8
  31. agno/db/mysql/__init__.py +2 -1
  32. agno/db/mysql/async_mysql.py +2912 -0
  33. agno/db/mysql/mysql.py +946 -68
  34. agno/db/mysql/schemas.py +72 -10
  35. agno/db/mysql/utils.py +198 -7
  36. agno/db/postgres/__init__.py +2 -1
  37. agno/db/postgres/async_postgres.py +2579 -0
  38. agno/db/postgres/postgres.py +942 -57
  39. agno/db/postgres/schemas.py +81 -18
  40. agno/db/postgres/utils.py +164 -2
  41. agno/db/redis/redis.py +671 -7
  42. agno/db/redis/schemas.py +50 -0
  43. agno/db/redis/utils.py +65 -7
  44. agno/db/schemas/__init__.py +2 -1
  45. agno/db/schemas/culture.py +120 -0
  46. agno/db/schemas/evals.py +1 -0
  47. agno/db/schemas/memory.py +17 -2
  48. agno/db/singlestore/schemas.py +63 -0
  49. agno/db/singlestore/singlestore.py +949 -83
  50. agno/db/singlestore/utils.py +60 -2
  51. agno/db/sqlite/__init__.py +2 -1
  52. agno/db/sqlite/async_sqlite.py +2911 -0
  53. agno/db/sqlite/schemas.py +62 -0
  54. agno/db/sqlite/sqlite.py +965 -46
  55. agno/db/sqlite/utils.py +169 -8
  56. agno/db/surrealdb/__init__.py +3 -0
  57. agno/db/surrealdb/metrics.py +292 -0
  58. agno/db/surrealdb/models.py +334 -0
  59. agno/db/surrealdb/queries.py +71 -0
  60. agno/db/surrealdb/surrealdb.py +1908 -0
  61. agno/db/surrealdb/utils.py +147 -0
  62. agno/db/utils.py +2 -0
  63. agno/eval/__init__.py +10 -0
  64. agno/eval/accuracy.py +75 -55
  65. agno/eval/agent_as_judge.py +861 -0
  66. agno/eval/base.py +29 -0
  67. agno/eval/performance.py +16 -7
  68. agno/eval/reliability.py +28 -16
  69. agno/eval/utils.py +35 -17
  70. agno/exceptions.py +27 -2
  71. agno/filters.py +354 -0
  72. agno/guardrails/prompt_injection.py +1 -0
  73. agno/hooks/__init__.py +3 -0
  74. agno/hooks/decorator.py +164 -0
  75. agno/integrations/discord/client.py +1 -1
  76. agno/knowledge/chunking/agentic.py +13 -10
  77. agno/knowledge/chunking/fixed.py +4 -1
  78. agno/knowledge/chunking/semantic.py +9 -4
  79. agno/knowledge/chunking/strategy.py +59 -15
  80. agno/knowledge/embedder/fastembed.py +1 -1
  81. agno/knowledge/embedder/nebius.py +1 -1
  82. agno/knowledge/embedder/ollama.py +8 -0
  83. agno/knowledge/embedder/openai.py +8 -8
  84. agno/knowledge/embedder/sentence_transformer.py +6 -2
  85. agno/knowledge/embedder/vllm.py +262 -0
  86. agno/knowledge/knowledge.py +1618 -318
  87. agno/knowledge/reader/base.py +6 -2
  88. agno/knowledge/reader/csv_reader.py +8 -10
  89. agno/knowledge/reader/docx_reader.py +5 -6
  90. agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
  91. agno/knowledge/reader/json_reader.py +5 -4
  92. agno/knowledge/reader/markdown_reader.py +8 -8
  93. agno/knowledge/reader/pdf_reader.py +17 -19
  94. agno/knowledge/reader/pptx_reader.py +101 -0
  95. agno/knowledge/reader/reader_factory.py +32 -3
  96. agno/knowledge/reader/s3_reader.py +3 -3
  97. agno/knowledge/reader/tavily_reader.py +193 -0
  98. agno/knowledge/reader/text_reader.py +22 -10
  99. agno/knowledge/reader/web_search_reader.py +1 -48
  100. agno/knowledge/reader/website_reader.py +10 -10
  101. agno/knowledge/reader/wikipedia_reader.py +33 -1
  102. agno/knowledge/types.py +1 -0
  103. agno/knowledge/utils.py +72 -7
  104. agno/media.py +22 -6
  105. agno/memory/__init__.py +14 -1
  106. agno/memory/manager.py +544 -83
  107. agno/memory/strategies/__init__.py +15 -0
  108. agno/memory/strategies/base.py +66 -0
  109. agno/memory/strategies/summarize.py +196 -0
  110. agno/memory/strategies/types.py +37 -0
  111. agno/models/aimlapi/aimlapi.py +17 -0
  112. agno/models/anthropic/claude.py +515 -40
  113. agno/models/aws/bedrock.py +102 -21
  114. agno/models/aws/claude.py +131 -274
  115. agno/models/azure/ai_foundry.py +41 -19
  116. agno/models/azure/openai_chat.py +39 -8
  117. agno/models/base.py +1249 -525
  118. agno/models/cerebras/cerebras.py +91 -21
  119. agno/models/cerebras/cerebras_openai.py +21 -2
  120. agno/models/cohere/chat.py +40 -6
  121. agno/models/cometapi/cometapi.py +18 -1
  122. agno/models/dashscope/dashscope.py +2 -3
  123. agno/models/deepinfra/deepinfra.py +18 -1
  124. agno/models/deepseek/deepseek.py +69 -3
  125. agno/models/fireworks/fireworks.py +18 -1
  126. agno/models/google/gemini.py +877 -80
  127. agno/models/google/utils.py +22 -0
  128. agno/models/groq/groq.py +51 -18
  129. agno/models/huggingface/huggingface.py +17 -6
  130. agno/models/ibm/watsonx.py +16 -6
  131. agno/models/internlm/internlm.py +18 -1
  132. agno/models/langdb/langdb.py +13 -1
  133. agno/models/litellm/chat.py +44 -9
  134. agno/models/litellm/litellm_openai.py +18 -1
  135. agno/models/message.py +28 -5
  136. agno/models/meta/llama.py +47 -14
  137. agno/models/meta/llama_openai.py +22 -17
  138. agno/models/mistral/mistral.py +8 -4
  139. agno/models/nebius/nebius.py +6 -7
  140. agno/models/nvidia/nvidia.py +20 -3
  141. agno/models/ollama/chat.py +24 -8
  142. agno/models/openai/chat.py +104 -29
  143. agno/models/openai/responses.py +101 -81
  144. agno/models/openrouter/openrouter.py +60 -3
  145. agno/models/perplexity/perplexity.py +17 -1
  146. agno/models/portkey/portkey.py +7 -6
  147. agno/models/requesty/requesty.py +24 -4
  148. agno/models/response.py +73 -2
  149. agno/models/sambanova/sambanova.py +20 -3
  150. agno/models/siliconflow/siliconflow.py +19 -2
  151. agno/models/together/together.py +20 -3
  152. agno/models/utils.py +254 -8
  153. agno/models/vercel/v0.py +20 -3
  154. agno/models/vertexai/__init__.py +0 -0
  155. agno/models/vertexai/claude.py +190 -0
  156. agno/models/vllm/vllm.py +19 -14
  157. agno/models/xai/xai.py +19 -2
  158. agno/os/app.py +549 -152
  159. agno/os/auth.py +190 -3
  160. agno/os/config.py +23 -0
  161. agno/os/interfaces/a2a/router.py +8 -11
  162. agno/os/interfaces/a2a/utils.py +1 -1
  163. agno/os/interfaces/agui/router.py +18 -3
  164. agno/os/interfaces/agui/utils.py +152 -39
  165. agno/os/interfaces/slack/router.py +55 -37
  166. agno/os/interfaces/slack/slack.py +9 -1
  167. agno/os/interfaces/whatsapp/router.py +0 -1
  168. agno/os/interfaces/whatsapp/security.py +3 -1
  169. agno/os/mcp.py +110 -52
  170. agno/os/middleware/__init__.py +2 -0
  171. agno/os/middleware/jwt.py +676 -112
  172. agno/os/router.py +40 -1478
  173. agno/os/routers/agents/__init__.py +3 -0
  174. agno/os/routers/agents/router.py +599 -0
  175. agno/os/routers/agents/schema.py +261 -0
  176. agno/os/routers/evals/evals.py +96 -39
  177. agno/os/routers/evals/schemas.py +65 -33
  178. agno/os/routers/evals/utils.py +80 -10
  179. agno/os/routers/health.py +10 -4
  180. agno/os/routers/knowledge/knowledge.py +196 -38
  181. agno/os/routers/knowledge/schemas.py +82 -22
  182. agno/os/routers/memory/memory.py +279 -52
  183. agno/os/routers/memory/schemas.py +46 -17
  184. agno/os/routers/metrics/metrics.py +20 -8
  185. agno/os/routers/metrics/schemas.py +16 -16
  186. agno/os/routers/session/session.py +462 -34
  187. agno/os/routers/teams/__init__.py +3 -0
  188. agno/os/routers/teams/router.py +512 -0
  189. agno/os/routers/teams/schema.py +257 -0
  190. agno/os/routers/traces/__init__.py +3 -0
  191. agno/os/routers/traces/schemas.py +414 -0
  192. agno/os/routers/traces/traces.py +499 -0
  193. agno/os/routers/workflows/__init__.py +3 -0
  194. agno/os/routers/workflows/router.py +624 -0
  195. agno/os/routers/workflows/schema.py +75 -0
  196. agno/os/schema.py +256 -693
  197. agno/os/scopes.py +469 -0
  198. agno/os/utils.py +514 -36
  199. agno/reasoning/anthropic.py +80 -0
  200. agno/reasoning/gemini.py +73 -0
  201. agno/reasoning/openai.py +5 -0
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +155 -32
  205. agno/run/base.py +55 -3
  206. agno/run/requirement.py +181 -0
  207. agno/run/team.py +125 -38
  208. agno/run/workflow.py +72 -18
  209. agno/session/agent.py +102 -89
  210. agno/session/summary.py +56 -15
  211. agno/session/team.py +164 -90
  212. agno/session/workflow.py +405 -40
  213. agno/table.py +10 -0
  214. agno/team/team.py +3974 -1903
  215. agno/tools/dalle.py +2 -4
  216. agno/tools/eleven_labs.py +23 -25
  217. agno/tools/exa.py +21 -16
  218. agno/tools/file.py +153 -23
  219. agno/tools/file_generation.py +16 -10
  220. agno/tools/firecrawl.py +15 -7
  221. agno/tools/function.py +193 -38
  222. agno/tools/gmail.py +238 -14
  223. agno/tools/google_drive.py +271 -0
  224. agno/tools/googlecalendar.py +36 -8
  225. agno/tools/googlesheets.py +20 -5
  226. agno/tools/jira.py +20 -0
  227. agno/tools/mcp/__init__.py +10 -0
  228. agno/tools/mcp/mcp.py +331 -0
  229. agno/tools/mcp/multi_mcp.py +347 -0
  230. agno/tools/mcp/params.py +24 -0
  231. agno/tools/mcp_toolbox.py +3 -3
  232. agno/tools/models/nebius.py +5 -5
  233. agno/tools/models_labs.py +20 -10
  234. agno/tools/nano_banana.py +151 -0
  235. agno/tools/notion.py +204 -0
  236. agno/tools/parallel.py +314 -0
  237. agno/tools/postgres.py +76 -36
  238. agno/tools/redshift.py +406 -0
  239. agno/tools/scrapegraph.py +1 -1
  240. agno/tools/shopify.py +1519 -0
  241. agno/tools/slack.py +18 -3
  242. agno/tools/spotify.py +919 -0
  243. agno/tools/tavily.py +146 -0
  244. agno/tools/toolkit.py +25 -0
  245. agno/tools/workflow.py +8 -1
  246. agno/tools/yfinance.py +12 -11
  247. agno/tracing/__init__.py +12 -0
  248. agno/tracing/exporter.py +157 -0
  249. agno/tracing/schemas.py +276 -0
  250. agno/tracing/setup.py +111 -0
  251. agno/utils/agent.py +938 -0
  252. agno/utils/cryptography.py +22 -0
  253. agno/utils/dttm.py +33 -0
  254. agno/utils/events.py +151 -3
  255. agno/utils/gemini.py +15 -5
  256. agno/utils/hooks.py +118 -4
  257. agno/utils/http.py +113 -2
  258. agno/utils/knowledge.py +12 -5
  259. agno/utils/log.py +1 -0
  260. agno/utils/mcp.py +92 -2
  261. agno/utils/media.py +187 -1
  262. agno/utils/merge_dict.py +3 -3
  263. agno/utils/message.py +60 -0
  264. agno/utils/models/ai_foundry.py +9 -2
  265. agno/utils/models/claude.py +49 -14
  266. agno/utils/models/cohere.py +9 -2
  267. agno/utils/models/llama.py +9 -2
  268. agno/utils/models/mistral.py +4 -2
  269. agno/utils/print_response/agent.py +109 -16
  270. agno/utils/print_response/team.py +223 -30
  271. agno/utils/print_response/workflow.py +251 -34
  272. agno/utils/streamlit.py +1 -1
  273. agno/utils/team.py +98 -9
  274. agno/utils/tokens.py +657 -0
  275. agno/vectordb/base.py +39 -7
  276. agno/vectordb/cassandra/cassandra.py +21 -5
  277. agno/vectordb/chroma/chromadb.py +43 -12
  278. agno/vectordb/clickhouse/clickhousedb.py +21 -5
  279. agno/vectordb/couchbase/couchbase.py +29 -5
  280. agno/vectordb/lancedb/lance_db.py +92 -181
  281. agno/vectordb/langchaindb/langchaindb.py +24 -4
  282. agno/vectordb/lightrag/lightrag.py +17 -3
  283. agno/vectordb/llamaindex/llamaindexdb.py +25 -5
  284. agno/vectordb/milvus/milvus.py +50 -37
  285. agno/vectordb/mongodb/__init__.py +7 -1
  286. agno/vectordb/mongodb/mongodb.py +36 -30
  287. agno/vectordb/pgvector/pgvector.py +201 -77
  288. agno/vectordb/pineconedb/pineconedb.py +41 -23
  289. agno/vectordb/qdrant/qdrant.py +67 -54
  290. agno/vectordb/redis/__init__.py +9 -0
  291. agno/vectordb/redis/redisdb.py +682 -0
  292. agno/vectordb/singlestore/singlestore.py +50 -29
  293. agno/vectordb/surrealdb/surrealdb.py +31 -41
  294. agno/vectordb/upstashdb/upstashdb.py +34 -6
  295. agno/vectordb/weaviate/weaviate.py +53 -14
  296. agno/workflow/__init__.py +2 -0
  297. agno/workflow/agent.py +299 -0
  298. agno/workflow/condition.py +120 -18
  299. agno/workflow/loop.py +77 -10
  300. agno/workflow/parallel.py +231 -143
  301. agno/workflow/router.py +118 -17
  302. agno/workflow/step.py +609 -170
  303. agno/workflow/steps.py +73 -6
  304. agno/workflow/types.py +96 -21
  305. agno/workflow/workflow.py +2039 -262
  306. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
  307. agno-2.3.13.dist-info/RECORD +613 -0
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -679
  310. agno/tools/memori.py +0 -339
  311. agno-2.1.2.dist-info/RECORD +0 -543
  312. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
  313. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,276 @@
1
+ """
2
+ Trace data models for Agno tracing.
3
+ """
4
+
5
+ from dataclasses import asdict, dataclass
6
+ from datetime import datetime, timezone
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ from opentelemetry.sdk.trace import ReadableSpan # type: ignore
10
+ from opentelemetry.trace import SpanKind, StatusCode # type: ignore
11
+
12
+
13
+ @dataclass
14
+ class Trace:
15
+ """Represents a complete trace (one record per trace_id)"""
16
+
17
+ trace_id: str
18
+ name: str # Name from root span
19
+ status: str # Overall status: OK, ERROR, UNSET
20
+ start_time: datetime # Python datetime object
21
+ end_time: datetime # Python datetime object
22
+ duration_ms: int
23
+ total_spans: int
24
+ error_count: int
25
+
26
+ # Context from root span
27
+ run_id: Optional[str]
28
+ session_id: Optional[str]
29
+ user_id: Optional[str]
30
+ agent_id: Optional[str]
31
+ team_id: Optional[str]
32
+ workflow_id: Optional[str]
33
+
34
+ created_at: datetime # Python datetime object
35
+
36
+ def to_dict(self) -> Dict[str, Any]:
37
+ """Convert Trace to dictionary for database storage (datetime -> ISO string)"""
38
+ data = asdict(self)
39
+ # Convert datetime objects to ISO format strings for database storage
40
+ data["start_time"] = self.start_time.isoformat()
41
+ data["end_time"] = self.end_time.isoformat()
42
+ data["created_at"] = self.created_at.isoformat()
43
+ return data
44
+
45
+ @classmethod
46
+ def from_dict(cls, data: Dict[str, Any]) -> "Trace":
47
+ """Create Trace from dictionary (ISO string -> datetime)"""
48
+ # Convert ISO format strings to datetime objects
49
+ start_time = data["start_time"]
50
+ if isinstance(start_time, str):
51
+ start_time = datetime.fromisoformat(start_time.replace("Z", "+00:00"))
52
+ elif isinstance(start_time, int):
53
+ start_time = datetime.fromtimestamp(start_time / 1_000_000_000, tz=timezone.utc)
54
+
55
+ end_time = data["end_time"]
56
+ if isinstance(end_time, str):
57
+ end_time = datetime.fromisoformat(end_time.replace("Z", "+00:00"))
58
+ elif isinstance(end_time, int):
59
+ end_time = datetime.fromtimestamp(end_time / 1_000_000_000, tz=timezone.utc)
60
+
61
+ created_at = data["created_at"]
62
+ if isinstance(created_at, str):
63
+ created_at = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
64
+ elif isinstance(created_at, int):
65
+ created_at = datetime.fromtimestamp(created_at, tz=timezone.utc)
66
+
67
+ return cls(
68
+ trace_id=data["trace_id"],
69
+ name=data["name"],
70
+ status=data["status"],
71
+ start_time=start_time,
72
+ end_time=end_time,
73
+ duration_ms=data["duration_ms"],
74
+ total_spans=data["total_spans"],
75
+ error_count=data["error_count"],
76
+ run_id=data.get("run_id"),
77
+ session_id=data.get("session_id"),
78
+ user_id=data.get("user_id"),
79
+ agent_id=data.get("agent_id"),
80
+ team_id=data.get("team_id"),
81
+ workflow_id=data.get("workflow_id"),
82
+ created_at=created_at,
83
+ )
84
+
85
+
86
+ @dataclass
87
+ class Span:
88
+ """Represents a single span within a trace"""
89
+
90
+ span_id: str
91
+ trace_id: str
92
+ parent_span_id: Optional[str]
93
+ name: str
94
+ span_kind: str
95
+ status_code: str
96
+ status_message: Optional[str]
97
+ start_time: datetime # Python datetime object
98
+ end_time: datetime # Python datetime object
99
+ duration_ms: int
100
+ attributes: Dict[str, Any]
101
+ created_at: datetime # Python datetime object
102
+
103
+ def to_dict(self) -> Dict[str, Any]:
104
+ """Convert Span to dictionary for database storage (datetime -> ISO string)"""
105
+ data = asdict(self)
106
+ # Convert datetime objects to ISO format strings for database storage
107
+ data["start_time"] = self.start_time.isoformat()
108
+ data["end_time"] = self.end_time.isoformat()
109
+ data["created_at"] = self.created_at.isoformat()
110
+ return data
111
+
112
+ @classmethod
113
+ def from_dict(cls, data: Dict[str, Any]) -> "Span":
114
+ """Create Span from dictionary (ISO string -> datetime)"""
115
+ # Convert ISO format strings to datetime objects
116
+ start_time = data["start_time"]
117
+ if isinstance(start_time, str):
118
+ start_time = datetime.fromisoformat(start_time.replace("Z", "+00:00"))
119
+ elif isinstance(start_time, int):
120
+ start_time = datetime.fromtimestamp(start_time / 1_000_000_000, tz=timezone.utc)
121
+
122
+ end_time = data["end_time"]
123
+ if isinstance(end_time, str):
124
+ end_time = datetime.fromisoformat(end_time.replace("Z", "+00:00"))
125
+ elif isinstance(end_time, int):
126
+ end_time = datetime.fromtimestamp(end_time / 1_000_000_000, tz=timezone.utc)
127
+
128
+ created_at = data["created_at"]
129
+ if isinstance(created_at, str):
130
+ created_at = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
131
+ elif isinstance(created_at, int):
132
+ created_at = datetime.fromtimestamp(created_at, tz=timezone.utc)
133
+
134
+ return cls(
135
+ span_id=data["span_id"],
136
+ trace_id=data["trace_id"],
137
+ parent_span_id=data.get("parent_span_id"),
138
+ name=data["name"],
139
+ span_kind=data["span_kind"],
140
+ status_code=data["status_code"],
141
+ status_message=data.get("status_message"),
142
+ start_time=start_time,
143
+ end_time=end_time,
144
+ duration_ms=data["duration_ms"],
145
+ attributes=data.get("attributes", {}),
146
+ created_at=created_at,
147
+ )
148
+
149
+ @classmethod
150
+ def from_otel_span(cls, otel_span: ReadableSpan) -> "Span":
151
+ """Convert OpenTelemetry ReadableSpan to Span"""
152
+ # Extract span context
153
+ span_context = otel_span.context
154
+ trace_id = format(span_context.trace_id, "032x") if span_context else "0" * 32
155
+ span_id = format(span_context.span_id, "016x") if span_context else "0" * 16
156
+
157
+ # Extract parent span ID if exists
158
+ parent_span_id = None
159
+ if otel_span.parent and otel_span.parent.span_id:
160
+ parent_span_id = format(otel_span.parent.span_id, "016x")
161
+
162
+ # Extract span kind
163
+ span_kind_map = {
164
+ SpanKind.INTERNAL: "INTERNAL",
165
+ SpanKind.SERVER: "SERVER",
166
+ SpanKind.CLIENT: "CLIENT",
167
+ SpanKind.PRODUCER: "PRODUCER",
168
+ SpanKind.CONSUMER: "CONSUMER",
169
+ }
170
+ span_kind = span_kind_map.get(otel_span.kind, "INTERNAL")
171
+
172
+ # Extract status
173
+ status_code_map = {
174
+ StatusCode.UNSET: "UNSET",
175
+ StatusCode.OK: "OK",
176
+ StatusCode.ERROR: "ERROR",
177
+ }
178
+ status_code = status_code_map.get(otel_span.status.status_code, "UNSET")
179
+ status_message = otel_span.status.description
180
+
181
+ # Calculate duration in milliseconds
182
+ start_time_ns = otel_span.start_time or 0
183
+ end_time_ns = otel_span.end_time or start_time_ns
184
+ duration_ms = int((end_time_ns - start_time_ns) / 1_000_000)
185
+
186
+ # Convert nanosecond timestamps to datetime objects
187
+ start_time = datetime.fromtimestamp(start_time_ns / 1_000_000_000, tz=timezone.utc)
188
+ end_time = datetime.fromtimestamp(end_time_ns / 1_000_000_000, tz=timezone.utc)
189
+
190
+ # Convert attributes to dictionary
191
+ attributes: Dict[str, Any] = {}
192
+ if otel_span.attributes:
193
+ for key, value in otel_span.attributes.items():
194
+ # Convert attribute values to JSON-serializable types
195
+ if isinstance(value, (str, int, float, bool, type(None))):
196
+ attributes[key] = value
197
+ elif isinstance(value, (list, tuple)):
198
+ attributes[key] = list(value)
199
+ else:
200
+ attributes[key] = str(value)
201
+
202
+ return cls(
203
+ span_id=span_id,
204
+ trace_id=trace_id,
205
+ parent_span_id=parent_span_id,
206
+ name=otel_span.name,
207
+ span_kind=span_kind,
208
+ status_code=status_code,
209
+ status_message=status_message,
210
+ start_time=start_time,
211
+ end_time=end_time,
212
+ duration_ms=duration_ms,
213
+ attributes=attributes,
214
+ created_at=datetime.now(timezone.utc),
215
+ )
216
+
217
+
218
+ def create_trace_from_spans(spans: List[Span]) -> Optional[Trace]:
219
+ """
220
+ Create a Trace object from a list of Span objects with the same trace_id.
221
+
222
+ Args:
223
+ spans: List of Span objects belonging to the same trace
224
+
225
+ Returns:
226
+ Trace object with aggregated information, or None if spans list is empty
227
+ """
228
+ if not spans:
229
+ return None
230
+
231
+ # Find root span (no parent)
232
+ root_span = next((s for s in spans if not s.parent_span_id), spans[0])
233
+
234
+ # Calculate aggregated metrics
235
+ trace_id = spans[0].trace_id
236
+ start_time = min(s.start_time for s in spans)
237
+ end_time = max(s.end_time for s in spans)
238
+ duration_ms = int((end_time - start_time).total_seconds() * 1000)
239
+ total_spans = len(spans)
240
+ error_count = sum(1 for s in spans if s.status_code == "ERROR")
241
+
242
+ # Determine overall status (ERROR if any span errored, OK otherwise)
243
+ status = "ERROR" if error_count > 0 else "OK"
244
+
245
+ # Extract context from root span's attributes
246
+ attrs = root_span.attributes
247
+ run_id = attrs.get("run_id") or attrs.get("agno.run.id")
248
+
249
+ session_id = attrs.get("session_id") or attrs.get("agno.session.id") or attrs.get("session.id")
250
+
251
+ user_id = attrs.get("user_id") or attrs.get("agno.user.id") or attrs.get("user.id")
252
+
253
+ # Try to extract agent_id from the span name or attributes
254
+ agent_id = attrs.get("agent_id") or attrs.get("agno.agent.id")
255
+
256
+ team_id = attrs.get("team_id") or attrs.get("agno.team.id")
257
+
258
+ workflow_id = attrs.get("workflow_id") or attrs.get("agno.workflow.id")
259
+
260
+ return Trace(
261
+ trace_id=trace_id,
262
+ name=root_span.name,
263
+ status=status,
264
+ start_time=start_time,
265
+ end_time=end_time,
266
+ duration_ms=duration_ms,
267
+ total_spans=total_spans,
268
+ error_count=error_count,
269
+ run_id=run_id,
270
+ session_id=session_id,
271
+ user_id=user_id,
272
+ agent_id=agent_id,
273
+ team_id=team_id,
274
+ workflow_id=workflow_id,
275
+ created_at=datetime.now(timezone.utc),
276
+ )
agno/tracing/setup.py ADDED
@@ -0,0 +1,111 @@
1
+ """
2
+ Setup helper functions for configuring Agno tracing.
3
+ """
4
+
5
+ from typing import Union
6
+
7
+ from agno.db.base import AsyncBaseDb, BaseDb
8
+ from agno.tracing.exporter import DatabaseSpanExporter
9
+ from agno.utils.log import logger
10
+
11
+ try:
12
+ from openinference.instrumentation.agno import AgnoInstrumentor # type: ignore
13
+ from opentelemetry import trace as trace_api # type: ignore
14
+ from opentelemetry.sdk.trace import TracerProvider # type: ignore
15
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor, SpanProcessor # type: ignore
16
+
17
+ OPENTELEMETRY_AVAILABLE = True
18
+ except ImportError:
19
+ OPENTELEMETRY_AVAILABLE = False
20
+
21
+
22
+ def setup_tracing(
23
+ db: Union[BaseDb, AsyncBaseDb],
24
+ batch_processing: bool = False,
25
+ max_queue_size: int = 2048,
26
+ max_export_batch_size: int = 512,
27
+ schedule_delay_millis: int = 5000,
28
+ ) -> None:
29
+ """
30
+ Set up OpenTelemetry tracing with database export for Agno agents.
31
+
32
+ This function configures automatic tracing for all Agno agents, teams, and workflows.
33
+ Traces are automatically captured for:
34
+ - Agent runs (agent.run, agent.arun)
35
+ - Model calls (model.response)
36
+ - Tool executions
37
+ - Team coordination
38
+ - Workflow steps
39
+
40
+ Args:
41
+ db: Database instance to store traces (sync or async)
42
+ batch_processing: If True, use BatchSpanProcessor for better performance
43
+ If False, use SimpleSpanProcessor (immediate export)
44
+ max_queue_size: Maximum queue size for batch processor
45
+ max_export_batch_size: Maximum batch size for export
46
+ schedule_delay_millis: Delay in milliseconds between batch exports
47
+
48
+ Raises:
49
+ ImportError: If OpenTelemetry packages are not installed
50
+
51
+ Example:
52
+ ```python
53
+ from agno.db.sqlite import SqliteDb
54
+ from agno.tracing import setup_tracing
55
+
56
+ db = SqliteDb(db_file="tmp/traces.db")
57
+ setup_tracing(db=db)
58
+
59
+ # Now all agents will be automatically traced
60
+ agent = Agent(...)
61
+ agent.run("Hello") # This will be traced automatically
62
+ ```
63
+ """
64
+ if not OPENTELEMETRY_AVAILABLE:
65
+ raise ImportError(
66
+ "OpenTelemetry packages are required for tracing. "
67
+ "Install with: pip install opentelemetry-api opentelemetry-sdk openinference-instrumentation-agno"
68
+ )
69
+
70
+ # Check if tracing is already set up (handles reload scenarios)
71
+ current_provider = trace_api.get_tracer_provider()
72
+ if isinstance(current_provider, TracerProvider):
73
+ # Already configured with a real TracerProvider, skip
74
+ return
75
+
76
+ try:
77
+ # Create tracer provider
78
+ tracer_provider = TracerProvider()
79
+
80
+ # Create database exporter
81
+ exporter = DatabaseSpanExporter(db=db)
82
+
83
+ # Configure span processor
84
+ processor: SpanProcessor
85
+ if batch_processing:
86
+ processor = BatchSpanProcessor(
87
+ exporter,
88
+ max_queue_size=max_queue_size,
89
+ max_export_batch_size=max_export_batch_size,
90
+ schedule_delay_millis=schedule_delay_millis,
91
+ )
92
+ logger.debug(
93
+ f"Tracing configured with BatchSpanProcessor "
94
+ f"(queue_size={max_queue_size}, batch_size={max_export_batch_size})"
95
+ )
96
+ else:
97
+ processor = SimpleSpanProcessor(exporter)
98
+ logger.debug("Tracing configured with SimpleSpanProcessor")
99
+
100
+ tracer_provider.add_span_processor(processor)
101
+
102
+ # Set the global tracer provider
103
+ trace_api.set_tracer_provider(tracer_provider)
104
+
105
+ # Instrument Agno with OpenInference
106
+ AgnoInstrumentor().instrument(tracer_provider=tracer_provider)
107
+
108
+ logger.info("Agno tracing successfully set up with database storage")
109
+ except Exception as e:
110
+ logger.error(f"Failed to set up tracing: {e}", exc_info=True)
111
+ raise