agno 2.0.1__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.
Files changed (314) hide show
  1. agno/agent/agent.py +6015 -2823
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +594 -186
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +2 -8
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +72 -0
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +999 -519
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +103 -31
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +139 -0
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +59 -5
  142. agno/models/openai/chat.py +69 -29
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +77 -1
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -178
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +248 -94
  205. agno/run/base.py +44 -5
  206. agno/run/team.py +238 -97
  207. agno/run/workflow.py +144 -33
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1610
  213. agno/tools/dalle.py +2 -4
  214. agno/tools/decorator.py +4 -2
  215. agno/tools/duckduckgo.py +15 -11
  216. agno/tools/e2b.py +14 -7
  217. agno/tools/eleven_labs.py +23 -25
  218. agno/tools/exa.py +21 -16
  219. agno/tools/file.py +153 -23
  220. agno/tools/file_generation.py +350 -0
  221. agno/tools/firecrawl.py +4 -4
  222. agno/tools/function.py +250 -30
  223. agno/tools/gmail.py +238 -14
  224. agno/tools/google_drive.py +270 -0
  225. agno/tools/googlecalendar.py +36 -8
  226. agno/tools/googlesheets.py +20 -5
  227. agno/tools/jira.py +20 -0
  228. agno/tools/knowledge.py +3 -3
  229. agno/tools/mcp/__init__.py +10 -0
  230. agno/tools/mcp/mcp.py +331 -0
  231. agno/tools/mcp/multi_mcp.py +347 -0
  232. agno/tools/mcp/params.py +24 -0
  233. agno/tools/mcp_toolbox.py +284 -0
  234. agno/tools/mem0.py +11 -17
  235. agno/tools/memori.py +1 -53
  236. agno/tools/memory.py +419 -0
  237. agno/tools/models/nebius.py +5 -5
  238. agno/tools/models_labs.py +20 -10
  239. agno/tools/notion.py +204 -0
  240. agno/tools/parallel.py +314 -0
  241. agno/tools/scrapegraph.py +58 -31
  242. agno/tools/searxng.py +2 -2
  243. agno/tools/serper.py +2 -2
  244. agno/tools/slack.py +18 -3
  245. agno/tools/spider.py +2 -2
  246. agno/tools/tavily.py +146 -0
  247. agno/tools/whatsapp.py +1 -1
  248. agno/tools/workflow.py +278 -0
  249. agno/tools/yfinance.py +12 -11
  250. agno/utils/agent.py +820 -0
  251. agno/utils/audio.py +27 -0
  252. agno/utils/common.py +90 -1
  253. agno/utils/events.py +217 -2
  254. agno/utils/gemini.py +180 -22
  255. agno/utils/hooks.py +57 -0
  256. agno/utils/http.py +111 -0
  257. agno/utils/knowledge.py +12 -5
  258. agno/utils/log.py +1 -0
  259. agno/utils/mcp.py +92 -2
  260. agno/utils/media.py +188 -10
  261. agno/utils/merge_dict.py +22 -1
  262. agno/utils/message.py +60 -0
  263. agno/utils/models/claude.py +40 -11
  264. agno/utils/print_response/agent.py +105 -21
  265. agno/utils/print_response/team.py +103 -38
  266. agno/utils/print_response/workflow.py +251 -34
  267. agno/utils/reasoning.py +22 -1
  268. agno/utils/serialize.py +32 -0
  269. agno/utils/streamlit.py +16 -10
  270. agno/utils/string.py +41 -0
  271. agno/utils/team.py +98 -9
  272. agno/utils/tools.py +1 -1
  273. agno/vectordb/base.py +23 -4
  274. agno/vectordb/cassandra/cassandra.py +65 -9
  275. agno/vectordb/chroma/chromadb.py +182 -38
  276. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  277. agno/vectordb/couchbase/couchbase.py +105 -10
  278. agno/vectordb/lancedb/lance_db.py +124 -133
  279. agno/vectordb/langchaindb/langchaindb.py +25 -7
  280. agno/vectordb/lightrag/lightrag.py +17 -3
  281. agno/vectordb/llamaindex/__init__.py +3 -0
  282. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  283. agno/vectordb/milvus/milvus.py +126 -9
  284. agno/vectordb/mongodb/__init__.py +7 -1
  285. agno/vectordb/mongodb/mongodb.py +112 -7
  286. agno/vectordb/pgvector/pgvector.py +142 -21
  287. agno/vectordb/pineconedb/pineconedb.py +80 -8
  288. agno/vectordb/qdrant/qdrant.py +125 -39
  289. agno/vectordb/redis/__init__.py +9 -0
  290. agno/vectordb/redis/redisdb.py +694 -0
  291. agno/vectordb/singlestore/singlestore.py +111 -25
  292. agno/vectordb/surrealdb/surrealdb.py +31 -5
  293. agno/vectordb/upstashdb/upstashdb.py +76 -8
  294. agno/vectordb/weaviate/weaviate.py +86 -15
  295. agno/workflow/__init__.py +2 -0
  296. agno/workflow/agent.py +299 -0
  297. agno/workflow/condition.py +112 -18
  298. agno/workflow/loop.py +69 -10
  299. agno/workflow/parallel.py +266 -118
  300. agno/workflow/router.py +110 -17
  301. agno/workflow/step.py +638 -129
  302. agno/workflow/steps.py +65 -6
  303. agno/workflow/types.py +61 -23
  304. agno/workflow/workflow.py +2085 -272
  305. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
  306. agno-2.3.0.dist-info/RECORD +577 -0
  307. agno/knowledge/reader/url_reader.py +0 -128
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -610
  310. agno/utils/models/aws_claude.py +0 -170
  311. agno-2.0.1.dist-info/RECORD +0 -515
  312. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  313. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
@@ -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(dbs: dict[str, BaseDb], settings: AgnoAPISettings = AgnoAPISettings(), **kwargs) -> APIRouter:
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
- db = get_db(dbs, db_id)
87
- user_memory = db.upsert_user_memory(
88
- memory=UserMemory(
89
- memory_id=str(uuid4()),
90
- memory=payload.memory,
91
- topics=payload.topics or [],
92
- user_id=payload.user_id,
93
- ),
94
- deserialize=False,
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.delete_user_memory(memory_id=memory_id)
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.delete_user_memories(memory_ids=request.memory_ids)
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
- user_memories, total_count = db.get_user_memories(
189
- limit=limit,
190
- page=page,
191
- user_id=user_id,
192
- agent_id=agent_id,
193
- team_id=team_id,
194
- topics=topics,
195
- search_content=search_content,
196
- sort_by=sort_by,
197
- sort_order=sort_order,
198
- deserialize=False,
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=[UserMemorySchema.from_dict(user_memory) for user_memory in user_memories], # type: ignore
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
- user_memory = db.get_user_memory(memory_id=memory_id, deserialize=False)
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
- return db.get_all_memory_topics()
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
- db = get_db(dbs, db_id)
324
- user_memory = db.upsert_user_memory(
325
- memory=UserMemory(
326
- memory_id=memory_id,
327
- memory=payload.memory,
328
- topics=payload.topics or [],
329
- user_id=payload.user_id,
330
- ),
331
- deserialize=False,
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
- user_stats, total_count = db.get_user_memory_stats(
376
- limit=limit,
377
- page=page,
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
- example=["preferences,technical,communication_style"],
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(dbs: dict[str, BaseDb], settings: AgnoAPISettings = AgnoAPISettings(), **kwargs) -> APIRouter:
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
- metrics, latest_updated_at = db.get_metrics(starting_date=starting_date, ending_date=ending_date)
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
- result = db.calculate_metrics()
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")