agno 2.2.13__py3-none-any.whl → 2.4.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (383) hide show
  1. agno/agent/__init__.py +6 -0
  2. agno/agent/agent.py +5252 -3145
  3. agno/agent/remote.py +525 -0
  4. agno/api/api.py +2 -0
  5. agno/client/__init__.py +3 -0
  6. agno/client/a2a/__init__.py +10 -0
  7. agno/client/a2a/client.py +554 -0
  8. agno/client/a2a/schemas.py +112 -0
  9. agno/client/a2a/utils.py +369 -0
  10. agno/client/os.py +2669 -0
  11. agno/compression/__init__.py +3 -0
  12. agno/compression/manager.py +247 -0
  13. agno/culture/manager.py +2 -2
  14. agno/db/base.py +927 -6
  15. agno/db/dynamo/dynamo.py +788 -2
  16. agno/db/dynamo/schemas.py +128 -0
  17. agno/db/dynamo/utils.py +26 -3
  18. agno/db/firestore/firestore.py +674 -50
  19. agno/db/firestore/schemas.py +41 -0
  20. agno/db/firestore/utils.py +25 -10
  21. agno/db/gcs_json/gcs_json_db.py +506 -3
  22. agno/db/gcs_json/utils.py +14 -2
  23. agno/db/in_memory/in_memory_db.py +203 -4
  24. agno/db/in_memory/utils.py +14 -2
  25. agno/db/json/json_db.py +498 -2
  26. agno/db/json/utils.py +14 -2
  27. agno/db/migrations/manager.py +199 -0
  28. agno/db/migrations/utils.py +19 -0
  29. agno/db/migrations/v1_to_v2.py +54 -16
  30. agno/db/migrations/versions/__init__.py +0 -0
  31. agno/db/migrations/versions/v2_3_0.py +977 -0
  32. agno/db/mongo/async_mongo.py +1013 -39
  33. agno/db/mongo/mongo.py +684 -4
  34. agno/db/mongo/schemas.py +48 -0
  35. agno/db/mongo/utils.py +17 -0
  36. agno/db/mysql/__init__.py +2 -1
  37. agno/db/mysql/async_mysql.py +2958 -0
  38. agno/db/mysql/mysql.py +722 -53
  39. agno/db/mysql/schemas.py +77 -11
  40. agno/db/mysql/utils.py +151 -8
  41. agno/db/postgres/async_postgres.py +1254 -137
  42. agno/db/postgres/postgres.py +2316 -93
  43. agno/db/postgres/schemas.py +153 -21
  44. agno/db/postgres/utils.py +22 -7
  45. agno/db/redis/redis.py +531 -3
  46. agno/db/redis/schemas.py +36 -0
  47. agno/db/redis/utils.py +31 -15
  48. agno/db/schemas/evals.py +1 -0
  49. agno/db/schemas/memory.py +20 -9
  50. agno/db/singlestore/schemas.py +70 -1
  51. agno/db/singlestore/singlestore.py +737 -74
  52. agno/db/singlestore/utils.py +13 -3
  53. agno/db/sqlite/async_sqlite.py +1069 -89
  54. agno/db/sqlite/schemas.py +133 -1
  55. agno/db/sqlite/sqlite.py +2203 -165
  56. agno/db/sqlite/utils.py +21 -11
  57. agno/db/surrealdb/models.py +25 -0
  58. agno/db/surrealdb/surrealdb.py +603 -1
  59. agno/db/utils.py +60 -0
  60. agno/eval/__init__.py +26 -3
  61. agno/eval/accuracy.py +25 -12
  62. agno/eval/agent_as_judge.py +871 -0
  63. agno/eval/base.py +29 -0
  64. agno/eval/performance.py +10 -4
  65. agno/eval/reliability.py +22 -13
  66. agno/eval/utils.py +2 -1
  67. agno/exceptions.py +42 -0
  68. agno/hooks/__init__.py +3 -0
  69. agno/hooks/decorator.py +164 -0
  70. agno/integrations/discord/client.py +13 -2
  71. agno/knowledge/__init__.py +4 -0
  72. agno/knowledge/chunking/code.py +90 -0
  73. agno/knowledge/chunking/document.py +65 -4
  74. agno/knowledge/chunking/fixed.py +4 -1
  75. agno/knowledge/chunking/markdown.py +102 -11
  76. agno/knowledge/chunking/recursive.py +2 -2
  77. agno/knowledge/chunking/semantic.py +130 -48
  78. agno/knowledge/chunking/strategy.py +18 -0
  79. agno/knowledge/embedder/azure_openai.py +0 -1
  80. agno/knowledge/embedder/google.py +1 -1
  81. agno/knowledge/embedder/mistral.py +1 -1
  82. agno/knowledge/embedder/nebius.py +1 -1
  83. agno/knowledge/embedder/openai.py +16 -12
  84. agno/knowledge/filesystem.py +412 -0
  85. agno/knowledge/knowledge.py +4261 -1199
  86. agno/knowledge/protocol.py +134 -0
  87. agno/knowledge/reader/arxiv_reader.py +3 -2
  88. agno/knowledge/reader/base.py +9 -7
  89. agno/knowledge/reader/csv_reader.py +91 -42
  90. agno/knowledge/reader/docx_reader.py +9 -10
  91. agno/knowledge/reader/excel_reader.py +225 -0
  92. agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
  93. agno/knowledge/reader/firecrawl_reader.py +3 -2
  94. agno/knowledge/reader/json_reader.py +16 -22
  95. agno/knowledge/reader/markdown_reader.py +15 -14
  96. agno/knowledge/reader/pdf_reader.py +33 -28
  97. agno/knowledge/reader/pptx_reader.py +9 -10
  98. agno/knowledge/reader/reader_factory.py +135 -1
  99. agno/knowledge/reader/s3_reader.py +8 -16
  100. agno/knowledge/reader/tavily_reader.py +3 -3
  101. agno/knowledge/reader/text_reader.py +15 -14
  102. agno/knowledge/reader/utils/__init__.py +17 -0
  103. agno/knowledge/reader/utils/spreadsheet.py +114 -0
  104. agno/knowledge/reader/web_search_reader.py +8 -65
  105. agno/knowledge/reader/website_reader.py +16 -13
  106. agno/knowledge/reader/wikipedia_reader.py +36 -3
  107. agno/knowledge/reader/youtube_reader.py +3 -2
  108. agno/knowledge/remote_content/__init__.py +33 -0
  109. agno/knowledge/remote_content/config.py +266 -0
  110. agno/knowledge/remote_content/remote_content.py +105 -17
  111. agno/knowledge/utils.py +76 -22
  112. agno/learn/__init__.py +71 -0
  113. agno/learn/config.py +463 -0
  114. agno/learn/curate.py +185 -0
  115. agno/learn/machine.py +725 -0
  116. agno/learn/schemas.py +1114 -0
  117. agno/learn/stores/__init__.py +38 -0
  118. agno/learn/stores/decision_log.py +1156 -0
  119. agno/learn/stores/entity_memory.py +3275 -0
  120. agno/learn/stores/learned_knowledge.py +1583 -0
  121. agno/learn/stores/protocol.py +117 -0
  122. agno/learn/stores/session_context.py +1217 -0
  123. agno/learn/stores/user_memory.py +1495 -0
  124. agno/learn/stores/user_profile.py +1220 -0
  125. agno/learn/utils.py +209 -0
  126. agno/media.py +22 -6
  127. agno/memory/__init__.py +14 -1
  128. agno/memory/manager.py +223 -8
  129. agno/memory/strategies/__init__.py +15 -0
  130. agno/memory/strategies/base.py +66 -0
  131. agno/memory/strategies/summarize.py +196 -0
  132. agno/memory/strategies/types.py +37 -0
  133. agno/models/aimlapi/aimlapi.py +17 -0
  134. agno/models/anthropic/claude.py +434 -59
  135. agno/models/aws/bedrock.py +121 -20
  136. agno/models/aws/claude.py +131 -274
  137. agno/models/azure/ai_foundry.py +10 -6
  138. agno/models/azure/openai_chat.py +33 -10
  139. agno/models/base.py +1162 -561
  140. agno/models/cerebras/cerebras.py +120 -24
  141. agno/models/cerebras/cerebras_openai.py +21 -2
  142. agno/models/cohere/chat.py +65 -6
  143. agno/models/cometapi/cometapi.py +18 -1
  144. agno/models/dashscope/dashscope.py +2 -3
  145. agno/models/deepinfra/deepinfra.py +18 -1
  146. agno/models/deepseek/deepseek.py +69 -3
  147. agno/models/fireworks/fireworks.py +18 -1
  148. agno/models/google/gemini.py +959 -89
  149. agno/models/google/utils.py +22 -0
  150. agno/models/groq/groq.py +48 -18
  151. agno/models/huggingface/huggingface.py +17 -6
  152. agno/models/ibm/watsonx.py +16 -6
  153. agno/models/internlm/internlm.py +18 -1
  154. agno/models/langdb/langdb.py +13 -1
  155. agno/models/litellm/chat.py +88 -9
  156. agno/models/litellm/litellm_openai.py +18 -1
  157. agno/models/message.py +24 -5
  158. agno/models/meta/llama.py +40 -13
  159. agno/models/meta/llama_openai.py +22 -21
  160. agno/models/metrics.py +12 -0
  161. agno/models/mistral/mistral.py +8 -4
  162. agno/models/n1n/__init__.py +3 -0
  163. agno/models/n1n/n1n.py +57 -0
  164. agno/models/nebius/nebius.py +6 -7
  165. agno/models/nvidia/nvidia.py +20 -3
  166. agno/models/ollama/__init__.py +2 -0
  167. agno/models/ollama/chat.py +17 -6
  168. agno/models/ollama/responses.py +100 -0
  169. agno/models/openai/__init__.py +2 -0
  170. agno/models/openai/chat.py +117 -26
  171. agno/models/openai/open_responses.py +46 -0
  172. agno/models/openai/responses.py +110 -32
  173. agno/models/openrouter/__init__.py +2 -0
  174. agno/models/openrouter/openrouter.py +67 -2
  175. agno/models/openrouter/responses.py +146 -0
  176. agno/models/perplexity/perplexity.py +19 -1
  177. agno/models/portkey/portkey.py +7 -6
  178. agno/models/requesty/requesty.py +19 -2
  179. agno/models/response.py +20 -2
  180. agno/models/sambanova/sambanova.py +20 -3
  181. agno/models/siliconflow/siliconflow.py +19 -2
  182. agno/models/together/together.py +20 -3
  183. agno/models/vercel/v0.py +20 -3
  184. agno/models/vertexai/claude.py +124 -4
  185. agno/models/vllm/vllm.py +19 -14
  186. agno/models/xai/xai.py +19 -2
  187. agno/os/app.py +467 -137
  188. agno/os/auth.py +253 -5
  189. agno/os/config.py +22 -0
  190. agno/os/interfaces/a2a/a2a.py +7 -6
  191. agno/os/interfaces/a2a/router.py +635 -26
  192. agno/os/interfaces/a2a/utils.py +32 -33
  193. agno/os/interfaces/agui/agui.py +5 -3
  194. agno/os/interfaces/agui/router.py +26 -16
  195. agno/os/interfaces/agui/utils.py +97 -57
  196. agno/os/interfaces/base.py +7 -7
  197. agno/os/interfaces/slack/router.py +16 -7
  198. agno/os/interfaces/slack/slack.py +7 -7
  199. agno/os/interfaces/whatsapp/router.py +35 -7
  200. agno/os/interfaces/whatsapp/security.py +3 -1
  201. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  202. agno/os/managers.py +326 -0
  203. agno/os/mcp.py +652 -79
  204. agno/os/middleware/__init__.py +4 -0
  205. agno/os/middleware/jwt.py +718 -115
  206. agno/os/middleware/trailing_slash.py +27 -0
  207. agno/os/router.py +105 -1558
  208. agno/os/routers/agents/__init__.py +3 -0
  209. agno/os/routers/agents/router.py +655 -0
  210. agno/os/routers/agents/schema.py +288 -0
  211. agno/os/routers/components/__init__.py +3 -0
  212. agno/os/routers/components/components.py +475 -0
  213. agno/os/routers/database.py +155 -0
  214. agno/os/routers/evals/evals.py +111 -18
  215. agno/os/routers/evals/schemas.py +38 -5
  216. agno/os/routers/evals/utils.py +80 -11
  217. agno/os/routers/health.py +3 -3
  218. agno/os/routers/knowledge/knowledge.py +284 -35
  219. agno/os/routers/knowledge/schemas.py +14 -2
  220. agno/os/routers/memory/memory.py +274 -11
  221. agno/os/routers/memory/schemas.py +44 -3
  222. agno/os/routers/metrics/metrics.py +30 -15
  223. agno/os/routers/metrics/schemas.py +10 -6
  224. agno/os/routers/registry/__init__.py +3 -0
  225. agno/os/routers/registry/registry.py +337 -0
  226. agno/os/routers/session/session.py +143 -14
  227. agno/os/routers/teams/__init__.py +3 -0
  228. agno/os/routers/teams/router.py +550 -0
  229. agno/os/routers/teams/schema.py +280 -0
  230. agno/os/routers/traces/__init__.py +3 -0
  231. agno/os/routers/traces/schemas.py +414 -0
  232. agno/os/routers/traces/traces.py +549 -0
  233. agno/os/routers/workflows/__init__.py +3 -0
  234. agno/os/routers/workflows/router.py +757 -0
  235. agno/os/routers/workflows/schema.py +139 -0
  236. agno/os/schema.py +157 -584
  237. agno/os/scopes.py +469 -0
  238. agno/os/settings.py +3 -0
  239. agno/os/utils.py +574 -185
  240. agno/reasoning/anthropic.py +85 -1
  241. agno/reasoning/azure_ai_foundry.py +93 -1
  242. agno/reasoning/deepseek.py +102 -2
  243. agno/reasoning/default.py +6 -7
  244. agno/reasoning/gemini.py +87 -3
  245. agno/reasoning/groq.py +109 -2
  246. agno/reasoning/helpers.py +6 -7
  247. agno/reasoning/manager.py +1238 -0
  248. agno/reasoning/ollama.py +93 -1
  249. agno/reasoning/openai.py +115 -1
  250. agno/reasoning/vertexai.py +85 -1
  251. agno/registry/__init__.py +3 -0
  252. agno/registry/registry.py +68 -0
  253. agno/remote/__init__.py +3 -0
  254. agno/remote/base.py +581 -0
  255. agno/run/__init__.py +2 -4
  256. agno/run/agent.py +134 -19
  257. agno/run/base.py +49 -1
  258. agno/run/cancel.py +65 -52
  259. agno/run/cancellation_management/__init__.py +9 -0
  260. agno/run/cancellation_management/base.py +78 -0
  261. agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
  262. agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
  263. agno/run/requirement.py +181 -0
  264. agno/run/team.py +111 -19
  265. agno/run/workflow.py +2 -1
  266. agno/session/agent.py +57 -92
  267. agno/session/summary.py +1 -1
  268. agno/session/team.py +62 -115
  269. agno/session/workflow.py +353 -57
  270. agno/skills/__init__.py +17 -0
  271. agno/skills/agent_skills.py +377 -0
  272. agno/skills/errors.py +32 -0
  273. agno/skills/loaders/__init__.py +4 -0
  274. agno/skills/loaders/base.py +27 -0
  275. agno/skills/loaders/local.py +216 -0
  276. agno/skills/skill.py +65 -0
  277. agno/skills/utils.py +107 -0
  278. agno/skills/validator.py +277 -0
  279. agno/table.py +10 -0
  280. agno/team/__init__.py +5 -1
  281. agno/team/remote.py +447 -0
  282. agno/team/team.py +3769 -2202
  283. agno/tools/brandfetch.py +27 -18
  284. agno/tools/browserbase.py +225 -16
  285. agno/tools/crawl4ai.py +3 -0
  286. agno/tools/duckduckgo.py +25 -71
  287. agno/tools/exa.py +0 -21
  288. agno/tools/file.py +14 -13
  289. agno/tools/file_generation.py +12 -6
  290. agno/tools/firecrawl.py +15 -7
  291. agno/tools/function.py +94 -113
  292. agno/tools/google_bigquery.py +11 -2
  293. agno/tools/google_drive.py +4 -3
  294. agno/tools/knowledge.py +9 -4
  295. agno/tools/mcp/mcp.py +301 -18
  296. agno/tools/mcp/multi_mcp.py +269 -14
  297. agno/tools/mem0.py +11 -10
  298. agno/tools/memory.py +47 -46
  299. agno/tools/mlx_transcribe.py +10 -7
  300. agno/tools/models/nebius.py +5 -5
  301. agno/tools/models_labs.py +20 -10
  302. agno/tools/nano_banana.py +151 -0
  303. agno/tools/parallel.py +0 -7
  304. agno/tools/postgres.py +76 -36
  305. agno/tools/python.py +14 -6
  306. agno/tools/reasoning.py +30 -23
  307. agno/tools/redshift.py +406 -0
  308. agno/tools/shopify.py +1519 -0
  309. agno/tools/spotify.py +919 -0
  310. agno/tools/tavily.py +4 -1
  311. agno/tools/toolkit.py +253 -18
  312. agno/tools/websearch.py +93 -0
  313. agno/tools/website.py +1 -1
  314. agno/tools/wikipedia.py +1 -1
  315. agno/tools/workflow.py +56 -48
  316. agno/tools/yfinance.py +12 -11
  317. agno/tracing/__init__.py +12 -0
  318. agno/tracing/exporter.py +161 -0
  319. agno/tracing/schemas.py +276 -0
  320. agno/tracing/setup.py +112 -0
  321. agno/utils/agent.py +251 -10
  322. agno/utils/cryptography.py +22 -0
  323. agno/utils/dttm.py +33 -0
  324. agno/utils/events.py +264 -7
  325. agno/utils/hooks.py +111 -3
  326. agno/utils/http.py +161 -2
  327. agno/utils/mcp.py +49 -8
  328. agno/utils/media.py +22 -1
  329. agno/utils/models/ai_foundry.py +9 -2
  330. agno/utils/models/claude.py +20 -5
  331. agno/utils/models/cohere.py +9 -2
  332. agno/utils/models/llama.py +9 -2
  333. agno/utils/models/mistral.py +4 -2
  334. agno/utils/os.py +0 -0
  335. agno/utils/print_response/agent.py +99 -16
  336. agno/utils/print_response/team.py +223 -24
  337. agno/utils/print_response/workflow.py +0 -2
  338. agno/utils/prompts.py +8 -6
  339. agno/utils/remote.py +23 -0
  340. agno/utils/response.py +1 -13
  341. agno/utils/string.py +91 -2
  342. agno/utils/team.py +62 -12
  343. agno/utils/tokens.py +657 -0
  344. agno/vectordb/base.py +15 -2
  345. agno/vectordb/cassandra/cassandra.py +1 -1
  346. agno/vectordb/chroma/__init__.py +2 -1
  347. agno/vectordb/chroma/chromadb.py +468 -23
  348. agno/vectordb/clickhouse/clickhousedb.py +1 -1
  349. agno/vectordb/couchbase/couchbase.py +6 -2
  350. agno/vectordb/lancedb/lance_db.py +7 -38
  351. agno/vectordb/lightrag/lightrag.py +7 -6
  352. agno/vectordb/milvus/milvus.py +118 -84
  353. agno/vectordb/mongodb/__init__.py +2 -1
  354. agno/vectordb/mongodb/mongodb.py +14 -31
  355. agno/vectordb/pgvector/pgvector.py +120 -66
  356. agno/vectordb/pineconedb/pineconedb.py +2 -19
  357. agno/vectordb/qdrant/__init__.py +2 -1
  358. agno/vectordb/qdrant/qdrant.py +33 -56
  359. agno/vectordb/redis/__init__.py +2 -1
  360. agno/vectordb/redis/redisdb.py +19 -31
  361. agno/vectordb/singlestore/singlestore.py +17 -9
  362. agno/vectordb/surrealdb/surrealdb.py +2 -38
  363. agno/vectordb/weaviate/__init__.py +2 -1
  364. agno/vectordb/weaviate/weaviate.py +7 -3
  365. agno/workflow/__init__.py +5 -1
  366. agno/workflow/agent.py +2 -2
  367. agno/workflow/condition.py +12 -10
  368. agno/workflow/loop.py +28 -9
  369. agno/workflow/parallel.py +21 -13
  370. agno/workflow/remote.py +362 -0
  371. agno/workflow/router.py +12 -9
  372. agno/workflow/step.py +261 -36
  373. agno/workflow/steps.py +12 -8
  374. agno/workflow/types.py +40 -77
  375. agno/workflow/workflow.py +939 -213
  376. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
  377. agno-2.4.3.dist-info/RECORD +677 -0
  378. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
  379. agno/tools/googlesearch.py +0 -98
  380. agno/tools/memori.py +0 -339
  381. agno-2.2.13.dist-info/RECORD +0 -575
  382. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
  383. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
agno/learn/utils.py ADDED
@@ -0,0 +1,209 @@
1
+ """
2
+ Learning Machine Utilities
3
+ ==========================
4
+ Helper functions for safe data handling.
5
+
6
+ All functions are designed to never raise exceptions -
7
+ they return None on any failure. This prevents learning
8
+ extraction errors from crashing the main agent.
9
+ """
10
+
11
+ from dataclasses import asdict, fields
12
+ from typing import Any, Dict, List, Optional, Type, TypeVar
13
+
14
+ T = TypeVar("T")
15
+
16
+
17
+ def _safe_get(data: Any, key: str, default: Any = None) -> Any:
18
+ """Safely get a key from dict-like data.
19
+
20
+ Args:
21
+ data: Dict or object with attributes.
22
+ key: Key or attribute name to get.
23
+ default: Value to return if not found.
24
+
25
+ Returns:
26
+ The value, or default if not found.
27
+ """
28
+ if isinstance(data, dict):
29
+ return data.get(key, default)
30
+ return getattr(data, key, default)
31
+
32
+
33
+ def _parse_json(data: Any) -> Optional[Dict]:
34
+ """Parse JSON string to dict, or return dict as-is.
35
+
36
+ Args:
37
+ data: JSON string, dict, or None.
38
+
39
+ Returns:
40
+ Parsed dict, or None if parsing fails.
41
+ """
42
+ if data is None:
43
+ return None
44
+ if isinstance(data, dict):
45
+ return data
46
+ if isinstance(data, str):
47
+ import json
48
+
49
+ try:
50
+ return json.loads(data)
51
+ except Exception:
52
+ return None
53
+ return None
54
+
55
+
56
+ def from_dict_safe(cls: Type[T], data: Any) -> Optional[T]:
57
+ """Safely create a dataclass instance from dict-like data.
58
+
59
+ Works with any dataclass - automatically handles subclass fields.
60
+ Never raises - returns None on any failure.
61
+
62
+ Args:
63
+ cls: The dataclass type to instantiate.
64
+ data: Dict, JSON string, or existing instance.
65
+
66
+ Returns:
67
+ Instance of cls, or None if parsing fails.
68
+
69
+ Example:
70
+ >>> profile = from_dict_safe(UserProfile, {"user_id": "123"})
71
+ >>> profile.user_id
72
+ '123'
73
+ """
74
+ if data is None:
75
+ return None
76
+
77
+ # Already the right type
78
+ if isinstance(data, cls):
79
+ return data
80
+
81
+ try:
82
+ # Parse JSON string if needed
83
+ parsed = _parse_json(data)
84
+ if parsed is None:
85
+ return None
86
+
87
+ # Get valid field names for this class
88
+ field_names = {f.name for f in fields(cls)} # type: ignore
89
+
90
+ # Filter to only valid fields
91
+ kwargs = {k: v for k, v in parsed.items() if k in field_names}
92
+
93
+ return cls(**kwargs)
94
+ except Exception:
95
+ return None
96
+
97
+
98
+ def print_panel(
99
+ title: str,
100
+ subtitle: str,
101
+ lines: List[str],
102
+ *,
103
+ empty_message: str = "No data",
104
+ raw_data: Any = None,
105
+ raw: bool = False,
106
+ ) -> None:
107
+ """Print formatted panel output for learning stores.
108
+
109
+ Uses rich library for formatted output with a bordered panel.
110
+ Falls back to pprint when raw=True or rich is unavailable.
111
+
112
+ Args:
113
+ title: Panel title (e.g., "User Profile", "Session Context")
114
+ subtitle: Panel subtitle (e.g., user_id, session_id)
115
+ lines: Content lines to display inside the panel
116
+ empty_message: Message shown when lines is empty
117
+ raw_data: Object to pprint when raw=True
118
+ raw: If True, use pprint instead of formatted panel
119
+
120
+ Example:
121
+ >>> print_panel(
122
+ ... title="User Profile",
123
+ ... subtitle="alice@example.com",
124
+ ... lines=["Name: Alice", "Memories:", " [abc123] Loves Python"],
125
+ ... raw_data=profile,
126
+ ... )
127
+ ╭──────────────── User Profile ─────────────────╮
128
+ │ Name: Alice │
129
+ │ Memories: │
130
+ │ [abc123] Loves Python │
131
+ ╰─────────────── alice@example.com ─────────────╯
132
+ """
133
+ if raw and raw_data is not None:
134
+ from pprint import pprint
135
+
136
+ pprint(to_dict_safe(raw_data) or raw_data)
137
+ return
138
+
139
+ try:
140
+ from rich.console import Console
141
+ from rich.panel import Panel
142
+
143
+ console = Console()
144
+
145
+ if not lines:
146
+ content = f"[dim]{empty_message}[/dim]"
147
+ else:
148
+ content = "\n".join(lines)
149
+
150
+ panel = Panel(
151
+ content,
152
+ title=f"[bold]{title}[/bold]",
153
+ subtitle=f"[dim]{subtitle}[/dim]",
154
+ border_style="blue",
155
+ )
156
+ console.print(panel)
157
+
158
+ except ImportError:
159
+ # Fallback if rich not installed
160
+ from pprint import pprint
161
+
162
+ print(f"=== {title} ({subtitle}) ===")
163
+ if not lines:
164
+ print(f" {empty_message}")
165
+ else:
166
+ for line in lines:
167
+ print(f" {line}")
168
+ print()
169
+
170
+
171
+ def to_dict_safe(obj: Any) -> Optional[Dict[str, Any]]:
172
+ """Safely convert a dataclass to dict.
173
+
174
+ Works with any dataclass. Never raises - returns None on failure.
175
+
176
+ Args:
177
+ obj: Dataclass instance to convert.
178
+
179
+ Returns:
180
+ Dict representation, or None if conversion fails.
181
+
182
+ Example:
183
+ >>> profile = UserProfile(user_id="123")
184
+ >>> to_dict_safe(profile)
185
+ {'user_id': '123', 'name': None, ...}
186
+ """
187
+ if obj is None:
188
+ return None
189
+
190
+ try:
191
+ # Already a dict
192
+ if isinstance(obj, dict):
193
+ return obj
194
+
195
+ # Has to_dict method
196
+ if hasattr(obj, "to_dict"):
197
+ return obj.to_dict()
198
+
199
+ # Is a dataclass
200
+ if hasattr(obj, "__dataclass_fields__"):
201
+ return asdict(obj)
202
+
203
+ # Has __dict__
204
+ if hasattr(obj, "__dict__"):
205
+ return dict(obj.__dict__)
206
+
207
+ return None
208
+ except Exception:
209
+ return None
agno/media.py CHANGED
@@ -4,6 +4,8 @@ from uuid import uuid4
4
4
 
5
5
  from pydantic import BaseModel, field_validator, model_validator
6
6
 
7
+ from agno.utils.log import log_error
8
+
7
9
 
8
10
  class Image(BaseModel):
9
11
  """Unified Image class for all use cases (input, output, artifacts)"""
@@ -395,10 +397,20 @@ class File(BaseModel):
395
397
  name: Optional[str] = None,
396
398
  format: Optional[str] = None,
397
399
  ) -> "File":
398
- """Create File from base64 encoded content"""
400
+ """Create File from base64 encoded content or plain text.
401
+
402
+ Handles both base64-encoded binary content and plain text content
403
+ (which is stored as UTF-8 strings for text/* MIME types).
404
+ """
399
405
  import base64
400
406
 
401
- content_bytes = base64.b64decode(base64_content)
407
+ try:
408
+ content_bytes = base64.b64decode(base64_content)
409
+ except Exception:
410
+ # If not valid base64, it might be plain text content (text/csv, text/plain, etc.)
411
+ # which is stored as UTF-8 strings, not base64
412
+ content_bytes = base64_content.encode("utf-8")
413
+
402
414
  return cls(
403
415
  content=content_bytes,
404
416
  id=id,
@@ -413,10 +425,14 @@ class File(BaseModel):
413
425
  import httpx
414
426
 
415
427
  if self.url:
416
- response = httpx.get(self.url)
417
- content = response.content
418
- mime_type = response.headers.get("Content-Type", "").split(";")[0]
419
- return content, mime_type
428
+ try:
429
+ response = httpx.get(self.url)
430
+ content = response.content
431
+ mime_type = response.headers.get("Content-Type", "").split(";")[0]
432
+ return content, mime_type
433
+ except Exception:
434
+ log_error(f"Failed to download file from {self.url}")
435
+ return None
420
436
  else:
421
437
  return None
422
438
 
agno/memory/__init__.py CHANGED
@@ -1,3 +1,16 @@
1
1
  from agno.memory.manager import MemoryManager, UserMemory
2
+ from agno.memory.strategies import (
3
+ MemoryOptimizationStrategy,
4
+ MemoryOptimizationStrategyFactory,
5
+ MemoryOptimizationStrategyType,
6
+ SummarizeStrategy,
7
+ )
2
8
 
3
- __all__ = ["MemoryManager", "UserMemory"]
9
+ __all__ = [
10
+ "MemoryManager",
11
+ "UserMemory",
12
+ "MemoryOptimizationStrategy",
13
+ "MemoryOptimizationStrategyType",
14
+ "MemoryOptimizationStrategyFactory",
15
+ "SummarizeStrategy",
16
+ ]
agno/memory/manager.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from copy import deepcopy
2
2
  from dataclasses import dataclass
3
- from datetime import datetime
4
3
  from os import getenv
5
4
  from textwrap import dedent
6
5
  from typing import Any, Callable, Dict, List, Literal, Optional, Type, Union
@@ -9,10 +8,16 @@ from pydantic import BaseModel, Field
9
8
 
10
9
  from agno.db.base import AsyncBaseDb, BaseDb
11
10
  from agno.db.schemas import UserMemory
11
+ from agno.memory.strategies import MemoryOptimizationStrategy
12
+ from agno.memory.strategies.types import (
13
+ MemoryOptimizationStrategyFactory,
14
+ MemoryOptimizationStrategyType,
15
+ )
12
16
  from agno.models.base import Model
13
17
  from agno.models.message import Message
14
18
  from agno.models.utils import get_model
15
19
  from agno.tools.function import Function
20
+ from agno.utils.dttm import now_epoch_s
16
21
  from agno.utils.log import (
17
22
  log_debug,
18
23
  log_error,
@@ -89,9 +94,6 @@ class MemoryManager:
89
94
  self.clear_memories = clear_memories
90
95
  self.debug_mode = debug_mode
91
96
 
92
- self._get_models()
93
-
94
- def _get_models(self) -> None:
95
97
  if self.model is not None:
96
98
  self.model = get_model(self.model)
97
99
 
@@ -227,7 +229,7 @@ class MemoryManager:
227
229
  memory.user_id = user_id
228
230
 
229
231
  if not memory.updated_at:
230
- memory.updated_at = datetime.now()
232
+ memory.updated_at = now_epoch_s()
231
233
 
232
234
  self._upsert_db_memory(memory=memory)
233
235
  return memory.memory_id
@@ -255,7 +257,7 @@ class MemoryManager:
255
257
  user_id = "default"
256
258
 
257
259
  if not memory.updated_at:
258
- memory.updated_at = datetime.now()
260
+ memory.updated_at = now_epoch_s()
259
261
 
260
262
  memory.memory_id = memory_id
261
263
  memory.user_id = user_id
@@ -291,6 +293,74 @@ class MemoryManager:
291
293
  log_warning("Memory DB not provided.")
292
294
  return None
293
295
 
296
+ def clear_user_memories(self, user_id: Optional[str] = None) -> None:
297
+ """Clear all memories for a specific user.
298
+
299
+ Args:
300
+ user_id (Optional[str]): The user id to clear memories for. If not provided, clears memories for the "default" user.
301
+ """
302
+ if user_id is None:
303
+ log_warning("Using default user id.")
304
+ user_id = "default"
305
+
306
+ if not self.db:
307
+ log_warning("Memory DB not provided.")
308
+ return
309
+
310
+ if isinstance(self.db, AsyncBaseDb):
311
+ raise ValueError(
312
+ "clear_user_memories() is not supported with an async DB. Please use aclear_user_memories() instead."
313
+ )
314
+
315
+ # TODO: This is inefficient - we fetch all memories just to get their IDs.
316
+ # Extend delete_user_memories() to accept just user_id and delete all memories
317
+ # for that user directly without requiring a list of memory_ids.
318
+ memories = self.get_user_memories(user_id=user_id)
319
+ if not memories:
320
+ log_debug(f"No memories found for user {user_id}")
321
+ return
322
+
323
+ # Extract memory IDs
324
+ memory_ids = [mem.memory_id for mem in memories if mem.memory_id]
325
+
326
+ if memory_ids:
327
+ # Delete all memories in a single batch operation
328
+ self.db.delete_user_memories(memory_ids=memory_ids, user_id=user_id)
329
+ log_debug(f"Cleared {len(memory_ids)} memories for user {user_id}")
330
+
331
+ async def aclear_user_memories(self, user_id: Optional[str] = None) -> None:
332
+ """Clear all memories for a specific user (async).
333
+
334
+ Args:
335
+ user_id (Optional[str]): The user id to clear memories for. If not provided, clears memories for the "default" user.
336
+ """
337
+ if user_id is None:
338
+ user_id = "default"
339
+
340
+ if not self.db:
341
+ log_warning("Memory DB not provided.")
342
+ return
343
+
344
+ if isinstance(self.db, AsyncBaseDb):
345
+ memories = await self.aget_user_memories(user_id=user_id)
346
+ else:
347
+ memories = self.get_user_memories(user_id=user_id)
348
+
349
+ if not memories:
350
+ log_debug(f"No memories found for user {user_id}")
351
+ return
352
+
353
+ # Extract memory IDs
354
+ memory_ids = [mem.memory_id for mem in memories if mem.memory_id]
355
+
356
+ if memory_ids:
357
+ # Delete all memories in a single batch operation
358
+ if isinstance(self.db, AsyncBaseDb):
359
+ await self.db.delete_user_memories(memory_ids=memory_ids, user_id=user_id)
360
+ else:
361
+ self.db.delete_user_memories(memory_ids=memory_ids, user_id=user_id)
362
+ log_debug(f"Cleared {len(memory_ids)} memories for user {user_id}")
363
+
294
364
  # -*- Agent Functions
295
365
  def create_user_memories(
296
366
  self,
@@ -671,7 +741,7 @@ class MemoryManager:
671
741
  # If updated_at is None, place at the beginning of the list
672
742
  sorted_memories_list = sorted(
673
743
  memories_list,
674
- key=lambda memory: memory.updated_at or datetime.min,
744
+ key=lambda m: m.updated_at if m.updated_at is not None else 0,
675
745
  )
676
746
  else:
677
747
  sorted_memories_list = []
@@ -694,6 +764,7 @@ class MemoryManager:
694
764
  if memories is None:
695
765
  memories = {}
696
766
 
767
+ MAX_UNIX_TS = 2**63 - 1
697
768
  memories_list = memories.get(user_id, [])
698
769
  # Sort memories by updated_at timestamp if available
699
770
  if memories_list:
@@ -701,7 +772,7 @@ class MemoryManager:
701
772
  # If updated_at is None, place at the end of the list
702
773
  sorted_memories_list = sorted(
703
774
  memories_list,
704
- key=lambda memory: memory.updated_at or datetime.max,
775
+ key=lambda m: m.updated_at if m.updated_at is not None else MAX_UNIX_TS,
705
776
  )
706
777
 
707
778
  else:
@@ -712,6 +783,150 @@ class MemoryManager:
712
783
 
713
784
  return sorted_memories_list
714
785
 
786
+ def optimize_memories(
787
+ self,
788
+ user_id: Optional[str] = None,
789
+ strategy: Union[
790
+ MemoryOptimizationStrategyType, MemoryOptimizationStrategy
791
+ ] = MemoryOptimizationStrategyType.SUMMARIZE,
792
+ apply: bool = True,
793
+ ) -> List[UserMemory]:
794
+ """Optimize user memories using the specified strategy.
795
+
796
+ Args:
797
+ user_id: User ID to optimize memories for. Defaults to "default".
798
+ strategy: Optimization strategy. Can be:
799
+ - Enum: MemoryOptimizationStrategyType.SUMMARIZE
800
+ - Instance: Custom MemoryOptimizationStrategy instance
801
+ apply: If True, automatically replace memories in database.
802
+
803
+ Returns:
804
+ List of optimized UserMemory objects.
805
+ """
806
+ if user_id is None:
807
+ user_id = "default"
808
+
809
+ if isinstance(self.db, AsyncBaseDb):
810
+ raise ValueError(
811
+ "optimize_memories() is not supported with an async DB. Please use aoptimize_memories() instead."
812
+ )
813
+
814
+ # Get user memories
815
+ memories = self.get_user_memories(user_id=user_id)
816
+ if not memories:
817
+ log_debug("No memories to optimize")
818
+ return []
819
+
820
+ # Get strategy instance
821
+ if isinstance(strategy, MemoryOptimizationStrategyType):
822
+ strategy_instance = MemoryOptimizationStrategyFactory.create_strategy(strategy)
823
+ else:
824
+ # Already a strategy instance
825
+ strategy_instance = strategy
826
+
827
+ # Optimize memories using strategy
828
+ optimization_model = self.get_model()
829
+ optimized_memories = strategy_instance.optimize(memories=memories, model=optimization_model)
830
+
831
+ # Apply to database if requested
832
+ if apply:
833
+ log_debug(f"Applying optimized memories to database for user {user_id}")
834
+
835
+ if not self.db:
836
+ log_warning("Memory DB not provided. Cannot apply optimized memories.")
837
+ return optimized_memories
838
+
839
+ # Clear all existing memories for the user
840
+ self.clear_user_memories(user_id=user_id)
841
+
842
+ # Add all optimized memories
843
+ for opt_mem in optimized_memories:
844
+ # Ensure memory has an ID (generate if needed for new memories)
845
+ if not opt_mem.memory_id:
846
+ from uuid import uuid4
847
+
848
+ opt_mem.memory_id = str(uuid4())
849
+
850
+ self.db.upsert_user_memory(memory=opt_mem)
851
+
852
+ optimized_tokens = strategy_instance.count_tokens(optimized_memories)
853
+ log_debug(f"Optimization complete. New token count: {optimized_tokens}")
854
+
855
+ return optimized_memories
856
+
857
+ async def aoptimize_memories(
858
+ self,
859
+ user_id: Optional[str] = None,
860
+ strategy: Union[
861
+ MemoryOptimizationStrategyType, MemoryOptimizationStrategy
862
+ ] = MemoryOptimizationStrategyType.SUMMARIZE,
863
+ apply: bool = True,
864
+ ) -> List[UserMemory]:
865
+ """Async version of optimize_memories.
866
+
867
+ Args:
868
+ user_id: User ID to optimize memories for. Defaults to "default".
869
+ strategy: Optimization strategy. Can be:
870
+ - Enum: MemoryOptimizationStrategyType.SUMMARIZE
871
+ - Instance: Custom MemoryOptimizationStrategy instance
872
+ apply: If True, automatically replace memories in database.
873
+
874
+ Returns:
875
+ List of optimized UserMemory objects.
876
+ """
877
+ if user_id is None:
878
+ user_id = "default"
879
+
880
+ # Get user memories - handle both sync and async DBs
881
+ if isinstance(self.db, AsyncBaseDb):
882
+ memories = await self.aget_user_memories(user_id=user_id)
883
+ else:
884
+ memories = self.get_user_memories(user_id=user_id)
885
+
886
+ if not memories:
887
+ log_debug("No memories to optimize")
888
+ return []
889
+
890
+ # Get strategy instance
891
+ if isinstance(strategy, MemoryOptimizationStrategyType):
892
+ strategy_instance = MemoryOptimizationStrategyFactory.create_strategy(strategy)
893
+ else:
894
+ # Already a strategy instance
895
+ strategy_instance = strategy
896
+
897
+ # Optimize memories using strategy (async)
898
+ optimization_model = self.get_model()
899
+ optimized_memories = await strategy_instance.aoptimize(memories=memories, model=optimization_model)
900
+
901
+ # Apply to database if requested
902
+ if apply:
903
+ log_debug(f"Optimizing memories for user {user_id}")
904
+
905
+ if not self.db:
906
+ log_warning("Memory DB not provided. Cannot apply optimized memories.")
907
+ return optimized_memories
908
+
909
+ # Clear all existing memories for the user
910
+ await self.aclear_user_memories(user_id=user_id)
911
+
912
+ # Add all optimized memories
913
+ for opt_mem in optimized_memories:
914
+ # Ensure memory has an ID (generate if needed for new memories)
915
+ if not opt_mem.memory_id:
916
+ from uuid import uuid4
917
+
918
+ opt_mem.memory_id = str(uuid4())
919
+
920
+ if isinstance(self.db, AsyncBaseDb):
921
+ await self.db.upsert_user_memory(memory=opt_mem)
922
+ elif isinstance(self.db, BaseDb):
923
+ self.db.upsert_user_memory(memory=opt_mem)
924
+
925
+ optimized_tokens = strategy_instance.count_tokens(optimized_memories)
926
+ log_debug(f"Memory optimization complete. New token count: {optimized_tokens}")
927
+
928
+ return optimized_memories
929
+
715
930
  # --Memory Manager Functions--
716
931
  def determine_tools_for_model(self, tools: List[Callable]) -> List[Union[Function, dict]]:
717
932
  # Have to reset each time, because of different user IDs
@@ -0,0 +1,15 @@
1
+ """Memory optimization strategy implementations."""
2
+
3
+ from agno.memory.strategies.base import MemoryOptimizationStrategy
4
+ from agno.memory.strategies.summarize import SummarizeStrategy
5
+ from agno.memory.strategies.types import (
6
+ MemoryOptimizationStrategyFactory,
7
+ MemoryOptimizationStrategyType,
8
+ )
9
+
10
+ __all__ = [
11
+ "MemoryOptimizationStrategy",
12
+ "MemoryOptimizationStrategyFactory",
13
+ "MemoryOptimizationStrategyType",
14
+ "SummarizeStrategy",
15
+ ]
@@ -0,0 +1,66 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import List
3
+
4
+ from agno.db.schemas import UserMemory
5
+ from agno.models.base import Model
6
+ from agno.utils.tokens import count_text_tokens
7
+
8
+
9
+ class MemoryOptimizationStrategy(ABC):
10
+ """Abstract base class for memory optimization strategies.
11
+
12
+ Subclasses must implement optimize() and aoptimize().
13
+ get_system_prompt() is optional and only needed for LLM-based strategies.
14
+ """
15
+
16
+ def get_system_prompt(self) -> str:
17
+ """Get system prompt for this optimization strategy.
18
+
19
+ Returns:
20
+ System prompt string for LLM-based strategies.
21
+ """
22
+ raise NotImplementedError
23
+
24
+ @abstractmethod
25
+ def optimize(
26
+ self,
27
+ memories: List[UserMemory],
28
+ model: Model,
29
+ ) -> List[UserMemory]:
30
+ """Optimize memories synchronously.
31
+
32
+ Args:
33
+ memories: List of UserMemory objects to optimize
34
+ model: Model to use for optimization (if needed)
35
+
36
+ Returns:
37
+ List of optimized UserMemory objects
38
+ """
39
+ raise NotImplementedError
40
+
41
+ @abstractmethod
42
+ async def aoptimize(
43
+ self,
44
+ memories: List[UserMemory],
45
+ model: Model,
46
+ ) -> List[UserMemory]:
47
+ """Optimize memories asynchronously.
48
+
49
+ Args:
50
+ memories: List of UserMemory objects to optimize
51
+ model: Model to use for optimization (if needed)
52
+
53
+ Returns:
54
+ List of optimized UserMemory objects
55
+ """
56
+ raise NotImplementedError
57
+
58
+ def count_tokens(self, memories: List[UserMemory]) -> int:
59
+ """Count total tokens across all memories.
60
+
61
+ Args:
62
+ memories: List of UserMemory objects
63
+ Returns:
64
+ Total token count
65
+ """
66
+ return sum(count_text_tokens(m.memory or "") for m in memories)