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
agno/tracing/schemas.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trace data models for Agno tracing.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import asdict, dataclass
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
from opentelemetry.sdk.trace import ReadableSpan # type: ignore
|
|
10
|
+
from opentelemetry.trace import SpanKind, StatusCode # type: ignore
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class Trace:
|
|
15
|
+
"""Represents a complete trace (one record per trace_id)"""
|
|
16
|
+
|
|
17
|
+
trace_id: str
|
|
18
|
+
name: str # Name from root span
|
|
19
|
+
status: str # Overall status: OK, ERROR, UNSET
|
|
20
|
+
start_time: datetime # Python datetime object
|
|
21
|
+
end_time: datetime # Python datetime object
|
|
22
|
+
duration_ms: int
|
|
23
|
+
total_spans: int
|
|
24
|
+
error_count: int
|
|
25
|
+
|
|
26
|
+
# Context from root span
|
|
27
|
+
run_id: Optional[str]
|
|
28
|
+
session_id: Optional[str]
|
|
29
|
+
user_id: Optional[str]
|
|
30
|
+
agent_id: Optional[str]
|
|
31
|
+
team_id: Optional[str]
|
|
32
|
+
workflow_id: Optional[str]
|
|
33
|
+
|
|
34
|
+
created_at: datetime # Python datetime object
|
|
35
|
+
|
|
36
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
37
|
+
"""Convert Trace to dictionary for database storage (datetime -> ISO string)"""
|
|
38
|
+
data = asdict(self)
|
|
39
|
+
# Convert datetime objects to ISO format strings for database storage
|
|
40
|
+
data["start_time"] = self.start_time.isoformat()
|
|
41
|
+
data["end_time"] = self.end_time.isoformat()
|
|
42
|
+
data["created_at"] = self.created_at.isoformat()
|
|
43
|
+
return data
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Trace":
|
|
47
|
+
"""Create Trace from dictionary (ISO string -> datetime)"""
|
|
48
|
+
# Convert ISO format strings to datetime objects
|
|
49
|
+
start_time = data["start_time"]
|
|
50
|
+
if isinstance(start_time, str):
|
|
51
|
+
start_time = datetime.fromisoformat(start_time.replace("Z", "+00:00"))
|
|
52
|
+
elif isinstance(start_time, int):
|
|
53
|
+
start_time = datetime.fromtimestamp(start_time / 1_000_000_000, tz=timezone.utc)
|
|
54
|
+
|
|
55
|
+
end_time = data["end_time"]
|
|
56
|
+
if isinstance(end_time, str):
|
|
57
|
+
end_time = datetime.fromisoformat(end_time.replace("Z", "+00:00"))
|
|
58
|
+
elif isinstance(end_time, int):
|
|
59
|
+
end_time = datetime.fromtimestamp(end_time / 1_000_000_000, tz=timezone.utc)
|
|
60
|
+
|
|
61
|
+
created_at = data["created_at"]
|
|
62
|
+
if isinstance(created_at, str):
|
|
63
|
+
created_at = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
|
|
64
|
+
elif isinstance(created_at, int):
|
|
65
|
+
created_at = datetime.fromtimestamp(created_at, tz=timezone.utc)
|
|
66
|
+
|
|
67
|
+
return cls(
|
|
68
|
+
trace_id=data["trace_id"],
|
|
69
|
+
name=data["name"],
|
|
70
|
+
status=data["status"],
|
|
71
|
+
start_time=start_time,
|
|
72
|
+
end_time=end_time,
|
|
73
|
+
duration_ms=data["duration_ms"],
|
|
74
|
+
total_spans=data["total_spans"],
|
|
75
|
+
error_count=data["error_count"],
|
|
76
|
+
run_id=data.get("run_id"),
|
|
77
|
+
session_id=data.get("session_id"),
|
|
78
|
+
user_id=data.get("user_id"),
|
|
79
|
+
agent_id=data.get("agent_id"),
|
|
80
|
+
team_id=data.get("team_id"),
|
|
81
|
+
workflow_id=data.get("workflow_id"),
|
|
82
|
+
created_at=created_at,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class Span:
|
|
88
|
+
"""Represents a single span within a trace"""
|
|
89
|
+
|
|
90
|
+
span_id: str
|
|
91
|
+
trace_id: str
|
|
92
|
+
parent_span_id: Optional[str]
|
|
93
|
+
name: str
|
|
94
|
+
span_kind: str
|
|
95
|
+
status_code: str
|
|
96
|
+
status_message: Optional[str]
|
|
97
|
+
start_time: datetime # Python datetime object
|
|
98
|
+
end_time: datetime # Python datetime object
|
|
99
|
+
duration_ms: int
|
|
100
|
+
attributes: Dict[str, Any]
|
|
101
|
+
created_at: datetime # Python datetime object
|
|
102
|
+
|
|
103
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
104
|
+
"""Convert Span to dictionary for database storage (datetime -> ISO string)"""
|
|
105
|
+
data = asdict(self)
|
|
106
|
+
# Convert datetime objects to ISO format strings for database storage
|
|
107
|
+
data["start_time"] = self.start_time.isoformat()
|
|
108
|
+
data["end_time"] = self.end_time.isoformat()
|
|
109
|
+
data["created_at"] = self.created_at.isoformat()
|
|
110
|
+
return data
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Span":
|
|
114
|
+
"""Create Span from dictionary (ISO string -> datetime)"""
|
|
115
|
+
# Convert ISO format strings to datetime objects
|
|
116
|
+
start_time = data["start_time"]
|
|
117
|
+
if isinstance(start_time, str):
|
|
118
|
+
start_time = datetime.fromisoformat(start_time.replace("Z", "+00:00"))
|
|
119
|
+
elif isinstance(start_time, int):
|
|
120
|
+
start_time = datetime.fromtimestamp(start_time / 1_000_000_000, tz=timezone.utc)
|
|
121
|
+
|
|
122
|
+
end_time = data["end_time"]
|
|
123
|
+
if isinstance(end_time, str):
|
|
124
|
+
end_time = datetime.fromisoformat(end_time.replace("Z", "+00:00"))
|
|
125
|
+
elif isinstance(end_time, int):
|
|
126
|
+
end_time = datetime.fromtimestamp(end_time / 1_000_000_000, tz=timezone.utc)
|
|
127
|
+
|
|
128
|
+
created_at = data["created_at"]
|
|
129
|
+
if isinstance(created_at, str):
|
|
130
|
+
created_at = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
|
|
131
|
+
elif isinstance(created_at, int):
|
|
132
|
+
created_at = datetime.fromtimestamp(created_at, tz=timezone.utc)
|
|
133
|
+
|
|
134
|
+
return cls(
|
|
135
|
+
span_id=data["span_id"],
|
|
136
|
+
trace_id=data["trace_id"],
|
|
137
|
+
parent_span_id=data.get("parent_span_id"),
|
|
138
|
+
name=data["name"],
|
|
139
|
+
span_kind=data["span_kind"],
|
|
140
|
+
status_code=data["status_code"],
|
|
141
|
+
status_message=data.get("status_message"),
|
|
142
|
+
start_time=start_time,
|
|
143
|
+
end_time=end_time,
|
|
144
|
+
duration_ms=data["duration_ms"],
|
|
145
|
+
attributes=data.get("attributes", {}),
|
|
146
|
+
created_at=created_at,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def from_otel_span(cls, otel_span: ReadableSpan) -> "Span":
|
|
151
|
+
"""Convert OpenTelemetry ReadableSpan to Span"""
|
|
152
|
+
# Extract span context
|
|
153
|
+
span_context = otel_span.context
|
|
154
|
+
trace_id = format(span_context.trace_id, "032x") if span_context else "0" * 32
|
|
155
|
+
span_id = format(span_context.span_id, "016x") if span_context else "0" * 16
|
|
156
|
+
|
|
157
|
+
# Extract parent span ID if exists
|
|
158
|
+
parent_span_id = None
|
|
159
|
+
if otel_span.parent and otel_span.parent.span_id:
|
|
160
|
+
parent_span_id = format(otel_span.parent.span_id, "016x")
|
|
161
|
+
|
|
162
|
+
# Extract span kind
|
|
163
|
+
span_kind_map = {
|
|
164
|
+
SpanKind.INTERNAL: "INTERNAL",
|
|
165
|
+
SpanKind.SERVER: "SERVER",
|
|
166
|
+
SpanKind.CLIENT: "CLIENT",
|
|
167
|
+
SpanKind.PRODUCER: "PRODUCER",
|
|
168
|
+
SpanKind.CONSUMER: "CONSUMER",
|
|
169
|
+
}
|
|
170
|
+
span_kind = span_kind_map.get(otel_span.kind, "INTERNAL")
|
|
171
|
+
|
|
172
|
+
# Extract status
|
|
173
|
+
status_code_map = {
|
|
174
|
+
StatusCode.UNSET: "UNSET",
|
|
175
|
+
StatusCode.OK: "OK",
|
|
176
|
+
StatusCode.ERROR: "ERROR",
|
|
177
|
+
}
|
|
178
|
+
status_code = status_code_map.get(otel_span.status.status_code, "UNSET")
|
|
179
|
+
status_message = otel_span.status.description
|
|
180
|
+
|
|
181
|
+
# Calculate duration in milliseconds
|
|
182
|
+
start_time_ns = otel_span.start_time or 0
|
|
183
|
+
end_time_ns = otel_span.end_time or start_time_ns
|
|
184
|
+
duration_ms = int((end_time_ns - start_time_ns) / 1_000_000)
|
|
185
|
+
|
|
186
|
+
# Convert nanosecond timestamps to datetime objects
|
|
187
|
+
start_time = datetime.fromtimestamp(start_time_ns / 1_000_000_000, tz=timezone.utc)
|
|
188
|
+
end_time = datetime.fromtimestamp(end_time_ns / 1_000_000_000, tz=timezone.utc)
|
|
189
|
+
|
|
190
|
+
# Convert attributes to dictionary
|
|
191
|
+
attributes: Dict[str, Any] = {}
|
|
192
|
+
if otel_span.attributes:
|
|
193
|
+
for key, value in otel_span.attributes.items():
|
|
194
|
+
# Convert attribute values to JSON-serializable types
|
|
195
|
+
if isinstance(value, (str, int, float, bool, type(None))):
|
|
196
|
+
attributes[key] = value
|
|
197
|
+
elif isinstance(value, (list, tuple)):
|
|
198
|
+
attributes[key] = list(value)
|
|
199
|
+
else:
|
|
200
|
+
attributes[key] = str(value)
|
|
201
|
+
|
|
202
|
+
return cls(
|
|
203
|
+
span_id=span_id,
|
|
204
|
+
trace_id=trace_id,
|
|
205
|
+
parent_span_id=parent_span_id,
|
|
206
|
+
name=otel_span.name,
|
|
207
|
+
span_kind=span_kind,
|
|
208
|
+
status_code=status_code,
|
|
209
|
+
status_message=status_message,
|
|
210
|
+
start_time=start_time,
|
|
211
|
+
end_time=end_time,
|
|
212
|
+
duration_ms=duration_ms,
|
|
213
|
+
attributes=attributes,
|
|
214
|
+
created_at=datetime.now(timezone.utc),
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def create_trace_from_spans(spans: List[Span]) -> Optional[Trace]:
|
|
219
|
+
"""
|
|
220
|
+
Create a Trace object from a list of Span objects with the same trace_id.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
spans: List of Span objects belonging to the same trace
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Trace object with aggregated information, or None if spans list is empty
|
|
227
|
+
"""
|
|
228
|
+
if not spans:
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
# Find root span (no parent)
|
|
232
|
+
root_span = next((s for s in spans if not s.parent_span_id), spans[0])
|
|
233
|
+
|
|
234
|
+
# Calculate aggregated metrics
|
|
235
|
+
trace_id = spans[0].trace_id
|
|
236
|
+
start_time = min(s.start_time for s in spans)
|
|
237
|
+
end_time = max(s.end_time for s in spans)
|
|
238
|
+
duration_ms = int((end_time - start_time).total_seconds() * 1000)
|
|
239
|
+
total_spans = len(spans)
|
|
240
|
+
error_count = sum(1 for s in spans if s.status_code == "ERROR")
|
|
241
|
+
|
|
242
|
+
# Determine overall status (ERROR if any span errored, OK otherwise)
|
|
243
|
+
status = "ERROR" if error_count > 0 else "OK"
|
|
244
|
+
|
|
245
|
+
# Extract context from root span's attributes
|
|
246
|
+
attrs = root_span.attributes
|
|
247
|
+
run_id = attrs.get("run_id") or attrs.get("agno.run.id")
|
|
248
|
+
|
|
249
|
+
session_id = attrs.get("session_id") or attrs.get("agno.session.id") or attrs.get("session.id")
|
|
250
|
+
|
|
251
|
+
user_id = attrs.get("user_id") or attrs.get("agno.user.id") or attrs.get("user.id")
|
|
252
|
+
|
|
253
|
+
# Try to extract agent_id from the span name or attributes
|
|
254
|
+
agent_id = attrs.get("agent_id") or attrs.get("agno.agent.id")
|
|
255
|
+
|
|
256
|
+
team_id = attrs.get("team_id") or attrs.get("agno.team.id")
|
|
257
|
+
|
|
258
|
+
workflow_id = attrs.get("workflow_id") or attrs.get("agno.workflow.id")
|
|
259
|
+
|
|
260
|
+
return Trace(
|
|
261
|
+
trace_id=trace_id,
|
|
262
|
+
name=root_span.name,
|
|
263
|
+
status=status,
|
|
264
|
+
start_time=start_time,
|
|
265
|
+
end_time=end_time,
|
|
266
|
+
duration_ms=duration_ms,
|
|
267
|
+
total_spans=total_spans,
|
|
268
|
+
error_count=error_count,
|
|
269
|
+
run_id=run_id,
|
|
270
|
+
session_id=session_id,
|
|
271
|
+
user_id=user_id,
|
|
272
|
+
agent_id=agent_id,
|
|
273
|
+
team_id=team_id,
|
|
274
|
+
workflow_id=workflow_id,
|
|
275
|
+
created_at=datetime.now(timezone.utc),
|
|
276
|
+
)
|
agno/tracing/setup.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Setup helper functions for configuring Agno tracing.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Union
|
|
6
|
+
|
|
7
|
+
from agno.db.base import AsyncBaseDb, BaseDb
|
|
8
|
+
from agno.remote.base import RemoteDb
|
|
9
|
+
from agno.tracing.exporter import DatabaseSpanExporter
|
|
10
|
+
from agno.utils.log import logger
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from openinference.instrumentation.agno import AgnoInstrumentor # type: ignore
|
|
14
|
+
from opentelemetry import trace as trace_api # type: ignore
|
|
15
|
+
from opentelemetry.sdk.trace import TracerProvider # type: ignore
|
|
16
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor, SpanProcessor # type: ignore
|
|
17
|
+
|
|
18
|
+
OPENTELEMETRY_AVAILABLE = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
OPENTELEMETRY_AVAILABLE = False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def setup_tracing(
|
|
24
|
+
db: Union[BaseDb, AsyncBaseDb, RemoteDb],
|
|
25
|
+
batch_processing: bool = False,
|
|
26
|
+
max_queue_size: int = 2048,
|
|
27
|
+
max_export_batch_size: int = 512,
|
|
28
|
+
schedule_delay_millis: int = 5000,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Set up OpenTelemetry tracing with database export for Agno agents.
|
|
32
|
+
|
|
33
|
+
This function configures automatic tracing for all Agno agents, teams, and workflows.
|
|
34
|
+
Traces are automatically captured for:
|
|
35
|
+
- Agent runs (agent.run, agent.arun)
|
|
36
|
+
- Model calls (model.response)
|
|
37
|
+
- Tool executions
|
|
38
|
+
- Team coordination
|
|
39
|
+
- Workflow steps
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
db: Database instance to store traces (sync or async)
|
|
43
|
+
batch_processing: If True, use BatchSpanProcessor for better performance
|
|
44
|
+
If False, use SimpleSpanProcessor (immediate export)
|
|
45
|
+
max_queue_size: Maximum queue size for batch processor
|
|
46
|
+
max_export_batch_size: Maximum batch size for export
|
|
47
|
+
schedule_delay_millis: Delay in milliseconds between batch exports
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
ImportError: If OpenTelemetry packages are not installed
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
```python
|
|
54
|
+
from agno.db.sqlite import SqliteDb
|
|
55
|
+
from agno.tracing import setup_tracing
|
|
56
|
+
|
|
57
|
+
db = SqliteDb(db_file="tmp/traces.db")
|
|
58
|
+
setup_tracing(db=db)
|
|
59
|
+
|
|
60
|
+
# Now all agents will be automatically traced
|
|
61
|
+
agent = Agent(...)
|
|
62
|
+
agent.run("Hello") # This will be traced automatically
|
|
63
|
+
```
|
|
64
|
+
"""
|
|
65
|
+
if not OPENTELEMETRY_AVAILABLE:
|
|
66
|
+
raise ImportError(
|
|
67
|
+
"OpenTelemetry packages are required for tracing. "
|
|
68
|
+
"Install with: pip install opentelemetry-api opentelemetry-sdk openinference-instrumentation-agno"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Check if tracing is already set up (handles reload scenarios)
|
|
72
|
+
current_provider = trace_api.get_tracer_provider()
|
|
73
|
+
if isinstance(current_provider, TracerProvider):
|
|
74
|
+
# Already configured with a real TracerProvider, skip
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
# Create tracer provider
|
|
79
|
+
tracer_provider = TracerProvider()
|
|
80
|
+
|
|
81
|
+
# Create database exporter
|
|
82
|
+
exporter = DatabaseSpanExporter(db=db)
|
|
83
|
+
|
|
84
|
+
# Configure span processor
|
|
85
|
+
processor: SpanProcessor
|
|
86
|
+
if batch_processing:
|
|
87
|
+
processor = BatchSpanProcessor(
|
|
88
|
+
exporter,
|
|
89
|
+
max_queue_size=max_queue_size,
|
|
90
|
+
max_export_batch_size=max_export_batch_size,
|
|
91
|
+
schedule_delay_millis=schedule_delay_millis,
|
|
92
|
+
)
|
|
93
|
+
logger.debug(
|
|
94
|
+
f"Tracing configured with BatchSpanProcessor "
|
|
95
|
+
f"(queue_size={max_queue_size}, batch_size={max_export_batch_size})"
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
processor = SimpleSpanProcessor(exporter)
|
|
99
|
+
logger.debug("Tracing configured with SimpleSpanProcessor")
|
|
100
|
+
|
|
101
|
+
tracer_provider.add_span_processor(processor)
|
|
102
|
+
|
|
103
|
+
# Set the global tracer provider
|
|
104
|
+
trace_api.set_tracer_provider(tracer_provider)
|
|
105
|
+
|
|
106
|
+
# Instrument Agno with OpenInference
|
|
107
|
+
AgnoInstrumentor().instrument(tracer_provider=tracer_provider)
|
|
108
|
+
|
|
109
|
+
logger.info("Agno tracing successfully set up with database storage")
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.error(f"Failed to set up tracing: {e}", exc_info=True)
|
|
112
|
+
raise
|