agno 2.0.1__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.
- agno/agent/agent.py +6015 -2823
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +385 -6
- agno/db/dynamo/dynamo.py +388 -81
- agno/db/dynamo/schemas.py +47 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +435 -64
- agno/db/firestore/schemas.py +11 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +384 -42
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +351 -66
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +339 -48
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +510 -37
- 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 +2036 -0
- agno/db/mongo/mongo.py +653 -76
- agno/db/mongo/schemas.py +13 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/mysql.py +687 -25
- agno/db/mysql/schemas.py +61 -37
- agno/db/mysql/utils.py +60 -2
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2001 -0
- agno/db/postgres/postgres.py +676 -57
- agno/db/postgres/schemas.py +43 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +344 -38
- agno/db/redis/schemas.py +18 -0
- agno/db/redis/utils.py +60 -2
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +26 -1
- agno/db/singlestore/singlestore.py +687 -53
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2371 -0
- agno/db/sqlite/schemas.py +24 -0
- agno/db/sqlite/sqlite.py +774 -85
- agno/db/sqlite/utils.py +168 -5
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1361 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +50 -22
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +68 -1
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/integrations/discord/client.py +1 -0
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/chunking/semantic.py +40 -8
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/aws_bedrock.py +9 -4
- agno/knowledge/embedder/azure_openai.py +54 -0
- agno/knowledge/embedder/base.py +2 -0
- agno/knowledge/embedder/cohere.py +184 -5
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/google.py +79 -1
- agno/knowledge/embedder/huggingface.py +9 -4
- agno/knowledge/embedder/jina.py +63 -0
- agno/knowledge/embedder/mistral.py +78 -11
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +13 -0
- agno/knowledge/embedder/openai.py +37 -65
- agno/knowledge/embedder/sentence_transformer.py +8 -4
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +594 -186
- agno/knowledge/reader/base.py +9 -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 +290 -0
- agno/knowledge/reader/json_reader.py +6 -5
- agno/knowledge/reader/markdown_reader.py +13 -13
- agno/knowledge/reader/pdf_reader.py +43 -68
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +51 -6
- agno/knowledge/reader/s3_reader.py +3 -15
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +13 -13
- agno/knowledge/reader/web_search_reader.py +2 -43
- agno/knowledge/reader/website_reader.py +43 -25
- agno/knowledge/reranker/__init__.py +2 -8
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +72 -0
- agno/memory/manager.py +336 -82
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/anthropic/claude.py +183 -37
- agno/models/aws/bedrock.py +52 -112
- agno/models/aws/claude.py +33 -1
- agno/models/azure/ai_foundry.py +33 -15
- agno/models/azure/openai_chat.py +25 -8
- agno/models/base.py +999 -519
- agno/models/cerebras/cerebras.py +19 -13
- agno/models/cerebras/cerebras_openai.py +8 -5
- agno/models/cohere/chat.py +27 -1
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/google/gemini.py +103 -31
- agno/models/groq/groq.py +28 -11
- agno/models/huggingface/huggingface.py +2 -1
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/chat.py +18 -1
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/message.py +139 -0
- agno/models/meta/llama.py +27 -10
- agno/models/meta/llama_openai.py +5 -17
- agno/models/nebius/nebius.py +6 -6
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/ollama/chat.py +59 -5
- agno/models/openai/chat.py +69 -29
- agno/models/openai/responses.py +103 -106
- agno/models/openrouter/openrouter.py +41 -3
- agno/models/perplexity/perplexity.py +4 -5
- agno/models/portkey/portkey.py +3 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +77 -1
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/together.py +2 -2
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +2 -2
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +96 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +3 -2
- agno/os/app.py +543 -178
- agno/os/auth.py +24 -14
- agno/os/config.py +1 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/agui.py +23 -7
- agno/os/interfaces/agui/router.py +27 -3
- agno/os/interfaces/agui/utils.py +242 -142
- agno/os/interfaces/base.py +6 -2
- agno/os/interfaces/slack/router.py +81 -23
- agno/os/interfaces/slack/slack.py +29 -14
- agno/os/interfaces/whatsapp/router.py +11 -4
- agno/os/interfaces/whatsapp/whatsapp.py +14 -7
- agno/os/mcp.py +111 -54
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +556 -139
- agno/os/routers/evals/evals.py +71 -34
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/evals/utils.py +6 -5
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +185 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +158 -53
- agno/os/routers/memory/schemas.py +20 -16
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +499 -38
- agno/os/schema.py +308 -198
- agno/os/utils.py +401 -41
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +3 -1
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +2 -2
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +7 -2
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +248 -94
- agno/run/base.py +44 -5
- agno/run/team.py +238 -97
- agno/run/workflow.py +144 -33
- agno/session/agent.py +105 -89
- agno/session/summary.py +65 -25
- agno/session/team.py +176 -96
- agno/session/workflow.py +406 -40
- agno/team/team.py +3854 -1610
- agno/tools/dalle.py +2 -4
- agno/tools/decorator.py +4 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +14 -7
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +250 -30
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +270 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/knowledge.py +3 -3
- 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 +284 -0
- agno/tools/mem0.py +11 -17
- agno/tools/memori.py +1 -53
- agno/tools/memory.py +419 -0
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/scrapegraph.py +58 -31
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/slack.py +18 -3
- agno/tools/spider.py +2 -2
- agno/tools/tavily.py +146 -0
- agno/tools/whatsapp.py +1 -1
- agno/tools/workflow.py +278 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +27 -0
- agno/utils/common.py +90 -1
- agno/utils/events.py +217 -2
- agno/utils/gemini.py +180 -22
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +111 -0
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +188 -10
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +60 -0
- agno/utils/models/claude.py +40 -11
- agno/utils/print_response/agent.py +105 -21
- agno/utils/print_response/team.py +103 -38
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/reasoning.py +22 -1
- agno/utils/serialize.py +32 -0
- agno/utils/streamlit.py +16 -10
- agno/utils/string.py +41 -0
- agno/utils/team.py +98 -9
- agno/utils/tools.py +1 -1
- agno/vectordb/base.py +23 -4
- agno/vectordb/cassandra/cassandra.py +65 -9
- agno/vectordb/chroma/chromadb.py +182 -38
- agno/vectordb/clickhouse/clickhousedb.py +64 -11
- agno/vectordb/couchbase/couchbase.py +105 -10
- agno/vectordb/lancedb/lance_db.py +124 -133
- agno/vectordb/langchaindb/langchaindb.py +25 -7
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +46 -7
- agno/vectordb/milvus/milvus.py +126 -9
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +112 -7
- agno/vectordb/pgvector/pgvector.py +142 -21
- agno/vectordb/pineconedb/pineconedb.py +80 -8
- agno/vectordb/qdrant/qdrant.py +125 -39
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/singlestore/singlestore.py +111 -25
- agno/vectordb/surrealdb/surrealdb.py +31 -5
- agno/vectordb/upstashdb/upstashdb.py +76 -8
- agno/vectordb/weaviate/weaviate.py +86 -15
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +112 -18
- agno/workflow/loop.py +69 -10
- agno/workflow/parallel.py +266 -118
- agno/workflow/router.py +110 -17
- agno/workflow/step.py +638 -129
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +61 -23
- agno/workflow/workflow.py +2085 -272
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
- agno-2.3.0.dist-info/RECORD +577 -0
- agno/knowledge/reader/url_reader.py +0 -128
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -610
- agno/utils/models/aws_claude.py +0 -170
- agno-2.0.1.dist-info/RECORD +0 -515
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
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
|
|
|
@@ -131,7 +152,8 @@ class Qdrant(VectorDb):
|
|
|
131
152
|
if fastembed_kwargs:
|
|
132
153
|
default_kwargs.update(fastembed_kwargs)
|
|
133
154
|
|
|
134
|
-
|
|
155
|
+
# Type ignore for mypy as SparseTextEmbedding constructor accepts flexible kwargs
|
|
156
|
+
self.sparse_encoder = SparseTextEmbedding(**default_kwargs) # type: ignore
|
|
135
157
|
|
|
136
158
|
except ImportError as e:
|
|
137
159
|
raise ImportError(
|
|
@@ -192,10 +214,12 @@ class Qdrant(VectorDb):
|
|
|
192
214
|
# Configure vectors based on search type
|
|
193
215
|
if self.search_type == SearchType.vector:
|
|
194
216
|
# Maintain backward compatibility with unnamed vectors
|
|
195
|
-
vectors_config = models.VectorParams(size=self.dimensions, distance=_distance)
|
|
217
|
+
vectors_config = models.VectorParams(size=self.dimensions or 1536, distance=_distance)
|
|
196
218
|
else:
|
|
197
219
|
# Use named vectors for hybrid search
|
|
198
|
-
vectors_config = {
|
|
220
|
+
vectors_config = {
|
|
221
|
+
self.dense_vector_name: models.VectorParams(size=self.dimensions or 1536, distance=_distance)
|
|
222
|
+
} # type: ignore
|
|
199
223
|
|
|
200
224
|
self.client.create_collection(
|
|
201
225
|
collection_name=self.collection,
|
|
@@ -220,10 +244,12 @@ class Qdrant(VectorDb):
|
|
|
220
244
|
# Configure vectors based on search type
|
|
221
245
|
if self.search_type == SearchType.vector:
|
|
222
246
|
# Maintain backward compatibility with unnamed vectors
|
|
223
|
-
vectors_config = models.VectorParams(size=self.dimensions, distance=_distance)
|
|
247
|
+
vectors_config = models.VectorParams(size=self.dimensions or 1536, distance=_distance)
|
|
224
248
|
else:
|
|
225
249
|
# Use named vectors for hybrid search
|
|
226
|
-
vectors_config = {
|
|
250
|
+
vectors_config = {
|
|
251
|
+
self.dense_vector_name: models.VectorParams(size=self.dimensions or 1536, distance=_distance)
|
|
252
|
+
} # type: ignore
|
|
227
253
|
|
|
228
254
|
await self.async_client.create_collection(
|
|
229
255
|
collection_name=self.collection,
|
|
@@ -281,7 +307,7 @@ class Qdrant(VectorDb):
|
|
|
281
307
|
return len(scroll_result[0]) > 0
|
|
282
308
|
return False
|
|
283
309
|
|
|
284
|
-
async def async_name_exists(self, name: str) -> bool:
|
|
310
|
+
async def async_name_exists(self, name: str) -> bool: # type: ignore[override]
|
|
285
311
|
"""
|
|
286
312
|
Asynchronously validates if a document with the given name exists in the collection.
|
|
287
313
|
|
|
@@ -341,7 +367,9 @@ class Qdrant(VectorDb):
|
|
|
341
367
|
vector[self.dense_vector_name] = document.embedding
|
|
342
368
|
|
|
343
369
|
if self.search_type in [SearchType.keyword, SearchType.hybrid]:
|
|
344
|
-
vector[self.sparse_vector_name] = next(
|
|
370
|
+
vector[self.sparse_vector_name] = next(
|
|
371
|
+
iter(self.sparse_encoder.embed([document.content]))
|
|
372
|
+
).as_object() # type: ignore
|
|
345
373
|
|
|
346
374
|
# Create payload with document properties
|
|
347
375
|
payload = {
|
|
@@ -363,7 +391,7 @@ class Qdrant(VectorDb):
|
|
|
363
391
|
points.append(
|
|
364
392
|
models.PointStruct(
|
|
365
393
|
id=doc_id,
|
|
366
|
-
vector=vector,
|
|
394
|
+
vector=vector, # type: ignore
|
|
367
395
|
payload=payload,
|
|
368
396
|
)
|
|
369
397
|
)
|
|
@@ -384,26 +412,69 @@ class Qdrant(VectorDb):
|
|
|
384
412
|
"""
|
|
385
413
|
log_debug(f"Inserting {len(documents)} documents asynchronously")
|
|
386
414
|
|
|
415
|
+
# Apply batch embedding when needed for vector or hybrid search
|
|
416
|
+
if self.search_type in [SearchType.vector, SearchType.hybrid]:
|
|
417
|
+
if self.embedder.enable_batch and hasattr(self.embedder, "async_get_embeddings_batch_and_usage"):
|
|
418
|
+
# Use batch embedding when enabled and supported
|
|
419
|
+
try:
|
|
420
|
+
# Extract content from all documents
|
|
421
|
+
doc_contents = [doc.content for doc in documents]
|
|
422
|
+
|
|
423
|
+
# Get batch embeddings and usage
|
|
424
|
+
embeddings, usages = await self.embedder.async_get_embeddings_batch_and_usage(doc_contents)
|
|
425
|
+
|
|
426
|
+
# Process documents with pre-computed embeddings
|
|
427
|
+
for j, doc in enumerate(documents):
|
|
428
|
+
try:
|
|
429
|
+
if j < len(embeddings):
|
|
430
|
+
doc.embedding = embeddings[j]
|
|
431
|
+
doc.usage = usages[j] if j < len(usages) else None
|
|
432
|
+
except Exception as e:
|
|
433
|
+
log_error(f"Error assigning batch embedding to document '{doc.name}': {e}")
|
|
434
|
+
|
|
435
|
+
except Exception as e:
|
|
436
|
+
# Check if this is a rate limit error - don't fall back as it would make things worse
|
|
437
|
+
error_str = str(e).lower()
|
|
438
|
+
is_rate_limit = any(
|
|
439
|
+
phrase in error_str
|
|
440
|
+
for phrase in ["rate limit", "too many requests", "429", "trial key", "api calls / minute"]
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
if is_rate_limit:
|
|
444
|
+
log_error(f"Rate limit detected during batch embedding. {e}")
|
|
445
|
+
raise e
|
|
446
|
+
else:
|
|
447
|
+
log_warning(f"Async batch embedding failed, falling back to individual embeddings: {e}")
|
|
448
|
+
# Fall back to individual embedding
|
|
449
|
+
for doc in documents:
|
|
450
|
+
if self.search_type in [SearchType.vector, SearchType.hybrid]:
|
|
451
|
+
doc.embed(embedder=self.embedder)
|
|
452
|
+
else:
|
|
453
|
+
# Use individual embedding
|
|
454
|
+
for doc in documents:
|
|
455
|
+
if self.search_type in [SearchType.vector, SearchType.hybrid]:
|
|
456
|
+
doc.embed(embedder=self.embedder)
|
|
457
|
+
|
|
387
458
|
async def process_document(document):
|
|
388
459
|
cleaned_content = document.content.replace("\x00", "\ufffd")
|
|
389
460
|
doc_id = md5(cleaned_content.encode()).hexdigest()
|
|
390
461
|
|
|
391
462
|
if self.search_type == SearchType.vector:
|
|
392
463
|
# For vector search, maintain backward compatibility with unnamed vectors
|
|
393
|
-
document.
|
|
394
|
-
vector = document.embedding
|
|
464
|
+
vector = document.embedding # Already embedded above
|
|
395
465
|
else:
|
|
396
466
|
# For other search types, use named vectors
|
|
397
467
|
vector = {}
|
|
398
468
|
if self.search_type in [SearchType.hybrid]:
|
|
399
|
-
|
|
400
|
-
vector[self.dense_vector_name] = document.embedding
|
|
469
|
+
vector[self.dense_vector_name] = document.embedding # Already embedded above
|
|
401
470
|
|
|
402
471
|
if self.search_type in [SearchType.keyword, SearchType.hybrid]:
|
|
403
|
-
vector[self.sparse_vector_name] = next(
|
|
472
|
+
vector[self.sparse_vector_name] = next(
|
|
473
|
+
iter(self.sparse_encoder.embed([document.content]))
|
|
474
|
+
).as_object() # type: ignore
|
|
404
475
|
|
|
405
476
|
if self.search_type in [SearchType.keyword, SearchType.hybrid]:
|
|
406
|
-
vector[self.sparse_vector_name] = next(self.sparse_encoder.embed([document.content])).as_object()
|
|
477
|
+
vector[self.sparse_vector_name] = next(iter(self.sparse_encoder.embed([document.content]))).as_object()
|
|
407
478
|
|
|
408
479
|
# Create payload with document properties
|
|
409
480
|
payload = {
|
|
@@ -423,9 +494,9 @@ class Qdrant(VectorDb):
|
|
|
423
494
|
payload["meta_data"].update(filters)
|
|
424
495
|
|
|
425
496
|
log_debug(f"Inserted document asynchronously: {document.name} ({document.meta_data})")
|
|
426
|
-
return models.PointStruct(
|
|
497
|
+
return models.PointStruct( # type: ignore
|
|
427
498
|
id=doc_id,
|
|
428
|
-
vector=vector,
|
|
499
|
+
vector=vector, # type: ignore
|
|
429
500
|
payload=payload,
|
|
430
501
|
)
|
|
431
502
|
|
|
@@ -458,7 +529,9 @@ class Qdrant(VectorDb):
|
|
|
458
529
|
log_debug("Redirecting the async request to async_insert")
|
|
459
530
|
await self.async_insert(content_hash=content_hash, documents=documents, filters=filters)
|
|
460
531
|
|
|
461
|
-
def search(
|
|
532
|
+
def search(
|
|
533
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
534
|
+
) -> List[Document]:
|
|
462
535
|
"""
|
|
463
536
|
Search for documents in the collection.
|
|
464
537
|
|
|
@@ -467,28 +540,37 @@ class Qdrant(VectorDb):
|
|
|
467
540
|
limit (int): Number of search results to return
|
|
468
541
|
filters (Optional[Dict[str, Any]]): Filters to apply while searching
|
|
469
542
|
"""
|
|
543
|
+
|
|
544
|
+
if isinstance(filters, List):
|
|
545
|
+
log_warning("Filters Expressions are not supported in Qdrant. No filters will be applied.")
|
|
546
|
+
filters = None
|
|
547
|
+
|
|
470
548
|
filters = self._format_filters(filters or {}) # type: ignore
|
|
471
549
|
if self.search_type == SearchType.vector:
|
|
472
|
-
results = self._run_vector_search_sync(query, limit, filters)
|
|
550
|
+
results = self._run_vector_search_sync(query, limit, filters) # type: ignore
|
|
473
551
|
elif self.search_type == SearchType.keyword:
|
|
474
|
-
results = self._run_keyword_search_sync(query, limit, filters)
|
|
552
|
+
results = self._run_keyword_search_sync(query, limit, filters) # type: ignore
|
|
475
553
|
elif self.search_type == SearchType.hybrid:
|
|
476
|
-
results = self._run_hybrid_search_sync(query, limit, filters)
|
|
554
|
+
results = self._run_hybrid_search_sync(query, limit, filters) # type: ignore
|
|
477
555
|
else:
|
|
478
556
|
raise ValueError(f"Unsupported search type: {self.search_type}")
|
|
479
557
|
|
|
480
558
|
return self._build_search_results(results, query)
|
|
481
559
|
|
|
482
560
|
async def async_search(
|
|
483
|
-
self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
|
|
561
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
484
562
|
) -> List[Document]:
|
|
563
|
+
if isinstance(filters, List):
|
|
564
|
+
log_warning("Filters Expressions are not supported in Qdrant. No filters will be applied.")
|
|
565
|
+
filters = None
|
|
566
|
+
|
|
485
567
|
filters = self._format_filters(filters or {}) # type: ignore
|
|
486
568
|
if self.search_type == SearchType.vector:
|
|
487
|
-
results = await self._run_vector_search_async(query, limit, filters)
|
|
569
|
+
results = await self._run_vector_search_async(query, limit, filters) # type: ignore
|
|
488
570
|
elif self.search_type == SearchType.keyword:
|
|
489
|
-
results = await self._run_keyword_search_async(query, limit, filters)
|
|
571
|
+
results = await self._run_keyword_search_async(query, limit, filters) # type: ignore
|
|
490
572
|
elif self.search_type == SearchType.hybrid:
|
|
491
|
-
results = await self._run_hybrid_search_async(query, limit, filters)
|
|
573
|
+
results = await self._run_hybrid_search_async(query, limit, filters) # type: ignore
|
|
492
574
|
else:
|
|
493
575
|
raise ValueError(f"Unsupported search type: {self.search_type}")
|
|
494
576
|
|
|
@@ -498,15 +580,15 @@ class Qdrant(VectorDb):
|
|
|
498
580
|
self,
|
|
499
581
|
query: str,
|
|
500
582
|
limit: int,
|
|
501
|
-
filters: Optional[Dict[str, Any]],
|
|
583
|
+
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]],
|
|
502
584
|
) -> List[models.ScoredPoint]:
|
|
503
585
|
dense_embedding = self.embedder.get_embedding(query)
|
|
504
|
-
sparse_embedding = next(self.sparse_encoder.embed([query])).as_object()
|
|
586
|
+
sparse_embedding = next(iter(self.sparse_encoder.embed([query]))).as_object()
|
|
505
587
|
call = self.client.query_points(
|
|
506
588
|
collection_name=self.collection,
|
|
507
589
|
prefetch=[
|
|
508
590
|
models.Prefetch(
|
|
509
|
-
query=models.SparseVector(**sparse_embedding),
|
|
591
|
+
query=models.SparseVector(**sparse_embedding), # type: ignore # type: ignore
|
|
510
592
|
limit=limit,
|
|
511
593
|
using=self.sparse_vector_name,
|
|
512
594
|
),
|
|
@@ -524,7 +606,7 @@ class Qdrant(VectorDb):
|
|
|
524
606
|
self,
|
|
525
607
|
query: str,
|
|
526
608
|
limit: int,
|
|
527
|
-
filters: Optional[Dict[str, Any]],
|
|
609
|
+
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]],
|
|
528
610
|
) -> List[models.ScoredPoint]:
|
|
529
611
|
dense_embedding = self.embedder.get_embedding(query)
|
|
530
612
|
|
|
@@ -555,12 +637,12 @@ class Qdrant(VectorDb):
|
|
|
555
637
|
self,
|
|
556
638
|
query: str,
|
|
557
639
|
limit: int,
|
|
558
|
-
filters: Optional[Dict[str, Any]],
|
|
640
|
+
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]],
|
|
559
641
|
) -> List[models.ScoredPoint]:
|
|
560
|
-
sparse_embedding = next(self.sparse_encoder.embed([query])).as_object()
|
|
642
|
+
sparse_embedding = next(iter(self.sparse_encoder.embed([query]))).as_object()
|
|
561
643
|
call = self.client.query_points(
|
|
562
644
|
collection_name=self.collection,
|
|
563
|
-
query=models.SparseVector(**sparse_embedding),
|
|
645
|
+
query=models.SparseVector(**sparse_embedding), # type: ignore
|
|
564
646
|
with_vectors=True,
|
|
565
647
|
with_payload=True,
|
|
566
648
|
limit=limit,
|
|
@@ -606,10 +688,10 @@ class Qdrant(VectorDb):
|
|
|
606
688
|
limit: int,
|
|
607
689
|
filters: Optional[Dict[str, Any]],
|
|
608
690
|
) -> List[models.ScoredPoint]:
|
|
609
|
-
sparse_embedding = next(self.sparse_encoder.embed([query])).as_object()
|
|
691
|
+
sparse_embedding = next(iter(self.sparse_encoder.embed([query]))).as_object()
|
|
610
692
|
call = await self.async_client.query_points(
|
|
611
693
|
collection_name=self.collection,
|
|
612
|
-
query=models.SparseVector(**sparse_embedding),
|
|
694
|
+
query=models.SparseVector(**sparse_embedding), # type: ignore
|
|
613
695
|
with_vectors=True,
|
|
614
696
|
with_payload=True,
|
|
615
697
|
limit=limit,
|
|
@@ -622,15 +704,15 @@ class Qdrant(VectorDb):
|
|
|
622
704
|
self,
|
|
623
705
|
query: str,
|
|
624
706
|
limit: int,
|
|
625
|
-
filters: Optional[Dict[str, Any]],
|
|
707
|
+
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]],
|
|
626
708
|
) -> List[models.ScoredPoint]:
|
|
627
709
|
dense_embedding = self.embedder.get_embedding(query)
|
|
628
|
-
sparse_embedding = next(self.sparse_encoder.embed([query])).as_object()
|
|
710
|
+
sparse_embedding = next(iter(self.sparse_encoder.embed([query]))).as_object()
|
|
629
711
|
call = await self.async_client.query_points(
|
|
630
712
|
collection_name=self.collection,
|
|
631
713
|
prefetch=[
|
|
632
714
|
models.Prefetch(
|
|
633
|
-
query=models.SparseVector(**sparse_embedding),
|
|
715
|
+
query=models.SparseVector(**sparse_embedding), # type: ignore # type: ignore
|
|
634
716
|
limit=limit,
|
|
635
717
|
using=self.sparse_vector_name,
|
|
636
718
|
),
|
|
@@ -689,7 +771,7 @@ class Qdrant(VectorDb):
|
|
|
689
771
|
filter_conditions.append(models.FieldCondition(key=key, match=models.MatchValue(value=value)))
|
|
690
772
|
|
|
691
773
|
if filter_conditions:
|
|
692
|
-
return models.Filter(must=filter_conditions)
|
|
774
|
+
return models.Filter(must=filter_conditions) # type: ignore
|
|
693
775
|
|
|
694
776
|
return None
|
|
695
777
|
|
|
@@ -807,7 +889,7 @@ class Qdrant(VectorDb):
|
|
|
807
889
|
)
|
|
808
890
|
|
|
809
891
|
# Create a filter that requires ALL metadata conditions to match
|
|
810
|
-
filter_condition = models.Filter(must=filter_conditions)
|
|
892
|
+
filter_condition = models.Filter(must=filter_conditions) # type: ignore
|
|
811
893
|
|
|
812
894
|
# First, count how many points will be deleted
|
|
813
895
|
count_result = self.client.count(collection_name=self.collection, count_filter=filter_condition, exact=True)
|
|
@@ -1046,3 +1128,7 @@ class Qdrant(VectorDb):
|
|
|
1046
1128
|
log_debug(f"Error closing async Qdrant client: {e}")
|
|
1047
1129
|
finally:
|
|
1048
1130
|
self._async_client = None
|
|
1131
|
+
|
|
1132
|
+
def get_supported_search_types(self) -> List[str]:
|
|
1133
|
+
"""Get the supported search types for this vector database."""
|
|
1134
|
+
return [SearchType.vector, SearchType.keyword, SearchType.hybrid]
|