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.
- agno/agent/__init__.py +6 -0
- agno/agent/agent.py +5252 -3145
- agno/agent/remote.py +525 -0
- agno/api/api.py +2 -0
- agno/client/__init__.py +3 -0
- agno/client/a2a/__init__.py +10 -0
- agno/client/a2a/client.py +554 -0
- agno/client/a2a/schemas.py +112 -0
- agno/client/a2a/utils.py +369 -0
- agno/client/os.py +2669 -0
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/manager.py +2 -2
- agno/db/base.py +927 -6
- agno/db/dynamo/dynamo.py +788 -2
- agno/db/dynamo/schemas.py +128 -0
- agno/db/dynamo/utils.py +26 -3
- agno/db/firestore/firestore.py +674 -50
- agno/db/firestore/schemas.py +41 -0
- agno/db/firestore/utils.py +25 -10
- agno/db/gcs_json/gcs_json_db.py +506 -3
- agno/db/gcs_json/utils.py +14 -2
- agno/db/in_memory/in_memory_db.py +203 -4
- agno/db/in_memory/utils.py +14 -2
- agno/db/json/json_db.py +498 -2
- agno/db/json/utils.py +14 -2
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/utils.py +19 -0
- agno/db/migrations/v1_to_v2.py +54 -16
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +977 -0
- agno/db/mongo/async_mongo.py +1013 -39
- agno/db/mongo/mongo.py +684 -4
- agno/db/mongo/schemas.py +48 -0
- agno/db/mongo/utils.py +17 -0
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2958 -0
- agno/db/mysql/mysql.py +722 -53
- agno/db/mysql/schemas.py +77 -11
- agno/db/mysql/utils.py +151 -8
- agno/db/postgres/async_postgres.py +1254 -137
- agno/db/postgres/postgres.py +2316 -93
- agno/db/postgres/schemas.py +153 -21
- agno/db/postgres/utils.py +22 -7
- agno/db/redis/redis.py +531 -3
- agno/db/redis/schemas.py +36 -0
- agno/db/redis/utils.py +31 -15
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +20 -9
- agno/db/singlestore/schemas.py +70 -1
- agno/db/singlestore/singlestore.py +737 -74
- agno/db/singlestore/utils.py +13 -3
- agno/db/sqlite/async_sqlite.py +1069 -89
- agno/db/sqlite/schemas.py +133 -1
- agno/db/sqlite/sqlite.py +2203 -165
- agno/db/sqlite/utils.py +21 -11
- agno/db/surrealdb/models.py +25 -0
- agno/db/surrealdb/surrealdb.py +603 -1
- agno/db/utils.py +60 -0
- agno/eval/__init__.py +26 -3
- agno/eval/accuracy.py +25 -12
- agno/eval/agent_as_judge.py +871 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +10 -4
- agno/eval/reliability.py +22 -13
- agno/eval/utils.py +2 -1
- agno/exceptions.py +42 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +13 -2
- agno/knowledge/__init__.py +4 -0
- agno/knowledge/chunking/code.py +90 -0
- agno/knowledge/chunking/document.py +65 -4
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/markdown.py +102 -11
- agno/knowledge/chunking/recursive.py +2 -2
- agno/knowledge/chunking/semantic.py +130 -48
- agno/knowledge/chunking/strategy.py +18 -0
- agno/knowledge/embedder/azure_openai.py +0 -1
- agno/knowledge/embedder/google.py +1 -1
- agno/knowledge/embedder/mistral.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/openai.py +16 -12
- agno/knowledge/filesystem.py +412 -0
- agno/knowledge/knowledge.py +4261 -1199
- agno/knowledge/protocol.py +134 -0
- agno/knowledge/reader/arxiv_reader.py +3 -2
- agno/knowledge/reader/base.py +9 -7
- agno/knowledge/reader/csv_reader.py +91 -42
- agno/knowledge/reader/docx_reader.py +9 -10
- agno/knowledge/reader/excel_reader.py +225 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
- agno/knowledge/reader/firecrawl_reader.py +3 -2
- agno/knowledge/reader/json_reader.py +16 -22
- agno/knowledge/reader/markdown_reader.py +15 -14
- agno/knowledge/reader/pdf_reader.py +33 -28
- agno/knowledge/reader/pptx_reader.py +9 -10
- agno/knowledge/reader/reader_factory.py +135 -1
- agno/knowledge/reader/s3_reader.py +8 -16
- agno/knowledge/reader/tavily_reader.py +3 -3
- agno/knowledge/reader/text_reader.py +15 -14
- agno/knowledge/reader/utils/__init__.py +17 -0
- agno/knowledge/reader/utils/spreadsheet.py +114 -0
- agno/knowledge/reader/web_search_reader.py +8 -65
- agno/knowledge/reader/website_reader.py +16 -13
- agno/knowledge/reader/wikipedia_reader.py +36 -3
- agno/knowledge/reader/youtube_reader.py +3 -2
- agno/knowledge/remote_content/__init__.py +33 -0
- agno/knowledge/remote_content/config.py +266 -0
- agno/knowledge/remote_content/remote_content.py +105 -17
- agno/knowledge/utils.py +76 -22
- agno/learn/__init__.py +71 -0
- agno/learn/config.py +463 -0
- agno/learn/curate.py +185 -0
- agno/learn/machine.py +725 -0
- agno/learn/schemas.py +1114 -0
- agno/learn/stores/__init__.py +38 -0
- agno/learn/stores/decision_log.py +1156 -0
- agno/learn/stores/entity_memory.py +3275 -0
- agno/learn/stores/learned_knowledge.py +1583 -0
- agno/learn/stores/protocol.py +117 -0
- agno/learn/stores/session_context.py +1217 -0
- agno/learn/stores/user_memory.py +1495 -0
- agno/learn/stores/user_profile.py +1220 -0
- agno/learn/utils.py +209 -0
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +223 -8
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/aimlapi.py +17 -0
- agno/models/anthropic/claude.py +434 -59
- agno/models/aws/bedrock.py +121 -20
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +10 -6
- agno/models/azure/openai_chat.py +33 -10
- agno/models/base.py +1162 -561
- agno/models/cerebras/cerebras.py +120 -24
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +65 -6
- agno/models/cometapi/cometapi.py +18 -1
- agno/models/dashscope/dashscope.py +2 -3
- agno/models/deepinfra/deepinfra.py +18 -1
- agno/models/deepseek/deepseek.py +69 -3
- agno/models/fireworks/fireworks.py +18 -1
- agno/models/google/gemini.py +959 -89
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +48 -18
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/internlm/internlm.py +18 -1
- agno/models/langdb/langdb.py +13 -1
- agno/models/litellm/chat.py +88 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +24 -5
- agno/models/meta/llama.py +40 -13
- agno/models/meta/llama_openai.py +22 -21
- agno/models/metrics.py +12 -0
- agno/models/mistral/mistral.py +8 -4
- agno/models/n1n/__init__.py +3 -0
- agno/models/n1n/n1n.py +57 -0
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/__init__.py +2 -0
- agno/models/ollama/chat.py +17 -6
- agno/models/ollama/responses.py +100 -0
- agno/models/openai/__init__.py +2 -0
- agno/models/openai/chat.py +117 -26
- agno/models/openai/open_responses.py +46 -0
- agno/models/openai/responses.py +110 -32
- agno/models/openrouter/__init__.py +2 -0
- agno/models/openrouter/openrouter.py +67 -2
- agno/models/openrouter/responses.py +146 -0
- agno/models/perplexity/perplexity.py +19 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +19 -2
- agno/models/response.py +20 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/claude.py +124 -4
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +467 -137
- agno/os/auth.py +253 -5
- agno/os/config.py +22 -0
- agno/os/interfaces/a2a/a2a.py +7 -6
- agno/os/interfaces/a2a/router.py +635 -26
- agno/os/interfaces/a2a/utils.py +32 -33
- agno/os/interfaces/agui/agui.py +5 -3
- agno/os/interfaces/agui/router.py +26 -16
- agno/os/interfaces/agui/utils.py +97 -57
- agno/os/interfaces/base.py +7 -7
- agno/os/interfaces/slack/router.py +16 -7
- agno/os/interfaces/slack/slack.py +7 -7
- agno/os/interfaces/whatsapp/router.py +35 -7
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/interfaces/whatsapp/whatsapp.py +11 -8
- agno/os/managers.py +326 -0
- agno/os/mcp.py +652 -79
- agno/os/middleware/__init__.py +4 -0
- agno/os/middleware/jwt.py +718 -115
- agno/os/middleware/trailing_slash.py +27 -0
- agno/os/router.py +105 -1558
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +655 -0
- agno/os/routers/agents/schema.py +288 -0
- agno/os/routers/components/__init__.py +3 -0
- agno/os/routers/components/components.py +475 -0
- agno/os/routers/database.py +155 -0
- agno/os/routers/evals/evals.py +111 -18
- agno/os/routers/evals/schemas.py +38 -5
- agno/os/routers/evals/utils.py +80 -11
- agno/os/routers/health.py +3 -3
- agno/os/routers/knowledge/knowledge.py +284 -35
- agno/os/routers/knowledge/schemas.py +14 -2
- agno/os/routers/memory/memory.py +274 -11
- agno/os/routers/memory/schemas.py +44 -3
- agno/os/routers/metrics/metrics.py +30 -15
- agno/os/routers/metrics/schemas.py +10 -6
- agno/os/routers/registry/__init__.py +3 -0
- agno/os/routers/registry/registry.py +337 -0
- agno/os/routers/session/session.py +143 -14
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +550 -0
- agno/os/routers/teams/schema.py +280 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +549 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +757 -0
- agno/os/routers/workflows/schema.py +139 -0
- agno/os/schema.py +157 -584
- agno/os/scopes.py +469 -0
- agno/os/settings.py +3 -0
- agno/os/utils.py +574 -185
- agno/reasoning/anthropic.py +85 -1
- agno/reasoning/azure_ai_foundry.py +93 -1
- agno/reasoning/deepseek.py +102 -2
- agno/reasoning/default.py +6 -7
- agno/reasoning/gemini.py +87 -3
- agno/reasoning/groq.py +109 -2
- agno/reasoning/helpers.py +6 -7
- agno/reasoning/manager.py +1238 -0
- agno/reasoning/ollama.py +93 -1
- agno/reasoning/openai.py +115 -1
- agno/reasoning/vertexai.py +85 -1
- agno/registry/__init__.py +3 -0
- agno/registry/registry.py +68 -0
- agno/remote/__init__.py +3 -0
- agno/remote/base.py +581 -0
- agno/run/__init__.py +2 -4
- agno/run/agent.py +134 -19
- agno/run/base.py +49 -1
- agno/run/cancel.py +65 -52
- agno/run/cancellation_management/__init__.py +9 -0
- agno/run/cancellation_management/base.py +78 -0
- agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
- agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
- agno/run/requirement.py +181 -0
- agno/run/team.py +111 -19
- agno/run/workflow.py +2 -1
- agno/session/agent.py +57 -92
- agno/session/summary.py +1 -1
- agno/session/team.py +62 -115
- agno/session/workflow.py +353 -57
- agno/skills/__init__.py +17 -0
- agno/skills/agent_skills.py +377 -0
- agno/skills/errors.py +32 -0
- agno/skills/loaders/__init__.py +4 -0
- agno/skills/loaders/base.py +27 -0
- agno/skills/loaders/local.py +216 -0
- agno/skills/skill.py +65 -0
- agno/skills/utils.py +107 -0
- agno/skills/validator.py +277 -0
- agno/table.py +10 -0
- agno/team/__init__.py +5 -1
- agno/team/remote.py +447 -0
- agno/team/team.py +3769 -2202
- agno/tools/brandfetch.py +27 -18
- agno/tools/browserbase.py +225 -16
- agno/tools/crawl4ai.py +3 -0
- agno/tools/duckduckgo.py +25 -71
- agno/tools/exa.py +0 -21
- agno/tools/file.py +14 -13
- agno/tools/file_generation.py +12 -6
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +94 -113
- agno/tools/google_bigquery.py +11 -2
- agno/tools/google_drive.py +4 -3
- agno/tools/knowledge.py +9 -4
- agno/tools/mcp/mcp.py +301 -18
- agno/tools/mcp/multi_mcp.py +269 -14
- agno/tools/mem0.py +11 -10
- agno/tools/memory.py +47 -46
- agno/tools/mlx_transcribe.py +10 -7
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/parallel.py +0 -7
- agno/tools/postgres.py +76 -36
- agno/tools/python.py +14 -6
- agno/tools/reasoning.py +30 -23
- agno/tools/redshift.py +406 -0
- agno/tools/shopify.py +1519 -0
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +4 -1
- agno/tools/toolkit.py +253 -18
- agno/tools/websearch.py +93 -0
- agno/tools/website.py +1 -1
- agno/tools/wikipedia.py +1 -1
- agno/tools/workflow.py +56 -48
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +161 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +112 -0
- agno/utils/agent.py +251 -10
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +264 -7
- agno/utils/hooks.py +111 -3
- agno/utils/http.py +161 -2
- agno/utils/mcp.py +49 -8
- agno/utils/media.py +22 -1
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +20 -5
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/os.py +0 -0
- agno/utils/print_response/agent.py +99 -16
- agno/utils/print_response/team.py +223 -24
- agno/utils/print_response/workflow.py +0 -2
- agno/utils/prompts.py +8 -6
- agno/utils/remote.py +23 -0
- agno/utils/response.py +1 -13
- agno/utils/string.py +91 -2
- agno/utils/team.py +62 -12
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +15 -2
- agno/vectordb/cassandra/cassandra.py +1 -1
- agno/vectordb/chroma/__init__.py +2 -1
- agno/vectordb/chroma/chromadb.py +468 -23
- agno/vectordb/clickhouse/clickhousedb.py +1 -1
- agno/vectordb/couchbase/couchbase.py +6 -2
- agno/vectordb/lancedb/lance_db.py +7 -38
- agno/vectordb/lightrag/lightrag.py +7 -6
- agno/vectordb/milvus/milvus.py +118 -84
- agno/vectordb/mongodb/__init__.py +2 -1
- agno/vectordb/mongodb/mongodb.py +14 -31
- agno/vectordb/pgvector/pgvector.py +120 -66
- agno/vectordb/pineconedb/pineconedb.py +2 -19
- agno/vectordb/qdrant/__init__.py +2 -1
- agno/vectordb/qdrant/qdrant.py +33 -56
- agno/vectordb/redis/__init__.py +2 -1
- agno/vectordb/redis/redisdb.py +19 -31
- agno/vectordb/singlestore/singlestore.py +17 -9
- agno/vectordb/surrealdb/surrealdb.py +2 -38
- agno/vectordb/weaviate/__init__.py +2 -1
- agno/vectordb/weaviate/weaviate.py +7 -3
- agno/workflow/__init__.py +5 -1
- agno/workflow/agent.py +2 -2
- agno/workflow/condition.py +12 -10
- agno/workflow/loop.py +28 -9
- agno/workflow/parallel.py +21 -13
- agno/workflow/remote.py +362 -0
- agno/workflow/router.py +12 -9
- agno/workflow/step.py +261 -36
- agno/workflow/steps.py +12 -8
- agno/workflow/types.py +40 -77
- agno/workflow/workflow.py +939 -213
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
- agno-2.4.3.dist-info/RECORD +677 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
- agno/tools/googlesearch.py +0 -98
- agno/tools/memori.py +0 -339
- agno-2.2.13.dist-info/RECORD +0 -575
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
- {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]
|