agno 2.0.0rc2__py3-none-any.whl → 2.3.0__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 +6009 -2874
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +385 -6
- agno/db/dynamo/dynamo.py +388 -81
- agno/db/dynamo/schemas.py +47 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +435 -64
- agno/db/firestore/schemas.py +11 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +384 -42
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +351 -66
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +339 -48
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +510 -37
- 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 +2036 -0
- agno/db/mongo/mongo.py +653 -76
- agno/db/mongo/schemas.py +13 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/mysql.py +687 -25
- agno/db/mysql/schemas.py +61 -37
- agno/db/mysql/utils.py +60 -2
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2001 -0
- agno/db/postgres/postgres.py +676 -57
- agno/db/postgres/schemas.py +43 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +344 -38
- agno/db/redis/schemas.py +18 -0
- agno/db/redis/utils.py +60 -2
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +26 -1
- agno/db/singlestore/singlestore.py +687 -53
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2371 -0
- agno/db/sqlite/schemas.py +24 -0
- agno/db/sqlite/sqlite.py +774 -85
- agno/db/sqlite/utils.py +168 -5
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1361 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +50 -22
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +68 -1
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/integrations/discord/client.py +1 -0
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/chunking/semantic.py +40 -8
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/aws_bedrock.py +9 -4
- agno/knowledge/embedder/azure_openai.py +54 -0
- agno/knowledge/embedder/base.py +2 -0
- agno/knowledge/embedder/cohere.py +184 -5
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/google.py +79 -1
- agno/knowledge/embedder/huggingface.py +9 -4
- agno/knowledge/embedder/jina.py +63 -0
- agno/knowledge/embedder/mistral.py +78 -11
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +13 -0
- agno/knowledge/embedder/openai.py +37 -65
- agno/knowledge/embedder/sentence_transformer.py +8 -4
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +595 -187
- agno/knowledge/reader/base.py +9 -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 +290 -0
- agno/knowledge/reader/json_reader.py +6 -5
- agno/knowledge/reader/markdown_reader.py +13 -13
- agno/knowledge/reader/pdf_reader.py +43 -68
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +51 -6
- agno/knowledge/reader/s3_reader.py +3 -15
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +13 -13
- agno/knowledge/reader/web_search_reader.py +2 -43
- agno/knowledge/reader/website_reader.py +43 -25
- agno/knowledge/reranker/__init__.py +3 -0
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +339 -266
- agno/memory/manager.py +336 -82
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/anthropic/claude.py +183 -37
- agno/models/aws/bedrock.py +52 -112
- agno/models/aws/claude.py +33 -1
- agno/models/azure/ai_foundry.py +33 -15
- agno/models/azure/openai_chat.py +25 -8
- agno/models/base.py +1011 -566
- agno/models/cerebras/cerebras.py +19 -13
- agno/models/cerebras/cerebras_openai.py +8 -5
- agno/models/cohere/chat.py +27 -1
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/google/gemini.py +110 -37
- agno/models/groq/groq.py +28 -11
- agno/models/huggingface/huggingface.py +2 -1
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/chat.py +18 -1
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/message.py +143 -4
- agno/models/meta/llama.py +27 -10
- agno/models/meta/llama_openai.py +5 -17
- agno/models/nebius/nebius.py +6 -6
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/ollama/chat.py +60 -6
- agno/models/openai/chat.py +102 -43
- agno/models/openai/responses.py +103 -106
- agno/models/openrouter/openrouter.py +41 -3
- agno/models/perplexity/perplexity.py +4 -5
- agno/models/portkey/portkey.py +3 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +81 -5
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/together.py +2 -2
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +2 -2
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +96 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +3 -2
- agno/os/app.py +543 -175
- agno/os/auth.py +24 -14
- agno/os/config.py +1 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/agui.py +23 -7
- agno/os/interfaces/agui/router.py +27 -3
- agno/os/interfaces/agui/utils.py +242 -142
- agno/os/interfaces/base.py +6 -2
- agno/os/interfaces/slack/router.py +81 -23
- agno/os/interfaces/slack/slack.py +29 -14
- agno/os/interfaces/whatsapp/router.py +11 -4
- agno/os/interfaces/whatsapp/whatsapp.py +14 -7
- agno/os/mcp.py +111 -54
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +556 -139
- agno/os/routers/evals/evals.py +71 -34
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/evals/utils.py +6 -5
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +185 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +158 -53
- agno/os/routers/memory/schemas.py +20 -16
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +499 -38
- agno/os/schema.py +308 -198
- agno/os/utils.py +401 -41
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +3 -1
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +2 -2
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +7 -2
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +266 -112
- agno/run/base.py +53 -24
- agno/run/team.py +252 -111
- agno/run/workflow.py +156 -45
- agno/session/agent.py +105 -89
- agno/session/summary.py +65 -25
- agno/session/team.py +176 -96
- agno/session/workflow.py +406 -40
- agno/team/team.py +3854 -1692
- agno/tools/brightdata.py +3 -3
- agno/tools/cartesia.py +3 -5
- agno/tools/dalle.py +9 -8
- agno/tools/decorator.py +4 -2
- agno/tools/desi_vocal.py +2 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +20 -13
- agno/tools/eleven_labs.py +26 -28
- agno/tools/exa.py +21 -16
- agno/tools/fal.py +4 -4
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +257 -37
- agno/tools/giphy.py +2 -2
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +270 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/knowledge.py +3 -3
- agno/tools/lumalab.py +3 -3
- 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 +284 -0
- agno/tools/mem0.py +11 -17
- agno/tools/memori.py +1 -53
- agno/tools/memory.py +419 -0
- agno/tools/models/azure_openai.py +2 -2
- agno/tools/models/gemini.py +3 -3
- agno/tools/models/groq.py +3 -5
- agno/tools/models/nebius.py +7 -7
- agno/tools/models_labs.py +25 -15
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +4 -9
- agno/tools/opencv.py +3 -3
- agno/tools/parallel.py +314 -0
- agno/tools/replicate.py +7 -7
- agno/tools/scrapegraph.py +58 -31
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/slack.py +18 -3
- agno/tools/spider.py +2 -2
- agno/tools/tavily.py +146 -0
- agno/tools/whatsapp.py +1 -1
- agno/tools/workflow.py +278 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +27 -0
- agno/utils/common.py +90 -1
- agno/utils/events.py +222 -7
- agno/utils/gemini.py +181 -23
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +111 -0
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +95 -5
- agno/utils/media.py +188 -10
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +60 -0
- agno/utils/models/claude.py +40 -11
- agno/utils/models/cohere.py +1 -1
- agno/utils/models/watsonx.py +1 -1
- agno/utils/openai.py +1 -1
- agno/utils/print_response/agent.py +105 -21
- agno/utils/print_response/team.py +103 -38
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/reasoning.py +22 -1
- agno/utils/serialize.py +32 -0
- agno/utils/streamlit.py +16 -10
- agno/utils/string.py +41 -0
- agno/utils/team.py +98 -9
- agno/utils/tools.py +1 -1
- agno/vectordb/base.py +23 -4
- agno/vectordb/cassandra/cassandra.py +65 -9
- agno/vectordb/chroma/chromadb.py +182 -38
- agno/vectordb/clickhouse/clickhousedb.py +64 -11
- agno/vectordb/couchbase/couchbase.py +105 -10
- agno/vectordb/lancedb/lance_db.py +183 -135
- agno/vectordb/langchaindb/langchaindb.py +25 -7
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +46 -7
- agno/vectordb/milvus/milvus.py +126 -9
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +112 -7
- agno/vectordb/pgvector/pgvector.py +142 -21
- agno/vectordb/pineconedb/pineconedb.py +80 -8
- agno/vectordb/qdrant/qdrant.py +125 -39
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/singlestore/singlestore.py +111 -25
- agno/vectordb/surrealdb/surrealdb.py +31 -5
- agno/vectordb/upstashdb/upstashdb.py +76 -8
- agno/vectordb/weaviate/weaviate.py +86 -15
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +112 -18
- agno/workflow/loop.py +69 -10
- agno/workflow/parallel.py +266 -118
- agno/workflow/router.py +110 -17
- agno/workflow/step.py +645 -136
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +71 -33
- agno/workflow/workflow.py +2113 -300
- agno-2.3.0.dist-info/METADATA +618 -0
- agno-2.3.0.dist-info/RECORD +577 -0
- agno-2.3.0.dist-info/licenses/LICENSE +201 -0
- agno/knowledge/reader/url_reader.py +0 -128
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -610
- agno/utils/models/aws_claude.py +0 -170
- agno-2.0.0rc2.dist-info/METADATA +0 -355
- agno-2.0.0rc2.dist-info/RECORD +0 -515
- agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/os/routers/memory/memory.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import math
|
|
3
|
-
from typing import List, Optional
|
|
3
|
+
from typing import List, Optional, Union, cast
|
|
4
4
|
from uuid import uuid4
|
|
5
5
|
|
|
6
|
-
from fastapi import Depends, HTTPException, Path, Query
|
|
6
|
+
from fastapi import Depends, HTTPException, Path, Query, Request
|
|
7
7
|
from fastapi.routing import APIRouter
|
|
8
8
|
|
|
9
|
-
from agno.db.base import BaseDb
|
|
9
|
+
from agno.db.base import AsyncBaseDb, BaseDb
|
|
10
10
|
from agno.db.schemas import UserMemory
|
|
11
11
|
from agno.os.auth import get_authentication_dependency
|
|
12
12
|
from agno.os.routers.memory.schemas import (
|
|
@@ -31,7 +31,9 @@ from agno.os.utils import get_db
|
|
|
31
31
|
logger = logging.getLogger(__name__)
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def get_memory_router(
|
|
34
|
+
def get_memory_router(
|
|
35
|
+
dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]], settings: AgnoAPISettings = AgnoAPISettings(), **kwargs
|
|
36
|
+
) -> APIRouter:
|
|
35
37
|
"""Create memory router with comprehensive OpenAPI documentation for user memory management endpoints."""
|
|
36
38
|
router = APIRouter(
|
|
37
39
|
dependencies=[Depends(get_authentication_dependency(settings))],
|
|
@@ -47,7 +49,7 @@ def get_memory_router(dbs: dict[str, BaseDb], settings: AgnoAPISettings = AgnoAP
|
|
|
47
49
|
return attach_routes(router=router, dbs=dbs)
|
|
48
50
|
|
|
49
51
|
|
|
50
|
-
def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
52
|
+
def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]]) -> APIRouter:
|
|
51
53
|
@router.post(
|
|
52
54
|
"/memories",
|
|
53
55
|
response_model=UserMemorySchema,
|
|
@@ -80,19 +82,42 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
80
82
|
},
|
|
81
83
|
)
|
|
82
84
|
async def create_memory(
|
|
85
|
+
request: Request,
|
|
83
86
|
payload: UserMemoryCreateSchema,
|
|
84
87
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for memory storage"),
|
|
88
|
+
table: Optional[str] = Query(default=None, description="Table to use for memory storage"),
|
|
85
89
|
) -> UserMemorySchema:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
)
|
|
90
|
+
if hasattr(request.state, "user_id"):
|
|
91
|
+
user_id = request.state.user_id
|
|
92
|
+
payload.user_id = user_id
|
|
93
|
+
|
|
94
|
+
if payload.user_id is None:
|
|
95
|
+
raise HTTPException(status_code=400, detail="User ID is required")
|
|
96
|
+
|
|
97
|
+
db = await get_db(dbs, db_id, table)
|
|
98
|
+
|
|
99
|
+
if isinstance(db, AsyncBaseDb):
|
|
100
|
+
db = cast(AsyncBaseDb, db)
|
|
101
|
+
user_memory = await db.upsert_user_memory(
|
|
102
|
+
memory=UserMemory(
|
|
103
|
+
memory_id=str(uuid4()),
|
|
104
|
+
memory=payload.memory,
|
|
105
|
+
topics=payload.topics or [],
|
|
106
|
+
user_id=payload.user_id,
|
|
107
|
+
),
|
|
108
|
+
deserialize=False,
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
user_memory = db.upsert_user_memory(
|
|
112
|
+
memory=UserMemory(
|
|
113
|
+
memory_id=str(uuid4()),
|
|
114
|
+
memory=payload.memory,
|
|
115
|
+
topics=payload.topics or [],
|
|
116
|
+
user_id=payload.user_id,
|
|
117
|
+
),
|
|
118
|
+
deserialize=False,
|
|
119
|
+
)
|
|
120
|
+
|
|
96
121
|
if not user_memory:
|
|
97
122
|
raise HTTPException(status_code=500, detail="Failed to create memory")
|
|
98
123
|
|
|
@@ -112,10 +137,16 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
112
137
|
)
|
|
113
138
|
async def delete_memory(
|
|
114
139
|
memory_id: str = Path(description="Memory ID to delete"),
|
|
140
|
+
user_id: Optional[str] = Query(default=None, description="User ID to delete memory for"),
|
|
115
141
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for deletion"),
|
|
142
|
+
table: Optional[str] = Query(default=None, description="Table to use for deletion"),
|
|
116
143
|
) -> None:
|
|
117
|
-
db = get_db(dbs, db_id)
|
|
118
|
-
db
|
|
144
|
+
db = await get_db(dbs, db_id, table)
|
|
145
|
+
if isinstance(db, AsyncBaseDb):
|
|
146
|
+
db = cast(AsyncBaseDb, db)
|
|
147
|
+
await db.delete_user_memory(memory_id=memory_id, user_id=user_id)
|
|
148
|
+
else:
|
|
149
|
+
db.delete_user_memory(memory_id=memory_id, user_id=user_id)
|
|
119
150
|
|
|
120
151
|
@router.delete(
|
|
121
152
|
"/memories",
|
|
@@ -135,9 +166,14 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
135
166
|
async def delete_memories(
|
|
136
167
|
request: DeleteMemoriesRequest,
|
|
137
168
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for deletion"),
|
|
169
|
+
table: Optional[str] = Query(default=None, description="Table to use for deletion"),
|
|
138
170
|
) -> None:
|
|
139
|
-
db = get_db(dbs, db_id)
|
|
140
|
-
db
|
|
171
|
+
db = await get_db(dbs, db_id, table)
|
|
172
|
+
if isinstance(db, AsyncBaseDb):
|
|
173
|
+
db = cast(AsyncBaseDb, db)
|
|
174
|
+
await db.delete_user_memories(memory_ids=request.memory_ids, user_id=request.user_id)
|
|
175
|
+
else:
|
|
176
|
+
db.delete_user_memories(memory_ids=request.memory_ids, user_id=request.user_id)
|
|
141
177
|
|
|
142
178
|
@router.get(
|
|
143
179
|
"/memories",
|
|
@@ -173,6 +209,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
173
209
|
},
|
|
174
210
|
)
|
|
175
211
|
async def get_memories(
|
|
212
|
+
request: Request,
|
|
176
213
|
user_id: Optional[str] = Query(default=None, description="Filter memories by user ID"),
|
|
177
214
|
agent_id: Optional[str] = Query(default=None, description="Filter memories by agent ID"),
|
|
178
215
|
team_id: Optional[str] = Query(default=None, description="Filter memories by team ID"),
|
|
@@ -183,22 +220,44 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
183
220
|
sort_by: Optional[str] = Query(default="updated_at", description="Field to sort memories by"),
|
|
184
221
|
sort_order: Optional[SortOrder] = Query(default="desc", description="Sort order (asc or desc)"),
|
|
185
222
|
db_id: Optional[str] = Query(default=None, description="Database ID to query memories from"),
|
|
223
|
+
table: Optional[str] = Query(default=None, description="The database table to use"),
|
|
186
224
|
) -> PaginatedResponse[UserMemorySchema]:
|
|
187
|
-
db = get_db(dbs, db_id)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
225
|
+
db = await get_db(dbs, db_id, table)
|
|
226
|
+
|
|
227
|
+
if hasattr(request.state, "user_id"):
|
|
228
|
+
user_id = request.state.user_id
|
|
229
|
+
|
|
230
|
+
if isinstance(db, AsyncBaseDb):
|
|
231
|
+
db = cast(AsyncBaseDb, db)
|
|
232
|
+
user_memories, total_count = await db.get_user_memories(
|
|
233
|
+
limit=limit,
|
|
234
|
+
page=page,
|
|
235
|
+
user_id=user_id,
|
|
236
|
+
agent_id=agent_id,
|
|
237
|
+
team_id=team_id,
|
|
238
|
+
topics=topics,
|
|
239
|
+
search_content=search_content,
|
|
240
|
+
sort_by=sort_by,
|
|
241
|
+
sort_order=sort_order,
|
|
242
|
+
deserialize=False,
|
|
243
|
+
)
|
|
244
|
+
else:
|
|
245
|
+
user_memories, total_count = db.get_user_memories( # type: ignore
|
|
246
|
+
limit=limit,
|
|
247
|
+
page=page,
|
|
248
|
+
user_id=user_id,
|
|
249
|
+
agent_id=agent_id,
|
|
250
|
+
team_id=team_id,
|
|
251
|
+
topics=topics,
|
|
252
|
+
search_content=search_content,
|
|
253
|
+
sort_by=sort_by,
|
|
254
|
+
sort_order=sort_order,
|
|
255
|
+
deserialize=False,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
memories = [UserMemorySchema.from_dict(user_memory) for user_memory in user_memories] # type: ignore
|
|
200
259
|
return PaginatedResponse(
|
|
201
|
-
data=[
|
|
260
|
+
data=[memory for memory in memories if memory is not None],
|
|
202
261
|
meta=PaginationInfo(
|
|
203
262
|
page=page,
|
|
204
263
|
limit=limit,
|
|
@@ -235,11 +294,22 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
235
294
|
},
|
|
236
295
|
)
|
|
237
296
|
async def get_memory(
|
|
297
|
+
request: Request,
|
|
238
298
|
memory_id: str = Path(description="Memory ID to retrieve"),
|
|
299
|
+
user_id: Optional[str] = Query(default=None, description="User ID to query memory for"),
|
|
239
300
|
db_id: Optional[str] = Query(default=None, description="Database ID to query memory from"),
|
|
301
|
+
table: Optional[str] = Query(default=None, description="Table to query memory from"),
|
|
240
302
|
) -> UserMemorySchema:
|
|
241
|
-
db = get_db(dbs, db_id)
|
|
242
|
-
|
|
303
|
+
db = await get_db(dbs, db_id, table)
|
|
304
|
+
|
|
305
|
+
if hasattr(request.state, "user_id"):
|
|
306
|
+
user_id = request.state.user_id
|
|
307
|
+
|
|
308
|
+
if isinstance(db, AsyncBaseDb):
|
|
309
|
+
db = cast(AsyncBaseDb, db)
|
|
310
|
+
user_memory = await db.get_user_memory(memory_id=memory_id, user_id=user_id, deserialize=False)
|
|
311
|
+
else:
|
|
312
|
+
user_memory = db.get_user_memory(memory_id=memory_id, user_id=user_id, deserialize=False)
|
|
243
313
|
if not user_memory:
|
|
244
314
|
raise HTTPException(status_code=404, detail=f"Memory with ID {memory_id} not found")
|
|
245
315
|
|
|
@@ -278,9 +348,14 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
278
348
|
)
|
|
279
349
|
async def get_topics(
|
|
280
350
|
db_id: Optional[str] = Query(default=None, description="Database ID to query topics from"),
|
|
351
|
+
table: Optional[str] = Query(default=None, description="Table to query topics from"),
|
|
281
352
|
) -> List[str]:
|
|
282
|
-
db = get_db(dbs, db_id)
|
|
283
|
-
|
|
353
|
+
db = await get_db(dbs, db_id, table)
|
|
354
|
+
if isinstance(db, AsyncBaseDb):
|
|
355
|
+
db = cast(AsyncBaseDb, db)
|
|
356
|
+
return await db.get_all_memory_topics()
|
|
357
|
+
else:
|
|
358
|
+
return db.get_all_memory_topics()
|
|
284
359
|
|
|
285
360
|
@router.patch(
|
|
286
361
|
"/memories/{memory_id}",
|
|
@@ -316,20 +391,42 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
316
391
|
},
|
|
317
392
|
)
|
|
318
393
|
async def update_memory(
|
|
394
|
+
request: Request,
|
|
319
395
|
payload: UserMemoryCreateSchema,
|
|
320
396
|
memory_id: str = Path(description="Memory ID to update"),
|
|
321
397
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for update"),
|
|
398
|
+
table: Optional[str] = Query(default=None, description="Table to use for update"),
|
|
322
399
|
) -> UserMemorySchema:
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
)
|
|
400
|
+
if hasattr(request.state, "user_id"):
|
|
401
|
+
user_id = request.state.user_id
|
|
402
|
+
payload.user_id = user_id
|
|
403
|
+
|
|
404
|
+
if payload.user_id is None:
|
|
405
|
+
raise HTTPException(status_code=400, detail="User ID is required")
|
|
406
|
+
|
|
407
|
+
db = await get_db(dbs, db_id, table)
|
|
408
|
+
|
|
409
|
+
if isinstance(db, AsyncBaseDb):
|
|
410
|
+
db = cast(AsyncBaseDb, db)
|
|
411
|
+
user_memory = await db.upsert_user_memory(
|
|
412
|
+
memory=UserMemory(
|
|
413
|
+
memory_id=memory_id,
|
|
414
|
+
memory=payload.memory,
|
|
415
|
+
topics=payload.topics or [],
|
|
416
|
+
user_id=payload.user_id,
|
|
417
|
+
),
|
|
418
|
+
deserialize=False,
|
|
419
|
+
)
|
|
420
|
+
else:
|
|
421
|
+
user_memory = db.upsert_user_memory(
|
|
422
|
+
memory=UserMemory(
|
|
423
|
+
memory_id=memory_id,
|
|
424
|
+
memory=payload.memory,
|
|
425
|
+
topics=payload.topics or [],
|
|
426
|
+
user_id=payload.user_id,
|
|
427
|
+
),
|
|
428
|
+
deserialize=False,
|
|
429
|
+
)
|
|
333
430
|
if not user_memory:
|
|
334
431
|
raise HTTPException(status_code=500, detail="Failed to update memory")
|
|
335
432
|
|
|
@@ -369,13 +466,21 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
369
466
|
limit: Optional[int] = Query(default=20, description="Number of user statistics to return per page"),
|
|
370
467
|
page: Optional[int] = Query(default=1, description="Page number for pagination"),
|
|
371
468
|
db_id: Optional[str] = Query(default=None, description="Database ID to query statistics from"),
|
|
469
|
+
table: Optional[str] = Query(default=None, description="Table to query statistics from"),
|
|
372
470
|
) -> PaginatedResponse[UserStatsSchema]:
|
|
373
|
-
db = get_db(dbs, db_id)
|
|
471
|
+
db = await get_db(dbs, db_id, table)
|
|
374
472
|
try:
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
473
|
+
if isinstance(db, AsyncBaseDb):
|
|
474
|
+
db = cast(AsyncBaseDb, db)
|
|
475
|
+
user_stats, total_count = await db.get_user_memory_stats(
|
|
476
|
+
limit=limit,
|
|
477
|
+
page=page,
|
|
478
|
+
)
|
|
479
|
+
else:
|
|
480
|
+
user_stats, total_count = db.get_user_memory_stats(
|
|
481
|
+
limit=limit,
|
|
482
|
+
page=page,
|
|
483
|
+
)
|
|
379
484
|
return PaginatedResponse(
|
|
380
485
|
data=[UserStatsSchema.from_dict(stats) for stats in user_stats],
|
|
381
486
|
meta=PaginationInfo(
|
|
@@ -396,7 +501,7 @@ def parse_topics(
|
|
|
396
501
|
topics: Optional[List[str]] = Query(
|
|
397
502
|
default=None,
|
|
398
503
|
description="Comma-separated list of topics to filter by",
|
|
399
|
-
|
|
504
|
+
examples=["preferences,technical,communication_style"],
|
|
400
505
|
),
|
|
401
506
|
) -> Optional[List[str]]:
|
|
402
507
|
"""Parse comma-separated topics into a list for filtering memories by topic."""
|
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
from datetime import datetime, timezone
|
|
2
2
|
from typing import Any, Dict, List, Optional
|
|
3
3
|
|
|
4
|
-
from pydantic import BaseModel
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class DeleteMemoriesRequest(BaseModel):
|
|
8
|
-
memory_ids: List[str]
|
|
8
|
+
memory_ids: List[str] = Field(..., description="List of memory IDs to delete", min_length=1)
|
|
9
|
+
user_id: Optional[str] = Field(None, description="User ID to filter memories for deletion")
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class UserMemorySchema(BaseModel):
|
|
12
|
-
memory_id: str
|
|
13
|
-
memory: str
|
|
14
|
-
topics: Optional[List[str]]
|
|
13
|
+
memory_id: str = Field(..., description="Unique identifier for the memory")
|
|
14
|
+
memory: str = Field(..., description="Memory content text")
|
|
15
|
+
topics: Optional[List[str]] = Field(None, description="Topics or tags associated with the memory")
|
|
15
16
|
|
|
16
|
-
agent_id: Optional[str]
|
|
17
|
-
team_id: Optional[str]
|
|
18
|
-
user_id: Optional[str]
|
|
17
|
+
agent_id: Optional[str] = Field(None, description="Agent ID associated with this memory")
|
|
18
|
+
team_id: Optional[str] = Field(None, description="Team ID associated with this memory")
|
|
19
|
+
user_id: Optional[str] = Field(None, description="User ID who owns this memory")
|
|
19
20
|
|
|
20
|
-
updated_at: Optional[datetime]
|
|
21
|
+
updated_at: Optional[datetime] = Field(None, description="Timestamp when memory was last updated")
|
|
21
22
|
|
|
22
23
|
@classmethod
|
|
23
|
-
def from_dict(cls, memory_dict: Dict[str, Any]) -> "UserMemorySchema":
|
|
24
|
+
def from_dict(cls, memory_dict: Dict[str, Any]) -> Optional["UserMemorySchema"]:
|
|
25
|
+
if memory_dict["memory"] == "":
|
|
26
|
+
return None
|
|
27
|
+
|
|
24
28
|
return cls(
|
|
25
29
|
memory_id=memory_dict["memory_id"],
|
|
26
30
|
user_id=str(memory_dict["user_id"]),
|
|
@@ -35,17 +39,17 @@ class UserMemorySchema(BaseModel):
|
|
|
35
39
|
class UserMemoryCreateSchema(BaseModel):
|
|
36
40
|
"""Define the payload expected for creating a new user memory"""
|
|
37
41
|
|
|
38
|
-
memory: str
|
|
39
|
-
user_id: str
|
|
40
|
-
topics: Optional[List[str]] = None
|
|
42
|
+
memory: str = Field(..., description="Memory content text", min_length=1, max_length=5000)
|
|
43
|
+
user_id: Optional[str] = Field(None, description="User ID who owns this memory")
|
|
44
|
+
topics: Optional[List[str]] = Field(None, description="Topics or tags to categorize the memory")
|
|
41
45
|
|
|
42
46
|
|
|
43
47
|
class UserStatsSchema(BaseModel):
|
|
44
48
|
"""Schema for user memory statistics"""
|
|
45
49
|
|
|
46
|
-
user_id: str
|
|
47
|
-
total_memories: int
|
|
48
|
-
last_memory_updated_at: Optional[datetime] = None
|
|
50
|
+
user_id: str = Field(..., description="User ID")
|
|
51
|
+
total_memories: int = Field(..., description="Total number of memories for this user", ge=0)
|
|
52
|
+
last_memory_updated_at: Optional[datetime] = Field(None, description="Timestamp of the most recent memory update")
|
|
49
53
|
|
|
50
54
|
@classmethod
|
|
51
55
|
def from_dict(cls, user_stats_dict: Dict[str, Any]) -> "UserStatsSchema":
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from datetime import date, datetime, timezone
|
|
3
|
-
from typing import List, Optional
|
|
3
|
+
from typing import List, Optional, Union, cast
|
|
4
4
|
|
|
5
5
|
from fastapi import Depends, HTTPException, Query
|
|
6
6
|
from fastapi.routing import APIRouter
|
|
7
7
|
|
|
8
|
-
from agno.db.base import BaseDb
|
|
8
|
+
from agno.db.base import AsyncBaseDb, BaseDb
|
|
9
9
|
from agno.os.auth import get_authentication_dependency
|
|
10
10
|
from agno.os.routers.metrics.schemas import DayAggregatedMetrics, MetricsResponse
|
|
11
11
|
from agno.os.schema import (
|
|
@@ -21,7 +21,9 @@ from agno.os.utils import get_db
|
|
|
21
21
|
logger = logging.getLogger(__name__)
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def get_metrics_router(
|
|
24
|
+
def get_metrics_router(
|
|
25
|
+
dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]], settings: AgnoAPISettings = AgnoAPISettings(), **kwargs
|
|
26
|
+
) -> APIRouter:
|
|
25
27
|
"""Create metrics router with comprehensive OpenAPI documentation for system metrics and analytics endpoints."""
|
|
26
28
|
router = APIRouter(
|
|
27
29
|
dependencies=[Depends(get_authentication_dependency(settings))],
|
|
@@ -37,7 +39,7 @@ def get_metrics_router(dbs: dict[str, BaseDb], settings: AgnoAPISettings = AgnoA
|
|
|
37
39
|
return attach_routes(router=router, dbs=dbs)
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
42
|
+
def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]]) -> APIRouter:
|
|
41
43
|
@router.get(
|
|
42
44
|
"/metrics",
|
|
43
45
|
response_model=MetricsResponse,
|
|
@@ -97,10 +99,15 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
97
99
|
default=None, description="Ending date for metrics range (YYYY-MM-DD format)"
|
|
98
100
|
),
|
|
99
101
|
db_id: Optional[str] = Query(default=None, description="Database ID to query metrics from"),
|
|
102
|
+
table: Optional[str] = Query(default=None, description="The database table to use"),
|
|
100
103
|
) -> MetricsResponse:
|
|
101
104
|
try:
|
|
102
|
-
db = get_db(dbs, db_id)
|
|
103
|
-
|
|
105
|
+
db = await get_db(dbs, db_id, table)
|
|
106
|
+
if isinstance(db, AsyncBaseDb):
|
|
107
|
+
db = cast(AsyncBaseDb, db)
|
|
108
|
+
metrics, latest_updated_at = await db.get_metrics(starting_date=starting_date, ending_date=ending_date)
|
|
109
|
+
else:
|
|
110
|
+
metrics, latest_updated_at = db.get_metrics(starting_date=starting_date, ending_date=ending_date)
|
|
104
111
|
|
|
105
112
|
return MetricsResponse(
|
|
106
113
|
metrics=[DayAggregatedMetrics.from_dict(metric) for metric in metrics],
|
|
@@ -163,10 +170,15 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
163
170
|
)
|
|
164
171
|
async def calculate_metrics(
|
|
165
172
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for metrics calculation"),
|
|
173
|
+
table: Optional[str] = Query(default=None, description="Table to use for metrics calculation"),
|
|
166
174
|
) -> List[DayAggregatedMetrics]:
|
|
167
175
|
try:
|
|
168
|
-
db = get_db(dbs, db_id)
|
|
169
|
-
|
|
176
|
+
db = await get_db(dbs, db_id, table)
|
|
177
|
+
if isinstance(db, AsyncBaseDb):
|
|
178
|
+
db = cast(AsyncBaseDb, db)
|
|
179
|
+
result = await db.calculate_metrics()
|
|
180
|
+
else:
|
|
181
|
+
result = db.calculate_metrics()
|
|
170
182
|
if result is None:
|
|
171
183
|
return []
|
|
172
184
|
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from typing import Any, Dict, List, Optional
|
|
3
3
|
|
|
4
|
-
from pydantic import BaseModel
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class DayAggregatedMetrics(BaseModel):
|
|
8
8
|
"""Aggregated metrics for a given day"""
|
|
9
9
|
|
|
10
|
-
id: str
|
|
10
|
+
id: str = Field(..., description="Unique identifier for the metrics record")
|
|
11
11
|
|
|
12
|
-
agent_runs_count: int
|
|
13
|
-
agent_sessions_count: int
|
|
14
|
-
team_runs_count: int
|
|
15
|
-
team_sessions_count: int
|
|
16
|
-
workflow_runs_count: int
|
|
17
|
-
workflow_sessions_count: int
|
|
18
|
-
users_count: int
|
|
19
|
-
token_metrics: Dict[str, Any]
|
|
20
|
-
model_metrics: List[Dict[str, Any]]
|
|
12
|
+
agent_runs_count: int = Field(..., description="Total number of agent runs", ge=0)
|
|
13
|
+
agent_sessions_count: int = Field(..., description="Total number of agent sessions", ge=0)
|
|
14
|
+
team_runs_count: int = Field(..., description="Total number of team runs", ge=0)
|
|
15
|
+
team_sessions_count: int = Field(..., description="Total number of team sessions", ge=0)
|
|
16
|
+
workflow_runs_count: int = Field(..., description="Total number of workflow runs", ge=0)
|
|
17
|
+
workflow_sessions_count: int = Field(..., description="Total number of workflow sessions", ge=0)
|
|
18
|
+
users_count: int = Field(..., description="Total number of unique users", ge=0)
|
|
19
|
+
token_metrics: Dict[str, Any] = Field(..., description="Token usage metrics (input, output, cached, etc.)")
|
|
20
|
+
model_metrics: List[Dict[str, Any]] = Field(..., description="Metrics grouped by model (model_id, provider, count)")
|
|
21
21
|
|
|
22
|
-
date: datetime
|
|
23
|
-
created_at: int
|
|
24
|
-
updated_at: int
|
|
22
|
+
date: datetime = Field(..., description="Date for which these metrics are aggregated")
|
|
23
|
+
created_at: int = Field(..., description="Unix timestamp when metrics were created", ge=0)
|
|
24
|
+
updated_at: int = Field(..., description="Unix timestamp when metrics were last updated", ge=0)
|
|
25
25
|
|
|
26
26
|
@classmethod
|
|
27
27
|
def from_dict(cls, metrics_dict: Dict[str, Any]) -> "DayAggregatedMetrics":
|
|
@@ -43,5 +43,5 @@ class DayAggregatedMetrics(BaseModel):
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
class MetricsResponse(BaseModel):
|
|
46
|
-
metrics: List[DayAggregatedMetrics]
|
|
47
|
-
updated_at: Optional[datetime]
|
|
46
|
+
metrics: List[DayAggregatedMetrics] = Field(..., description="List of daily aggregated metrics")
|
|
47
|
+
updated_at: Optional[datetime] = Field(None, description="Timestamp of the most recent metrics update")
|