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
agno/db/mysql/schemas.py
CHANGED
|
@@ -39,6 +39,8 @@ USER_MEMORY_TABLE_SCHEMA = {
|
|
|
39
39
|
"team_id": {"type": lambda: String(128), "nullable": True},
|
|
40
40
|
"user_id": {"type": lambda: String(128), "nullable": True, "index": True},
|
|
41
41
|
"topics": {"type": JSON, "nullable": True},
|
|
42
|
+
"feedback": {"type": Text, "nullable": True},
|
|
43
|
+
"created_at": {"type": BigInteger, "nullable": False, "index": True},
|
|
42
44
|
"updated_at": {"type": BigInteger, "nullable": True, "index": True},
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -76,20 +78,20 @@ KNOWLEDGE_TABLE_SCHEMA = {
|
|
|
76
78
|
|
|
77
79
|
METRICS_TABLE_SCHEMA = {
|
|
78
80
|
"id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
|
|
79
|
-
"agent_runs_count": {"type": BigInteger, "nullable": False},
|
|
80
|
-
"team_runs_count": {"type": BigInteger, "nullable": False},
|
|
81
|
-
"workflow_runs_count": {"type": BigInteger, "nullable": False},
|
|
82
|
-
"agent_sessions_count": {"type": BigInteger, "nullable": False},
|
|
83
|
-
"team_sessions_count": {"type": BigInteger, "nullable": False},
|
|
84
|
-
"workflow_sessions_count": {"type": BigInteger, "nullable": False},
|
|
85
|
-
"users_count": {"type": BigInteger, "nullable": False},
|
|
86
|
-
"token_metrics": {"type": JSON, "nullable": False},
|
|
87
|
-
"model_metrics": {"type": JSON, "nullable": False},
|
|
81
|
+
"agent_runs_count": {"type": BigInteger, "nullable": False, "default": 0},
|
|
82
|
+
"team_runs_count": {"type": BigInteger, "nullable": False, "default": 0},
|
|
83
|
+
"workflow_runs_count": {"type": BigInteger, "nullable": False, "default": 0},
|
|
84
|
+
"agent_sessions_count": {"type": BigInteger, "nullable": False, "default": 0},
|
|
85
|
+
"team_sessions_count": {"type": BigInteger, "nullable": False, "default": 0},
|
|
86
|
+
"workflow_sessions_count": {"type": BigInteger, "nullable": False, "default": 0},
|
|
87
|
+
"users_count": {"type": BigInteger, "nullable": False, "default": 0},
|
|
88
|
+
"token_metrics": {"type": JSON, "nullable": False, "default": {}},
|
|
89
|
+
"model_metrics": {"type": JSON, "nullable": False, "default": {}},
|
|
88
90
|
"date": {"type": Date, "nullable": False, "index": True},
|
|
89
91
|
"aggregation_period": {"type": lambda: String(20), "nullable": False},
|
|
90
92
|
"created_at": {"type": BigInteger, "nullable": False},
|
|
91
93
|
"updated_at": {"type": BigInteger, "nullable": True},
|
|
92
|
-
"completed": {"type": Boolean, "nullable": False},
|
|
94
|
+
"completed": {"type": Boolean, "nullable": False, "default": False},
|
|
93
95
|
"_unique_constraints": [
|
|
94
96
|
{
|
|
95
97
|
"name": "uq_metrics_date_period",
|
|
@@ -98,6 +100,62 @@ METRICS_TABLE_SCHEMA = {
|
|
|
98
100
|
],
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
CULTURAL_KNOWLEDGE_TABLE_SCHEMA = {
|
|
104
|
+
"id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
|
|
105
|
+
"name": {"type": lambda: String(255), "nullable": False, "index": True},
|
|
106
|
+
"summary": {"type": Text, "nullable": True},
|
|
107
|
+
"content": {"type": JSON, "nullable": True},
|
|
108
|
+
"metadata": {"type": JSON, "nullable": True},
|
|
109
|
+
"input": {"type": Text, "nullable": True},
|
|
110
|
+
"created_at": {"type": BigInteger, "nullable": True},
|
|
111
|
+
"updated_at": {"type": BigInteger, "nullable": True},
|
|
112
|
+
"agent_id": {"type": lambda: String(128), "nullable": True},
|
|
113
|
+
"team_id": {"type": lambda: String(128), "nullable": True},
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
VERSIONS_TABLE_SCHEMA = {
|
|
117
|
+
"table_name": {"type": lambda: String(128), "nullable": False, "primary_key": True},
|
|
118
|
+
"version": {"type": lambda: String(10), "nullable": False},
|
|
119
|
+
"created_at": {"type": lambda: String(128), "nullable": False, "index": True},
|
|
120
|
+
"updated_at": {"type": lambda: String(128), "nullable": True},
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
TRACE_TABLE_SCHEMA = {
|
|
124
|
+
"trace_id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
|
|
125
|
+
"name": {"type": lambda: String(255), "nullable": False},
|
|
126
|
+
"status": {"type": lambda: String(50), "nullable": False, "index": True},
|
|
127
|
+
"start_time": {"type": lambda: String(128), "nullable": False, "index": True}, # ISO 8601 datetime string
|
|
128
|
+
"end_time": {"type": lambda: String(128), "nullable": False}, # ISO 8601 datetime string
|
|
129
|
+
"duration_ms": {"type": BigInteger, "nullable": False},
|
|
130
|
+
"run_id": {"type": lambda: String(128), "nullable": True, "index": True},
|
|
131
|
+
"session_id": {"type": lambda: String(128), "nullable": True, "index": True},
|
|
132
|
+
"user_id": {"type": lambda: String(128), "nullable": True, "index": True},
|
|
133
|
+
"agent_id": {"type": lambda: String(128), "nullable": True, "index": True},
|
|
134
|
+
"team_id": {"type": lambda: String(128), "nullable": True, "index": True},
|
|
135
|
+
"workflow_id": {"type": lambda: String(128), "nullable": True, "index": True},
|
|
136
|
+
"created_at": {"type": lambda: String(128), "nullable": False, "index": True}, # ISO 8601 datetime string
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
SPAN_TABLE_SCHEMA = {
|
|
140
|
+
"span_id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
|
|
141
|
+
"trace_id": {
|
|
142
|
+
"type": lambda: String(128),
|
|
143
|
+
"nullable": False,
|
|
144
|
+
"index": True,
|
|
145
|
+
"foreign_key": "agno_traces.trace_id", # Foreign key to traces table
|
|
146
|
+
},
|
|
147
|
+
"parent_span_id": {"type": lambda: String(128), "nullable": True, "index": True},
|
|
148
|
+
"name": {"type": lambda: String(255), "nullable": False},
|
|
149
|
+
"span_kind": {"type": lambda: String(50), "nullable": False},
|
|
150
|
+
"status_code": {"type": lambda: String(50), "nullable": False},
|
|
151
|
+
"status_message": {"type": Text, "nullable": True},
|
|
152
|
+
"start_time": {"type": lambda: String(128), "nullable": False, "index": True}, # ISO 8601 datetime string
|
|
153
|
+
"end_time": {"type": lambda: String(128), "nullable": False}, # ISO 8601 datetime string
|
|
154
|
+
"duration_ms": {"type": BigInteger, "nullable": False},
|
|
155
|
+
"attributes": {"type": JSON, "nullable": True},
|
|
156
|
+
"created_at": {"type": lambda: String(128), "nullable": False, "index": True}, # ISO 8601 datetime string
|
|
157
|
+
}
|
|
158
|
+
|
|
101
159
|
|
|
102
160
|
def get_table_schema_definition(table_type: str) -> dict[str, Any]:
|
|
103
161
|
"""
|
|
@@ -115,6 +173,10 @@ def get_table_schema_definition(table_type: str) -> dict[str, Any]:
|
|
|
115
173
|
"metrics": METRICS_TABLE_SCHEMA,
|
|
116
174
|
"memories": USER_MEMORY_TABLE_SCHEMA,
|
|
117
175
|
"knowledge": KNOWLEDGE_TABLE_SCHEMA,
|
|
176
|
+
"culture": CULTURAL_KNOWLEDGE_TABLE_SCHEMA,
|
|
177
|
+
"versions": VERSIONS_TABLE_SCHEMA,
|
|
178
|
+
"traces": TRACE_TABLE_SCHEMA,
|
|
179
|
+
"spans": SPAN_TABLE_SCHEMA,
|
|
118
180
|
}
|
|
119
181
|
|
|
120
182
|
schema = schemas.get(table_type, {})
|
agno/db/mysql/utils.py
CHANGED
|
@@ -5,14 +5,14 @@ from datetime import date, datetime, timedelta, timezone
|
|
|
5
5
|
from typing import Any, Dict, List, Optional
|
|
6
6
|
from uuid import uuid4
|
|
7
7
|
|
|
8
|
-
from sqlalchemy import Engine
|
|
9
|
-
|
|
10
8
|
from agno.db.mysql.schemas import get_table_schema_definition
|
|
9
|
+
from agno.db.schemas.culture import CulturalKnowledge
|
|
11
10
|
from agno.utils.log import log_debug, log_error, log_warning
|
|
12
11
|
|
|
13
12
|
try:
|
|
14
|
-
from sqlalchemy import Table
|
|
13
|
+
from sqlalchemy import Engine, Table
|
|
15
14
|
from sqlalchemy.dialects import mysql
|
|
15
|
+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
|
|
16
16
|
from sqlalchemy.inspection import inspect
|
|
17
17
|
from sqlalchemy.orm import Session
|
|
18
18
|
from sqlalchemy.sql.expression import text
|
|
@@ -90,8 +90,10 @@ def is_valid_table(db_engine: Engine, table_name: str, table_type: str, db_schem
|
|
|
90
90
|
Check if the existing table has the expected column names.
|
|
91
91
|
|
|
92
92
|
Args:
|
|
93
|
+
db_engine: Database engine
|
|
93
94
|
table_name (str): Name of the table to validate
|
|
94
|
-
|
|
95
|
+
table_type (str): Type of table (for schema lookup)
|
|
96
|
+
db_schema (str): Database schema name
|
|
95
97
|
|
|
96
98
|
Returns:
|
|
97
99
|
bool: True if table has all expected columns, False otherwise
|
|
@@ -122,6 +124,7 @@ def bulk_upsert_metrics(session: Session, table: Table, metrics_records: list[di
|
|
|
122
124
|
"""Bulk upsert metrics into the database.
|
|
123
125
|
|
|
124
126
|
Args:
|
|
127
|
+
session (Session): The SQLAlchemy session
|
|
125
128
|
table (Table): The table to upsert into.
|
|
126
129
|
metrics_records (list[dict]): The metrics records to upsert.
|
|
127
130
|
|
|
@@ -155,7 +158,10 @@ def bulk_upsert_metrics(session: Session, table: Table, metrics_records: list[di
|
|
|
155
158
|
|
|
156
159
|
for record in metrics_records:
|
|
157
160
|
select_stmt = select(table).where(
|
|
158
|
-
and_(
|
|
161
|
+
and_(
|
|
162
|
+
table.c.date == record["date"],
|
|
163
|
+
table.c.aggregation_period == record["aggregation_period"],
|
|
164
|
+
)
|
|
159
165
|
)
|
|
160
166
|
result = session.execute(select_stmt).fetchone()
|
|
161
167
|
if result:
|
|
@@ -164,6 +170,55 @@ def bulk_upsert_metrics(session: Session, table: Table, metrics_records: list[di
|
|
|
164
170
|
return results # type: ignore
|
|
165
171
|
|
|
166
172
|
|
|
173
|
+
async def abulk_upsert_metrics(session: AsyncSession, table: Table, metrics_records: list[dict]) -> list[dict]:
|
|
174
|
+
"""Async bulk upsert metrics into the database.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
session (AsyncSession): The async SQLAlchemy session
|
|
178
|
+
table (Table): The table to upsert into.
|
|
179
|
+
metrics_records (list[dict]): The metrics records to upsert.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
list[dict]: The upserted metrics records.
|
|
183
|
+
"""
|
|
184
|
+
if not metrics_records:
|
|
185
|
+
return []
|
|
186
|
+
|
|
187
|
+
results = []
|
|
188
|
+
|
|
189
|
+
# MySQL doesn't support returning in the same way as PostgreSQL
|
|
190
|
+
# We'll need to insert/update and then fetch the records
|
|
191
|
+
for record in metrics_records:
|
|
192
|
+
stmt = mysql.insert(table).values(record)
|
|
193
|
+
|
|
194
|
+
# Columns to update in case of conflict
|
|
195
|
+
update_dict = {
|
|
196
|
+
col.name: record.get(col.name)
|
|
197
|
+
for col in table.columns
|
|
198
|
+
if col.name not in ["id", "date", "created_at", "aggregation_period"] and col.name in record
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
stmt = stmt.on_duplicate_key_update(**update_dict)
|
|
202
|
+
await session.execute(stmt)
|
|
203
|
+
|
|
204
|
+
# Fetch the updated records
|
|
205
|
+
from sqlalchemy import and_, select
|
|
206
|
+
|
|
207
|
+
for record in metrics_records:
|
|
208
|
+
select_stmt = select(table).where(
|
|
209
|
+
and_(
|
|
210
|
+
table.c.date == record["date"],
|
|
211
|
+
table.c.aggregation_period == record["aggregation_period"],
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
result = await session.execute(select_stmt)
|
|
215
|
+
fetched_row = result.fetchone()
|
|
216
|
+
if fetched_row:
|
|
217
|
+
results.append(dict(fetched_row._mapping))
|
|
218
|
+
|
|
219
|
+
return results
|
|
220
|
+
|
|
221
|
+
|
|
167
222
|
def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
168
223
|
"""Calculate metrics for the given single date.
|
|
169
224
|
|
|
@@ -204,7 +259,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
204
259
|
all_user_ids = set()
|
|
205
260
|
|
|
206
261
|
for session_type, sessions_count_key, runs_count_key in session_types:
|
|
207
|
-
sessions = sessions_data.get(session_type, [])
|
|
262
|
+
sessions = sessions_data.get(session_type, []) or []
|
|
208
263
|
metrics[sessions_count_key] = len(sessions)
|
|
209
264
|
|
|
210
265
|
for session in sessions:
|
|
@@ -225,7 +280,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
225
280
|
|
|
226
281
|
model_metrics = []
|
|
227
282
|
for model, count in model_counts.items():
|
|
228
|
-
model_id, model_provider = model.
|
|
283
|
+
model_id, model_provider = model.rsplit(":", 1)
|
|
229
284
|
model_metrics.append({"model_id": model_id, "model_provider": model_provider, "count": count})
|
|
230
285
|
|
|
231
286
|
metrics["users_count"] = len(all_user_ids)
|
|
@@ -295,3 +350,139 @@ def get_dates_to_calculate_metrics_for(starting_date: date) -> list[date]:
|
|
|
295
350
|
if days_diff <= 0:
|
|
296
351
|
return []
|
|
297
352
|
return [starting_date + timedelta(days=x) for x in range(days_diff)]
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
# -- Cultural Knowledge util methods --
|
|
356
|
+
def serialize_cultural_knowledge_for_db(
|
|
357
|
+
cultural_knowledge: CulturalKnowledge,
|
|
358
|
+
) -> Dict[str, Any]:
|
|
359
|
+
"""Serialize a CulturalKnowledge object for database storage.
|
|
360
|
+
|
|
361
|
+
Converts the model's separate content, categories, and notes fields
|
|
362
|
+
into a single JSON dict for the database content column.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
Dict[str, Any]: A dictionary with the content field as JSON containing content, categories, and notes.
|
|
369
|
+
"""
|
|
370
|
+
content_dict: Dict[str, Any] = {}
|
|
371
|
+
if cultural_knowledge.content is not None:
|
|
372
|
+
content_dict["content"] = cultural_knowledge.content
|
|
373
|
+
if cultural_knowledge.categories is not None:
|
|
374
|
+
content_dict["categories"] = cultural_knowledge.categories
|
|
375
|
+
if cultural_knowledge.notes is not None:
|
|
376
|
+
content_dict["notes"] = cultural_knowledge.notes
|
|
377
|
+
|
|
378
|
+
return content_dict if content_dict else {}
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
|
|
382
|
+
"""Deserialize a database row to a CulturalKnowledge object.
|
|
383
|
+
|
|
384
|
+
The database stores content as a JSON dict containing content, categories, and notes.
|
|
385
|
+
This method extracts those fields and converts them back to the model format.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
db_row (Dict[str, Any]): The database row as a dictionary.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
CulturalKnowledge: The cultural knowledge object.
|
|
392
|
+
"""
|
|
393
|
+
# Extract content, categories, and notes from the JSON content field
|
|
394
|
+
content_json = db_row.get("content", {}) or {}
|
|
395
|
+
|
|
396
|
+
return CulturalKnowledge.from_dict(
|
|
397
|
+
{
|
|
398
|
+
"id": db_row.get("id"),
|
|
399
|
+
"name": db_row.get("name"),
|
|
400
|
+
"summary": db_row.get("summary"),
|
|
401
|
+
"content": content_json.get("content"),
|
|
402
|
+
"categories": content_json.get("categories"),
|
|
403
|
+
"notes": content_json.get("notes"),
|
|
404
|
+
"metadata": db_row.get("metadata"),
|
|
405
|
+
"input": db_row.get("input"),
|
|
406
|
+
"created_at": db_row.get("created_at"),
|
|
407
|
+
"updated_at": db_row.get("updated_at"),
|
|
408
|
+
"agent_id": db_row.get("agent_id"),
|
|
409
|
+
"team_id": db_row.get("team_id"),
|
|
410
|
+
}
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
# -- Async DB util methods --
|
|
415
|
+
async def acreate_schema(session: AsyncSession, db_schema: str) -> None:
|
|
416
|
+
"""Async version: Create the database schema if it doesn't exist.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
session: The async SQLAlchemy session to use
|
|
420
|
+
db_schema (str): The definition of the database schema to create
|
|
421
|
+
"""
|
|
422
|
+
try:
|
|
423
|
+
log_debug(f"Creating database if not exists: {db_schema}")
|
|
424
|
+
# MySQL uses CREATE DATABASE instead of CREATE SCHEMA
|
|
425
|
+
await session.execute(text(f"CREATE DATABASE IF NOT EXISTS `{db_schema}`;"))
|
|
426
|
+
except Exception as e:
|
|
427
|
+
log_warning(f"Could not create database {db_schema}: {e}")
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
async def ais_table_available(session: AsyncSession, table_name: str, db_schema: str) -> bool:
|
|
431
|
+
"""Async version: Check if a table with the given name exists in the given schema.
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
bool: True if the table exists, False otherwise.
|
|
435
|
+
"""
|
|
436
|
+
try:
|
|
437
|
+
exists_query = text(
|
|
438
|
+
"SELECT 1 FROM information_schema.tables WHERE table_schema = :schema AND table_name = :table"
|
|
439
|
+
)
|
|
440
|
+
result = await session.execute(exists_query, {"schema": db_schema, "table": table_name})
|
|
441
|
+
exists = result.scalar() is not None
|
|
442
|
+
if not exists:
|
|
443
|
+
log_debug(f"Table {db_schema}.{table_name} {'exists' if exists else 'does not exist'}")
|
|
444
|
+
|
|
445
|
+
return exists
|
|
446
|
+
|
|
447
|
+
except Exception as e:
|
|
448
|
+
log_error(f"Error checking if table exists: {e}")
|
|
449
|
+
return False
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
async def ais_valid_table(db_engine: AsyncEngine, table_name: str, table_type: str, db_schema: str) -> bool:
|
|
453
|
+
"""Async version: Check if the existing table has the expected column names.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
db_engine: Async database engine
|
|
457
|
+
table_name (str): Name of the table to validate
|
|
458
|
+
table_type (str): Type of table (for schema lookup)
|
|
459
|
+
db_schema (str): Database schema name
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
bool: True if table has all expected columns, False otherwise
|
|
463
|
+
"""
|
|
464
|
+
try:
|
|
465
|
+
expected_table_schema = get_table_schema_definition(table_type)
|
|
466
|
+
expected_columns = {col_name for col_name in expected_table_schema.keys() if not col_name.startswith("_")}
|
|
467
|
+
|
|
468
|
+
# Get existing columns from the async engine
|
|
469
|
+
async with db_engine.connect() as conn:
|
|
470
|
+
existing_columns = await conn.run_sync(_get_table_columns, table_name, db_schema)
|
|
471
|
+
|
|
472
|
+
# Check if all expected columns exist
|
|
473
|
+
missing_columns = expected_columns - existing_columns
|
|
474
|
+
if missing_columns:
|
|
475
|
+
log_warning(f"Missing columns {missing_columns} in table {db_schema}.{table_name}")
|
|
476
|
+
return False
|
|
477
|
+
|
|
478
|
+
return True
|
|
479
|
+
except Exception as e:
|
|
480
|
+
log_error(f"Error validating table schema for {db_schema}.{table_name}: {e}")
|
|
481
|
+
return False
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def _get_table_columns(connection, table_name: str, db_schema: str) -> set[str]:
|
|
485
|
+
"""Helper function to get table columns using sync inspector."""
|
|
486
|
+
inspector = inspect(connection)
|
|
487
|
+
columns_info = inspector.get_columns(table_name, schema=db_schema)
|
|
488
|
+
return {col["name"] for col in columns_info}
|
agno/db/postgres/__init__.py
CHANGED