agno 2.1.2__py3-none-any.whl → 2.3.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. agno/agent/agent.py +5540 -2273
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/compression/__init__.py +3 -0
  5. agno/compression/manager.py +247 -0
  6. agno/culture/__init__.py +3 -0
  7. agno/culture/manager.py +956 -0
  8. agno/db/async_postgres/__init__.py +3 -0
  9. agno/db/base.py +689 -6
  10. agno/db/dynamo/dynamo.py +933 -37
  11. agno/db/dynamo/schemas.py +174 -10
  12. agno/db/dynamo/utils.py +63 -4
  13. agno/db/firestore/firestore.py +831 -9
  14. agno/db/firestore/schemas.py +51 -0
  15. agno/db/firestore/utils.py +102 -4
  16. agno/db/gcs_json/gcs_json_db.py +660 -12
  17. agno/db/gcs_json/utils.py +60 -26
  18. agno/db/in_memory/in_memory_db.py +287 -14
  19. agno/db/in_memory/utils.py +60 -2
  20. agno/db/json/json_db.py +590 -14
  21. agno/db/json/utils.py +60 -26
  22. agno/db/migrations/manager.py +199 -0
  23. agno/db/migrations/v1_to_v2.py +43 -13
  24. agno/db/migrations/versions/__init__.py +0 -0
  25. agno/db/migrations/versions/v2_3_0.py +938 -0
  26. agno/db/mongo/__init__.py +15 -1
  27. agno/db/mongo/async_mongo.py +2760 -0
  28. agno/db/mongo/mongo.py +879 -11
  29. agno/db/mongo/schemas.py +42 -0
  30. agno/db/mongo/utils.py +80 -8
  31. agno/db/mysql/__init__.py +2 -1
  32. agno/db/mysql/async_mysql.py +2912 -0
  33. agno/db/mysql/mysql.py +946 -68
  34. agno/db/mysql/schemas.py +72 -10
  35. agno/db/mysql/utils.py +198 -7
  36. agno/db/postgres/__init__.py +2 -1
  37. agno/db/postgres/async_postgres.py +2579 -0
  38. agno/db/postgres/postgres.py +942 -57
  39. agno/db/postgres/schemas.py +81 -18
  40. agno/db/postgres/utils.py +164 -2
  41. agno/db/redis/redis.py +671 -7
  42. agno/db/redis/schemas.py +50 -0
  43. agno/db/redis/utils.py +65 -7
  44. agno/db/schemas/__init__.py +2 -1
  45. agno/db/schemas/culture.py +120 -0
  46. agno/db/schemas/evals.py +1 -0
  47. agno/db/schemas/memory.py +17 -2
  48. agno/db/singlestore/schemas.py +63 -0
  49. agno/db/singlestore/singlestore.py +949 -83
  50. agno/db/singlestore/utils.py +60 -2
  51. agno/db/sqlite/__init__.py +2 -1
  52. agno/db/sqlite/async_sqlite.py +2911 -0
  53. agno/db/sqlite/schemas.py +62 -0
  54. agno/db/sqlite/sqlite.py +965 -46
  55. agno/db/sqlite/utils.py +169 -8
  56. agno/db/surrealdb/__init__.py +3 -0
  57. agno/db/surrealdb/metrics.py +292 -0
  58. agno/db/surrealdb/models.py +334 -0
  59. agno/db/surrealdb/queries.py +71 -0
  60. agno/db/surrealdb/surrealdb.py +1908 -0
  61. agno/db/surrealdb/utils.py +147 -0
  62. agno/db/utils.py +2 -0
  63. agno/eval/__init__.py +10 -0
  64. agno/eval/accuracy.py +75 -55
  65. agno/eval/agent_as_judge.py +861 -0
  66. agno/eval/base.py +29 -0
  67. agno/eval/performance.py +16 -7
  68. agno/eval/reliability.py +28 -16
  69. agno/eval/utils.py +35 -17
  70. agno/exceptions.py +27 -2
  71. agno/filters.py +354 -0
  72. agno/guardrails/prompt_injection.py +1 -0
  73. agno/hooks/__init__.py +3 -0
  74. agno/hooks/decorator.py +164 -0
  75. agno/integrations/discord/client.py +1 -1
  76. agno/knowledge/chunking/agentic.py +13 -10
  77. agno/knowledge/chunking/fixed.py +4 -1
  78. agno/knowledge/chunking/semantic.py +9 -4
  79. agno/knowledge/chunking/strategy.py +59 -15
  80. agno/knowledge/embedder/fastembed.py +1 -1
  81. agno/knowledge/embedder/nebius.py +1 -1
  82. agno/knowledge/embedder/ollama.py +8 -0
  83. agno/knowledge/embedder/openai.py +8 -8
  84. agno/knowledge/embedder/sentence_transformer.py +6 -2
  85. agno/knowledge/embedder/vllm.py +262 -0
  86. agno/knowledge/knowledge.py +1618 -318
  87. agno/knowledge/reader/base.py +6 -2
  88. agno/knowledge/reader/csv_reader.py +8 -10
  89. agno/knowledge/reader/docx_reader.py +5 -6
  90. agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
  91. agno/knowledge/reader/json_reader.py +5 -4
  92. agno/knowledge/reader/markdown_reader.py +8 -8
  93. agno/knowledge/reader/pdf_reader.py +17 -19
  94. agno/knowledge/reader/pptx_reader.py +101 -0
  95. agno/knowledge/reader/reader_factory.py +32 -3
  96. agno/knowledge/reader/s3_reader.py +3 -3
  97. agno/knowledge/reader/tavily_reader.py +193 -0
  98. agno/knowledge/reader/text_reader.py +22 -10
  99. agno/knowledge/reader/web_search_reader.py +1 -48
  100. agno/knowledge/reader/website_reader.py +10 -10
  101. agno/knowledge/reader/wikipedia_reader.py +33 -1
  102. agno/knowledge/types.py +1 -0
  103. agno/knowledge/utils.py +72 -7
  104. agno/media.py +22 -6
  105. agno/memory/__init__.py +14 -1
  106. agno/memory/manager.py +544 -83
  107. agno/memory/strategies/__init__.py +15 -0
  108. agno/memory/strategies/base.py +66 -0
  109. agno/memory/strategies/summarize.py +196 -0
  110. agno/memory/strategies/types.py +37 -0
  111. agno/models/aimlapi/aimlapi.py +17 -0
  112. agno/models/anthropic/claude.py +515 -40
  113. agno/models/aws/bedrock.py +102 -21
  114. agno/models/aws/claude.py +131 -274
  115. agno/models/azure/ai_foundry.py +41 -19
  116. agno/models/azure/openai_chat.py +39 -8
  117. agno/models/base.py +1249 -525
  118. agno/models/cerebras/cerebras.py +91 -21
  119. agno/models/cerebras/cerebras_openai.py +21 -2
  120. agno/models/cohere/chat.py +40 -6
  121. agno/models/cometapi/cometapi.py +18 -1
  122. agno/models/dashscope/dashscope.py +2 -3
  123. agno/models/deepinfra/deepinfra.py +18 -1
  124. agno/models/deepseek/deepseek.py +69 -3
  125. agno/models/fireworks/fireworks.py +18 -1
  126. agno/models/google/gemini.py +877 -80
  127. agno/models/google/utils.py +22 -0
  128. agno/models/groq/groq.py +51 -18
  129. agno/models/huggingface/huggingface.py +17 -6
  130. agno/models/ibm/watsonx.py +16 -6
  131. agno/models/internlm/internlm.py +18 -1
  132. agno/models/langdb/langdb.py +13 -1
  133. agno/models/litellm/chat.py +44 -9
  134. agno/models/litellm/litellm_openai.py +18 -1
  135. agno/models/message.py +28 -5
  136. agno/models/meta/llama.py +47 -14
  137. agno/models/meta/llama_openai.py +22 -17
  138. agno/models/mistral/mistral.py +8 -4
  139. agno/models/nebius/nebius.py +6 -7
  140. agno/models/nvidia/nvidia.py +20 -3
  141. agno/models/ollama/chat.py +24 -8
  142. agno/models/openai/chat.py +104 -29
  143. agno/models/openai/responses.py +101 -81
  144. agno/models/openrouter/openrouter.py +60 -3
  145. agno/models/perplexity/perplexity.py +17 -1
  146. agno/models/portkey/portkey.py +7 -6
  147. agno/models/requesty/requesty.py +24 -4
  148. agno/models/response.py +73 -2
  149. agno/models/sambanova/sambanova.py +20 -3
  150. agno/models/siliconflow/siliconflow.py +19 -2
  151. agno/models/together/together.py +20 -3
  152. agno/models/utils.py +254 -8
  153. agno/models/vercel/v0.py +20 -3
  154. agno/models/vertexai/__init__.py +0 -0
  155. agno/models/vertexai/claude.py +190 -0
  156. agno/models/vllm/vllm.py +19 -14
  157. agno/models/xai/xai.py +19 -2
  158. agno/os/app.py +549 -152
  159. agno/os/auth.py +190 -3
  160. agno/os/config.py +23 -0
  161. agno/os/interfaces/a2a/router.py +8 -11
  162. agno/os/interfaces/a2a/utils.py +1 -1
  163. agno/os/interfaces/agui/router.py +18 -3
  164. agno/os/interfaces/agui/utils.py +152 -39
  165. agno/os/interfaces/slack/router.py +55 -37
  166. agno/os/interfaces/slack/slack.py +9 -1
  167. agno/os/interfaces/whatsapp/router.py +0 -1
  168. agno/os/interfaces/whatsapp/security.py +3 -1
  169. agno/os/mcp.py +110 -52
  170. agno/os/middleware/__init__.py +2 -0
  171. agno/os/middleware/jwt.py +676 -112
  172. agno/os/router.py +40 -1478
  173. agno/os/routers/agents/__init__.py +3 -0
  174. agno/os/routers/agents/router.py +599 -0
  175. agno/os/routers/agents/schema.py +261 -0
  176. agno/os/routers/evals/evals.py +96 -39
  177. agno/os/routers/evals/schemas.py +65 -33
  178. agno/os/routers/evals/utils.py +80 -10
  179. agno/os/routers/health.py +10 -4
  180. agno/os/routers/knowledge/knowledge.py +196 -38
  181. agno/os/routers/knowledge/schemas.py +82 -22
  182. agno/os/routers/memory/memory.py +279 -52
  183. agno/os/routers/memory/schemas.py +46 -17
  184. agno/os/routers/metrics/metrics.py +20 -8
  185. agno/os/routers/metrics/schemas.py +16 -16
  186. agno/os/routers/session/session.py +462 -34
  187. agno/os/routers/teams/__init__.py +3 -0
  188. agno/os/routers/teams/router.py +512 -0
  189. agno/os/routers/teams/schema.py +257 -0
  190. agno/os/routers/traces/__init__.py +3 -0
  191. agno/os/routers/traces/schemas.py +414 -0
  192. agno/os/routers/traces/traces.py +499 -0
  193. agno/os/routers/workflows/__init__.py +3 -0
  194. agno/os/routers/workflows/router.py +624 -0
  195. agno/os/routers/workflows/schema.py +75 -0
  196. agno/os/schema.py +256 -693
  197. agno/os/scopes.py +469 -0
  198. agno/os/utils.py +514 -36
  199. agno/reasoning/anthropic.py +80 -0
  200. agno/reasoning/gemini.py +73 -0
  201. agno/reasoning/openai.py +5 -0
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +155 -32
  205. agno/run/base.py +55 -3
  206. agno/run/requirement.py +181 -0
  207. agno/run/team.py +125 -38
  208. agno/run/workflow.py +72 -18
  209. agno/session/agent.py +102 -89
  210. agno/session/summary.py +56 -15
  211. agno/session/team.py +164 -90
  212. agno/session/workflow.py +405 -40
  213. agno/table.py +10 -0
  214. agno/team/team.py +3974 -1903
  215. agno/tools/dalle.py +2 -4
  216. agno/tools/eleven_labs.py +23 -25
  217. agno/tools/exa.py +21 -16
  218. agno/tools/file.py +153 -23
  219. agno/tools/file_generation.py +16 -10
  220. agno/tools/firecrawl.py +15 -7
  221. agno/tools/function.py +193 -38
  222. agno/tools/gmail.py +238 -14
  223. agno/tools/google_drive.py +271 -0
  224. agno/tools/googlecalendar.py +36 -8
  225. agno/tools/googlesheets.py +20 -5
  226. agno/tools/jira.py +20 -0
  227. agno/tools/mcp/__init__.py +10 -0
  228. agno/tools/mcp/mcp.py +331 -0
  229. agno/tools/mcp/multi_mcp.py +347 -0
  230. agno/tools/mcp/params.py +24 -0
  231. agno/tools/mcp_toolbox.py +3 -3
  232. agno/tools/models/nebius.py +5 -5
  233. agno/tools/models_labs.py +20 -10
  234. agno/tools/nano_banana.py +151 -0
  235. agno/tools/notion.py +204 -0
  236. agno/tools/parallel.py +314 -0
  237. agno/tools/postgres.py +76 -36
  238. agno/tools/redshift.py +406 -0
  239. agno/tools/scrapegraph.py +1 -1
  240. agno/tools/shopify.py +1519 -0
  241. agno/tools/slack.py +18 -3
  242. agno/tools/spotify.py +919 -0
  243. agno/tools/tavily.py +146 -0
  244. agno/tools/toolkit.py +25 -0
  245. agno/tools/workflow.py +8 -1
  246. agno/tools/yfinance.py +12 -11
  247. agno/tracing/__init__.py +12 -0
  248. agno/tracing/exporter.py +157 -0
  249. agno/tracing/schemas.py +276 -0
  250. agno/tracing/setup.py +111 -0
  251. agno/utils/agent.py +938 -0
  252. agno/utils/cryptography.py +22 -0
  253. agno/utils/dttm.py +33 -0
  254. agno/utils/events.py +151 -3
  255. agno/utils/gemini.py +15 -5
  256. agno/utils/hooks.py +118 -4
  257. agno/utils/http.py +113 -2
  258. agno/utils/knowledge.py +12 -5
  259. agno/utils/log.py +1 -0
  260. agno/utils/mcp.py +92 -2
  261. agno/utils/media.py +187 -1
  262. agno/utils/merge_dict.py +3 -3
  263. agno/utils/message.py +60 -0
  264. agno/utils/models/ai_foundry.py +9 -2
  265. agno/utils/models/claude.py +49 -14
  266. agno/utils/models/cohere.py +9 -2
  267. agno/utils/models/llama.py +9 -2
  268. agno/utils/models/mistral.py +4 -2
  269. agno/utils/print_response/agent.py +109 -16
  270. agno/utils/print_response/team.py +223 -30
  271. agno/utils/print_response/workflow.py +251 -34
  272. agno/utils/streamlit.py +1 -1
  273. agno/utils/team.py +98 -9
  274. agno/utils/tokens.py +657 -0
  275. agno/vectordb/base.py +39 -7
  276. agno/vectordb/cassandra/cassandra.py +21 -5
  277. agno/vectordb/chroma/chromadb.py +43 -12
  278. agno/vectordb/clickhouse/clickhousedb.py +21 -5
  279. agno/vectordb/couchbase/couchbase.py +29 -5
  280. agno/vectordb/lancedb/lance_db.py +92 -181
  281. agno/vectordb/langchaindb/langchaindb.py +24 -4
  282. agno/vectordb/lightrag/lightrag.py +17 -3
  283. agno/vectordb/llamaindex/llamaindexdb.py +25 -5
  284. agno/vectordb/milvus/milvus.py +50 -37
  285. agno/vectordb/mongodb/__init__.py +7 -1
  286. agno/vectordb/mongodb/mongodb.py +36 -30
  287. agno/vectordb/pgvector/pgvector.py +201 -77
  288. agno/vectordb/pineconedb/pineconedb.py +41 -23
  289. agno/vectordb/qdrant/qdrant.py +67 -54
  290. agno/vectordb/redis/__init__.py +9 -0
  291. agno/vectordb/redis/redisdb.py +682 -0
  292. agno/vectordb/singlestore/singlestore.py +50 -29
  293. agno/vectordb/surrealdb/surrealdb.py +31 -41
  294. agno/vectordb/upstashdb/upstashdb.py +34 -6
  295. agno/vectordb/weaviate/weaviate.py +53 -14
  296. agno/workflow/__init__.py +2 -0
  297. agno/workflow/agent.py +299 -0
  298. agno/workflow/condition.py +120 -18
  299. agno/workflow/loop.py +77 -10
  300. agno/workflow/parallel.py +231 -143
  301. agno/workflow/router.py +118 -17
  302. agno/workflow/step.py +609 -170
  303. agno/workflow/steps.py +73 -6
  304. agno/workflow/types.py +96 -21
  305. agno/workflow/workflow.py +2039 -262
  306. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
  307. agno-2.3.13.dist-info/RECORD +613 -0
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -679
  310. agno/tools/memori.py +0 -339
  311. agno-2.1.2.dist-info/RECORD +0 -543
  312. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
  313. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
@@ -16,23 +16,23 @@ class ContentStatus(str, Enum):
16
16
  class ContentStatusResponse(BaseModel):
17
17
  """Response model for content status endpoint."""
18
18
 
19
- status: ContentStatus
20
- status_message: str = ""
19
+ status: ContentStatus = Field(..., description="Current processing status of the content")
20
+ status_message: str = Field("", description="Status message or error details")
21
21
 
22
22
 
23
23
  class ContentResponseSchema(BaseModel):
24
- id: str
25
- name: Optional[str] = None
26
- description: Optional[str] = None
27
- type: Optional[str] = None
28
- size: Optional[str] = None
29
- linked_to: Optional[str] = None
30
- metadata: Optional[dict] = None
31
- access_count: Optional[int] = None
32
- status: Optional[ContentStatus] = None
33
- status_message: Optional[str] = None
34
- created_at: Optional[datetime] = None
35
- updated_at: Optional[datetime] = None
24
+ id: str = Field(..., description="Unique identifier for the content")
25
+ name: Optional[str] = Field(None, description="Name of the content")
26
+ description: Optional[str] = Field(None, description="Description of the content")
27
+ type: Optional[str] = Field(None, description="MIME type of the content")
28
+ size: Optional[str] = Field(None, description="Size of the content in bytes")
29
+ linked_to: Optional[str] = Field(None, description="ID of related content if linked")
30
+ metadata: Optional[dict] = Field(None, description="Additional metadata as key-value pairs")
31
+ access_count: Optional[int] = Field(None, description="Number of times content has been accessed", ge=0)
32
+ status: Optional[ContentStatus] = Field(None, description="Processing status of the content")
33
+ status_message: Optional[str] = Field(None, description="Status message or error details")
34
+ created_at: Optional[datetime] = Field(None, description="Timestamp when content was created")
35
+ updated_at: Optional[datetime] = Field(None, description="Timestamp when content was last updated")
36
36
 
37
37
  @classmethod
38
38
  def from_dict(cls, content: Dict[str, Any]) -> "ContentResponseSchema":
@@ -99,20 +99,80 @@ class ContentUpdateSchema(BaseModel):
99
99
 
100
100
 
101
101
  class ReaderSchema(BaseModel):
102
- id: str
103
- name: Optional[str] = None
104
- description: Optional[str] = None
105
- chunkers: Optional[List[str]] = None
102
+ id: str = Field(..., description="Unique identifier for the reader")
103
+ name: Optional[str] = Field(None, description="Name of the reader")
104
+ description: Optional[str] = Field(None, description="Description of the reader's capabilities")
105
+ chunkers: Optional[List[str]] = Field(None, description="List of supported chunking strategies")
106
106
 
107
107
 
108
108
  class ChunkerSchema(BaseModel):
109
109
  key: str
110
110
  name: Optional[str] = None
111
111
  description: Optional[str] = None
112
+ metadata: Optional[Dict[str, Any]] = None
113
+
114
+
115
+ class VectorDbSchema(BaseModel):
116
+ id: str = Field(..., description="Unique identifier for the vector database")
117
+ name: Optional[str] = Field(None, description="Name of the vector database")
118
+ description: Optional[str] = Field(None, description="Description of the vector database")
119
+ search_types: Optional[List[str]] = Field(
120
+ None, description="List of supported search types (vector, keyword, hybrid)"
121
+ )
122
+
123
+
124
+ class VectorSearchResult(BaseModel):
125
+ """Schema for search result documents."""
126
+
127
+ id: str = Field(..., description="Unique identifier for the search result document")
128
+ content: str = Field(..., description="Content text of the document")
129
+ name: Optional[str] = Field(None, description="Name of the document")
130
+ meta_data: Optional[Dict[str, Any]] = Field(None, description="Metadata associated with the document")
131
+ usage: Optional[Dict[str, Any]] = Field(None, description="Usage statistics (e.g., token counts)")
132
+ reranking_score: Optional[float] = Field(None, description="Reranking score for relevance", ge=0.0, le=1.0)
133
+ content_id: Optional[str] = Field(None, description="ID of the source content")
134
+ content_origin: Optional[str] = Field(None, description="Origin URL or source of the content")
135
+ size: Optional[int] = Field(None, description="Size of the content in bytes", ge=0)
136
+
137
+ @classmethod
138
+ def from_document(cls, document) -> "VectorSearchResult":
139
+ """Convert a Document object to a serializable VectorSearchResult."""
140
+ return cls(
141
+ id=document.id,
142
+ content=document.content,
143
+ name=getattr(document, "name", None),
144
+ meta_data=getattr(document, "meta_data", None),
145
+ usage=getattr(document, "usage", None),
146
+ reranking_score=getattr(document, "reranking_score", None),
147
+ content_id=getattr(document, "content_id", None),
148
+ content_origin=getattr(document, "content_origin", None),
149
+ size=getattr(document, "size", None),
150
+ )
151
+
152
+
153
+ class VectorSearchRequestSchema(BaseModel):
154
+ """Schema for vector search request."""
155
+
156
+ class Meta(BaseModel):
157
+ """Inline metadata schema for pagination."""
158
+
159
+ limit: int = Field(20, description="Number of results per page", ge=1, le=100)
160
+ page: int = Field(1, description="Page number", ge=1)
161
+
162
+ query: str = Field(..., description="The search query text")
163
+ db_id: Optional[str] = Field(None, description="The content database ID to search in")
164
+ vector_db_ids: Optional[List[str]] = Field(None, description="List of vector database IDs to search in")
165
+ search_type: Optional[str] = Field(None, description="The type of search to perform (vector, keyword, hybrid)")
166
+ max_results: Optional[int] = Field(None, description="The maximum number of results to return", ge=1, le=1000)
167
+ filters: Optional[Dict[str, Any]] = Field(None, description="Filters to apply to the search results")
168
+ meta: Optional[Meta] = Field(
169
+ None, description="Pagination metadata. Limit and page number to return a subset of results."
170
+ )
112
171
 
113
172
 
114
173
  class ConfigResponseSchema(BaseModel):
115
- readers: Optional[Dict[str, ReaderSchema]] = None
116
- readersForType: Optional[Dict[str, List[str]]] = None
117
- chunkers: Optional[Dict[str, ChunkerSchema]] = None
118
- filters: Optional[List[str]] = None
174
+ readers: Optional[Dict[str, ReaderSchema]] = Field(None, description="Available content readers")
175
+ readersForType: Optional[Dict[str, List[str]]] = Field(None, description="Mapping of content types to reader IDs")
176
+ chunkers: Optional[Dict[str, ChunkerSchema]] = Field(None, description="Available chunking strategies")
177
+ filters: Optional[List[str]] = Field(None, description="Available filter tags")
178
+ vector_dbs: Optional[List[VectorDbSchema]] = Field(None, description="Configured vector databases")
@@ -1,16 +1,19 @@
1
1
  import logging
2
2
  import math
3
- from typing import List, Optional
3
+ from typing import List, Optional, Union, cast
4
4
  from uuid import uuid4
5
5
 
6
6
  from fastapi import Depends, HTTPException, Path, Query, Request
7
7
  from fastapi.routing import APIRouter
8
8
 
9
- from agno.db.base import BaseDb
9
+ from agno.db.base import AsyncBaseDb, BaseDb
10
10
  from agno.db.schemas import UserMemory
11
+ from agno.models.utils import get_model
11
12
  from agno.os.auth import get_authentication_dependency
12
13
  from agno.os.routers.memory.schemas import (
13
14
  DeleteMemoriesRequest,
15
+ OptimizeMemoriesRequest,
16
+ OptimizeMemoriesResponse,
14
17
  UserMemoryCreateSchema,
15
18
  UserMemorySchema,
16
19
  UserStatsSchema,
@@ -31,7 +34,9 @@ from agno.os.utils import get_db
31
34
  logger = logging.getLogger(__name__)
32
35
 
33
36
 
34
- def get_memory_router(dbs: dict[str, BaseDb], settings: AgnoAPISettings = AgnoAPISettings(), **kwargs) -> APIRouter:
37
+ def get_memory_router(
38
+ dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]], settings: AgnoAPISettings = AgnoAPISettings(), **kwargs
39
+ ) -> APIRouter:
35
40
  """Create memory router with comprehensive OpenAPI documentation for user memory management endpoints."""
36
41
  router = APIRouter(
37
42
  dependencies=[Depends(get_authentication_dependency(settings))],
@@ -47,7 +52,7 @@ def get_memory_router(dbs: dict[str, BaseDb], settings: AgnoAPISettings = AgnoAP
47
52
  return attach_routes(router=router, dbs=dbs)
48
53
 
49
54
 
50
- def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
55
+ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]]) -> APIRouter:
51
56
  @router.post(
52
57
  "/memories",
53
58
  response_model=UserMemorySchema,
@@ -83,6 +88,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
83
88
  request: Request,
84
89
  payload: UserMemoryCreateSchema,
85
90
  db_id: Optional[str] = Query(default=None, description="Database ID to use for memory storage"),
91
+ table: Optional[str] = Query(default=None, description="Table to use for memory storage"),
86
92
  ) -> UserMemorySchema:
87
93
  if hasattr(request.state, "user_id"):
88
94
  user_id = request.state.user_id
@@ -91,16 +97,30 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
91
97
  if payload.user_id is None:
92
98
  raise HTTPException(status_code=400, detail="User ID is required")
93
99
 
94
- db = get_db(dbs, db_id)
95
- user_memory = db.upsert_user_memory(
96
- memory=UserMemory(
97
- memory_id=str(uuid4()),
98
- memory=payload.memory,
99
- topics=payload.topics or [],
100
- user_id=payload.user_id,
101
- ),
102
- deserialize=False,
103
- )
100
+ db = await get_db(dbs, db_id, table)
101
+
102
+ if isinstance(db, AsyncBaseDb):
103
+ db = cast(AsyncBaseDb, db)
104
+ user_memory = await db.upsert_user_memory(
105
+ memory=UserMemory(
106
+ memory_id=str(uuid4()),
107
+ memory=payload.memory,
108
+ topics=payload.topics or [],
109
+ user_id=payload.user_id,
110
+ ),
111
+ deserialize=False,
112
+ )
113
+ else:
114
+ user_memory = db.upsert_user_memory(
115
+ memory=UserMemory(
116
+ memory_id=str(uuid4()),
117
+ memory=payload.memory,
118
+ topics=payload.topics or [],
119
+ user_id=payload.user_id,
120
+ ),
121
+ deserialize=False,
122
+ )
123
+
104
124
  if not user_memory:
105
125
  raise HTTPException(status_code=500, detail="Failed to create memory")
106
126
 
@@ -122,9 +142,14 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
122
142
  memory_id: str = Path(description="Memory ID to delete"),
123
143
  user_id: Optional[str] = Query(default=None, description="User ID to delete memory for"),
124
144
  db_id: Optional[str] = Query(default=None, description="Database ID to use for deletion"),
145
+ table: Optional[str] = Query(default=None, description="Table to use for deletion"),
125
146
  ) -> None:
126
- db = get_db(dbs, db_id)
127
- db.delete_user_memory(memory_id=memory_id, user_id=user_id)
147
+ db = await get_db(dbs, db_id, table)
148
+ if isinstance(db, AsyncBaseDb):
149
+ db = cast(AsyncBaseDb, db)
150
+ await db.delete_user_memory(memory_id=memory_id, user_id=user_id)
151
+ else:
152
+ db.delete_user_memory(memory_id=memory_id, user_id=user_id)
128
153
 
129
154
  @router.delete(
130
155
  "/memories",
@@ -144,9 +169,14 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
144
169
  async def delete_memories(
145
170
  request: DeleteMemoriesRequest,
146
171
  db_id: Optional[str] = Query(default=None, description="Database ID to use for deletion"),
172
+ table: Optional[str] = Query(default=None, description="Table to use for deletion"),
147
173
  ) -> None:
148
- db = get_db(dbs, db_id)
149
- db.delete_user_memories(memory_ids=request.memory_ids, user_id=request.user_id)
174
+ db = await get_db(dbs, db_id, table)
175
+ if isinstance(db, AsyncBaseDb):
176
+ db = cast(AsyncBaseDb, db)
177
+ await db.delete_user_memories(memory_ids=request.memory_ids, user_id=request.user_id)
178
+ else:
179
+ db.delete_user_memories(memory_ids=request.memory_ids, user_id=request.user_id)
150
180
 
151
181
  @router.get(
152
182
  "/memories",
@@ -193,26 +223,44 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
193
223
  sort_by: Optional[str] = Query(default="updated_at", description="Field to sort memories by"),
194
224
  sort_order: Optional[SortOrder] = Query(default="desc", description="Sort order (asc or desc)"),
195
225
  db_id: Optional[str] = Query(default=None, description="Database ID to query memories from"),
226
+ table: Optional[str] = Query(default=None, description="The database table to use"),
196
227
  ) -> PaginatedResponse[UserMemorySchema]:
197
- db = get_db(dbs, db_id)
228
+ db = await get_db(dbs, db_id, table)
198
229
 
199
230
  if hasattr(request.state, "user_id"):
200
231
  user_id = request.state.user_id
201
232
 
202
- user_memories, total_count = db.get_user_memories(
203
- limit=limit,
204
- page=page,
205
- user_id=user_id,
206
- agent_id=agent_id,
207
- team_id=team_id,
208
- topics=topics,
209
- search_content=search_content,
210
- sort_by=sort_by,
211
- sort_order=sort_order,
212
- deserialize=False,
213
- )
233
+ if isinstance(db, AsyncBaseDb):
234
+ db = cast(AsyncBaseDb, db)
235
+ user_memories, total_count = await db.get_user_memories(
236
+ limit=limit,
237
+ page=page,
238
+ user_id=user_id,
239
+ agent_id=agent_id,
240
+ team_id=team_id,
241
+ topics=topics,
242
+ search_content=search_content,
243
+ sort_by=sort_by,
244
+ sort_order=sort_order,
245
+ deserialize=False,
246
+ )
247
+ else:
248
+ user_memories, total_count = db.get_user_memories( # type: ignore
249
+ limit=limit,
250
+ page=page,
251
+ user_id=user_id,
252
+ agent_id=agent_id,
253
+ team_id=team_id,
254
+ topics=topics,
255
+ search_content=search_content,
256
+ sort_by=sort_by,
257
+ sort_order=sort_order,
258
+ deserialize=False,
259
+ )
260
+
261
+ memories = [UserMemorySchema.from_dict(user_memory) for user_memory in user_memories] # type: ignore
214
262
  return PaginatedResponse(
215
- data=[UserMemorySchema.from_dict(user_memory) for user_memory in user_memories], # type: ignore
263
+ data=[memory for memory in memories if memory is not None],
216
264
  meta=PaginationInfo(
217
265
  page=page,
218
266
  limit=limit,
@@ -249,12 +297,22 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
249
297
  },
250
298
  )
251
299
  async def get_memory(
300
+ request: Request,
252
301
  memory_id: str = Path(description="Memory ID to retrieve"),
253
302
  user_id: Optional[str] = Query(default=None, description="User ID to query memory for"),
254
303
  db_id: Optional[str] = Query(default=None, description="Database ID to query memory from"),
304
+ table: Optional[str] = Query(default=None, description="Table to query memory from"),
255
305
  ) -> UserMemorySchema:
256
- db = get_db(dbs, db_id)
257
- user_memory = db.get_user_memory(memory_id=memory_id, user_id=user_id, deserialize=False)
306
+ db = await get_db(dbs, db_id, table)
307
+
308
+ if hasattr(request.state, "user_id"):
309
+ user_id = request.state.user_id
310
+
311
+ if isinstance(db, AsyncBaseDb):
312
+ db = cast(AsyncBaseDb, db)
313
+ user_memory = await db.get_user_memory(memory_id=memory_id, user_id=user_id, deserialize=False)
314
+ else:
315
+ user_memory = db.get_user_memory(memory_id=memory_id, user_id=user_id, deserialize=False)
258
316
  if not user_memory:
259
317
  raise HTTPException(status_code=404, detail=f"Memory with ID {memory_id} not found")
260
318
 
@@ -293,9 +351,14 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
293
351
  )
294
352
  async def get_topics(
295
353
  db_id: Optional[str] = Query(default=None, description="Database ID to query topics from"),
354
+ table: Optional[str] = Query(default=None, description="Table to query topics from"),
296
355
  ) -> List[str]:
297
- db = get_db(dbs, db_id)
298
- return db.get_all_memory_topics()
356
+ db = await get_db(dbs, db_id, table)
357
+ if isinstance(db, AsyncBaseDb):
358
+ db = cast(AsyncBaseDb, db)
359
+ return await db.get_all_memory_topics()
360
+ else:
361
+ return db.get_all_memory_topics()
299
362
 
300
363
  @router.patch(
301
364
  "/memories/{memory_id}",
@@ -335,9 +398,8 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
335
398
  payload: UserMemoryCreateSchema,
336
399
  memory_id: str = Path(description="Memory ID to update"),
337
400
  db_id: Optional[str] = Query(default=None, description="Database ID to use for update"),
401
+ table: Optional[str] = Query(default=None, description="Table to use for update"),
338
402
  ) -> UserMemorySchema:
339
- db = get_db(dbs, db_id)
340
-
341
403
  if hasattr(request.state, "user_id"):
342
404
  user_id = request.state.user_id
343
405
  payload.user_id = user_id
@@ -345,15 +407,29 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
345
407
  if payload.user_id is None:
346
408
  raise HTTPException(status_code=400, detail="User ID is required")
347
409
 
348
- user_memory = db.upsert_user_memory(
349
- memory=UserMemory(
350
- memory_id=memory_id,
351
- memory=payload.memory,
352
- topics=payload.topics or [],
353
- user_id=payload.user_id,
354
- ),
355
- deserialize=False,
356
- )
410
+ db = await get_db(dbs, db_id, table)
411
+
412
+ if isinstance(db, AsyncBaseDb):
413
+ db = cast(AsyncBaseDb, db)
414
+ user_memory = await db.upsert_user_memory(
415
+ memory=UserMemory(
416
+ memory_id=memory_id,
417
+ memory=payload.memory,
418
+ topics=payload.topics or [],
419
+ user_id=payload.user_id,
420
+ ),
421
+ deserialize=False,
422
+ )
423
+ else:
424
+ user_memory = db.upsert_user_memory(
425
+ memory=UserMemory(
426
+ memory_id=memory_id,
427
+ memory=payload.memory,
428
+ topics=payload.topics or [],
429
+ user_id=payload.user_id,
430
+ ),
431
+ deserialize=False,
432
+ )
357
433
  if not user_memory:
358
434
  raise HTTPException(status_code=500, detail="Failed to update memory")
359
435
 
@@ -393,13 +469,24 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
393
469
  limit: Optional[int] = Query(default=20, description="Number of user statistics to return per page"),
394
470
  page: Optional[int] = Query(default=1, description="Page number for pagination"),
395
471
  db_id: Optional[str] = Query(default=None, description="Database ID to query statistics from"),
472
+ table: Optional[str] = Query(default=None, description="Table to query statistics from"),
396
473
  ) -> PaginatedResponse[UserStatsSchema]:
397
- db = get_db(dbs, db_id)
474
+ db = await get_db(dbs, db_id, table)
398
475
  try:
399
- user_stats, total_count = db.get_user_memory_stats(
400
- limit=limit,
401
- page=page,
402
- )
476
+ # Ensure limit and page are integers
477
+ limit = int(limit) if limit is not None else 20
478
+ page = int(page) if page is not None else 1
479
+ if isinstance(db, AsyncBaseDb):
480
+ db = cast(AsyncBaseDb, db)
481
+ user_stats, total_count = await db.get_user_memory_stats(
482
+ limit=limit,
483
+ page=page,
484
+ )
485
+ else:
486
+ user_stats, total_count = db.get_user_memory_stats(
487
+ limit=limit,
488
+ page=page,
489
+ )
403
490
  return PaginatedResponse(
404
491
  data=[UserStatsSchema.from_dict(stats) for stats in user_stats],
405
492
  meta=PaginationInfo(
@@ -413,6 +500,146 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
413
500
  except Exception as e:
414
501
  raise HTTPException(status_code=500, detail=f"Failed to get user statistics: {str(e)}")
415
502
 
503
+ @router.post(
504
+ "/optimize-memories",
505
+ response_model=OptimizeMemoriesResponse,
506
+ status_code=200,
507
+ operation_id="optimize_memories",
508
+ summary="Optimize User Memories",
509
+ description=(
510
+ "Optimize all memories for a given user using the default summarize strategy. "
511
+ "This operation combines all memories into a single comprehensive summary, "
512
+ "achieving maximum token reduction while preserving all key information. "
513
+ "To use a custom model, specify the model parameter in 'provider:model_id' format "
514
+ "(e.g., 'openai:gpt-4o-mini', 'anthropic:claude-3-5-sonnet-20241022'). "
515
+ "If not specified, uses MemoryManager's default model (gpt-4o). "
516
+ "Set apply=false to preview optimization results without saving to database."
517
+ ),
518
+ responses={
519
+ 200: {
520
+ "description": "Memories optimized successfully",
521
+ "content": {
522
+ "application/json": {
523
+ "example": {
524
+ "memories": [
525
+ {
526
+ "memory_id": "f9361a69-2997-40c7-ae4e-a5861d434047",
527
+ "memory": "User has a 3-year-old golden retriever named Max who loves fetch and walks. Lives in San Francisco's Mission district, works as a product manager in tech. Enjoys hiking Bay Area trails, trying new restaurants (especially Japanese, Thai, Mexican), and learning piano for 1.5 years.",
528
+ "topics": ["pets", "location", "work", "hobbies", "food_preferences"],
529
+ "user_id": "user2",
530
+ "updated_at": "2025-11-18T10:30:00Z",
531
+ }
532
+ ],
533
+ "memories_before": 4,
534
+ "memories_after": 1,
535
+ "tokens_before": 450,
536
+ "tokens_after": 180,
537
+ "tokens_saved": 270,
538
+ "reduction_percentage": 60.0,
539
+ }
540
+ }
541
+ },
542
+ },
543
+ 400: {
544
+ "description": "Bad request - User ID is required or invalid model string format",
545
+ "model": BadRequestResponse,
546
+ },
547
+ 404: {"description": "No memories found for user", "model": NotFoundResponse},
548
+ 500: {"description": "Failed to optimize memories", "model": InternalServerErrorResponse},
549
+ },
550
+ )
551
+ async def optimize_memories(
552
+ request: OptimizeMemoriesRequest,
553
+ db_id: Optional[str] = Query(default=None, description="Database ID to use for optimization"),
554
+ table: Optional[str] = Query(default=None, description="Table to use for optimization"),
555
+ ) -> OptimizeMemoriesResponse:
556
+ """Optimize user memories using the default summarize strategy."""
557
+ from agno.memory import MemoryManager
558
+ from agno.memory.strategies.types import MemoryOptimizationStrategyType
559
+
560
+ try:
561
+ # Get database instance
562
+ db = await get_db(dbs, db_id, table)
563
+
564
+ # Create memory manager with optional model
565
+ if request.model:
566
+ try:
567
+ model_instance = get_model(request.model)
568
+ except ValueError as e:
569
+ raise HTTPException(status_code=400, detail=str(e))
570
+ memory_manager = MemoryManager(model=model_instance, db=db)
571
+ else:
572
+ # No model specified - use MemoryManager's default
573
+ memory_manager = MemoryManager(db=db)
574
+
575
+ # Get current memories to count tokens before optimization
576
+ if isinstance(db, AsyncBaseDb):
577
+ memories_before = await memory_manager.aget_user_memories(user_id=request.user_id)
578
+ else:
579
+ memories_before = memory_manager.get_user_memories(user_id=request.user_id)
580
+
581
+ if not memories_before:
582
+ raise HTTPException(status_code=404, detail=f"No memories found for user {request.user_id}")
583
+
584
+ # Count tokens before optimization
585
+ from agno.memory.strategies.summarize import SummarizeStrategy
586
+
587
+ strategy = SummarizeStrategy()
588
+ tokens_before = strategy.count_tokens(memories_before)
589
+ memories_before_count = len(memories_before)
590
+
591
+ # Optimize memories with default SUMMARIZE strategy
592
+ if isinstance(db, AsyncBaseDb):
593
+ optimized_memories = await memory_manager.aoptimize_memories(
594
+ user_id=request.user_id,
595
+ strategy=MemoryOptimizationStrategyType.SUMMARIZE,
596
+ apply=request.apply,
597
+ )
598
+ else:
599
+ optimized_memories = memory_manager.optimize_memories(
600
+ user_id=request.user_id,
601
+ strategy=MemoryOptimizationStrategyType.SUMMARIZE,
602
+ apply=request.apply,
603
+ )
604
+
605
+ # Count tokens after optimization
606
+ tokens_after = strategy.count_tokens(optimized_memories)
607
+ memories_after_count = len(optimized_memories)
608
+
609
+ # Calculate statistics
610
+ tokens_saved = tokens_before - tokens_after
611
+ reduction_percentage = (tokens_saved / tokens_before * 100.0) if tokens_before > 0 else 0.0
612
+
613
+ # Convert to schema objects
614
+ optimized_memory_schemas = [
615
+ UserMemorySchema(
616
+ memory_id=mem.memory_id or "",
617
+ memory=mem.memory or "",
618
+ topics=mem.topics,
619
+ agent_id=mem.agent_id,
620
+ team_id=mem.team_id,
621
+ user_id=mem.user_id,
622
+ updated_at=mem.updated_at,
623
+ )
624
+ for mem in optimized_memories
625
+ ]
626
+
627
+ return OptimizeMemoriesResponse(
628
+ memories=optimized_memory_schemas,
629
+ memories_before=memories_before_count,
630
+ memories_after=memories_after_count,
631
+ tokens_before=tokens_before,
632
+ tokens_after=tokens_after,
633
+ tokens_saved=tokens_saved,
634
+ reduction_percentage=reduction_percentage,
635
+ )
636
+
637
+ except HTTPException:
638
+ raise
639
+ except Exception as e:
640
+ logger.error(f"Failed to optimize memories for user {request.user_id}: {str(e)}")
641
+ raise HTTPException(status_code=500, detail=f"Failed to optimize memories: {str(e)}")
642
+
416
643
  return router
417
644
 
418
645