agno 2.0.0rc2__py3-none-any.whl → 2.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. agno/agent/agent.py +6009 -2874
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +595 -187
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +3 -0
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +339 -266
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +1011 -566
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +110 -37
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +143 -4
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +60 -6
  142. agno/models/openai/chat.py +102 -43
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +81 -5
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -175
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +266 -112
  205. agno/run/base.py +53 -24
  206. agno/run/team.py +252 -111
  207. agno/run/workflow.py +156 -45
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1692
  213. agno/tools/brightdata.py +3 -3
  214. agno/tools/cartesia.py +3 -5
  215. agno/tools/dalle.py +9 -8
  216. agno/tools/decorator.py +4 -2
  217. agno/tools/desi_vocal.py +2 -2
  218. agno/tools/duckduckgo.py +15 -11
  219. agno/tools/e2b.py +20 -13
  220. agno/tools/eleven_labs.py +26 -28
  221. agno/tools/exa.py +21 -16
  222. agno/tools/fal.py +4 -4
  223. agno/tools/file.py +153 -23
  224. agno/tools/file_generation.py +350 -0
  225. agno/tools/firecrawl.py +4 -4
  226. agno/tools/function.py +257 -37
  227. agno/tools/giphy.py +2 -2
  228. agno/tools/gmail.py +238 -14
  229. agno/tools/google_drive.py +270 -0
  230. agno/tools/googlecalendar.py +36 -8
  231. agno/tools/googlesheets.py +20 -5
  232. agno/tools/jira.py +20 -0
  233. agno/tools/knowledge.py +3 -3
  234. agno/tools/lumalab.py +3 -3
  235. agno/tools/mcp/__init__.py +10 -0
  236. agno/tools/mcp/mcp.py +331 -0
  237. agno/tools/mcp/multi_mcp.py +347 -0
  238. agno/tools/mcp/params.py +24 -0
  239. agno/tools/mcp_toolbox.py +284 -0
  240. agno/tools/mem0.py +11 -17
  241. agno/tools/memori.py +1 -53
  242. agno/tools/memory.py +419 -0
  243. agno/tools/models/azure_openai.py +2 -2
  244. agno/tools/models/gemini.py +3 -3
  245. agno/tools/models/groq.py +3 -5
  246. agno/tools/models/nebius.py +7 -7
  247. agno/tools/models_labs.py +25 -15
  248. agno/tools/notion.py +204 -0
  249. agno/tools/openai.py +4 -9
  250. agno/tools/opencv.py +3 -3
  251. agno/tools/parallel.py +314 -0
  252. agno/tools/replicate.py +7 -7
  253. agno/tools/scrapegraph.py +58 -31
  254. agno/tools/searxng.py +2 -2
  255. agno/tools/serper.py +2 -2
  256. agno/tools/slack.py +18 -3
  257. agno/tools/spider.py +2 -2
  258. agno/tools/tavily.py +146 -0
  259. agno/tools/whatsapp.py +1 -1
  260. agno/tools/workflow.py +278 -0
  261. agno/tools/yfinance.py +12 -11
  262. agno/utils/agent.py +820 -0
  263. agno/utils/audio.py +27 -0
  264. agno/utils/common.py +90 -1
  265. agno/utils/events.py +222 -7
  266. agno/utils/gemini.py +181 -23
  267. agno/utils/hooks.py +57 -0
  268. agno/utils/http.py +111 -0
  269. agno/utils/knowledge.py +12 -5
  270. agno/utils/log.py +1 -0
  271. agno/utils/mcp.py +95 -5
  272. agno/utils/media.py +188 -10
  273. agno/utils/merge_dict.py +22 -1
  274. agno/utils/message.py +60 -0
  275. agno/utils/models/claude.py +40 -11
  276. agno/utils/models/cohere.py +1 -1
  277. agno/utils/models/watsonx.py +1 -1
  278. agno/utils/openai.py +1 -1
  279. agno/utils/print_response/agent.py +105 -21
  280. agno/utils/print_response/team.py +103 -38
  281. agno/utils/print_response/workflow.py +251 -34
  282. agno/utils/reasoning.py +22 -1
  283. agno/utils/serialize.py +32 -0
  284. agno/utils/streamlit.py +16 -10
  285. agno/utils/string.py +41 -0
  286. agno/utils/team.py +98 -9
  287. agno/utils/tools.py +1 -1
  288. agno/vectordb/base.py +23 -4
  289. agno/vectordb/cassandra/cassandra.py +65 -9
  290. agno/vectordb/chroma/chromadb.py +182 -38
  291. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  292. agno/vectordb/couchbase/couchbase.py +105 -10
  293. agno/vectordb/lancedb/lance_db.py +183 -135
  294. agno/vectordb/langchaindb/langchaindb.py +25 -7
  295. agno/vectordb/lightrag/lightrag.py +17 -3
  296. agno/vectordb/llamaindex/__init__.py +3 -0
  297. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  298. agno/vectordb/milvus/milvus.py +126 -9
  299. agno/vectordb/mongodb/__init__.py +7 -1
  300. agno/vectordb/mongodb/mongodb.py +112 -7
  301. agno/vectordb/pgvector/pgvector.py +142 -21
  302. agno/vectordb/pineconedb/pineconedb.py +80 -8
  303. agno/vectordb/qdrant/qdrant.py +125 -39
  304. agno/vectordb/redis/__init__.py +9 -0
  305. agno/vectordb/redis/redisdb.py +694 -0
  306. agno/vectordb/singlestore/singlestore.py +111 -25
  307. agno/vectordb/surrealdb/surrealdb.py +31 -5
  308. agno/vectordb/upstashdb/upstashdb.py +76 -8
  309. agno/vectordb/weaviate/weaviate.py +86 -15
  310. agno/workflow/__init__.py +2 -0
  311. agno/workflow/agent.py +299 -0
  312. agno/workflow/condition.py +112 -18
  313. agno/workflow/loop.py +69 -10
  314. agno/workflow/parallel.py +266 -118
  315. agno/workflow/router.py +110 -17
  316. agno/workflow/step.py +645 -136
  317. agno/workflow/steps.py +65 -6
  318. agno/workflow/types.py +71 -33
  319. agno/workflow/workflow.py +2113 -300
  320. agno-2.3.0.dist-info/METADATA +618 -0
  321. agno-2.3.0.dist-info/RECORD +577 -0
  322. agno-2.3.0.dist-info/licenses/LICENSE +201 -0
  323. agno/knowledge/reader/url_reader.py +0 -128
  324. agno/tools/googlesearch.py +0 -98
  325. agno/tools/mcp.py +0 -610
  326. agno/utils/models/aws_claude.py +0 -170
  327. agno-2.0.0rc2.dist-info/METADATA +0 -355
  328. agno-2.0.0rc2.dist-info/RECORD +0 -515
  329. agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
  330. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  331. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,10 @@
1
1
  import asyncio
2
2
  import time
3
- from typing import Any, Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional, Union
4
4
 
5
5
  from bson import ObjectId
6
6
 
7
+ from agno.filters import FilterExpr
7
8
  from agno.knowledge.document import Document
8
9
  from agno.knowledge.embedder import Embedder
9
10
  from agno.utils.log import log_debug, log_info, log_warning, logger
@@ -33,6 +34,9 @@ class MongoDb(VectorDb):
33
34
  def __init__(
34
35
  self,
35
36
  collection_name: str,
37
+ name: Optional[str] = None,
38
+ description: Optional[str] = None,
39
+ id: Optional[str] = None,
36
40
  db_url: Optional[str] = "mongodb://localhost:27017/",
37
41
  database: str = "agno",
38
42
  embedder: Optional[Embedder] = None,
@@ -56,6 +60,8 @@ class MongoDb(VectorDb):
56
60
 
57
61
  Args:
58
62
  collection_name (str): Name of the MongoDB collection.
63
+ name (Optional[str]): Name of the vector database.
64
+ description (Optional[str]): Description of the vector database.
59
65
  db_url (Optional[str]): MongoDB connection string.
60
66
  database (str): Database name.
61
67
  embedder (Embedder): Embedder instance for generating embeddings.
@@ -74,11 +80,24 @@ class MongoDb(VectorDb):
74
80
  hybrid_rank_constant (int): Default rank constant (k) for Reciprocal Rank Fusion in hybrid search. This constant is added to the rank before taking the reciprocal, helping to smooth scores. A common value is 60.
75
81
  **kwargs: Additional arguments for MongoClient.
76
82
  """
83
+ # Validate required parameters
77
84
  if not collection_name:
78
85
  raise ValueError("Collection name must not be empty.")
79
86
  if not database:
80
87
  raise ValueError("Database name must not be empty.")
88
+
89
+ # Dynamic ID generation based on unique identifiers
90
+ if id is None:
91
+ from agno.utils.string import generate_id
92
+
93
+ connection_identifier = db_url or "mongodb://localhost:27017/"
94
+ seed = f"{connection_identifier}#{database}#{collection_name}"
95
+ id = generate_id(seed)
96
+
81
97
  self.collection_name = collection_name
98
+ # Initialize base class with name, description, and generated ID
99
+ super().__init__(id=id, name=name, description=description)
100
+
82
101
  self.database = database
83
102
  self.search_index_name = search_index_name
84
103
  self.cosmos_compatibility = cosmos_compatibility
@@ -567,9 +586,16 @@ class MongoDb(VectorDb):
567
586
  return True
568
587
 
569
588
  def search(
570
- self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None, min_score: float = 0.0
589
+ self,
590
+ query: str,
591
+ limit: int = 5,
592
+ filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
593
+ min_score: float = 0.0,
571
594
  ) -> List[Document]:
572
595
  """Search for documents using vector similarity."""
596
+ if isinstance(filters, List):
597
+ log_warning("Filters Expressions are not supported in MongoDB. No filters will be applied.")
598
+ filters = None
573
599
  if self.search_type == SearchType.hybrid:
574
600
  return self.hybrid_search(query, limit=limit, filters=filters)
575
601
 
@@ -1018,8 +1044,44 @@ class MongoDb(VectorDb):
1018
1044
  log_debug(f"Inserting {len(documents)} documents asynchronously")
1019
1045
  collection = await self._get_async_collection()
1020
1046
 
1021
- embed_tasks = [document.async_embed(embedder=self.embedder) for document in documents]
1022
- await asyncio.gather(*embed_tasks, return_exceptions=True)
1047
+ if self.embedder.enable_batch and hasattr(self.embedder, "async_get_embeddings_batch_and_usage"):
1048
+ # Use batch embedding when enabled and supported
1049
+ try:
1050
+ # Extract content from all documents
1051
+ doc_contents = [doc.content for doc in documents]
1052
+
1053
+ # Get batch embeddings and usage
1054
+ embeddings, usages = await self.embedder.async_get_embeddings_batch_and_usage(doc_contents)
1055
+
1056
+ # Process documents with pre-computed embeddings
1057
+ for j, doc in enumerate(documents):
1058
+ try:
1059
+ if j < len(embeddings):
1060
+ doc.embedding = embeddings[j]
1061
+ doc.usage = usages[j] if j < len(usages) else None
1062
+ except Exception as e:
1063
+ logger.error(f"Error assigning batch embedding to document '{doc.name}': {e}")
1064
+
1065
+ except Exception as e:
1066
+ # Check if this is a rate limit error - don't fall back as it would make things worse
1067
+ error_str = str(e).lower()
1068
+ is_rate_limit = any(
1069
+ phrase in error_str
1070
+ for phrase in ["rate limit", "too many requests", "429", "trial key", "api calls / minute"]
1071
+ )
1072
+
1073
+ if is_rate_limit:
1074
+ logger.error(f"Rate limit detected during batch embedding. {e}")
1075
+ raise e
1076
+ else:
1077
+ logger.warning(f"Async batch embedding failed, falling back to individual embeddings: {e}")
1078
+ # Fall back to individual embedding
1079
+ embed_tasks = [doc.async_embed(embedder=self.embedder) for doc in documents]
1080
+ await asyncio.gather(*embed_tasks, return_exceptions=True)
1081
+ else:
1082
+ # Use individual embedding
1083
+ embed_tasks = [document.async_embed(embedder=self.embedder) for document in documents]
1084
+ await asyncio.gather(*embed_tasks, return_exceptions=True)
1023
1085
 
1024
1086
  prepared_docs = []
1025
1087
  for document in documents:
@@ -1047,8 +1109,44 @@ class MongoDb(VectorDb):
1047
1109
  log_info(f"Upserting {len(documents)} documents asynchronously")
1048
1110
  collection = await self._get_async_collection()
1049
1111
 
1050
- embed_tasks = [document.async_embed(embedder=self.embedder) for document in documents]
1051
- await asyncio.gather(*embed_tasks, return_exceptions=True)
1112
+ if self.embedder.enable_batch and hasattr(self.embedder, "async_get_embeddings_batch_and_usage"):
1113
+ # Use batch embedding when enabled and supported
1114
+ try:
1115
+ # Extract content from all documents
1116
+ doc_contents = [doc.content for doc in documents]
1117
+
1118
+ # Get batch embeddings and usage
1119
+ embeddings, usages = await self.embedder.async_get_embeddings_batch_and_usage(doc_contents)
1120
+
1121
+ # Process documents with pre-computed embeddings
1122
+ for j, doc in enumerate(documents):
1123
+ try:
1124
+ if j < len(embeddings):
1125
+ doc.embedding = embeddings[j]
1126
+ doc.usage = usages[j] if j < len(usages) else None
1127
+ except Exception as e:
1128
+ logger.error(f"Error assigning batch embedding to document '{doc.name}': {e}")
1129
+
1130
+ except Exception as e:
1131
+ # Check if this is a rate limit error - don't fall back as it would make things worse
1132
+ error_str = str(e).lower()
1133
+ is_rate_limit = any(
1134
+ phrase in error_str
1135
+ for phrase in ["rate limit", "too many requests", "429", "trial key", "api calls / minute"]
1136
+ )
1137
+
1138
+ if is_rate_limit:
1139
+ logger.error(f"Rate limit detected during batch embedding. {e}")
1140
+ raise e
1141
+ else:
1142
+ logger.warning(f"Async batch embedding failed, falling back to individual embeddings: {e}")
1143
+ # Fall back to individual embedding
1144
+ embed_tasks = [doc.async_embed(embedder=self.embedder) for doc in documents]
1145
+ await asyncio.gather(*embed_tasks, return_exceptions=True)
1146
+ else:
1147
+ # Use individual embedding
1148
+ embed_tasks = [document.async_embed(embedder=self.embedder) for document in documents]
1149
+ await asyncio.gather(*embed_tasks, return_exceptions=True)
1052
1150
 
1053
1151
  for document in documents:
1054
1152
  try:
@@ -1063,9 +1161,12 @@ class MongoDb(VectorDb):
1063
1161
  logger.error(f"Error upserting document '{document.name}' asynchronously: {e}")
1064
1162
 
1065
1163
  async def async_search(
1066
- self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
1164
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
1067
1165
  ) -> List[Document]:
1068
1166
  """Search for documents asynchronously."""
1167
+ if isinstance(filters, List):
1168
+ log_warning("Filters Expressions are not supported in MongoDB. No filters will be applied.")
1169
+ filters = None
1069
1170
  query_embedding = self.embedder.get_embedding(query)
1070
1171
  if query_embedding is None:
1071
1172
  logger.error(f"Failed to generate embedding for query: {query}")
@@ -1310,3 +1411,7 @@ class MongoDb(VectorDb):
1310
1411
  except Exception as e:
1311
1412
  logger.error(f"Error updating metadata for content_id '{content_id}': {e}")
1312
1413
  raise
1414
+
1415
+ def get_supported_search_types(self) -> List[str]:
1416
+ """Get the supported search types for this vector database."""
1417
+ return [SearchType.vector, SearchType.hybrid]
@@ -3,15 +3,18 @@ from hashlib import md5
3
3
  from math import sqrt
4
4
  from typing import Any, Dict, List, Optional, Union, cast
5
5
 
6
+ from agno.utils.string import generate_id
7
+
6
8
  try:
7
- from sqlalchemy import update
9
+ from sqlalchemy import and_, not_, or_, update
8
10
  from sqlalchemy.dialects import postgresql
9
11
  from sqlalchemy.engine import Engine, create_engine
10
12
  from sqlalchemy.inspection import inspect
11
13
  from sqlalchemy.orm import Session, scoped_session, sessionmaker
12
14
  from sqlalchemy.schema import Column, Index, MetaData, Table
15
+ from sqlalchemy.sql.elements import ColumnElement
13
16
  from sqlalchemy.sql.expression import bindparam, desc, func, select, text
14
- from sqlalchemy.types import DateTime, String
17
+ from sqlalchemy.types import DateTime, Integer, String
15
18
 
16
19
  except ImportError:
17
20
  raise ImportError("`sqlalchemy` not installed. Please install using `pip install sqlalchemy psycopg`")
@@ -21,6 +24,7 @@ try:
21
24
  except ImportError:
22
25
  raise ImportError("`pgvector` not installed. Please install using `pip install pgvector`")
23
26
 
27
+ from agno.filters import FilterExpr
24
28
  from agno.knowledge.document import Document
25
29
  from agno.knowledge.embedder import Embedder
26
30
  from agno.knowledge.reranker.base import Reranker
@@ -43,6 +47,9 @@ class PgVector(VectorDb):
43
47
  self,
44
48
  table_name: str,
45
49
  schema: str = "ai",
50
+ name: Optional[str] = None,
51
+ description: Optional[str] = None,
52
+ id: Optional[str] = None,
46
53
  db_url: Optional[str] = None,
47
54
  db_engine: Optional[Engine] = None,
48
55
  embedder: Optional[Embedder] = None,
@@ -55,7 +62,6 @@ class PgVector(VectorDb):
55
62
  schema_version: int = 1,
56
63
  auto_upgrade_schema: bool = False,
57
64
  reranker: Optional[Reranker] = None,
58
- use_batch: bool = False,
59
65
  ):
60
66
  """
61
67
  Initialize the PgVector instance.
@@ -63,6 +69,8 @@ class PgVector(VectorDb):
63
69
  Args:
64
70
  table_name (str): Name of the table to store vector data.
65
71
  schema (str): Database schema name.
72
+ name (Optional[str]): Name of the vector database.
73
+ description (Optional[str]): Description of the vector database.
66
74
  db_url (Optional[str]): Database connection URL.
67
75
  db_engine (Optional[Engine]): SQLAlchemy database engine.
68
76
  embedder (Optional[Embedder]): Embedder instance for creating embeddings.
@@ -81,6 +89,15 @@ class PgVector(VectorDb):
81
89
  if db_engine is None and db_url is None:
82
90
  raise ValueError("Either 'db_url' or 'db_engine' must be provided.")
83
91
 
92
+ if id is None:
93
+ base_seed = db_url or str(db_engine.url) # type: ignore
94
+ schema_suffix = table_name if table_name is not None else "ai"
95
+ seed = f"{base_seed}#{schema_suffix}"
96
+ id = generate_id(seed)
97
+
98
+ # Initialize base class with name and description
99
+ super().__init__(id=id, name=name, description=description)
100
+
84
101
  if db_engine is None:
85
102
  if db_url is None:
86
103
  raise ValueError("Must provide 'db_url' if 'db_engine' is None.")
@@ -96,7 +113,6 @@ class PgVector(VectorDb):
96
113
  self.db_url: Optional[str] = db_url
97
114
  self.db_engine: Engine = db_engine
98
115
  self.metadata: MetaData = MetaData(schema=self.schema)
99
- self.use_batch: bool = use_batch
100
116
 
101
117
  # Embedder for embedding the document contents
102
118
  if embedder is None:
@@ -337,8 +353,8 @@ class PgVector(VectorDb):
337
353
  batch_docs = documents[i : i + batch_size]
338
354
  log_debug(f"Processing batch starting at index {i}, size: {len(batch_docs)}")
339
355
  try:
340
- embed_tasks = [doc.async_embed(embedder=self.embedder) for doc in batch_docs]
341
- await asyncio.gather(*embed_tasks, return_exceptions=True)
356
+ # Embed all documents in the batch
357
+ await self._async_embed_documents(batch_docs)
342
358
 
343
359
  # Prepare documents for insertion
344
360
  batch_records = []
@@ -493,6 +509,52 @@ class PgVector(VectorDb):
493
509
  "content_id": doc.content_id,
494
510
  }
495
511
 
512
+ async def _async_embed_documents(self, batch_docs: List[Document]) -> None:
513
+ """
514
+ Embed a batch of documents using either batch embedding or individual embedding.
515
+
516
+ Args:
517
+ batch_docs: List of documents to embed
518
+ """
519
+ if self.embedder.enable_batch and hasattr(self.embedder, "async_get_embeddings_batch_and_usage"):
520
+ # Use batch embedding when enabled and supported
521
+ try:
522
+ # Extract content from all documents
523
+ doc_contents = [doc.content for doc in batch_docs]
524
+
525
+ # Get batch embeddings and usage
526
+ embeddings, usages = await self.embedder.async_get_embeddings_batch_and_usage(doc_contents)
527
+
528
+ # Process documents with pre-computed embeddings
529
+ for j, doc in enumerate(batch_docs):
530
+ try:
531
+ if j < len(embeddings):
532
+ doc.embedding = embeddings[j]
533
+ doc.usage = usages[j] if j < len(usages) else None
534
+ except Exception as e:
535
+ logger.error(f"Error assigning batch embedding to document '{doc.name}': {e}")
536
+
537
+ except Exception as e:
538
+ # Check if this is a rate limit error - don't fall back as it would make things worse
539
+ error_str = str(e).lower()
540
+ is_rate_limit = any(
541
+ phrase in error_str
542
+ for phrase in ["rate limit", "too many requests", "429", "trial key", "api calls / minute"]
543
+ )
544
+
545
+ if is_rate_limit:
546
+ logger.error(f"Rate limit detected during batch embedding. {e}")
547
+ raise e
548
+ else:
549
+ logger.warning(f"Async batch embedding failed, falling back to individual embeddings: {e}")
550
+ # Fall back to individual embedding
551
+ embed_tasks = [doc.async_embed(embedder=self.embedder) for doc in batch_docs]
552
+ await asyncio.gather(*embed_tasks, return_exceptions=True)
553
+ else:
554
+ # Use individual embedding
555
+ embed_tasks = [doc.async_embed(embedder=self.embedder) for doc in batch_docs]
556
+ await asyncio.gather(*embed_tasks, return_exceptions=True)
557
+
496
558
  async def async_upsert(
497
559
  self,
498
560
  content_hash: str,
@@ -530,8 +592,8 @@ class PgVector(VectorDb):
530
592
  batch_docs = documents[i : i + batch_size]
531
593
  log_info(f"Processing batch starting at index {i}, size: {len(batch_docs)}")
532
594
  try:
533
- embed_tasks = [doc.async_embed(embedder=self.embedder) for doc in batch_docs]
534
- await asyncio.gather(*embed_tasks, return_exceptions=True)
595
+ # Embed all documents in the batch
596
+ await self._async_embed_documents(batch_docs)
535
597
 
536
598
  # Prepare documents for upserting
537
599
  batch_records_dict = {} # Use dict to deduplicate by ID
@@ -620,14 +682,16 @@ class PgVector(VectorDb):
620
682
  logger.error(f"Error updating metadata for document {content_id}: {e}")
621
683
  raise
622
684
 
623
- def search(self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None) -> List[Document]:
685
+ def search(
686
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
687
+ ) -> List[Document]:
624
688
  """
625
689
  Perform a search based on the configured search type.
626
690
 
627
691
  Args:
628
692
  query (str): The search query.
629
693
  limit (int): Maximum number of results to return.
630
- filters (Optional[Dict[str, Any]]): Filters to apply to the search.
694
+ filters (Optional[Union[Dict[str, Any], List[FilterExpr]]]): Filters to apply to the search.
631
695
 
632
696
  Returns:
633
697
  List[Document]: List of matching documents.
@@ -643,19 +707,42 @@ class PgVector(VectorDb):
643
707
  return []
644
708
 
645
709
  async def async_search(
646
- self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
710
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
647
711
  ) -> List[Document]:
648
712
  """Search asynchronously by running in a thread."""
649
713
  return await asyncio.to_thread(self.search, query, limit, filters)
650
714
 
651
- def vector_search(self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None) -> List[Document]:
715
+ def _dsl_to_sqlalchemy(self, filter_expr, table) -> ColumnElement[bool]:
716
+ op = filter_expr["op"]
717
+
718
+ if op == "EQ":
719
+ return table.c.meta_data[filter_expr["key"]].astext == str(filter_expr["value"])
720
+ elif op == "IN":
721
+ # Postgres JSONB array containment
722
+ return table.c.meta_data[filter_expr["key"]].astext.in_([str(v) for v in filter_expr["values"]])
723
+ elif op == "GT":
724
+ return table.c.meta_data[filter_expr["key"]].astext.cast(Integer) > filter_expr["value"]
725
+ elif op == "LT":
726
+ return table.c.meta_data[filter_expr["key"]].astext.cast(Integer) < filter_expr["value"]
727
+ elif op == "NOT":
728
+ return not_(self._dsl_to_sqlalchemy(filter_expr["condition"], table))
729
+ elif op == "AND":
730
+ return and_(*[self._dsl_to_sqlalchemy(cond, table) for cond in filter_expr["conditions"]])
731
+ elif op == "OR":
732
+ return or_(*[self._dsl_to_sqlalchemy(cond, table) for cond in filter_expr["conditions"]])
733
+ else:
734
+ raise ValueError(f"Unknown filter operator: {op}")
735
+
736
+ def vector_search(
737
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
738
+ ) -> List[Document]:
652
739
  """
653
740
  Perform a vector similarity search.
654
741
 
655
742
  Args:
656
743
  query (str): The search query.
657
744
  limit (int): Maximum number of results to return.
658
- filters (Optional[Dict[str, Any]]): Filters to apply to the search.
745
+ filters (Optional[Union[Dict[str, Any], List[FilterExpr]]]): Filters to apply to the search.
659
746
 
660
747
  Returns:
661
748
  List[Document]: List of matching documents.
@@ -682,7 +769,17 @@ class PgVector(VectorDb):
682
769
 
683
770
  # Apply filters if provided
684
771
  if filters is not None:
685
- stmt = stmt.where(self.table.c.meta_data.contains(filters))
772
+ # Handle dict filters
773
+ if isinstance(filters, dict):
774
+ stmt = stmt.where(self.table.c.meta_data.contains(filters))
775
+ # Handle FilterExpr DSL
776
+ else:
777
+ # Convert each DSL expression to SQLAlchemy and AND them together
778
+ sqlalchemy_conditions = [
779
+ self._dsl_to_sqlalchemy(f.to_dict() if hasattr(f, "to_dict") else f, self.table)
780
+ for f in filters
781
+ ]
782
+ stmt = stmt.where(and_(*sqlalchemy_conditions))
686
783
 
687
784
  # Order the results based on the distance metric
688
785
  if self.distance == Distance.l2:
@@ -755,14 +852,16 @@ class PgVector(VectorDb):
755
852
  processed_words = [word + "*" for word in words]
756
853
  return " ".join(processed_words)
757
854
 
758
- def keyword_search(self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None) -> List[Document]:
855
+ def keyword_search(
856
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
857
+ ) -> List[Document]:
759
858
  """
760
859
  Perform a keyword search on the 'content' column.
761
860
 
762
861
  Args:
763
862
  query (str): The search query.
764
863
  limit (int): Maximum number of results to return.
765
- filters (Optional[Dict[str, Any]]): Filters to apply to the search.
864
+ filters (Optional[Union[Dict[str, Any], List[FilterExpr]]]): Filters to apply to the search.
766
865
 
767
866
  Returns:
768
867
  List[Document]: List of matching documents.
@@ -791,8 +890,17 @@ class PgVector(VectorDb):
791
890
 
792
891
  # Apply filters if provided
793
892
  if filters is not None:
794
- # Use the contains() method for JSONB columns to check if the filters column contains the specified filters
795
- stmt = stmt.where(self.table.c.meta_data.contains(filters))
893
+ # Handle dict filters
894
+ if isinstance(filters, dict):
895
+ stmt = stmt.where(self.table.c.meta_data.contains(filters))
896
+ # Handle FilterExpr DSL
897
+ else:
898
+ # Convert each DSL expression to SQLAlchemy and AND them together
899
+ sqlalchemy_conditions = [
900
+ self._dsl_to_sqlalchemy(f.to_dict() if hasattr(f, "to_dict") else f, self.table)
901
+ for f in filters
902
+ ]
903
+ stmt = stmt.where(and_(*sqlalchemy_conditions))
796
904
 
797
905
  # Order by the relevance rank
798
906
  stmt = stmt.order_by(text_rank.desc())
@@ -838,7 +946,7 @@ class PgVector(VectorDb):
838
946
  self,
839
947
  query: str,
840
948
  limit: int = 5,
841
- filters: Optional[Dict[str, Any]] = None,
949
+ filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
842
950
  ) -> List[Document]:
843
951
  """
844
952
  Perform a hybrid search combining vector similarity and full-text search.
@@ -846,7 +954,7 @@ class PgVector(VectorDb):
846
954
  Args:
847
955
  query (str): The search query.
848
956
  limit (int): Maximum number of results to return.
849
- filters (Optional[Dict[str, Any]]): Filters to apply to the search.
957
+ filters (Optional[Union[Dict[str, Any], List[FilterExpr]]]): Filters to apply to the search.
850
958
 
851
959
  Returns:
852
960
  List[Document]: List of matching documents.
@@ -913,7 +1021,17 @@ class PgVector(VectorDb):
913
1021
 
914
1022
  # Apply filters if provided
915
1023
  if filters is not None:
916
- stmt = stmt.where(self.table.c.meta_data.contains(filters))
1024
+ # Handle dict filters
1025
+ if isinstance(filters, dict):
1026
+ stmt = stmt.where(self.table.c.meta_data.contains(filters))
1027
+ # Handle FilterExpr DSL
1028
+ else:
1029
+ # Convert each DSL expression to SQLAlchemy and AND them together
1030
+ sqlalchemy_conditions = [
1031
+ self._dsl_to_sqlalchemy(f.to_dict() if hasattr(f, "to_dict") else f, self.table)
1032
+ for f in filters
1033
+ ]
1034
+ stmt = stmt.where(and_(*sqlalchemy_conditions))
917
1035
 
918
1036
  # Order the results by the hybrid score in descending order
919
1037
  stmt = stmt.order_by(desc("hybrid_score"))
@@ -1339,3 +1457,6 @@ class PgVector(VectorDb):
1339
1457
  copied_obj.table = copied_obj.get_table()
1340
1458
 
1341
1459
  return copied_obj
1460
+
1461
+ def get_supported_search_types(self) -> List[str]:
1462
+ return [SearchType.vector, SearchType.keyword, SearchType.hybrid]
@@ -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
@@ -307,6 +326,8 @@ class PineconeDb(VectorDb):
307
326
  show_progress: bool = False,
308
327
  ) -> None:
309
328
  """Upsert documents into the index asynchronously with batching."""
329
+ if self.content_hash_exists(content_hash):
330
+ await asyncio.to_thread(self._delete_by_content_hash, content_hash)
310
331
  if not documents:
311
332
  return
312
333
 
@@ -320,7 +341,7 @@ class PineconeDb(VectorDb):
320
341
 
321
342
  # Process each batch in parallel
322
343
  async def process_batch(batch_docs):
323
- return await self._prepare_vectors(batch_docs)
344
+ return await self._prepare_vectors(batch_docs, content_hash, filters)
324
345
 
325
346
  # Run all batches in parallel
326
347
  batch_vectors = await asyncio.gather(*[process_batch(batch) for batch in batches])
@@ -335,21 +356,65 @@ class PineconeDb(VectorDb):
335
356
 
336
357
  log_debug(f"Finished async upsert of {len(documents)} documents")
337
358
 
338
- async def _prepare_vectors(self, documents: List[Document]) -> List[Dict[str, Any]]:
359
+ async def _prepare_vectors(
360
+ self, documents: List[Document], content_hash: str, filters: Optional[Dict[str, Any]] = None
361
+ ) -> List[Dict[str, Any]]:
339
362
  """Prepare vectors for upsert."""
340
363
  vectors = []
341
- embed_tasks = [document.async_embed(embedder=self.embedder) for document in documents]
342
- await asyncio.gather(*embed_tasks, return_exceptions=True)
364
+
365
+ if self.embedder.enable_batch and hasattr(self.embedder, "async_get_embeddings_batch_and_usage"):
366
+ # Use batch embedding when enabled and supported
367
+ try:
368
+ # Extract content from all documents
369
+ doc_contents = [doc.content for doc in documents]
370
+
371
+ # Get batch embeddings and usage
372
+ embeddings, usages = await self.embedder.async_get_embeddings_batch_and_usage(doc_contents)
373
+
374
+ # Process documents with pre-computed embeddings
375
+ for j, doc in enumerate(documents):
376
+ try:
377
+ if j < len(embeddings):
378
+ doc.embedding = embeddings[j]
379
+ doc.usage = usages[j] if j < len(usages) else None
380
+ except Exception as e:
381
+ logger.error(f"Error assigning batch embedding to document '{doc.name}': {e}")
382
+
383
+ except Exception as e:
384
+ # Check if this is a rate limit error - don't fall back as it would make things worse
385
+ error_str = str(e).lower()
386
+ is_rate_limit = any(
387
+ phrase in error_str
388
+ for phrase in ["rate limit", "too many requests", "429", "trial key", "api calls / minute"]
389
+ )
390
+
391
+ if is_rate_limit:
392
+ logger.error(f"Rate limit detected during batch embedding. {e}")
393
+ raise e
394
+ else:
395
+ logger.warning(f"Async batch embedding failed, falling back to individual embeddings: {e}")
396
+ # Fall back to individual embedding
397
+ embed_tasks = [doc.async_embed(embedder=self.embedder) for doc in documents]
398
+ await asyncio.gather(*embed_tasks, return_exceptions=True)
399
+ else:
400
+ # Use individual embedding
401
+ embed_tasks = [document.async_embed(embedder=self.embedder) for document in documents]
402
+ await asyncio.gather(*embed_tasks, return_exceptions=True)
343
403
 
344
404
  for doc in documents:
345
405
  doc.meta_data["text"] = doc.content
346
406
  # Include name and content_id in metadata
347
407
  metadata = doc.meta_data.copy()
408
+ if filters:
409
+ metadata.update(filters)
410
+
348
411
  if doc.name:
349
412
  metadata["name"] = doc.name
350
413
  if doc.content_id:
351
414
  metadata["content_id"] = doc.content_id
352
415
 
416
+ metadata["content_hash"] = content_hash
417
+
353
418
  data_to_upsert = {
354
419
  "id": doc.id,
355
420
  "values": doc.embedding,
@@ -410,7 +475,7 @@ class PineconeDb(VectorDb):
410
475
  self,
411
476
  query: str,
412
477
  limit: int = 5,
413
- filters: Optional[Dict[str, Union[str, float, int, bool, List, dict]]] = None,
478
+ filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
414
479
  namespace: Optional[str] = None,
415
480
  include_values: Optional[bool] = None,
416
481
  ) -> List[Document]:
@@ -428,6 +493,9 @@ class PineconeDb(VectorDb):
428
493
  List[Document]: The list of matching documents.
429
494
 
430
495
  """
496
+ if isinstance(filters, List):
497
+ log_warning("Filters Expressions are not supported in PineconeDB. No filters will be applied.")
498
+ filters = None
431
499
  dense_embedding = self.embedder.get_embedding(query)
432
500
 
433
501
  if self.use_hybrid_search:
@@ -476,7 +544,7 @@ class PineconeDb(VectorDb):
476
544
  self,
477
545
  query: str,
478
546
  limit: int = 5,
479
- filters: Optional[Dict[str, Union[str, float, int, bool, List, dict]]] = None,
547
+ filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
480
548
  namespace: Optional[str] = None,
481
549
  include_values: Optional[bool] = None,
482
550
  ) -> List[Document]:
@@ -673,3 +741,7 @@ class PineconeDb(VectorDb):
673
741
  except Exception as e:
674
742
  logger.error(f"Error updating metadata for content_id '{content_id}': {e}")
675
743
  raise
744
+
745
+ def get_supported_search_types(self) -> List[str]:
746
+ """Get the supported search types for this vector database."""
747
+ return [] # PineconeDb doesn't use SearchType enum