sie-qdrant 0.1.8__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.
sie_qdrant/__init__.py ADDED
@@ -0,0 +1,93 @@
1
+ """SIE integration for Qdrant.
2
+
3
+ This package provides vectorizer helpers for Qdrant:
4
+
5
+ - SIEVectorizer: Compute dense embeddings via SIE for Qdrant collections
6
+ - SIENamedVectorizer: Compute multiple vector types (dense + sparse) for Qdrant named vectors
7
+
8
+ Qdrant supports both dense and sparse vectors natively. Dense vectors are
9
+ stored as ``list[float]``, and sparse vectors use ``SparseVector(indices, values)``
10
+ — no expansion to full vocabulary length is needed (unlike Weaviate).
11
+
12
+ Example usage (dense):
13
+
14
+ from qdrant_client import QdrantClient
15
+ from qdrant_client.models import Distance, VectorParams, PointStruct
16
+ from sie_qdrant import SIEVectorizer
17
+
18
+ vectorizer = SIEVectorizer(
19
+ base_url="http://localhost:8080",
20
+ model="BAAI/bge-m3",
21
+ )
22
+
23
+ qdrant = QdrantClient("http://localhost:6333")
24
+ qdrant.create_collection(
25
+ collection_name="documents",
26
+ vectors_config=VectorParams(size=1024, distance=Distance.COSINE),
27
+ )
28
+
29
+ # Embed and insert
30
+ texts = ["first doc", "second doc"]
31
+ vectors = vectorizer.embed_documents(texts)
32
+ qdrant.upsert(
33
+ collection_name="documents",
34
+ points=[
35
+ PointStruct(id=i, vector=v, payload={"text": t})
36
+ for i, (t, v) in enumerate(zip(texts, vectors))
37
+ ],
38
+ )
39
+
40
+ # Embed query and search
41
+ query_vector = vectorizer.embed_query("search text")
42
+ results = qdrant.query_points(
43
+ collection_name="documents",
44
+ query=query_vector,
45
+ limit=5,
46
+ )
47
+
48
+ Example usage (named vectors for hybrid search):
49
+
50
+ from qdrant_client import QdrantClient
51
+ from qdrant_client.models import (
52
+ Distance, VectorParams, PointStruct,
53
+ SparseVectorParams, SparseVector,
54
+ )
55
+ from sie_qdrant import SIENamedVectorizer
56
+
57
+ vectorizer = SIENamedVectorizer(
58
+ base_url="http://localhost:8080",
59
+ model="BAAI/bge-m3",
60
+ )
61
+
62
+ qdrant = QdrantClient("http://localhost:6333")
63
+ qdrant.create_collection(
64
+ collection_name="documents",
65
+ vectors_config={"dense": VectorParams(size=1024, distance=Distance.COSINE)},
66
+ sparse_vectors_config={"sparse": SparseVectorParams()},
67
+ )
68
+
69
+ # Embed with both dense and sparse in one SIE call
70
+ texts = ["first doc", "second doc"]
71
+ named = vectorizer.embed_documents(texts)
72
+ qdrant.upsert(
73
+ collection_name="documents",
74
+ points=[
75
+ PointStruct(
76
+ id=i,
77
+ vector={
78
+ "dense": v["dense"],
79
+ "sparse": SparseVector(**v["sparse"]),
80
+ },
81
+ payload={"text": t},
82
+ )
83
+ for i, (t, v) in enumerate(zip(texts, named))
84
+ ],
85
+ )
86
+ """
87
+
88
+ from sie_qdrant.vectorizer import SIENamedVectorizer, SIEVectorizer
89
+
90
+ __all__ = [
91
+ "SIENamedVectorizer",
92
+ "SIEVectorizer",
93
+ ]
@@ -0,0 +1,299 @@
1
+ """SIE vectorizer helpers for Qdrant.
2
+
3
+ Qdrant supports both dense and sparse vectors natively. Dense vectors are
4
+ stored as ``list[float]``, and sparse vectors use Qdrant's native
5
+ ``SparseVector(indices=..., values=...)`` format — no expansion needed.
6
+
7
+ These helpers wrap SIE's encode() to produce vectors in the format
8
+ Qdrant expects, handling Item creation and output type selection.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any
14
+
15
+
16
+ class SIEVectorizer:
17
+ """Compute dense embeddings via SIE for Qdrant collections.
18
+
19
+ Wraps ``SIEClient.encode()`` with ``output_types=["dense"]`` and
20
+ converts results to ``list[float]`` — the format Qdrant's
21
+ ``PointStruct(vector=...)`` expects for flat vector collections.
22
+
23
+ Example:
24
+ >>> vectorizer = SIEVectorizer(
25
+ ... base_url="http://localhost:8080",
26
+ ... model="BAAI/bge-m3",
27
+ ... )
28
+ >>> vectors = vectorizer.embed_documents(["hello", "world"])
29
+ >>> query_vec = vectorizer.embed_query("search text")
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ base_url: str = "http://localhost:8080",
35
+ model: str = "BAAI/bge-m3",
36
+ *,
37
+ instruction: str | None = None,
38
+ output_dtype: str | None = None,
39
+ gpu: str | None = None,
40
+ options: dict[str, Any] | None = None,
41
+ timeout_s: float = 180.0,
42
+ ) -> None:
43
+ """Initialize SIE vectorizer.
44
+
45
+ Args:
46
+ base_url: SIE server URL.
47
+ model: Model name for embedding.
48
+ instruction: Instruction prefix for instruction-tuned models (e.g., E5).
49
+ output_dtype: Output data type (e.g., "float32", "float16", "int8", "binary").
50
+ gpu: GPU type for routing (e.g., "l4", "a100").
51
+ options: Model-specific options.
52
+ timeout_s: Request timeout in seconds.
53
+ """
54
+ self._base_url = base_url
55
+ self._model = model
56
+ self._instruction = instruction
57
+ self._output_dtype = output_dtype
58
+ self._gpu = gpu
59
+ self._options = options
60
+ self._timeout_s = timeout_s
61
+ self._client: Any = None
62
+
63
+ @property
64
+ def client(self) -> Any:
65
+ """Lazily initialize the SIE client."""
66
+ if self._client is None:
67
+ from sie_sdk import SIEClient
68
+
69
+ self._client = SIEClient(
70
+ self._base_url,
71
+ timeout_s=self._timeout_s,
72
+ gpu=self._gpu,
73
+ options=self._options,
74
+ )
75
+ return self._client
76
+
77
+ def embed_documents(self, texts: list[str]) -> list[list[float]]:
78
+ """Embed a list of documents.
79
+
80
+ Args:
81
+ texts: Document texts to embed.
82
+
83
+ Returns:
84
+ List of dense vectors, one per document. Each vector is a
85
+ ``list[float]`` ready for ``PointStruct(vector=...)``.
86
+ """
87
+ if not texts:
88
+ return []
89
+
90
+ from sie_sdk.types import Item
91
+
92
+ items = [Item(text=text) for text in texts]
93
+ results = self.client.encode(
94
+ self._model,
95
+ items,
96
+ output_types=["dense"],
97
+ instruction=self._instruction,
98
+ output_dtype=self._output_dtype,
99
+ )
100
+
101
+ return [self._extract_dense(result) for result in results]
102
+
103
+ def embed_query(self, text: str) -> list[float]:
104
+ """Embed a single query text.
105
+
106
+ Passes ``is_query=True`` so instruction-tuned models (e.g., E5)
107
+ apply query-specific prefixes or behavior.
108
+
109
+ Args:
110
+ text: Query text to embed.
111
+
112
+ Returns:
113
+ Dense vector as ``list[float]``, ready for
114
+ ``client.query_points()``.
115
+ """
116
+ from sie_sdk.types import Item
117
+
118
+ result = self.client.encode(
119
+ self._model,
120
+ Item(text=text),
121
+ output_types=["dense"],
122
+ instruction=self._instruction,
123
+ output_dtype=self._output_dtype,
124
+ options={"is_query": True},
125
+ )
126
+
127
+ return self._extract_dense(result)
128
+
129
+ def _extract_dense(self, result: Any) -> list[float]:
130
+ """Extract dense embedding values from SDK result."""
131
+ dense = result.get("dense") if isinstance(result, dict) else getattr(result, "dense", None)
132
+ if dense is None:
133
+ msg = "Encode result missing dense embedding"
134
+ raise ValueError(msg)
135
+ return dense.tolist() if hasattr(dense, "tolist") else list(dense)
136
+
137
+
138
+ class SIENamedVectorizer:
139
+ """Compute multiple vector types via SIE for Qdrant named vectors.
140
+
141
+ Uses a single SIE encode() call to produce multiple output types
142
+ (e.g., dense + sparse) for Qdrant's named vector feature. This
143
+ maps to collections configured with multiple vector configs.
144
+
145
+ Unlike Weaviate, Qdrant supports sparse vectors natively via
146
+ ``SparseVector(indices=..., values=...)``, so sparse output is
147
+ returned in its native dict format without expansion.
148
+
149
+ Example:
150
+ >>> vectorizer = SIENamedVectorizer(
151
+ ... base_url="http://localhost:8080",
152
+ ... model="BAAI/bge-m3",
153
+ ... output_types=["dense", "sparse"],
154
+ ... )
155
+ >>> named_vectors = vectorizer.embed_documents(["hello", "world"])
156
+ >>> # [{"dense": [0.1, ...], "sparse": {"indices": [...], "values": [...]}}, ...]
157
+ """
158
+
159
+ def __init__(
160
+ self,
161
+ base_url: str = "http://localhost:8080",
162
+ model: str = "BAAI/bge-m3",
163
+ *,
164
+ output_types: list[str] | None = None,
165
+ instruction: str | None = None,
166
+ output_dtype: str | None = None,
167
+ gpu: str | None = None,
168
+ options: dict[str, Any] | None = None,
169
+ timeout_s: float = 180.0,
170
+ ) -> None:
171
+ """Initialize SIE named vectorizer.
172
+
173
+ Args:
174
+ base_url: SIE server URL.
175
+ model: Model name for embedding.
176
+ output_types: Vector types to produce (default: ["dense", "sparse"]).
177
+ Must match the named vector config on the Qdrant collection.
178
+ instruction: Instruction prefix for instruction-tuned models (e.g., E5).
179
+ output_dtype: Output data type (e.g., "float32", "float16", "int8", "binary").
180
+ gpu: GPU type for routing (e.g., "l4", "a100").
181
+ options: Model-specific options.
182
+ timeout_s: Request timeout in seconds.
183
+ """
184
+ self._base_url = base_url
185
+ self._model = model
186
+ self._output_types = output_types or ["dense", "sparse"]
187
+ self._instruction = instruction
188
+ self._output_dtype = output_dtype
189
+ self._gpu = gpu
190
+ self._options = options
191
+ self._timeout_s = timeout_s
192
+ self._client: Any = None
193
+
194
+ @property
195
+ def client(self) -> Any:
196
+ """Lazily initialize the SIE client."""
197
+ if self._client is None:
198
+ from sie_sdk import SIEClient
199
+
200
+ self._client = SIEClient(
201
+ self._base_url,
202
+ timeout_s=self._timeout_s,
203
+ gpu=self._gpu,
204
+ options=self._options,
205
+ )
206
+ return self._client
207
+
208
+ def embed_documents(self, texts: list[str]) -> list[dict[str, Any]]:
209
+ """Embed documents with multiple vector types.
210
+
211
+ Args:
212
+ texts: Document texts to embed.
213
+
214
+ Returns:
215
+ List of dicts mapping vector name to vector values.
216
+ Dense vectors are ``list[float]``, sparse vectors are
217
+ ``{"indices": list[int], "values": list[float]}``.
218
+ """
219
+ if not texts:
220
+ return []
221
+
222
+ from sie_sdk.types import Item
223
+
224
+ items = [Item(text=text) for text in texts]
225
+ results = self.client.encode(
226
+ self._model,
227
+ items,
228
+ output_types=self._output_types,
229
+ instruction=self._instruction,
230
+ output_dtype=self._output_dtype,
231
+ )
232
+
233
+ return [self._extract_named(result) for result in results]
234
+
235
+ def embed_query(self, text: str) -> dict[str, Any]:
236
+ """Embed a query with multiple vector types.
237
+
238
+ Passes ``is_query=True`` so instruction-tuned models apply
239
+ query-specific behavior.
240
+
241
+ Args:
242
+ text: Query text to embed.
243
+
244
+ Returns:
245
+ Dict mapping vector name to vector values.
246
+ """
247
+ from sie_sdk.types import Item
248
+
249
+ result = self.client.encode(
250
+ self._model,
251
+ Item(text=text),
252
+ output_types=self._output_types,
253
+ instruction=self._instruction,
254
+ output_dtype=self._output_dtype,
255
+ options={"is_query": True},
256
+ )
257
+
258
+ return self._extract_named(result)
259
+
260
+ def _extract_named(self, result: Any) -> dict[str, Any]:
261
+ """Extract all requested vector types from SDK result.
262
+
263
+ Dense vectors are converted to ``list[float]``.
264
+ Sparse vectors are returned as ``{"indices": list[int], "values": list[float]}``
265
+ matching Qdrant's native ``SparseVector`` format.
266
+ """
267
+ named: dict[str, Any] = {}
268
+ for output_type in self._output_types:
269
+ raw = result.get(output_type) if isinstance(result, dict) else getattr(result, output_type, None)
270
+ if raw is None:
271
+ continue
272
+ if output_type == "sparse":
273
+ named[output_type] = self._format_sparse(raw)
274
+ else:
275
+ named[output_type] = raw.tolist() if hasattr(raw, "tolist") else list(raw)
276
+ return named
277
+
278
+ @staticmethod
279
+ def _format_sparse(sparse: Any) -> dict[str, list]:
280
+ """Format SIE sparse output for Qdrant's SparseVector.
281
+
282
+ SIE returns sparse vectors as ``{"indices": [...], "values": [...]}``.
283
+ Qdrant's ``SparseVector`` accepts the same format natively, so we
284
+ just convert numpy arrays to Python lists.
285
+
286
+ Unlike Weaviate (which requires expanding sparse to full vocabulary
287
+ length), Qdrant stores sparse vectors efficiently in their native
288
+ indices+values form.
289
+ """
290
+ indices = sparse.get("indices") if isinstance(sparse, dict) else getattr(sparse, "indices", None)
291
+ values = sparse.get("values") if isinstance(sparse, dict) else getattr(sparse, "values", None)
292
+
293
+ if indices is None or values is None:
294
+ return {"indices": [], "values": []}
295
+
296
+ indices_list = indices.tolist() if hasattr(indices, "tolist") else list(indices)
297
+ values_list = values.tolist() if hasattr(values, "tolist") else list(values)
298
+
299
+ return {"indices": indices_list, "values": values_list}
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: sie-qdrant
3
+ Version: 0.1.8
4
+ Summary: SIE integration for Qdrant
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: qdrant-client>=1.7
7
+ Requires-Dist: sie-sdk>=0.1.0
8
+ Provides-Extra: dev
9
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
10
+ Requires-Dist: pytest>=8.0; extra == 'dev'
11
+ Description-Content-Type: text/markdown
12
+
13
+ # sie-qdrant
14
+
15
+ SIE integration for Qdrant.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install sie-qdrant
21
+ ```
22
+
23
+ ## Dense embeddings
24
+
25
+ ```python
26
+ from qdrant_client import QdrantClient
27
+ from qdrant_client.models import Distance, VectorParams, PointStruct
28
+ from sie_qdrant import SIEVectorizer
29
+
30
+ vectorizer = SIEVectorizer(base_url="http://localhost:8080", model="BAAI/bge-m3")
31
+
32
+ qdrant = QdrantClient("http://localhost:6333")
33
+ qdrant.create_collection(
34
+ collection_name="documents",
35
+ vectors_config=VectorParams(size=1024, distance=Distance.COSINE),
36
+ )
37
+
38
+ texts = ["first doc", "second doc"]
39
+ vectors = vectorizer.embed_documents(texts)
40
+ qdrant.upsert(
41
+ collection_name="documents",
42
+ points=[
43
+ PointStruct(id=i, vector=v, payload={"text": t})
44
+ for i, (t, v) in enumerate(zip(texts, vectors))
45
+ ],
46
+ )
47
+
48
+ query_vec = vectorizer.embed_query("search text")
49
+ results = qdrant.query_points(
50
+ collection_name="documents", query=query_vec, limit=5
51
+ )
52
+ ```
53
+
54
+ ## Named vectors (dense + sparse)
55
+
56
+ SIE's multi-output encode produces dense and sparse vectors in one call.
57
+ Qdrant supports sparse vectors natively via `SparseVector(indices, values)`,
58
+ so no expansion to full vocabulary length is needed:
59
+
60
+ ```python
61
+ from qdrant_client import QdrantClient
62
+ from qdrant_client.models import (
63
+ Distance, VectorParams, PointStruct,
64
+ SparseVectorParams, SparseVector,
65
+ )
66
+ from sie_qdrant import SIENamedVectorizer
67
+
68
+ vectorizer = SIENamedVectorizer(
69
+ base_url="http://localhost:8080",
70
+ model="BAAI/bge-m3",
71
+ output_types=["dense", "sparse"],
72
+ )
73
+
74
+ qdrant = QdrantClient("http://localhost:6333")
75
+ qdrant.create_collection(
76
+ collection_name="documents",
77
+ vectors_config={"dense": VectorParams(size=1024, distance=Distance.COSINE)},
78
+ sparse_vectors_config={"sparse": SparseVectorParams()},
79
+ )
80
+
81
+ named = vectorizer.embed_documents(["hello world"])
82
+ qdrant.upsert(
83
+ collection_name="documents",
84
+ points=[
85
+ PointStruct(
86
+ id=0,
87
+ vector={
88
+ "dense": named[0]["dense"],
89
+ "sparse": SparseVector(**named[0]["sparse"]),
90
+ },
91
+ payload={"text": "hello world"},
92
+ )
93
+ ],
94
+ )
95
+ ```
96
+
97
+ **Storage advantage:** Unlike integrations that expand sparse vectors to full
98
+ vocabulary length (~30K floats), Qdrant stores sparse vectors in their native
99
+ indices+values form, making hybrid search storage-efficient.
100
+
101
+ ## Testing
102
+
103
+ ```bash
104
+ # Unit tests (no server needed)
105
+ pytest
106
+
107
+ # Integration tests (requires SIE + Qdrant)
108
+ pytest -m integration
109
+ ```
@@ -0,0 +1,5 @@
1
+ sie_qdrant/__init__.py,sha256=ublrXDeyfPEJg5gmYT1myTr0JyLJMYuajDqp1NOQ-Xw,2836
2
+ sie_qdrant/vectorizer.py,sha256=lB2UB3Ug-bXs-IUmmSGM9GpoQqUiCXFDephBfOHrEK0,10507
3
+ sie_qdrant-0.1.8.dist-info/METADATA,sha256=1yZ95vr_A02Od4W290Mj8vTOYm_8WHvyqE7kHxuqc44,2824
4
+ sie_qdrant-0.1.8.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
5
+ sie_qdrant-0.1.8.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any