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.
Files changed (383) hide show
  1. agno/agent/__init__.py +6 -0
  2. agno/agent/agent.py +5252 -3145
  3. agno/agent/remote.py +525 -0
  4. agno/api/api.py +2 -0
  5. agno/client/__init__.py +3 -0
  6. agno/client/a2a/__init__.py +10 -0
  7. agno/client/a2a/client.py +554 -0
  8. agno/client/a2a/schemas.py +112 -0
  9. agno/client/a2a/utils.py +369 -0
  10. agno/client/os.py +2669 -0
  11. agno/compression/__init__.py +3 -0
  12. agno/compression/manager.py +247 -0
  13. agno/culture/manager.py +2 -2
  14. agno/db/base.py +927 -6
  15. agno/db/dynamo/dynamo.py +788 -2
  16. agno/db/dynamo/schemas.py +128 -0
  17. agno/db/dynamo/utils.py +26 -3
  18. agno/db/firestore/firestore.py +674 -50
  19. agno/db/firestore/schemas.py +41 -0
  20. agno/db/firestore/utils.py +25 -10
  21. agno/db/gcs_json/gcs_json_db.py +506 -3
  22. agno/db/gcs_json/utils.py +14 -2
  23. agno/db/in_memory/in_memory_db.py +203 -4
  24. agno/db/in_memory/utils.py +14 -2
  25. agno/db/json/json_db.py +498 -2
  26. agno/db/json/utils.py +14 -2
  27. agno/db/migrations/manager.py +199 -0
  28. agno/db/migrations/utils.py +19 -0
  29. agno/db/migrations/v1_to_v2.py +54 -16
  30. agno/db/migrations/versions/__init__.py +0 -0
  31. agno/db/migrations/versions/v2_3_0.py +977 -0
  32. agno/db/mongo/async_mongo.py +1013 -39
  33. agno/db/mongo/mongo.py +684 -4
  34. agno/db/mongo/schemas.py +48 -0
  35. agno/db/mongo/utils.py +17 -0
  36. agno/db/mysql/__init__.py +2 -1
  37. agno/db/mysql/async_mysql.py +2958 -0
  38. agno/db/mysql/mysql.py +722 -53
  39. agno/db/mysql/schemas.py +77 -11
  40. agno/db/mysql/utils.py +151 -8
  41. agno/db/postgres/async_postgres.py +1254 -137
  42. agno/db/postgres/postgres.py +2316 -93
  43. agno/db/postgres/schemas.py +153 -21
  44. agno/db/postgres/utils.py +22 -7
  45. agno/db/redis/redis.py +531 -3
  46. agno/db/redis/schemas.py +36 -0
  47. agno/db/redis/utils.py +31 -15
  48. agno/db/schemas/evals.py +1 -0
  49. agno/db/schemas/memory.py +20 -9
  50. agno/db/singlestore/schemas.py +70 -1
  51. agno/db/singlestore/singlestore.py +737 -74
  52. agno/db/singlestore/utils.py +13 -3
  53. agno/db/sqlite/async_sqlite.py +1069 -89
  54. agno/db/sqlite/schemas.py +133 -1
  55. agno/db/sqlite/sqlite.py +2203 -165
  56. agno/db/sqlite/utils.py +21 -11
  57. agno/db/surrealdb/models.py +25 -0
  58. agno/db/surrealdb/surrealdb.py +603 -1
  59. agno/db/utils.py +60 -0
  60. agno/eval/__init__.py +26 -3
  61. agno/eval/accuracy.py +25 -12
  62. agno/eval/agent_as_judge.py +871 -0
  63. agno/eval/base.py +29 -0
  64. agno/eval/performance.py +10 -4
  65. agno/eval/reliability.py +22 -13
  66. agno/eval/utils.py +2 -1
  67. agno/exceptions.py +42 -0
  68. agno/hooks/__init__.py +3 -0
  69. agno/hooks/decorator.py +164 -0
  70. agno/integrations/discord/client.py +13 -2
  71. agno/knowledge/__init__.py +4 -0
  72. agno/knowledge/chunking/code.py +90 -0
  73. agno/knowledge/chunking/document.py +65 -4
  74. agno/knowledge/chunking/fixed.py +4 -1
  75. agno/knowledge/chunking/markdown.py +102 -11
  76. agno/knowledge/chunking/recursive.py +2 -2
  77. agno/knowledge/chunking/semantic.py +130 -48
  78. agno/knowledge/chunking/strategy.py +18 -0
  79. agno/knowledge/embedder/azure_openai.py +0 -1
  80. agno/knowledge/embedder/google.py +1 -1
  81. agno/knowledge/embedder/mistral.py +1 -1
  82. agno/knowledge/embedder/nebius.py +1 -1
  83. agno/knowledge/embedder/openai.py +16 -12
  84. agno/knowledge/filesystem.py +412 -0
  85. agno/knowledge/knowledge.py +4261 -1199
  86. agno/knowledge/protocol.py +134 -0
  87. agno/knowledge/reader/arxiv_reader.py +3 -2
  88. agno/knowledge/reader/base.py +9 -7
  89. agno/knowledge/reader/csv_reader.py +91 -42
  90. agno/knowledge/reader/docx_reader.py +9 -10
  91. agno/knowledge/reader/excel_reader.py +225 -0
  92. agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
  93. agno/knowledge/reader/firecrawl_reader.py +3 -2
  94. agno/knowledge/reader/json_reader.py +16 -22
  95. agno/knowledge/reader/markdown_reader.py +15 -14
  96. agno/knowledge/reader/pdf_reader.py +33 -28
  97. agno/knowledge/reader/pptx_reader.py +9 -10
  98. agno/knowledge/reader/reader_factory.py +135 -1
  99. agno/knowledge/reader/s3_reader.py +8 -16
  100. agno/knowledge/reader/tavily_reader.py +3 -3
  101. agno/knowledge/reader/text_reader.py +15 -14
  102. agno/knowledge/reader/utils/__init__.py +17 -0
  103. agno/knowledge/reader/utils/spreadsheet.py +114 -0
  104. agno/knowledge/reader/web_search_reader.py +8 -65
  105. agno/knowledge/reader/website_reader.py +16 -13
  106. agno/knowledge/reader/wikipedia_reader.py +36 -3
  107. agno/knowledge/reader/youtube_reader.py +3 -2
  108. agno/knowledge/remote_content/__init__.py +33 -0
  109. agno/knowledge/remote_content/config.py +266 -0
  110. agno/knowledge/remote_content/remote_content.py +105 -17
  111. agno/knowledge/utils.py +76 -22
  112. agno/learn/__init__.py +71 -0
  113. agno/learn/config.py +463 -0
  114. agno/learn/curate.py +185 -0
  115. agno/learn/machine.py +725 -0
  116. agno/learn/schemas.py +1114 -0
  117. agno/learn/stores/__init__.py +38 -0
  118. agno/learn/stores/decision_log.py +1156 -0
  119. agno/learn/stores/entity_memory.py +3275 -0
  120. agno/learn/stores/learned_knowledge.py +1583 -0
  121. agno/learn/stores/protocol.py +117 -0
  122. agno/learn/stores/session_context.py +1217 -0
  123. agno/learn/stores/user_memory.py +1495 -0
  124. agno/learn/stores/user_profile.py +1220 -0
  125. agno/learn/utils.py +209 -0
  126. agno/media.py +22 -6
  127. agno/memory/__init__.py +14 -1
  128. agno/memory/manager.py +223 -8
  129. agno/memory/strategies/__init__.py +15 -0
  130. agno/memory/strategies/base.py +66 -0
  131. agno/memory/strategies/summarize.py +196 -0
  132. agno/memory/strategies/types.py +37 -0
  133. agno/models/aimlapi/aimlapi.py +17 -0
  134. agno/models/anthropic/claude.py +434 -59
  135. agno/models/aws/bedrock.py +121 -20
  136. agno/models/aws/claude.py +131 -274
  137. agno/models/azure/ai_foundry.py +10 -6
  138. agno/models/azure/openai_chat.py +33 -10
  139. agno/models/base.py +1162 -561
  140. agno/models/cerebras/cerebras.py +120 -24
  141. agno/models/cerebras/cerebras_openai.py +21 -2
  142. agno/models/cohere/chat.py +65 -6
  143. agno/models/cometapi/cometapi.py +18 -1
  144. agno/models/dashscope/dashscope.py +2 -3
  145. agno/models/deepinfra/deepinfra.py +18 -1
  146. agno/models/deepseek/deepseek.py +69 -3
  147. agno/models/fireworks/fireworks.py +18 -1
  148. agno/models/google/gemini.py +959 -89
  149. agno/models/google/utils.py +22 -0
  150. agno/models/groq/groq.py +48 -18
  151. agno/models/huggingface/huggingface.py +17 -6
  152. agno/models/ibm/watsonx.py +16 -6
  153. agno/models/internlm/internlm.py +18 -1
  154. agno/models/langdb/langdb.py +13 -1
  155. agno/models/litellm/chat.py +88 -9
  156. agno/models/litellm/litellm_openai.py +18 -1
  157. agno/models/message.py +24 -5
  158. agno/models/meta/llama.py +40 -13
  159. agno/models/meta/llama_openai.py +22 -21
  160. agno/models/metrics.py +12 -0
  161. agno/models/mistral/mistral.py +8 -4
  162. agno/models/n1n/__init__.py +3 -0
  163. agno/models/n1n/n1n.py +57 -0
  164. agno/models/nebius/nebius.py +6 -7
  165. agno/models/nvidia/nvidia.py +20 -3
  166. agno/models/ollama/__init__.py +2 -0
  167. agno/models/ollama/chat.py +17 -6
  168. agno/models/ollama/responses.py +100 -0
  169. agno/models/openai/__init__.py +2 -0
  170. agno/models/openai/chat.py +117 -26
  171. agno/models/openai/open_responses.py +46 -0
  172. agno/models/openai/responses.py +110 -32
  173. agno/models/openrouter/__init__.py +2 -0
  174. agno/models/openrouter/openrouter.py +67 -2
  175. agno/models/openrouter/responses.py +146 -0
  176. agno/models/perplexity/perplexity.py +19 -1
  177. agno/models/portkey/portkey.py +7 -6
  178. agno/models/requesty/requesty.py +19 -2
  179. agno/models/response.py +20 -2
  180. agno/models/sambanova/sambanova.py +20 -3
  181. agno/models/siliconflow/siliconflow.py +19 -2
  182. agno/models/together/together.py +20 -3
  183. agno/models/vercel/v0.py +20 -3
  184. agno/models/vertexai/claude.py +124 -4
  185. agno/models/vllm/vllm.py +19 -14
  186. agno/models/xai/xai.py +19 -2
  187. agno/os/app.py +467 -137
  188. agno/os/auth.py +253 -5
  189. agno/os/config.py +22 -0
  190. agno/os/interfaces/a2a/a2a.py +7 -6
  191. agno/os/interfaces/a2a/router.py +635 -26
  192. agno/os/interfaces/a2a/utils.py +32 -33
  193. agno/os/interfaces/agui/agui.py +5 -3
  194. agno/os/interfaces/agui/router.py +26 -16
  195. agno/os/interfaces/agui/utils.py +97 -57
  196. agno/os/interfaces/base.py +7 -7
  197. agno/os/interfaces/slack/router.py +16 -7
  198. agno/os/interfaces/slack/slack.py +7 -7
  199. agno/os/interfaces/whatsapp/router.py +35 -7
  200. agno/os/interfaces/whatsapp/security.py +3 -1
  201. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  202. agno/os/managers.py +326 -0
  203. agno/os/mcp.py +652 -79
  204. agno/os/middleware/__init__.py +4 -0
  205. agno/os/middleware/jwt.py +718 -115
  206. agno/os/middleware/trailing_slash.py +27 -0
  207. agno/os/router.py +105 -1558
  208. agno/os/routers/agents/__init__.py +3 -0
  209. agno/os/routers/agents/router.py +655 -0
  210. agno/os/routers/agents/schema.py +288 -0
  211. agno/os/routers/components/__init__.py +3 -0
  212. agno/os/routers/components/components.py +475 -0
  213. agno/os/routers/database.py +155 -0
  214. agno/os/routers/evals/evals.py +111 -18
  215. agno/os/routers/evals/schemas.py +38 -5
  216. agno/os/routers/evals/utils.py +80 -11
  217. agno/os/routers/health.py +3 -3
  218. agno/os/routers/knowledge/knowledge.py +284 -35
  219. agno/os/routers/knowledge/schemas.py +14 -2
  220. agno/os/routers/memory/memory.py +274 -11
  221. agno/os/routers/memory/schemas.py +44 -3
  222. agno/os/routers/metrics/metrics.py +30 -15
  223. agno/os/routers/metrics/schemas.py +10 -6
  224. agno/os/routers/registry/__init__.py +3 -0
  225. agno/os/routers/registry/registry.py +337 -0
  226. agno/os/routers/session/session.py +143 -14
  227. agno/os/routers/teams/__init__.py +3 -0
  228. agno/os/routers/teams/router.py +550 -0
  229. agno/os/routers/teams/schema.py +280 -0
  230. agno/os/routers/traces/__init__.py +3 -0
  231. agno/os/routers/traces/schemas.py +414 -0
  232. agno/os/routers/traces/traces.py +549 -0
  233. agno/os/routers/workflows/__init__.py +3 -0
  234. agno/os/routers/workflows/router.py +757 -0
  235. agno/os/routers/workflows/schema.py +139 -0
  236. agno/os/schema.py +157 -584
  237. agno/os/scopes.py +469 -0
  238. agno/os/settings.py +3 -0
  239. agno/os/utils.py +574 -185
  240. agno/reasoning/anthropic.py +85 -1
  241. agno/reasoning/azure_ai_foundry.py +93 -1
  242. agno/reasoning/deepseek.py +102 -2
  243. agno/reasoning/default.py +6 -7
  244. agno/reasoning/gemini.py +87 -3
  245. agno/reasoning/groq.py +109 -2
  246. agno/reasoning/helpers.py +6 -7
  247. agno/reasoning/manager.py +1238 -0
  248. agno/reasoning/ollama.py +93 -1
  249. agno/reasoning/openai.py +115 -1
  250. agno/reasoning/vertexai.py +85 -1
  251. agno/registry/__init__.py +3 -0
  252. agno/registry/registry.py +68 -0
  253. agno/remote/__init__.py +3 -0
  254. agno/remote/base.py +581 -0
  255. agno/run/__init__.py +2 -4
  256. agno/run/agent.py +134 -19
  257. agno/run/base.py +49 -1
  258. agno/run/cancel.py +65 -52
  259. agno/run/cancellation_management/__init__.py +9 -0
  260. agno/run/cancellation_management/base.py +78 -0
  261. agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
  262. agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
  263. agno/run/requirement.py +181 -0
  264. agno/run/team.py +111 -19
  265. agno/run/workflow.py +2 -1
  266. agno/session/agent.py +57 -92
  267. agno/session/summary.py +1 -1
  268. agno/session/team.py +62 -115
  269. agno/session/workflow.py +353 -57
  270. agno/skills/__init__.py +17 -0
  271. agno/skills/agent_skills.py +377 -0
  272. agno/skills/errors.py +32 -0
  273. agno/skills/loaders/__init__.py +4 -0
  274. agno/skills/loaders/base.py +27 -0
  275. agno/skills/loaders/local.py +216 -0
  276. agno/skills/skill.py +65 -0
  277. agno/skills/utils.py +107 -0
  278. agno/skills/validator.py +277 -0
  279. agno/table.py +10 -0
  280. agno/team/__init__.py +5 -1
  281. agno/team/remote.py +447 -0
  282. agno/team/team.py +3769 -2202
  283. agno/tools/brandfetch.py +27 -18
  284. agno/tools/browserbase.py +225 -16
  285. agno/tools/crawl4ai.py +3 -0
  286. agno/tools/duckduckgo.py +25 -71
  287. agno/tools/exa.py +0 -21
  288. agno/tools/file.py +14 -13
  289. agno/tools/file_generation.py +12 -6
  290. agno/tools/firecrawl.py +15 -7
  291. agno/tools/function.py +94 -113
  292. agno/tools/google_bigquery.py +11 -2
  293. agno/tools/google_drive.py +4 -3
  294. agno/tools/knowledge.py +9 -4
  295. agno/tools/mcp/mcp.py +301 -18
  296. agno/tools/mcp/multi_mcp.py +269 -14
  297. agno/tools/mem0.py +11 -10
  298. agno/tools/memory.py +47 -46
  299. agno/tools/mlx_transcribe.py +10 -7
  300. agno/tools/models/nebius.py +5 -5
  301. agno/tools/models_labs.py +20 -10
  302. agno/tools/nano_banana.py +151 -0
  303. agno/tools/parallel.py +0 -7
  304. agno/tools/postgres.py +76 -36
  305. agno/tools/python.py +14 -6
  306. agno/tools/reasoning.py +30 -23
  307. agno/tools/redshift.py +406 -0
  308. agno/tools/shopify.py +1519 -0
  309. agno/tools/spotify.py +919 -0
  310. agno/tools/tavily.py +4 -1
  311. agno/tools/toolkit.py +253 -18
  312. agno/tools/websearch.py +93 -0
  313. agno/tools/website.py +1 -1
  314. agno/tools/wikipedia.py +1 -1
  315. agno/tools/workflow.py +56 -48
  316. agno/tools/yfinance.py +12 -11
  317. agno/tracing/__init__.py +12 -0
  318. agno/tracing/exporter.py +161 -0
  319. agno/tracing/schemas.py +276 -0
  320. agno/tracing/setup.py +112 -0
  321. agno/utils/agent.py +251 -10
  322. agno/utils/cryptography.py +22 -0
  323. agno/utils/dttm.py +33 -0
  324. agno/utils/events.py +264 -7
  325. agno/utils/hooks.py +111 -3
  326. agno/utils/http.py +161 -2
  327. agno/utils/mcp.py +49 -8
  328. agno/utils/media.py +22 -1
  329. agno/utils/models/ai_foundry.py +9 -2
  330. agno/utils/models/claude.py +20 -5
  331. agno/utils/models/cohere.py +9 -2
  332. agno/utils/models/llama.py +9 -2
  333. agno/utils/models/mistral.py +4 -2
  334. agno/utils/os.py +0 -0
  335. agno/utils/print_response/agent.py +99 -16
  336. agno/utils/print_response/team.py +223 -24
  337. agno/utils/print_response/workflow.py +0 -2
  338. agno/utils/prompts.py +8 -6
  339. agno/utils/remote.py +23 -0
  340. agno/utils/response.py +1 -13
  341. agno/utils/string.py +91 -2
  342. agno/utils/team.py +62 -12
  343. agno/utils/tokens.py +657 -0
  344. agno/vectordb/base.py +15 -2
  345. agno/vectordb/cassandra/cassandra.py +1 -1
  346. agno/vectordb/chroma/__init__.py +2 -1
  347. agno/vectordb/chroma/chromadb.py +468 -23
  348. agno/vectordb/clickhouse/clickhousedb.py +1 -1
  349. agno/vectordb/couchbase/couchbase.py +6 -2
  350. agno/vectordb/lancedb/lance_db.py +7 -38
  351. agno/vectordb/lightrag/lightrag.py +7 -6
  352. agno/vectordb/milvus/milvus.py +118 -84
  353. agno/vectordb/mongodb/__init__.py +2 -1
  354. agno/vectordb/mongodb/mongodb.py +14 -31
  355. agno/vectordb/pgvector/pgvector.py +120 -66
  356. agno/vectordb/pineconedb/pineconedb.py +2 -19
  357. agno/vectordb/qdrant/__init__.py +2 -1
  358. agno/vectordb/qdrant/qdrant.py +33 -56
  359. agno/vectordb/redis/__init__.py +2 -1
  360. agno/vectordb/redis/redisdb.py +19 -31
  361. agno/vectordb/singlestore/singlestore.py +17 -9
  362. agno/vectordb/surrealdb/surrealdb.py +2 -38
  363. agno/vectordb/weaviate/__init__.py +2 -1
  364. agno/vectordb/weaviate/weaviate.py +7 -3
  365. agno/workflow/__init__.py +5 -1
  366. agno/workflow/agent.py +2 -2
  367. agno/workflow/condition.py +12 -10
  368. agno/workflow/loop.py +28 -9
  369. agno/workflow/parallel.py +21 -13
  370. agno/workflow/remote.py +362 -0
  371. agno/workflow/router.py +12 -9
  372. agno/workflow/step.py +261 -36
  373. agno/workflow/steps.py +12 -8
  374. agno/workflow/types.py +40 -77
  375. agno/workflow/workflow.py +939 -213
  376. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
  377. agno-2.4.3.dist-info/RECORD +677 -0
  378. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
  379. agno/tools/googlesearch.py +0 -98
  380. agno/tools/memori.py +0 -339
  381. agno-2.2.13.dist-info/RECORD +0 -575
  382. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
  383. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
@@ -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.os.auth import get_authentication_dependency
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
- limit: Optional[int] = Query(default=20, description="Number of user statistics to return per page"),
467
- page: Optional[int] = Query(default=1, description="Page number for pagination"),
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
- from datetime import datetime, timezone
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=memory_dict["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=datetime.fromtimestamp(updated_at, tz=timezone.utc) if updated_at else None,
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, datetime, timezone
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:00",
82
- "created_at": 1753993132,
83
- "updated_at": 1753993741,
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=datetime.fromtimestamp(latest_updated_at, tz=timezone.utc)
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:00",
161
- "created_at": 1755016907,
162
- "updated_at": 1755016907,
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: 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
+ 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
- created_at=metrics_dict.get("created_at", 0),
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
- updated_at=metrics_dict.get("updated_at", 0),
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),
@@ -0,0 +1,3 @@
1
+ from agno.os.routers.registry.registry import get_registry_router
2
+
3
+ __all__ = ["get_registry_router"]