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.
- agno/agent/agent.py +5540 -2273
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +689 -6
- agno/db/dynamo/dynamo.py +933 -37
- agno/db/dynamo/schemas.py +174 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +831 -9
- agno/db/firestore/schemas.py +51 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +660 -12
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +287 -14
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +590 -14
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +43 -13
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2760 -0
- agno/db/mongo/mongo.py +879 -11
- agno/db/mongo/schemas.py +42 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2912 -0
- agno/db/mysql/mysql.py +946 -68
- agno/db/mysql/schemas.py +72 -10
- agno/db/mysql/utils.py +198 -7
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2579 -0
- agno/db/postgres/postgres.py +942 -57
- agno/db/postgres/schemas.py +81 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +671 -7
- agno/db/redis/schemas.py +50 -0
- agno/db/redis/utils.py +65 -7
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +17 -2
- agno/db/singlestore/schemas.py +63 -0
- agno/db/singlestore/singlestore.py +949 -83
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2911 -0
- agno/db/sqlite/schemas.py +62 -0
- agno/db/sqlite/sqlite.py +965 -46
- agno/db/sqlite/utils.py +169 -8
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +334 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1908 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +2 -0
- agno/eval/__init__.py +10 -0
- agno/eval/accuracy.py +75 -55
- agno/eval/agent_as_judge.py +861 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +16 -7
- agno/eval/reliability.py +28 -16
- agno/eval/utils.py +35 -17
- agno/exceptions.py +27 -2
- agno/filters.py +354 -0
- agno/guardrails/prompt_injection.py +1 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +1 -1
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/semantic.py +9 -4
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +8 -0
- agno/knowledge/embedder/openai.py +8 -8
- agno/knowledge/embedder/sentence_transformer.py +6 -2
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/knowledge.py +1618 -318
- agno/knowledge/reader/base.py +6 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
- agno/knowledge/reader/json_reader.py +5 -4
- agno/knowledge/reader/markdown_reader.py +8 -8
- agno/knowledge/reader/pdf_reader.py +17 -19
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +32 -3
- agno/knowledge/reader/s3_reader.py +3 -3
- agno/knowledge/reader/tavily_reader.py +193 -0
- agno/knowledge/reader/text_reader.py +22 -10
- agno/knowledge/reader/web_search_reader.py +1 -48
- agno/knowledge/reader/website_reader.py +10 -10
- agno/knowledge/reader/wikipedia_reader.py +33 -1
- agno/knowledge/types.py +1 -0
- agno/knowledge/utils.py +72 -7
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +544 -83
- 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 +515 -40
- agno/models/aws/bedrock.py +102 -21
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +41 -19
- agno/models/azure/openai_chat.py +39 -8
- agno/models/base.py +1249 -525
- agno/models/cerebras/cerebras.py +91 -21
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +40 -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 +877 -80
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +51 -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 +44 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +28 -5
- agno/models/meta/llama.py +47 -14
- agno/models/meta/llama_openai.py +22 -17
- agno/models/mistral/mistral.py +8 -4
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/chat.py +24 -8
- agno/models/openai/chat.py +104 -29
- agno/models/openai/responses.py +101 -81
- agno/models/openrouter/openrouter.py +60 -3
- agno/models/perplexity/perplexity.py +17 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +24 -4
- agno/models/response.py +73 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +190 -0
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +549 -152
- agno/os/auth.py +190 -3
- agno/os/config.py +23 -0
- agno/os/interfaces/a2a/router.py +8 -11
- agno/os/interfaces/a2a/utils.py +1 -1
- agno/os/interfaces/agui/router.py +18 -3
- agno/os/interfaces/agui/utils.py +152 -39
- agno/os/interfaces/slack/router.py +55 -37
- agno/os/interfaces/slack/slack.py +9 -1
- agno/os/interfaces/whatsapp/router.py +0 -1
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/mcp.py +110 -52
- agno/os/middleware/__init__.py +2 -0
- agno/os/middleware/jwt.py +676 -112
- agno/os/router.py +40 -1478
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +599 -0
- agno/os/routers/agents/schema.py +261 -0
- agno/os/routers/evals/evals.py +96 -39
- agno/os/routers/evals/schemas.py +65 -33
- agno/os/routers/evals/utils.py +80 -10
- agno/os/routers/health.py +10 -4
- agno/os/routers/knowledge/knowledge.py +196 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +279 -52
- agno/os/routers/memory/schemas.py +46 -17
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +462 -34
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +512 -0
- agno/os/routers/teams/schema.py +257 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +499 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +624 -0
- agno/os/routers/workflows/schema.py +75 -0
- agno/os/schema.py +256 -693
- agno/os/scopes.py +469 -0
- agno/os/utils.py +514 -36
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/openai.py +5 -0
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +155 -32
- agno/run/base.py +55 -3
- agno/run/requirement.py +181 -0
- agno/run/team.py +125 -38
- agno/run/workflow.py +72 -18
- agno/session/agent.py +102 -89
- agno/session/summary.py +56 -15
- agno/session/team.py +164 -90
- agno/session/workflow.py +405 -40
- agno/table.py +10 -0
- agno/team/team.py +3974 -1903
- agno/tools/dalle.py +2 -4
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +16 -10
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +193 -38
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +271 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +3 -3
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +76 -36
- agno/tools/redshift.py +406 -0
- agno/tools/scrapegraph.py +1 -1
- agno/tools/shopify.py +1519 -0
- agno/tools/slack.py +18 -3
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +146 -0
- agno/tools/toolkit.py +25 -0
- agno/tools/workflow.py +8 -1
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +157 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +111 -0
- agno/utils/agent.py +938 -0
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +151 -3
- agno/utils/gemini.py +15 -5
- agno/utils/hooks.py +118 -4
- agno/utils/http.py +113 -2
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +187 -1
- agno/utils/merge_dict.py +3 -3
- agno/utils/message.py +60 -0
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +49 -14
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/print_response/agent.py +109 -16
- agno/utils/print_response/team.py +223 -30
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/streamlit.py +1 -1
- agno/utils/team.py +98 -9
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +39 -7
- agno/vectordb/cassandra/cassandra.py +21 -5
- agno/vectordb/chroma/chromadb.py +43 -12
- agno/vectordb/clickhouse/clickhousedb.py +21 -5
- agno/vectordb/couchbase/couchbase.py +29 -5
- agno/vectordb/lancedb/lance_db.py +92 -181
- agno/vectordb/langchaindb/langchaindb.py +24 -4
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/llamaindexdb.py +25 -5
- agno/vectordb/milvus/milvus.py +50 -37
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +36 -30
- agno/vectordb/pgvector/pgvector.py +201 -77
- agno/vectordb/pineconedb/pineconedb.py +41 -23
- agno/vectordb/qdrant/qdrant.py +67 -54
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +682 -0
- agno/vectordb/singlestore/singlestore.py +50 -29
- agno/vectordb/surrealdb/surrealdb.py +31 -41
- agno/vectordb/upstashdb/upstashdb.py +34 -6
- agno/vectordb/weaviate/weaviate.py +53 -14
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +120 -18
- agno/workflow/loop.py +77 -10
- agno/workflow/parallel.py +231 -143
- agno/workflow/router.py +118 -17
- agno/workflow/step.py +609 -170
- agno/workflow/steps.py +73 -6
- agno/workflow/types.py +96 -21
- agno/workflow/workflow.py +2039 -262
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
- agno-2.3.13.dist-info/RECORD +613 -0
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -679
- agno/tools/memori.py +0 -339
- agno-2.1.2.dist-info/RECORD +0 -543
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, Union
|
|
2
|
+
from uuid import uuid4
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from agno.agent import Agent
|
|
7
|
+
from agno.os.routers.agents.schema import AgentResponse
|
|
8
|
+
from agno.os.schema import ModelResponse
|
|
9
|
+
from agno.os.utils import (
|
|
10
|
+
format_team_tools,
|
|
11
|
+
get_team_input_schema_dict,
|
|
12
|
+
)
|
|
13
|
+
from agno.run import RunContext
|
|
14
|
+
from agno.run.team import TeamRunOutput
|
|
15
|
+
from agno.session import TeamSession
|
|
16
|
+
from agno.team.team import Team
|
|
17
|
+
from agno.utils.agent import aexecute_instructions, aexecute_system_message
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TeamResponse(BaseModel):
|
|
21
|
+
id: Optional[str] = None
|
|
22
|
+
name: Optional[str] = None
|
|
23
|
+
db_id: Optional[str] = None
|
|
24
|
+
description: Optional[str] = None
|
|
25
|
+
model: Optional[ModelResponse] = None
|
|
26
|
+
tools: Optional[Dict[str, Any]] = None
|
|
27
|
+
sessions: Optional[Dict[str, Any]] = None
|
|
28
|
+
knowledge: Optional[Dict[str, Any]] = None
|
|
29
|
+
memory: Optional[Dict[str, Any]] = None
|
|
30
|
+
reasoning: Optional[Dict[str, Any]] = None
|
|
31
|
+
default_tools: Optional[Dict[str, Any]] = None
|
|
32
|
+
system_message: Optional[Dict[str, Any]] = None
|
|
33
|
+
response_settings: Optional[Dict[str, Any]] = None
|
|
34
|
+
introduction: Optional[str] = None
|
|
35
|
+
streaming: Optional[Dict[str, Any]] = None
|
|
36
|
+
members: Optional[List[Union[AgentResponse, "TeamResponse"]]] = None
|
|
37
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
38
|
+
input_schema: Optional[Dict[str, Any]] = None
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
async def from_team(cls, team: Team) -> "TeamResponse":
|
|
42
|
+
def filter_meaningful_config(d: Dict[str, Any], defaults: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
43
|
+
"""Filter out fields that match their default values, keeping only meaningful user configurations"""
|
|
44
|
+
filtered = {}
|
|
45
|
+
for key, value in d.items():
|
|
46
|
+
if value is None:
|
|
47
|
+
continue
|
|
48
|
+
# Skip if value matches the default exactly
|
|
49
|
+
if key in defaults and value == defaults[key]:
|
|
50
|
+
continue
|
|
51
|
+
# Keep non-default values
|
|
52
|
+
filtered[key] = value
|
|
53
|
+
return filtered if filtered else None
|
|
54
|
+
|
|
55
|
+
# Define default values for filtering (similar to agent defaults)
|
|
56
|
+
team_defaults = {
|
|
57
|
+
# Sessions defaults
|
|
58
|
+
"add_history_to_context": False,
|
|
59
|
+
"num_history_runs": 3,
|
|
60
|
+
"enable_session_summaries": False,
|
|
61
|
+
"cache_session": False,
|
|
62
|
+
# Knowledge defaults
|
|
63
|
+
"add_references": False,
|
|
64
|
+
"references_format": "json",
|
|
65
|
+
"enable_agentic_knowledge_filters": False,
|
|
66
|
+
# Memory defaults
|
|
67
|
+
"enable_agentic_memory": False,
|
|
68
|
+
"enable_user_memories": False,
|
|
69
|
+
# Reasoning defaults
|
|
70
|
+
"reasoning": False,
|
|
71
|
+
"reasoning_min_steps": 1,
|
|
72
|
+
"reasoning_max_steps": 10,
|
|
73
|
+
# Default tools defaults
|
|
74
|
+
"search_knowledge": True,
|
|
75
|
+
"read_chat_history": False,
|
|
76
|
+
"get_member_information_tool": False,
|
|
77
|
+
# System message defaults
|
|
78
|
+
"system_message_role": "system",
|
|
79
|
+
"markdown": False,
|
|
80
|
+
"add_datetime_to_context": False,
|
|
81
|
+
"add_location_to_context": False,
|
|
82
|
+
"resolve_in_context": True,
|
|
83
|
+
# Response settings defaults
|
|
84
|
+
"parse_response": True,
|
|
85
|
+
"use_json_mode": False,
|
|
86
|
+
# Streaming defaults
|
|
87
|
+
"stream_events": False,
|
|
88
|
+
"stream_intermediate_steps": False,
|
|
89
|
+
"stream_member_events": False,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
run_id = str(uuid4())
|
|
93
|
+
session_id = str(uuid4())
|
|
94
|
+
_tools = team._determine_tools_for_model(
|
|
95
|
+
model=team.model, # type: ignore
|
|
96
|
+
session=TeamSession(session_id=session_id, session_data={}),
|
|
97
|
+
run_response=TeamRunOutput(run_id=run_id),
|
|
98
|
+
run_context=RunContext(run_id=run_id, session_id=session_id, session_state={}),
|
|
99
|
+
async_mode=True,
|
|
100
|
+
team_run_context={},
|
|
101
|
+
check_mcp_tools=False,
|
|
102
|
+
)
|
|
103
|
+
team_tools = _tools
|
|
104
|
+
formatted_tools = format_team_tools(team_tools) if team_tools else None
|
|
105
|
+
|
|
106
|
+
model_name = team.model.name or team.model.__class__.__name__ if team.model else None
|
|
107
|
+
model_provider = team.model.provider or team.model.__class__.__name__ if team.model else ""
|
|
108
|
+
model_id = team.model.id if team.model else None
|
|
109
|
+
|
|
110
|
+
if model_provider and model_id:
|
|
111
|
+
model_provider = f"{model_provider} {model_id}"
|
|
112
|
+
elif model_name and model_id:
|
|
113
|
+
model_provider = f"{model_name} {model_id}"
|
|
114
|
+
elif model_id:
|
|
115
|
+
model_provider = model_id
|
|
116
|
+
|
|
117
|
+
session_table = team.db.session_table_name if team.db else None
|
|
118
|
+
knowledge_table = team.db.knowledge_table_name if team.db and team.knowledge else None
|
|
119
|
+
|
|
120
|
+
tools_info = {
|
|
121
|
+
"tools": formatted_tools,
|
|
122
|
+
"tool_call_limit": team.tool_call_limit,
|
|
123
|
+
"tool_choice": team.tool_choice,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
sessions_info = {
|
|
127
|
+
"session_table": session_table,
|
|
128
|
+
"add_history_to_context": team.add_history_to_context,
|
|
129
|
+
"enable_session_summaries": team.enable_session_summaries,
|
|
130
|
+
"num_history_runs": team.num_history_runs,
|
|
131
|
+
"cache_session": team.cache_session,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
knowledge_info = {
|
|
135
|
+
"knowledge_table": knowledge_table,
|
|
136
|
+
"enable_agentic_knowledge_filters": team.enable_agentic_knowledge_filters,
|
|
137
|
+
"knowledge_filters": team.knowledge_filters,
|
|
138
|
+
"references_format": team.references_format,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
memory_info: Optional[Dict[str, Any]] = None
|
|
142
|
+
if team.memory_manager is not None:
|
|
143
|
+
memory_info = {
|
|
144
|
+
"enable_agentic_memory": team.enable_agentic_memory,
|
|
145
|
+
"enable_user_memories": team.enable_user_memories,
|
|
146
|
+
"metadata": team.metadata,
|
|
147
|
+
"memory_table": team.db.memory_table_name if team.db and team.enable_user_memories else None,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if team.memory_manager.model is not None:
|
|
151
|
+
memory_info["model"] = ModelResponse(
|
|
152
|
+
name=team.memory_manager.model.name,
|
|
153
|
+
model=team.memory_manager.model.id,
|
|
154
|
+
provider=team.memory_manager.model.provider,
|
|
155
|
+
).model_dump()
|
|
156
|
+
|
|
157
|
+
reasoning_info: Dict[str, Any] = {
|
|
158
|
+
"reasoning": team.reasoning,
|
|
159
|
+
"reasoning_agent_id": team.reasoning_agent.id if team.reasoning_agent else None,
|
|
160
|
+
"reasoning_min_steps": team.reasoning_min_steps,
|
|
161
|
+
"reasoning_max_steps": team.reasoning_max_steps,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if team.reasoning_model:
|
|
165
|
+
reasoning_info["reasoning_model"] = ModelResponse(
|
|
166
|
+
name=team.reasoning_model.name,
|
|
167
|
+
model=team.reasoning_model.id,
|
|
168
|
+
provider=team.reasoning_model.provider,
|
|
169
|
+
).model_dump()
|
|
170
|
+
|
|
171
|
+
default_tools_info = {
|
|
172
|
+
"search_knowledge": team.search_knowledge,
|
|
173
|
+
"read_chat_history": team.read_chat_history,
|
|
174
|
+
"get_member_information_tool": team.get_member_information_tool,
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
team_instructions = team.instructions if team.instructions else None
|
|
178
|
+
if team_instructions and callable(team_instructions):
|
|
179
|
+
team_instructions = await aexecute_instructions(instructions=team_instructions, agent=team, team=team)
|
|
180
|
+
|
|
181
|
+
team_system_message = team.system_message if team.system_message else None
|
|
182
|
+
if team_system_message and callable(team_system_message):
|
|
183
|
+
team_system_message = await aexecute_system_message(
|
|
184
|
+
system_message=team_system_message, agent=team, team=team
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
system_message_info = {
|
|
188
|
+
"system_message": team_system_message,
|
|
189
|
+
"system_message_role": team.system_message_role,
|
|
190
|
+
"description": team.description,
|
|
191
|
+
"instructions": team_instructions,
|
|
192
|
+
"expected_output": team.expected_output,
|
|
193
|
+
"additional_context": team.additional_context,
|
|
194
|
+
"markdown": team.markdown,
|
|
195
|
+
"add_datetime_to_context": team.add_datetime_to_context,
|
|
196
|
+
"add_location_to_context": team.add_location_to_context,
|
|
197
|
+
"resolve_in_context": team.resolve_in_context,
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
response_settings_info: Dict[str, Any] = {
|
|
201
|
+
"output_schema_name": team.output_schema.__name__ if team.output_schema else None,
|
|
202
|
+
"parser_model_prompt": team.parser_model_prompt,
|
|
203
|
+
"parse_response": team.parse_response,
|
|
204
|
+
"use_json_mode": team.use_json_mode,
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if team.parser_model:
|
|
208
|
+
response_settings_info["parser_model"] = ModelResponse(
|
|
209
|
+
name=team.parser_model.name,
|
|
210
|
+
model=team.parser_model.id,
|
|
211
|
+
provider=team.parser_model.provider,
|
|
212
|
+
).model_dump()
|
|
213
|
+
|
|
214
|
+
streaming_info = {
|
|
215
|
+
"stream": team.stream,
|
|
216
|
+
"stream_events": team.stream_events,
|
|
217
|
+
"stream_intermediate_steps": team.stream_intermediate_steps,
|
|
218
|
+
"stream_member_events": team.stream_member_events,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
# Build team model only if it has at least one non-null field
|
|
222
|
+
_team_model_data: Dict[str, Any] = {}
|
|
223
|
+
if team.model and team.model.name is not None:
|
|
224
|
+
_team_model_data["name"] = team.model.name
|
|
225
|
+
if team.model and team.model.id is not None:
|
|
226
|
+
_team_model_data["model"] = team.model.id
|
|
227
|
+
if team.model and team.model.provider is not None:
|
|
228
|
+
_team_model_data["provider"] = team.model.provider
|
|
229
|
+
|
|
230
|
+
members: List[Union[AgentResponse, TeamResponse]] = []
|
|
231
|
+
for member in team.members:
|
|
232
|
+
if isinstance(member, Agent):
|
|
233
|
+
agent_response = await AgentResponse.from_agent(member)
|
|
234
|
+
members.append(agent_response)
|
|
235
|
+
if isinstance(member, Team):
|
|
236
|
+
team_response = await TeamResponse.from_team(member)
|
|
237
|
+
members.append(team_response)
|
|
238
|
+
|
|
239
|
+
return TeamResponse(
|
|
240
|
+
id=team.id,
|
|
241
|
+
name=team.name,
|
|
242
|
+
db_id=team.db.id if team.db else None,
|
|
243
|
+
model=ModelResponse(**_team_model_data) if _team_model_data else None,
|
|
244
|
+
tools=filter_meaningful_config(tools_info, {}),
|
|
245
|
+
sessions=filter_meaningful_config(sessions_info, team_defaults),
|
|
246
|
+
knowledge=filter_meaningful_config(knowledge_info, team_defaults),
|
|
247
|
+
memory=filter_meaningful_config(memory_info, team_defaults) if memory_info else None,
|
|
248
|
+
reasoning=filter_meaningful_config(reasoning_info, team_defaults),
|
|
249
|
+
default_tools=filter_meaningful_config(default_tools_info, team_defaults),
|
|
250
|
+
system_message=filter_meaningful_config(system_message_info, team_defaults),
|
|
251
|
+
response_settings=filter_meaningful_config(response_settings_info, team_defaults),
|
|
252
|
+
introduction=team.introduction,
|
|
253
|
+
streaming=filter_meaningful_config(streaming_info, team_defaults),
|
|
254
|
+
members=members if members else None,
|
|
255
|
+
metadata=team.metadata,
|
|
256
|
+
input_schema=get_team_input_schema_dict(team),
|
|
257
|
+
)
|
|
@@ -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]
|