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,1001 @@
1
+ import json
2
+ import time
3
+ from datetime import date, datetime, timedelta, timezone
4
+ from typing import Any, Dict, List, Optional, Tuple, Union
5
+ from uuid import uuid4
6
+
7
+ from agno.db.base import BaseDb, SessionType
8
+ from agno.db.gcs_json.utils import (
9
+ apply_sorting,
10
+ calculate_date_metrics,
11
+ fetch_all_sessions_data,
12
+ get_dates_to_calculate_metrics_for,
13
+ )
14
+ from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
15
+ from agno.db.schemas.knowledge import KnowledgeRow
16
+ from agno.db.schemas.memory import UserMemory
17
+ from agno.session import AgentSession, Session, TeamSession, WorkflowSession
18
+ from agno.utils.log import log_debug, log_error, log_info, log_warning
19
+
20
+ try:
21
+ from google.cloud import storage as gcs # type: ignore
22
+ except ImportError:
23
+ raise ImportError("`google-cloud-storage` not installed. Please install it with `pip install google-cloud-storage`")
24
+
25
+
26
+ class GcsJsonDb(BaseDb):
27
+ def __init__(
28
+ self,
29
+ bucket_name: str,
30
+ prefix: Optional[str] = None,
31
+ session_table: Optional[str] = None,
32
+ memory_table: Optional[str] = None,
33
+ metrics_table: Optional[str] = None,
34
+ eval_table: Optional[str] = None,
35
+ knowledge_table: Optional[str] = None,
36
+ project: Optional[str] = None,
37
+ credentials: Optional[Any] = None,
38
+ ):
39
+ """
40
+ Interface for interacting with JSON files stored in Google Cloud Storage as database.
41
+
42
+ Args:
43
+ bucket_name (str): Name of the GCS bucket where JSON files will be stored.
44
+ prefix (Optional[str]): Path prefix for organizing files in the bucket. Defaults to "agno/".
45
+ session_table (Optional[str]): Name of the JSON file to store sessions (without .json extension).
46
+ memory_table (Optional[str]): Name of the JSON file to store user memories.
47
+ metrics_table (Optional[str]): Name of the JSON file to store metrics.
48
+ eval_table (Optional[str]): Name of the JSON file to store evaluation runs.
49
+ knowledge_table (Optional[str]): Name of the JSON file to store knowledge content.
50
+ project (Optional[str]): GCP project ID. If None, uses default project.
51
+ location (Optional[str]): GCS bucket location. If None, uses default location.
52
+ credentials (Optional[Any]): GCP credentials. If None, uses default credentials.
53
+ """
54
+ super().__init__(
55
+ session_table=session_table,
56
+ memory_table=memory_table,
57
+ metrics_table=metrics_table,
58
+ eval_table=eval_table,
59
+ knowledge_table=knowledge_table,
60
+ )
61
+
62
+ self.bucket_name = bucket_name
63
+ self.prefix = prefix or "agno/"
64
+ if self.prefix and not self.prefix.endswith("/"):
65
+ self.prefix += "/"
66
+
67
+ # Initialize GCS client and bucket
68
+ self.client = gcs.Client(project=project, credentials=credentials)
69
+ self.bucket = self.client.bucket(self.bucket_name)
70
+
71
+ def _get_blob_name(self, filename: str) -> str:
72
+ """Get the full blob name including prefix for a given filename."""
73
+ return f"{self.prefix}{filename}.json"
74
+
75
+ def _read_json_file(self, filename: str, create_table_if_not_found: Optional[bool] = False) -> List[Dict[str, Any]]:
76
+ """Read data from a JSON file in GCS, creating it if it doesn't exist.
77
+
78
+ Args:
79
+ filename (str): The name of the JSON file to read.
80
+
81
+ Returns:
82
+ List[Dict[str, Any]]: The data from the JSON file.
83
+
84
+ Raises:
85
+ json.JSONDecodeError: If the JSON file is not valid.
86
+ """
87
+ blob_name = self._get_blob_name(filename)
88
+ blob = self.bucket.blob(blob_name)
89
+
90
+ try:
91
+ data_str = blob.download_as_bytes().decode("utf-8")
92
+ return json.loads(data_str)
93
+
94
+ except Exception as e:
95
+ # Check if it's a 404 (file not found) error
96
+ if "404" in str(e) or "Not Found" in str(e):
97
+ if create_table_if_not_found:
98
+ log_debug(f"Creating new GCS JSON file: {blob_name}")
99
+ blob.upload_from_string("[]", content_type="application/json")
100
+ return []
101
+ else:
102
+ log_error(f"Error reading the {blob_name} JSON file from GCS: {e}")
103
+ raise json.JSONDecodeError(f"Error reading {blob_name}", "", 0)
104
+
105
+ def _write_json_file(self, filename: str, data: List[Dict[str, Any]]) -> None:
106
+ """Write data to a JSON file in GCS.
107
+
108
+ Args:
109
+ filename (str): The name of the JSON file to write.
110
+ data (List[Dict[str, Any]]): The data to write to the JSON file.
111
+
112
+ Raises:
113
+ Exception: If an error occurs while writing to the JSON file.
114
+ """
115
+ blob_name = self._get_blob_name(filename)
116
+ blob = self.bucket.blob(blob_name)
117
+
118
+ try:
119
+ json_data = json.dumps(data, indent=2, default=str)
120
+ blob.upload_from_string(json_data, content_type="application/json")
121
+
122
+ except Exception as e:
123
+ log_error(f"Error writing to the {blob_name} JSON file in GCS: {e}")
124
+ return
125
+
126
+ # -- Session methods --
127
+
128
+ def delete_session(self, session_id: str) -> bool:
129
+ """Delete a session from the GCS JSON file.
130
+
131
+ Args:
132
+ session_id (str): The ID of the session to delete.
133
+
134
+ Returns:
135
+ bool: True if the session was deleted, False otherwise.
136
+
137
+ Raises:
138
+ Exception: If an error occurs during deletion.
139
+ """
140
+ try:
141
+ sessions = self._read_json_file(self.session_table_name)
142
+ original_count = len(sessions)
143
+ sessions = [s for s in sessions if s.get("session_id") != session_id]
144
+
145
+ if len(sessions) < original_count:
146
+ self._write_json_file(self.session_table_name, sessions)
147
+ log_debug(f"Successfully deleted session with session_id: {session_id}")
148
+ return True
149
+
150
+ else:
151
+ log_debug(f"No session found to delete with session_id: {session_id}")
152
+ return False
153
+
154
+ except Exception as e:
155
+ log_warning(f"Error deleting session: {e}")
156
+ return False
157
+
158
+ def delete_sessions(self, session_ids: List[str]) -> None:
159
+ """Delete multiple sessions from the GCS JSON file.
160
+
161
+ Args:
162
+ session_ids (List[str]): The IDs of the sessions to delete.
163
+
164
+ Raises:
165
+ Exception: If an error occurs during deletion.
166
+ """
167
+ try:
168
+ sessions = self._read_json_file(self.session_table_name)
169
+ sessions = [s for s in sessions if s.get("session_id") not in session_ids]
170
+ self._write_json_file(self.session_table_name, sessions)
171
+ log_debug(f"Successfully deleted sessions with ids: {session_ids}")
172
+
173
+ except Exception as e:
174
+ log_warning(f"Error deleting sessions: {e}")
175
+
176
+ def get_session(
177
+ self,
178
+ session_id: str,
179
+ session_type: Optional[SessionType] = None,
180
+ user_id: Optional[str] = None,
181
+ deserialize: Optional[bool] = True,
182
+ ) -> Optional[Union[AgentSession, TeamSession, WorkflowSession, Dict[str, Any]]]:
183
+ """Read a session from the GCS JSON file.
184
+
185
+ Args:
186
+ session_id (str): The ID of the session to read.
187
+ session_type (Optional[SessionType]): The type of the session to read.
188
+ user_id (Optional[str]): The ID of the user to read the session for.
189
+ deserialize (Optional[bool]): Whether to deserialize the session.
190
+
191
+ Returns:
192
+ Union[Session, Dict[str, Any], None]:
193
+ - When deserialize=True: Session object
194
+ - When deserialize=False: Session dictionary
195
+
196
+ Raises:
197
+ Exception: If an error occurs while reading the session.
198
+ """
199
+ try:
200
+ sessions = self._read_json_file(self.session_table_name)
201
+
202
+ for session_data in sessions:
203
+ if session_data.get("session_id") == session_id:
204
+ if user_id is not None and session_data.get("user_id") != user_id:
205
+ continue
206
+
207
+ session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
208
+ if session_data.get("session_type") != session_type_value:
209
+ continue
210
+
211
+ if not deserialize:
212
+ return session_data
213
+
214
+ if session_type == SessionType.AGENT:
215
+ return AgentSession.from_dict(session_data)
216
+ elif session_type == SessionType.TEAM:
217
+ return TeamSession.from_dict(session_data)
218
+ elif session_type == SessionType.WORKFLOW:
219
+ return WorkflowSession.from_dict(session_data)
220
+
221
+ return None
222
+
223
+ except Exception as e:
224
+ log_warning(f"Exception reading from session file: {e}")
225
+ return None
226
+
227
+ def get_sessions(
228
+ self,
229
+ session_type: Optional[SessionType] = None,
230
+ user_id: Optional[str] = None,
231
+ component_id: Optional[str] = None,
232
+ session_name: Optional[str] = None,
233
+ start_timestamp: Optional[int] = None,
234
+ end_timestamp: Optional[int] = None,
235
+ limit: Optional[int] = None,
236
+ page: Optional[int] = None,
237
+ sort_by: Optional[str] = None,
238
+ sort_order: Optional[str] = None,
239
+ deserialize: Optional[bool] = True,
240
+ ) -> Union[List[Session], Tuple[List[Dict[str, Any]], int]]:
241
+ """Get all sessions from the GCS JSON file with filtering and pagination.
242
+
243
+ Args:
244
+ session_type (Optional[SessionType]): The type of the sessions to read.
245
+ user_id (Optional[str]): The ID of the user to read the sessions for.
246
+ component_id (Optional[str]): The ID of the component to read the sessions for.
247
+ session_name (Optional[str]): The name of the session to read.
248
+ start_timestamp (Optional[int]): The start timestamp of the sessions to read.
249
+ end_timestamp (Optional[int]): The end timestamp of the sessions to read.
250
+ limit (Optional[int]): The limit of the sessions to read.
251
+ page (Optional[int]): The page of the sessions to read.
252
+ sort_by (Optional[str]): The field to sort the sessions by.
253
+ sort_order (Optional[str]): The order to sort the sessions by.
254
+ deserialize (Optional[bool]): Whether to deserialize the sessions.
255
+ create_table_if_not_found (Optional[bool]): Whether to create a file to track sessions if it doesn't exist.
256
+
257
+ Returns:
258
+ Union[List[AgentSession], List[TeamSession], List[WorkflowSession], Tuple[List[Dict[str, Any]], int]]:
259
+ - When deserialize=True: List of sessions
260
+ - When deserialize=False: Tuple with list of sessions and total count
261
+
262
+ Raises:
263
+ Exception: If an error occurs while reading the sessions.
264
+ """
265
+ try:
266
+ sessions = self._read_json_file(self.session_table_name)
267
+
268
+ # Apply filters
269
+ filtered_sessions = []
270
+ for session_data in sessions:
271
+ if user_id is not None and session_data.get("user_id") != user_id:
272
+ continue
273
+ if component_id is not None:
274
+ if session_type == SessionType.AGENT and session_data.get("agent_id") != component_id:
275
+ continue
276
+ elif session_type == SessionType.TEAM and session_data.get("team_id") != component_id:
277
+ continue
278
+ elif session_type == SessionType.WORKFLOW and session_data.get("workflow_id") != component_id:
279
+ continue
280
+ if start_timestamp is not None and session_data.get("created_at", 0) < start_timestamp:
281
+ continue
282
+ if end_timestamp is not None and session_data.get("created_at", 0) > end_timestamp:
283
+ continue
284
+ if session_name is not None:
285
+ stored_name = session_data.get("session_data", {}).get("session_name", "")
286
+ if session_name.lower() not in stored_name.lower():
287
+ continue
288
+ session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
289
+ if session_data.get("session_type") != session_type_value:
290
+ continue
291
+
292
+ filtered_sessions.append(session_data)
293
+
294
+ total_count = len(filtered_sessions)
295
+
296
+ # Apply sorting
297
+ filtered_sessions = apply_sorting(filtered_sessions, sort_by, sort_order)
298
+
299
+ # Apply pagination
300
+ if limit is not None:
301
+ start_idx = 0
302
+ if page is not None:
303
+ start_idx = (page - 1) * limit
304
+ filtered_sessions = filtered_sessions[start_idx : start_idx + limit]
305
+
306
+ if not deserialize:
307
+ return filtered_sessions, total_count
308
+
309
+ if session_type == SessionType.AGENT:
310
+ return [AgentSession.from_dict(session) for session in filtered_sessions] # type: ignore
311
+ elif session_type == SessionType.TEAM:
312
+ return [TeamSession.from_dict(session) for session in filtered_sessions] # type: ignore
313
+ elif session_type == SessionType.WORKFLOW:
314
+ return [WorkflowSession.from_dict(session) for session in filtered_sessions] # type: ignore
315
+ else:
316
+ raise ValueError(f"Invalid session type: {session_type}")
317
+
318
+ except Exception as e:
319
+ log_warning(f"Exception reading from session file: {e}")
320
+ return [] if deserialize else ([], 0)
321
+
322
+ def rename_session(
323
+ self, session_id: str, session_type: SessionType, session_name: str, deserialize: Optional[bool] = True
324
+ ) -> Optional[Union[Session, Dict[str, Any]]]:
325
+ """Rename a session in the GCS JSON file."""
326
+ try:
327
+ sessions = self._read_json_file(self.session_table_name)
328
+
329
+ for i, session_data in enumerate(sessions):
330
+ if (
331
+ session_data.get("session_id") == session_id
332
+ and session_data.get("session_type") == session_type.value
333
+ ):
334
+ # Update session name in session_data
335
+ if "session_data" not in session_data:
336
+ session_data["session_data"] = {}
337
+ session_data["session_data"]["session_name"] = session_name
338
+
339
+ sessions[i] = session_data
340
+ self._write_json_file(self.session_table_name, sessions)
341
+
342
+ if not deserialize:
343
+ return session_data
344
+
345
+ if session_type == SessionType.AGENT:
346
+ return AgentSession.from_dict(session_data)
347
+ elif session_type == SessionType.TEAM:
348
+ return TeamSession.from_dict(session_data)
349
+ elif session_type == SessionType.WORKFLOW:
350
+ return WorkflowSession.from_dict(session_data)
351
+
352
+ return None
353
+ except Exception as e:
354
+ log_warning(f"Exception renaming session: {e}")
355
+ return None
356
+
357
+ def upsert_session(
358
+ self, session: Session, deserialize: Optional[bool] = True
359
+ ) -> Optional[Union[Session, Dict[str, Any]]]:
360
+ """Insert or update a session in the GCS JSON file."""
361
+ try:
362
+ sessions = self._read_json_file(self.session_table_name, create_table_if_not_found=True)
363
+ session_dict = session.to_dict()
364
+
365
+ # Add session_type based on session instance type
366
+ if isinstance(session, AgentSession):
367
+ session_dict["session_type"] = SessionType.AGENT.value
368
+ elif isinstance(session, TeamSession):
369
+ session_dict["session_type"] = SessionType.TEAM.value
370
+ elif isinstance(session, WorkflowSession):
371
+ session_dict["session_type"] = SessionType.WORKFLOW.value
372
+
373
+ # Find existing session to update
374
+ session_updated = False
375
+ for i, existing_session in enumerate(sessions):
376
+ if existing_session.get("session_id") == session_dict.get("session_id") and self._matches_session_key(
377
+ existing_session, session
378
+ ):
379
+ # Update existing session
380
+ session_dict["updated_at"] = int(time.time())
381
+ sessions[i] = session_dict
382
+ session_updated = True
383
+ break
384
+
385
+ if not session_updated:
386
+ # Add new session
387
+ session_dict["created_at"] = session_dict.get("created_at", int(time.time()))
388
+ session_dict["updated_at"] = session_dict.get("created_at")
389
+ sessions.append(session_dict)
390
+
391
+ self._write_json_file(self.session_table_name, sessions)
392
+
393
+ if not deserialize:
394
+ return session_dict
395
+
396
+ return session
397
+
398
+ except Exception as e:
399
+ log_warning(f"Exception upserting session: {e}")
400
+ return None
401
+
402
+ def _matches_session_key(self, existing_session: Dict[str, Any], session: Session) -> bool:
403
+ """Check if existing session matches the key for the session type."""
404
+ if isinstance(session, AgentSession):
405
+ return existing_session.get("agent_id") == session.agent_id
406
+ elif isinstance(session, TeamSession):
407
+ return existing_session.get("team_id") == session.team_id
408
+ elif isinstance(session, WorkflowSession):
409
+ return existing_session.get("workflow_id") == session.workflow_id
410
+ return False
411
+
412
+ # -- Memory methods --
413
+ def delete_user_memory(self, memory_id: str) -> None:
414
+ """Delete a user memory from the GCS JSON file."""
415
+ try:
416
+ memories = self._read_json_file(self.memory_table_name)
417
+ original_count = len(memories)
418
+ memories = [m for m in memories if m.get("memory_id") != memory_id]
419
+
420
+ if len(memories) < original_count:
421
+ self._write_json_file(self.memory_table_name, memories)
422
+ log_debug(f"Successfully deleted user memory id: {memory_id}")
423
+
424
+ else:
425
+ log_debug(f"No user memory found with id: {memory_id}")
426
+
427
+ except Exception as e:
428
+ log_warning(f"Error deleting user memory: {e}")
429
+
430
+ def delete_user_memories(self, memory_ids: List[str]) -> None:
431
+ """Delete multiple user memories from the GCS JSON file."""
432
+ try:
433
+ memories = self._read_json_file(self.memory_table_name)
434
+ memories = [m for m in memories if m.get("memory_id") not in memory_ids]
435
+ self._write_json_file(self.memory_table_name, memories)
436
+ log_debug(f"Successfully deleted user memories with ids: {memory_ids}")
437
+ except Exception as e:
438
+ log_warning(f"Error deleting user memories: {e}")
439
+
440
+ def get_all_memory_topics(self) -> List[str]:
441
+ """Get all memory topics from the GCS JSON file."""
442
+ try:
443
+ memories = self._read_json_file(self.memory_table_name)
444
+ topics = set()
445
+ for memory in memories:
446
+ memory_topics = memory.get("topics", [])
447
+ if isinstance(memory_topics, list):
448
+ topics.update(memory_topics)
449
+ return list(topics)
450
+
451
+ except Exception as e:
452
+ log_warning(f"Exception reading from memory file: {e}")
453
+ return []
454
+
455
+ def get_user_memory(
456
+ self, memory_id: str, deserialize: Optional[bool] = True
457
+ ) -> Optional[Union[UserMemory, Dict[str, Any]]]:
458
+ """Get a memory from the GCS JSON file."""
459
+ try:
460
+ memories = self._read_json_file(self.memory_table_name)
461
+
462
+ for memory_data in memories:
463
+ if memory_data.get("memory_id") == memory_id:
464
+ if not deserialize:
465
+ return memory_data
466
+
467
+ return UserMemory.from_dict(memory_data)
468
+
469
+ return None
470
+ except Exception as e:
471
+ log_warning(f"Exception reading from memory file: {e}")
472
+ return None
473
+
474
+ def get_user_memories(
475
+ self,
476
+ user_id: Optional[str] = None,
477
+ agent_id: Optional[str] = None,
478
+ team_id: Optional[str] = None,
479
+ topics: Optional[List[str]] = None,
480
+ search_content: Optional[str] = None,
481
+ limit: Optional[int] = None,
482
+ page: Optional[int] = None,
483
+ sort_by: Optional[str] = None,
484
+ sort_order: Optional[str] = None,
485
+ deserialize: Optional[bool] = True,
486
+ ) -> Union[List[UserMemory], Tuple[List[Dict[str, Any]], int]]:
487
+ """Get all memories from the GCS JSON file with filtering and pagination."""
488
+ try:
489
+ memories = self._read_json_file(self.memory_table_name)
490
+
491
+ # Apply filters
492
+ filtered_memories = []
493
+ for memory_data in memories:
494
+ if user_id is not None and memory_data.get("user_id") != user_id:
495
+ continue
496
+ if agent_id is not None and memory_data.get("agent_id") != agent_id:
497
+ continue
498
+ if team_id is not None and memory_data.get("team_id") != team_id:
499
+ continue
500
+ if topics is not None:
501
+ memory_topics = memory_data.get("topics", [])
502
+ if not any(topic in memory_topics for topic in topics):
503
+ continue
504
+ if search_content is not None:
505
+ memory_content = str(memory_data.get("memory", ""))
506
+ if search_content.lower() not in memory_content.lower():
507
+ continue
508
+
509
+ filtered_memories.append(memory_data)
510
+
511
+ total_count = len(filtered_memories)
512
+
513
+ # Apply sorting
514
+ filtered_memories = apply_sorting(filtered_memories, sort_by, sort_order)
515
+
516
+ # Apply pagination
517
+ if limit is not None:
518
+ start_idx = 0
519
+ if page is not None:
520
+ start_idx = (page - 1) * limit
521
+ filtered_memories = filtered_memories[start_idx : start_idx + limit]
522
+
523
+ if not deserialize:
524
+ return filtered_memories, total_count
525
+
526
+ return [UserMemory.from_dict(memory) for memory in filtered_memories]
527
+
528
+ except Exception as e:
529
+ log_warning(f"Exception reading from memory file: {e}")
530
+ return [] if deserialize else ([], 0)
531
+
532
+ def get_user_memory_stats(
533
+ self, limit: Optional[int] = None, page: Optional[int] = None
534
+ ) -> Tuple[List[Dict[str, Any]], int]:
535
+ """Get user memory statistics."""
536
+ try:
537
+ memories = self._read_json_file(self.memory_table_name)
538
+ user_stats = {}
539
+
540
+ for memory in memories:
541
+ user_id = memory.get("user_id")
542
+ if user_id:
543
+ if user_id not in user_stats:
544
+ user_stats[user_id] = {"user_id": user_id, "total_memories": 0, "last_memory_updated_at": 0}
545
+ user_stats[user_id]["total_memories"] += 1
546
+ updated_at = memory.get("updated_at", 0)
547
+ if updated_at > user_stats[user_id]["last_memory_updated_at"]:
548
+ user_stats[user_id]["last_memory_updated_at"] = updated_at
549
+
550
+ stats_list = list(user_stats.values())
551
+ stats_list.sort(key=lambda x: x["last_memory_updated_at"], reverse=True)
552
+
553
+ total_count = len(stats_list)
554
+
555
+ # Apply pagination
556
+ if limit is not None:
557
+ start_idx = 0
558
+ if page is not None:
559
+ start_idx = (page - 1) * limit
560
+ stats_list = stats_list[start_idx : start_idx + limit]
561
+
562
+ return stats_list, total_count
563
+
564
+ except Exception as e:
565
+ log_warning(f"Exception getting user memory stats: {e}")
566
+ return [], 0
567
+
568
+ def upsert_user_memory(
569
+ self, memory: UserMemory, deserialize: Optional[bool] = True
570
+ ) -> Optional[Union[UserMemory, Dict[str, Any]]]:
571
+ """Upsert a user memory in the GCS JSON file."""
572
+ try:
573
+ memories = self._read_json_file(self.memory_table_name, create_table_if_not_found=True)
574
+
575
+ if memory.memory_id is None:
576
+ memory.memory_id = str(uuid4())
577
+
578
+ memory_dict = memory.to_dict() if hasattr(memory, "to_dict") else memory.__dict__
579
+ memory_dict["updated_at"] = int(time.time())
580
+
581
+ # Find existing memory to update
582
+ memory_updated = False
583
+ for i, existing_memory in enumerate(memories):
584
+ if existing_memory.get("memory_id") == memory.memory_id:
585
+ memories[i] = memory_dict
586
+ memory_updated = True
587
+ break
588
+
589
+ if not memory_updated:
590
+ memories.append(memory_dict)
591
+
592
+ self._write_json_file(self.memory_table_name, memories)
593
+
594
+ if not deserialize:
595
+ return memory_dict
596
+ return UserMemory.from_dict(memory_dict)
597
+
598
+ except Exception as e:
599
+ log_error(f"Exception upserting user memory: {e}")
600
+ return None
601
+
602
+ def clear_memories(self) -> None:
603
+ """Delete all memories from the database.
604
+
605
+ Raises:
606
+ Exception: If an error occurs during deletion.
607
+ """
608
+ try:
609
+ # Simply write an empty list to the memory JSON file
610
+ self._write_json_file(self.memory_table_name, [])
611
+
612
+ except Exception as e:
613
+ log_warning(f"Exception deleting all memories: {e}")
614
+
615
+ # -- Metrics methods --
616
+ def calculate_metrics(self) -> Optional[list[dict]]:
617
+ """Calculate metrics for all dates without complete metrics."""
618
+ try:
619
+ metrics = self._read_json_file(self.metrics_table_name, create_table_if_not_found=True)
620
+
621
+ starting_date = self._get_metrics_calculation_starting_date(metrics)
622
+ if starting_date is None:
623
+ log_info("No session data found. Won't calculate metrics.")
624
+ return None
625
+
626
+ dates_to_process = get_dates_to_calculate_metrics_for(starting_date)
627
+ if not dates_to_process:
628
+ log_info("Metrics already calculated for all relevant dates.")
629
+ return None
630
+
631
+ start_timestamp = int(datetime.combine(dates_to_process[0], datetime.min.time()).timestamp())
632
+ end_timestamp = int(
633
+ datetime.combine(dates_to_process[-1] + timedelta(days=1), datetime.min.time()).timestamp()
634
+ )
635
+
636
+ sessions = self._get_all_sessions_for_metrics_calculation(start_timestamp, end_timestamp)
637
+ all_sessions_data = fetch_all_sessions_data(
638
+ sessions=sessions, dates_to_process=dates_to_process, start_timestamp=start_timestamp
639
+ )
640
+ if not all_sessions_data:
641
+ log_info("No new session data found. Won't calculate metrics.")
642
+ return None
643
+
644
+ results = []
645
+
646
+ for date_to_process in dates_to_process:
647
+ date_key = date_to_process.isoformat()
648
+ sessions_for_date = all_sessions_data.get(date_key, {})
649
+
650
+ # Skip dates with no sessions
651
+ if not any(len(sessions) > 0 for sessions in sessions_for_date.values()):
652
+ continue
653
+
654
+ metrics_record = calculate_date_metrics(date_to_process, sessions_for_date)
655
+
656
+ # Upsert metrics record
657
+ existing_record_idx = None
658
+ for i, existing_metric in enumerate(metrics):
659
+ if (
660
+ existing_metric.get("date") == str(date_to_process)
661
+ and existing_metric.get("aggregation_period") == "daily"
662
+ ):
663
+ existing_record_idx = i
664
+ break
665
+
666
+ if existing_record_idx is not None:
667
+ metrics[existing_record_idx] = metrics_record
668
+ else:
669
+ metrics.append(metrics_record)
670
+
671
+ results.append(metrics_record)
672
+
673
+ if results:
674
+ self._write_json_file(self.metrics_table_name, metrics)
675
+
676
+ return results
677
+
678
+ except Exception as e:
679
+ log_warning(f"Exception refreshing metrics: {e}")
680
+ return None
681
+
682
+ def _get_metrics_calculation_starting_date(self, metrics: List[Dict[str, Any]]) -> Optional[date]:
683
+ """Get the first date for which metrics calculation is needed."""
684
+ if metrics:
685
+ # Sort by date in descending order
686
+ sorted_metrics = sorted(metrics, key=lambda x: x.get("date", ""), reverse=True)
687
+ latest_metric = sorted_metrics[0]
688
+
689
+ if latest_metric.get("completed", False):
690
+ latest_date = datetime.strptime(latest_metric["date"], "%Y-%m-%d").date()
691
+ return latest_date + timedelta(days=1)
692
+ else:
693
+ return datetime.strptime(latest_metric["date"], "%Y-%m-%d").date()
694
+
695
+ # No metrics records. Return the date of the first recorded session.
696
+ # We need to get sessions of all types, so we'll read directly from the file
697
+ all_sessions = self._read_json_file(self.session_table_name)
698
+ if all_sessions:
699
+ # Sort by created_at
700
+ all_sessions.sort(key=lambda x: x.get("created_at", 0))
701
+ first_session_date = all_sessions[0]["created_at"]
702
+ return datetime.fromtimestamp(first_session_date, tz=timezone.utc).date()
703
+
704
+ return None
705
+
706
+ def _get_all_sessions_for_metrics_calculation(
707
+ self, start_timestamp: Optional[int] = None, end_timestamp: Optional[int] = None
708
+ ) -> List[Dict[str, Any]]:
709
+ """Get all sessions for metrics calculation."""
710
+ try:
711
+ sessions = self._read_json_file(self.session_table_name)
712
+
713
+ filtered_sessions = []
714
+ for session in sessions:
715
+ created_at = session.get("created_at", 0)
716
+ if start_timestamp is not None and created_at < start_timestamp:
717
+ continue
718
+ if end_timestamp is not None and created_at >= end_timestamp:
719
+ continue
720
+
721
+ # Only include necessary fields for metrics
722
+ filtered_session = {
723
+ "user_id": session.get("user_id"),
724
+ "session_data": session.get("session_data"),
725
+ "runs": session.get("runs"),
726
+ "created_at": session.get("created_at"),
727
+ "session_type": session.get("session_type"),
728
+ }
729
+ filtered_sessions.append(filtered_session)
730
+
731
+ return filtered_sessions
732
+
733
+ except Exception as e:
734
+ log_warning(f"Exception reading sessions for metrics: {e}")
735
+ return []
736
+
737
+ def get_metrics(
738
+ self,
739
+ starting_date: Optional[date] = None,
740
+ ending_date: Optional[date] = None,
741
+ ) -> Tuple[List[dict], Optional[int]]:
742
+ """Get all metrics matching the given date range."""
743
+ try:
744
+ metrics = self._read_json_file(self.metrics_table_name)
745
+
746
+ filtered_metrics = []
747
+ latest_updated_at = None
748
+
749
+ for metric in metrics:
750
+ metric_date = datetime.strptime(metric.get("date", ""), "%Y-%m-%d").date()
751
+
752
+ if starting_date and metric_date < starting_date:
753
+ continue
754
+ if ending_date and metric_date > ending_date:
755
+ continue
756
+
757
+ filtered_metrics.append(metric)
758
+
759
+ updated_at = metric.get("updated_at")
760
+ if updated_at and (latest_updated_at is None or updated_at > latest_updated_at):
761
+ latest_updated_at = updated_at
762
+
763
+ return filtered_metrics, latest_updated_at
764
+
765
+ except Exception as e:
766
+ log_warning(f"Exception getting metrics: {e}")
767
+ return [], None
768
+
769
+ # -- Knowledge methods --
770
+ def delete_knowledge_content(self, id: str):
771
+ """Delete knowledge content by ID."""
772
+ try:
773
+ knowledge_items = self._read_json_file(self.knowledge_table_name)
774
+ knowledge_items = [item for item in knowledge_items if item.get("id") != id]
775
+ self._write_json_file(self.knowledge_table_name, knowledge_items)
776
+ except Exception as e:
777
+ log_warning(f"Error deleting knowledge content: {e}")
778
+
779
+ def get_knowledge_content(self, id: str) -> Optional[KnowledgeRow]:
780
+ """Get knowledge content by ID."""
781
+ try:
782
+ knowledge_items = self._read_json_file(self.knowledge_table_name)
783
+
784
+ for item in knowledge_items:
785
+ if item.get("id") == id:
786
+ return KnowledgeRow.model_validate(item)
787
+
788
+ return None
789
+ except Exception as e:
790
+ log_warning(f"Error getting knowledge content: {e}")
791
+ return None
792
+
793
+ def get_knowledge_contents(
794
+ self,
795
+ limit: Optional[int] = None,
796
+ page: Optional[int] = None,
797
+ sort_by: Optional[str] = None,
798
+ sort_order: Optional[str] = None,
799
+ ) -> Tuple[List[KnowledgeRow], int]:
800
+ """Get all knowledge contents from the GCS JSON file."""
801
+ try:
802
+ knowledge_items = self._read_json_file(self.knowledge_table_name)
803
+
804
+ total_count = len(knowledge_items)
805
+
806
+ # Apply sorting
807
+ knowledge_items = apply_sorting(knowledge_items, sort_by, sort_order)
808
+
809
+ # Apply pagination
810
+ if limit is not None:
811
+ start_idx = 0
812
+ if page is not None:
813
+ start_idx = (page - 1) * limit
814
+ knowledge_items = knowledge_items[start_idx : start_idx + limit]
815
+
816
+ return [KnowledgeRow.model_validate(item) for item in knowledge_items], total_count
817
+
818
+ except Exception as e:
819
+ log_warning(f"Error getting knowledge contents: {e}")
820
+ return [], 0
821
+
822
+ def upsert_knowledge_content(self, knowledge_row: KnowledgeRow):
823
+ """Upsert knowledge content in the GCS JSON file."""
824
+ try:
825
+ knowledge_items = self._read_json_file(self.knowledge_table_name, create_table_if_not_found=True)
826
+ knowledge_dict = knowledge_row.model_dump()
827
+
828
+ # Find existing item to update
829
+ item_updated = False
830
+ for i, existing_item in enumerate(knowledge_items):
831
+ if existing_item.get("id") == knowledge_row.id:
832
+ knowledge_items[i] = knowledge_dict
833
+ item_updated = True
834
+ break
835
+
836
+ if not item_updated:
837
+ knowledge_items.append(knowledge_dict)
838
+
839
+ self._write_json_file(self.knowledge_table_name, knowledge_items)
840
+ return knowledge_row
841
+
842
+ except Exception as e:
843
+ log_warning(f"Error upserting knowledge row: {e}")
844
+ return None
845
+
846
+ # -- Eval methods --
847
+ def create_eval_run(self, eval_run: EvalRunRecord) -> Optional[EvalRunRecord]:
848
+ """Create an EvalRunRecord in the GCS JSON file."""
849
+ try:
850
+ eval_runs = self._read_json_file(self.eval_table_name, create_table_if_not_found=True)
851
+
852
+ current_time = int(time.time())
853
+ eval_dict = eval_run.model_dump()
854
+ eval_dict["created_at"] = current_time
855
+ eval_dict["updated_at"] = current_time
856
+
857
+ eval_runs.append(eval_dict)
858
+ self._write_json_file(self.eval_table_name, eval_runs)
859
+
860
+ return eval_run
861
+ except Exception as e:
862
+ log_warning(f"Error creating eval run: {e}")
863
+ return None
864
+
865
+ def delete_eval_run(self, eval_run_id: str) -> None:
866
+ """Delete an eval run from the GCS JSON file."""
867
+ try:
868
+ eval_runs = self._read_json_file(self.eval_table_name)
869
+ original_count = len(eval_runs)
870
+ eval_runs = [run for run in eval_runs if run.get("run_id") != eval_run_id]
871
+
872
+ if len(eval_runs) < original_count:
873
+ self._write_json_file(self.eval_table_name, eval_runs)
874
+ log_debug(f"Deleted eval run with ID: {eval_run_id}")
875
+ else:
876
+ log_warning(f"No eval run found with ID: {eval_run_id}")
877
+ except Exception as e:
878
+ log_warning(f"Error deleting eval run {eval_run_id}: {e}")
879
+
880
+ def delete_eval_runs(self, eval_run_ids: List[str]) -> None:
881
+ """Delete multiple eval runs from the GCS JSON file."""
882
+ try:
883
+ eval_runs = self._read_json_file(self.eval_table_name)
884
+ original_count = len(eval_runs)
885
+ eval_runs = [run for run in eval_runs if run.get("run_id") not in eval_run_ids]
886
+
887
+ deleted_count = original_count - len(eval_runs)
888
+ if deleted_count > 0:
889
+ self._write_json_file(self.eval_table_name, eval_runs)
890
+ log_debug(f"Deleted {deleted_count} eval runs")
891
+ else:
892
+ log_warning(f"No eval runs found with IDs: {eval_run_ids}")
893
+ except Exception as e:
894
+ log_warning(f"Error deleting eval runs {eval_run_ids}: {e}")
895
+
896
+ def get_eval_run(
897
+ self, eval_run_id: str, deserialize: Optional[bool] = True
898
+ ) -> Optional[Union[EvalRunRecord, Dict[str, Any]]]:
899
+ """Get an eval run from the GCS JSON file."""
900
+ try:
901
+ eval_runs = self._read_json_file(self.eval_table_name)
902
+
903
+ for run_data in eval_runs:
904
+ if run_data.get("run_id") == eval_run_id:
905
+ if not deserialize:
906
+ return run_data
907
+ return EvalRunRecord.model_validate(run_data)
908
+
909
+ return None
910
+ except Exception as e:
911
+ log_warning(f"Exception getting eval run {eval_run_id}: {e}")
912
+ return None
913
+
914
+ def get_eval_runs(
915
+ self,
916
+ limit: Optional[int] = None,
917
+ page: Optional[int] = None,
918
+ sort_by: Optional[str] = None,
919
+ sort_order: Optional[str] = None,
920
+ agent_id: Optional[str] = None,
921
+ team_id: Optional[str] = None,
922
+ workflow_id: Optional[str] = None,
923
+ model_id: Optional[str] = None,
924
+ filter_type: Optional[EvalFilterType] = None,
925
+ eval_type: Optional[List[EvalType]] = None,
926
+ deserialize: Optional[bool] = True,
927
+ ) -> Union[List[EvalRunRecord], Tuple[List[Dict[str, Any]], int]]:
928
+ """Get all eval runs from the GCS JSON file with filtering and pagination."""
929
+ try:
930
+ eval_runs = self._read_json_file(self.eval_table_name)
931
+
932
+ # Apply filters
933
+ filtered_runs = []
934
+ for run_data in eval_runs:
935
+ if agent_id is not None and run_data.get("agent_id") != agent_id:
936
+ continue
937
+ if team_id is not None and run_data.get("team_id") != team_id:
938
+ continue
939
+ if workflow_id is not None and run_data.get("workflow_id") != workflow_id:
940
+ continue
941
+ if model_id is not None and run_data.get("model_id") != model_id:
942
+ continue
943
+ if eval_type is not None and len(eval_type) > 0:
944
+ if run_data.get("eval_type") not in eval_type:
945
+ continue
946
+ if filter_type is not None:
947
+ if filter_type == EvalFilterType.AGENT and run_data.get("agent_id") is None:
948
+ continue
949
+ elif filter_type == EvalFilterType.TEAM and run_data.get("team_id") is None:
950
+ continue
951
+ elif filter_type == EvalFilterType.WORKFLOW and run_data.get("workflow_id") is None:
952
+ continue
953
+
954
+ filtered_runs.append(run_data)
955
+
956
+ total_count = len(filtered_runs)
957
+
958
+ # Apply sorting (default by created_at desc)
959
+ if sort_by is None:
960
+ filtered_runs.sort(key=lambda x: x.get("created_at", 0), reverse=True)
961
+ else:
962
+ filtered_runs = apply_sorting(filtered_runs, sort_by, sort_order)
963
+
964
+ # Apply pagination
965
+ if limit is not None:
966
+ start_idx = 0
967
+ if page is not None:
968
+ start_idx = (page - 1) * limit
969
+ filtered_runs = filtered_runs[start_idx : start_idx + limit]
970
+
971
+ if not deserialize:
972
+ return filtered_runs, total_count
973
+
974
+ return [EvalRunRecord.model_validate(run) for run in filtered_runs]
975
+
976
+ except Exception as e:
977
+ log_warning(f"Exception getting eval runs: {e}")
978
+ return [] if deserialize else ([], 0)
979
+
980
+ def rename_eval_run(
981
+ self, eval_run_id: str, name: str, deserialize: Optional[bool] = True
982
+ ) -> Optional[Union[EvalRunRecord, Dict[str, Any]]]:
983
+ """Rename an eval run in the GCS JSON file."""
984
+ try:
985
+ eval_runs = self._read_json_file(self.eval_table_name)
986
+
987
+ for i, run_data in enumerate(eval_runs):
988
+ if run_data.get("run_id") == eval_run_id:
989
+ run_data["name"] = name
990
+ run_data["updated_at"] = int(time.time())
991
+ eval_runs[i] = run_data
992
+ self._write_json_file(self.eval_table_name, eval_runs)
993
+
994
+ if not deserialize:
995
+ return run_data
996
+ return EvalRunRecord.model_validate(run_data)
997
+
998
+ return None
999
+ except Exception as e:
1000
+ log_warning(f"Error renaming eval run {eval_run_id}: {e}")
1001
+ return None