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/os/routers/memory/memory.py
CHANGED
|
@@ -8,9 +8,12 @@ from fastapi.routing import APIRouter
|
|
|
8
8
|
|
|
9
9
|
from agno.db.base import AsyncBaseDb, BaseDb
|
|
10
10
|
from agno.db.schemas import UserMemory
|
|
11
|
-
from agno.
|
|
11
|
+
from agno.models.utils import get_model
|
|
12
|
+
from agno.os.auth import get_auth_token_from_request, get_authentication_dependency
|
|
12
13
|
from agno.os.routers.memory.schemas import (
|
|
13
14
|
DeleteMemoriesRequest,
|
|
15
|
+
OptimizeMemoriesRequest,
|
|
16
|
+
OptimizeMemoriesResponse,
|
|
14
17
|
UserMemoryCreateSchema,
|
|
15
18
|
UserMemorySchema,
|
|
16
19
|
UserStatsSchema,
|
|
@@ -27,12 +30,13 @@ from agno.os.schema import (
|
|
|
27
30
|
)
|
|
28
31
|
from agno.os.settings import AgnoAPISettings
|
|
29
32
|
from agno.os.utils import get_db
|
|
33
|
+
from agno.remote.base import RemoteDb
|
|
30
34
|
|
|
31
35
|
logger = logging.getLogger(__name__)
|
|
32
36
|
|
|
33
37
|
|
|
34
38
|
def get_memory_router(
|
|
35
|
-
dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]], settings: AgnoAPISettings = AgnoAPISettings(), **kwargs
|
|
39
|
+
dbs: dict[str, list[Union[BaseDb, AsyncBaseDb, RemoteDb]]], settings: AgnoAPISettings = AgnoAPISettings(), **kwargs
|
|
36
40
|
) -> APIRouter:
|
|
37
41
|
"""Create memory router with comprehensive OpenAPI documentation for user memory management endpoints."""
|
|
38
42
|
router = APIRouter(
|
|
@@ -49,7 +53,7 @@ def get_memory_router(
|
|
|
49
53
|
return attach_routes(router=router, dbs=dbs)
|
|
50
54
|
|
|
51
55
|
|
|
52
|
-
def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]]) -> APIRouter:
|
|
56
|
+
def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBaseDb, RemoteDb]]]) -> APIRouter:
|
|
53
57
|
@router.post(
|
|
54
58
|
"/memories",
|
|
55
59
|
response_model=UserMemorySchema,
|
|
@@ -87,7 +91,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
87
91
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for memory storage"),
|
|
88
92
|
table: Optional[str] = Query(default=None, description="Table to use for memory storage"),
|
|
89
93
|
) -> UserMemorySchema:
|
|
90
|
-
if hasattr(request.state, "user_id"):
|
|
94
|
+
if hasattr(request.state, "user_id") and request.state.user_id is not None:
|
|
91
95
|
user_id = request.state.user_id
|
|
92
96
|
payload.user_id = user_id
|
|
93
97
|
|
|
@@ -96,6 +100,18 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
96
100
|
|
|
97
101
|
db = await get_db(dbs, db_id, table)
|
|
98
102
|
|
|
103
|
+
if isinstance(db, RemoteDb):
|
|
104
|
+
auth_token = get_auth_token_from_request(request)
|
|
105
|
+
headers = {"Authorization": f"Bearer {auth_token}"} if auth_token else None
|
|
106
|
+
return await db.create_memory(
|
|
107
|
+
memory=payload.memory,
|
|
108
|
+
topics=payload.topics or [],
|
|
109
|
+
user_id=payload.user_id,
|
|
110
|
+
db_id=db_id,
|
|
111
|
+
table=table,
|
|
112
|
+
headers=headers,
|
|
113
|
+
)
|
|
114
|
+
|
|
99
115
|
if isinstance(db, AsyncBaseDb):
|
|
100
116
|
db = cast(AsyncBaseDb, db)
|
|
101
117
|
user_memory = await db.upsert_user_memory(
|
|
@@ -136,12 +152,24 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
136
152
|
},
|
|
137
153
|
)
|
|
138
154
|
async def delete_memory(
|
|
155
|
+
request: Request,
|
|
139
156
|
memory_id: str = Path(description="Memory ID to delete"),
|
|
140
157
|
user_id: Optional[str] = Query(default=None, description="User ID to delete memory for"),
|
|
141
158
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for deletion"),
|
|
142
159
|
table: Optional[str] = Query(default=None, description="Table to use for deletion"),
|
|
143
160
|
) -> None:
|
|
144
161
|
db = await get_db(dbs, db_id, table)
|
|
162
|
+
|
|
163
|
+
if isinstance(db, RemoteDb):
|
|
164
|
+
auth_token = get_auth_token_from_request(request)
|
|
165
|
+
headers = {"Authorization": f"Bearer {auth_token}"} if auth_token else None
|
|
166
|
+
return await db.delete_memory(
|
|
167
|
+
memory_id=memory_id,
|
|
168
|
+
user_id=user_id,
|
|
169
|
+
db_id=db_id,
|
|
170
|
+
table=table,
|
|
171
|
+
headers=headers,
|
|
172
|
+
)
|
|
145
173
|
if isinstance(db, AsyncBaseDb):
|
|
146
174
|
db = cast(AsyncBaseDb, db)
|
|
147
175
|
await db.delete_user_memory(memory_id=memory_id, user_id=user_id)
|
|
@@ -164,11 +192,24 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
164
192
|
},
|
|
165
193
|
)
|
|
166
194
|
async def delete_memories(
|
|
195
|
+
http_request: Request,
|
|
167
196
|
request: DeleteMemoriesRequest,
|
|
168
197
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for deletion"),
|
|
169
198
|
table: Optional[str] = Query(default=None, description="Table to use for deletion"),
|
|
170
199
|
) -> None:
|
|
171
200
|
db = await get_db(dbs, db_id, table)
|
|
201
|
+
|
|
202
|
+
if isinstance(db, RemoteDb):
|
|
203
|
+
auth_token = get_auth_token_from_request(http_request)
|
|
204
|
+
headers = {"Authorization": f"Bearer {auth_token}"} if auth_token else None
|
|
205
|
+
return await db.delete_memories(
|
|
206
|
+
memory_ids=request.memory_ids,
|
|
207
|
+
user_id=request.user_id,
|
|
208
|
+
db_id=db_id,
|
|
209
|
+
table=table,
|
|
210
|
+
headers=headers,
|
|
211
|
+
)
|
|
212
|
+
|
|
172
213
|
if isinstance(db, AsyncBaseDb):
|
|
173
214
|
db = cast(AsyncBaseDb, db)
|
|
174
215
|
await db.delete_user_memories(memory_ids=request.memory_ids, user_id=request.user_id)
|
|
@@ -215,8 +256,8 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
215
256
|
team_id: Optional[str] = Query(default=None, description="Filter memories by team ID"),
|
|
216
257
|
topics: Optional[List[str]] = Depends(parse_topics),
|
|
217
258
|
search_content: Optional[str] = Query(default=None, description="Fuzzy search within memory content"),
|
|
218
|
-
limit: Optional[int] = Query(default=20, description="Number of memories to return per page"),
|
|
219
|
-
page: Optional[int] = Query(default=1, description="Page number for pagination"),
|
|
259
|
+
limit: Optional[int] = Query(default=20, description="Number of memories to return per page", ge=1),
|
|
260
|
+
page: Optional[int] = Query(default=1, description="Page number for pagination", ge=0),
|
|
220
261
|
sort_by: Optional[str] = Query(default="updated_at", description="Field to sort memories by"),
|
|
221
262
|
sort_order: Optional[SortOrder] = Query(default="desc", description="Sort order (asc or desc)"),
|
|
222
263
|
db_id: Optional[str] = Query(default=None, description="Database ID to query memories from"),
|
|
@@ -224,9 +265,27 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
224
265
|
) -> PaginatedResponse[UserMemorySchema]:
|
|
225
266
|
db = await get_db(dbs, db_id, table)
|
|
226
267
|
|
|
227
|
-
if hasattr(request.state, "user_id"):
|
|
268
|
+
if hasattr(request.state, "user_id") and request.state.user_id is not None:
|
|
228
269
|
user_id = request.state.user_id
|
|
229
270
|
|
|
271
|
+
if isinstance(db, RemoteDb):
|
|
272
|
+
auth_token = get_auth_token_from_request(request)
|
|
273
|
+
headers = {"Authorization": f"Bearer {auth_token}"} if auth_token else None
|
|
274
|
+
return await db.get_memories(
|
|
275
|
+
user_id=user_id,
|
|
276
|
+
agent_id=agent_id,
|
|
277
|
+
team_id=team_id,
|
|
278
|
+
topics=topics,
|
|
279
|
+
search_content=search_content,
|
|
280
|
+
limit=limit,
|
|
281
|
+
page=page,
|
|
282
|
+
sort_by=sort_by,
|
|
283
|
+
sort_order=sort_order.value if sort_order else "desc",
|
|
284
|
+
db_id=db_id,
|
|
285
|
+
table=table,
|
|
286
|
+
headers=headers,
|
|
287
|
+
)
|
|
288
|
+
|
|
230
289
|
if isinstance(db, AsyncBaseDb):
|
|
231
290
|
db = cast(AsyncBaseDb, db)
|
|
232
291
|
user_memories, total_count = await db.get_user_memories(
|
|
@@ -302,9 +361,20 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
302
361
|
) -> UserMemorySchema:
|
|
303
362
|
db = await get_db(dbs, db_id, table)
|
|
304
363
|
|
|
305
|
-
if hasattr(request.state, "user_id"):
|
|
364
|
+
if hasattr(request.state, "user_id") and request.state.user_id is not None:
|
|
306
365
|
user_id = request.state.user_id
|
|
307
366
|
|
|
367
|
+
if isinstance(db, RemoteDb):
|
|
368
|
+
auth_token = get_auth_token_from_request(request)
|
|
369
|
+
headers = {"Authorization": f"Bearer {auth_token}"} if auth_token else None
|
|
370
|
+
return await db.get_memory(
|
|
371
|
+
memory_id=memory_id,
|
|
372
|
+
user_id=user_id,
|
|
373
|
+
db_id=db_id,
|
|
374
|
+
table=table,
|
|
375
|
+
headers=headers,
|
|
376
|
+
)
|
|
377
|
+
|
|
308
378
|
if isinstance(db, AsyncBaseDb):
|
|
309
379
|
db = cast(AsyncBaseDb, db)
|
|
310
380
|
user_memory = await db.get_user_memory(memory_id=memory_id, user_id=user_id, deserialize=False)
|
|
@@ -347,10 +417,21 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
347
417
|
},
|
|
348
418
|
)
|
|
349
419
|
async def get_topics(
|
|
420
|
+
request: Request,
|
|
350
421
|
db_id: Optional[str] = Query(default=None, description="Database ID to query topics from"),
|
|
351
422
|
table: Optional[str] = Query(default=None, description="Table to query topics from"),
|
|
352
423
|
) -> List[str]:
|
|
353
424
|
db = await get_db(dbs, db_id, table)
|
|
425
|
+
|
|
426
|
+
if isinstance(db, RemoteDb):
|
|
427
|
+
auth_token = get_auth_token_from_request(request)
|
|
428
|
+
headers = {"Authorization": f"Bearer {auth_token}"} if auth_token else None
|
|
429
|
+
return await db.get_memory_topics(
|
|
430
|
+
db_id=db_id,
|
|
431
|
+
table=table,
|
|
432
|
+
headers=headers,
|
|
433
|
+
)
|
|
434
|
+
|
|
354
435
|
if isinstance(db, AsyncBaseDb):
|
|
355
436
|
db = cast(AsyncBaseDb, db)
|
|
356
437
|
return await db.get_all_memory_topics()
|
|
@@ -397,7 +478,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
397
478
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for update"),
|
|
398
479
|
table: Optional[str] = Query(default=None, description="Table to use for update"),
|
|
399
480
|
) -> UserMemorySchema:
|
|
400
|
-
if hasattr(request.state, "user_id"):
|
|
481
|
+
if hasattr(request.state, "user_id") and request.state.user_id is not None:
|
|
401
482
|
user_id = request.state.user_id
|
|
402
483
|
payload.user_id = user_id
|
|
403
484
|
|
|
@@ -406,6 +487,19 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
406
487
|
|
|
407
488
|
db = await get_db(dbs, db_id, table)
|
|
408
489
|
|
|
490
|
+
if isinstance(db, RemoteDb):
|
|
491
|
+
auth_token = get_auth_token_from_request(request)
|
|
492
|
+
headers = {"Authorization": f"Bearer {auth_token}"} if auth_token else None
|
|
493
|
+
return await db.update_memory(
|
|
494
|
+
memory_id=memory_id,
|
|
495
|
+
user_id=payload.user_id,
|
|
496
|
+
memory=payload.memory,
|
|
497
|
+
topics=payload.topics or [],
|
|
498
|
+
db_id=db_id,
|
|
499
|
+
table=table,
|
|
500
|
+
headers=headers,
|
|
501
|
+
)
|
|
502
|
+
|
|
409
503
|
if isinstance(db, AsyncBaseDb):
|
|
410
504
|
db = cast(AsyncBaseDb, db)
|
|
411
505
|
user_memory = await db.upsert_user_memory(
|
|
@@ -463,13 +557,29 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
463
557
|
},
|
|
464
558
|
)
|
|
465
559
|
async def get_user_memory_stats(
|
|
466
|
-
|
|
467
|
-
|
|
560
|
+
request: Request,
|
|
561
|
+
limit: Optional[int] = Query(default=20, description="Number of user statistics to return per page", ge=1),
|
|
562
|
+
page: Optional[int] = Query(default=1, description="Page number for pagination", ge=0),
|
|
468
563
|
db_id: Optional[str] = Query(default=None, description="Database ID to query statistics from"),
|
|
469
564
|
table: Optional[str] = Query(default=None, description="Table to query statistics from"),
|
|
470
565
|
) -> PaginatedResponse[UserStatsSchema]:
|
|
471
566
|
db = await get_db(dbs, db_id, table)
|
|
567
|
+
|
|
568
|
+
if isinstance(db, RemoteDb):
|
|
569
|
+
auth_token = get_auth_token_from_request(request)
|
|
570
|
+
headers = {"Authorization": f"Bearer {auth_token}"} if auth_token else None
|
|
571
|
+
return await db.get_user_memory_stats(
|
|
572
|
+
limit=limit,
|
|
573
|
+
page=page,
|
|
574
|
+
db_id=db_id,
|
|
575
|
+
table=table,
|
|
576
|
+
headers=headers,
|
|
577
|
+
)
|
|
578
|
+
|
|
472
579
|
try:
|
|
580
|
+
# Ensure limit and page are integers
|
|
581
|
+
limit = int(limit) if limit is not None else 20
|
|
582
|
+
page = int(page) if page is not None else 1
|
|
473
583
|
if isinstance(db, AsyncBaseDb):
|
|
474
584
|
db = cast(AsyncBaseDb, db)
|
|
475
585
|
user_stats, total_count = await db.get_user_memory_stats(
|
|
@@ -494,6 +604,159 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
494
604
|
except Exception as e:
|
|
495
605
|
raise HTTPException(status_code=500, detail=f"Failed to get user statistics: {str(e)}")
|
|
496
606
|
|
|
607
|
+
@router.post(
|
|
608
|
+
"/optimize-memories",
|
|
609
|
+
response_model=OptimizeMemoriesResponse,
|
|
610
|
+
status_code=200,
|
|
611
|
+
operation_id="optimize_memories",
|
|
612
|
+
summary="Optimize User Memories",
|
|
613
|
+
description=(
|
|
614
|
+
"Optimize all memories for a given user using the default summarize strategy. "
|
|
615
|
+
"This operation combines all memories into a single comprehensive summary, "
|
|
616
|
+
"achieving maximum token reduction while preserving all key information. "
|
|
617
|
+
"To use a custom model, specify the model parameter in 'provider:model_id' format "
|
|
618
|
+
"(e.g., 'openai:gpt-4o-mini', 'anthropic:claude-3-5-sonnet-20241022'). "
|
|
619
|
+
"If not specified, uses MemoryManager's default model (gpt-4o). "
|
|
620
|
+
"Set apply=false to preview optimization results without saving to database."
|
|
621
|
+
),
|
|
622
|
+
responses={
|
|
623
|
+
200: {
|
|
624
|
+
"description": "Memories optimized successfully",
|
|
625
|
+
"content": {
|
|
626
|
+
"application/json": {
|
|
627
|
+
"example": {
|
|
628
|
+
"memories": [
|
|
629
|
+
{
|
|
630
|
+
"memory_id": "f9361a69-2997-40c7-ae4e-a5861d434047",
|
|
631
|
+
"memory": "User has a 3-year-old golden retriever named Max who loves fetch and walks. Lives in San Francisco's Mission district, works as a product manager in tech. Enjoys hiking Bay Area trails, trying new restaurants (especially Japanese, Thai, Mexican), and learning piano for 1.5 years.",
|
|
632
|
+
"topics": ["pets", "location", "work", "hobbies", "food_preferences"],
|
|
633
|
+
"user_id": "user2",
|
|
634
|
+
"updated_at": "2025-11-18T10:30:00Z",
|
|
635
|
+
}
|
|
636
|
+
],
|
|
637
|
+
"memories_before": 4,
|
|
638
|
+
"memories_after": 1,
|
|
639
|
+
"tokens_before": 450,
|
|
640
|
+
"tokens_after": 180,
|
|
641
|
+
"tokens_saved": 270,
|
|
642
|
+
"reduction_percentage": 60.0,
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
400: {
|
|
648
|
+
"description": "Bad request - User ID is required or invalid model string format",
|
|
649
|
+
"model": BadRequestResponse,
|
|
650
|
+
},
|
|
651
|
+
404: {"description": "No memories found for user", "model": NotFoundResponse},
|
|
652
|
+
500: {"description": "Failed to optimize memories", "model": InternalServerErrorResponse},
|
|
653
|
+
},
|
|
654
|
+
)
|
|
655
|
+
async def optimize_memories(
|
|
656
|
+
http_request: Request,
|
|
657
|
+
request: OptimizeMemoriesRequest,
|
|
658
|
+
db_id: Optional[str] = Query(default=None, description="Database ID to use for optimization"),
|
|
659
|
+
table: Optional[str] = Query(default=None, description="Table to use for optimization"),
|
|
660
|
+
) -> OptimizeMemoriesResponse:
|
|
661
|
+
"""Optimize user memories using the default summarize strategy."""
|
|
662
|
+
from agno.memory import MemoryManager
|
|
663
|
+
from agno.memory.strategies.types import MemoryOptimizationStrategyType
|
|
664
|
+
|
|
665
|
+
try:
|
|
666
|
+
# Get database instance
|
|
667
|
+
db = await get_db(dbs, db_id, table)
|
|
668
|
+
|
|
669
|
+
if isinstance(db, RemoteDb):
|
|
670
|
+
auth_token = get_auth_token_from_request(http_request)
|
|
671
|
+
headers = {"Authorization": f"Bearer {auth_token}"} if auth_token else None
|
|
672
|
+
return await db.optimize_memories(
|
|
673
|
+
user_id=request.user_id,
|
|
674
|
+
model=request.model,
|
|
675
|
+
apply=request.apply,
|
|
676
|
+
db_id=db_id,
|
|
677
|
+
table=table,
|
|
678
|
+
headers=headers,
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
# Create memory manager with optional model
|
|
682
|
+
if request.model:
|
|
683
|
+
try:
|
|
684
|
+
model_instance = get_model(request.model)
|
|
685
|
+
except ValueError as e:
|
|
686
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
687
|
+
memory_manager = MemoryManager(model=model_instance, db=db)
|
|
688
|
+
else:
|
|
689
|
+
# No model specified - use MemoryManager's default
|
|
690
|
+
memory_manager = MemoryManager(db=db)
|
|
691
|
+
|
|
692
|
+
# Get current memories to count tokens before optimization
|
|
693
|
+
if isinstance(db, AsyncBaseDb):
|
|
694
|
+
memories_before = await memory_manager.aget_user_memories(user_id=request.user_id)
|
|
695
|
+
else:
|
|
696
|
+
memories_before = memory_manager.get_user_memories(user_id=request.user_id)
|
|
697
|
+
|
|
698
|
+
if not memories_before:
|
|
699
|
+
raise HTTPException(status_code=404, detail=f"No memories found for user {request.user_id}")
|
|
700
|
+
|
|
701
|
+
# Count tokens before optimization
|
|
702
|
+
from agno.memory.strategies.summarize import SummarizeStrategy
|
|
703
|
+
|
|
704
|
+
strategy = SummarizeStrategy()
|
|
705
|
+
tokens_before = strategy.count_tokens(memories_before)
|
|
706
|
+
memories_before_count = len(memories_before)
|
|
707
|
+
|
|
708
|
+
# Optimize memories with default SUMMARIZE strategy
|
|
709
|
+
if isinstance(db, AsyncBaseDb):
|
|
710
|
+
optimized_memories = await memory_manager.aoptimize_memories(
|
|
711
|
+
user_id=request.user_id,
|
|
712
|
+
strategy=MemoryOptimizationStrategyType.SUMMARIZE,
|
|
713
|
+
apply=request.apply,
|
|
714
|
+
)
|
|
715
|
+
else:
|
|
716
|
+
optimized_memories = memory_manager.optimize_memories(
|
|
717
|
+
user_id=request.user_id,
|
|
718
|
+
strategy=MemoryOptimizationStrategyType.SUMMARIZE,
|
|
719
|
+
apply=request.apply,
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
# Count tokens after optimization
|
|
723
|
+
tokens_after = strategy.count_tokens(optimized_memories)
|
|
724
|
+
memories_after_count = len(optimized_memories)
|
|
725
|
+
|
|
726
|
+
# Calculate statistics
|
|
727
|
+
tokens_saved = tokens_before - tokens_after
|
|
728
|
+
reduction_percentage = (tokens_saved / tokens_before * 100.0) if tokens_before > 0 else 0.0
|
|
729
|
+
|
|
730
|
+
# Convert to schema objects
|
|
731
|
+
optimized_memory_schemas = [
|
|
732
|
+
UserMemorySchema(
|
|
733
|
+
memory_id=mem.memory_id or "",
|
|
734
|
+
memory=mem.memory or "",
|
|
735
|
+
topics=mem.topics,
|
|
736
|
+
agent_id=mem.agent_id,
|
|
737
|
+
team_id=mem.team_id,
|
|
738
|
+
user_id=mem.user_id,
|
|
739
|
+
updated_at=mem.updated_at,
|
|
740
|
+
)
|
|
741
|
+
for mem in optimized_memories
|
|
742
|
+
]
|
|
743
|
+
|
|
744
|
+
return OptimizeMemoriesResponse(
|
|
745
|
+
memories=optimized_memory_schemas,
|
|
746
|
+
memories_before=memories_before_count,
|
|
747
|
+
memories_after=memories_after_count,
|
|
748
|
+
tokens_before=tokens_before,
|
|
749
|
+
tokens_after=tokens_after,
|
|
750
|
+
tokens_saved=tokens_saved,
|
|
751
|
+
reduction_percentage=reduction_percentage,
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
except HTTPException:
|
|
755
|
+
raise
|
|
756
|
+
except Exception as e:
|
|
757
|
+
logger.error(f"Failed to optimize memories for user {request.user_id}: {str(e)}")
|
|
758
|
+
raise HTTPException(status_code=500, detail=f"Failed to optimize memories: {str(e)}")
|
|
759
|
+
|
|
497
760
|
return router
|
|
498
761
|
|
|
499
762
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime
|
|
2
3
|
from typing import Any, Dict, List, Optional
|
|
3
4
|
|
|
4
5
|
from pydantic import BaseModel, Field
|
|
5
6
|
|
|
7
|
+
from agno.os.utils import to_utc_datetime
|
|
8
|
+
|
|
6
9
|
|
|
7
10
|
class DeleteMemoriesRequest(BaseModel):
|
|
8
11
|
memory_ids: List[str] = Field(..., description="List of memory IDs to delete", min_length=1)
|
|
@@ -25,12 +28,24 @@ class UserMemorySchema(BaseModel):
|
|
|
25
28
|
if memory_dict["memory"] == "":
|
|
26
29
|
return None
|
|
27
30
|
|
|
31
|
+
# Handle nested memory content (relevant for some memories migrated from v1)
|
|
32
|
+
if isinstance(memory_dict["memory"], dict):
|
|
33
|
+
if memory_dict["memory"].get("memory") is not None:
|
|
34
|
+
memory = str(memory_dict["memory"]["memory"])
|
|
35
|
+
else:
|
|
36
|
+
try:
|
|
37
|
+
memory = json.dumps(memory_dict["memory"])
|
|
38
|
+
except json.JSONDecodeError:
|
|
39
|
+
memory = str(memory_dict["memory"])
|
|
40
|
+
else:
|
|
41
|
+
memory = memory_dict["memory"]
|
|
42
|
+
|
|
28
43
|
return cls(
|
|
29
44
|
memory_id=memory_dict["memory_id"],
|
|
30
45
|
user_id=str(memory_dict["user_id"]),
|
|
31
46
|
agent_id=memory_dict.get("agent_id"),
|
|
32
47
|
team_id=memory_dict.get("team_id"),
|
|
33
|
-
memory=
|
|
48
|
+
memory=memory,
|
|
34
49
|
topics=memory_dict.get("topics", []),
|
|
35
50
|
updated_at=memory_dict["updated_at"],
|
|
36
51
|
)
|
|
@@ -58,5 +73,31 @@ class UserStatsSchema(BaseModel):
|
|
|
58
73
|
return cls(
|
|
59
74
|
user_id=str(user_stats_dict["user_id"]),
|
|
60
75
|
total_memories=user_stats_dict["total_memories"],
|
|
61
|
-
last_memory_updated_at=
|
|
76
|
+
last_memory_updated_at=to_utc_datetime(updated_at),
|
|
62
77
|
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class OptimizeMemoriesRequest(BaseModel):
|
|
81
|
+
"""Schema for memory optimization request"""
|
|
82
|
+
|
|
83
|
+
user_id: str = Field(..., description="User ID to optimize memories for")
|
|
84
|
+
model: Optional[str] = Field(
|
|
85
|
+
default=None,
|
|
86
|
+
description="Model to use for optimization in format 'provider:model_id' (e.g., 'openai:gpt-4o-mini', 'anthropic:claude-3-5-sonnet-20241022', 'google:gemini-2.0-flash-exp'). If not specified, uses MemoryManager's default model (gpt-4o).",
|
|
87
|
+
)
|
|
88
|
+
apply: bool = Field(
|
|
89
|
+
default=True,
|
|
90
|
+
description="If True, apply optimization changes to database. If False, return preview only without saving.",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class OptimizeMemoriesResponse(BaseModel):
|
|
95
|
+
"""Schema for memory optimization response"""
|
|
96
|
+
|
|
97
|
+
memories: List[UserMemorySchema] = Field(..., description="List of optimized memory objects")
|
|
98
|
+
memories_before: int = Field(..., description="Number of memories before optimization", ge=0)
|
|
99
|
+
memories_after: int = Field(..., description="Number of memories after optimization", ge=0)
|
|
100
|
+
tokens_before: int = Field(..., description="Token count before optimization", ge=0)
|
|
101
|
+
tokens_after: int = Field(..., description="Token count after optimization", ge=0)
|
|
102
|
+
tokens_saved: int = Field(..., description="Number of tokens saved through optimization", ge=0)
|
|
103
|
+
reduction_percentage: float = Field(..., description="Percentage of token reduction achieved", ge=0.0, le=100.0)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from datetime import date
|
|
2
|
+
from datetime import date
|
|
3
3
|
from typing import List, Optional, Union, cast
|
|
4
4
|
|
|
5
|
-
from fastapi import Depends, HTTPException, Query
|
|
5
|
+
from fastapi import Depends, HTTPException, Query, Request
|
|
6
6
|
from fastapi.routing import APIRouter
|
|
7
7
|
|
|
8
8
|
from agno.db.base import AsyncBaseDb, BaseDb
|
|
9
|
-
from agno.os.auth import get_authentication_dependency
|
|
9
|
+
from agno.os.auth import get_auth_token_from_request, get_authentication_dependency
|
|
10
10
|
from agno.os.routers.metrics.schemas import DayAggregatedMetrics, MetricsResponse
|
|
11
11
|
from agno.os.schema import (
|
|
12
12
|
BadRequestResponse,
|
|
@@ -16,13 +16,14 @@ from agno.os.schema import (
|
|
|
16
16
|
ValidationErrorResponse,
|
|
17
17
|
)
|
|
18
18
|
from agno.os.settings import AgnoAPISettings
|
|
19
|
-
from agno.os.utils import get_db
|
|
19
|
+
from agno.os.utils import get_db, to_utc_datetime
|
|
20
|
+
from agno.remote.base import RemoteDb
|
|
20
21
|
|
|
21
22
|
logger = logging.getLogger(__name__)
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def get_metrics_router(
|
|
25
|
-
dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]], settings: AgnoAPISettings = AgnoAPISettings(), **kwargs
|
|
26
|
+
dbs: dict[str, list[Union[BaseDb, AsyncBaseDb, RemoteDb]]], settings: AgnoAPISettings = AgnoAPISettings(), **kwargs
|
|
26
27
|
) -> APIRouter:
|
|
27
28
|
"""Create metrics router with comprehensive OpenAPI documentation for system metrics and analytics endpoints."""
|
|
28
29
|
router = APIRouter(
|
|
@@ -39,7 +40,7 @@ def get_metrics_router(
|
|
|
39
40
|
return attach_routes(router=router, dbs=dbs)
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]]) -> APIRouter:
|
|
43
|
+
def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBaseDb, RemoteDb]]]) -> APIRouter:
|
|
43
44
|
@router.get(
|
|
44
45
|
"/metrics",
|
|
45
46
|
response_model=MetricsResponse,
|
|
@@ -78,9 +79,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
78
79
|
"reasoning_tokens": 0,
|
|
79
80
|
},
|
|
80
81
|
"model_metrics": [{"model_id": "gpt-4o", "model_provider": "OpenAI", "count": 5}],
|
|
81
|
-
"date": "2025-07-31T00:00:
|
|
82
|
-
"created_at":
|
|
83
|
-
"updated_at":
|
|
82
|
+
"date": "2025-07-31T00:00:00Z",
|
|
83
|
+
"created_at": "2025-07-31T12:38:52Z",
|
|
84
|
+
"updated_at": "2025-07-31T12:49:01Z",
|
|
84
85
|
}
|
|
85
86
|
]
|
|
86
87
|
}
|
|
@@ -92,6 +93,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
92
93
|
},
|
|
93
94
|
)
|
|
94
95
|
async def get_metrics(
|
|
96
|
+
request: Request,
|
|
95
97
|
starting_date: Optional[date] = Query(
|
|
96
98
|
default=None, description="Starting date for metrics range (YYYY-MM-DD format)"
|
|
97
99
|
),
|
|
@@ -103,6 +105,14 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
103
105
|
) -> MetricsResponse:
|
|
104
106
|
try:
|
|
105
107
|
db = await get_db(dbs, db_id, table)
|
|
108
|
+
|
|
109
|
+
if isinstance(db, RemoteDb):
|
|
110
|
+
auth_token = get_auth_token_from_request(request)
|
|
111
|
+
headers = {"Authorization": f"Bearer {auth_token}"} if auth_token else None
|
|
112
|
+
return await db.get_metrics(
|
|
113
|
+
starting_date=starting_date, ending_date=ending_date, db_id=db_id, table=table, headers=headers
|
|
114
|
+
)
|
|
115
|
+
|
|
106
116
|
if isinstance(db, AsyncBaseDb):
|
|
107
117
|
db = cast(AsyncBaseDb, db)
|
|
108
118
|
metrics, latest_updated_at = await db.get_metrics(starting_date=starting_date, ending_date=ending_date)
|
|
@@ -111,9 +121,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
111
121
|
|
|
112
122
|
return MetricsResponse(
|
|
113
123
|
metrics=[DayAggregatedMetrics.from_dict(metric) for metric in metrics],
|
|
114
|
-
updated_at=
|
|
115
|
-
if latest_updated_at is not None
|
|
116
|
-
else None,
|
|
124
|
+
updated_at=to_utc_datetime(latest_updated_at),
|
|
117
125
|
)
|
|
118
126
|
|
|
119
127
|
except Exception as e:
|
|
@@ -157,9 +165,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
157
165
|
"reasoning_tokens": 0,
|
|
158
166
|
},
|
|
159
167
|
"model_metrics": [{"model_id": "gpt-4o", "model_provider": "OpenAI", "count": 2}],
|
|
160
|
-
"date": "2025-08-12T00:00:
|
|
161
|
-
"created_at":
|
|
162
|
-
"updated_at":
|
|
168
|
+
"date": "2025-08-12T00:00:00Z",
|
|
169
|
+
"created_at": "2025-08-12T08:01:47Z",
|
|
170
|
+
"updated_at": "2025-08-12T08:01:47Z",
|
|
163
171
|
}
|
|
164
172
|
]
|
|
165
173
|
}
|
|
@@ -169,11 +177,18 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
169
177
|
},
|
|
170
178
|
)
|
|
171
179
|
async def calculate_metrics(
|
|
180
|
+
request: Request,
|
|
172
181
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for metrics calculation"),
|
|
173
182
|
table: Optional[str] = Query(default=None, description="Table to use for metrics calculation"),
|
|
174
183
|
) -> List[DayAggregatedMetrics]:
|
|
175
184
|
try:
|
|
176
185
|
db = await get_db(dbs, db_id, table)
|
|
186
|
+
|
|
187
|
+
if isinstance(db, RemoteDb):
|
|
188
|
+
auth_token = get_auth_token_from_request(request)
|
|
189
|
+
headers = {"Authorization": f"Bearer {auth_token}"} if auth_token else None
|
|
190
|
+
return await db.refresh_metrics(db_id=db_id, table=table, headers=headers)
|
|
191
|
+
|
|
177
192
|
if isinstance(db, AsyncBaseDb):
|
|
178
193
|
db = cast(AsyncBaseDb, db)
|
|
179
194
|
result = await db.calculate_metrics()
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
from datetime import datetime
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
2
|
from typing import Any, Dict, List, Optional
|
|
3
3
|
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
6
|
+
from agno.os.utils import to_utc_datetime
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
class DayAggregatedMetrics(BaseModel):
|
|
8
10
|
"""Aggregated metrics for a given day"""
|
|
@@ -20,22 +22,24 @@ class DayAggregatedMetrics(BaseModel):
|
|
|
20
22
|
model_metrics: List[Dict[str, Any]] = Field(..., description="Metrics grouped by model (model_id, provider, count)")
|
|
21
23
|
|
|
22
24
|
date: datetime = Field(..., description="Date for which these metrics are aggregated")
|
|
23
|
-
created_at:
|
|
24
|
-
updated_at:
|
|
25
|
+
created_at: datetime = Field(..., description="Timestamp when metrics were created")
|
|
26
|
+
updated_at: datetime = Field(..., description="Timestamp when metrics were last updated")
|
|
25
27
|
|
|
26
28
|
@classmethod
|
|
27
29
|
def from_dict(cls, metrics_dict: Dict[str, Any]) -> "DayAggregatedMetrics":
|
|
30
|
+
created_at = to_utc_datetime(metrics_dict.get("created_at")) or datetime.now(timezone.utc)
|
|
31
|
+
updated_at = to_utc_datetime(metrics_dict.get("updated_at", created_at)) or created_at
|
|
28
32
|
return cls(
|
|
29
33
|
agent_runs_count=metrics_dict.get("agent_runs_count", 0),
|
|
30
34
|
agent_sessions_count=metrics_dict.get("agent_sessions_count", 0),
|
|
31
|
-
|
|
32
|
-
date=metrics_dict.get("date", datetime.now()),
|
|
35
|
+
date=metrics_dict.get("date", datetime.now(timezone.utc)),
|
|
33
36
|
id=metrics_dict.get("id", ""),
|
|
34
37
|
model_metrics=metrics_dict.get("model_metrics", {}),
|
|
35
38
|
team_runs_count=metrics_dict.get("team_runs_count", 0),
|
|
36
39
|
team_sessions_count=metrics_dict.get("team_sessions_count", 0),
|
|
37
40
|
token_metrics=metrics_dict.get("token_metrics", {}),
|
|
38
|
-
|
|
41
|
+
created_at=created_at,
|
|
42
|
+
updated_at=updated_at,
|
|
39
43
|
users_count=metrics_dict.get("users_count", 0),
|
|
40
44
|
workflow_runs_count=metrics_dict.get("workflow_runs_count", 0),
|
|
41
45
|
workflow_sessions_count=metrics_dict.get("workflow_sessions_count", 0),
|