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/machine.py ADDED
@@ -0,0 +1,725 @@
1
+ """
2
+ Learning Machine
3
+ ================
4
+ Unified learning system for agents.
5
+
6
+ Coordinates multiple learning stores to give agents:
7
+ - User memory (who they're talking to)
8
+ - Session context (what's happened so far)
9
+ - Entity memory (knowledge about external things)
10
+ - Learned knowledge (reusable insights)
11
+
12
+ Plus maintenance via the Curator for keeping memories healthy.
13
+ """
14
+
15
+ from dataclasses import dataclass, field
16
+ from os import getenv
17
+ from typing import Any, Callable, Dict, List, Optional, Union
18
+
19
+ from agno.learn.config import (
20
+ DecisionLogConfig,
21
+ EntityMemoryConfig,
22
+ LearnedKnowledgeConfig,
23
+ LearningMode,
24
+ SessionContextConfig,
25
+ UserMemoryConfig,
26
+ UserProfileConfig,
27
+ )
28
+ from agno.learn.curate import Curator
29
+ from agno.learn.stores.protocol import LearningStore
30
+ from agno.utils.log import (
31
+ log_debug,
32
+ log_warning,
33
+ set_log_level_to_debug,
34
+ set_log_level_to_info,
35
+ )
36
+
37
+ try:
38
+ from agno.db.base import AsyncBaseDb, BaseDb
39
+ from agno.models.base import Model
40
+ except ImportError:
41
+ pass
42
+
43
+ # Type aliases for cleaner signatures
44
+ UserProfileInput = Union[bool, UserProfileConfig, LearningStore, None]
45
+ UserMemoryInput = Union[bool, UserMemoryConfig, LearningStore, None]
46
+ EntityMemoryInput = Union[bool, EntityMemoryConfig, LearningStore, None]
47
+ SessionContextInput = Union[bool, SessionContextConfig, LearningStore, None]
48
+ LearnedKnowledgeInput = Union[bool, LearnedKnowledgeConfig, LearningStore, None]
49
+ DecisionLogInput = Union[bool, DecisionLogConfig, LearningStore, None]
50
+
51
+
52
+ @dataclass
53
+ class LearningMachine:
54
+ """Central orchestrator for agent learning.
55
+
56
+ Coordinates all learning stores and provides unified interface
57
+ for recall, processing, tool generation, and maintenance.
58
+
59
+ Args:
60
+ db: Database backend for persistence.
61
+ model: Model for learning extraction.
62
+ knowledge: Knowledge base for learned knowledge store.
63
+
64
+ user_profile: Enable user profile. Accepts bool, Config, or Store.
65
+ user_memory: Enable user memory. Accepts bool, Config, or Store.
66
+ session_context: Enable session context. Accepts bool, Config, or Store.
67
+ entity_memory: Enable entity memory. Accepts bool, Config, or Store.
68
+ learned_knowledge: Enable learned knowledge. Auto-enabled when knowledge provided.
69
+
70
+ namespace: Default namespace for entity_memory and learned_knowledge.
71
+ custom_stores: Additional stores implementing LearningStore protocol.
72
+ debug_mode: Enable debug logging.
73
+ """
74
+
75
+ db: Optional[Union["BaseDb", "AsyncBaseDb"]] = None
76
+ model: Optional["Model"] = None
77
+ knowledge: Optional[Any] = None
78
+
79
+ # Store configurations (accepts bool, Config, or Store instance)
80
+ user_profile: UserProfileInput = False
81
+ user_memory: UserMemoryInput = False
82
+ session_context: SessionContextInput = False
83
+ entity_memory: EntityMemoryInput = False
84
+ learned_knowledge: LearnedKnowledgeInput = False
85
+ decision_log: DecisionLogInput = False # Phase 2
86
+
87
+ # Namespace for entity_memory and learned_knowledge
88
+ namespace: str = "global"
89
+
90
+ # Custom stores
91
+ custom_stores: Optional[Dict[str, LearningStore]] = None
92
+
93
+ # Debug mode
94
+ debug_mode: bool = False
95
+
96
+ # Internal state (lazy initialization)
97
+ _stores: Optional[Dict[str, LearningStore]] = field(default=None, init=False)
98
+ _curator: Optional[Any] = field(default=None, init=False)
99
+
100
+ # =========================================================================
101
+ # Initialization (Lazy)
102
+ # =========================================================================
103
+
104
+ @property
105
+ def stores(self) -> Dict[str, LearningStore]:
106
+ """All registered stores, keyed by name. Lazily initialized."""
107
+ if self._stores is None:
108
+ self._initialize_stores()
109
+ return self._stores # type: ignore
110
+
111
+ def _initialize_stores(self) -> None:
112
+ """Initialize all configured stores."""
113
+ self._stores = {}
114
+
115
+ # User Profile
116
+ if self.user_profile:
117
+ self._stores["user_profile"] = self._resolve_store(
118
+ input_value=self.user_profile,
119
+ store_type="user_profile",
120
+ )
121
+
122
+ # User Memory
123
+ if self.user_memory:
124
+ self._stores["user_memory"] = self._resolve_store(
125
+ input_value=self.user_memory,
126
+ store_type="user_memory",
127
+ )
128
+
129
+ # Session Context
130
+ if self.session_context:
131
+ self._stores["session_context"] = self._resolve_store(
132
+ input_value=self.session_context,
133
+ store_type="session_context",
134
+ )
135
+
136
+ # Entity Memory
137
+ if self.entity_memory:
138
+ self._stores["entity_memory"] = self._resolve_store(
139
+ input_value=self.entity_memory,
140
+ store_type="entity_memory",
141
+ )
142
+
143
+ # Learned Knowledge (auto-enable if knowledge provided)
144
+ if self.learned_knowledge or self.knowledge is not None:
145
+ self._stores["learned_knowledge"] = self._resolve_store(
146
+ input_value=self.learned_knowledge if self.learned_knowledge else True,
147
+ store_type="learned_knowledge",
148
+ )
149
+
150
+ # Decision Log (Phase 2)
151
+ if self.decision_log:
152
+ self._stores["decision_log"] = self._resolve_store(
153
+ input_value=self.decision_log,
154
+ store_type="decision_log",
155
+ )
156
+
157
+ # Custom stores
158
+ if self.custom_stores:
159
+ for name, store in self.custom_stores.items():
160
+ self._stores[name] = store
161
+
162
+ log_debug(f"LearningMachine initialized with stores: {list(self._stores.keys())}")
163
+
164
+ def _resolve_store(
165
+ self,
166
+ input_value: Any,
167
+ store_type: str,
168
+ ) -> LearningStore:
169
+ """Resolve input to a store instance.
170
+
171
+ Args:
172
+ input_value: bool, Config, or Store instance
173
+ store_type: One of "user_profile", "user_memory", "session_context", "entity_memory", "learned_knowledge"
174
+
175
+ Returns:
176
+ Initialized store instance.
177
+ """
178
+ # Already a store instance
179
+ if isinstance(input_value, LearningStore):
180
+ return input_value
181
+
182
+ # Create store based on type
183
+ if store_type == "user_profile":
184
+ return self._create_user_profile_store(config=input_value)
185
+ elif store_type == "user_memory":
186
+ return self._create_user_memory_store(config=input_value)
187
+ elif store_type == "session_context":
188
+ return self._create_session_context_store(config=input_value)
189
+ elif store_type == "entity_memory":
190
+ return self._create_entity_memory_store(config=input_value)
191
+ elif store_type == "learned_knowledge":
192
+ return self._create_learned_knowledge_store(config=input_value)
193
+ elif store_type == "decision_log":
194
+ return self._create_decision_log_store(config=input_value)
195
+ else:
196
+ raise ValueError(f"Unknown store type: {store_type}")
197
+
198
+ def _create_user_profile_store(self, config: Any) -> LearningStore:
199
+ """Create UserProfileStore with resolved config."""
200
+ from agno.learn.stores import UserProfileStore
201
+
202
+ if isinstance(config, UserProfileConfig):
203
+ if config.db is None:
204
+ config.db = self.db
205
+ if config.model is None:
206
+ config.model = self.model
207
+ else:
208
+ config = UserProfileConfig(
209
+ db=self.db,
210
+ model=self.model,
211
+ mode=LearningMode.ALWAYS,
212
+ )
213
+
214
+ return UserProfileStore(config=config, debug_mode=self.debug_mode)
215
+
216
+ def _create_user_memory_store(self, config: Any) -> LearningStore:
217
+ """Create UserMemoryStore with resolved config."""
218
+ from agno.learn.stores import UserMemoryStore
219
+
220
+ if isinstance(config, UserMemoryConfig):
221
+ if config.db is None:
222
+ config.db = self.db
223
+ if config.model is None:
224
+ config.model = self.model
225
+ else:
226
+ config = UserMemoryConfig(
227
+ db=self.db,
228
+ model=self.model,
229
+ mode=LearningMode.ALWAYS,
230
+ )
231
+
232
+ return UserMemoryStore(config=config, debug_mode=self.debug_mode)
233
+
234
+ def _create_session_context_store(self, config: Any) -> LearningStore:
235
+ """Create SessionContextStore with resolved config."""
236
+ from agno.learn.stores import SessionContextStore
237
+
238
+ if isinstance(config, SessionContextConfig):
239
+ if config.db is None:
240
+ config.db = self.db
241
+ if config.model is None:
242
+ config.model = self.model
243
+ else:
244
+ config = SessionContextConfig(
245
+ db=self.db,
246
+ model=self.model,
247
+ enable_planning=False,
248
+ )
249
+
250
+ return SessionContextStore(config=config, debug_mode=self.debug_mode)
251
+
252
+ def _create_entity_memory_store(self, config: Any) -> LearningStore:
253
+ """Create EntityMemoryStore with resolved config."""
254
+ from agno.learn.stores import EntityMemoryStore
255
+
256
+ if isinstance(config, EntityMemoryConfig):
257
+ if config.db is None:
258
+ config.db = self.db
259
+ if config.model is None:
260
+ config.model = self.model
261
+ else:
262
+ config = EntityMemoryConfig(
263
+ db=self.db,
264
+ model=self.model,
265
+ namespace=self.namespace,
266
+ mode=LearningMode.ALWAYS,
267
+ )
268
+
269
+ return EntityMemoryStore(config=config, debug_mode=self.debug_mode)
270
+
271
+ def _create_learned_knowledge_store(self, config: Any) -> LearningStore:
272
+ """Create LearnedKnowledgeStore with resolved config."""
273
+ from agno.learn.stores import LearnedKnowledgeStore
274
+
275
+ if isinstance(config, LearnedKnowledgeConfig):
276
+ if config.model is None:
277
+ config.model = self.model
278
+ if config.knowledge is None and self.knowledge is not None:
279
+ config.knowledge = self.knowledge
280
+ else:
281
+ config = LearnedKnowledgeConfig(
282
+ model=self.model,
283
+ knowledge=self.knowledge,
284
+ mode=LearningMode.AGENTIC,
285
+ )
286
+
287
+ return LearnedKnowledgeStore(config=config, debug_mode=self.debug_mode)
288
+
289
+ def _create_decision_log_store(self, config: Any) -> LearningStore:
290
+ """Create DecisionLogStore with resolved config."""
291
+ from agno.learn.stores import DecisionLogStore
292
+
293
+ if isinstance(config, DecisionLogConfig):
294
+ if config.db is None:
295
+ config.db = self.db
296
+ if config.model is None:
297
+ config.model = self.model
298
+ else:
299
+ config = DecisionLogConfig(
300
+ db=self.db,
301
+ model=self.model,
302
+ mode=LearningMode.AGENTIC, # Default to AGENTIC for explicit logging
303
+ )
304
+
305
+ return DecisionLogStore(config=config, debug_mode=self.debug_mode)
306
+
307
+ # =========================================================================
308
+ # Store Accessors (Type-Safe)
309
+ # =========================================================================
310
+
311
+ @property
312
+ def user_profile_store(self) -> Optional[LearningStore]:
313
+ """Get user profile store if enabled."""
314
+ return self.stores.get("user_profile")
315
+
316
+ @property
317
+ def user_memory_store(self) -> Optional[LearningStore]:
318
+ """Get user memory store if enabled."""
319
+ return self.stores.get("user_memory")
320
+
321
+ @property
322
+ def session_context_store(self) -> Optional[LearningStore]:
323
+ """Get session context store if enabled."""
324
+ return self.stores.get("session_context")
325
+
326
+ @property
327
+ def entity_memory_store(self) -> Optional[LearningStore]:
328
+ """Get entity memory store if enabled."""
329
+ return self.stores.get("entity_memory")
330
+
331
+ @property
332
+ def learned_knowledge_store(self) -> Optional[LearningStore]:
333
+ """Get learned knowledge store if enabled."""
334
+ return self.stores.get("learned_knowledge")
335
+
336
+ @property
337
+ def decision_log_store(self) -> Optional[LearningStore]:
338
+ """Get decision log store if enabled."""
339
+ return self.stores.get("decision_log")
340
+
341
+ @property
342
+ def was_updated(self) -> bool:
343
+ """True if any store was updated in the last operation."""
344
+ return any(getattr(store, "was_updated", False) for store in self.stores.values())
345
+
346
+ # =========================================================================
347
+ # Main API
348
+ # =========================================================================
349
+
350
+ def build_context(
351
+ self,
352
+ user_id: Optional[str] = None,
353
+ session_id: Optional[str] = None,
354
+ message: Optional[str] = None,
355
+ entity_id: Optional[str] = None,
356
+ entity_type: Optional[str] = None,
357
+ namespace: Optional[str] = None,
358
+ agent_id: Optional[str] = None,
359
+ team_id: Optional[str] = None,
360
+ **kwargs,
361
+ ) -> str:
362
+ """Build memory context for the agent's system prompt.
363
+
364
+ Call before generating a response to give the agent relevant context.
365
+
366
+ Args:
367
+ user_id: User identifier (for user profile lookup).
368
+ session_id: Session identifier (for session context lookup).
369
+ message: Current message (for semantic search of learnings).
370
+ entity_id: Entity to retrieve (for entity memory).
371
+ entity_type: Type of entity to retrieve.
372
+ namespace: Namespace filter for entity_memory and learned_knowledge.
373
+ agent_id: Optional agent context.
374
+ team_id: Optional team context.
375
+
376
+ Returns:
377
+ Context string to inject into the agent's system prompt.
378
+ """
379
+ results = self.recall(
380
+ user_id=user_id,
381
+ session_id=session_id,
382
+ message=message,
383
+ entity_id=entity_id,
384
+ entity_type=entity_type,
385
+ namespace=namespace or self.namespace,
386
+ agent_id=agent_id,
387
+ team_id=team_id,
388
+ **kwargs,
389
+ )
390
+
391
+ return self._format_results(results=results)
392
+
393
+ async def abuild_context(
394
+ self,
395
+ user_id: Optional[str] = None,
396
+ session_id: Optional[str] = None,
397
+ message: Optional[str] = None,
398
+ entity_id: Optional[str] = None,
399
+ entity_type: Optional[str] = None,
400
+ namespace: Optional[str] = None,
401
+ agent_id: Optional[str] = None,
402
+ team_id: Optional[str] = None,
403
+ **kwargs,
404
+ ) -> str:
405
+ """Async version of build_context."""
406
+ results = await self.arecall(
407
+ user_id=user_id,
408
+ session_id=session_id,
409
+ message=message,
410
+ entity_id=entity_id,
411
+ entity_type=entity_type,
412
+ namespace=namespace or self.namespace,
413
+ agent_id=agent_id,
414
+ team_id=team_id,
415
+ **kwargs,
416
+ )
417
+
418
+ return self._format_results(results=results)
419
+
420
+ def get_tools(
421
+ self,
422
+ user_id: Optional[str] = None,
423
+ session_id: Optional[str] = None,
424
+ namespace: Optional[str] = None,
425
+ agent_id: Optional[str] = None,
426
+ team_id: Optional[str] = None,
427
+ **kwargs,
428
+ ) -> List[Callable]:
429
+ """Get learning tools to expose to the agent.
430
+
431
+ Returns tools based on which stores are enabled:
432
+ - user_profile: update_user_memory
433
+ - entity_memory: search_entities, create_entity, update_entity, add_fact, etc.
434
+ - learned_knowledge: search_learnings, save_learning
435
+
436
+ Args:
437
+ user_id: User identifier (required for user profile tools).
438
+ session_id: Session identifier.
439
+ namespace: Default namespace for entity/learning operations.
440
+ agent_id: Optional agent context.
441
+ team_id: Optional team context.
442
+
443
+ Returns:
444
+ List of callable tools.
445
+ """
446
+ tools = []
447
+ context = {
448
+ "user_id": user_id,
449
+ "session_id": session_id,
450
+ "namespace": namespace or self.namespace,
451
+ "agent_id": agent_id,
452
+ "team_id": team_id,
453
+ **kwargs,
454
+ }
455
+
456
+ for name, store in self.stores.items():
457
+ try:
458
+ store_tools = store.get_tools(**context)
459
+ if store_tools:
460
+ tools.extend(store_tools)
461
+ log_debug(f"Got {len(store_tools)} tools from {name}")
462
+ except Exception as e:
463
+ log_warning(f"Error getting tools from {name}: {e}")
464
+
465
+ return tools
466
+
467
+ async def aget_tools(
468
+ self,
469
+ user_id: Optional[str] = None,
470
+ session_id: Optional[str] = None,
471
+ namespace: Optional[str] = None,
472
+ agent_id: Optional[str] = None,
473
+ team_id: Optional[str] = None,
474
+ **kwargs,
475
+ ) -> List[Callable]:
476
+ """Async version of get_tools."""
477
+ tools = []
478
+ context = {
479
+ "user_id": user_id,
480
+ "session_id": session_id,
481
+ "namespace": namespace or self.namespace,
482
+ "agent_id": agent_id,
483
+ "team_id": team_id,
484
+ **kwargs,
485
+ }
486
+
487
+ for name, store in self.stores.items():
488
+ try:
489
+ store_tools = await store.aget_tools(**context)
490
+ if store_tools:
491
+ tools.extend(store_tools)
492
+ log_debug(f"Got {len(store_tools)} tools from {name}")
493
+ except Exception as e:
494
+ log_warning(f"Error getting tools from {name}: {e}")
495
+
496
+ return tools
497
+
498
+ def process(
499
+ self,
500
+ messages: List[Any],
501
+ user_id: Optional[str] = None,
502
+ session_id: Optional[str] = None,
503
+ namespace: Optional[str] = None,
504
+ agent_id: Optional[str] = None,
505
+ team_id: Optional[str] = None,
506
+ **kwargs,
507
+ ) -> None:
508
+ """Extract and save learnings from a conversation.
509
+
510
+ Call after a conversation to extract learnings. Each store
511
+ processes based on its mode (ALWAYS stores extract automatically).
512
+
513
+ Args:
514
+ messages: Conversation messages to analyze.
515
+ user_id: User identifier (for user profile extraction).
516
+ session_id: Session identifier (for session context extraction).
517
+ namespace: Namespace for entity/learning saves.
518
+ agent_id: Optional agent context.
519
+ team_id: Optional team context.
520
+ """
521
+ context = {
522
+ "messages": messages,
523
+ "user_id": user_id,
524
+ "session_id": session_id,
525
+ "namespace": namespace or self.namespace,
526
+ "agent_id": agent_id,
527
+ "team_id": team_id,
528
+ **kwargs,
529
+ }
530
+
531
+ for name, store in self.stores.items():
532
+ try:
533
+ store.process(**context)
534
+ if getattr(store, "was_updated", False):
535
+ log_debug(f"Store {name} was updated")
536
+ except Exception as e:
537
+ log_warning(f"Error processing through {name}: {e}")
538
+
539
+ async def aprocess(
540
+ self,
541
+ messages: List[Any],
542
+ user_id: Optional[str] = None,
543
+ session_id: Optional[str] = None,
544
+ namespace: Optional[str] = None,
545
+ agent_id: Optional[str] = None,
546
+ team_id: Optional[str] = None,
547
+ **kwargs,
548
+ ) -> None:
549
+ """Async version of process."""
550
+ context = {
551
+ "messages": messages,
552
+ "user_id": user_id,
553
+ "session_id": session_id,
554
+ "namespace": namespace or self.namespace,
555
+ "agent_id": agent_id,
556
+ "team_id": team_id,
557
+ **kwargs,
558
+ }
559
+
560
+ for name, store in self.stores.items():
561
+ try:
562
+ await store.aprocess(**context)
563
+ if getattr(store, "was_updated", False):
564
+ log_debug(f"Store {name} was updated")
565
+ except Exception as e:
566
+ log_warning(f"Error processing through {name}: {e}")
567
+
568
+ # =========================================================================
569
+ # Lower-Level API
570
+ # =========================================================================
571
+
572
+ def recall(
573
+ self,
574
+ user_id: Optional[str] = None,
575
+ session_id: Optional[str] = None,
576
+ message: Optional[str] = None,
577
+ entity_id: Optional[str] = None,
578
+ entity_type: Optional[str] = None,
579
+ namespace: Optional[str] = None,
580
+ agent_id: Optional[str] = None,
581
+ team_id: Optional[str] = None,
582
+ **kwargs,
583
+ ) -> Dict[str, Any]:
584
+ """Retrieve raw data from all stores.
585
+
586
+ Most users should use `build_context()` instead.
587
+
588
+ Returns:
589
+ Dict mapping store names to their recalled data.
590
+ """
591
+ results = {}
592
+ context = {
593
+ "user_id": user_id,
594
+ "session_id": session_id,
595
+ "message": message,
596
+ "query": message, # For learned_knowledge
597
+ "entity_id": entity_id,
598
+ "entity_type": entity_type,
599
+ "namespace": namespace or self.namespace,
600
+ "agent_id": agent_id,
601
+ "team_id": team_id,
602
+ **kwargs,
603
+ }
604
+
605
+ for name, store in self.stores.items():
606
+ try:
607
+ result = store.recall(**context)
608
+ results[name] = result
609
+ try:
610
+ log_debug(f"Recalled from {name}: {result}")
611
+ except Exception:
612
+ pass
613
+ except Exception as e:
614
+ log_warning(f"Error recalling from {name}: {e}")
615
+
616
+ return results
617
+
618
+ async def arecall(
619
+ self,
620
+ user_id: Optional[str] = None,
621
+ session_id: Optional[str] = None,
622
+ message: Optional[str] = None,
623
+ entity_id: Optional[str] = None,
624
+ entity_type: Optional[str] = None,
625
+ namespace: Optional[str] = None,
626
+ agent_id: Optional[str] = None,
627
+ team_id: Optional[str] = None,
628
+ **kwargs,
629
+ ) -> Dict[str, Any]:
630
+ """Async version of recall."""
631
+ results = {}
632
+ context = {
633
+ "user_id": user_id,
634
+ "session_id": session_id,
635
+ "message": message,
636
+ "query": message,
637
+ "entity_id": entity_id,
638
+ "entity_type": entity_type,
639
+ "namespace": namespace or self.namespace,
640
+ "agent_id": agent_id,
641
+ "team_id": team_id,
642
+ **kwargs,
643
+ }
644
+
645
+ for name, store in self.stores.items():
646
+ try:
647
+ result = await store.arecall(**context)
648
+ if result is not None:
649
+ results[name] = result
650
+ try:
651
+ log_debug(f"Recalled from {name}: {result}")
652
+ except Exception:
653
+ pass
654
+ except Exception as e:
655
+ log_warning(f"Error recalling from {name}: {e}")
656
+
657
+ return results
658
+
659
+ def _format_results(self, results: Dict[str, Any]) -> str:
660
+ """Format recalled data into context string."""
661
+ parts = []
662
+
663
+ for name, data in results.items():
664
+ store = self.stores.get(name)
665
+ if store:
666
+ try:
667
+ formatted = store.build_context(data=data)
668
+ if formatted:
669
+ parts.append(formatted)
670
+ except Exception as e:
671
+ log_warning(f"Error building context from {name}: {e}")
672
+
673
+ return "\n\n".join(parts)
674
+
675
+ # =========================================================================
676
+ # Curation
677
+ # =========================================================================
678
+
679
+ @property
680
+ def curator(self) -> "Curator":
681
+ """Get the curator for memory maintenance.
682
+
683
+ Lazily creates the curator on first access.
684
+
685
+ Example:
686
+ >>> learning.curator.prune(user_id="alice", max_age_days=90)
687
+ >>> learning.curator.deduplicate(user_id="alice")
688
+ """
689
+ if self._curator is None:
690
+ from agno.learn.curate import Curator
691
+
692
+ self._curator = Curator(machine=self)
693
+ return self._curator
694
+
695
+ # =========================================================================
696
+ # Debug
697
+ # =========================================================================
698
+
699
+ def set_log_level(self) -> None:
700
+ """Set log level based on debug_mode or AGNO_DEBUG env var."""
701
+ if self.debug_mode or getenv("AGNO_DEBUG", "false").lower() == "true":
702
+ self.debug_mode = True
703
+ set_log_level_to_debug()
704
+ else:
705
+ set_log_level_to_info()
706
+
707
+ # =========================================================================
708
+ # Representation
709
+ # =========================================================================
710
+
711
+ def __repr__(self) -> str:
712
+ """String representation for debugging."""
713
+ store_names = list(self.stores.keys()) if self._stores is not None else "[not initialized]"
714
+ db_name = self.db.__class__.__name__ if self.db else None
715
+ model_name = self.model.id if self.model and hasattr(self.model, "id") else None
716
+ has_knowledge = self.knowledge is not None
717
+
718
+ return (
719
+ f"LearningMachine("
720
+ f"stores={store_names}, "
721
+ f"db={db_name}, "
722
+ f"model={model_name}, "
723
+ f"knowledge={has_knowledge}, "
724
+ f"namespace={self.namespace!r})"
725
+ )