Skip to content

Commit 76bc19c

Browse files
authored
feat: add ability to specify qdrant client kwargs (#4048)
1 parent 94492cc commit 76bc19c

7 files changed

Lines changed: 60 additions & 0 deletions

File tree

‎integrations/qdrant/src/haystack_integrations/document_stores/qdrant/document_store.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ def __init__(
150150
write_batch_size: int = 100,
151151
scroll_size: int = 10_000,
152152
payload_fields_to_index: list[dict] | None = None,
153+
client_kwargs: dict[str, Any] | None = None,
153154
) -> None:
154155
"""
155156
Initializes a QdrantDocumentStore.
@@ -236,6 +237,14 @@ def __init__(
236237
The scroll size for reading documents.
237238
:param payload_fields_to_index:
238239
List of payload fields to index.
240+
:param client_kwargs:
241+
Additional keyword arguments passed to both `QdrantClient` and `AsyncQdrantClient`.
242+
These arguments take precedence over named client parameters in case of overlap.
243+
Serialized as-is by `to_dict()`: callbacks and `httpx.Limits` may not serialize, and
244+
authentication headers will appear in plain text in pipeline YAML.
245+
The same arguments are used for sync and async clients. For example, an async
246+
`auth_token_provider` is also passed to `QdrantClient`, where it may not work with
247+
synchronous requests.
239248
"""
240249

241250
self._client: qdrant_client.QdrantClient | None = None
@@ -255,6 +264,7 @@ def __init__(
255264
self.path = path
256265
self.force_disable_check_same_thread = force_disable_check_same_thread
257266
self.metadata = metadata or {}
267+
self.client_kwargs = dict(client_kwargs or {})
258268

259269
# Store the Qdrant collection specific attributes
260270
self.shard_number = shard_number
@@ -2485,6 +2495,7 @@ def _prepare_client_params(self) -> dict[str, Any]:
24852495
# when using a hosted Qdrant service, which means running to_dict() exposes the api key.
24862496
"metadata": {**self.metadata},
24872497
"force_disable_check_same_thread": self.force_disable_check_same_thread,
2498+
**{key: {**value} if isinstance(value, dict) else value for key, value in self.client_kwargs.items()},
24882499
}
24892500

24902501
def _prepare_collection_config(

‎integrations/qdrant/tests/test_dict_converters.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def test_to_dict():
4343
"write_batch_size": 100,
4444
"scroll_size": 10000,
4545
"payload_fields_to_index": None,
46+
"client_kwargs": {},
4647
},
4748
}
4849

@@ -73,6 +74,7 @@ def test_from_dict():
7374
"write_batch_size": 1000,
7475
"scroll_size": 10000,
7576
"payload_fields_to_index": None,
77+
"client_kwargs": {"check_compatibility": False},
7678
},
7779
}
7880
)
@@ -102,5 +104,6 @@ def test_from_dict():
102104
document_store.scroll_size == 10000,
103105
document_store.api_key == Secret.from_env_var("ENV_VAR", strict=False),
104106
document_store.payload_fields_to_index is None,
107+
document_store.client_kwargs == {"check_compatibility": False},
105108
]
106109
)

‎integrations/qdrant/tests/test_document_store.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def test_to_dict(self, monkeypatch):
4949
wait_result_from_api=True,
5050
use_sparse_embeddings=False,
5151
api_key=Secret.from_env_var("QDRANT_API_KEY"),
52+
client_kwargs={"check_compatibility": False},
5253
)
5354
expected_dict = {
5455
"type": "haystack_integrations.document_stores.qdrant.document_store.QdrantDocumentStore",
@@ -91,6 +92,7 @@ def test_to_dict(self, monkeypatch):
9192
"write_batch_size": 100,
9293
"scroll_size": 10000,
9394
"payload_fields_to_index": None,
95+
"client_kwargs": {"check_compatibility": False},
9496
},
9597
}
9698
assert doc_store.to_dict() == expected_dict
@@ -347,6 +349,30 @@ def test_prepare_client_params_does_not_mutate_metadata(self):
347349
params["metadata"]["added"] = "x"
348350
assert metadata == {"key": "value"}
349351

352+
def test_prepare_client_params_does_not_mutate_client_kwargs(self):
353+
client_kwargs = {"grpc_options": {"custom_option": "value"}}
354+
document_store = QdrantDocumentStore(location=":memory:", client_kwargs=client_kwargs)
355+
params = document_store._prepare_client_params()
356+
params["grpc_options"]["grpc.primary_user_agent"] = "qdrant-client"
357+
358+
assert client_kwargs == {"grpc_options": {"custom_option": "value"}}
359+
assert document_store.to_dict()["init_parameters"]["client_kwargs"] == client_kwargs
360+
361+
@pytest.mark.parametrize("port", [6333, 7333])
362+
def test_client_kwargs_are_forwarded_with_client_kwargs_taking_precedence(self, port):
363+
document_store = QdrantDocumentStore(
364+
location=":memory:", port=port, client_kwargs={"check_compatibility": False, "port": 8333}
365+
)
366+
367+
with (
368+
patch("haystack_integrations.document_stores.qdrant.document_store.qdrant_client.QdrantClient") as client,
369+
patch.object(document_store, "_set_up_collection"),
370+
):
371+
document_store._initialize_client()
372+
373+
assert client.call_args.kwargs["check_compatibility"] is False
374+
assert client.call_args.kwargs["port"] == 8333
375+
350376
def test_get_batches_from_generator(self):
351377
batches = list(get_batches_from_generator([1, 2, 3, 4, 5], 2))
352378
assert batches == [(1, 2), (3, 4), (5,)]

‎integrations/qdrant/tests/test_document_store_async.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,23 @@
3535

3636
@pytest.mark.asyncio
3737
class TestQdrantDocumentStoreAsyncUnit:
38+
@pytest.mark.parametrize("port", [6333, 7333])
39+
async def test_client_kwargs_are_forwarded_async(self, port):
40+
document_store = QdrantDocumentStore(
41+
location=":memory:", port=port, client_kwargs={"check_compatibility": False, "port": 8333}
42+
)
43+
44+
with (
45+
patch(
46+
"haystack_integrations.document_stores.qdrant.document_store.qdrant_client.AsyncQdrantClient"
47+
) as client,
48+
patch.object(document_store, "_set_up_collection_async", new_callable=AsyncMock),
49+
):
50+
await document_store._initialize_async_client()
51+
52+
assert client.call_args.kwargs["check_compatibility"] is False
53+
assert client.call_args.kwargs["port"] == 8333
54+
3855
async def test_query_hybrid_search_batch_failure_async(self):
3956
document_store = QdrantDocumentStore(location=":memory:", use_sparse_embeddings=True)
4057
await document_store._initialize_async_client()

‎integrations/qdrant/tests/test_embedding_retriever.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def test_to_dict(self):
8383
"write_batch_size": 100,
8484
"scroll_size": 10000,
8585
"payload_fields_to_index": None,
86+
"client_kwargs": {},
8687
},
8788
},
8889
"filters": None,

‎integrations/qdrant/tests/test_hybrid_retriever.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def test_to_dict(self):
8181
"write_batch_size": 100,
8282
"scroll_size": 10000,
8383
"payload_fields_to_index": None,
84+
"client_kwargs": {},
8485
},
8586
},
8687
"filters": None,

‎integrations/qdrant/tests/test_sparse_embedding_retriever.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def test_to_dict(self):
8282
"write_batch_size": 100,
8383
"scroll_size": 10000,
8484
"payload_fields_to_index": None,
85+
"client_kwargs": {},
8586
},
8687
},
8788
"filters": None,

0 commit comments

Comments
 (0)