agno 1.8.1__py3-none-any.whl → 2.0.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 (590) hide show
  1. agno/__init__.py +8 -0
  2. agno/agent/__init__.py +19 -27
  3. agno/agent/agent.py +3143 -4170
  4. agno/api/agent.py +11 -67
  5. agno/api/api.py +5 -46
  6. agno/api/evals.py +8 -19
  7. agno/api/os.py +17 -0
  8. agno/api/routes.py +6 -41
  9. agno/api/schemas/__init__.py +9 -0
  10. agno/api/schemas/agent.py +5 -21
  11. agno/api/schemas/evals.py +7 -16
  12. agno/api/schemas/os.py +14 -0
  13. agno/api/schemas/team.py +5 -21
  14. agno/api/schemas/utils.py +21 -0
  15. agno/api/schemas/workflows.py +11 -7
  16. agno/api/settings.py +53 -0
  17. agno/api/team.py +11 -66
  18. agno/api/workflow.py +28 -0
  19. agno/cloud/aws/base.py +214 -0
  20. agno/cloud/aws/s3/__init__.py +2 -0
  21. agno/cloud/aws/s3/api_client.py +43 -0
  22. agno/cloud/aws/s3/bucket.py +195 -0
  23. agno/cloud/aws/s3/object.py +57 -0
  24. agno/db/__init__.py +24 -0
  25. agno/db/base.py +245 -0
  26. agno/db/dynamo/__init__.py +3 -0
  27. agno/db/dynamo/dynamo.py +1743 -0
  28. agno/db/dynamo/schemas.py +278 -0
  29. agno/db/dynamo/utils.py +684 -0
  30. agno/db/firestore/__init__.py +3 -0
  31. agno/db/firestore/firestore.py +1432 -0
  32. agno/db/firestore/schemas.py +130 -0
  33. agno/db/firestore/utils.py +278 -0
  34. agno/db/gcs_json/__init__.py +3 -0
  35. agno/db/gcs_json/gcs_json_db.py +1001 -0
  36. agno/db/gcs_json/utils.py +194 -0
  37. agno/db/in_memory/__init__.py +3 -0
  38. agno/db/in_memory/in_memory_db.py +882 -0
  39. agno/db/in_memory/utils.py +172 -0
  40. agno/db/json/__init__.py +3 -0
  41. agno/db/json/json_db.py +1045 -0
  42. agno/db/json/utils.py +196 -0
  43. agno/db/migrations/v1_to_v2.py +162 -0
  44. agno/db/mongo/__init__.py +3 -0
  45. agno/db/mongo/mongo.py +1416 -0
  46. agno/db/mongo/schemas.py +77 -0
  47. agno/db/mongo/utils.py +204 -0
  48. agno/db/mysql/__init__.py +3 -0
  49. agno/db/mysql/mysql.py +1719 -0
  50. agno/db/mysql/schemas.py +124 -0
  51. agno/db/mysql/utils.py +297 -0
  52. agno/db/postgres/__init__.py +3 -0
  53. agno/db/postgres/postgres.py +1710 -0
  54. agno/db/postgres/schemas.py +124 -0
  55. agno/db/postgres/utils.py +280 -0
  56. agno/db/redis/__init__.py +3 -0
  57. agno/db/redis/redis.py +1367 -0
  58. agno/db/redis/schemas.py +109 -0
  59. agno/db/redis/utils.py +288 -0
  60. agno/db/schemas/__init__.py +3 -0
  61. agno/db/schemas/evals.py +33 -0
  62. agno/db/schemas/knowledge.py +40 -0
  63. agno/db/schemas/memory.py +46 -0
  64. agno/db/singlestore/__init__.py +3 -0
  65. agno/db/singlestore/schemas.py +116 -0
  66. agno/db/singlestore/singlestore.py +1712 -0
  67. agno/db/singlestore/utils.py +326 -0
  68. agno/db/sqlite/__init__.py +3 -0
  69. agno/db/sqlite/schemas.py +119 -0
  70. agno/db/sqlite/sqlite.py +1676 -0
  71. agno/db/sqlite/utils.py +268 -0
  72. agno/db/utils.py +88 -0
  73. agno/eval/__init__.py +14 -0
  74. agno/eval/accuracy.py +154 -48
  75. agno/eval/performance.py +88 -23
  76. agno/eval/reliability.py +73 -20
  77. agno/eval/utils.py +23 -13
  78. agno/integrations/discord/__init__.py +3 -0
  79. agno/{app → integrations}/discord/client.py +15 -11
  80. agno/knowledge/__init__.py +2 -2
  81. agno/{document → knowledge}/chunking/agentic.py +2 -2
  82. agno/{document → knowledge}/chunking/document.py +2 -2
  83. agno/{document → knowledge}/chunking/fixed.py +3 -3
  84. agno/{document → knowledge}/chunking/markdown.py +2 -2
  85. agno/{document → knowledge}/chunking/recursive.py +2 -2
  86. agno/{document → knowledge}/chunking/row.py +2 -2
  87. agno/knowledge/chunking/semantic.py +59 -0
  88. agno/knowledge/chunking/strategy.py +121 -0
  89. agno/knowledge/content.py +74 -0
  90. agno/knowledge/document/__init__.py +5 -0
  91. agno/{document → knowledge/document}/base.py +12 -2
  92. agno/knowledge/embedder/__init__.py +5 -0
  93. agno/{embedder → knowledge/embedder}/aws_bedrock.py +127 -1
  94. agno/{embedder → knowledge/embedder}/azure_openai.py +65 -1
  95. agno/{embedder → knowledge/embedder}/base.py +6 -0
  96. agno/{embedder → knowledge/embedder}/cohere.py +72 -1
  97. agno/{embedder → knowledge/embedder}/fastembed.py +17 -1
  98. agno/{embedder → knowledge/embedder}/fireworks.py +1 -1
  99. agno/{embedder → knowledge/embedder}/google.py +74 -1
  100. agno/{embedder → knowledge/embedder}/huggingface.py +36 -2
  101. agno/{embedder → knowledge/embedder}/jina.py +48 -2
  102. agno/knowledge/embedder/langdb.py +22 -0
  103. agno/knowledge/embedder/mistral.py +139 -0
  104. agno/{embedder → knowledge/embedder}/nebius.py +1 -1
  105. agno/{embedder → knowledge/embedder}/ollama.py +54 -3
  106. agno/knowledge/embedder/openai.py +223 -0
  107. agno/{embedder → knowledge/embedder}/sentence_transformer.py +16 -1
  108. agno/{embedder → knowledge/embedder}/together.py +1 -1
  109. agno/{embedder → knowledge/embedder}/voyageai.py +49 -1
  110. agno/knowledge/knowledge.py +1551 -0
  111. agno/knowledge/reader/__init__.py +7 -0
  112. agno/{document → knowledge}/reader/arxiv_reader.py +32 -4
  113. agno/knowledge/reader/base.py +88 -0
  114. agno/{document → knowledge}/reader/csv_reader.py +47 -65
  115. agno/knowledge/reader/docx_reader.py +83 -0
  116. agno/{document → knowledge}/reader/firecrawl_reader.py +42 -21
  117. agno/{document → knowledge}/reader/json_reader.py +30 -9
  118. agno/{document → knowledge}/reader/markdown_reader.py +58 -9
  119. agno/{document → knowledge}/reader/pdf_reader.py +71 -126
  120. agno/knowledge/reader/reader_factory.py +268 -0
  121. agno/knowledge/reader/s3_reader.py +101 -0
  122. agno/{document → knowledge}/reader/text_reader.py +31 -10
  123. agno/knowledge/reader/url_reader.py +128 -0
  124. agno/knowledge/reader/web_search_reader.py +366 -0
  125. agno/{document → knowledge}/reader/website_reader.py +37 -10
  126. agno/knowledge/reader/wikipedia_reader.py +59 -0
  127. agno/knowledge/reader/youtube_reader.py +78 -0
  128. agno/knowledge/remote_content/remote_content.py +88 -0
  129. agno/{reranker → knowledge/reranker}/base.py +1 -1
  130. agno/{reranker → knowledge/reranker}/cohere.py +2 -2
  131. agno/{reranker → knowledge/reranker}/infinity.py +2 -2
  132. agno/{reranker → knowledge/reranker}/sentence_transformer.py +2 -2
  133. agno/knowledge/types.py +30 -0
  134. agno/knowledge/utils.py +169 -0
  135. agno/media.py +269 -268
  136. agno/memory/__init__.py +2 -10
  137. agno/memory/manager.py +1003 -148
  138. agno/models/aimlapi/__init__.py +2 -2
  139. agno/models/aimlapi/aimlapi.py +6 -6
  140. agno/models/anthropic/claude.py +131 -131
  141. agno/models/aws/bedrock.py +110 -182
  142. agno/models/aws/claude.py +64 -18
  143. agno/models/azure/ai_foundry.py +73 -23
  144. agno/models/base.py +346 -290
  145. agno/models/cerebras/cerebras.py +84 -27
  146. agno/models/cohere/chat.py +106 -98
  147. agno/models/google/gemini.py +105 -46
  148. agno/models/groq/groq.py +97 -35
  149. agno/models/huggingface/huggingface.py +92 -27
  150. agno/models/ibm/watsonx.py +72 -13
  151. agno/models/litellm/chat.py +85 -13
  152. agno/models/message.py +46 -151
  153. agno/models/meta/llama.py +85 -49
  154. agno/models/metrics.py +120 -0
  155. agno/models/mistral/mistral.py +90 -21
  156. agno/models/ollama/__init__.py +0 -2
  157. agno/models/ollama/chat.py +85 -47
  158. agno/models/openai/chat.py +154 -37
  159. agno/models/openai/responses.py +178 -105
  160. agno/models/perplexity/perplexity.py +26 -2
  161. agno/models/portkey/portkey.py +0 -7
  162. agno/models/response.py +15 -9
  163. agno/models/utils.py +20 -0
  164. agno/models/vercel/__init__.py +2 -2
  165. agno/models/vercel/v0.py +1 -1
  166. agno/models/vllm/__init__.py +2 -2
  167. agno/models/vllm/vllm.py +3 -3
  168. agno/models/xai/xai.py +10 -10
  169. agno/os/__init__.py +3 -0
  170. agno/os/app.py +497 -0
  171. agno/os/auth.py +47 -0
  172. agno/os/config.py +103 -0
  173. agno/os/interfaces/agui/__init__.py +3 -0
  174. agno/os/interfaces/agui/agui.py +31 -0
  175. agno/{app/agui/async_router.py → os/interfaces/agui/router.py} +16 -16
  176. agno/{app → os/interfaces}/agui/utils.py +77 -33
  177. agno/os/interfaces/base.py +21 -0
  178. agno/os/interfaces/slack/__init__.py +3 -0
  179. agno/{app/slack/async_router.py → os/interfaces/slack/router.py} +3 -5
  180. agno/os/interfaces/slack/slack.py +32 -0
  181. agno/os/interfaces/whatsapp/__init__.py +3 -0
  182. agno/{app/whatsapp/async_router.py → os/interfaces/whatsapp/router.py} +4 -7
  183. agno/os/interfaces/whatsapp/whatsapp.py +29 -0
  184. agno/os/mcp.py +235 -0
  185. agno/os/router.py +1400 -0
  186. agno/os/routers/__init__.py +3 -0
  187. agno/os/routers/evals/__init__.py +3 -0
  188. agno/os/routers/evals/evals.py +393 -0
  189. agno/os/routers/evals/schemas.py +142 -0
  190. agno/os/routers/evals/utils.py +161 -0
  191. agno/os/routers/knowledge/__init__.py +3 -0
  192. agno/os/routers/knowledge/knowledge.py +850 -0
  193. agno/os/routers/knowledge/schemas.py +118 -0
  194. agno/os/routers/memory/__init__.py +3 -0
  195. agno/os/routers/memory/memory.py +410 -0
  196. agno/os/routers/memory/schemas.py +58 -0
  197. agno/os/routers/metrics/__init__.py +3 -0
  198. agno/os/routers/metrics/metrics.py +178 -0
  199. agno/os/routers/metrics/schemas.py +47 -0
  200. agno/os/routers/session/__init__.py +3 -0
  201. agno/os/routers/session/session.py +536 -0
  202. agno/os/schema.py +945 -0
  203. agno/{app/playground → os}/settings.py +7 -15
  204. agno/os/utils.py +270 -0
  205. agno/reasoning/azure_ai_foundry.py +4 -4
  206. agno/reasoning/deepseek.py +4 -4
  207. agno/reasoning/default.py +6 -11
  208. agno/reasoning/groq.py +4 -4
  209. agno/reasoning/helpers.py +4 -6
  210. agno/reasoning/ollama.py +4 -4
  211. agno/reasoning/openai.py +4 -4
  212. agno/run/agent.py +633 -0
  213. agno/run/base.py +53 -77
  214. agno/run/cancel.py +81 -0
  215. agno/run/team.py +243 -96
  216. agno/run/workflow.py +550 -12
  217. agno/session/__init__.py +10 -0
  218. agno/session/agent.py +244 -0
  219. agno/session/summary.py +225 -0
  220. agno/session/team.py +262 -0
  221. agno/{storage/session/v2 → session}/workflow.py +47 -24
  222. agno/team/__init__.py +15 -16
  223. agno/team/team.py +3260 -4824
  224. agno/tools/agentql.py +14 -5
  225. agno/tools/airflow.py +9 -4
  226. agno/tools/api.py +7 -3
  227. agno/tools/apify.py +2 -46
  228. agno/tools/arxiv.py +8 -3
  229. agno/tools/aws_lambda.py +7 -5
  230. agno/tools/aws_ses.py +7 -1
  231. agno/tools/baidusearch.py +4 -1
  232. agno/tools/bitbucket.py +4 -4
  233. agno/tools/brandfetch.py +14 -11
  234. agno/tools/bravesearch.py +4 -1
  235. agno/tools/brightdata.py +43 -23
  236. agno/tools/browserbase.py +13 -4
  237. agno/tools/calcom.py +12 -10
  238. agno/tools/calculator.py +10 -27
  239. agno/tools/cartesia.py +20 -17
  240. agno/tools/{clickup_tool.py → clickup.py} +12 -25
  241. agno/tools/confluence.py +8 -8
  242. agno/tools/crawl4ai.py +7 -1
  243. agno/tools/csv_toolkit.py +9 -8
  244. agno/tools/dalle.py +22 -12
  245. agno/tools/daytona.py +13 -16
  246. agno/tools/decorator.py +6 -3
  247. agno/tools/desi_vocal.py +17 -8
  248. agno/tools/discord.py +11 -8
  249. agno/tools/docker.py +30 -42
  250. agno/tools/duckdb.py +34 -53
  251. agno/tools/duckduckgo.py +8 -7
  252. agno/tools/e2b.py +62 -62
  253. agno/tools/eleven_labs.py +36 -29
  254. agno/tools/email.py +4 -1
  255. agno/tools/evm.py +7 -1
  256. agno/tools/exa.py +19 -14
  257. agno/tools/fal.py +30 -30
  258. agno/tools/file.py +9 -8
  259. agno/tools/financial_datasets.py +25 -44
  260. agno/tools/firecrawl.py +22 -22
  261. agno/tools/function.py +127 -18
  262. agno/tools/giphy.py +23 -11
  263. agno/tools/github.py +48 -126
  264. agno/tools/gmail.py +45 -61
  265. agno/tools/google_bigquery.py +7 -6
  266. agno/tools/google_maps.py +11 -26
  267. agno/tools/googlesearch.py +7 -2
  268. agno/tools/googlesheets.py +21 -17
  269. agno/tools/hackernews.py +9 -5
  270. agno/tools/jina.py +5 -4
  271. agno/tools/jira.py +18 -9
  272. agno/tools/knowledge.py +31 -32
  273. agno/tools/linear.py +19 -34
  274. agno/tools/linkup.py +5 -1
  275. agno/tools/local_file_system.py +8 -5
  276. agno/tools/lumalab.py +32 -20
  277. agno/tools/mcp.py +1 -2
  278. agno/tools/mem0.py +18 -12
  279. agno/tools/memori.py +14 -10
  280. agno/tools/mlx_transcribe.py +3 -2
  281. agno/tools/models/azure_openai.py +33 -15
  282. agno/tools/models/gemini.py +59 -32
  283. agno/tools/models/groq.py +30 -23
  284. agno/tools/models/nebius.py +28 -12
  285. agno/tools/models_labs.py +40 -16
  286. agno/tools/moviepy_video.py +7 -6
  287. agno/tools/neo4j.py +10 -8
  288. agno/tools/newspaper.py +7 -2
  289. agno/tools/newspaper4k.py +8 -3
  290. agno/tools/openai.py +58 -32
  291. agno/tools/openbb.py +12 -11
  292. agno/tools/opencv.py +63 -47
  293. agno/tools/openweather.py +14 -12
  294. agno/tools/pandas.py +11 -3
  295. agno/tools/postgres.py +4 -12
  296. agno/tools/pubmed.py +4 -1
  297. agno/tools/python.py +9 -22
  298. agno/tools/reasoning.py +35 -27
  299. agno/tools/reddit.py +11 -26
  300. agno/tools/replicate.py +55 -42
  301. agno/tools/resend.py +4 -1
  302. agno/tools/scrapegraph.py +15 -14
  303. agno/tools/searxng.py +10 -23
  304. agno/tools/serpapi.py +6 -3
  305. agno/tools/serper.py +13 -4
  306. agno/tools/shell.py +9 -2
  307. agno/tools/slack.py +12 -11
  308. agno/tools/sleep.py +3 -2
  309. agno/tools/spider.py +24 -4
  310. agno/tools/sql.py +7 -6
  311. agno/tools/tavily.py +6 -4
  312. agno/tools/telegram.py +12 -4
  313. agno/tools/todoist.py +11 -31
  314. agno/tools/toolkit.py +1 -1
  315. agno/tools/trafilatura.py +22 -6
  316. agno/tools/trello.py +9 -22
  317. agno/tools/twilio.py +10 -3
  318. agno/tools/user_control_flow.py +6 -1
  319. agno/tools/valyu.py +34 -5
  320. agno/tools/visualization.py +19 -28
  321. agno/tools/webbrowser.py +4 -3
  322. agno/tools/webex.py +11 -7
  323. agno/tools/website.py +15 -46
  324. agno/tools/webtools.py +12 -4
  325. agno/tools/whatsapp.py +5 -9
  326. agno/tools/wikipedia.py +20 -13
  327. agno/tools/x.py +14 -13
  328. agno/tools/yfinance.py +13 -40
  329. agno/tools/youtube.py +26 -20
  330. agno/tools/zendesk.py +7 -2
  331. agno/tools/zep.py +10 -7
  332. agno/tools/zoom.py +10 -9
  333. agno/utils/common.py +1 -19
  334. agno/utils/events.py +100 -123
  335. agno/utils/gemini.py +32 -2
  336. agno/utils/knowledge.py +29 -0
  337. agno/utils/log.py +54 -4
  338. agno/utils/mcp.py +68 -10
  339. agno/utils/media.py +39 -0
  340. agno/utils/message.py +12 -1
  341. agno/utils/models/aws_claude.py +1 -1
  342. agno/utils/models/claude.py +47 -4
  343. agno/utils/models/cohere.py +1 -1
  344. agno/utils/models/mistral.py +8 -7
  345. agno/utils/models/schema_utils.py +3 -3
  346. agno/utils/models/watsonx.py +1 -1
  347. agno/utils/openai.py +1 -1
  348. agno/utils/pprint.py +33 -32
  349. agno/utils/print_response/agent.py +779 -0
  350. agno/utils/print_response/team.py +1669 -0
  351. agno/utils/print_response/workflow.py +1451 -0
  352. agno/utils/prompts.py +14 -14
  353. agno/utils/reasoning.py +87 -0
  354. agno/utils/response.py +42 -42
  355. agno/utils/streamlit.py +481 -0
  356. agno/utils/string.py +8 -22
  357. agno/utils/team.py +50 -0
  358. agno/utils/timer.py +2 -2
  359. agno/vectordb/base.py +33 -21
  360. agno/vectordb/cassandra/cassandra.py +287 -23
  361. agno/vectordb/chroma/chromadb.py +482 -59
  362. agno/vectordb/clickhouse/clickhousedb.py +270 -63
  363. agno/vectordb/couchbase/couchbase.py +309 -29
  364. agno/vectordb/lancedb/lance_db.py +360 -21
  365. agno/vectordb/langchaindb/__init__.py +5 -0
  366. agno/vectordb/langchaindb/langchaindb.py +145 -0
  367. agno/vectordb/lightrag/__init__.py +5 -0
  368. agno/vectordb/lightrag/lightrag.py +374 -0
  369. agno/vectordb/llamaindex/llamaindexdb.py +127 -0
  370. agno/vectordb/milvus/milvus.py +242 -32
  371. agno/vectordb/mongodb/mongodb.py +200 -24
  372. agno/vectordb/pgvector/pgvector.py +319 -37
  373. agno/vectordb/pineconedb/pineconedb.py +221 -27
  374. agno/vectordb/qdrant/qdrant.py +334 -14
  375. agno/vectordb/singlestore/singlestore.py +286 -29
  376. agno/vectordb/surrealdb/surrealdb.py +187 -7
  377. agno/vectordb/upstashdb/upstashdb.py +342 -26
  378. agno/vectordb/weaviate/weaviate.py +227 -165
  379. agno/workflow/__init__.py +17 -13
  380. agno/workflow/{v2/condition.py → condition.py} +135 -32
  381. agno/workflow/{v2/loop.py → loop.py} +115 -28
  382. agno/workflow/{v2/parallel.py → parallel.py} +138 -108
  383. agno/workflow/{v2/router.py → router.py} +133 -32
  384. agno/workflow/{v2/step.py → step.py} +207 -49
  385. agno/workflow/{v2/steps.py → steps.py} +147 -66
  386. agno/workflow/types.py +482 -0
  387. agno/workflow/workflow.py +2410 -696
  388. agno-2.0.0.dist-info/METADATA +494 -0
  389. agno-2.0.0.dist-info/RECORD +515 -0
  390. agno-2.0.0.dist-info/licenses/LICENSE +201 -0
  391. agno/agent/metrics.py +0 -107
  392. agno/api/app.py +0 -35
  393. agno/api/playground.py +0 -92
  394. agno/api/schemas/app.py +0 -12
  395. agno/api/schemas/playground.py +0 -22
  396. agno/api/schemas/user.py +0 -35
  397. agno/api/schemas/workspace.py +0 -46
  398. agno/api/user.py +0 -160
  399. agno/api/workflows.py +0 -33
  400. agno/api/workspace.py +0 -175
  401. agno/app/agui/__init__.py +0 -3
  402. agno/app/agui/app.py +0 -17
  403. agno/app/agui/sync_router.py +0 -120
  404. agno/app/base.py +0 -186
  405. agno/app/discord/__init__.py +0 -3
  406. agno/app/fastapi/__init__.py +0 -3
  407. agno/app/fastapi/app.py +0 -107
  408. agno/app/fastapi/async_router.py +0 -457
  409. agno/app/fastapi/sync_router.py +0 -448
  410. agno/app/playground/app.py +0 -228
  411. agno/app/playground/async_router.py +0 -1050
  412. agno/app/playground/deploy.py +0 -249
  413. agno/app/playground/operator.py +0 -183
  414. agno/app/playground/schemas.py +0 -220
  415. agno/app/playground/serve.py +0 -55
  416. agno/app/playground/sync_router.py +0 -1042
  417. agno/app/playground/utils.py +0 -46
  418. agno/app/settings.py +0 -15
  419. agno/app/slack/__init__.py +0 -3
  420. agno/app/slack/app.py +0 -19
  421. agno/app/slack/sync_router.py +0 -92
  422. agno/app/utils.py +0 -54
  423. agno/app/whatsapp/__init__.py +0 -3
  424. agno/app/whatsapp/app.py +0 -15
  425. agno/app/whatsapp/sync_router.py +0 -197
  426. agno/cli/auth_server.py +0 -249
  427. agno/cli/config.py +0 -274
  428. agno/cli/console.py +0 -88
  429. agno/cli/credentials.py +0 -23
  430. agno/cli/entrypoint.py +0 -571
  431. agno/cli/operator.py +0 -357
  432. agno/cli/settings.py +0 -96
  433. agno/cli/ws/ws_cli.py +0 -817
  434. agno/constants.py +0 -13
  435. agno/document/__init__.py +0 -5
  436. agno/document/chunking/semantic.py +0 -45
  437. agno/document/chunking/strategy.py +0 -31
  438. agno/document/reader/__init__.py +0 -5
  439. agno/document/reader/base.py +0 -47
  440. agno/document/reader/docx_reader.py +0 -60
  441. agno/document/reader/gcs/pdf_reader.py +0 -44
  442. agno/document/reader/s3/pdf_reader.py +0 -59
  443. agno/document/reader/s3/text_reader.py +0 -63
  444. agno/document/reader/url_reader.py +0 -59
  445. agno/document/reader/youtube_reader.py +0 -58
  446. agno/embedder/__init__.py +0 -5
  447. agno/embedder/langdb.py +0 -80
  448. agno/embedder/mistral.py +0 -82
  449. agno/embedder/openai.py +0 -78
  450. agno/file/__init__.py +0 -5
  451. agno/file/file.py +0 -16
  452. agno/file/local/csv.py +0 -32
  453. agno/file/local/txt.py +0 -19
  454. agno/infra/app.py +0 -240
  455. agno/infra/base.py +0 -144
  456. agno/infra/context.py +0 -20
  457. agno/infra/db_app.py +0 -52
  458. agno/infra/resource.py +0 -205
  459. agno/infra/resources.py +0 -55
  460. agno/knowledge/agent.py +0 -702
  461. agno/knowledge/arxiv.py +0 -33
  462. agno/knowledge/combined.py +0 -36
  463. agno/knowledge/csv.py +0 -144
  464. agno/knowledge/csv_url.py +0 -124
  465. agno/knowledge/document.py +0 -223
  466. agno/knowledge/docx.py +0 -137
  467. agno/knowledge/firecrawl.py +0 -34
  468. agno/knowledge/gcs/__init__.py +0 -0
  469. agno/knowledge/gcs/base.py +0 -39
  470. agno/knowledge/gcs/pdf.py +0 -125
  471. agno/knowledge/json.py +0 -137
  472. agno/knowledge/langchain.py +0 -71
  473. agno/knowledge/light_rag.py +0 -273
  474. agno/knowledge/llamaindex.py +0 -66
  475. agno/knowledge/markdown.py +0 -154
  476. agno/knowledge/pdf.py +0 -164
  477. agno/knowledge/pdf_bytes.py +0 -42
  478. agno/knowledge/pdf_url.py +0 -148
  479. agno/knowledge/s3/__init__.py +0 -0
  480. agno/knowledge/s3/base.py +0 -64
  481. agno/knowledge/s3/pdf.py +0 -33
  482. agno/knowledge/s3/text.py +0 -34
  483. agno/knowledge/text.py +0 -141
  484. agno/knowledge/url.py +0 -46
  485. agno/knowledge/website.py +0 -179
  486. agno/knowledge/wikipedia.py +0 -32
  487. agno/knowledge/youtube.py +0 -35
  488. agno/memory/agent.py +0 -423
  489. agno/memory/classifier.py +0 -104
  490. agno/memory/db/__init__.py +0 -5
  491. agno/memory/db/base.py +0 -42
  492. agno/memory/db/mongodb.py +0 -189
  493. agno/memory/db/postgres.py +0 -203
  494. agno/memory/db/sqlite.py +0 -193
  495. agno/memory/memory.py +0 -22
  496. agno/memory/row.py +0 -36
  497. agno/memory/summarizer.py +0 -201
  498. agno/memory/summary.py +0 -19
  499. agno/memory/team.py +0 -415
  500. agno/memory/v2/__init__.py +0 -2
  501. agno/memory/v2/db/__init__.py +0 -1
  502. agno/memory/v2/db/base.py +0 -42
  503. agno/memory/v2/db/firestore.py +0 -339
  504. agno/memory/v2/db/mongodb.py +0 -196
  505. agno/memory/v2/db/postgres.py +0 -214
  506. agno/memory/v2/db/redis.py +0 -187
  507. agno/memory/v2/db/schema.py +0 -54
  508. agno/memory/v2/db/sqlite.py +0 -209
  509. agno/memory/v2/manager.py +0 -437
  510. agno/memory/v2/memory.py +0 -1097
  511. agno/memory/v2/schema.py +0 -55
  512. agno/memory/v2/summarizer.py +0 -215
  513. agno/memory/workflow.py +0 -38
  514. agno/models/ollama/tools.py +0 -430
  515. agno/models/qwen/__init__.py +0 -5
  516. agno/playground/__init__.py +0 -10
  517. agno/playground/deploy.py +0 -3
  518. agno/playground/playground.py +0 -3
  519. agno/playground/serve.py +0 -3
  520. agno/playground/settings.py +0 -3
  521. agno/reranker/__init__.py +0 -0
  522. agno/run/response.py +0 -467
  523. agno/run/v2/__init__.py +0 -0
  524. agno/run/v2/workflow.py +0 -567
  525. agno/storage/__init__.py +0 -0
  526. agno/storage/agent/__init__.py +0 -0
  527. agno/storage/agent/dynamodb.py +0 -1
  528. agno/storage/agent/json.py +0 -1
  529. agno/storage/agent/mongodb.py +0 -1
  530. agno/storage/agent/postgres.py +0 -1
  531. agno/storage/agent/singlestore.py +0 -1
  532. agno/storage/agent/sqlite.py +0 -1
  533. agno/storage/agent/yaml.py +0 -1
  534. agno/storage/base.py +0 -60
  535. agno/storage/dynamodb.py +0 -673
  536. agno/storage/firestore.py +0 -297
  537. agno/storage/gcs_json.py +0 -261
  538. agno/storage/in_memory.py +0 -234
  539. agno/storage/json.py +0 -237
  540. agno/storage/mongodb.py +0 -328
  541. agno/storage/mysql.py +0 -685
  542. agno/storage/postgres.py +0 -682
  543. agno/storage/redis.py +0 -336
  544. agno/storage/session/__init__.py +0 -16
  545. agno/storage/session/agent.py +0 -64
  546. agno/storage/session/team.py +0 -63
  547. agno/storage/session/v2/__init__.py +0 -5
  548. agno/storage/session/workflow.py +0 -61
  549. agno/storage/singlestore.py +0 -606
  550. agno/storage/sqlite.py +0 -646
  551. agno/storage/workflow/__init__.py +0 -0
  552. agno/storage/workflow/mongodb.py +0 -1
  553. agno/storage/workflow/postgres.py +0 -1
  554. agno/storage/workflow/sqlite.py +0 -1
  555. agno/storage/yaml.py +0 -241
  556. agno/tools/thinking.py +0 -73
  557. agno/utils/defaults.py +0 -57
  558. agno/utils/filesystem.py +0 -39
  559. agno/utils/git.py +0 -52
  560. agno/utils/json_io.py +0 -30
  561. agno/utils/load_env.py +0 -19
  562. agno/utils/py_io.py +0 -19
  563. agno/utils/pyproject.py +0 -18
  564. agno/utils/resource_filter.py +0 -31
  565. agno/workflow/v2/__init__.py +0 -21
  566. agno/workflow/v2/types.py +0 -357
  567. agno/workflow/v2/workflow.py +0 -3312
  568. agno/workspace/__init__.py +0 -0
  569. agno/workspace/config.py +0 -325
  570. agno/workspace/enums.py +0 -6
  571. agno/workspace/helpers.py +0 -52
  572. agno/workspace/operator.py +0 -757
  573. agno/workspace/settings.py +0 -158
  574. agno-1.8.1.dist-info/METADATA +0 -982
  575. agno-1.8.1.dist-info/RECORD +0 -566
  576. agno-1.8.1.dist-info/entry_points.txt +0 -3
  577. agno-1.8.1.dist-info/licenses/LICENSE +0 -375
  578. /agno/{app → db/migrations}/__init__.py +0 -0
  579. /agno/{app/playground/__init__.py → db/schemas/metrics.py} +0 -0
  580. /agno/{cli → integrations}/__init__.py +0 -0
  581. /agno/{cli/ws → knowledge/chunking}/__init__.py +0 -0
  582. /agno/{document/chunking → knowledge/remote_content}/__init__.py +0 -0
  583. /agno/{document/reader/gcs → knowledge/reranker}/__init__.py +0 -0
  584. /agno/{document/reader/s3 → os/interfaces}/__init__.py +0 -0
  585. /agno/{app → os/interfaces}/slack/security.py +0 -0
  586. /agno/{app → os/interfaces}/whatsapp/security.py +0 -0
  587. /agno/{file/local → utils/print_response}/__init__.py +0 -0
  588. /agno/{infra → vectordb/llamaindex}/__init__.py +0 -0
  589. {agno-1.8.1.dist-info → agno-2.0.0.dist-info}/WHEEL +0 -0
  590. {agno-1.8.1.dist-info → agno-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,882 @@
1
+ import time
2
+ from datetime import date, datetime, timedelta, timezone
3
+ from typing import Any, Dict, List, Optional, Tuple, Union
4
+ from uuid import uuid4
5
+
6
+ from agno.db.base import BaseDb, SessionType
7
+ from agno.db.in_memory.utils import (
8
+ apply_sorting,
9
+ calculate_date_metrics,
10
+ fetch_all_sessions_data,
11
+ get_dates_to_calculate_metrics_for,
12
+ )
13
+ from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
14
+ from agno.db.schemas.knowledge import KnowledgeRow
15
+ from agno.db.schemas.memory import UserMemory
16
+ from agno.session import AgentSession, Session, TeamSession, WorkflowSession
17
+ from agno.utils.log import log_debug, log_error, log_info, log_warning
18
+
19
+
20
+ class InMemoryDb(BaseDb):
21
+ def __init__(self):
22
+ """Interface for in-memory storage."""
23
+ super().__init__()
24
+
25
+ # Initialize in-memory storage dictionaries
26
+ self._sessions: List[Dict[str, Any]] = []
27
+ self._memories: List[Dict[str, Any]] = []
28
+ self._metrics: List[Dict[str, Any]] = []
29
+ self._eval_runs: List[Dict[str, Any]] = []
30
+ self._knowledge: List[Dict[str, Any]] = []
31
+
32
+ # -- Session methods --
33
+
34
+ def delete_session(self, session_id: str) -> bool:
35
+ """Delete a session from in-memory storage.
36
+
37
+ Args:
38
+ session_id (str): The ID of the session to delete.
39
+
40
+ Returns:
41
+ bool: True if the session was deleted, False otherwise.
42
+
43
+ Raises:
44
+ Exception: If an error occurs during deletion.
45
+ """
46
+ try:
47
+ original_count = len(self._sessions)
48
+ self._sessions = [s for s in self._sessions if s.get("session_id") != session_id]
49
+
50
+ if len(self._sessions) < original_count:
51
+ log_debug(f"Successfully deleted session with session_id: {session_id}")
52
+ return True
53
+ else:
54
+ log_debug(f"No session found to delete with session_id: {session_id}")
55
+ return False
56
+
57
+ except Exception as e:
58
+ log_error(f"Error deleting session: {e}")
59
+ return False
60
+
61
+ def delete_sessions(self, session_ids: List[str]) -> None:
62
+ """Delete multiple sessions from in-memory storage.
63
+
64
+ Args:
65
+ session_ids (List[str]): The IDs of the sessions to delete.
66
+
67
+ Raises:
68
+ Exception: If an error occurs during deletion.
69
+ """
70
+ try:
71
+ self._sessions = [s for s in self._sessions if s.get("session_id") not in session_ids]
72
+ log_debug(f"Successfully deleted sessions with ids: {session_ids}")
73
+
74
+ except Exception as e:
75
+ log_error(f"Error deleting sessions: {e}")
76
+
77
+ def get_session(
78
+ self,
79
+ session_id: str,
80
+ session_type: SessionType,
81
+ user_id: Optional[str] = None,
82
+ deserialize: Optional[bool] = True,
83
+ ) -> Optional[Union[AgentSession, TeamSession, WorkflowSession, Dict[str, Any]]]:
84
+ """Read a session from in-memory storage.
85
+
86
+ Args:
87
+ session_id (str): The ID of the session to read.
88
+ session_type (SessionType): The type of the session to read.
89
+ user_id (Optional[str]): The ID of the user to read the session for.
90
+ deserialize (Optional[bool]): Whether to deserialize the session.
91
+
92
+ Returns:
93
+ Union[Session, Dict[str, Any], None]:
94
+ - When deserialize=True: Session object
95
+ - When deserialize=False: Session dictionary
96
+
97
+ Raises:
98
+ Exception: If an error occurs while reading the session.
99
+ """
100
+ try:
101
+ for session_data in self._sessions:
102
+ if session_data.get("session_id") == session_id:
103
+ if user_id is not None and session_data.get("user_id") != user_id:
104
+ continue
105
+ session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
106
+ if session_data.get("session_type") != session_type_value:
107
+ continue
108
+
109
+ if not deserialize:
110
+ return session_data
111
+
112
+ if session_type == SessionType.AGENT:
113
+ return AgentSession.from_dict(session_data)
114
+ elif session_type == SessionType.TEAM:
115
+ return TeamSession.from_dict(session_data)
116
+ else:
117
+ return WorkflowSession.from_dict(session_data)
118
+
119
+ return None
120
+
121
+ except Exception as e:
122
+ import traceback
123
+
124
+ traceback.print_exc()
125
+ log_error(f"Exception reading session: {e}")
126
+ return None
127
+
128
+ def get_sessions(
129
+ self,
130
+ session_type: SessionType,
131
+ user_id: Optional[str] = None,
132
+ component_id: Optional[str] = None,
133
+ session_name: Optional[str] = None,
134
+ start_timestamp: Optional[int] = None,
135
+ end_timestamp: Optional[int] = None,
136
+ limit: Optional[int] = None,
137
+ page: Optional[int] = None,
138
+ sort_by: Optional[str] = None,
139
+ sort_order: Optional[str] = None,
140
+ deserialize: Optional[bool] = True,
141
+ ) -> Union[List[Session], Tuple[List[Dict[str, Any]], int]]:
142
+ """Get all sessions from in-memory storage with filtering and pagination.
143
+
144
+ Args:
145
+ session_type (SessionType): The type of the sessions to read.
146
+ user_id (Optional[str]): The ID of the user to read the sessions for.
147
+ component_id (Optional[str]): The ID of the component to read the sessions for.
148
+ session_name (Optional[str]): The name of the session to read.
149
+ start_timestamp (Optional[int]): The start timestamp of the sessions to read.
150
+ end_timestamp (Optional[int]): The end timestamp of the sessions to read.
151
+ limit (Optional[int]): The limit of the sessions to read.
152
+ page (Optional[int]): The page of the sessions to read.
153
+ sort_by (Optional[str]): The field to sort the sessions by.
154
+ sort_order (Optional[str]): The order to sort the sessions by.
155
+ deserialize (Optional[bool]): Whether to deserialize the sessions.
156
+
157
+ Returns:
158
+ Union[List[AgentSession], List[TeamSession], List[WorkflowSession], Tuple[List[Dict[str, Any]], int]]:
159
+ - When deserialize=True: List of sessions
160
+ - When deserialize=False: Tuple with list of sessions and total count
161
+
162
+ Raises:
163
+ Exception: If an error occurs while reading the sessions.
164
+ """
165
+ try:
166
+ # Apply filters
167
+ filtered_sessions = []
168
+ for session_data in self._sessions:
169
+ if user_id is not None and session_data.get("user_id") != user_id:
170
+ continue
171
+ if component_id is not None:
172
+ if session_type == SessionType.AGENT and session_data.get("agent_id") != component_id:
173
+ continue
174
+ elif session_type == SessionType.TEAM and session_data.get("team_id") != component_id:
175
+ continue
176
+ elif session_type == SessionType.WORKFLOW and session_data.get("workflow_id") != component_id:
177
+ continue
178
+ if start_timestamp is not None and session_data.get("created_at", 0) < start_timestamp:
179
+ continue
180
+ if end_timestamp is not None and session_data.get("created_at", 0) > end_timestamp:
181
+ continue
182
+ if session_name is not None:
183
+ stored_name = session_data.get("session_data", {}).get("session_name", "")
184
+ if session_name.lower() not in stored_name.lower():
185
+ continue
186
+ session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
187
+ if session_data.get("session_type") != session_type_value:
188
+ continue
189
+
190
+ filtered_sessions.append(session_data)
191
+
192
+ total_count = len(filtered_sessions)
193
+
194
+ # Apply sorting
195
+ filtered_sessions = apply_sorting(filtered_sessions, sort_by, sort_order)
196
+
197
+ # Apply pagination
198
+ if limit is not None:
199
+ start_idx = 0
200
+ if page is not None:
201
+ start_idx = (page - 1) * limit
202
+ filtered_sessions = filtered_sessions[start_idx : start_idx + limit]
203
+
204
+ if not deserialize:
205
+ return filtered_sessions, total_count
206
+
207
+ if session_type == SessionType.AGENT:
208
+ return [AgentSession.from_dict(session) for session in filtered_sessions] # type: ignore
209
+ elif session_type == SessionType.TEAM:
210
+ return [TeamSession.from_dict(session) for session in filtered_sessions] # type: ignore
211
+ elif session_type == SessionType.WORKFLOW:
212
+ return [WorkflowSession.from_dict(session) for session in filtered_sessions] # type: ignore
213
+ else:
214
+ raise ValueError(f"Invalid session type: {session_type}")
215
+
216
+ except Exception as e:
217
+ log_error(f"Exception reading sessions: {e}")
218
+ return [] if deserialize else ([], 0)
219
+
220
+ def rename_session(
221
+ self, session_id: str, session_type: SessionType, session_name: str, deserialize: Optional[bool] = True
222
+ ) -> Optional[Union[Session, Dict[str, Any]]]:
223
+ try:
224
+ for i, session in enumerate(self._sessions):
225
+ if session.get("session_id") == session_id and session.get("session_type") == session_type.value:
226
+ # Update session name in session_data
227
+ if "session_data" not in session:
228
+ session["session_data"] = {}
229
+ session["session_data"]["session_name"] = session_name
230
+
231
+ self._sessions[i] = session
232
+
233
+ log_debug(f"Renamed session with id '{session_id}' to '{session_name}'")
234
+
235
+ if not deserialize:
236
+ return session
237
+
238
+ if session_type == SessionType.AGENT:
239
+ return AgentSession.from_dict(session)
240
+ elif session_type == SessionType.TEAM:
241
+ return TeamSession.from_dict(session)
242
+ else:
243
+ return WorkflowSession.from_dict(session)
244
+
245
+ return None
246
+
247
+ except Exception as e:
248
+ log_error(f"Exception renaming session: {e}")
249
+ return None
250
+
251
+ def upsert_session(
252
+ self, session: Session, deserialize: Optional[bool] = True
253
+ ) -> Optional[Union[Session, Dict[str, Any]]]:
254
+ try:
255
+ session_dict = session.to_dict()
256
+
257
+ # Add session_type based on session instance type
258
+ if isinstance(session, AgentSession):
259
+ session_dict["session_type"] = SessionType.AGENT.value
260
+ elif isinstance(session, TeamSession):
261
+ session_dict["session_type"] = SessionType.TEAM.value
262
+ elif isinstance(session, WorkflowSession):
263
+ session_dict["session_type"] = SessionType.WORKFLOW.value
264
+
265
+ # Find existing session to update
266
+ session_updated = False
267
+ for i, existing_session in enumerate(self._sessions):
268
+ if existing_session.get("session_id") == session_dict.get("session_id") and self._matches_session_key(
269
+ existing_session, session
270
+ ):
271
+ # Update existing session
272
+ session_dict["updated_at"] = int(time.time())
273
+ self._sessions[i] = session_dict
274
+ session_updated = True
275
+ break
276
+
277
+ if not session_updated:
278
+ # Add new session
279
+ session_dict["created_at"] = session_dict.get("created_at", int(time.time()))
280
+ session_dict["updated_at"] = session_dict.get("created_at")
281
+ self._sessions.append(session_dict)
282
+
283
+ if not deserialize:
284
+ return session_dict
285
+
286
+ return session
287
+
288
+ except Exception as e:
289
+ log_error(f"Exception upserting session: {e}")
290
+ return None
291
+
292
+ def _matches_session_key(self, existing_session: Dict[str, Any], session: Session) -> bool:
293
+ """Check if existing session matches the key for the session type."""
294
+ if isinstance(session, AgentSession):
295
+ return existing_session.get("agent_id") == session.agent_id
296
+ elif isinstance(session, TeamSession):
297
+ return existing_session.get("team_id") == session.team_id
298
+ elif isinstance(session, WorkflowSession):
299
+ return existing_session.get("workflow_id") == session.workflow_id
300
+ return False
301
+
302
+ # -- Memory methods --
303
+ def delete_user_memory(self, memory_id: str):
304
+ try:
305
+ original_count = len(self._memories)
306
+ self._memories = [m for m in self._memories if m.get("memory_id") != memory_id]
307
+
308
+ if len(self._memories) < original_count:
309
+ log_debug(f"Successfully deleted user memory id: {memory_id}")
310
+ else:
311
+ log_debug(f"No memory found with id: {memory_id}")
312
+
313
+ except Exception as e:
314
+ log_error(f"Error deleting memory: {e}")
315
+
316
+ def delete_user_memories(self, memory_ids: List[str]) -> None:
317
+ """Delete multiple user memories from in-memory storage."""
318
+ try:
319
+ self._memories = [m for m in self._memories if m.get("memory_id") not in memory_ids]
320
+ log_debug(f"Successfully deleted {len(memory_ids)} user memories")
321
+
322
+ except Exception as e:
323
+ log_error(f"Error deleting memories: {e}")
324
+
325
+ def get_all_memory_topics(self) -> List[str]:
326
+ try:
327
+ topics = set()
328
+ for memory in self._memories:
329
+ memory_topics = memory.get("topics", [])
330
+ if isinstance(memory_topics, list):
331
+ topics.update(memory_topics)
332
+ return list(topics)
333
+
334
+ except Exception as e:
335
+ log_error(f"Exception reading from memory storage: {e}")
336
+ return []
337
+
338
+ def get_user_memory(
339
+ self, memory_id: str, deserialize: Optional[bool] = True
340
+ ) -> Optional[Union[UserMemory, Dict[str, Any]]]:
341
+ try:
342
+ for memory_data in self._memories:
343
+ if memory_data.get("memory_id") == memory_id:
344
+ if not deserialize:
345
+ return memory_data
346
+ return UserMemory.from_dict(memory_data)
347
+
348
+ return None
349
+
350
+ except Exception as e:
351
+ log_error(f"Exception reading from memory storage: {e}")
352
+ return None
353
+
354
+ def get_user_memories(
355
+ self,
356
+ user_id: Optional[str] = None,
357
+ agent_id: Optional[str] = None,
358
+ team_id: Optional[str] = None,
359
+ topics: Optional[List[str]] = None,
360
+ search_content: Optional[str] = None,
361
+ limit: Optional[int] = None,
362
+ page: Optional[int] = None,
363
+ sort_by: Optional[str] = None,
364
+ sort_order: Optional[str] = None,
365
+ deserialize: Optional[bool] = True,
366
+ ) -> Union[List[UserMemory], Tuple[List[Dict[str, Any]], int]]:
367
+ try:
368
+ # Apply filters
369
+ filtered_memories = []
370
+ for memory_data in self._memories:
371
+ if user_id is not None and memory_data.get("user_id") != user_id:
372
+ continue
373
+ if agent_id is not None and memory_data.get("agent_id") != agent_id:
374
+ continue
375
+ if team_id is not None and memory_data.get("team_id") != team_id:
376
+ continue
377
+ if topics is not None:
378
+ memory_topics = memory_data.get("topics", [])
379
+ if not any(topic in memory_topics for topic in topics):
380
+ continue
381
+ if search_content is not None:
382
+ memory_content = str(memory_data.get("memory", ""))
383
+ if search_content.lower() not in memory_content.lower():
384
+ continue
385
+
386
+ filtered_memories.append(memory_data)
387
+
388
+ total_count = len(filtered_memories)
389
+
390
+ # Apply sorting
391
+ filtered_memories = apply_sorting(filtered_memories, sort_by, sort_order)
392
+
393
+ # Apply pagination
394
+ if limit is not None:
395
+ start_idx = 0
396
+ if page is not None:
397
+ start_idx = (page - 1) * limit
398
+ filtered_memories = filtered_memories[start_idx : start_idx + limit]
399
+
400
+ if not deserialize:
401
+ return filtered_memories, total_count
402
+
403
+ return [UserMemory.from_dict(memory) for memory in filtered_memories]
404
+
405
+ except Exception as e:
406
+ log_error(f"Exception reading from memory storage: {e}")
407
+ return [] if deserialize else ([], 0)
408
+
409
+ def get_user_memory_stats(
410
+ self, limit: Optional[int] = None, page: Optional[int] = None
411
+ ) -> Tuple[List[Dict[str, Any]], int]:
412
+ """Get user memory statistics."""
413
+ try:
414
+ user_stats = {}
415
+
416
+ for memory in self._memories:
417
+ user_id = memory.get("user_id")
418
+ if user_id:
419
+ if user_id not in user_stats:
420
+ user_stats[user_id] = {"user_id": user_id, "total_memories": 0, "last_memory_updated_at": 0}
421
+ user_stats[user_id]["total_memories"] += 1
422
+ updated_at = memory.get("updated_at", 0)
423
+ if updated_at > user_stats[user_id]["last_memory_updated_at"]:
424
+ user_stats[user_id]["last_memory_updated_at"] = updated_at
425
+
426
+ stats_list = list(user_stats.values())
427
+ stats_list.sort(key=lambda x: x["last_memory_updated_at"], reverse=True)
428
+
429
+ total_count = len(stats_list)
430
+
431
+ # Apply pagination
432
+ if limit is not None:
433
+ start_idx = 0
434
+ if page is not None:
435
+ start_idx = (page - 1) * limit
436
+ stats_list = stats_list[start_idx : start_idx + limit]
437
+
438
+ return stats_list, total_count
439
+
440
+ except Exception as e:
441
+ log_error(f"Exception getting user memory stats: {e}")
442
+ return [], 0
443
+
444
+ def upsert_user_memory(
445
+ self, memory: UserMemory, deserialize: Optional[bool] = True
446
+ ) -> Optional[Union[UserMemory, Dict[str, Any]]]:
447
+ try:
448
+ if memory.memory_id is None:
449
+ memory.memory_id = str(uuid4())
450
+
451
+ memory_dict = memory.to_dict() if hasattr(memory, "to_dict") else memory.__dict__
452
+ memory_dict["updated_at"] = int(time.time())
453
+
454
+ # Find existing memory to update
455
+ memory_updated = False
456
+ for i, existing_memory in enumerate(self._memories):
457
+ if existing_memory.get("memory_id") == memory.memory_id:
458
+ self._memories[i] = memory_dict
459
+ memory_updated = True
460
+ break
461
+
462
+ if not memory_updated:
463
+ self._memories.append(memory_dict)
464
+
465
+ if not deserialize:
466
+ return memory_dict
467
+ return UserMemory.from_dict(memory_dict)
468
+
469
+ except Exception as e:
470
+ log_warning(f"Exception upserting user memory: {e}")
471
+ return None
472
+
473
+ def clear_memories(self) -> None:
474
+ """Delete all memories.
475
+
476
+ Raises:
477
+ Exception: If an error occurs during deletion.
478
+ """
479
+ try:
480
+ self._memories.clear()
481
+
482
+ except Exception as e:
483
+ log_warning(f"Exception deleting all memories: {e}")
484
+
485
+ # -- Metrics methods --
486
+ def calculate_metrics(self) -> Optional[list[dict]]:
487
+ """Calculate metrics for all dates without complete metrics."""
488
+ try:
489
+ starting_date = self._get_metrics_calculation_starting_date(self._metrics)
490
+ if starting_date is None:
491
+ log_info("No session data found. Won't calculate metrics.")
492
+ return None
493
+
494
+ dates_to_process = get_dates_to_calculate_metrics_for(starting_date)
495
+ if not dates_to_process:
496
+ log_info("Metrics already calculated for all relevant dates.")
497
+ return None
498
+
499
+ start_timestamp = int(datetime.combine(dates_to_process[0], datetime.min.time()).timestamp())
500
+ end_timestamp = int(
501
+ datetime.combine(dates_to_process[-1] + timedelta(days=1), datetime.min.time()).timestamp()
502
+ )
503
+
504
+ sessions = self._get_all_sessions_for_metrics_calculation(start_timestamp, end_timestamp)
505
+ all_sessions_data = fetch_all_sessions_data(
506
+ sessions=sessions, dates_to_process=dates_to_process, start_timestamp=start_timestamp
507
+ )
508
+ if not all_sessions_data:
509
+ log_info("No new session data found. Won't calculate metrics.")
510
+ return None
511
+
512
+ results = []
513
+
514
+ for date_to_process in dates_to_process:
515
+ date_key = date_to_process.isoformat()
516
+ sessions_for_date = all_sessions_data.get(date_key, {})
517
+
518
+ # Skip dates with no sessions
519
+ if not any(len(sessions) > 0 for sessions in sessions_for_date.values()):
520
+ continue
521
+
522
+ metrics_record = calculate_date_metrics(date_to_process, sessions_for_date)
523
+
524
+ # Upsert metrics record
525
+ existing_record_idx = None
526
+ for i, existing_metric in enumerate(self._metrics):
527
+ if (
528
+ existing_metric.get("date") == str(date_to_process)
529
+ and existing_metric.get("aggregation_period") == "daily"
530
+ ):
531
+ existing_record_idx = i
532
+ break
533
+
534
+ if existing_record_idx is not None:
535
+ self._metrics[existing_record_idx] = metrics_record
536
+ else:
537
+ self._metrics.append(metrics_record)
538
+
539
+ results.append(metrics_record)
540
+
541
+ log_debug("Updated metrics calculations")
542
+
543
+ return results
544
+
545
+ except Exception as e:
546
+ log_warning(f"Exception refreshing metrics: {e}")
547
+ return None
548
+
549
+ def _get_metrics_calculation_starting_date(self, metrics: List[Dict[str, Any]]) -> Optional[date]:
550
+ """Get the first date for which metrics calculation is needed."""
551
+ if metrics:
552
+ # Sort by date in descending order
553
+ sorted_metrics = sorted(metrics, key=lambda x: x.get("date", ""), reverse=True)
554
+ latest_metric = sorted_metrics[0]
555
+
556
+ if latest_metric.get("completed", False):
557
+ latest_date = datetime.strptime(latest_metric["date"], "%Y-%m-%d").date()
558
+ return latest_date + timedelta(days=1)
559
+ else:
560
+ return datetime.strptime(latest_metric["date"], "%Y-%m-%d").date()
561
+
562
+ # No metrics records. Return the date of the first recorded session.
563
+ if self._sessions:
564
+ # Sort by created_at
565
+ sorted_sessions = sorted(self._sessions, key=lambda x: x.get("created_at", 0))
566
+ first_session_date = sorted_sessions[0]["created_at"]
567
+ return datetime.fromtimestamp(first_session_date, tz=timezone.utc).date()
568
+
569
+ return None
570
+
571
+ def _get_all_sessions_for_metrics_calculation(
572
+ self, start_timestamp: Optional[int] = None, end_timestamp: Optional[int] = None
573
+ ) -> List[Dict[str, Any]]:
574
+ """Get all sessions for metrics calculation."""
575
+ try:
576
+ filtered_sessions = []
577
+ for session in self._sessions:
578
+ created_at = session.get("created_at", 0)
579
+ if start_timestamp is not None and created_at < start_timestamp:
580
+ continue
581
+ if end_timestamp is not None and created_at >= end_timestamp:
582
+ continue
583
+
584
+ # Only include necessary fields for metrics
585
+ filtered_session = {
586
+ "user_id": session.get("user_id"),
587
+ "session_data": session.get("session_data"),
588
+ "runs": session.get("runs"),
589
+ "created_at": session.get("created_at"),
590
+ "session_type": session.get("session_type"),
591
+ }
592
+ filtered_sessions.append(filtered_session)
593
+
594
+ return filtered_sessions
595
+
596
+ except Exception as e:
597
+ log_error(f"Exception reading sessions for metrics: {e}")
598
+ return []
599
+
600
+ def get_metrics(
601
+ self,
602
+ starting_date: Optional[date] = None,
603
+ ending_date: Optional[date] = None,
604
+ ) -> Tuple[List[dict], Optional[int]]:
605
+ """Get all metrics matching the given date range."""
606
+ try:
607
+ filtered_metrics = []
608
+ latest_updated_at = None
609
+
610
+ for metric in self._metrics:
611
+ metric_date = datetime.strptime(metric.get("date", ""), "%Y-%m-%d").date()
612
+
613
+ if starting_date and metric_date < starting_date:
614
+ continue
615
+ if ending_date and metric_date > ending_date:
616
+ continue
617
+
618
+ filtered_metrics.append(metric)
619
+
620
+ updated_at = metric.get("updated_at")
621
+ if updated_at and (latest_updated_at is None or updated_at > latest_updated_at):
622
+ latest_updated_at = updated_at
623
+
624
+ return filtered_metrics, latest_updated_at
625
+
626
+ except Exception as e:
627
+ log_error(f"Exception getting metrics: {e}")
628
+ return [], None
629
+
630
+ # -- Knowledge methods --
631
+
632
+ def delete_knowledge_content(self, id: str):
633
+ """Delete a knowledge row from in-memory storage.
634
+
635
+ Args:
636
+ id (str): The ID of the knowledge row to delete.
637
+
638
+ Raises:
639
+ Exception: If an error occurs during deletion.
640
+ """
641
+ try:
642
+ self._knowledge = [item for item in self._knowledge if item.get("id") != id]
643
+
644
+ except Exception as e:
645
+ log_error(f"Error deleting knowledge content: {e}")
646
+
647
+ def get_knowledge_content(self, id: str) -> Optional[KnowledgeRow]:
648
+ """Get a knowledge row from in-memory storage.
649
+
650
+ Args:
651
+ id (str): The ID of the knowledge row to get.
652
+
653
+ Returns:
654
+ Optional[KnowledgeRow]: The knowledge row, or None if it doesn't exist.
655
+
656
+ Raises:
657
+ Exception: If an error occurs during retrieval.
658
+ """
659
+ try:
660
+ for item in self._knowledge:
661
+ if item.get("id") == id:
662
+ return KnowledgeRow.model_validate(item)
663
+
664
+ return None
665
+
666
+ except Exception as e:
667
+ log_error(f"Error getting knowledge content: {e}")
668
+ return None
669
+
670
+ def get_knowledge_contents(
671
+ self,
672
+ limit: Optional[int] = None,
673
+ page: Optional[int] = None,
674
+ sort_by: Optional[str] = None,
675
+ sort_order: Optional[str] = None,
676
+ ) -> Tuple[List[KnowledgeRow], int]:
677
+ """Get all knowledge contents from in-memory storage.
678
+
679
+ Args:
680
+ limit (Optional[int]): The maximum number of knowledge contents to return.
681
+ page (Optional[int]): The page number.
682
+ sort_by (Optional[str]): The column to sort by.
683
+ sort_order (Optional[str]): The order to sort by.
684
+
685
+ Returns:
686
+ Tuple[List[KnowledgeRow], int]: The knowledge contents and total count.
687
+
688
+ Raises:
689
+ Exception: If an error occurs during retrieval.
690
+ """
691
+ try:
692
+ knowledge_items = self._knowledge.copy()
693
+
694
+ total_count = len(knowledge_items)
695
+
696
+ # Apply sorting
697
+ knowledge_items = apply_sorting(knowledge_items, sort_by, sort_order)
698
+
699
+ # Apply pagination
700
+ if limit is not None:
701
+ start_idx = 0
702
+ if page is not None:
703
+ start_idx = (page - 1) * limit
704
+ knowledge_items = knowledge_items[start_idx : start_idx + limit]
705
+
706
+ return [KnowledgeRow.model_validate(item) for item in knowledge_items], total_count
707
+
708
+ except Exception as e:
709
+ log_error(f"Error getting knowledge contents: {e}")
710
+ return [], 0
711
+
712
+ def upsert_knowledge_content(self, knowledge_row: KnowledgeRow):
713
+ """Upsert knowledge content.
714
+
715
+ Args:
716
+ knowledge_row (KnowledgeRow): The knowledge row to upsert.
717
+
718
+ Returns:
719
+ Optional[KnowledgeRow]: The upserted knowledge row, or None if the operation fails.
720
+
721
+ Raises:
722
+ Exception: If an error occurs during upsert.
723
+ """
724
+ try:
725
+ knowledge_dict = knowledge_row.model_dump()
726
+
727
+ # Find existing item to update
728
+ item_updated = False
729
+ for i, existing_item in enumerate(self._knowledge):
730
+ if existing_item.get("id") == knowledge_row.id:
731
+ self._knowledge[i] = knowledge_dict
732
+ item_updated = True
733
+ break
734
+
735
+ if not item_updated:
736
+ self._knowledge.append(knowledge_dict)
737
+
738
+ return knowledge_row
739
+
740
+ except Exception as e:
741
+ log_error(f"Error upserting knowledge row: {e}")
742
+ return None
743
+
744
+ # -- Eval methods --
745
+
746
+ def create_eval_run(self, eval_run: EvalRunRecord) -> Optional[EvalRunRecord]:
747
+ """Create an EvalRunRecord"""
748
+ try:
749
+ current_time = int(time.time())
750
+ eval_dict = eval_run.model_dump()
751
+ eval_dict["created_at"] = current_time
752
+ eval_dict["updated_at"] = current_time
753
+
754
+ self._eval_runs.append(eval_dict)
755
+
756
+ log_debug(f"Created eval run with id '{eval_run.run_id}'")
757
+
758
+ return eval_run
759
+
760
+ except Exception as e:
761
+ log_error(f"Error creating eval run: {e}")
762
+ return None
763
+
764
+ def delete_eval_runs(self, eval_run_ids: List[str]) -> None:
765
+ """Delete multiple eval runs from in-memory storage."""
766
+ try:
767
+ original_count = len(self._eval_runs)
768
+ self._eval_runs = [run for run in self._eval_runs if run.get("run_id") not in eval_run_ids]
769
+
770
+ deleted_count = original_count - len(self._eval_runs)
771
+ if deleted_count > 0:
772
+ log_debug(f"Deleted {deleted_count} eval runs")
773
+ else:
774
+ log_debug(f"No eval runs found with IDs: {eval_run_ids}")
775
+
776
+ except Exception as e:
777
+ log_error(f"Error deleting eval runs {eval_run_ids}: {e}")
778
+
779
+ def get_eval_run(
780
+ self, eval_run_id: str, deserialize: Optional[bool] = True
781
+ ) -> Optional[Union[EvalRunRecord, Dict[str, Any]]]:
782
+ """Get an eval run from in-memory storage."""
783
+ try:
784
+ for run_data in self._eval_runs:
785
+ if run_data.get("run_id") == eval_run_id:
786
+ if not deserialize:
787
+ return run_data
788
+ return EvalRunRecord.model_validate(run_data)
789
+
790
+ return None
791
+
792
+ except Exception as e:
793
+ log_error(f"Exception getting eval run {eval_run_id}: {e}")
794
+ return None
795
+
796
+ def get_eval_runs(
797
+ self,
798
+ limit: Optional[int] = None,
799
+ page: Optional[int] = None,
800
+ sort_by: Optional[str] = None,
801
+ sort_order: Optional[str] = None,
802
+ agent_id: Optional[str] = None,
803
+ team_id: Optional[str] = None,
804
+ workflow_id: Optional[str] = None,
805
+ model_id: Optional[str] = None,
806
+ filter_type: Optional[EvalFilterType] = None,
807
+ eval_type: Optional[List[EvalType]] = None,
808
+ deserialize: Optional[bool] = True,
809
+ ) -> Union[List[EvalRunRecord], Tuple[List[Dict[str, Any]], int]]:
810
+ """Get all eval runs from in-memory storage with filtering and pagination."""
811
+ try:
812
+ # Apply filters
813
+ filtered_runs = []
814
+ for run_data in self._eval_runs:
815
+ if agent_id is not None and run_data.get("agent_id") != agent_id:
816
+ continue
817
+ if team_id is not None and run_data.get("team_id") != team_id:
818
+ continue
819
+ if workflow_id is not None and run_data.get("workflow_id") != workflow_id:
820
+ continue
821
+ if model_id is not None and run_data.get("model_id") != model_id:
822
+ continue
823
+ if eval_type is not None and len(eval_type) > 0:
824
+ if run_data.get("eval_type") not in eval_type:
825
+ continue
826
+ if filter_type is not None:
827
+ if filter_type == EvalFilterType.AGENT and run_data.get("agent_id") is None:
828
+ continue
829
+ elif filter_type == EvalFilterType.TEAM and run_data.get("team_id") is None:
830
+ continue
831
+ elif filter_type == EvalFilterType.WORKFLOW and run_data.get("workflow_id") is None:
832
+ continue
833
+
834
+ filtered_runs.append(run_data)
835
+
836
+ total_count = len(filtered_runs)
837
+
838
+ # Apply sorting (default by created_at desc)
839
+ if sort_by is None:
840
+ filtered_runs.sort(key=lambda x: x.get("created_at", 0), reverse=True)
841
+ else:
842
+ filtered_runs = apply_sorting(filtered_runs, sort_by, sort_order)
843
+
844
+ # Apply pagination
845
+ if limit is not None:
846
+ start_idx = 0
847
+ if page is not None:
848
+ start_idx = (page - 1) * limit
849
+ filtered_runs = filtered_runs[start_idx : start_idx + limit]
850
+
851
+ if not deserialize:
852
+ return filtered_runs, total_count
853
+
854
+ return [EvalRunRecord.model_validate(run) for run in filtered_runs]
855
+
856
+ except Exception as e:
857
+ log_error(f"Exception getting eval runs: {e}")
858
+ return [] if deserialize else ([], 0)
859
+
860
+ def rename_eval_run(
861
+ self, eval_run_id: str, name: str, deserialize: Optional[bool] = True
862
+ ) -> Optional[Union[EvalRunRecord, Dict[str, Any]]]:
863
+ """Rename an eval run."""
864
+ try:
865
+ for i, run_data in enumerate(self._eval_runs):
866
+ if run_data.get("run_id") == eval_run_id:
867
+ run_data["name"] = name
868
+ run_data["updated_at"] = int(time.time())
869
+ self._eval_runs[i] = run_data
870
+
871
+ log_debug(f"Renamed eval run with id '{eval_run_id}' to '{name}'")
872
+
873
+ if not deserialize:
874
+ return run_data
875
+
876
+ return EvalRunRecord.model_validate(run_data)
877
+
878
+ return None
879
+
880
+ except Exception as e:
881
+ log_error(f"Error renaming eval run {eval_run_id}: {e}")
882
+ return None