agno 2.0.1__py3-none-any.whl → 2.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. agno/agent/agent.py +6015 -2823
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +594 -186
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +2 -8
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +72 -0
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +999 -519
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +103 -31
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +139 -0
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +59 -5
  142. agno/models/openai/chat.py +69 -29
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +77 -1
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -178
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +248 -94
  205. agno/run/base.py +44 -5
  206. agno/run/team.py +238 -97
  207. agno/run/workflow.py +144 -33
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1610
  213. agno/tools/dalle.py +2 -4
  214. agno/tools/decorator.py +4 -2
  215. agno/tools/duckduckgo.py +15 -11
  216. agno/tools/e2b.py +14 -7
  217. agno/tools/eleven_labs.py +23 -25
  218. agno/tools/exa.py +21 -16
  219. agno/tools/file.py +153 -23
  220. agno/tools/file_generation.py +350 -0
  221. agno/tools/firecrawl.py +4 -4
  222. agno/tools/function.py +250 -30
  223. agno/tools/gmail.py +238 -14
  224. agno/tools/google_drive.py +270 -0
  225. agno/tools/googlecalendar.py +36 -8
  226. agno/tools/googlesheets.py +20 -5
  227. agno/tools/jira.py +20 -0
  228. agno/tools/knowledge.py +3 -3
  229. agno/tools/mcp/__init__.py +10 -0
  230. agno/tools/mcp/mcp.py +331 -0
  231. agno/tools/mcp/multi_mcp.py +347 -0
  232. agno/tools/mcp/params.py +24 -0
  233. agno/tools/mcp_toolbox.py +284 -0
  234. agno/tools/mem0.py +11 -17
  235. agno/tools/memori.py +1 -53
  236. agno/tools/memory.py +419 -0
  237. agno/tools/models/nebius.py +5 -5
  238. agno/tools/models_labs.py +20 -10
  239. agno/tools/notion.py +204 -0
  240. agno/tools/parallel.py +314 -0
  241. agno/tools/scrapegraph.py +58 -31
  242. agno/tools/searxng.py +2 -2
  243. agno/tools/serper.py +2 -2
  244. agno/tools/slack.py +18 -3
  245. agno/tools/spider.py +2 -2
  246. agno/tools/tavily.py +146 -0
  247. agno/tools/whatsapp.py +1 -1
  248. agno/tools/workflow.py +278 -0
  249. agno/tools/yfinance.py +12 -11
  250. agno/utils/agent.py +820 -0
  251. agno/utils/audio.py +27 -0
  252. agno/utils/common.py +90 -1
  253. agno/utils/events.py +217 -2
  254. agno/utils/gemini.py +180 -22
  255. agno/utils/hooks.py +57 -0
  256. agno/utils/http.py +111 -0
  257. agno/utils/knowledge.py +12 -5
  258. agno/utils/log.py +1 -0
  259. agno/utils/mcp.py +92 -2
  260. agno/utils/media.py +188 -10
  261. agno/utils/merge_dict.py +22 -1
  262. agno/utils/message.py +60 -0
  263. agno/utils/models/claude.py +40 -11
  264. agno/utils/print_response/agent.py +105 -21
  265. agno/utils/print_response/team.py +103 -38
  266. agno/utils/print_response/workflow.py +251 -34
  267. agno/utils/reasoning.py +22 -1
  268. agno/utils/serialize.py +32 -0
  269. agno/utils/streamlit.py +16 -10
  270. agno/utils/string.py +41 -0
  271. agno/utils/team.py +98 -9
  272. agno/utils/tools.py +1 -1
  273. agno/vectordb/base.py +23 -4
  274. agno/vectordb/cassandra/cassandra.py +65 -9
  275. agno/vectordb/chroma/chromadb.py +182 -38
  276. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  277. agno/vectordb/couchbase/couchbase.py +105 -10
  278. agno/vectordb/lancedb/lance_db.py +124 -133
  279. agno/vectordb/langchaindb/langchaindb.py +25 -7
  280. agno/vectordb/lightrag/lightrag.py +17 -3
  281. agno/vectordb/llamaindex/__init__.py +3 -0
  282. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  283. agno/vectordb/milvus/milvus.py +126 -9
  284. agno/vectordb/mongodb/__init__.py +7 -1
  285. agno/vectordb/mongodb/mongodb.py +112 -7
  286. agno/vectordb/pgvector/pgvector.py +142 -21
  287. agno/vectordb/pineconedb/pineconedb.py +80 -8
  288. agno/vectordb/qdrant/qdrant.py +125 -39
  289. agno/vectordb/redis/__init__.py +9 -0
  290. agno/vectordb/redis/redisdb.py +694 -0
  291. agno/vectordb/singlestore/singlestore.py +111 -25
  292. agno/vectordb/surrealdb/surrealdb.py +31 -5
  293. agno/vectordb/upstashdb/upstashdb.py +76 -8
  294. agno/vectordb/weaviate/weaviate.py +86 -15
  295. agno/workflow/__init__.py +2 -0
  296. agno/workflow/agent.py +299 -0
  297. agno/workflow/condition.py +112 -18
  298. agno/workflow/loop.py +69 -10
  299. agno/workflow/parallel.py +266 -118
  300. agno/workflow/router.py +110 -17
  301. agno/workflow/step.py +638 -129
  302. agno/workflow/steps.py +65 -6
  303. agno/workflow/types.py +61 -23
  304. agno/workflow/workflow.py +2085 -272
  305. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
  306. agno-2.3.0.dist-info/RECORD +577 -0
  307. agno/knowledge/reader/url_reader.py +0 -128
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -610
  310. agno/utils/models/aws_claude.py +0 -170
  311. agno-2.0.1.dist-info/RECORD +0 -515
  312. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  313. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/db/redis/redis.py CHANGED
@@ -10,22 +10,26 @@ from agno.db.redis.utils import (
10
10
  apply_sorting,
11
11
  calculate_date_metrics,
12
12
  create_index_entries,
13
+ deserialize_cultural_knowledge_from_db,
13
14
  deserialize_data,
14
15
  fetch_all_sessions_data,
15
16
  generate_redis_key,
16
17
  get_all_keys_for_table,
17
18
  get_dates_to_calculate_metrics_for,
18
19
  remove_index_entries,
20
+ serialize_cultural_knowledge_for_db,
19
21
  serialize_data,
20
22
  )
23
+ from agno.db.schemas.culture import CulturalKnowledge
21
24
  from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
22
25
  from agno.db.schemas.knowledge import KnowledgeRow
23
26
  from agno.db.schemas.memory import UserMemory
24
27
  from agno.session import AgentSession, Session, TeamSession, WorkflowSession
25
28
  from agno.utils.log import log_debug, log_error, log_info
29
+ from agno.utils.string import generate_id
26
30
 
27
31
  try:
28
- from redis import Redis
32
+ from redis import Redis, RedisCluster
29
33
  except ImportError:
30
34
  raise ImportError("`redis` not installed. Please install it using `pip install redis`")
31
35
 
@@ -34,7 +38,7 @@ class RedisDb(BaseDb):
34
38
  def __init__(
35
39
  self,
36
40
  id: Optional[str] = None,
37
- redis_client: Optional[Redis] = None,
41
+ redis_client: Optional[Union[Redis, RedisCluster]] = None,
38
42
  db_url: Optional[str] = None,
39
43
  db_prefix: str = "agno",
40
44
  expire: Optional[int] = None,
@@ -43,6 +47,7 @@ class RedisDb(BaseDb):
43
47
  metrics_table: Optional[str] = None,
44
48
  eval_table: Optional[str] = None,
45
49
  knowledge_table: Optional[str] = None,
50
+ culture_table: Optional[str] = None,
46
51
  ):
47
52
  """
48
53
  Interface for interacting with a Redis database.
@@ -52,6 +57,8 @@ class RedisDb(BaseDb):
52
57
  2. Use the db_url
53
58
  3. Raise an error if neither is provided
54
59
 
60
+ db_url only supports single-node Redis connections, if you need Redis Cluster support, provide a redis_client.
61
+
55
62
  Args:
56
63
  id (Optional[str]): The ID of the database.
57
64
  redis_client (Optional[Redis]): Redis client instance to use. If not provided a new client will be created.
@@ -63,10 +70,16 @@ class RedisDb(BaseDb):
63
70
  metrics_table (Optional[str]): Name of the table to store metrics
64
71
  eval_table (Optional[str]): Name of the table to store evaluation runs
65
72
  knowledge_table (Optional[str]): Name of the table to store knowledge documents
73
+ culture_table (Optional[str]): Name of the table to store cultural knowledge
66
74
 
67
75
  Raises:
68
76
  ValueError: If neither redis_client nor db_url is provided.
69
77
  """
78
+ if id is None:
79
+ base_seed = db_url or str(redis_client)
80
+ seed = f"{base_seed}#{db_prefix}"
81
+ id = generate_id(seed)
82
+
70
83
  super().__init__(
71
84
  id=id,
72
85
  session_table=session_table,
@@ -74,6 +87,7 @@ class RedisDb(BaseDb):
74
87
  metrics_table=metrics_table,
75
88
  eval_table=eval_table,
76
89
  knowledge_table=knowledge_table,
90
+ culture_table=culture_table,
77
91
  )
78
92
 
79
93
  self.db_prefix = db_prefix
@@ -88,6 +102,10 @@ class RedisDb(BaseDb):
88
102
 
89
103
  # -- DB methods --
90
104
 
105
+ def table_exists(self, table_name: str) -> bool:
106
+ """Redis implementation, always returns True."""
107
+ return True
108
+
91
109
  def _get_table_name(self, table_type: str) -> str:
92
110
  """Get the active table name for the given table type."""
93
111
  if table_type == "sessions":
@@ -105,6 +123,9 @@ class RedisDb(BaseDb):
105
123
  elif table_type == "knowledge":
106
124
  return self.knowledge_table_name
107
125
 
126
+ elif table_type == "culture":
127
+ return self.culture_table_name
128
+
108
129
  else:
109
130
  raise ValueError(f"Unknown table type: {table_type}")
110
131
 
@@ -233,6 +254,14 @@ class RedisDb(BaseDb):
233
254
  log_error(f"Error getting all records for {table_type}: {e}")
234
255
  return []
235
256
 
257
+ def get_latest_schema_version(self):
258
+ """Get the latest version of the database schema."""
259
+ pass
260
+
261
+ def upsert_schema_version(self, version: str) -> None:
262
+ """Upsert the schema version into the database."""
263
+ pass
264
+
236
265
  # -- Session methods --
237
266
 
238
267
  def delete_session(self, session_id: str) -> bool:
@@ -258,7 +287,7 @@ class RedisDb(BaseDb):
258
287
 
259
288
  except Exception as e:
260
289
  log_error(f"Error deleting session: {e}")
261
- return False
290
+ raise e
262
291
 
263
292
  def delete_sessions(self, session_ids: List[str]) -> None:
264
293
  """Delete multiple sessions from Redis.
@@ -282,6 +311,7 @@ class RedisDb(BaseDb):
282
311
 
283
312
  except Exception as e:
284
313
  log_error(f"Error deleting sessions: {e}")
314
+ raise e
285
315
 
286
316
  def get_session(
287
317
  self,
@@ -294,8 +324,8 @@ class RedisDb(BaseDb):
294
324
 
295
325
  Args:
296
326
  session_id (str): The ID of the session to get.
327
+ session_type (SessionType): The type of session to get.
297
328
  user_id (Optional[str]): The ID of the user to filter by.
298
- session_type (Optional[SessionType]): The type of session to filter by.
299
329
 
300
330
  Returns:
301
331
  Optional[Union[AgentSession, TeamSession, WorkflowSession]]: The session if found, None otherwise.
@@ -311,8 +341,6 @@ class RedisDb(BaseDb):
311
341
  # Apply filters
312
342
  if user_id is not None and session.get("user_id") != user_id:
313
343
  return None
314
- if session_type is not None and session.get("session_type") != session_type:
315
- return None
316
344
 
317
345
  if not deserialize:
318
346
  return session
@@ -328,7 +356,7 @@ class RedisDb(BaseDb):
328
356
 
329
357
  except Exception as e:
330
358
  log_error(f"Exception reading session: {e}")
331
- return None
359
+ raise e
332
360
 
333
361
  # TODO: optimizable
334
362
  def get_sessions(
@@ -409,7 +437,7 @@ class RedisDb(BaseDb):
409
437
 
410
438
  except Exception as e:
411
439
  log_error(f"Exception reading sessions: {e}")
412
- return [], 0
440
+ raise e
413
441
 
414
442
  def rename_session(
415
443
  self, session_id: str, session_type: SessionType, session_name: str, deserialize: Optional[bool] = True
@@ -459,7 +487,7 @@ class RedisDb(BaseDb):
459
487
 
460
488
  except Exception as e:
461
489
  log_error(f"Error renaming session: {e}")
462
- return None
490
+ raise e
463
491
 
464
492
  def upsert_session(
465
493
  self, session: Session, deserialize: Optional[bool] = True
@@ -579,15 +607,53 @@ class RedisDb(BaseDb):
579
607
 
580
608
  except Exception as e:
581
609
  log_error(f"Error upserting session: {e}")
582
- return None
610
+ raise e
611
+
612
+ def upsert_sessions(
613
+ self, sessions: List[Session], deserialize: Optional[bool] = True, preserve_updated_at: bool = False
614
+ ) -> List[Union[Session, Dict[str, Any]]]:
615
+ """
616
+ Bulk upsert multiple sessions for improved performance on large datasets.
617
+
618
+ Args:
619
+ sessions (List[Session]): List of sessions to upsert.
620
+ deserialize (Optional[bool]): Whether to deserialize the sessions. Defaults to True.
621
+
622
+ Returns:
623
+ List[Union[Session, Dict[str, Any]]]: List of upserted sessions.
624
+
625
+ Raises:
626
+ Exception: If an error occurs during bulk upsert.
627
+ """
628
+ if not sessions:
629
+ return []
630
+
631
+ try:
632
+ log_info(
633
+ f"RedisDb doesn't support efficient bulk operations, falling back to individual upserts for {len(sessions)} sessions"
634
+ )
635
+
636
+ # Fall back to individual upserts
637
+ results = []
638
+ for session in sessions:
639
+ if session is not None:
640
+ result = self.upsert_session(session, deserialize=deserialize)
641
+ if result is not None:
642
+ results.append(result)
643
+ return results
644
+
645
+ except Exception as e:
646
+ log_error(f"Exception during bulk session upsert: {e}")
647
+ return []
583
648
 
584
649
  # -- Memory methods --
585
650
 
586
- def delete_user_memory(self, memory_id: str):
651
+ def delete_user_memory(self, memory_id: str, user_id: Optional[str] = None):
587
652
  """Delete a user memory from Redis.
588
653
 
589
654
  Args:
590
655
  memory_id (str): The ID of the memory to delete.
656
+ user_id (Optional[str]): The ID of the user. If provided, verifies the memory belongs to this user before deleting.
591
657
 
592
658
  Returns:
593
659
  bool: True if the memory was deleted, False otherwise.
@@ -596,6 +662,16 @@ class RedisDb(BaseDb):
596
662
  Exception: If any error occurs while deleting the memory.
597
663
  """
598
664
  try:
665
+ # If user_id is provided, verify ownership before deleting
666
+ if user_id is not None:
667
+ memory = self._get_record("memories", memory_id)
668
+ if memory is None:
669
+ log_debug(f"No user memory found with id: {memory_id}")
670
+ return
671
+ if memory.get("user_id") != user_id:
672
+ log_debug(f"Memory {memory_id} does not belong to user {user_id}")
673
+ return
674
+
599
675
  if self._delete_record(
600
676
  "memories", memory_id, index_fields=["user_id", "agent_id", "team_id", "workflow_id"]
601
677
  ):
@@ -605,16 +681,27 @@ class RedisDb(BaseDb):
605
681
 
606
682
  except Exception as e:
607
683
  log_error(f"Error deleting user memory: {e}")
684
+ raise e
608
685
 
609
- def delete_user_memories(self, memory_ids: List[str]) -> None:
686
+ def delete_user_memories(self, memory_ids: List[str], user_id: Optional[str] = None) -> None:
610
687
  """Delete user memories from Redis.
611
688
 
612
689
  Args:
613
690
  memory_ids (List[str]): The IDs of the memories to delete.
691
+ user_id (Optional[str]): The ID of the user. If provided, only deletes memories belonging to this user.
614
692
  """
615
693
  try:
616
694
  # TODO: cant we optimize this?
617
695
  for memory_id in memory_ids:
696
+ # If user_id is provided, verify ownership before deleting
697
+ if user_id is not None:
698
+ memory = self._get_record("memories", memory_id)
699
+ if memory is None:
700
+ continue
701
+ if memory.get("user_id") != user_id:
702
+ log_debug(f"Memory {memory_id} does not belong to user {user_id}, skipping deletion")
703
+ continue
704
+
618
705
  self._delete_record(
619
706
  "memories",
620
707
  memory_id,
@@ -623,6 +710,7 @@ class RedisDb(BaseDb):
623
710
 
624
711
  except Exception as e:
625
712
  log_error(f"Error deleting user memories: {e}")
713
+ raise e
626
714
 
627
715
  def get_all_memory_topics(self) -> List[str]:
628
716
  """Get all memory topics from Redis.
@@ -643,15 +731,17 @@ class RedisDb(BaseDb):
643
731
 
644
732
  except Exception as e:
645
733
  log_error(f"Exception reading memory topics: {e}")
646
- return []
734
+ raise e
647
735
 
648
736
  def get_user_memory(
649
- self, memory_id: str, deserialize: Optional[bool] = True
737
+ self, memory_id: str, deserialize: Optional[bool] = True, user_id: Optional[str] = None
650
738
  ) -> Optional[Union[UserMemory, Dict[str, Any]]]:
651
739
  """Get a memory from Redis.
652
740
 
653
741
  Args:
654
742
  memory_id (str): The ID of the memory to get.
743
+ deserialize (Optional[bool]): Whether to deserialize the memory. Defaults to True.
744
+ user_id (Optional[str]): The ID of the user. If provided, only returns the memory if it belongs to this user.
655
745
 
656
746
  Returns:
657
747
  Optional[UserMemory]: The memory data if found, None otherwise.
@@ -661,6 +751,10 @@ class RedisDb(BaseDb):
661
751
  if memory_raw is None:
662
752
  return None
663
753
 
754
+ # Filter by user_id if provided
755
+ if user_id is not None and memory_raw.get("user_id") != user_id:
756
+ return None
757
+
664
758
  if not deserialize:
665
759
  return memory_raw
666
760
 
@@ -668,7 +762,7 @@ class RedisDb(BaseDb):
668
762
 
669
763
  except Exception as e:
670
764
  log_error(f"Exception reading memory: {e}")
671
- return None
765
+ raise e
672
766
 
673
767
  def get_user_memories(
674
768
  self,
@@ -741,7 +835,7 @@ class RedisDb(BaseDb):
741
835
 
742
836
  except Exception as e:
743
837
  log_error(f"Exception reading memories: {e}")
744
- return [] if deserialize else ([], 0)
838
+ raise e
745
839
 
746
840
  def get_user_memory_stats(
747
841
  self,
@@ -766,21 +860,21 @@ class RedisDb(BaseDb):
766
860
  # Group by user_id
767
861
  user_stats = {}
768
862
  for memory in all_memories:
769
- user_id = memory.get("user_id")
770
- if user_id is None:
863
+ memory_user_id = memory.get("user_id")
864
+ if memory_user_id is None:
771
865
  continue
772
866
 
773
- if user_id not in user_stats:
774
- user_stats[user_id] = {
775
- "user_id": user_id,
867
+ if memory_user_id not in user_stats:
868
+ user_stats[memory_user_id] = {
869
+ "user_id": memory_user_id,
776
870
  "total_memories": 0,
777
871
  "last_memory_updated_at": 0,
778
872
  }
779
873
 
780
- user_stats[user_id]["total_memories"] += 1
874
+ user_stats[memory_user_id]["total_memories"] += 1
781
875
  updated_at = memory.get("updated_at", 0)
782
- if updated_at > user_stats[user_id]["last_memory_updated_at"]:
783
- user_stats[user_id]["last_memory_updated_at"] = updated_at
876
+ if updated_at > user_stats[memory_user_id]["last_memory_updated_at"]:
877
+ user_stats[memory_user_id]["last_memory_updated_at"] = updated_at
784
878
 
785
879
  stats_list = list(user_stats.values())
786
880
 
@@ -795,7 +889,7 @@ class RedisDb(BaseDb):
795
889
 
796
890
  except Exception as e:
797
891
  log_error(f"Exception getting user memory stats: {e}")
798
- return [], 0
892
+ raise e
799
893
 
800
894
  def upsert_user_memory(
801
895
  self, memory: UserMemory, deserialize: Optional[bool] = True
@@ -819,6 +913,9 @@ class RedisDb(BaseDb):
819
913
  "memory_id": memory.memory_id,
820
914
  "memory": memory.memory,
821
915
  "topics": memory.topics,
916
+ "input": memory.input,
917
+ "feedback": memory.feedback,
918
+ "created_at": memory.created_at,
822
919
  "updated_at": int(time.time()),
823
920
  }
824
921
 
@@ -836,7 +933,44 @@ class RedisDb(BaseDb):
836
933
 
837
934
  except Exception as e:
838
935
  log_error(f"Error upserting user memory: {e}")
839
- return None
936
+ raise e
937
+
938
+ def upsert_memories(
939
+ self, memories: List[UserMemory], deserialize: Optional[bool] = True, preserve_updated_at: bool = False
940
+ ) -> List[Union[UserMemory, Dict[str, Any]]]:
941
+ """
942
+ Bulk upsert multiple user memories for improved performance on large datasets.
943
+
944
+ Args:
945
+ memories (List[UserMemory]): List of memories to upsert.
946
+ deserialize (Optional[bool]): Whether to deserialize the memories. Defaults to True.
947
+
948
+ Returns:
949
+ List[Union[UserMemory, Dict[str, Any]]]: List of upserted memories.
950
+
951
+ Raises:
952
+ Exception: If an error occurs during bulk upsert.
953
+ """
954
+ if not memories:
955
+ return []
956
+
957
+ try:
958
+ log_info(
959
+ f"RedisDb doesn't support efficient bulk operations, falling back to individual upserts for {len(memories)} memories"
960
+ )
961
+
962
+ # Fall back to individual upserts
963
+ results = []
964
+ for memory in memories:
965
+ if memory is not None:
966
+ result = self.upsert_user_memory(memory, deserialize=deserialize)
967
+ if result is not None:
968
+ results.append(result)
969
+ return results
970
+
971
+ except Exception as e:
972
+ log_error(f"Exception during bulk memory upsert: {e}")
973
+ return []
840
974
 
841
975
  def clear_memories(self) -> None:
842
976
  """Delete all memories from the database.
@@ -853,9 +987,8 @@ class RedisDb(BaseDb):
853
987
  self.redis_client.delete(*keys)
854
988
 
855
989
  except Exception as e:
856
- from agno.utils.log import log_warning
857
-
858
- log_warning(f"Exception deleting all memories: {e}")
990
+ log_error(f"Exception deleting all memories: {e}")
991
+ raise e
859
992
 
860
993
  # -- Metrics methods --
861
994
 
@@ -893,7 +1026,7 @@ class RedisDb(BaseDb):
893
1026
 
894
1027
  except Exception as e:
895
1028
  log_error(f"Error reading sessions for metrics: {e}")
896
- return []
1029
+ raise e
897
1030
 
898
1031
  def _get_metrics_calculation_starting_date(self) -> Optional[date]:
899
1032
  """Get the first date for which metrics calculation is needed.
@@ -930,7 +1063,7 @@ class RedisDb(BaseDb):
930
1063
 
931
1064
  except Exception as e:
932
1065
  log_error(f"Error getting metrics starting date: {e}")
933
- return None
1066
+ raise e
934
1067
 
935
1068
  def calculate_metrics(self) -> Optional[list[dict]]:
936
1069
  """Calculate metrics for all dates without complete metrics.
@@ -1037,7 +1170,7 @@ class RedisDb(BaseDb):
1037
1170
 
1038
1171
  except Exception as e:
1039
1172
  log_error(f"Error getting metrics: {e}")
1040
- return [], None
1173
+ raise e
1041
1174
 
1042
1175
  # -- Knowledge methods --
1043
1176
 
@@ -1055,6 +1188,7 @@ class RedisDb(BaseDb):
1055
1188
 
1056
1189
  except Exception as e:
1057
1190
  log_error(f"Error deleting knowledge content: {e}")
1191
+ raise e
1058
1192
 
1059
1193
  def get_knowledge_content(self, id: str) -> Optional[KnowledgeRow]:
1060
1194
  """Get a knowledge row from the database.
@@ -1077,7 +1211,7 @@ class RedisDb(BaseDb):
1077
1211
 
1078
1212
  except Exception as e:
1079
1213
  log_error(f"Error getting knowledge content: {e}")
1080
- return None
1214
+ raise e
1081
1215
 
1082
1216
  def get_knowledge_contents(
1083
1217
  self,
@@ -1120,7 +1254,7 @@ class RedisDb(BaseDb):
1120
1254
 
1121
1255
  except Exception as e:
1122
1256
  log_error(f"Error getting knowledge contents: {e}")
1123
- return [], 0
1257
+ raise e
1124
1258
 
1125
1259
  def upsert_knowledge_content(self, knowledge_row: KnowledgeRow):
1126
1260
  """Upsert knowledge content in the database.
@@ -1142,7 +1276,7 @@ class RedisDb(BaseDb):
1142
1276
 
1143
1277
  except Exception as e:
1144
1278
  log_error(f"Error upserting knowledge content: {e}")
1145
- return None
1279
+ raise e
1146
1280
 
1147
1281
  # -- Eval methods --
1148
1282
 
@@ -1175,7 +1309,7 @@ class RedisDb(BaseDb):
1175
1309
 
1176
1310
  except Exception as e:
1177
1311
  log_error(f"Error creating eval run: {e}")
1178
- return None
1312
+ raise e
1179
1313
 
1180
1314
  def delete_eval_run(self, eval_run_id: str) -> None:
1181
1315
  """Delete an eval run from Redis.
@@ -1250,7 +1384,7 @@ class RedisDb(BaseDb):
1250
1384
 
1251
1385
  except Exception as e:
1252
1386
  log_error(f"Exception getting eval run {eval_run_id}: {e}")
1253
- return None
1387
+ raise e
1254
1388
 
1255
1389
  def get_eval_runs(
1256
1390
  self,
@@ -1326,7 +1460,7 @@ class RedisDb(BaseDb):
1326
1460
 
1327
1461
  except Exception as e:
1328
1462
  log_error(f"Exception getting eval runs: {e}")
1329
- return []
1463
+ raise e
1330
1464
 
1331
1465
  def rename_eval_run(
1332
1466
  self, eval_run_id: str, name: str, deserialize: Optional[bool] = True
@@ -1365,3 +1499,175 @@ class RedisDb(BaseDb):
1365
1499
  except Exception as e:
1366
1500
  log_error(f"Error updating eval run name {eval_run_id}: {e}")
1367
1501
  raise
1502
+
1503
+ # -- Cultural Knowledge methods --
1504
+ def clear_cultural_knowledge(self) -> None:
1505
+ """Delete all cultural knowledge from the database.
1506
+
1507
+ Raises:
1508
+ Exception: If an error occurs during deletion.
1509
+ """
1510
+ try:
1511
+ keys = get_all_keys_for_table(redis_client=self.redis_client, prefix=self.db_prefix, table_type="culture")
1512
+
1513
+ if keys:
1514
+ self.redis_client.delete(*keys)
1515
+
1516
+ except Exception as e:
1517
+ log_error(f"Exception deleting all cultural knowledge: {e}")
1518
+ raise e
1519
+
1520
+ def delete_cultural_knowledge(self, id: str) -> None:
1521
+ """Delete cultural knowledge by ID.
1522
+
1523
+ Args:
1524
+ id (str): The ID of the cultural knowledge to delete.
1525
+
1526
+ Raises:
1527
+ Exception: If an error occurs during deletion.
1528
+ """
1529
+ try:
1530
+ if self._delete_record("culture", id, index_fields=["name", "agent_id", "team_id"]):
1531
+ log_debug(f"Successfully deleted cultural knowledge id: {id}")
1532
+ else:
1533
+ log_debug(f"No cultural knowledge found with id: {id}")
1534
+
1535
+ except Exception as e:
1536
+ log_error(f"Error deleting cultural knowledge: {e}")
1537
+ raise e
1538
+
1539
+ def get_cultural_knowledge(
1540
+ self, id: str, deserialize: Optional[bool] = True
1541
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
1542
+ """Get cultural knowledge by ID.
1543
+
1544
+ Args:
1545
+ id (str): The ID of the cultural knowledge to retrieve.
1546
+ deserialize (Optional[bool]): Whether to deserialize to CulturalKnowledge object. Defaults to True.
1547
+
1548
+ Returns:
1549
+ Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The cultural knowledge if found, None otherwise.
1550
+
1551
+ Raises:
1552
+ Exception: If an error occurs during retrieval.
1553
+ """
1554
+ try:
1555
+ cultural_knowledge = self._get_record("culture", id)
1556
+
1557
+ if cultural_knowledge is None:
1558
+ return None
1559
+
1560
+ if not deserialize:
1561
+ return cultural_knowledge
1562
+
1563
+ return deserialize_cultural_knowledge_from_db(cultural_knowledge)
1564
+
1565
+ except Exception as e:
1566
+ log_error(f"Error getting cultural knowledge: {e}")
1567
+ raise e
1568
+
1569
+ def get_all_cultural_knowledge(
1570
+ self,
1571
+ agent_id: Optional[str] = None,
1572
+ team_id: Optional[str] = None,
1573
+ name: Optional[str] = None,
1574
+ limit: Optional[int] = None,
1575
+ page: Optional[int] = None,
1576
+ sort_by: Optional[str] = None,
1577
+ sort_order: Optional[str] = None,
1578
+ deserialize: Optional[bool] = True,
1579
+ ) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
1580
+ """Get all cultural knowledge with filtering and pagination.
1581
+
1582
+ Args:
1583
+ agent_id (Optional[str]): Filter by agent ID.
1584
+ team_id (Optional[str]): Filter by team ID.
1585
+ name (Optional[str]): Filter by name (case-insensitive partial match).
1586
+ limit (Optional[int]): Maximum number of results to return.
1587
+ page (Optional[int]): Page number for pagination.
1588
+ sort_by (Optional[str]): Field to sort by.
1589
+ sort_order (Optional[str]): Sort order ('asc' or 'desc').
1590
+ deserialize (Optional[bool]): Whether to deserialize to CulturalKnowledge objects. Defaults to True.
1591
+
1592
+ Returns:
1593
+ Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
1594
+ - When deserialize=True: List of CulturalKnowledge objects
1595
+ - When deserialize=False: Tuple with list of dictionaries and total count
1596
+
1597
+ Raises:
1598
+ Exception: If an error occurs during retrieval.
1599
+ """
1600
+ try:
1601
+ all_cultural_knowledge = self._get_all_records("culture")
1602
+
1603
+ # Apply filters
1604
+ filtered_items = []
1605
+ for item in all_cultural_knowledge:
1606
+ if agent_id is not None and item.get("agent_id") != agent_id:
1607
+ continue
1608
+ if team_id is not None and item.get("team_id") != team_id:
1609
+ continue
1610
+ if name is not None and name.lower() not in item.get("name", "").lower():
1611
+ continue
1612
+
1613
+ filtered_items.append(item)
1614
+
1615
+ sorted_items = apply_sorting(records=filtered_items, sort_by=sort_by, sort_order=sort_order)
1616
+ paginated_items = apply_pagination(records=sorted_items, limit=limit, page=page)
1617
+
1618
+ if not deserialize:
1619
+ return paginated_items, len(filtered_items)
1620
+
1621
+ return [deserialize_cultural_knowledge_from_db(item) for item in paginated_items]
1622
+
1623
+ except Exception as e:
1624
+ log_error(f"Error getting all cultural knowledge: {e}")
1625
+ raise e
1626
+
1627
+ def upsert_cultural_knowledge(
1628
+ self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
1629
+ ) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
1630
+ """Upsert cultural knowledge in Redis.
1631
+
1632
+ Args:
1633
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge to upsert.
1634
+ deserialize (Optional[bool]): Whether to deserialize the result. Defaults to True.
1635
+
1636
+ Returns:
1637
+ Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The upserted cultural knowledge.
1638
+
1639
+ Raises:
1640
+ Exception: If an error occurs during upsert.
1641
+ """
1642
+ try:
1643
+ # Serialize content, categories, and notes into a dict for DB storage
1644
+ content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
1645
+ item_id = cultural_knowledge.id or str(uuid4())
1646
+
1647
+ # Create the item dict with serialized content
1648
+ data = {
1649
+ "id": item_id,
1650
+ "name": cultural_knowledge.name,
1651
+ "summary": cultural_knowledge.summary,
1652
+ "content": content_dict if content_dict else None,
1653
+ "metadata": cultural_knowledge.metadata,
1654
+ "input": cultural_knowledge.input,
1655
+ "created_at": cultural_knowledge.created_at,
1656
+ "updated_at": int(time.time()),
1657
+ "agent_id": cultural_knowledge.agent_id,
1658
+ "team_id": cultural_knowledge.team_id,
1659
+ }
1660
+
1661
+ success = self._store_record("culture", item_id, data, index_fields=["name", "agent_id", "team_id"])
1662
+
1663
+ if not success:
1664
+ return None
1665
+
1666
+ if not deserialize:
1667
+ return data
1668
+
1669
+ return deserialize_cultural_knowledge_from_db(data)
1670
+
1671
+ except Exception as e:
1672
+ log_error(f"Error upserting cultural knowledge: {e}")
1673
+ raise e