agno 2.2.13__py3-none-any.whl → 2.4.3__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 (383) hide show
  1. agno/agent/__init__.py +6 -0
  2. agno/agent/agent.py +5252 -3145
  3. agno/agent/remote.py +525 -0
  4. agno/api/api.py +2 -0
  5. agno/client/__init__.py +3 -0
  6. agno/client/a2a/__init__.py +10 -0
  7. agno/client/a2a/client.py +554 -0
  8. agno/client/a2a/schemas.py +112 -0
  9. agno/client/a2a/utils.py +369 -0
  10. agno/client/os.py +2669 -0
  11. agno/compression/__init__.py +3 -0
  12. agno/compression/manager.py +247 -0
  13. agno/culture/manager.py +2 -2
  14. agno/db/base.py +927 -6
  15. agno/db/dynamo/dynamo.py +788 -2
  16. agno/db/dynamo/schemas.py +128 -0
  17. agno/db/dynamo/utils.py +26 -3
  18. agno/db/firestore/firestore.py +674 -50
  19. agno/db/firestore/schemas.py +41 -0
  20. agno/db/firestore/utils.py +25 -10
  21. agno/db/gcs_json/gcs_json_db.py +506 -3
  22. agno/db/gcs_json/utils.py +14 -2
  23. agno/db/in_memory/in_memory_db.py +203 -4
  24. agno/db/in_memory/utils.py +14 -2
  25. agno/db/json/json_db.py +498 -2
  26. agno/db/json/utils.py +14 -2
  27. agno/db/migrations/manager.py +199 -0
  28. agno/db/migrations/utils.py +19 -0
  29. agno/db/migrations/v1_to_v2.py +54 -16
  30. agno/db/migrations/versions/__init__.py +0 -0
  31. agno/db/migrations/versions/v2_3_0.py +977 -0
  32. agno/db/mongo/async_mongo.py +1013 -39
  33. agno/db/mongo/mongo.py +684 -4
  34. agno/db/mongo/schemas.py +48 -0
  35. agno/db/mongo/utils.py +17 -0
  36. agno/db/mysql/__init__.py +2 -1
  37. agno/db/mysql/async_mysql.py +2958 -0
  38. agno/db/mysql/mysql.py +722 -53
  39. agno/db/mysql/schemas.py +77 -11
  40. agno/db/mysql/utils.py +151 -8
  41. agno/db/postgres/async_postgres.py +1254 -137
  42. agno/db/postgres/postgres.py +2316 -93
  43. agno/db/postgres/schemas.py +153 -21
  44. agno/db/postgres/utils.py +22 -7
  45. agno/db/redis/redis.py +531 -3
  46. agno/db/redis/schemas.py +36 -0
  47. agno/db/redis/utils.py +31 -15
  48. agno/db/schemas/evals.py +1 -0
  49. agno/db/schemas/memory.py +20 -9
  50. agno/db/singlestore/schemas.py +70 -1
  51. agno/db/singlestore/singlestore.py +737 -74
  52. agno/db/singlestore/utils.py +13 -3
  53. agno/db/sqlite/async_sqlite.py +1069 -89
  54. agno/db/sqlite/schemas.py +133 -1
  55. agno/db/sqlite/sqlite.py +2203 -165
  56. agno/db/sqlite/utils.py +21 -11
  57. agno/db/surrealdb/models.py +25 -0
  58. agno/db/surrealdb/surrealdb.py +603 -1
  59. agno/db/utils.py +60 -0
  60. agno/eval/__init__.py +26 -3
  61. agno/eval/accuracy.py +25 -12
  62. agno/eval/agent_as_judge.py +871 -0
  63. agno/eval/base.py +29 -0
  64. agno/eval/performance.py +10 -4
  65. agno/eval/reliability.py +22 -13
  66. agno/eval/utils.py +2 -1
  67. agno/exceptions.py +42 -0
  68. agno/hooks/__init__.py +3 -0
  69. agno/hooks/decorator.py +164 -0
  70. agno/integrations/discord/client.py +13 -2
  71. agno/knowledge/__init__.py +4 -0
  72. agno/knowledge/chunking/code.py +90 -0
  73. agno/knowledge/chunking/document.py +65 -4
  74. agno/knowledge/chunking/fixed.py +4 -1
  75. agno/knowledge/chunking/markdown.py +102 -11
  76. agno/knowledge/chunking/recursive.py +2 -2
  77. agno/knowledge/chunking/semantic.py +130 -48
  78. agno/knowledge/chunking/strategy.py +18 -0
  79. agno/knowledge/embedder/azure_openai.py +0 -1
  80. agno/knowledge/embedder/google.py +1 -1
  81. agno/knowledge/embedder/mistral.py +1 -1
  82. agno/knowledge/embedder/nebius.py +1 -1
  83. agno/knowledge/embedder/openai.py +16 -12
  84. agno/knowledge/filesystem.py +412 -0
  85. agno/knowledge/knowledge.py +4261 -1199
  86. agno/knowledge/protocol.py +134 -0
  87. agno/knowledge/reader/arxiv_reader.py +3 -2
  88. agno/knowledge/reader/base.py +9 -7
  89. agno/knowledge/reader/csv_reader.py +91 -42
  90. agno/knowledge/reader/docx_reader.py +9 -10
  91. agno/knowledge/reader/excel_reader.py +225 -0
  92. agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
  93. agno/knowledge/reader/firecrawl_reader.py +3 -2
  94. agno/knowledge/reader/json_reader.py +16 -22
  95. agno/knowledge/reader/markdown_reader.py +15 -14
  96. agno/knowledge/reader/pdf_reader.py +33 -28
  97. agno/knowledge/reader/pptx_reader.py +9 -10
  98. agno/knowledge/reader/reader_factory.py +135 -1
  99. agno/knowledge/reader/s3_reader.py +8 -16
  100. agno/knowledge/reader/tavily_reader.py +3 -3
  101. agno/knowledge/reader/text_reader.py +15 -14
  102. agno/knowledge/reader/utils/__init__.py +17 -0
  103. agno/knowledge/reader/utils/spreadsheet.py +114 -0
  104. agno/knowledge/reader/web_search_reader.py +8 -65
  105. agno/knowledge/reader/website_reader.py +16 -13
  106. agno/knowledge/reader/wikipedia_reader.py +36 -3
  107. agno/knowledge/reader/youtube_reader.py +3 -2
  108. agno/knowledge/remote_content/__init__.py +33 -0
  109. agno/knowledge/remote_content/config.py +266 -0
  110. agno/knowledge/remote_content/remote_content.py +105 -17
  111. agno/knowledge/utils.py +76 -22
  112. agno/learn/__init__.py +71 -0
  113. agno/learn/config.py +463 -0
  114. agno/learn/curate.py +185 -0
  115. agno/learn/machine.py +725 -0
  116. agno/learn/schemas.py +1114 -0
  117. agno/learn/stores/__init__.py +38 -0
  118. agno/learn/stores/decision_log.py +1156 -0
  119. agno/learn/stores/entity_memory.py +3275 -0
  120. agno/learn/stores/learned_knowledge.py +1583 -0
  121. agno/learn/stores/protocol.py +117 -0
  122. agno/learn/stores/session_context.py +1217 -0
  123. agno/learn/stores/user_memory.py +1495 -0
  124. agno/learn/stores/user_profile.py +1220 -0
  125. agno/learn/utils.py +209 -0
  126. agno/media.py +22 -6
  127. agno/memory/__init__.py +14 -1
  128. agno/memory/manager.py +223 -8
  129. agno/memory/strategies/__init__.py +15 -0
  130. agno/memory/strategies/base.py +66 -0
  131. agno/memory/strategies/summarize.py +196 -0
  132. agno/memory/strategies/types.py +37 -0
  133. agno/models/aimlapi/aimlapi.py +17 -0
  134. agno/models/anthropic/claude.py +434 -59
  135. agno/models/aws/bedrock.py +121 -20
  136. agno/models/aws/claude.py +131 -274
  137. agno/models/azure/ai_foundry.py +10 -6
  138. agno/models/azure/openai_chat.py +33 -10
  139. agno/models/base.py +1162 -561
  140. agno/models/cerebras/cerebras.py +120 -24
  141. agno/models/cerebras/cerebras_openai.py +21 -2
  142. agno/models/cohere/chat.py +65 -6
  143. agno/models/cometapi/cometapi.py +18 -1
  144. agno/models/dashscope/dashscope.py +2 -3
  145. agno/models/deepinfra/deepinfra.py +18 -1
  146. agno/models/deepseek/deepseek.py +69 -3
  147. agno/models/fireworks/fireworks.py +18 -1
  148. agno/models/google/gemini.py +959 -89
  149. agno/models/google/utils.py +22 -0
  150. agno/models/groq/groq.py +48 -18
  151. agno/models/huggingface/huggingface.py +17 -6
  152. agno/models/ibm/watsonx.py +16 -6
  153. agno/models/internlm/internlm.py +18 -1
  154. agno/models/langdb/langdb.py +13 -1
  155. agno/models/litellm/chat.py +88 -9
  156. agno/models/litellm/litellm_openai.py +18 -1
  157. agno/models/message.py +24 -5
  158. agno/models/meta/llama.py +40 -13
  159. agno/models/meta/llama_openai.py +22 -21
  160. agno/models/metrics.py +12 -0
  161. agno/models/mistral/mistral.py +8 -4
  162. agno/models/n1n/__init__.py +3 -0
  163. agno/models/n1n/n1n.py +57 -0
  164. agno/models/nebius/nebius.py +6 -7
  165. agno/models/nvidia/nvidia.py +20 -3
  166. agno/models/ollama/__init__.py +2 -0
  167. agno/models/ollama/chat.py +17 -6
  168. agno/models/ollama/responses.py +100 -0
  169. agno/models/openai/__init__.py +2 -0
  170. agno/models/openai/chat.py +117 -26
  171. agno/models/openai/open_responses.py +46 -0
  172. agno/models/openai/responses.py +110 -32
  173. agno/models/openrouter/__init__.py +2 -0
  174. agno/models/openrouter/openrouter.py +67 -2
  175. agno/models/openrouter/responses.py +146 -0
  176. agno/models/perplexity/perplexity.py +19 -1
  177. agno/models/portkey/portkey.py +7 -6
  178. agno/models/requesty/requesty.py +19 -2
  179. agno/models/response.py +20 -2
  180. agno/models/sambanova/sambanova.py +20 -3
  181. agno/models/siliconflow/siliconflow.py +19 -2
  182. agno/models/together/together.py +20 -3
  183. agno/models/vercel/v0.py +20 -3
  184. agno/models/vertexai/claude.py +124 -4
  185. agno/models/vllm/vllm.py +19 -14
  186. agno/models/xai/xai.py +19 -2
  187. agno/os/app.py +467 -137
  188. agno/os/auth.py +253 -5
  189. agno/os/config.py +22 -0
  190. agno/os/interfaces/a2a/a2a.py +7 -6
  191. agno/os/interfaces/a2a/router.py +635 -26
  192. agno/os/interfaces/a2a/utils.py +32 -33
  193. agno/os/interfaces/agui/agui.py +5 -3
  194. agno/os/interfaces/agui/router.py +26 -16
  195. agno/os/interfaces/agui/utils.py +97 -57
  196. agno/os/interfaces/base.py +7 -7
  197. agno/os/interfaces/slack/router.py +16 -7
  198. agno/os/interfaces/slack/slack.py +7 -7
  199. agno/os/interfaces/whatsapp/router.py +35 -7
  200. agno/os/interfaces/whatsapp/security.py +3 -1
  201. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  202. agno/os/managers.py +326 -0
  203. agno/os/mcp.py +652 -79
  204. agno/os/middleware/__init__.py +4 -0
  205. agno/os/middleware/jwt.py +718 -115
  206. agno/os/middleware/trailing_slash.py +27 -0
  207. agno/os/router.py +105 -1558
  208. agno/os/routers/agents/__init__.py +3 -0
  209. agno/os/routers/agents/router.py +655 -0
  210. agno/os/routers/agents/schema.py +288 -0
  211. agno/os/routers/components/__init__.py +3 -0
  212. agno/os/routers/components/components.py +475 -0
  213. agno/os/routers/database.py +155 -0
  214. agno/os/routers/evals/evals.py +111 -18
  215. agno/os/routers/evals/schemas.py +38 -5
  216. agno/os/routers/evals/utils.py +80 -11
  217. agno/os/routers/health.py +3 -3
  218. agno/os/routers/knowledge/knowledge.py +284 -35
  219. agno/os/routers/knowledge/schemas.py +14 -2
  220. agno/os/routers/memory/memory.py +274 -11
  221. agno/os/routers/memory/schemas.py +44 -3
  222. agno/os/routers/metrics/metrics.py +30 -15
  223. agno/os/routers/metrics/schemas.py +10 -6
  224. agno/os/routers/registry/__init__.py +3 -0
  225. agno/os/routers/registry/registry.py +337 -0
  226. agno/os/routers/session/session.py +143 -14
  227. agno/os/routers/teams/__init__.py +3 -0
  228. agno/os/routers/teams/router.py +550 -0
  229. agno/os/routers/teams/schema.py +280 -0
  230. agno/os/routers/traces/__init__.py +3 -0
  231. agno/os/routers/traces/schemas.py +414 -0
  232. agno/os/routers/traces/traces.py +549 -0
  233. agno/os/routers/workflows/__init__.py +3 -0
  234. agno/os/routers/workflows/router.py +757 -0
  235. agno/os/routers/workflows/schema.py +139 -0
  236. agno/os/schema.py +157 -584
  237. agno/os/scopes.py +469 -0
  238. agno/os/settings.py +3 -0
  239. agno/os/utils.py +574 -185
  240. agno/reasoning/anthropic.py +85 -1
  241. agno/reasoning/azure_ai_foundry.py +93 -1
  242. agno/reasoning/deepseek.py +102 -2
  243. agno/reasoning/default.py +6 -7
  244. agno/reasoning/gemini.py +87 -3
  245. agno/reasoning/groq.py +109 -2
  246. agno/reasoning/helpers.py +6 -7
  247. agno/reasoning/manager.py +1238 -0
  248. agno/reasoning/ollama.py +93 -1
  249. agno/reasoning/openai.py +115 -1
  250. agno/reasoning/vertexai.py +85 -1
  251. agno/registry/__init__.py +3 -0
  252. agno/registry/registry.py +68 -0
  253. agno/remote/__init__.py +3 -0
  254. agno/remote/base.py +581 -0
  255. agno/run/__init__.py +2 -4
  256. agno/run/agent.py +134 -19
  257. agno/run/base.py +49 -1
  258. agno/run/cancel.py +65 -52
  259. agno/run/cancellation_management/__init__.py +9 -0
  260. agno/run/cancellation_management/base.py +78 -0
  261. agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
  262. agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
  263. agno/run/requirement.py +181 -0
  264. agno/run/team.py +111 -19
  265. agno/run/workflow.py +2 -1
  266. agno/session/agent.py +57 -92
  267. agno/session/summary.py +1 -1
  268. agno/session/team.py +62 -115
  269. agno/session/workflow.py +353 -57
  270. agno/skills/__init__.py +17 -0
  271. agno/skills/agent_skills.py +377 -0
  272. agno/skills/errors.py +32 -0
  273. agno/skills/loaders/__init__.py +4 -0
  274. agno/skills/loaders/base.py +27 -0
  275. agno/skills/loaders/local.py +216 -0
  276. agno/skills/skill.py +65 -0
  277. agno/skills/utils.py +107 -0
  278. agno/skills/validator.py +277 -0
  279. agno/table.py +10 -0
  280. agno/team/__init__.py +5 -1
  281. agno/team/remote.py +447 -0
  282. agno/team/team.py +3769 -2202
  283. agno/tools/brandfetch.py +27 -18
  284. agno/tools/browserbase.py +225 -16
  285. agno/tools/crawl4ai.py +3 -0
  286. agno/tools/duckduckgo.py +25 -71
  287. agno/tools/exa.py +0 -21
  288. agno/tools/file.py +14 -13
  289. agno/tools/file_generation.py +12 -6
  290. agno/tools/firecrawl.py +15 -7
  291. agno/tools/function.py +94 -113
  292. agno/tools/google_bigquery.py +11 -2
  293. agno/tools/google_drive.py +4 -3
  294. agno/tools/knowledge.py +9 -4
  295. agno/tools/mcp/mcp.py +301 -18
  296. agno/tools/mcp/multi_mcp.py +269 -14
  297. agno/tools/mem0.py +11 -10
  298. agno/tools/memory.py +47 -46
  299. agno/tools/mlx_transcribe.py +10 -7
  300. agno/tools/models/nebius.py +5 -5
  301. agno/tools/models_labs.py +20 -10
  302. agno/tools/nano_banana.py +151 -0
  303. agno/tools/parallel.py +0 -7
  304. agno/tools/postgres.py +76 -36
  305. agno/tools/python.py +14 -6
  306. agno/tools/reasoning.py +30 -23
  307. agno/tools/redshift.py +406 -0
  308. agno/tools/shopify.py +1519 -0
  309. agno/tools/spotify.py +919 -0
  310. agno/tools/tavily.py +4 -1
  311. agno/tools/toolkit.py +253 -18
  312. agno/tools/websearch.py +93 -0
  313. agno/tools/website.py +1 -1
  314. agno/tools/wikipedia.py +1 -1
  315. agno/tools/workflow.py +56 -48
  316. agno/tools/yfinance.py +12 -11
  317. agno/tracing/__init__.py +12 -0
  318. agno/tracing/exporter.py +161 -0
  319. agno/tracing/schemas.py +276 -0
  320. agno/tracing/setup.py +112 -0
  321. agno/utils/agent.py +251 -10
  322. agno/utils/cryptography.py +22 -0
  323. agno/utils/dttm.py +33 -0
  324. agno/utils/events.py +264 -7
  325. agno/utils/hooks.py +111 -3
  326. agno/utils/http.py +161 -2
  327. agno/utils/mcp.py +49 -8
  328. agno/utils/media.py +22 -1
  329. agno/utils/models/ai_foundry.py +9 -2
  330. agno/utils/models/claude.py +20 -5
  331. agno/utils/models/cohere.py +9 -2
  332. agno/utils/models/llama.py +9 -2
  333. agno/utils/models/mistral.py +4 -2
  334. agno/utils/os.py +0 -0
  335. agno/utils/print_response/agent.py +99 -16
  336. agno/utils/print_response/team.py +223 -24
  337. agno/utils/print_response/workflow.py +0 -2
  338. agno/utils/prompts.py +8 -6
  339. agno/utils/remote.py +23 -0
  340. agno/utils/response.py +1 -13
  341. agno/utils/string.py +91 -2
  342. agno/utils/team.py +62 -12
  343. agno/utils/tokens.py +657 -0
  344. agno/vectordb/base.py +15 -2
  345. agno/vectordb/cassandra/cassandra.py +1 -1
  346. agno/vectordb/chroma/__init__.py +2 -1
  347. agno/vectordb/chroma/chromadb.py +468 -23
  348. agno/vectordb/clickhouse/clickhousedb.py +1 -1
  349. agno/vectordb/couchbase/couchbase.py +6 -2
  350. agno/vectordb/lancedb/lance_db.py +7 -38
  351. agno/vectordb/lightrag/lightrag.py +7 -6
  352. agno/vectordb/milvus/milvus.py +118 -84
  353. agno/vectordb/mongodb/__init__.py +2 -1
  354. agno/vectordb/mongodb/mongodb.py +14 -31
  355. agno/vectordb/pgvector/pgvector.py +120 -66
  356. agno/vectordb/pineconedb/pineconedb.py +2 -19
  357. agno/vectordb/qdrant/__init__.py +2 -1
  358. agno/vectordb/qdrant/qdrant.py +33 -56
  359. agno/vectordb/redis/__init__.py +2 -1
  360. agno/vectordb/redis/redisdb.py +19 -31
  361. agno/vectordb/singlestore/singlestore.py +17 -9
  362. agno/vectordb/surrealdb/surrealdb.py +2 -38
  363. agno/vectordb/weaviate/__init__.py +2 -1
  364. agno/vectordb/weaviate/weaviate.py +7 -3
  365. agno/workflow/__init__.py +5 -1
  366. agno/workflow/agent.py +2 -2
  367. agno/workflow/condition.py +12 -10
  368. agno/workflow/loop.py +28 -9
  369. agno/workflow/parallel.py +21 -13
  370. agno/workflow/remote.py +362 -0
  371. agno/workflow/router.py +12 -9
  372. agno/workflow/step.py +261 -36
  373. agno/workflow/steps.py +12 -8
  374. agno/workflow/types.py +40 -77
  375. agno/workflow/workflow.py +939 -213
  376. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
  377. agno-2.4.3.dist-info/RECORD +677 -0
  378. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
  379. agno/tools/googlesearch.py +0 -98
  380. agno/tools/memori.py +0 -339
  381. agno-2.2.13.dist-info/RECORD +0 -575
  382. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
  383. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,414 @@
1
+ from datetime import datetime
2
+ from typing import Any, Dict, List, Optional
3
+
4
+ from pydantic import BaseModel, Field
5
+
6
+ from agno.os.utils import format_duration_ms
7
+
8
+
9
+ def _derive_span_type(span: Any) -> str:
10
+ """
11
+ Derive the correct span type from span attributes.
12
+
13
+ OpenInference sets span_kind to:
14
+ - AGENT for both agents and teams
15
+ - CHAIN for workflows
16
+
17
+ We use additional context (agno.team.id, agno.workflow.id) to differentiate:
18
+ - WORKFLOW: CHAIN spans or spans with agno.workflow.id
19
+ - TEAM: AGENT spans with agno.team.id
20
+ - AGENT: AGENT spans without agno.team.id
21
+ - LLM, TOOL, etc.: unchanged
22
+ """
23
+ span_kind = span.attributes.get("openinference.span.kind", "UNKNOWN")
24
+
25
+ # Check for workflow (CHAIN kind or has workflow.id)
26
+ if span_kind == "CHAIN":
27
+ return "WORKFLOW"
28
+
29
+ # Check for team vs agent
30
+ if span_kind == "AGENT":
31
+ # If it has a team.id attribute, it's a TEAM span
32
+ if span.attributes.get("agno.team.id") or span.attributes.get("team.id"):
33
+ return "TEAM"
34
+ return "AGENT"
35
+
36
+ # Return original span kind for LLM, TOOL, etc.
37
+ return span_kind
38
+
39
+
40
+ class TraceNode(BaseModel):
41
+ """Recursive node structure for rendering trace hierarchy in the frontend"""
42
+
43
+ id: str = Field(..., description="Span ID")
44
+ name: str = Field(..., description="Span name (e.g., 'agent.run', 'llm.invoke')")
45
+ type: str = Field(..., description="Span kind (AGENT, TEAM, WORKFLOW, LLM, TOOL)")
46
+ duration: str = Field(..., description="Human-readable duration (e.g., '123ms', '1.5s')")
47
+ start_time: datetime = Field(..., description="Start time (Pydantic auto-serializes to ISO 8601)")
48
+ end_time: datetime = Field(..., description="End time (Pydantic auto-serializes to ISO 8601)")
49
+ status: str = Field(..., description="Status code (OK, ERROR)")
50
+ input: Optional[str] = Field(None, description="Input to the span")
51
+ output: Optional[str] = Field(None, description="Output from the span")
52
+ error: Optional[str] = Field(None, description="Error message if status is ERROR")
53
+ spans: Optional[List["TraceNode"]] = Field(None, description="Child spans in the trace hierarchy")
54
+ step_type: Optional[str] = Field(None, description="Workflow step type (Step, Condition, function, Agent, Team)")
55
+ metadata: Optional[Dict[str, Any]] = Field(None, description="Additional span attributes and data")
56
+ extra_data: Optional[Dict[str, Any]] = Field(
57
+ None, description="Flexible field for custom attributes and additional data"
58
+ )
59
+
60
+ @classmethod
61
+ def from_span(cls, span: Any, spans: Optional[List["TraceNode"]] = None) -> "TraceNode":
62
+ """Create TraceNode from a Span object"""
63
+ # Derive the correct span type (AGENT, TEAM, WORKFLOW, LLM, TOOL, etc.)
64
+ span_type = _derive_span_type(span)
65
+
66
+ # Also get the raw span_kind for metadata extraction logic
67
+ span_kind = span.attributes.get("openinference.span.kind", "UNKNOWN")
68
+
69
+ # Extract input/output at root level (for all span types)
70
+ input_val = span.attributes.get("input.value")
71
+ output_val = span.attributes.get("output.value")
72
+
73
+ # Extract error information
74
+ error_val = None
75
+ if span.status_code == "ERROR":
76
+ error_val = span.status_message or span.attributes.get("exception.message")
77
+ output_val = None
78
+
79
+ # Build metadata with key attributes based on span kind
80
+ metadata: Dict[str, Any] = {}
81
+
82
+ if span_kind == "AGENT":
83
+ if run_id := span.attributes.get("agno.run.id"):
84
+ metadata["run_id"] = run_id
85
+
86
+ elif span_kind == "LLM":
87
+ if model_name := span.attributes.get("llm.model_name"):
88
+ metadata["model"] = model_name
89
+ if input_tokens := span.attributes.get("llm.token_count.prompt"):
90
+ metadata["input_tokens"] = input_tokens
91
+ if output_tokens := span.attributes.get("llm.token_count.completion"):
92
+ metadata["output_tokens"] = output_tokens
93
+
94
+ elif span_kind == "TOOL":
95
+ if tool_name := span.attributes.get("tool.name"):
96
+ metadata["tool_name"] = tool_name
97
+ if tool_params := span.attributes.get("tool.parameters"):
98
+ metadata["parameters"] = tool_params
99
+
100
+ elif span_kind == "CHAIN":
101
+ if workflow_description := span.attributes.get("agno.workflow.description"):
102
+ metadata["description"] = workflow_description
103
+ if steps_count := span.attributes.get("agno.workflow.steps_count"):
104
+ metadata["steps_count"] = steps_count
105
+ if steps := span.attributes.get("agno.workflow.steps"):
106
+ metadata["steps"] = steps
107
+ if step_types := span.attributes.get("agno.workflow.step_types"):
108
+ metadata["step_types"] = step_types
109
+
110
+ # Add session/user context if present
111
+ if session_id := span.attributes.get("session.id"):
112
+ metadata["session_id"] = session_id
113
+ if user_id := span.attributes.get("user.id"):
114
+ metadata["user_id"] = user_id
115
+
116
+ # Use datetime objects directly
117
+ return cls(
118
+ id=span.span_id,
119
+ name=span.name,
120
+ type=span_type,
121
+ duration=format_duration_ms(span.duration_ms),
122
+ start_time=span.start_time,
123
+ end_time=span.end_time,
124
+ status=span.status_code,
125
+ input=input_val,
126
+ output=output_val,
127
+ error=error_val,
128
+ spans=spans,
129
+ step_type=None, # Set by _build_span_tree for workflow steps
130
+ metadata=metadata if metadata else None,
131
+ extra_data=None,
132
+ )
133
+
134
+
135
+ class TraceSummary(BaseModel):
136
+ """Summary information for trace list view"""
137
+
138
+ trace_id: str = Field(..., description="Unique trace identifier")
139
+ name: str = Field(..., description="Trace name (usually root span name)")
140
+ status: str = Field(..., description="Overall status (OK, ERROR, UNSET)")
141
+ duration: str = Field(..., description="Human-readable total duration")
142
+ start_time: datetime = Field(..., description="Trace start time (Pydantic auto-serializes to ISO 8601)")
143
+ end_time: datetime = Field(..., description="Trace end time (Pydantic auto-serializes to ISO 8601)")
144
+ total_spans: int = Field(..., description="Total number of spans in this trace")
145
+ error_count: int = Field(..., description="Number of spans with errors")
146
+ input: Optional[str] = Field(None, description="Input to the agent")
147
+ run_id: Optional[str] = Field(None, description="Associated run ID")
148
+ session_id: Optional[str] = Field(None, description="Associated session ID")
149
+ user_id: Optional[str] = Field(None, description="Associated user ID")
150
+ agent_id: Optional[str] = Field(None, description="Associated agent ID")
151
+ team_id: Optional[str] = Field(None, description="Associated team ID")
152
+ workflow_id: Optional[str] = Field(None, description="Associated workflow ID")
153
+ created_at: datetime = Field(..., description="Time when trace was created (Pydantic auto-serializes to ISO 8601)")
154
+
155
+ @classmethod
156
+ def from_trace(cls, trace: Any, input: Optional[str] = None) -> "TraceSummary":
157
+ # Use datetime objects directly (Pydantic will auto-serialize to ISO 8601)
158
+ return cls(
159
+ trace_id=trace.trace_id,
160
+ name=trace.name,
161
+ status=trace.status,
162
+ duration=format_duration_ms(trace.duration_ms),
163
+ start_time=trace.start_time,
164
+ end_time=trace.end_time,
165
+ total_spans=trace.total_spans,
166
+ error_count=trace.error_count,
167
+ input=input,
168
+ run_id=trace.run_id,
169
+ session_id=trace.session_id,
170
+ user_id=trace.user_id,
171
+ agent_id=trace.agent_id,
172
+ team_id=trace.team_id,
173
+ workflow_id=trace.workflow_id,
174
+ created_at=trace.created_at,
175
+ )
176
+
177
+
178
+ class TraceSessionStats(BaseModel):
179
+ """Aggregated trace statistics grouped by session"""
180
+
181
+ session_id: str = Field(..., description="Session identifier")
182
+ user_id: Optional[str] = Field(None, description="User ID associated with the session")
183
+ agent_id: Optional[str] = Field(None, description="Agent ID(s) used in the session")
184
+ team_id: Optional[str] = Field(None, description="Team ID associated with the session")
185
+ workflow_id: Optional[str] = Field(None, description="Workflow ID associated with the session")
186
+ total_traces: int = Field(..., description="Total number of traces in this session")
187
+ first_trace_at: datetime = Field(..., description="Time of first trace (Pydantic auto-serializes to ISO 8601)")
188
+ last_trace_at: datetime = Field(..., description="Time of last trace (Pydantic auto-serializes to ISO 8601)")
189
+
190
+
191
+ class TraceDetail(BaseModel):
192
+ """Detailed trace information with hierarchical span tree"""
193
+
194
+ trace_id: str = Field(..., description="Unique trace identifier")
195
+ name: str = Field(..., description="Trace name (usually root span name)")
196
+ status: str = Field(..., description="Overall status (OK, ERROR)")
197
+ duration: str = Field(..., description="Human-readable total duration")
198
+ start_time: datetime = Field(..., description="Trace start time (Pydantic auto-serializes to ISO 8601)")
199
+ end_time: datetime = Field(..., description="Trace end time (Pydantic auto-serializes to ISO 8601)")
200
+ total_spans: int = Field(..., description="Total number of spans in this trace")
201
+ error_count: int = Field(..., description="Number of spans with errors")
202
+ input: Optional[str] = Field(None, description="Input to the agent/workflow")
203
+ output: Optional[str] = Field(None, description="Output from the agent/workflow")
204
+ error: Optional[str] = Field(None, description="Error message if status is ERROR")
205
+ run_id: Optional[str] = Field(None, description="Associated run ID")
206
+ session_id: Optional[str] = Field(None, description="Associated session ID")
207
+ user_id: Optional[str] = Field(None, description="Associated user ID")
208
+ agent_id: Optional[str] = Field(None, description="Associated agent ID")
209
+ team_id: Optional[str] = Field(None, description="Associated team ID")
210
+ workflow_id: Optional[str] = Field(None, description="Associated workflow ID")
211
+ created_at: datetime = Field(..., description="Time when trace was created (Pydantic auto-serializes to ISO 8601)")
212
+ tree: List[TraceNode] = Field(..., description="Hierarchical tree of spans (root nodes)")
213
+
214
+ @classmethod
215
+ def from_trace_and_spans(cls, trace: Any, spans: List[Any]) -> "TraceDetail":
216
+ """Create TraceDetail from a Trace and its Spans, building the tree structure"""
217
+ # Find root span to extract input/output/error
218
+ root_span = next((s for s in spans if not s.parent_span_id), None)
219
+ trace_input = None
220
+ trace_output = None
221
+ trace_error = None
222
+
223
+ if root_span:
224
+ trace_input = root_span.attributes.get("input.value")
225
+ output_val = root_span.attributes.get("output.value")
226
+
227
+ # If trace status is ERROR, extract error and set output to None
228
+ if trace.status == "ERROR" or root_span.status_code == "ERROR":
229
+ trace_error = root_span.status_message or root_span.attributes.get("exception.message")
230
+ trace_output = None
231
+ else:
232
+ trace_output = output_val
233
+
234
+ span_kind = root_span.attributes.get("openinference.span.kind", "")
235
+ output_is_empty = not trace_output or trace_output == "None" or str(trace_output).strip() == "None"
236
+ if span_kind == "CHAIN" and output_is_empty and trace.status != "ERROR":
237
+ # Find direct children of root span (workflow steps)
238
+ root_span_id = root_span.span_id
239
+ direct_children = [s for s in spans if s.parent_span_id == root_span_id]
240
+ if direct_children:
241
+ # Sort by end_time to get the last executed step
242
+ direct_children.sort(key=lambda s: s.end_time, reverse=True)
243
+ last_step = direct_children[0]
244
+ # Get output from the last step
245
+ trace_output = last_step.attributes.get("output.value")
246
+
247
+ # Calculate total tokens from all LLM spans
248
+ total_input_tokens = 0
249
+ total_output_tokens = 0
250
+ for span in spans:
251
+ if span.attributes.get("openinference.span.kind") == "LLM":
252
+ input_tokens = span.attributes.get("llm.token_count.prompt", 0)
253
+ output_tokens = span.attributes.get("llm.token_count.completion", 0)
254
+ if input_tokens:
255
+ total_input_tokens += input_tokens
256
+ if output_tokens:
257
+ total_output_tokens += output_tokens
258
+
259
+ # Build span tree with token totals
260
+ span_tree = cls._build_span_tree(
261
+ spans,
262
+ total_input_tokens,
263
+ total_output_tokens,
264
+ trace_start_time=trace.start_time,
265
+ trace_end_time=trace.end_time,
266
+ trace_duration_ms=trace.duration_ms,
267
+ )
268
+
269
+ # Use datetime objects directly (Pydantic will auto-serialize to ISO 8601)
270
+ return cls(
271
+ trace_id=trace.trace_id,
272
+ name=trace.name,
273
+ status=trace.status,
274
+ duration=format_duration_ms(trace.duration_ms),
275
+ start_time=trace.start_time,
276
+ end_time=trace.end_time,
277
+ total_spans=trace.total_spans,
278
+ error_count=trace.error_count,
279
+ input=trace_input,
280
+ output=trace_output,
281
+ error=trace_error,
282
+ run_id=trace.run_id,
283
+ session_id=trace.session_id,
284
+ user_id=trace.user_id,
285
+ agent_id=trace.agent_id,
286
+ team_id=trace.team_id,
287
+ workflow_id=trace.workflow_id,
288
+ created_at=trace.created_at,
289
+ tree=span_tree,
290
+ )
291
+
292
+ @staticmethod
293
+ def _build_span_tree(
294
+ spans: List[Any],
295
+ total_input_tokens: int,
296
+ total_output_tokens: int,
297
+ trace_start_time: Optional[datetime] = None,
298
+ trace_end_time: Optional[datetime] = None,
299
+ trace_duration_ms: Optional[int] = None,
300
+ ) -> List[TraceNode]:
301
+ """Build hierarchical tree from flat list of spans
302
+
303
+ Args:
304
+ spans: List of span objects
305
+ total_input_tokens: Total input tokens across all spans
306
+ total_output_tokens: Total output tokens across all spans
307
+ trace_start_time: Corrected start time from trace aggregation
308
+ trace_end_time: Corrected end time from trace aggregation
309
+ trace_duration_ms: Corrected duration from trace aggregation
310
+ """
311
+ if not spans:
312
+ return []
313
+
314
+ # Create a map of parent_id -> list of spans
315
+ spans_map: Dict[Optional[str], List[Any]] = {}
316
+ for span in spans:
317
+ parent_id = span.parent_span_id
318
+ if parent_id not in spans_map:
319
+ spans_map[parent_id] = []
320
+ spans_map[parent_id].append(span)
321
+
322
+ # Extract step_types list from workflow root span for index-based matching
323
+ step_types_list: List[str] = []
324
+ root_spans = spans_map.get(None, [])
325
+ for root_span in root_spans:
326
+ span_kind = root_span.attributes.get("openinference.span.kind", "")
327
+ if span_kind == "CHAIN":
328
+ step_types = root_span.attributes.get("agno.workflow.step_types", [])
329
+ if step_types:
330
+ step_types_list = list(step_types)
331
+ break # Use first workflow root span's step_types
332
+
333
+ # Recursive function to build tree for a span
334
+ # step_index is used to track position within direct children of root (workflow steps)
335
+ def build_node(span: Any, is_root: bool = False, step_index: Optional[int] = None) -> TraceNode:
336
+ span_id = span.span_id
337
+ children_spans = spans_map.get(span_id, [])
338
+
339
+ # Sort children spans by start time
340
+ if children_spans:
341
+ children_spans.sort(key=lambda s: s.start_time)
342
+
343
+ # Recursively build spans
344
+ # For root span's direct children (workflow steps), pass the index
345
+ children_nodes: Optional[List[TraceNode]] = None
346
+ if is_root and step_types_list:
347
+ children_nodes = []
348
+ for idx, child in enumerate(children_spans):
349
+ children_nodes.append(build_node(child, step_index=idx))
350
+ elif children_spans:
351
+ children_nodes = [build_node(child) for child in children_spans]
352
+
353
+ # For root span, create custom metadata with token totals
354
+ if is_root:
355
+ # Build simplified metadata for root with token totals
356
+ root_metadata: Dict[str, Any] = {}
357
+ if total_input_tokens > 0:
358
+ root_metadata["total_input_tokens"] = total_input_tokens
359
+ if total_output_tokens > 0:
360
+ root_metadata["total_output_tokens"] = total_output_tokens
361
+
362
+ # Use trace-level timing if available
363
+ start_time = trace_start_time if trace_start_time else span.start_time
364
+ end_time = trace_end_time if trace_end_time else span.end_time
365
+ duration_ms = trace_duration_ms if trace_duration_ms is not None else span.duration_ms
366
+
367
+ # Derive the correct span type (AGENT, TEAM, WORKFLOW, etc.)
368
+ span_type = _derive_span_type(span)
369
+ span_kind = span.attributes.get("openinference.span.kind", "UNKNOWN")
370
+
371
+ # Add workflow-specific metadata for CHAIN/WORKFLOW spans
372
+ if span_kind == "CHAIN":
373
+ if workflow_description := span.attributes.get("agno.workflow.description"):
374
+ root_metadata["description"] = workflow_description
375
+ if steps_count := span.attributes.get("agno.workflow.steps_count"):
376
+ root_metadata["steps_count"] = steps_count
377
+ if steps := span.attributes.get("agno.workflow.steps"):
378
+ root_metadata["steps"] = steps
379
+ if step_types := span.attributes.get("agno.workflow.step_types"):
380
+ root_metadata["step_types"] = step_types
381
+
382
+ # Use datetime objects directly (Pydantic will auto-serialize to ISO 8601)
383
+ # Skip input/output/error for root span (already at top level of TraceDetail)
384
+
385
+ return TraceNode(
386
+ id=span.span_id,
387
+ name=span.name,
388
+ type=span_type,
389
+ duration=format_duration_ms(duration_ms),
390
+ start_time=start_time,
391
+ end_time=end_time,
392
+ status=span.status_code,
393
+ input=None, # Skip for root span (already at TraceDetail level)
394
+ output=None, # Skip for root span (already at TraceDetail level)
395
+ error=None, # Skip for root span (already at TraceDetail level)
396
+ spans=children_nodes if children_nodes else None,
397
+ metadata=root_metadata if root_metadata else None,
398
+ extra_data=None,
399
+ )
400
+ else:
401
+ # Create node from span
402
+ node = TraceNode.from_span(span, spans=children_nodes)
403
+
404
+ # For workflow step spans (direct children of root), assign step_type by index
405
+ if step_index is not None and step_types_list and step_index < len(step_types_list):
406
+ node.step_type = step_types_list[step_index]
407
+
408
+ return node
409
+
410
+ # Sort root spans by start time
411
+ root_spans.sort(key=lambda s: s.start_time)
412
+
413
+ # Build tree starting from roots
414
+ return [build_node(root, is_root=True) for root in root_spans]