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.
- agno/agent/agent.py +5540 -2273
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +689 -6
- agno/db/dynamo/dynamo.py +933 -37
- agno/db/dynamo/schemas.py +174 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +831 -9
- agno/db/firestore/schemas.py +51 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +660 -12
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +287 -14
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +590 -14
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +43 -13
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2760 -0
- agno/db/mongo/mongo.py +879 -11
- agno/db/mongo/schemas.py +42 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2912 -0
- agno/db/mysql/mysql.py +946 -68
- agno/db/mysql/schemas.py +72 -10
- agno/db/mysql/utils.py +198 -7
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2579 -0
- agno/db/postgres/postgres.py +942 -57
- agno/db/postgres/schemas.py +81 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +671 -7
- agno/db/redis/schemas.py +50 -0
- agno/db/redis/utils.py +65 -7
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +17 -2
- agno/db/singlestore/schemas.py +63 -0
- agno/db/singlestore/singlestore.py +949 -83
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2911 -0
- agno/db/sqlite/schemas.py +62 -0
- agno/db/sqlite/sqlite.py +965 -46
- agno/db/sqlite/utils.py +169 -8
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +334 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1908 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +2 -0
- agno/eval/__init__.py +10 -0
- agno/eval/accuracy.py +75 -55
- agno/eval/agent_as_judge.py +861 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +16 -7
- agno/eval/reliability.py +28 -16
- agno/eval/utils.py +35 -17
- agno/exceptions.py +27 -2
- agno/filters.py +354 -0
- agno/guardrails/prompt_injection.py +1 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +1 -1
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/semantic.py +9 -4
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +8 -0
- agno/knowledge/embedder/openai.py +8 -8
- agno/knowledge/embedder/sentence_transformer.py +6 -2
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/knowledge.py +1618 -318
- agno/knowledge/reader/base.py +6 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
- agno/knowledge/reader/json_reader.py +5 -4
- agno/knowledge/reader/markdown_reader.py +8 -8
- agno/knowledge/reader/pdf_reader.py +17 -19
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +32 -3
- agno/knowledge/reader/s3_reader.py +3 -3
- agno/knowledge/reader/tavily_reader.py +193 -0
- agno/knowledge/reader/text_reader.py +22 -10
- agno/knowledge/reader/web_search_reader.py +1 -48
- agno/knowledge/reader/website_reader.py +10 -10
- agno/knowledge/reader/wikipedia_reader.py +33 -1
- agno/knowledge/types.py +1 -0
- agno/knowledge/utils.py +72 -7
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +544 -83
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/aimlapi.py +17 -0
- agno/models/anthropic/claude.py +515 -40
- agno/models/aws/bedrock.py +102 -21
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +41 -19
- agno/models/azure/openai_chat.py +39 -8
- agno/models/base.py +1249 -525
- agno/models/cerebras/cerebras.py +91 -21
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +40 -6
- agno/models/cometapi/cometapi.py +18 -1
- agno/models/dashscope/dashscope.py +2 -3
- agno/models/deepinfra/deepinfra.py +18 -1
- agno/models/deepseek/deepseek.py +69 -3
- agno/models/fireworks/fireworks.py +18 -1
- agno/models/google/gemini.py +877 -80
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +51 -18
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/internlm/internlm.py +18 -1
- agno/models/langdb/langdb.py +13 -1
- agno/models/litellm/chat.py +44 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +28 -5
- agno/models/meta/llama.py +47 -14
- agno/models/meta/llama_openai.py +22 -17
- agno/models/mistral/mistral.py +8 -4
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/chat.py +24 -8
- agno/models/openai/chat.py +104 -29
- agno/models/openai/responses.py +101 -81
- agno/models/openrouter/openrouter.py +60 -3
- agno/models/perplexity/perplexity.py +17 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +24 -4
- agno/models/response.py +73 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +190 -0
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +549 -152
- agno/os/auth.py +190 -3
- agno/os/config.py +23 -0
- agno/os/interfaces/a2a/router.py +8 -11
- agno/os/interfaces/a2a/utils.py +1 -1
- agno/os/interfaces/agui/router.py +18 -3
- agno/os/interfaces/agui/utils.py +152 -39
- agno/os/interfaces/slack/router.py +55 -37
- agno/os/interfaces/slack/slack.py +9 -1
- agno/os/interfaces/whatsapp/router.py +0 -1
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/mcp.py +110 -52
- agno/os/middleware/__init__.py +2 -0
- agno/os/middleware/jwt.py +676 -112
- agno/os/router.py +40 -1478
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +599 -0
- agno/os/routers/agents/schema.py +261 -0
- agno/os/routers/evals/evals.py +96 -39
- agno/os/routers/evals/schemas.py +65 -33
- agno/os/routers/evals/utils.py +80 -10
- agno/os/routers/health.py +10 -4
- agno/os/routers/knowledge/knowledge.py +196 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +279 -52
- agno/os/routers/memory/schemas.py +46 -17
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +462 -34
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +512 -0
- agno/os/routers/teams/schema.py +257 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +499 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +624 -0
- agno/os/routers/workflows/schema.py +75 -0
- agno/os/schema.py +256 -693
- agno/os/scopes.py +469 -0
- agno/os/utils.py +514 -36
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/openai.py +5 -0
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +155 -32
- agno/run/base.py +55 -3
- agno/run/requirement.py +181 -0
- agno/run/team.py +125 -38
- agno/run/workflow.py +72 -18
- agno/session/agent.py +102 -89
- agno/session/summary.py +56 -15
- agno/session/team.py +164 -90
- agno/session/workflow.py +405 -40
- agno/table.py +10 -0
- agno/team/team.py +3974 -1903
- agno/tools/dalle.py +2 -4
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +16 -10
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +193 -38
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +271 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +3 -3
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +76 -36
- agno/tools/redshift.py +406 -0
- agno/tools/scrapegraph.py +1 -1
- agno/tools/shopify.py +1519 -0
- agno/tools/slack.py +18 -3
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +146 -0
- agno/tools/toolkit.py +25 -0
- agno/tools/workflow.py +8 -1
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +157 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +111 -0
- agno/utils/agent.py +938 -0
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +151 -3
- agno/utils/gemini.py +15 -5
- agno/utils/hooks.py +118 -4
- agno/utils/http.py +113 -2
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +187 -1
- agno/utils/merge_dict.py +3 -3
- agno/utils/message.py +60 -0
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +49 -14
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/print_response/agent.py +109 -16
- agno/utils/print_response/team.py +223 -30
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/streamlit.py +1 -1
- agno/utils/team.py +98 -9
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +39 -7
- agno/vectordb/cassandra/cassandra.py +21 -5
- agno/vectordb/chroma/chromadb.py +43 -12
- agno/vectordb/clickhouse/clickhousedb.py +21 -5
- agno/vectordb/couchbase/couchbase.py +29 -5
- agno/vectordb/lancedb/lance_db.py +92 -181
- agno/vectordb/langchaindb/langchaindb.py +24 -4
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/llamaindexdb.py +25 -5
- agno/vectordb/milvus/milvus.py +50 -37
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +36 -30
- agno/vectordb/pgvector/pgvector.py +201 -77
- agno/vectordb/pineconedb/pineconedb.py +41 -23
- agno/vectordb/qdrant/qdrant.py +67 -54
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +682 -0
- agno/vectordb/singlestore/singlestore.py +50 -29
- agno/vectordb/surrealdb/surrealdb.py +31 -41
- agno/vectordb/upstashdb/upstashdb.py +34 -6
- agno/vectordb/weaviate/weaviate.py +53 -14
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +120 -18
- agno/workflow/loop.py +77 -10
- agno/workflow/parallel.py +231 -143
- agno/workflow/router.py +118 -17
- agno/workflow/step.py +609 -170
- agno/workflow/steps.py +73 -6
- agno/workflow/types.py +96 -21
- agno/workflow/workflow.py +2039 -262
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
- agno-2.3.13.dist-info/RECORD +613 -0
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -679
- agno/tools/memori.py +0 -339
- agno-2.1.2.dist-info/RECORD +0 -543
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
- {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(
|
|
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,
|
|
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,
|
|
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
|
agno/vectordb/qdrant/qdrant.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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=
|
|
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=
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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=
|
|
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=
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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=
|
|
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]
|