From 54ce79a0c917311caebcd496a7f26fd965099156 Mon Sep 17 00:00:00 2001 From: Anush008 Date: Thu, 3 Sep 2026 13:42:45 +0530 Subject: [PATCH] chore: Review updates --- CHANGELOG.md | 8 +- docs/changelog/introduction.mdx | 2 +- pyproject.toml | 4 +- src/startup/embedding_validator.py | 2 +- src/vector_store/__init__.py | 10 +- src/vector_store/qdrant.py | 33 +++++- tests/vector_store/test_qdrant.py | 178 +++++++++++++++++++++++++++++ uv.lock | 38 ++---- 8 files changed, 239 insertions(+), 36 deletions(-) create mode 100644 tests/vector_store/test_qdrant.py diff --git a/CHANGELOG.md b/CHANGELOG.md index de9d9938..d4a7660d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] + +### Added + +- Qdrant vector store backend (`VECTOR_STORE_TYPE=qdrant`) as an optional `qdrant` extra (#683) + ## [3.1.0] - 2026-08-25 ### Added @@ -380,7 +386,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Prometheus token tracking for deriver and dialectic operations - n8n integration - Cloud Events for auditable telemetry -- External Vector Store support for turbopuffer, lancedb, and qdrant with reconciliation flow +- External Vector Store support for turbopuffer and lancedb with reconciliation flow ### Changed diff --git a/docs/changelog/introduction.mdx b/docs/changelog/introduction.mdx index 54947cda..a152d16a 100644 --- a/docs/changelog/introduction.mdx +++ b/docs/changelog/introduction.mdx @@ -383,7 +383,7 @@ Welcome to the Honcho changelog! This section documents all notable changes to t - Prometheus token tracking for deriver and dialectic operations - n8n integration - Cloud Events for auditable telemetry - - External Vector Store support for turbopuffer, lancedb, and qdrant with reconciliation flow + - External Vector Store support for turbopuffer and lancedb with reconciliation flow ### Changed diff --git a/pyproject.toml b/pyproject.toml index 2ff7e1fe..efc47336 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,6 @@ dependencies = [ "typing-extensions>=4.11.0", "json-repair>=0.49.0", "turbopuffer>=1.8.1", - "qdrant-client>=1.18.0", "redis>=7.0.0,<8.0.0", "cashews[redis]==7.5.0", "scikit-learn>=1.6.0", @@ -44,6 +43,9 @@ lancedb = [ "lancedb>=0.25.3; sys_platform != \"darwin\" or platform_machine != \"x86_64\"", "pyarrow>=19.0.0", ] +qdrant = [ + "qdrant-client>=1.18.0", +] [dependency-groups] dev = [ "pytest>=8.2.2", diff --git a/src/startup/embedding_validator.py b/src/startup/embedding_validator.py index a397c9e2..d0584dff 100644 --- a/src/startup/embedding_validator.py +++ b/src/startup/embedding_validator.py @@ -76,7 +76,7 @@ async def validate_embedding_schema( dims = await _introspect_pgvector_dims_with_retry(engine, schema) _assert_pgvector_dims_match(dims, schema=schema, target_dim=target_dim) - if s.VECTOR_STORE.TYPE in ("turbopuffer", "lancedb"): + if s.VECTOR_STORE.TYPE in ("turbopuffer", "lancedb", "qdrant"): await _sample_external_namespaces(engine, target_dim=target_dim) diff --git a/src/vector_store/__init__.py b/src/vector_store/__init__.py index 6f9eb413..e5e6df10 100644 --- a/src/vector_store/__init__.py +++ b/src/vector_store/__init__.py @@ -215,7 +215,15 @@ def _create_store_by_type(store_type: str) -> VectorStore: return LanceDBVectorStore() elif store_type == "qdrant": - from src.vector_store.qdrant import QdrantVectorStore + try: + from src.vector_store.qdrant import QdrantVectorStore + except ImportError as exc: + raise RuntimeError( + "VECTOR_STORE.TYPE is set to 'qdrant', but the 'qdrant-client' " + + "package could not be imported. Install Honcho's 'qdrant' extra " + + "(for example, `uv sync --extra qdrant`)" + + f"Original import error: {exc}" + ) from exc return QdrantVectorStore() else: diff --git a/src/vector_store/qdrant.py b/src/vector_store/qdrant.py index 6a06adc9..56274002 100644 --- a/src/vector_store/qdrant.py +++ b/src/vector_store/qdrant.py @@ -57,6 +57,10 @@ class QdrantVectorStore(VectorStore): conditions.append( models.FieldCondition(key=k, match=models.MatchAny(any=v["in"])) # pyright: ignore[reportUnknownArgumentType] ) + elif isinstance(v, list): + conditions.append( + models.FieldCondition(key=k, match=models.MatchAny(any=v)) # pyright: ignore[reportUnknownArgumentType] + ) else: conditions.append( models.FieldCondition(key=k, match=models.MatchValue(value=v)) # pyright: ignore[reportArgumentType] @@ -71,7 +75,7 @@ class QdrantVectorStore(VectorStore): models.PointStruct( id=_point_id(v.id), vector=v.embedding, - payload={"_id": v.id, **v.metadata}, + payload={**v.metadata, "_id": v.id}, ) for v in vectors ] @@ -93,16 +97,24 @@ class QdrantVectorStore(VectorStore): top_k: int = 10, filters: dict[str, Any] | None = None, max_distance: float | None = None, + include_attributes: bool | list[str] = True, ) -> list[VectorQueryResult]: if not await self._client.collection_exists(namespace): return [] + if include_attributes is False: + with_payload: bool | list[str] = ["_id"] + elif isinstance(include_attributes, list): + with_payload = ["_id", *include_attributes] + else: + with_payload = True + response = await self._client.query_points( collection_name=namespace, query=embedding, limit=top_k, query_filter=self._build_filter(filters) if filters else None, - with_payload=True, + with_payload=with_payload, ) results: list[VectorQueryResult] = [] @@ -136,3 +148,20 @@ class QdrantVectorStore(VectorStore): async def close(self) -> None: await self._client.close() + + async def probe_namespace_dim(self, namespace: str) -> int | None: + if not await self._client.collection_exists(namespace): + return None + + vectors = (await self._client.get_collection(namespace)).config.params.vectors + if vectors is None: + raise VectorStoreError( + f"Qdrant collection {namespace!r} has no vector configuration" + ) + if isinstance(vectors, dict): + if not vectors: + raise VectorStoreError( + f"Qdrant collection {namespace!r} has empty named-vector configuration" + ) + vectors = next(iter(vectors.values())) + return int(vectors.size) diff --git a/tests/vector_store/test_qdrant.py b/tests/vector_store/test_qdrant.py new file mode 100644 index 00000000..85462503 --- /dev/null +++ b/tests/vector_store/test_qdrant.py @@ -0,0 +1,178 @@ +from __future__ import annotations + +import uuid +from types import SimpleNamespace +from typing import Any +from unittest.mock import AsyncMock, MagicMock + +import pytest +from qdrant_client import models + +from src.exceptions import VectorStoreError +from src.vector_store.qdrant import ( + QdrantVectorStore, + _point_id, # pyright: ignore[reportPrivateUsage] +) + + +@pytest.fixture +def store(monkeypatch: pytest.MonkeyPatch) -> QdrantVectorStore: + monkeypatch.setattr("src.vector_store.qdrant.AsyncQdrantClient", MagicMock()) + return QdrantVectorStore() + + +def _mock_client(store: QdrantVectorStore) -> MagicMock: + client = MagicMock() + client.collection_exists = AsyncMock(return_value=True) + client.query_points = AsyncMock(return_value=SimpleNamespace(points=[])) + client.get_collection = AsyncMock() + store._client = client # pyright: ignore[reportPrivateUsage] + return client + + +def _hit(*, id: str, score: float, payload: dict[str, Any]) -> SimpleNamespace: + return SimpleNamespace(id=id, score=score, payload=payload) + + +def test_point_id_is_deterministic_and_a_valid_uuid() -> None: + assert _point_id("user_123") == _point_id("user_123") + assert _point_id("user_123") != _point_id("user_456") + uuid.UUID(_point_id("user_123")) + + +def test_build_filter_membership(store: QdrantVectorStore) -> None: + f = store._build_filter({"session_name": {"in": ["s1", "s2"]}}) # pyright: ignore[reportPrivateUsage] + assert f is not None and f.must is not None + + +@pytest.mark.asyncio +async def test_query_returns_empty_when_collection_missing( + store: QdrantVectorStore, +) -> None: + client = _mock_client(store) + client.collection_exists = AsyncMock(return_value=False) + + results = await store.query("honcho.msg.missing", [0.1, 0.2, 0.3, 0.4]) + + assert results == [] + client.query_points.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_query_include_attributes_false_still_recovers_id( + store: QdrantVectorStore, +) -> None: + client = _mock_client(store) + + await store.query("honcho.msg.test", [0.1, 0.2, 0.3, 0.4], include_attributes=False) + + assert client.query_points.await_args.kwargs["with_payload"] == ["_id"] + + +@pytest.mark.asyncio +async def test_query_attribute_list_projects_id_plus_listed( + store: QdrantVectorStore, +) -> None: + client = _mock_client(store) + + await store.query( + "honcho.msg.test", + [0.1, 0.2, 0.3, 0.4], + include_attributes=["message_id"], + ) + + assert client.query_points.await_args.kwargs["with_payload"] == [ + "_id", + "message_id", + ] + + +@pytest.mark.asyncio +async def test_query_converts_hits_to_results_with_distance_and_metadata( + store: QdrantVectorStore, +) -> None: + client = _mock_client(store) + client.query_points = AsyncMock( + return_value=SimpleNamespace( + points=[ + _hit( + id="", + score=0.88, + payload={"_id": "vec_1", "message_id": "msg_1"}, + ), + _hit(id="", score=0.66, payload={"_id": "vec_2"}), + ] + ) + ) + + results = await store.query("honcho.msg.test", [0.1, 0.2, 0.3, 0.4]) + + assert [r.id for r in results] == ["vec_1", "vec_2"] + assert results[0].score == 1.0 - 0.88 + assert results[1].score == 1.0 - 0.66 + assert results[0].metadata == {"message_id": "msg_1"} + assert results[1].metadata == {} + + +@pytest.mark.asyncio +async def test_query_filters_by_max_distance(store: QdrantVectorStore) -> None: + client = _mock_client(store) + client.query_points = AsyncMock( + return_value=SimpleNamespace( + points=[ + _hit(id="", score=0.95, payload={"_id": "vec_close"}), + _hit(id="", score=0.1, payload={"_id": "vec_far"}), + ] + ) + ) + + results = await store.query( + "honcho.msg.test", + [0.1, 0.2, 0.3, 0.4], + max_distance=0.5, + ) + + assert [r.id for r in results] == ["vec_close"] + + +@pytest.mark.asyncio +async def test_probe_returns_none_for_missing_collection( + store: QdrantVectorStore, +) -> None: + client = _mock_client(store) + client.collection_exists = AsyncMock(return_value=False) + + assert await store.probe_namespace_dim("does_not_exist") is None + + +@pytest.mark.asyncio +async def test_probe_returns_declared_dim(store: QdrantVectorStore) -> None: + client = _mock_client(store) + client.get_collection = AsyncMock( + return_value=SimpleNamespace( + config=SimpleNamespace( + params=SimpleNamespace( + vectors=models.VectorParams( + size=768, distance=models.Distance.COSINE + ) + ) + ) + ) + ) + + assert await store.probe_namespace_dim("probe_test") == 768 + + +@pytest.mark.asyncio +async def test_probe_raises_when_vector_config_missing( + store: QdrantVectorStore, +) -> None: + client = _mock_client(store) + client.get_collection = AsyncMock( + return_value=SimpleNamespace( + config=SimpleNamespace(params=SimpleNamespace(vectors=None)) + ) + ) + + with pytest.raises(VectorStoreError): + await store.probe_namespace_dim("corrupt") diff --git a/uv.lock b/uv.lock index f3584c35..027e8bd8 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,10 @@ version = 1 revision = 3 requires-python = ">=3.13" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version < '3.14'", +] [options] exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. @@ -822,26 +826,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/db/1d56e5f5823257b291962d6c0ce106146c6447f405b60b234c4f222a7cde/grpcio-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:dfab85db094068ff42e2a3563f60ab3dddcc9d6488a35abf0132daec13209c8a", size = 6055009, upload-time = "2026-03-30T08:46:46.265Z" }, - { url = "https://files.pythonhosted.org/packages/6e/18/c83f3cad64c5ca63bca7e91e5e46b0d026afc5af9d0a9972472ceba294b3/grpcio-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5c07e82e822e1161354e32da2662f741a4944ea955f9f580ec8fb409dd6f6060", size = 12035295, upload-time = "2026-03-30T08:46:49.099Z" }, - { url = "https://files.pythonhosted.org/packages/0f/8e/e14966b435be2dda99fbe89db9525ea436edc79780431a1c2875a3582644/grpcio-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba0915d51fd4ced2db5ff719f84e270afe0e2d4c45a7bdb1e8d036e4502928c2", size = 6610297, upload-time = "2026-03-30T08:46:52.123Z" }, - { url = "https://files.pythonhosted.org/packages/cc/26/d5eb38f42ce0e3fdc8174ea4d52036ef8d58cc4426cb800f2610f625dd75/grpcio-1.80.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3cb8130ba457d2aa09fa6b7c3ed6b6e4e6a2685fce63cb803d479576c4d80e21", size = 7300208, upload-time = "2026-03-30T08:46:54.859Z" }, - { url = "https://files.pythonhosted.org/packages/25/51/bd267c989f85a17a5b3eea65a6feb4ff672af41ca614e5a0279cc0ea381c/grpcio-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09e5e478b3d14afd23f12e49e8b44c8684ac3c5f08561c43a5b9691c54d136ab", size = 6813442, upload-time = "2026-03-30T08:46:57.056Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d9/d80eef735b19e9169e30164bbf889b46f9df9127598a83d174eb13a48b26/grpcio-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:00168469238b022500e486c1c33916acf2f2a9b2c022202cf8a1885d2e3073c1", size = 7414743, upload-time = "2026-03-30T08:46:59.682Z" }, - { url = "https://files.pythonhosted.org/packages/de/f2/567f5bd5054398ed6b0509b9a30900376dcf2786bd936812098808b49d8d/grpcio-1.80.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8502122a3cc1714038e39a0b071acb1207ca7844208d5ea0d091317555ee7106", size = 8426046, upload-time = "2026-03-30T08:47:02.474Z" }, - { url = "https://files.pythonhosted.org/packages/62/29/73ef0141b4732ff5eacd68430ff2512a65c004696997f70476a83e548e7e/grpcio-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce1794f4ea6cc3ca29463f42d665c32ba1b964b48958a66497917fe9069f26e6", size = 7851641, upload-time = "2026-03-30T08:47:05.462Z" }, - { url = "https://files.pythonhosted.org/packages/46/69/abbfa360eb229a8623bab5f5a4f8105e445bd38ce81a89514ba55d281ad0/grpcio-1.80.0-cp311-cp311-win32.whl", hash = "sha256:51b4a7189b0bef2aa30adce3c78f09c83526cf3dddb24c6a96555e3b97340440", size = 4154368, upload-time = "2026-03-30T08:47:08.027Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d4/ae92206d01183b08613e846076115f5ac5991bae358d2a749fa864da5699/grpcio-1.80.0-cp311-cp311-win_amd64.whl", hash = "sha256:02e64bb0bb2da14d947a49e6f120a75e947250aebe65f9629b62bb1f5c14e6e9", size = 4894235, upload-time = "2026-03-30T08:47:10.839Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e8/a2b749265eb3415abc94f2e619bbd9e9707bebdda787e61c593004ec927a/grpcio-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:c624cc9f1008361014378c9d776de7182b11fe8b2e5a81bc69f23a295f2a1ad0", size = 6015616, upload-time = "2026-03-30T08:47:13.428Z" }, - { url = "https://files.pythonhosted.org/packages/3e/97/b1282161a15d699d1e90c360df18d19165a045ce1c343c7f313f5e8a0b77/grpcio-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f49eddcac43c3bf350c0385366a58f36bed8cc2c0ec35ef7b74b49e56552c0c2", size = 12014204, upload-time = "2026-03-30T08:47:15.873Z" }, - { url = "https://files.pythonhosted.org/packages/6e/5e/d319c6e997b50c155ac5a8cb12f5173d5b42677510e886d250d50264949d/grpcio-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d334591df610ab94714048e0d5b4f3dd5ad1bee74dfec11eee344220077a79de", size = 6563866, upload-time = "2026-03-30T08:47:18.588Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f6/fdd975a2cb4d78eb67769a7b3b3830970bfa2e919f1decf724ae4445f42c/grpcio-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0cb517eb1d0d0aaf1d87af7cc5b801d686557c1d88b2619f5e31fab3c2315921", size = 7273060, upload-time = "2026-03-30T08:47:21.113Z" }, - { url = "https://files.pythonhosted.org/packages/db/f0/a3deb5feba60d9538a962913e37bd2e69a195f1c3376a3dd44fe0427e996/grpcio-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e78c4ac0d97dc2e569b2f4bcbbb447491167cb358d1a389fc4af71ab6f70411", size = 6782121, upload-time = "2026-03-30T08:47:23.827Z" }, - { url = "https://files.pythonhosted.org/packages/ca/84/36c6dcfddc093e108141f757c407902a05085e0c328007cb090d56646cdf/grpcio-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2ed770b4c06984f3b47eb0517b1c69ad0b84ef3f40128f51448433be904634cd", size = 7383811, upload-time = "2026-03-30T08:47:26.517Z" }, - { url = "https://files.pythonhosted.org/packages/7c/ef/f3a77e3dc5b471a0ec86c564c98d6adfa3510d38f8ee99010410858d591e/grpcio-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:256507e2f524092f1473071a05e65a5b10d84b82e3ff24c5b571513cfaa61e2f", size = 8393860, upload-time = "2026-03-30T08:47:29.439Z" }, - { url = "https://files.pythonhosted.org/packages/9b/8d/9d4d27ed7f33d109c50d6b5ce578a9914aa68edab75d65869a17e630a8d1/grpcio-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a6284a5d907c37db53350645567c522be314bac859a64a7a5ca63b77bb7958f", size = 7830132, upload-time = "2026-03-30T08:47:33.254Z" }, - { url = "https://files.pythonhosted.org/packages/14/e4/9990b41c6d7a44e1e9dee8ac11d7a9802ba1378b40d77468a7761d1ad288/grpcio-1.80.0-cp312-cp312-win32.whl", hash = "sha256:c71309cfce2f22be26aa4a847357c502db6c621f1a49825ae98aa0907595b193", size = 4140904, upload-time = "2026-03-30T08:47:35.319Z" }, - { url = "https://files.pythonhosted.org/packages/2f/2c/296f6138caca1f4b92a31ace4ae1b87dab692fc16a7a3417af3bb3c805bf/grpcio-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe648599c0e37594c4809d81a9e77bd138cc82eb8baa71b6a86af65426723ff", size = 4880944, upload-time = "2026-03-30T08:47:37.831Z" }, { url = "https://files.pythonhosted.org/packages/2f/3a/7c3c25789e3f069e581dc342e03613c5b1cb012c4e8c7d9d5cf960a75856/grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad", size = 6017243, upload-time = "2026-03-30T08:47:40.075Z" }, { url = "https://files.pythonhosted.org/packages/04/19/21a9806eb8240e174fd1ab0cd5b9aa948bb0e05c2f2f55f9d5d7405e6d08/grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0", size = 12010840, upload-time = "2026-03-30T08:47:43.11Z" }, { url = "https://files.pythonhosted.org/packages/18/3a/23347d35f76f639e807fb7a36fad3068aed100996849a33809591f26eca6/grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f", size = 6567644, upload-time = "2026-03-30T08:47:46.806Z" }, @@ -911,7 +895,6 @@ dependencies = [ { name = "pydantic-settings" }, { name = "pyjwt" }, { name = "python-dotenv" }, - { name = "qdrant-client" }, { name = "redis" }, { name = "rich" }, { name = "scikit-learn" }, @@ -928,6 +911,9 @@ lancedb = [ { name = "lancedb", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "pyarrow" }, ] +qdrant = [ + { name = "qdrant-client" }, +] [package.dev-dependencies] dev = [ @@ -971,7 +957,7 @@ requires-dist = [ { name = "pydantic-settings", specifier = ">=2.10.1" }, { name = "pyjwt", specifier = ">=2.10.0" }, { name = "python-dotenv", specifier = ">=1.0.0" }, - { name = "qdrant-client", specifier = ">=1.18.0" }, + { name = "qdrant-client", marker = "extra == 'qdrant'", specifier = ">=1.18.0" }, { name = "redis", specifier = ">=7.0.0,<8.0.0" }, { name = "rich", specifier = ">=13.7.1" }, { name = "scikit-learn", specifier = ">=1.6.0" }, @@ -982,7 +968,7 @@ requires-dist = [ { name = "turbopuffer", specifier = ">=1.8.1" }, { name = "typing-extensions", specifier = ">=4.11.0" }, ] -provides-extras = ["lancedb"] +provides-extras = ["lancedb", "qdrant"] [package.metadata.requires-dev] dev = [ @@ -2484,12 +2470,6 @@ name = "pywin32" version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, - { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, - { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" },