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
@@ -22,6 +22,7 @@ except ImportError:
22
22
  raise ImportError("The `pinecone` package is not installed, please install using `pip install pinecone`.")
23
23
 
24
24
 
25
+ from agno.filters import FilterExpr
25
26
  from agno.knowledge.document import Document
26
27
  from agno.knowledge.embedder import Embedder
27
28
  from agno.knowledge.reranker.base import Reranker
@@ -66,9 +67,11 @@ class PineconeDb(VectorDb):
66
67
 
67
68
  def __init__(
68
69
  self,
69
- name: str,
70
70
  dimension: int,
71
71
  spec: Union[Dict, ServerlessSpec, PodSpec],
72
+ name: Optional[str] = None,
73
+ description: Optional[str] = None,
74
+ id: Optional[str] = None,
72
75
  embedder: Optional[Embedder] = None,
73
76
  metric: Optional[str] = "cosine",
74
77
  additional_headers: Optional[Dict[str, str]] = None,
@@ -84,6 +87,23 @@ class PineconeDb(VectorDb):
84
87
  reranker: Optional[Reranker] = None,
85
88
  **kwargs,
86
89
  ):
90
+ # Validate required parameters
91
+ if dimension is None or dimension <= 0:
92
+ raise ValueError("Dimension must be provided and greater than 0.")
93
+ if spec is None:
94
+ raise ValueError("Spec must be provided for Pinecone index.")
95
+
96
+ # Dynamic ID generation based on unique identifiers
97
+ if id is None:
98
+ from agno.utils.string import generate_id
99
+
100
+ index_name = name or "default_index"
101
+ seed = f"{host or 'pinecone'}#{index_name}#{dimension}"
102
+ id = generate_id(seed)
103
+
104
+ # Initialize base class with name, description, and generated ID
105
+ super().__init__(id=id, name=name, description=description)
106
+
87
107
  self._client = None
88
108
  self._index = None
89
109
  self.api_key: Optional[str] = api_key
@@ -93,7 +113,6 @@ class PineconeDb(VectorDb):
93
113
  self.pool_threads: Optional[int] = pool_threads
94
114
  self.namespace: Optional[str] = namespace
95
115
  self.index_api: Optional[Any] = index_api
96
- self.name: str = name
97
116
  self.dimension: Optional[int] = dimension
98
117
  self.spec: Union[Dict, ServerlessSpec, PodSpec] = spec
99
118
  self.metric: Optional[str] = metric
@@ -198,23 +217,6 @@ class PineconeDb(VectorDb):
198
217
  log_debug(f"Deleting index: {self.name}")
199
218
  self.client.delete_index(name=self.name, timeout=self.timeout)
200
219
 
201
- def doc_exists(self, document: Document) -> bool:
202
- """Check if a document exists in the index.
203
-
204
- Args:
205
- document (Document): The document to check.
206
-
207
- Returns:
208
- bool: True if the document exists, False otherwise.
209
-
210
- """
211
- response = self.index.fetch(ids=[document.id], namespace=self.namespace)
212
- return len(response.vectors) > 0
213
-
214
- async def async_doc_exists(self, document: Document) -> bool:
215
- """Check if a document exists in the index asynchronously."""
216
- return await asyncio.to_thread(self.doc_exists, document)
217
-
218
220
  def name_exists(self, name: str) -> bool:
219
221
  """Check if an index with the given name exists.
220
222
 
@@ -307,6 +309,8 @@ class PineconeDb(VectorDb):
307
309
  show_progress: bool = False,
308
310
  ) -> None:
309
311
  """Upsert documents into the index asynchronously with batching."""
312
+ if self.content_hash_exists(content_hash):
313
+ await asyncio.to_thread(self._delete_by_content_hash, content_hash)
310
314
  if not documents:
311
315
  return
312
316
 
@@ -320,7 +324,7 @@ class PineconeDb(VectorDb):
320
324
 
321
325
  # Process each batch in parallel
322
326
  async def process_batch(batch_docs):
323
- return await self._prepare_vectors(batch_docs)
327
+ return await self._prepare_vectors(batch_docs, content_hash, filters)
324
328
 
325
329
  # Run all batches in parallel
326
330
  batch_vectors = await asyncio.gather(*[process_batch(batch) for batch in batches])
@@ -335,7 +339,9 @@ class PineconeDb(VectorDb):
335
339
 
336
340
  log_debug(f"Finished async upsert of {len(documents)} documents")
337
341
 
338
- async def _prepare_vectors(self, documents: List[Document]) -> List[Dict[str, Any]]:
342
+ async def _prepare_vectors(
343
+ self, documents: List[Document], content_hash: str, filters: Optional[Dict[str, Any]] = None
344
+ ) -> List[Dict[str, Any]]:
339
345
  """Prepare vectors for upsert."""
340
346
  vectors = []
341
347
 
@@ -382,11 +388,16 @@ class PineconeDb(VectorDb):
382
388
  doc.meta_data["text"] = doc.content
383
389
  # Include name and content_id in metadata
384
390
  metadata = doc.meta_data.copy()
391
+ if filters:
392
+ metadata.update(filters)
393
+
385
394
  if doc.name:
386
395
  metadata["name"] = doc.name
387
396
  if doc.content_id:
388
397
  metadata["content_id"] = doc.content_id
389
398
 
399
+ metadata["content_hash"] = content_hash
400
+
390
401
  data_to_upsert = {
391
402
  "id": doc.id,
392
403
  "values": doc.embedding,
@@ -447,7 +458,7 @@ class PineconeDb(VectorDb):
447
458
  self,
448
459
  query: str,
449
460
  limit: int = 5,
450
- filters: Optional[Dict[str, Union[str, float, int, bool, List, dict]]] = None,
461
+ filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
451
462
  namespace: Optional[str] = None,
452
463
  include_values: Optional[bool] = None,
453
464
  ) -> List[Document]:
@@ -465,6 +476,9 @@ class PineconeDb(VectorDb):
465
476
  List[Document]: The list of matching documents.
466
477
 
467
478
  """
479
+ if isinstance(filters, List):
480
+ log_warning("Filters Expressions are not supported in PineconeDB. No filters will be applied.")
481
+ filters = None
468
482
  dense_embedding = self.embedder.get_embedding(query)
469
483
 
470
484
  if self.use_hybrid_search:
@@ -513,7 +527,7 @@ class PineconeDb(VectorDb):
513
527
  self,
514
528
  query: str,
515
529
  limit: int = 5,
516
- filters: Optional[Dict[str, Union[str, float, int, bool, List, dict]]] = None,
530
+ filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
517
531
  namespace: Optional[str] = None,
518
532
  include_values: Optional[bool] = None,
519
533
  ) -> List[Document]:
@@ -710,3 +724,7 @@ class PineconeDb(VectorDb):
710
724
  except Exception as e:
711
725
  logger.error(f"Error updating metadata for content_id '{content_id}': {e}")
712
726
  raise
727
+
728
+ def get_supported_search_types(self) -> List[str]:
729
+ """Get the supported search types for this vector database."""
730
+ return [] # PineconeDb doesn't use SearchType enum
@@ -1,5 +1,5 @@
1
1
  from hashlib import md5
2
- from typing import Any, Dict, List, Optional
2
+ from typing import Any, Dict, List, Optional, Union
3
3
 
4
4
  try:
5
5
  from qdrant_client import AsyncQdrantClient, QdrantClient # noqa: F401
@@ -9,6 +9,7 @@ except ImportError:
9
9
  "The `qdrant-client` package is not installed. Please install it via `pip install qdrant-client`."
10
10
  )
11
11
 
12
+ from agno.filters import FilterExpr
12
13
  from agno.knowledge.document import Document
13
14
  from agno.knowledge.embedder import Embedder
14
15
  from agno.knowledge.reranker.base import Reranker
@@ -28,6 +29,9 @@ class Qdrant(VectorDb):
28
29
  def __init__(
29
30
  self,
30
31
  collection: str,
32
+ name: Optional[str] = None,
33
+ description: Optional[str] = None,
34
+ id: Optional[str] = None,
31
35
  embedder: Optional[Embedder] = None,
32
36
  distance: Distance = Distance.cosine,
33
37
  location: Optional[str] = None,
@@ -52,6 +56,8 @@ class Qdrant(VectorDb):
52
56
  """
53
57
  Args:
54
58
  collection (str): Name of the Qdrant collection.
59
+ name (Optional[str]): Name of the vector database.
60
+ description (Optional[str]): Description of the vector database.
55
61
  embedder (Optional[Embedder]): Optional embedder for automatic vector generation.
56
62
  distance (Distance): Distance metric to use (default: cosine).
57
63
  location (Optional[str]): `":memory:"` for in-memory, or str used as `url`. If `None`, use default host/port.
@@ -73,6 +79,21 @@ class Qdrant(VectorDb):
73
79
  fastembed_kwargs (Optional[dict]): Keyword args for `fastembed.SparseTextEmbedding.__init__()`.
74
80
  **kwargs: Keyword args for `qdrant_client.QdrantClient.__init__()`.
75
81
  """
82
+ # Validate required parameters
83
+ if not collection:
84
+ raise ValueError("Collection name must be provided.")
85
+
86
+ # Dynamic ID generation based on unique identifiers
87
+ if id is None:
88
+ from agno.utils.string import generate_id
89
+
90
+ host_identifier = host or location or url or "localhost"
91
+ seed = f"{host_identifier}#{collection}"
92
+ id = generate_id(seed)
93
+
94
+ # Initialize base class with name, description, and generated ID
95
+ super().__init__(id=id, name=name, description=description)
96
+
76
97
  # Collection attributes
77
98
  self.collection: str = collection
78
99
 
@@ -238,33 +259,6 @@ class Qdrant(VectorDb):
238
259
  else None,
239
260
  )
240
261
 
241
- def doc_exists(self, document: Document) -> bool:
242
- """
243
- Validating if the document exists or not
244
-
245
- Args:
246
- document (Document): Document to validate
247
- """
248
- if self.client:
249
- cleaned_content = document.content.replace("\x00", "\ufffd")
250
- doc_id = md5(cleaned_content.encode()).hexdigest()
251
- collection_points = self.client.retrieve(
252
- collection_name=self.collection,
253
- ids=[doc_id],
254
- )
255
- return len(collection_points) > 0
256
- return False
257
-
258
- async def async_doc_exists(self, document: Document) -> bool:
259
- """Check if a document exists asynchronously."""
260
- cleaned_content = document.content.replace("\x00", "\ufffd")
261
- doc_id = md5(cleaned_content.encode()).hexdigest()
262
- collection_points = await self.async_client.retrieve(
263
- collection_name=self.collection,
264
- ids=[doc_id],
265
- )
266
- return len(collection_points) > 0
267
-
268
262
  def name_exists(self, name: str) -> bool:
269
263
  """
270
264
  Validates if a document with the given name exists in the collection.
@@ -326,7 +320,9 @@ class Qdrant(VectorDb):
326
320
  points = []
327
321
  for document in documents:
328
322
  cleaned_content = document.content.replace("\x00", "\ufffd")
329
- doc_id = md5(cleaned_content.encode()).hexdigest()
323
+ # Include content_hash in ID to ensure uniqueness across different content hashes
324
+ base_id = document.id or md5(cleaned_content.encode()).hexdigest()
325
+ doc_id = md5(f"{base_id}_{content_hash}".encode()).hexdigest()
330
326
 
331
327
  # TODO(v2.0.0): Remove conditional vector naming logic
332
328
  if self.use_named_vectors:
@@ -436,7 +432,9 @@ class Qdrant(VectorDb):
436
432
 
437
433
  async def process_document(document):
438
434
  cleaned_content = document.content.replace("\x00", "\ufffd")
439
- doc_id = md5(cleaned_content.encode()).hexdigest()
435
+ # Include content_hash in ID to ensure uniqueness across different content hashes
436
+ base_id = document.id or md5(cleaned_content.encode()).hexdigest()
437
+ doc_id = md5(f"{base_id}_{content_hash}".encode()).hexdigest()
440
438
 
441
439
  if self.search_type == SearchType.vector:
442
440
  # For vector search, maintain backward compatibility with unnamed vectors
@@ -508,7 +506,9 @@ class Qdrant(VectorDb):
508
506
  log_debug("Redirecting the async request to async_insert")
509
507
  await self.async_insert(content_hash=content_hash, documents=documents, filters=filters)
510
508
 
511
- def search(self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None) -> List[Document]:
509
+ def search(
510
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
511
+ ) -> List[Document]:
512
512
  """
513
513
  Search for documents in the collection.
514
514
 
@@ -517,28 +517,37 @@ class Qdrant(VectorDb):
517
517
  limit (int): Number of search results to return
518
518
  filters (Optional[Dict[str, Any]]): Filters to apply while searching
519
519
  """
520
- filters = self._format_filters(filters or {}) # type: ignore
520
+
521
+ if isinstance(filters, List):
522
+ log_warning("Filters Expressions are not supported in Qdrant. No filters will be applied.")
523
+ filters = None
524
+
525
+ formatted_filters = self._format_filters(filters or {}) # type: ignore
521
526
  if self.search_type == SearchType.vector:
522
- results = self._run_vector_search_sync(query, limit, filters)
527
+ results = self._run_vector_search_sync(query, limit, formatted_filters=formatted_filters) # type: ignore
523
528
  elif self.search_type == SearchType.keyword:
524
- results = self._run_keyword_search_sync(query, limit, filters)
529
+ results = self._run_keyword_search_sync(query, limit, formatted_filters=formatted_filters) # type: ignore
525
530
  elif self.search_type == SearchType.hybrid:
526
- results = self._run_hybrid_search_sync(query, limit, filters)
531
+ results = self._run_hybrid_search_sync(query, limit, formatted_filters=formatted_filters) # type: ignore
527
532
  else:
528
533
  raise ValueError(f"Unsupported search type: {self.search_type}")
529
534
 
530
535
  return self._build_search_results(results, query)
531
536
 
532
537
  async def async_search(
533
- self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
538
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
534
539
  ) -> List[Document]:
535
- filters = self._format_filters(filters or {}) # type: ignore
540
+ if isinstance(filters, List):
541
+ log_warning("Filters Expressions are not supported in Qdrant. No filters will be applied.")
542
+ filters = None
543
+
544
+ formatted_filters = self._format_filters(filters or {}) # type: ignore
536
545
  if self.search_type == SearchType.vector:
537
- results = await self._run_vector_search_async(query, limit, filters)
546
+ results = await self._run_vector_search_async(query, limit, formatted_filters=formatted_filters) # type: ignore
538
547
  elif self.search_type == SearchType.keyword:
539
- results = await self._run_keyword_search_async(query, limit, filters)
548
+ results = await self._run_keyword_search_async(query, limit, formatted_filters=formatted_filters) # type: ignore
540
549
  elif self.search_type == SearchType.hybrid:
541
- results = await self._run_hybrid_search_async(query, limit, filters)
550
+ results = await self._run_hybrid_search_async(query, limit, formatted_filters=formatted_filters) # type: ignore
542
551
  else:
543
552
  raise ValueError(f"Unsupported search type: {self.search_type}")
544
553
 
@@ -548,7 +557,7 @@ class Qdrant(VectorDb):
548
557
  self,
549
558
  query: str,
550
559
  limit: int,
551
- filters: Optional[Dict[str, Any]],
560
+ formatted_filters: Optional[models.Filter],
552
561
  ) -> List[models.ScoredPoint]:
553
562
  dense_embedding = self.embedder.get_embedding(query)
554
563
  sparse_embedding = next(iter(self.sparse_encoder.embed([query]))).as_object()
@@ -566,7 +575,7 @@ class Qdrant(VectorDb):
566
575
  with_vectors=True,
567
576
  with_payload=True,
568
577
  limit=limit,
569
- query_filter=filters,
578
+ query_filter=formatted_filters,
570
579
  )
571
580
  return call.points
572
581
 
@@ -574,7 +583,7 @@ class Qdrant(VectorDb):
574
583
  self,
575
584
  query: str,
576
585
  limit: int,
577
- filters: Optional[Dict[str, Any]],
586
+ formatted_filters: Optional[models.Filter],
578
587
  ) -> List[models.ScoredPoint]:
579
588
  dense_embedding = self.embedder.get_embedding(query)
580
589
 
@@ -586,7 +595,7 @@ class Qdrant(VectorDb):
586
595
  with_vectors=True,
587
596
  with_payload=True,
588
597
  limit=limit,
589
- query_filter=filters,
598
+ query_filter=formatted_filters,
590
599
  using=self.dense_vector_name,
591
600
  )
592
601
  else:
@@ -597,7 +606,7 @@ class Qdrant(VectorDb):
597
606
  with_vectors=True,
598
607
  with_payload=True,
599
608
  limit=limit,
600
- query_filter=filters,
609
+ query_filter=formatted_filters,
601
610
  )
602
611
  return call.points
603
612
 
@@ -605,7 +614,7 @@ class Qdrant(VectorDb):
605
614
  self,
606
615
  query: str,
607
616
  limit: int,
608
- filters: Optional[Dict[str, Any]],
617
+ formatted_filters: Optional[models.Filter],
609
618
  ) -> List[models.ScoredPoint]:
610
619
  sparse_embedding = next(iter(self.sparse_encoder.embed([query]))).as_object()
611
620
  call = self.client.query_points(
@@ -615,7 +624,7 @@ class Qdrant(VectorDb):
615
624
  with_payload=True,
616
625
  limit=limit,
617
626
  using=self.sparse_vector_name,
618
- query_filter=filters,
627
+ query_filter=formatted_filters,
619
628
  )
620
629
  return call.points
621
630
 
@@ -623,7 +632,7 @@ class Qdrant(VectorDb):
623
632
  self,
624
633
  query: str,
625
634
  limit: int,
626
- filters: Optional[Dict[str, Any]],
635
+ formatted_filters: Optional[models.Filter],
627
636
  ) -> List[models.ScoredPoint]:
628
637
  dense_embedding = self.embedder.get_embedding(query)
629
638
 
@@ -635,7 +644,7 @@ class Qdrant(VectorDb):
635
644
  with_vectors=True,
636
645
  with_payload=True,
637
646
  limit=limit,
638
- query_filter=filters,
647
+ query_filter=formatted_filters,
639
648
  using=self.dense_vector_name,
640
649
  )
641
650
  else:
@@ -646,7 +655,7 @@ class Qdrant(VectorDb):
646
655
  with_vectors=True,
647
656
  with_payload=True,
648
657
  limit=limit,
649
- query_filter=filters,
658
+ query_filter=formatted_filters,
650
659
  )
651
660
  return call.points
652
661
 
@@ -654,7 +663,7 @@ class Qdrant(VectorDb):
654
663
  self,
655
664
  query: str,
656
665
  limit: int,
657
- filters: Optional[Dict[str, Any]],
666
+ formatted_filters: Optional[models.Filter],
658
667
  ) -> List[models.ScoredPoint]:
659
668
  sparse_embedding = next(iter(self.sparse_encoder.embed([query]))).as_object()
660
669
  call = await self.async_client.query_points(
@@ -664,7 +673,7 @@ class Qdrant(VectorDb):
664
673
  with_payload=True,
665
674
  limit=limit,
666
675
  using=self.sparse_vector_name,
667
- query_filter=filters,
676
+ query_filter=formatted_filters,
668
677
  )
669
678
  return call.points
670
679
 
@@ -672,7 +681,7 @@ class Qdrant(VectorDb):
672
681
  self,
673
682
  query: str,
674
683
  limit: int,
675
- filters: Optional[Dict[str, Any]],
684
+ formatted_filters: Optional[models.Filter],
676
685
  ) -> List[models.ScoredPoint]:
677
686
  dense_embedding = self.embedder.get_embedding(query)
678
687
  sparse_embedding = next(iter(self.sparse_encoder.embed([query]))).as_object()
@@ -690,7 +699,7 @@ class Qdrant(VectorDb):
690
699
  with_vectors=True,
691
700
  with_payload=True,
692
701
  limit=limit,
693
- query_filter=filters,
702
+ query_filter=formatted_filters,
694
703
  )
695
704
  return call.points
696
705
 
@@ -1096,3 +1105,7 @@ class Qdrant(VectorDb):
1096
1105
  log_debug(f"Error closing async Qdrant client: {e}")
1097
1106
  finally:
1098
1107
  self._async_client = None
1108
+
1109
+ def get_supported_search_types(self) -> List[str]:
1110
+ """Get the supported search types for this vector database."""
1111
+ return [SearchType.vector, SearchType.keyword, SearchType.hybrid]
@@ -0,0 +1,9 @@
1
+ from agno.vectordb.redis.redisdb import RedisDB
2
+
3
+ # Backward compatibility alias
4
+ RedisVectorDb = RedisDB
5
+
6
+ __all__ = [
7
+ "RedisVectorDb",
8
+ "RedisDB",
9
+ ]