agno 2.2.13__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 (575) hide show
  1. agno/__init__.py +8 -0
  2. agno/agent/__init__.py +51 -0
  3. agno/agent/agent.py +10405 -0
  4. agno/api/__init__.py +0 -0
  5. agno/api/agent.py +28 -0
  6. agno/api/api.py +40 -0
  7. agno/api/evals.py +22 -0
  8. agno/api/os.py +17 -0
  9. agno/api/routes.py +13 -0
  10. agno/api/schemas/__init__.py +9 -0
  11. agno/api/schemas/agent.py +16 -0
  12. agno/api/schemas/evals.py +16 -0
  13. agno/api/schemas/os.py +14 -0
  14. agno/api/schemas/response.py +6 -0
  15. agno/api/schemas/team.py +16 -0
  16. agno/api/schemas/utils.py +21 -0
  17. agno/api/schemas/workflows.py +16 -0
  18. agno/api/settings.py +53 -0
  19. agno/api/team.py +30 -0
  20. agno/api/workflow.py +28 -0
  21. agno/cloud/aws/base.py +214 -0
  22. agno/cloud/aws/s3/__init__.py +2 -0
  23. agno/cloud/aws/s3/api_client.py +43 -0
  24. agno/cloud/aws/s3/bucket.py +195 -0
  25. agno/cloud/aws/s3/object.py +57 -0
  26. agno/culture/__init__.py +3 -0
  27. agno/culture/manager.py +956 -0
  28. agno/db/__init__.py +24 -0
  29. agno/db/async_postgres/__init__.py +3 -0
  30. agno/db/base.py +598 -0
  31. agno/db/dynamo/__init__.py +3 -0
  32. agno/db/dynamo/dynamo.py +2042 -0
  33. agno/db/dynamo/schemas.py +314 -0
  34. agno/db/dynamo/utils.py +743 -0
  35. agno/db/firestore/__init__.py +3 -0
  36. agno/db/firestore/firestore.py +1795 -0
  37. agno/db/firestore/schemas.py +140 -0
  38. agno/db/firestore/utils.py +376 -0
  39. agno/db/gcs_json/__init__.py +3 -0
  40. agno/db/gcs_json/gcs_json_db.py +1335 -0
  41. agno/db/gcs_json/utils.py +228 -0
  42. agno/db/in_memory/__init__.py +3 -0
  43. agno/db/in_memory/in_memory_db.py +1160 -0
  44. agno/db/in_memory/utils.py +230 -0
  45. agno/db/json/__init__.py +3 -0
  46. agno/db/json/json_db.py +1328 -0
  47. agno/db/json/utils.py +230 -0
  48. agno/db/migrations/__init__.py +0 -0
  49. agno/db/migrations/v1_to_v2.py +635 -0
  50. agno/db/mongo/__init__.py +17 -0
  51. agno/db/mongo/async_mongo.py +2026 -0
  52. agno/db/mongo/mongo.py +1982 -0
  53. agno/db/mongo/schemas.py +87 -0
  54. agno/db/mongo/utils.py +259 -0
  55. agno/db/mysql/__init__.py +3 -0
  56. agno/db/mysql/mysql.py +2308 -0
  57. agno/db/mysql/schemas.py +138 -0
  58. agno/db/mysql/utils.py +355 -0
  59. agno/db/postgres/__init__.py +4 -0
  60. agno/db/postgres/async_postgres.py +1927 -0
  61. agno/db/postgres/postgres.py +2260 -0
  62. agno/db/postgres/schemas.py +139 -0
  63. agno/db/postgres/utils.py +442 -0
  64. agno/db/redis/__init__.py +3 -0
  65. agno/db/redis/redis.py +1660 -0
  66. agno/db/redis/schemas.py +123 -0
  67. agno/db/redis/utils.py +346 -0
  68. agno/db/schemas/__init__.py +4 -0
  69. agno/db/schemas/culture.py +120 -0
  70. agno/db/schemas/evals.py +33 -0
  71. agno/db/schemas/knowledge.py +40 -0
  72. agno/db/schemas/memory.py +46 -0
  73. agno/db/schemas/metrics.py +0 -0
  74. agno/db/singlestore/__init__.py +3 -0
  75. agno/db/singlestore/schemas.py +130 -0
  76. agno/db/singlestore/singlestore.py +2272 -0
  77. agno/db/singlestore/utils.py +384 -0
  78. agno/db/sqlite/__init__.py +4 -0
  79. agno/db/sqlite/async_sqlite.py +2293 -0
  80. agno/db/sqlite/schemas.py +133 -0
  81. agno/db/sqlite/sqlite.py +2288 -0
  82. agno/db/sqlite/utils.py +431 -0
  83. agno/db/surrealdb/__init__.py +3 -0
  84. agno/db/surrealdb/metrics.py +292 -0
  85. agno/db/surrealdb/models.py +309 -0
  86. agno/db/surrealdb/queries.py +71 -0
  87. agno/db/surrealdb/surrealdb.py +1353 -0
  88. agno/db/surrealdb/utils.py +147 -0
  89. agno/db/utils.py +116 -0
  90. agno/debug.py +18 -0
  91. agno/eval/__init__.py +14 -0
  92. agno/eval/accuracy.py +834 -0
  93. agno/eval/performance.py +773 -0
  94. agno/eval/reliability.py +306 -0
  95. agno/eval/utils.py +119 -0
  96. agno/exceptions.py +161 -0
  97. agno/filters.py +354 -0
  98. agno/guardrails/__init__.py +6 -0
  99. agno/guardrails/base.py +19 -0
  100. agno/guardrails/openai.py +144 -0
  101. agno/guardrails/pii.py +94 -0
  102. agno/guardrails/prompt_injection.py +52 -0
  103. agno/integrations/__init__.py +0 -0
  104. agno/integrations/discord/__init__.py +3 -0
  105. agno/integrations/discord/client.py +203 -0
  106. agno/knowledge/__init__.py +5 -0
  107. agno/knowledge/chunking/__init__.py +0 -0
  108. agno/knowledge/chunking/agentic.py +79 -0
  109. agno/knowledge/chunking/document.py +91 -0
  110. agno/knowledge/chunking/fixed.py +57 -0
  111. agno/knowledge/chunking/markdown.py +151 -0
  112. agno/knowledge/chunking/recursive.py +63 -0
  113. agno/knowledge/chunking/row.py +39 -0
  114. agno/knowledge/chunking/semantic.py +86 -0
  115. agno/knowledge/chunking/strategy.py +165 -0
  116. agno/knowledge/content.py +74 -0
  117. agno/knowledge/document/__init__.py +5 -0
  118. agno/knowledge/document/base.py +58 -0
  119. agno/knowledge/embedder/__init__.py +5 -0
  120. agno/knowledge/embedder/aws_bedrock.py +343 -0
  121. agno/knowledge/embedder/azure_openai.py +210 -0
  122. agno/knowledge/embedder/base.py +23 -0
  123. agno/knowledge/embedder/cohere.py +323 -0
  124. agno/knowledge/embedder/fastembed.py +62 -0
  125. agno/knowledge/embedder/fireworks.py +13 -0
  126. agno/knowledge/embedder/google.py +258 -0
  127. agno/knowledge/embedder/huggingface.py +94 -0
  128. agno/knowledge/embedder/jina.py +182 -0
  129. agno/knowledge/embedder/langdb.py +22 -0
  130. agno/knowledge/embedder/mistral.py +206 -0
  131. agno/knowledge/embedder/nebius.py +13 -0
  132. agno/knowledge/embedder/ollama.py +154 -0
  133. agno/knowledge/embedder/openai.py +195 -0
  134. agno/knowledge/embedder/sentence_transformer.py +63 -0
  135. agno/knowledge/embedder/together.py +13 -0
  136. agno/knowledge/embedder/vllm.py +262 -0
  137. agno/knowledge/embedder/voyageai.py +165 -0
  138. agno/knowledge/knowledge.py +1988 -0
  139. agno/knowledge/reader/__init__.py +7 -0
  140. agno/knowledge/reader/arxiv_reader.py +81 -0
  141. agno/knowledge/reader/base.py +95 -0
  142. agno/knowledge/reader/csv_reader.py +166 -0
  143. agno/knowledge/reader/docx_reader.py +82 -0
  144. agno/knowledge/reader/field_labeled_csv_reader.py +292 -0
  145. agno/knowledge/reader/firecrawl_reader.py +201 -0
  146. agno/knowledge/reader/json_reader.py +87 -0
  147. agno/knowledge/reader/markdown_reader.py +137 -0
  148. agno/knowledge/reader/pdf_reader.py +431 -0
  149. agno/knowledge/reader/pptx_reader.py +101 -0
  150. agno/knowledge/reader/reader_factory.py +313 -0
  151. agno/knowledge/reader/s3_reader.py +89 -0
  152. agno/knowledge/reader/tavily_reader.py +194 -0
  153. agno/knowledge/reader/text_reader.py +115 -0
  154. agno/knowledge/reader/web_search_reader.py +372 -0
  155. agno/knowledge/reader/website_reader.py +455 -0
  156. agno/knowledge/reader/wikipedia_reader.py +59 -0
  157. agno/knowledge/reader/youtube_reader.py +78 -0
  158. agno/knowledge/remote_content/__init__.py +0 -0
  159. agno/knowledge/remote_content/remote_content.py +88 -0
  160. agno/knowledge/reranker/__init__.py +3 -0
  161. agno/knowledge/reranker/base.py +14 -0
  162. agno/knowledge/reranker/cohere.py +64 -0
  163. agno/knowledge/reranker/infinity.py +195 -0
  164. agno/knowledge/reranker/sentence_transformer.py +54 -0
  165. agno/knowledge/types.py +39 -0
  166. agno/knowledge/utils.py +189 -0
  167. agno/media.py +462 -0
  168. agno/memory/__init__.py +3 -0
  169. agno/memory/manager.py +1327 -0
  170. agno/models/__init__.py +0 -0
  171. agno/models/aimlapi/__init__.py +5 -0
  172. agno/models/aimlapi/aimlapi.py +45 -0
  173. agno/models/anthropic/__init__.py +5 -0
  174. agno/models/anthropic/claude.py +757 -0
  175. agno/models/aws/__init__.py +15 -0
  176. agno/models/aws/bedrock.py +701 -0
  177. agno/models/aws/claude.py +378 -0
  178. agno/models/azure/__init__.py +18 -0
  179. agno/models/azure/ai_foundry.py +485 -0
  180. agno/models/azure/openai_chat.py +131 -0
  181. agno/models/base.py +2175 -0
  182. agno/models/cerebras/__init__.py +12 -0
  183. agno/models/cerebras/cerebras.py +501 -0
  184. agno/models/cerebras/cerebras_openai.py +112 -0
  185. agno/models/cohere/__init__.py +5 -0
  186. agno/models/cohere/chat.py +389 -0
  187. agno/models/cometapi/__init__.py +5 -0
  188. agno/models/cometapi/cometapi.py +57 -0
  189. agno/models/dashscope/__init__.py +5 -0
  190. agno/models/dashscope/dashscope.py +91 -0
  191. agno/models/deepinfra/__init__.py +5 -0
  192. agno/models/deepinfra/deepinfra.py +28 -0
  193. agno/models/deepseek/__init__.py +5 -0
  194. agno/models/deepseek/deepseek.py +61 -0
  195. agno/models/defaults.py +1 -0
  196. agno/models/fireworks/__init__.py +5 -0
  197. agno/models/fireworks/fireworks.py +26 -0
  198. agno/models/google/__init__.py +5 -0
  199. agno/models/google/gemini.py +1085 -0
  200. agno/models/groq/__init__.py +5 -0
  201. agno/models/groq/groq.py +556 -0
  202. agno/models/huggingface/__init__.py +5 -0
  203. agno/models/huggingface/huggingface.py +491 -0
  204. agno/models/ibm/__init__.py +5 -0
  205. agno/models/ibm/watsonx.py +422 -0
  206. agno/models/internlm/__init__.py +3 -0
  207. agno/models/internlm/internlm.py +26 -0
  208. agno/models/langdb/__init__.py +1 -0
  209. agno/models/langdb/langdb.py +48 -0
  210. agno/models/litellm/__init__.py +14 -0
  211. agno/models/litellm/chat.py +468 -0
  212. agno/models/litellm/litellm_openai.py +25 -0
  213. agno/models/llama_cpp/__init__.py +5 -0
  214. agno/models/llama_cpp/llama_cpp.py +22 -0
  215. agno/models/lmstudio/__init__.py +5 -0
  216. agno/models/lmstudio/lmstudio.py +25 -0
  217. agno/models/message.py +434 -0
  218. agno/models/meta/__init__.py +12 -0
  219. agno/models/meta/llama.py +475 -0
  220. agno/models/meta/llama_openai.py +78 -0
  221. agno/models/metrics.py +120 -0
  222. agno/models/mistral/__init__.py +5 -0
  223. agno/models/mistral/mistral.py +432 -0
  224. agno/models/nebius/__init__.py +3 -0
  225. agno/models/nebius/nebius.py +54 -0
  226. agno/models/nexus/__init__.py +3 -0
  227. agno/models/nexus/nexus.py +22 -0
  228. agno/models/nvidia/__init__.py +5 -0
  229. agno/models/nvidia/nvidia.py +28 -0
  230. agno/models/ollama/__init__.py +5 -0
  231. agno/models/ollama/chat.py +441 -0
  232. agno/models/openai/__init__.py +9 -0
  233. agno/models/openai/chat.py +883 -0
  234. agno/models/openai/like.py +27 -0
  235. agno/models/openai/responses.py +1050 -0
  236. agno/models/openrouter/__init__.py +5 -0
  237. agno/models/openrouter/openrouter.py +66 -0
  238. agno/models/perplexity/__init__.py +5 -0
  239. agno/models/perplexity/perplexity.py +187 -0
  240. agno/models/portkey/__init__.py +3 -0
  241. agno/models/portkey/portkey.py +81 -0
  242. agno/models/requesty/__init__.py +5 -0
  243. agno/models/requesty/requesty.py +52 -0
  244. agno/models/response.py +199 -0
  245. agno/models/sambanova/__init__.py +5 -0
  246. agno/models/sambanova/sambanova.py +28 -0
  247. agno/models/siliconflow/__init__.py +5 -0
  248. agno/models/siliconflow/siliconflow.py +25 -0
  249. agno/models/together/__init__.py +5 -0
  250. agno/models/together/together.py +25 -0
  251. agno/models/utils.py +266 -0
  252. agno/models/vercel/__init__.py +3 -0
  253. agno/models/vercel/v0.py +26 -0
  254. agno/models/vertexai/__init__.py +0 -0
  255. agno/models/vertexai/claude.py +70 -0
  256. agno/models/vllm/__init__.py +3 -0
  257. agno/models/vllm/vllm.py +78 -0
  258. agno/models/xai/__init__.py +3 -0
  259. agno/models/xai/xai.py +113 -0
  260. agno/os/__init__.py +3 -0
  261. agno/os/app.py +876 -0
  262. agno/os/auth.py +57 -0
  263. agno/os/config.py +104 -0
  264. agno/os/interfaces/__init__.py +1 -0
  265. agno/os/interfaces/a2a/__init__.py +3 -0
  266. agno/os/interfaces/a2a/a2a.py +42 -0
  267. agno/os/interfaces/a2a/router.py +250 -0
  268. agno/os/interfaces/a2a/utils.py +924 -0
  269. agno/os/interfaces/agui/__init__.py +3 -0
  270. agno/os/interfaces/agui/agui.py +47 -0
  271. agno/os/interfaces/agui/router.py +144 -0
  272. agno/os/interfaces/agui/utils.py +534 -0
  273. agno/os/interfaces/base.py +25 -0
  274. agno/os/interfaces/slack/__init__.py +3 -0
  275. agno/os/interfaces/slack/router.py +148 -0
  276. agno/os/interfaces/slack/security.py +30 -0
  277. agno/os/interfaces/slack/slack.py +47 -0
  278. agno/os/interfaces/whatsapp/__init__.py +3 -0
  279. agno/os/interfaces/whatsapp/router.py +211 -0
  280. agno/os/interfaces/whatsapp/security.py +53 -0
  281. agno/os/interfaces/whatsapp/whatsapp.py +36 -0
  282. agno/os/mcp.py +292 -0
  283. agno/os/middleware/__init__.py +7 -0
  284. agno/os/middleware/jwt.py +233 -0
  285. agno/os/router.py +1763 -0
  286. agno/os/routers/__init__.py +3 -0
  287. agno/os/routers/evals/__init__.py +3 -0
  288. agno/os/routers/evals/evals.py +430 -0
  289. agno/os/routers/evals/schemas.py +142 -0
  290. agno/os/routers/evals/utils.py +162 -0
  291. agno/os/routers/health.py +31 -0
  292. agno/os/routers/home.py +52 -0
  293. agno/os/routers/knowledge/__init__.py +3 -0
  294. agno/os/routers/knowledge/knowledge.py +997 -0
  295. agno/os/routers/knowledge/schemas.py +178 -0
  296. agno/os/routers/memory/__init__.py +3 -0
  297. agno/os/routers/memory/memory.py +515 -0
  298. agno/os/routers/memory/schemas.py +62 -0
  299. agno/os/routers/metrics/__init__.py +3 -0
  300. agno/os/routers/metrics/metrics.py +190 -0
  301. agno/os/routers/metrics/schemas.py +47 -0
  302. agno/os/routers/session/__init__.py +3 -0
  303. agno/os/routers/session/session.py +997 -0
  304. agno/os/schema.py +1055 -0
  305. agno/os/settings.py +43 -0
  306. agno/os/utils.py +630 -0
  307. agno/py.typed +0 -0
  308. agno/reasoning/__init__.py +0 -0
  309. agno/reasoning/anthropic.py +80 -0
  310. agno/reasoning/azure_ai_foundry.py +67 -0
  311. agno/reasoning/deepseek.py +63 -0
  312. agno/reasoning/default.py +97 -0
  313. agno/reasoning/gemini.py +73 -0
  314. agno/reasoning/groq.py +71 -0
  315. agno/reasoning/helpers.py +63 -0
  316. agno/reasoning/ollama.py +67 -0
  317. agno/reasoning/openai.py +86 -0
  318. agno/reasoning/step.py +31 -0
  319. agno/reasoning/vertexai.py +76 -0
  320. agno/run/__init__.py +6 -0
  321. agno/run/agent.py +787 -0
  322. agno/run/base.py +229 -0
  323. agno/run/cancel.py +81 -0
  324. agno/run/messages.py +32 -0
  325. agno/run/team.py +753 -0
  326. agno/run/workflow.py +708 -0
  327. agno/session/__init__.py +10 -0
  328. agno/session/agent.py +295 -0
  329. agno/session/summary.py +265 -0
  330. agno/session/team.py +392 -0
  331. agno/session/workflow.py +205 -0
  332. agno/team/__init__.py +37 -0
  333. agno/team/team.py +8793 -0
  334. agno/tools/__init__.py +10 -0
  335. agno/tools/agentql.py +120 -0
  336. agno/tools/airflow.py +69 -0
  337. agno/tools/api.py +122 -0
  338. agno/tools/apify.py +314 -0
  339. agno/tools/arxiv.py +127 -0
  340. agno/tools/aws_lambda.py +53 -0
  341. agno/tools/aws_ses.py +66 -0
  342. agno/tools/baidusearch.py +89 -0
  343. agno/tools/bitbucket.py +292 -0
  344. agno/tools/brandfetch.py +213 -0
  345. agno/tools/bravesearch.py +106 -0
  346. agno/tools/brightdata.py +367 -0
  347. agno/tools/browserbase.py +209 -0
  348. agno/tools/calcom.py +255 -0
  349. agno/tools/calculator.py +151 -0
  350. agno/tools/cartesia.py +187 -0
  351. agno/tools/clickup.py +244 -0
  352. agno/tools/confluence.py +240 -0
  353. agno/tools/crawl4ai.py +158 -0
  354. agno/tools/csv_toolkit.py +185 -0
  355. agno/tools/dalle.py +110 -0
  356. agno/tools/daytona.py +475 -0
  357. agno/tools/decorator.py +262 -0
  358. agno/tools/desi_vocal.py +108 -0
  359. agno/tools/discord.py +161 -0
  360. agno/tools/docker.py +716 -0
  361. agno/tools/duckdb.py +379 -0
  362. agno/tools/duckduckgo.py +91 -0
  363. agno/tools/e2b.py +703 -0
  364. agno/tools/eleven_labs.py +196 -0
  365. agno/tools/email.py +67 -0
  366. agno/tools/evm.py +129 -0
  367. agno/tools/exa.py +396 -0
  368. agno/tools/fal.py +127 -0
  369. agno/tools/file.py +240 -0
  370. agno/tools/file_generation.py +350 -0
  371. agno/tools/financial_datasets.py +288 -0
  372. agno/tools/firecrawl.py +143 -0
  373. agno/tools/function.py +1187 -0
  374. agno/tools/giphy.py +93 -0
  375. agno/tools/github.py +1760 -0
  376. agno/tools/gmail.py +922 -0
  377. agno/tools/google_bigquery.py +117 -0
  378. agno/tools/google_drive.py +270 -0
  379. agno/tools/google_maps.py +253 -0
  380. agno/tools/googlecalendar.py +674 -0
  381. agno/tools/googlesearch.py +98 -0
  382. agno/tools/googlesheets.py +377 -0
  383. agno/tools/hackernews.py +77 -0
  384. agno/tools/jina.py +101 -0
  385. agno/tools/jira.py +170 -0
  386. agno/tools/knowledge.py +218 -0
  387. agno/tools/linear.py +426 -0
  388. agno/tools/linkup.py +58 -0
  389. agno/tools/local_file_system.py +90 -0
  390. agno/tools/lumalab.py +183 -0
  391. agno/tools/mcp/__init__.py +10 -0
  392. agno/tools/mcp/mcp.py +331 -0
  393. agno/tools/mcp/multi_mcp.py +347 -0
  394. agno/tools/mcp/params.py +24 -0
  395. agno/tools/mcp_toolbox.py +284 -0
  396. agno/tools/mem0.py +193 -0
  397. agno/tools/memori.py +339 -0
  398. agno/tools/memory.py +419 -0
  399. agno/tools/mlx_transcribe.py +139 -0
  400. agno/tools/models/__init__.py +0 -0
  401. agno/tools/models/azure_openai.py +190 -0
  402. agno/tools/models/gemini.py +203 -0
  403. agno/tools/models/groq.py +158 -0
  404. agno/tools/models/morph.py +186 -0
  405. agno/tools/models/nebius.py +124 -0
  406. agno/tools/models_labs.py +195 -0
  407. agno/tools/moviepy_video.py +349 -0
  408. agno/tools/neo4j.py +134 -0
  409. agno/tools/newspaper.py +46 -0
  410. agno/tools/newspaper4k.py +93 -0
  411. agno/tools/notion.py +204 -0
  412. agno/tools/openai.py +202 -0
  413. agno/tools/openbb.py +160 -0
  414. agno/tools/opencv.py +321 -0
  415. agno/tools/openweather.py +233 -0
  416. agno/tools/oxylabs.py +385 -0
  417. agno/tools/pandas.py +102 -0
  418. agno/tools/parallel.py +314 -0
  419. agno/tools/postgres.py +257 -0
  420. agno/tools/pubmed.py +188 -0
  421. agno/tools/python.py +205 -0
  422. agno/tools/reasoning.py +283 -0
  423. agno/tools/reddit.py +467 -0
  424. agno/tools/replicate.py +117 -0
  425. agno/tools/resend.py +62 -0
  426. agno/tools/scrapegraph.py +222 -0
  427. agno/tools/searxng.py +152 -0
  428. agno/tools/serpapi.py +116 -0
  429. agno/tools/serper.py +255 -0
  430. agno/tools/shell.py +53 -0
  431. agno/tools/slack.py +136 -0
  432. agno/tools/sleep.py +20 -0
  433. agno/tools/spider.py +116 -0
  434. agno/tools/sql.py +154 -0
  435. agno/tools/streamlit/__init__.py +0 -0
  436. agno/tools/streamlit/components.py +113 -0
  437. agno/tools/tavily.py +254 -0
  438. agno/tools/telegram.py +48 -0
  439. agno/tools/todoist.py +218 -0
  440. agno/tools/tool_registry.py +1 -0
  441. agno/tools/toolkit.py +146 -0
  442. agno/tools/trafilatura.py +388 -0
  443. agno/tools/trello.py +274 -0
  444. agno/tools/twilio.py +186 -0
  445. agno/tools/user_control_flow.py +78 -0
  446. agno/tools/valyu.py +228 -0
  447. agno/tools/visualization.py +467 -0
  448. agno/tools/webbrowser.py +28 -0
  449. agno/tools/webex.py +76 -0
  450. agno/tools/website.py +54 -0
  451. agno/tools/webtools.py +45 -0
  452. agno/tools/whatsapp.py +286 -0
  453. agno/tools/wikipedia.py +63 -0
  454. agno/tools/workflow.py +278 -0
  455. agno/tools/x.py +335 -0
  456. agno/tools/yfinance.py +257 -0
  457. agno/tools/youtube.py +184 -0
  458. agno/tools/zendesk.py +82 -0
  459. agno/tools/zep.py +454 -0
  460. agno/tools/zoom.py +382 -0
  461. agno/utils/__init__.py +0 -0
  462. agno/utils/agent.py +820 -0
  463. agno/utils/audio.py +49 -0
  464. agno/utils/certs.py +27 -0
  465. agno/utils/code_execution.py +11 -0
  466. agno/utils/common.py +132 -0
  467. agno/utils/dttm.py +13 -0
  468. agno/utils/enum.py +22 -0
  469. agno/utils/env.py +11 -0
  470. agno/utils/events.py +696 -0
  471. agno/utils/format_str.py +16 -0
  472. agno/utils/functions.py +166 -0
  473. agno/utils/gemini.py +426 -0
  474. agno/utils/hooks.py +57 -0
  475. agno/utils/http.py +74 -0
  476. agno/utils/json_schema.py +234 -0
  477. agno/utils/knowledge.py +36 -0
  478. agno/utils/location.py +19 -0
  479. agno/utils/log.py +255 -0
  480. agno/utils/mcp.py +214 -0
  481. agno/utils/media.py +352 -0
  482. agno/utils/merge_dict.py +41 -0
  483. agno/utils/message.py +118 -0
  484. agno/utils/models/__init__.py +0 -0
  485. agno/utils/models/ai_foundry.py +43 -0
  486. agno/utils/models/claude.py +358 -0
  487. agno/utils/models/cohere.py +87 -0
  488. agno/utils/models/llama.py +78 -0
  489. agno/utils/models/mistral.py +98 -0
  490. agno/utils/models/openai_responses.py +140 -0
  491. agno/utils/models/schema_utils.py +153 -0
  492. agno/utils/models/watsonx.py +41 -0
  493. agno/utils/openai.py +257 -0
  494. agno/utils/pickle.py +32 -0
  495. agno/utils/pprint.py +178 -0
  496. agno/utils/print_response/__init__.py +0 -0
  497. agno/utils/print_response/agent.py +842 -0
  498. agno/utils/print_response/team.py +1724 -0
  499. agno/utils/print_response/workflow.py +1668 -0
  500. agno/utils/prompts.py +111 -0
  501. agno/utils/reasoning.py +108 -0
  502. agno/utils/response.py +163 -0
  503. agno/utils/response_iterator.py +17 -0
  504. agno/utils/safe_formatter.py +24 -0
  505. agno/utils/serialize.py +32 -0
  506. agno/utils/shell.py +22 -0
  507. agno/utils/streamlit.py +487 -0
  508. agno/utils/string.py +231 -0
  509. agno/utils/team.py +139 -0
  510. agno/utils/timer.py +41 -0
  511. agno/utils/tools.py +102 -0
  512. agno/utils/web.py +23 -0
  513. agno/utils/whatsapp.py +305 -0
  514. agno/utils/yaml_io.py +25 -0
  515. agno/vectordb/__init__.py +3 -0
  516. agno/vectordb/base.py +127 -0
  517. agno/vectordb/cassandra/__init__.py +5 -0
  518. agno/vectordb/cassandra/cassandra.py +501 -0
  519. agno/vectordb/cassandra/extra_param_mixin.py +11 -0
  520. agno/vectordb/cassandra/index.py +13 -0
  521. agno/vectordb/chroma/__init__.py +5 -0
  522. agno/vectordb/chroma/chromadb.py +929 -0
  523. agno/vectordb/clickhouse/__init__.py +9 -0
  524. agno/vectordb/clickhouse/clickhousedb.py +835 -0
  525. agno/vectordb/clickhouse/index.py +9 -0
  526. agno/vectordb/couchbase/__init__.py +3 -0
  527. agno/vectordb/couchbase/couchbase.py +1442 -0
  528. agno/vectordb/distance.py +7 -0
  529. agno/vectordb/lancedb/__init__.py +6 -0
  530. agno/vectordb/lancedb/lance_db.py +995 -0
  531. agno/vectordb/langchaindb/__init__.py +5 -0
  532. agno/vectordb/langchaindb/langchaindb.py +163 -0
  533. agno/vectordb/lightrag/__init__.py +5 -0
  534. agno/vectordb/lightrag/lightrag.py +388 -0
  535. agno/vectordb/llamaindex/__init__.py +3 -0
  536. agno/vectordb/llamaindex/llamaindexdb.py +166 -0
  537. agno/vectordb/milvus/__init__.py +4 -0
  538. agno/vectordb/milvus/milvus.py +1182 -0
  539. agno/vectordb/mongodb/__init__.py +9 -0
  540. agno/vectordb/mongodb/mongodb.py +1417 -0
  541. agno/vectordb/pgvector/__init__.py +12 -0
  542. agno/vectordb/pgvector/index.py +23 -0
  543. agno/vectordb/pgvector/pgvector.py +1462 -0
  544. agno/vectordb/pineconedb/__init__.py +5 -0
  545. agno/vectordb/pineconedb/pineconedb.py +747 -0
  546. agno/vectordb/qdrant/__init__.py +5 -0
  547. agno/vectordb/qdrant/qdrant.py +1134 -0
  548. agno/vectordb/redis/__init__.py +9 -0
  549. agno/vectordb/redis/redisdb.py +694 -0
  550. agno/vectordb/search.py +7 -0
  551. agno/vectordb/singlestore/__init__.py +10 -0
  552. agno/vectordb/singlestore/index.py +41 -0
  553. agno/vectordb/singlestore/singlestore.py +763 -0
  554. agno/vectordb/surrealdb/__init__.py +3 -0
  555. agno/vectordb/surrealdb/surrealdb.py +699 -0
  556. agno/vectordb/upstashdb/__init__.py +5 -0
  557. agno/vectordb/upstashdb/upstashdb.py +718 -0
  558. agno/vectordb/weaviate/__init__.py +8 -0
  559. agno/vectordb/weaviate/index.py +15 -0
  560. agno/vectordb/weaviate/weaviate.py +1005 -0
  561. agno/workflow/__init__.py +23 -0
  562. agno/workflow/agent.py +299 -0
  563. agno/workflow/condition.py +738 -0
  564. agno/workflow/loop.py +735 -0
  565. agno/workflow/parallel.py +824 -0
  566. agno/workflow/router.py +702 -0
  567. agno/workflow/step.py +1432 -0
  568. agno/workflow/steps.py +592 -0
  569. agno/workflow/types.py +520 -0
  570. agno/workflow/workflow.py +4321 -0
  571. agno-2.2.13.dist-info/METADATA +614 -0
  572. agno-2.2.13.dist-info/RECORD +575 -0
  573. agno-2.2.13.dist-info/WHEEL +5 -0
  574. agno-2.2.13.dist-info/licenses/LICENSE +201 -0
  575. agno-2.2.13.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1160 @@
1
+ import time
2
+ from copy import deepcopy
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.in_memory.utils import (
9
+ apply_sorting,
10
+ calculate_date_metrics,
11
+ deserialize_cultural_knowledge_from_db,
12
+ fetch_all_sessions_data,
13
+ get_dates_to_calculate_metrics_for,
14
+ serialize_cultural_knowledge_for_db,
15
+ )
16
+ from agno.db.schemas.culture import CulturalKnowledge
17
+ from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
18
+ from agno.db.schemas.knowledge import KnowledgeRow
19
+ from agno.db.schemas.memory import UserMemory
20
+ from agno.session import AgentSession, Session, TeamSession, WorkflowSession
21
+ from agno.utils.log import log_debug, log_error, log_info, log_warning
22
+
23
+
24
+ class InMemoryDb(BaseDb):
25
+ def __init__(self):
26
+ """Interface for in-memory storage."""
27
+ super().__init__()
28
+
29
+ # Initialize in-memory storage dictionaries
30
+ self._sessions: List[Dict[str, Any]] = []
31
+ self._memories: List[Dict[str, Any]] = []
32
+ self._metrics: List[Dict[str, Any]] = []
33
+ self._eval_runs: List[Dict[str, Any]] = []
34
+ self._knowledge: List[Dict[str, Any]] = []
35
+ self._cultural_knowledge: List[Dict[str, Any]] = []
36
+
37
+ def table_exists(self, table_name: str) -> bool:
38
+ """In-memory implementation, always returns True."""
39
+ return True
40
+
41
+ # -- Session methods --
42
+
43
+ def delete_session(self, session_id: str) -> bool:
44
+ """Delete a session from in-memory storage.
45
+
46
+ Args:
47
+ session_id (str): The ID of the session to delete.
48
+
49
+ Returns:
50
+ bool: True if the session was deleted, False otherwise.
51
+
52
+ Raises:
53
+ Exception: If an error occurs during deletion.
54
+ """
55
+ try:
56
+ original_count = len(self._sessions)
57
+ self._sessions = [s for s in self._sessions if s.get("session_id") != session_id]
58
+
59
+ if len(self._sessions) < original_count:
60
+ log_debug(f"Successfully deleted session with session_id: {session_id}")
61
+ return True
62
+ else:
63
+ log_debug(f"No session found to delete with session_id: {session_id}")
64
+ return False
65
+
66
+ except Exception as e:
67
+ log_error(f"Error deleting session: {e}")
68
+ raise e
69
+
70
+ def delete_sessions(self, session_ids: List[str]) -> None:
71
+ """Delete multiple sessions from in-memory storage.
72
+
73
+ Args:
74
+ session_ids (List[str]): The IDs of the sessions to delete.
75
+
76
+ Raises:
77
+ Exception: If an error occurs during deletion.
78
+ """
79
+ try:
80
+ self._sessions = [s for s in self._sessions if s.get("session_id") not in session_ids]
81
+ log_debug(f"Successfully deleted sessions with ids: {session_ids}")
82
+
83
+ except Exception as e:
84
+ log_error(f"Error deleting sessions: {e}")
85
+ raise e
86
+
87
+ def get_session(
88
+ self,
89
+ session_id: str,
90
+ session_type: SessionType,
91
+ user_id: Optional[str] = None,
92
+ deserialize: Optional[bool] = True,
93
+ ) -> Optional[Union[AgentSession, TeamSession, WorkflowSession, Dict[str, Any]]]:
94
+ """Read a session from in-memory storage.
95
+
96
+ Args:
97
+ session_id (str): The ID of the session to read.
98
+ session_type (SessionType): The type of the session to read.
99
+ user_id (Optional[str]): The ID of the user to read the session for.
100
+ deserialize (Optional[bool]): Whether to deserialize the session.
101
+
102
+ Returns:
103
+ Union[Session, Dict[str, Any], None]:
104
+ - When deserialize=True: Session object
105
+ - When deserialize=False: Session dictionary
106
+
107
+ Raises:
108
+ Exception: If an error occurs while reading the session.
109
+ """
110
+ try:
111
+ for session_data in self._sessions:
112
+ if session_data.get("session_id") == session_id:
113
+ if user_id is not None and session_data.get("user_id") != user_id:
114
+ continue
115
+
116
+ session_data_copy = deepcopy(session_data)
117
+
118
+ if not deserialize:
119
+ return session_data_copy
120
+
121
+ if session_type == SessionType.AGENT:
122
+ return AgentSession.from_dict(session_data_copy)
123
+ elif session_type == SessionType.TEAM:
124
+ return TeamSession.from_dict(session_data_copy)
125
+ else:
126
+ return WorkflowSession.from_dict(session_data_copy)
127
+
128
+ return None
129
+
130
+ except Exception as e:
131
+ import traceback
132
+
133
+ traceback.print_exc()
134
+ log_error(f"Exception reading session: {e}")
135
+ raise e
136
+
137
+ def get_sessions(
138
+ self,
139
+ session_type: SessionType,
140
+ user_id: Optional[str] = None,
141
+ component_id: Optional[str] = None,
142
+ session_name: Optional[str] = None,
143
+ start_timestamp: Optional[int] = None,
144
+ end_timestamp: Optional[int] = None,
145
+ limit: Optional[int] = None,
146
+ page: Optional[int] = None,
147
+ sort_by: Optional[str] = None,
148
+ sort_order: Optional[str] = None,
149
+ deserialize: Optional[bool] = True,
150
+ ) -> Union[List[Session], Tuple[List[Dict[str, Any]], int]]:
151
+ """Get all sessions from in-memory storage with filtering and pagination.
152
+
153
+ Args:
154
+ session_type (SessionType): The type of the sessions to read.
155
+ user_id (Optional[str]): The ID of the user to read the sessions for.
156
+ component_id (Optional[str]): The ID of the component to read the sessions for.
157
+ session_name (Optional[str]): The name of the session to read.
158
+ start_timestamp (Optional[int]): The start timestamp of the sessions to read.
159
+ end_timestamp (Optional[int]): The end timestamp of the sessions to read.
160
+ limit (Optional[int]): The limit of the sessions to read.
161
+ page (Optional[int]): The page of the sessions to read.
162
+ sort_by (Optional[str]): The field to sort the sessions by.
163
+ sort_order (Optional[str]): The order to sort the sessions by.
164
+ deserialize (Optional[bool]): Whether to deserialize the sessions.
165
+
166
+ Returns:
167
+ Union[List[AgentSession], List[TeamSession], List[WorkflowSession], Tuple[List[Dict[str, Any]], int]]:
168
+ - When deserialize=True: List of sessions
169
+ - When deserialize=False: Tuple with list of sessions and total count
170
+
171
+ Raises:
172
+ Exception: If an error occurs while reading the sessions.
173
+ """
174
+ try:
175
+ # Apply filters
176
+ filtered_sessions = []
177
+ for session_data in self._sessions:
178
+ if user_id is not None and session_data.get("user_id") != user_id:
179
+ continue
180
+ if component_id is not None:
181
+ if session_type == SessionType.AGENT and session_data.get("agent_id") != component_id:
182
+ continue
183
+ elif session_type == SessionType.TEAM and session_data.get("team_id") != component_id:
184
+ continue
185
+ elif session_type == SessionType.WORKFLOW and session_data.get("workflow_id") != component_id:
186
+ continue
187
+ if start_timestamp is not None and session_data.get("created_at", 0) < start_timestamp:
188
+ continue
189
+ if end_timestamp is not None and session_data.get("created_at", 0) > end_timestamp:
190
+ continue
191
+ if session_name is not None:
192
+ stored_name = session_data.get("session_data", {}).get("session_name", "")
193
+ if session_name.lower() not in stored_name.lower():
194
+ continue
195
+ session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
196
+ if session_data.get("session_type") != session_type_value:
197
+ continue
198
+
199
+ filtered_sessions.append(deepcopy(session_data))
200
+
201
+ total_count = len(filtered_sessions)
202
+
203
+ # Apply sorting
204
+ filtered_sessions = apply_sorting(filtered_sessions, sort_by, sort_order)
205
+
206
+ # Apply pagination
207
+ if limit is not None:
208
+ start_idx = 0
209
+ if page is not None:
210
+ start_idx = (page - 1) * limit
211
+ filtered_sessions = filtered_sessions[start_idx : start_idx + limit]
212
+
213
+ if not deserialize:
214
+ return filtered_sessions, total_count
215
+
216
+ if session_type == SessionType.AGENT:
217
+ return [AgentSession.from_dict(session) for session in filtered_sessions] # type: ignore
218
+ elif session_type == SessionType.TEAM:
219
+ return [TeamSession.from_dict(session) for session in filtered_sessions] # type: ignore
220
+ elif session_type == SessionType.WORKFLOW:
221
+ return [WorkflowSession.from_dict(session) for session in filtered_sessions] # type: ignore
222
+ else:
223
+ raise ValueError(f"Invalid session type: {session_type}")
224
+
225
+ except Exception as e:
226
+ log_error(f"Exception reading sessions: {e}")
227
+ raise e
228
+
229
+ def rename_session(
230
+ self, session_id: str, session_type: SessionType, session_name: str, deserialize: Optional[bool] = True
231
+ ) -> Optional[Union[Session, Dict[str, Any]]]:
232
+ try:
233
+ for i, session in enumerate(self._sessions):
234
+ if session.get("session_id") == session_id and session.get("session_type") == session_type.value:
235
+ # Update session name in session_data
236
+ if "session_data" not in session:
237
+ session["session_data"] = {}
238
+ session["session_data"]["session_name"] = session_name
239
+
240
+ self._sessions[i] = session
241
+
242
+ log_debug(f"Renamed session with id '{session_id}' to '{session_name}'")
243
+
244
+ session_copy = deepcopy(session)
245
+ if not deserialize:
246
+ return session_copy
247
+
248
+ if session_type == SessionType.AGENT:
249
+ return AgentSession.from_dict(session_copy)
250
+ elif session_type == SessionType.TEAM:
251
+ return TeamSession.from_dict(session_copy)
252
+ else:
253
+ return WorkflowSession.from_dict(session_copy)
254
+
255
+ return None
256
+
257
+ except Exception as e:
258
+ log_error(f"Exception renaming session: {e}")
259
+ raise e
260
+
261
+ def upsert_session(
262
+ self, session: Session, deserialize: Optional[bool] = True
263
+ ) -> Optional[Union[Session, Dict[str, Any]]]:
264
+ try:
265
+ session_dict = session.to_dict()
266
+
267
+ # Add session_type based on session instance type
268
+ if isinstance(session, AgentSession):
269
+ session_dict["session_type"] = SessionType.AGENT.value
270
+ elif isinstance(session, TeamSession):
271
+ session_dict["session_type"] = SessionType.TEAM.value
272
+ elif isinstance(session, WorkflowSession):
273
+ session_dict["session_type"] = SessionType.WORKFLOW.value
274
+
275
+ # Find existing session to update
276
+ session_updated = False
277
+ for i, existing_session in enumerate(self._sessions):
278
+ if existing_session.get("session_id") == session_dict.get("session_id") and self._matches_session_key(
279
+ existing_session, session
280
+ ):
281
+ session_dict["updated_at"] = int(time.time())
282
+ self._sessions[i] = deepcopy(session_dict)
283
+ session_updated = True
284
+ break
285
+
286
+ if not session_updated:
287
+ session_dict["created_at"] = session_dict.get("created_at", int(time.time()))
288
+ session_dict["updated_at"] = session_dict.get("created_at")
289
+ self._sessions.append(deepcopy(session_dict))
290
+
291
+ session_dict_copy = deepcopy(session_dict)
292
+ if not deserialize:
293
+ return session_dict_copy
294
+
295
+ if session_dict_copy["session_type"] == SessionType.AGENT:
296
+ return AgentSession.from_dict(session_dict_copy)
297
+ elif session_dict_copy["session_type"] == SessionType.TEAM:
298
+ return TeamSession.from_dict(session_dict_copy)
299
+ else:
300
+ return WorkflowSession.from_dict(session_dict_copy)
301
+
302
+ except Exception as e:
303
+ log_error(f"Exception upserting session: {e}")
304
+ raise e
305
+
306
+ def _matches_session_key(self, existing_session: Dict[str, Any], session: Session) -> bool:
307
+ """Check if existing session matches the key for the session type."""
308
+ if isinstance(session, AgentSession):
309
+ return existing_session.get("agent_id") == session.agent_id
310
+ elif isinstance(session, TeamSession):
311
+ return existing_session.get("team_id") == session.team_id
312
+ elif isinstance(session, WorkflowSession):
313
+ return existing_session.get("workflow_id") == session.workflow_id
314
+ return False
315
+
316
+ def upsert_sessions(
317
+ self, sessions: List[Session], deserialize: Optional[bool] = True, preserve_updated_at: bool = False
318
+ ) -> List[Union[Session, Dict[str, Any]]]:
319
+ """
320
+ Bulk upsert multiple sessions for improved performance on large datasets.
321
+
322
+ Args:
323
+ sessions (List[Session]): List of sessions to upsert.
324
+ deserialize (Optional[bool]): Whether to deserialize the sessions. Defaults to True.
325
+
326
+ Returns:
327
+ List[Union[Session, Dict[str, Any]]]: List of upserted sessions.
328
+
329
+ Raises:
330
+ Exception: If an error occurs during bulk upsert.
331
+ """
332
+ if not sessions:
333
+ return []
334
+
335
+ try:
336
+ log_info(f"In-memory database: processing {len(sessions)} sessions with individual upsert operations")
337
+
338
+ results = []
339
+ for session in sessions:
340
+ if session is not None:
341
+ result = self.upsert_session(session, deserialize=deserialize)
342
+ if result is not None:
343
+ results.append(result)
344
+ return results
345
+
346
+ except Exception as e:
347
+ log_error(f"Exception during bulk session upsert: {e}")
348
+ return []
349
+
350
+ # -- Memory methods --
351
+ def delete_user_memory(self, memory_id: str, user_id: Optional[str] = None):
352
+ """Delete a user memory from in-memory storage.
353
+
354
+ Args:
355
+ memory_id (str): The ID of the memory to delete.
356
+ user_id (Optional[str]): The ID of the user. If provided, verifies the memory belongs to this user before deletion.
357
+
358
+ Raises:
359
+ Exception: If an error occurs during deletion.
360
+ """
361
+ try:
362
+ original_count = len(self._memories)
363
+
364
+ # If user_id is provided, verify ownership before deleting
365
+ if user_id is not None:
366
+ self._memories = [
367
+ m for m in self._memories if not (m.get("memory_id") == memory_id and m.get("user_id") == user_id)
368
+ ]
369
+ else:
370
+ self._memories = [m for m in self._memories if m.get("memory_id") != memory_id]
371
+
372
+ if len(self._memories) < original_count:
373
+ log_debug(f"Successfully deleted user memory id: {memory_id}")
374
+ else:
375
+ log_debug(f"No memory found with id: {memory_id}")
376
+
377
+ except Exception as e:
378
+ log_error(f"Error deleting memory: {e}")
379
+ raise e
380
+
381
+ def delete_user_memories(self, memory_ids: List[str], user_id: Optional[str] = None) -> None:
382
+ """Delete multiple user memories from in-memory storage.
383
+
384
+ Args:
385
+ memory_ids (List[str]): The IDs of the memories to delete.
386
+ user_id (Optional[str]): The ID of the user. If provided, only deletes memories belonging to this user.
387
+
388
+ Raises:
389
+ Exception: If an error occurs during deletion.
390
+ """
391
+ try:
392
+ # If user_id is provided, verify ownership before deleting
393
+ if user_id is not None:
394
+ self._memories = [
395
+ m for m in self._memories if not (m.get("memory_id") in memory_ids and m.get("user_id") == user_id)
396
+ ]
397
+ else:
398
+ self._memories = [m for m in self._memories if m.get("memory_id") not in memory_ids]
399
+ log_debug(f"Successfully deleted {len(memory_ids)} user memories")
400
+
401
+ except Exception as e:
402
+ log_error(f"Error deleting memories: {e}")
403
+ raise e
404
+
405
+ def get_all_memory_topics(self) -> List[str]:
406
+ """Get all memory topics from in-memory storage.
407
+
408
+ Returns:
409
+ List[str]: List of unique topics.
410
+
411
+ Raises:
412
+ Exception: If an error occurs while reading topics.
413
+ """
414
+ try:
415
+ topics = set()
416
+ for memory in self._memories:
417
+ memory_topics = memory.get("topics", [])
418
+ if isinstance(memory_topics, list):
419
+ topics.update(memory_topics)
420
+ return list(topics)
421
+
422
+ except Exception as e:
423
+ log_error(f"Exception reading from memory storage: {e}")
424
+ raise e
425
+
426
+ def get_user_memory(
427
+ self, memory_id: str, deserialize: Optional[bool] = True, user_id: Optional[str] = None
428
+ ) -> Optional[Union[UserMemory, Dict[str, Any]]]:
429
+ """Get a user memory from in-memory storage.
430
+
431
+ Args:
432
+ memory_id (str): The ID of the memory to retrieve.
433
+ deserialize (Optional[bool]): Whether to deserialize the memory. Defaults to True.
434
+ user_id (Optional[str]): The ID of the user. If provided, only returns the memory if it belongs to this user.
435
+
436
+ Returns:
437
+ Optional[Union[UserMemory, Dict[str, Any]]]: The memory object or dictionary, or None if not found.
438
+
439
+ Raises:
440
+ Exception: If an error occurs while reading the memory.
441
+ """
442
+ try:
443
+ for memory_data in self._memories:
444
+ if memory_data.get("memory_id") == memory_id:
445
+ # Filter by user_id if provided
446
+ if user_id is not None and memory_data.get("user_id") != user_id:
447
+ continue
448
+
449
+ memory_data_copy = deepcopy(memory_data)
450
+ if not deserialize:
451
+ return memory_data_copy
452
+ return UserMemory.from_dict(memory_data_copy)
453
+
454
+ return None
455
+
456
+ except Exception as e:
457
+ log_error(f"Exception reading from memory storage: {e}")
458
+ raise e
459
+
460
+ def get_user_memories(
461
+ self,
462
+ user_id: Optional[str] = None,
463
+ agent_id: Optional[str] = None,
464
+ team_id: Optional[str] = None,
465
+ topics: Optional[List[str]] = None,
466
+ search_content: Optional[str] = None,
467
+ limit: Optional[int] = None,
468
+ page: Optional[int] = None,
469
+ sort_by: Optional[str] = None,
470
+ sort_order: Optional[str] = None,
471
+ deserialize: Optional[bool] = True,
472
+ ) -> Union[List[UserMemory], Tuple[List[Dict[str, Any]], int]]:
473
+ try:
474
+ # Apply filters
475
+ filtered_memories = []
476
+ for memory_data in self._memories:
477
+ if user_id is not None and memory_data.get("user_id") != user_id:
478
+ continue
479
+ if agent_id is not None and memory_data.get("agent_id") != agent_id:
480
+ continue
481
+ if team_id is not None and memory_data.get("team_id") != team_id:
482
+ continue
483
+ if topics is not None:
484
+ memory_topics = memory_data.get("topics", [])
485
+ if not any(topic in memory_topics for topic in topics):
486
+ continue
487
+ if search_content is not None:
488
+ memory_content = str(memory_data.get("memory", ""))
489
+ if search_content.lower() not in memory_content.lower():
490
+ continue
491
+
492
+ filtered_memories.append(deepcopy(memory_data))
493
+
494
+ total_count = len(filtered_memories)
495
+
496
+ # Apply sorting
497
+ filtered_memories = apply_sorting(filtered_memories, sort_by, sort_order)
498
+
499
+ # Apply pagination
500
+ if limit is not None:
501
+ start_idx = 0
502
+ if page is not None:
503
+ start_idx = (page - 1) * limit
504
+ filtered_memories = filtered_memories[start_idx : start_idx + limit]
505
+
506
+ if not deserialize:
507
+ return filtered_memories, total_count
508
+
509
+ return [UserMemory.from_dict(memory) for memory in filtered_memories]
510
+
511
+ except Exception as e:
512
+ log_error(f"Exception reading from memory storage: {e}")
513
+ raise e
514
+
515
+ def get_user_memory_stats(
516
+ self, limit: Optional[int] = None, page: Optional[int] = None
517
+ ) -> Tuple[List[Dict[str, Any]], int]:
518
+ """Get user memory statistics.
519
+
520
+ Args:
521
+ limit (Optional[int]): Maximum number of stats to return.
522
+ page (Optional[int]): Page number for pagination.
523
+
524
+ Returns:
525
+ Tuple[List[Dict[str, Any]], int]: List of user memory statistics and total count.
526
+
527
+ Raises:
528
+ Exception: If an error occurs while getting stats.
529
+ """
530
+ try:
531
+ user_stats = {}
532
+
533
+ for memory in self._memories:
534
+ memory_user_id = memory.get("user_id")
535
+
536
+ if memory_user_id:
537
+ if memory_user_id not in user_stats:
538
+ user_stats[memory_user_id] = {
539
+ "user_id": memory_user_id,
540
+ "total_memories": 0,
541
+ "last_memory_updated_at": 0,
542
+ }
543
+ user_stats[memory_user_id]["total_memories"] += 1
544
+ updated_at = memory.get("updated_at", 0)
545
+ if updated_at > user_stats[memory_user_id]["last_memory_updated_at"]:
546
+ user_stats[memory_user_id]["last_memory_updated_at"] = updated_at
547
+
548
+ stats_list = list(user_stats.values())
549
+ stats_list.sort(key=lambda x: x["last_memory_updated_at"], reverse=True)
550
+
551
+ total_count = len(stats_list)
552
+
553
+ # Apply pagination
554
+ if limit is not None:
555
+ start_idx = 0
556
+ if page is not None:
557
+ start_idx = (page - 1) * limit
558
+ stats_list = stats_list[start_idx : start_idx + limit]
559
+
560
+ return stats_list, total_count
561
+
562
+ except Exception as e:
563
+ log_error(f"Exception getting user memory stats: {e}")
564
+ raise e
565
+
566
+ def upsert_user_memory(
567
+ self, memory: UserMemory, deserialize: Optional[bool] = True
568
+ ) -> Optional[Union[UserMemory, Dict[str, Any]]]:
569
+ try:
570
+ if memory.memory_id is None:
571
+ memory.memory_id = str(uuid4())
572
+
573
+ memory_dict = memory.to_dict() if hasattr(memory, "to_dict") else memory.__dict__
574
+ memory_dict["updated_at"] = int(time.time())
575
+
576
+ # Find existing memory to update
577
+ memory_updated = False
578
+ for i, existing_memory in enumerate(self._memories):
579
+ if existing_memory.get("memory_id") == memory.memory_id:
580
+ self._memories[i] = memory_dict
581
+ memory_updated = True
582
+ break
583
+
584
+ if not memory_updated:
585
+ self._memories.append(memory_dict)
586
+
587
+ memory_dict_copy = deepcopy(memory_dict)
588
+ if not deserialize:
589
+ return memory_dict_copy
590
+
591
+ return UserMemory.from_dict(memory_dict_copy)
592
+
593
+ except Exception as e:
594
+ log_warning(f"Exception upserting user memory: {e}")
595
+ raise e
596
+
597
+ def upsert_memories(
598
+ self, memories: List[UserMemory], deserialize: Optional[bool] = True, preserve_updated_at: bool = False
599
+ ) -> List[Union[UserMemory, Dict[str, Any]]]:
600
+ """
601
+ Bulk upsert multiple user memories for improved performance on large datasets.
602
+
603
+ Args:
604
+ memories (List[UserMemory]): List of memories to upsert.
605
+ deserialize (Optional[bool]): Whether to deserialize the memories. Defaults to True.
606
+
607
+ Returns:
608
+ List[Union[UserMemory, Dict[str, Any]]]: List of upserted memories.
609
+
610
+ Raises:
611
+ Exception: If an error occurs during bulk upsert.
612
+ """
613
+ if not memories:
614
+ return []
615
+
616
+ try:
617
+ log_info(f"In-memory database: processing {len(memories)} memories with individual upsert operations")
618
+ # For in-memory database, individual upserts are actually efficient
619
+ # since we're just manipulating Python lists and dictionaries
620
+ results = []
621
+ for memory in memories:
622
+ if memory is not None:
623
+ result = self.upsert_user_memory(memory, deserialize=deserialize)
624
+ if result is not None:
625
+ results.append(result)
626
+ return results
627
+
628
+ except Exception as e:
629
+ log_error(f"Exception during bulk memory upsert: {e}")
630
+ return []
631
+
632
+ def clear_memories(self) -> None:
633
+ """Delete all memories.
634
+
635
+ Raises:
636
+ Exception: If an error occurs during deletion.
637
+ """
638
+ try:
639
+ self._memories.clear()
640
+
641
+ except Exception as e:
642
+ log_warning(f"Exception deleting all memories: {e}")
643
+ raise e
644
+
645
+ # -- Metrics methods --
646
+ def calculate_metrics(self) -> Optional[list[dict]]:
647
+ """Calculate metrics for all dates without complete metrics."""
648
+ try:
649
+ starting_date = self._get_metrics_calculation_starting_date(self._metrics)
650
+ if starting_date is None:
651
+ log_info("No session data found. Won't calculate metrics.")
652
+ return None
653
+
654
+ dates_to_process = get_dates_to_calculate_metrics_for(starting_date)
655
+ if not dates_to_process:
656
+ log_info("Metrics already calculated for all relevant dates.")
657
+ return None
658
+
659
+ start_timestamp = int(datetime.combine(dates_to_process[0], datetime.min.time()).timestamp())
660
+ end_timestamp = int(
661
+ datetime.combine(dates_to_process[-1] + timedelta(days=1), datetime.min.time()).timestamp()
662
+ )
663
+
664
+ sessions = self._get_all_sessions_for_metrics_calculation(start_timestamp, end_timestamp)
665
+ all_sessions_data = fetch_all_sessions_data(
666
+ sessions=sessions, dates_to_process=dates_to_process, start_timestamp=start_timestamp
667
+ )
668
+ if not all_sessions_data:
669
+ log_info("No new session data found. Won't calculate metrics.")
670
+ return None
671
+
672
+ results = []
673
+
674
+ for date_to_process in dates_to_process:
675
+ date_key = date_to_process.isoformat()
676
+ sessions_for_date = all_sessions_data.get(date_key, {})
677
+
678
+ # Skip dates with no sessions
679
+ if not any(len(sessions) > 0 for sessions in sessions_for_date.values()):
680
+ continue
681
+
682
+ metrics_record = calculate_date_metrics(date_to_process, sessions_for_date)
683
+
684
+ # Upsert metrics record
685
+ existing_record_idx = None
686
+ for i, existing_metric in enumerate(self._metrics):
687
+ if (
688
+ existing_metric.get("date") == str(date_to_process)
689
+ and existing_metric.get("aggregation_period") == "daily"
690
+ ):
691
+ existing_record_idx = i
692
+ break
693
+
694
+ if existing_record_idx is not None:
695
+ self._metrics[existing_record_idx] = metrics_record
696
+ else:
697
+ self._metrics.append(metrics_record)
698
+
699
+ results.append(metrics_record)
700
+
701
+ log_debug("Updated metrics calculations")
702
+
703
+ return results
704
+
705
+ except Exception as e:
706
+ log_warning(f"Exception refreshing metrics: {e}")
707
+ raise e
708
+
709
+ def _get_metrics_calculation_starting_date(self, metrics: List[Dict[str, Any]]) -> Optional[date]:
710
+ """Get the first date for which metrics calculation is needed."""
711
+ if metrics:
712
+ # Sort by date in descending order
713
+ sorted_metrics = sorted(metrics, key=lambda x: x.get("date", ""), reverse=True)
714
+ latest_metric = sorted_metrics[0]
715
+
716
+ if latest_metric.get("completed", False):
717
+ latest_date = datetime.strptime(latest_metric["date"], "%Y-%m-%d").date()
718
+ return latest_date + timedelta(days=1)
719
+ else:
720
+ return datetime.strptime(latest_metric["date"], "%Y-%m-%d").date()
721
+
722
+ # No metrics records. Return the date of the first recorded session.
723
+ if self._sessions:
724
+ # Sort by created_at
725
+ sorted_sessions = sorted(self._sessions, key=lambda x: x.get("created_at", 0))
726
+ first_session_date = sorted_sessions[0]["created_at"]
727
+ return datetime.fromtimestamp(first_session_date, tz=timezone.utc).date()
728
+
729
+ return None
730
+
731
+ def _get_all_sessions_for_metrics_calculation(
732
+ self, start_timestamp: Optional[int] = None, end_timestamp: Optional[int] = None
733
+ ) -> List[Dict[str, Any]]:
734
+ """Get all sessions for metrics calculation."""
735
+ try:
736
+ filtered_sessions = []
737
+ for session in self._sessions:
738
+ created_at = session.get("created_at", 0)
739
+ if start_timestamp is not None and created_at < start_timestamp:
740
+ continue
741
+ if end_timestamp is not None and created_at >= end_timestamp:
742
+ continue
743
+
744
+ # Only include necessary fields for metrics
745
+ filtered_session = {
746
+ "user_id": session.get("user_id"),
747
+ "session_data": deepcopy(session.get("session_data")),
748
+ "runs": deepcopy(session.get("runs")),
749
+ "created_at": session.get("created_at"),
750
+ "session_type": session.get("session_type"),
751
+ }
752
+ filtered_sessions.append(filtered_session)
753
+
754
+ return filtered_sessions
755
+
756
+ except Exception as e:
757
+ log_error(f"Exception reading sessions for metrics: {e}")
758
+ raise e
759
+
760
+ def get_metrics(
761
+ self,
762
+ starting_date: Optional[date] = None,
763
+ ending_date: Optional[date] = None,
764
+ ) -> Tuple[List[dict], Optional[int]]:
765
+ """Get all metrics matching the given date range."""
766
+ try:
767
+ filtered_metrics = []
768
+ latest_updated_at = None
769
+
770
+ for metric in self._metrics:
771
+ metric_date = datetime.strptime(metric.get("date", ""), "%Y-%m-%d").date()
772
+
773
+ if starting_date and metric_date < starting_date:
774
+ continue
775
+ if ending_date and metric_date > ending_date:
776
+ continue
777
+
778
+ filtered_metrics.append(deepcopy(metric))
779
+
780
+ updated_at = metric.get("updated_at")
781
+ if updated_at and (latest_updated_at is None or updated_at > latest_updated_at):
782
+ latest_updated_at = updated_at
783
+
784
+ return filtered_metrics, latest_updated_at
785
+
786
+ except Exception as e:
787
+ log_error(f"Exception getting metrics: {e}")
788
+ raise e
789
+
790
+ # -- Knowledge methods --
791
+
792
+ def delete_knowledge_content(self, id: str):
793
+ """Delete a knowledge row from in-memory storage.
794
+
795
+ Args:
796
+ id (str): The ID of the knowledge row to delete.
797
+
798
+ Raises:
799
+ Exception: If an error occurs during deletion.
800
+ """
801
+ try:
802
+ self._knowledge = [item for item in self._knowledge if item.get("id") != id]
803
+
804
+ except Exception as e:
805
+ log_error(f"Error deleting knowledge content: {e}")
806
+ raise e
807
+
808
+ def get_knowledge_content(self, id: str) -> Optional[KnowledgeRow]:
809
+ """Get a knowledge row from in-memory storage.
810
+
811
+ Args:
812
+ id (str): The ID of the knowledge row to get.
813
+
814
+ Returns:
815
+ Optional[KnowledgeRow]: The knowledge row, or None if it doesn't exist.
816
+
817
+ Raises:
818
+ Exception: If an error occurs during retrieval.
819
+ """
820
+ try:
821
+ for item in self._knowledge:
822
+ if item.get("id") == id:
823
+ return KnowledgeRow.model_validate(item)
824
+
825
+ return None
826
+
827
+ except Exception as e:
828
+ log_error(f"Error getting knowledge content: {e}")
829
+ raise e
830
+
831
+ def get_knowledge_contents(
832
+ self,
833
+ limit: Optional[int] = None,
834
+ page: Optional[int] = None,
835
+ sort_by: Optional[str] = None,
836
+ sort_order: Optional[str] = None,
837
+ ) -> Tuple[List[KnowledgeRow], int]:
838
+ """Get all knowledge contents from in-memory storage.
839
+
840
+ Args:
841
+ limit (Optional[int]): The maximum number of knowledge contents to return.
842
+ page (Optional[int]): The page number.
843
+ sort_by (Optional[str]): The column to sort by.
844
+ sort_order (Optional[str]): The order to sort by.
845
+
846
+ Returns:
847
+ Tuple[List[KnowledgeRow], int]: The knowledge contents and total count.
848
+
849
+ Raises:
850
+ Exception: If an error occurs during retrieval.
851
+ """
852
+ try:
853
+ knowledge_items = [deepcopy(item) for item in self._knowledge]
854
+
855
+ total_count = len(knowledge_items)
856
+
857
+ # Apply sorting
858
+ knowledge_items = apply_sorting(knowledge_items, sort_by, sort_order)
859
+
860
+ # Apply pagination
861
+ if limit is not None:
862
+ start_idx = 0
863
+ if page is not None:
864
+ start_idx = (page - 1) * limit
865
+ knowledge_items = knowledge_items[start_idx : start_idx + limit]
866
+
867
+ return [KnowledgeRow.model_validate(item) for item in knowledge_items], total_count
868
+
869
+ except Exception as e:
870
+ log_error(f"Error getting knowledge contents: {e}")
871
+ raise e
872
+
873
+ def upsert_knowledge_content(self, knowledge_row: KnowledgeRow):
874
+ """Upsert knowledge content.
875
+
876
+ Args:
877
+ knowledge_row (KnowledgeRow): The knowledge row to upsert.
878
+
879
+ Returns:
880
+ Optional[KnowledgeRow]: The upserted knowledge row, or None if the operation fails.
881
+
882
+ Raises:
883
+ Exception: If an error occurs during upsert.
884
+ """
885
+ try:
886
+ knowledge_dict = knowledge_row.model_dump()
887
+
888
+ # Find existing item to update
889
+ item_updated = False
890
+ for i, existing_item in enumerate(self._knowledge):
891
+ if existing_item.get("id") == knowledge_row.id:
892
+ self._knowledge[i] = knowledge_dict
893
+ item_updated = True
894
+ break
895
+
896
+ if not item_updated:
897
+ self._knowledge.append(knowledge_dict)
898
+
899
+ return knowledge_row
900
+
901
+ except Exception as e:
902
+ log_error(f"Error upserting knowledge row: {e}")
903
+ raise e
904
+
905
+ # -- Eval methods --
906
+
907
+ def create_eval_run(self, eval_run: EvalRunRecord) -> Optional[EvalRunRecord]:
908
+ """Create an EvalRunRecord"""
909
+ try:
910
+ current_time = int(time.time())
911
+ eval_dict = eval_run.model_dump()
912
+ eval_dict["created_at"] = current_time
913
+ eval_dict["updated_at"] = current_time
914
+
915
+ self._eval_runs.append(eval_dict)
916
+
917
+ log_debug(f"Created eval run with id '{eval_run.run_id}'")
918
+
919
+ return eval_run
920
+
921
+ except Exception as e:
922
+ log_error(f"Error creating eval run: {e}")
923
+ raise e
924
+
925
+ def delete_eval_runs(self, eval_run_ids: List[str]) -> None:
926
+ """Delete multiple eval runs from in-memory storage."""
927
+ try:
928
+ original_count = len(self._eval_runs)
929
+ self._eval_runs = [run for run in self._eval_runs if run.get("run_id") not in eval_run_ids]
930
+
931
+ deleted_count = original_count - len(self._eval_runs)
932
+ if deleted_count > 0:
933
+ log_debug(f"Deleted {deleted_count} eval runs")
934
+ else:
935
+ log_debug(f"No eval runs found with IDs: {eval_run_ids}")
936
+
937
+ except Exception as e:
938
+ log_error(f"Error deleting eval runs {eval_run_ids}: {e}")
939
+ raise e
940
+
941
+ def get_eval_run(
942
+ self, eval_run_id: str, deserialize: Optional[bool] = True
943
+ ) -> Optional[Union[EvalRunRecord, Dict[str, Any]]]:
944
+ """Get an eval run from in-memory storage."""
945
+ try:
946
+ for run_data in self._eval_runs:
947
+ if run_data.get("run_id") == eval_run_id:
948
+ run_data_copy = deepcopy(run_data)
949
+ if not deserialize:
950
+ return run_data_copy
951
+ return EvalRunRecord.model_validate(run_data_copy)
952
+
953
+ return None
954
+
955
+ except Exception as e:
956
+ log_error(f"Exception getting eval run {eval_run_id}: {e}")
957
+ raise e
958
+
959
+ def get_eval_runs(
960
+ self,
961
+ limit: Optional[int] = None,
962
+ page: Optional[int] = None,
963
+ sort_by: Optional[str] = None,
964
+ sort_order: Optional[str] = None,
965
+ agent_id: Optional[str] = None,
966
+ team_id: Optional[str] = None,
967
+ workflow_id: Optional[str] = None,
968
+ model_id: Optional[str] = None,
969
+ filter_type: Optional[EvalFilterType] = None,
970
+ eval_type: Optional[List[EvalType]] = None,
971
+ deserialize: Optional[bool] = True,
972
+ ) -> Union[List[EvalRunRecord], Tuple[List[Dict[str, Any]], int]]:
973
+ """Get all eval runs from in-memory storage with filtering and pagination."""
974
+ try:
975
+ # Apply filters
976
+ filtered_runs = []
977
+ for run_data in self._eval_runs:
978
+ if agent_id is not None and run_data.get("agent_id") != agent_id:
979
+ continue
980
+ if team_id is not None and run_data.get("team_id") != team_id:
981
+ continue
982
+ if workflow_id is not None and run_data.get("workflow_id") != workflow_id:
983
+ continue
984
+ if model_id is not None and run_data.get("model_id") != model_id:
985
+ continue
986
+ if eval_type is not None and len(eval_type) > 0:
987
+ if run_data.get("eval_type") not in eval_type:
988
+ continue
989
+ if filter_type is not None:
990
+ if filter_type == EvalFilterType.AGENT and run_data.get("agent_id") is None:
991
+ continue
992
+ elif filter_type == EvalFilterType.TEAM and run_data.get("team_id") is None:
993
+ continue
994
+ elif filter_type == EvalFilterType.WORKFLOW and run_data.get("workflow_id") is None:
995
+ continue
996
+
997
+ filtered_runs.append(deepcopy(run_data))
998
+
999
+ total_count = len(filtered_runs)
1000
+
1001
+ # Apply sorting (default by created_at desc)
1002
+ if sort_by is None:
1003
+ filtered_runs.sort(key=lambda x: x.get("created_at", 0), reverse=True)
1004
+ else:
1005
+ filtered_runs = apply_sorting(filtered_runs, sort_by, sort_order)
1006
+
1007
+ # Apply pagination
1008
+ if limit is not None:
1009
+ start_idx = 0
1010
+ if page is not None:
1011
+ start_idx = (page - 1) * limit
1012
+ filtered_runs = filtered_runs[start_idx : start_idx + limit]
1013
+
1014
+ if not deserialize:
1015
+ return filtered_runs, total_count
1016
+
1017
+ return [EvalRunRecord.model_validate(run) for run in filtered_runs]
1018
+
1019
+ except Exception as e:
1020
+ log_error(f"Exception getting eval runs: {e}")
1021
+ raise e
1022
+
1023
+ def rename_eval_run(
1024
+ self, eval_run_id: str, name: str, deserialize: Optional[bool] = True
1025
+ ) -> Optional[Union[EvalRunRecord, Dict[str, Any]]]:
1026
+ """Rename an eval run."""
1027
+ try:
1028
+ for i, run_data in enumerate(self._eval_runs):
1029
+ if run_data.get("run_id") == eval_run_id:
1030
+ run_data["name"] = name
1031
+ run_data["updated_at"] = int(time.time())
1032
+ self._eval_runs[i] = run_data
1033
+
1034
+ log_debug(f"Renamed eval run with id '{eval_run_id}' to '{name}'")
1035
+
1036
+ run_data_copy = deepcopy(run_data)
1037
+ if not deserialize:
1038
+ return run_data_copy
1039
+
1040
+ return EvalRunRecord.model_validate(run_data_copy)
1041
+
1042
+ return None
1043
+
1044
+ except Exception as e:
1045
+ log_error(f"Error renaming eval run {eval_run_id}: {e}")
1046
+ raise e
1047
+
1048
+ # -- Culture methods --
1049
+
1050
+ def clear_cultural_knowledge(self) -> None:
1051
+ """Delete all cultural knowledge from in-memory storage."""
1052
+ try:
1053
+ self._cultural_knowledge = []
1054
+ except Exception as e:
1055
+ log_error(f"Error clearing cultural knowledge: {e}")
1056
+ raise e
1057
+
1058
+ def delete_cultural_knowledge(self, id: str) -> None:
1059
+ """Delete a cultural knowledge entry from in-memory storage."""
1060
+ try:
1061
+ self._cultural_knowledge = [ck for ck in self._cultural_knowledge if ck.get("id") != id]
1062
+ except Exception as e:
1063
+ log_error(f"Error deleting cultural knowledge: {e}")
1064
+ raise e
1065
+
1066
+ def get_cultural_knowledge(
1067
+ self, id: str, deserialize: Optional[bool] = True
1068
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
1069
+ """Get a cultural knowledge entry from in-memory storage."""
1070
+ try:
1071
+ for ck_data in self._cultural_knowledge:
1072
+ if ck_data.get("id") == id:
1073
+ ck_data_copy = deepcopy(ck_data)
1074
+ if not deserialize:
1075
+ return ck_data_copy
1076
+ return deserialize_cultural_knowledge_from_db(ck_data_copy)
1077
+ return None
1078
+ except Exception as e:
1079
+ log_error(f"Error getting cultural knowledge: {e}")
1080
+ raise e
1081
+
1082
+ def get_all_cultural_knowledge(
1083
+ self,
1084
+ name: Optional[str] = None,
1085
+ agent_id: Optional[str] = None,
1086
+ team_id: Optional[str] = None,
1087
+ limit: Optional[int] = None,
1088
+ page: Optional[int] = None,
1089
+ sort_by: Optional[str] = None,
1090
+ sort_order: Optional[str] = None,
1091
+ deserialize: Optional[bool] = True,
1092
+ ) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
1093
+ """Get all cultural knowledge from in-memory storage."""
1094
+ try:
1095
+ filtered_ck = []
1096
+ for ck_data in self._cultural_knowledge:
1097
+ if name and ck_data.get("name") != name:
1098
+ continue
1099
+ if agent_id and ck_data.get("agent_id") != agent_id:
1100
+ continue
1101
+ if team_id and ck_data.get("team_id") != team_id:
1102
+ continue
1103
+ filtered_ck.append(ck_data)
1104
+
1105
+ # Apply sorting
1106
+ if sort_by:
1107
+ filtered_ck = apply_sorting(filtered_ck, sort_by, sort_order)
1108
+
1109
+ total_count = len(filtered_ck)
1110
+
1111
+ # Apply pagination
1112
+ if limit and page:
1113
+ start = (page - 1) * limit
1114
+ filtered_ck = filtered_ck[start : start + limit]
1115
+ elif limit:
1116
+ filtered_ck = filtered_ck[:limit]
1117
+
1118
+ if not deserialize:
1119
+ return [deepcopy(ck) for ck in filtered_ck], total_count
1120
+
1121
+ return [deserialize_cultural_knowledge_from_db(deepcopy(ck)) for ck in filtered_ck]
1122
+ except Exception as e:
1123
+ log_error(f"Error getting all cultural knowledge: {e}")
1124
+ raise e
1125
+
1126
+ def upsert_cultural_knowledge(
1127
+ self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
1128
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
1129
+ """Upsert a cultural knowledge entry into in-memory storage."""
1130
+ try:
1131
+ if not cultural_knowledge.id:
1132
+ cultural_knowledge.id = str(uuid4())
1133
+
1134
+ # Serialize content, categories, and notes into a dict for DB storage
1135
+ content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
1136
+
1137
+ # Create the item dict with serialized content
1138
+ ck_dict = {
1139
+ "id": cultural_knowledge.id,
1140
+ "name": cultural_knowledge.name,
1141
+ "summary": cultural_knowledge.summary,
1142
+ "content": content_dict if content_dict else None,
1143
+ "metadata": cultural_knowledge.metadata,
1144
+ "input": cultural_knowledge.input,
1145
+ "created_at": cultural_knowledge.created_at,
1146
+ "updated_at": int(time.time()),
1147
+ "agent_id": cultural_knowledge.agent_id,
1148
+ "team_id": cultural_knowledge.team_id,
1149
+ }
1150
+
1151
+ # Remove existing entry with same id
1152
+ self._cultural_knowledge = [ck for ck in self._cultural_knowledge if ck.get("id") != cultural_knowledge.id]
1153
+
1154
+ # Add new entry
1155
+ self._cultural_knowledge.append(ck_dict)
1156
+
1157
+ return self.get_cultural_knowledge(cultural_knowledge.id, deserialize=deserialize)
1158
+ except Exception as e:
1159
+ log_error(f"Error upserting cultural knowledge: {e}")
1160
+ raise e