qdrant-ruby 0.9.10 → 0.11.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -1
- data/Gemfile +2 -2
- data/Gemfile.lock +52 -48
- data/README.md +90 -52
- data/Rakefile +12 -1
- data/lib/qdrant/base.rb +8 -0
- data/lib/qdrant/client/connection.rb +51 -0
- data/lib/qdrant/client/request.rb +95 -0
- data/lib/qdrant/client/response.rb +41 -0
- data/lib/qdrant/client.rb +22 -15
- data/lib/qdrant/clusters.rb +1 -1
- data/lib/qdrant/collections.rb +3 -1
- data/lib/qdrant/points.rb +10 -3
- data/lib/qdrant/service.rb +4 -0
- data/lib/qdrant/version.rb +1 -1
- data/script/e2e.rb +130 -0
- metadata +9 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6899d046fe093cba0a8f390313bba21f2febb5319984fa516b99d2d3a825af17
|
|
4
|
+
data.tar.gz: e99fb180f6d3adca5f91133d5b88869a11fb5b004147c3ddb0987428b479713d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 619eb3d6fa73efb8bc57351418c6db3824f9b2fa416911be4555278944489d29cebf9892f26c23ff7406b631450a992059e8c84f05f64845a3d45799f9844fe5
|
|
7
|
+
data.tar.gz: c336aa478ac184ce7c70543bd5faaa15ce3d3340a77862ef3d0ccbbcfbe80430348ca0977f83591ace3309d18d108140e3e7e304e3d037c81711fec1939a35f9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.11.0] - 2026-10-21
|
|
4
|
+
- Fix `collections.restore_snapshot` to use PUT (POST always returned 404)
|
|
5
|
+
- Fix `clusters.remove_peer` to call `DELETE /cluster/peer/{peer_id}` instead of `POST /cluster/recover`
|
|
6
|
+
- `client.aliases` no longer caches the first response
|
|
7
|
+
- `points.list` now requires `ids:` (the API always required it)
|
|
8
|
+
- Deprecation warnings for `points.search`, `batch_search`, `recommend`, `batch_recommend` (use `points.query`), the lock API and `init_from:` (both removed in Qdrant 1.16)
|
|
9
|
+
- README: fix inaccurate examples and remove the nonexistent `backups` section
|
|
10
|
+
|
|
11
|
+
## [0.10.0] - 2026-08-12
|
|
12
|
+
- Drop Faraday dependency
|
|
13
|
+
|
|
14
|
+
## [0.9.10] - 2025-11-23
|
|
15
|
+
|
|
3
16
|
## [0.9.9] - 2024-04-11
|
|
4
17
|
- Allow sparse_vectors to be passed to collections.create
|
|
5
18
|
- Support query endpoint
|
|
@@ -14,7 +27,8 @@
|
|
|
14
27
|
- Updated Points#delete() method: Removed the requirement to specify points: in parameters. Now generates an error if neither points: nor filters: are provided, aligning with delete_points documentation standards.
|
|
15
28
|
|
|
16
29
|
## [0.9.5] - 2024-01-12
|
|
17
|
-
- Bugfix: ArgumentError for filter in points delete
|
|
30
|
+
- Bugfix: ArgumentError for filter in points delete
|
|
31
|
+
|
|
18
32
|
## [0.9.4] - 2023-08-31
|
|
19
33
|
- Introduce `Points#get_all()` method
|
|
20
34
|
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
qdrant-ruby (0.
|
|
5
|
-
|
|
4
|
+
qdrant-ruby (0.11.0)
|
|
5
|
+
logger
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
9
9
|
specs:
|
|
10
|
-
ast (2.4.
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
ast (2.4.3)
|
|
11
|
+
byebug (13.0.0)
|
|
12
|
+
reline (>= 0.6.0)
|
|
13
13
|
coderay (1.1.3)
|
|
14
14
|
diff-lcs (1.6.2)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
ruby2_keywords (>= 0.0.4)
|
|
19
|
-
faraday-net_http (3.0.2)
|
|
20
|
-
json (2.7.4)
|
|
21
|
-
language_server-protocol (3.17.0.3)
|
|
15
|
+
io-console (0.9.2)
|
|
16
|
+
json (3.1.0)
|
|
17
|
+
language_server-protocol (3.17.0.6)
|
|
22
18
|
lint_roller (1.1.0)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
logger (1.7.0)
|
|
20
|
+
method_source (1.1.0)
|
|
21
|
+
parallel (2.3.0)
|
|
22
|
+
parser (3.3.12.0)
|
|
26
23
|
ast (~> 2.4.1)
|
|
27
24
|
racc
|
|
28
|
-
|
|
25
|
+
prism (1.9.0)
|
|
26
|
+
pry (0.16.0)
|
|
29
27
|
coderay (~> 1.1)
|
|
30
28
|
method_source (~> 1.0)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
reline (>= 0.6.0)
|
|
30
|
+
pry-byebug (3.12.0)
|
|
31
|
+
byebug (~> 13.0)
|
|
32
|
+
pry (>= 0.13, < 0.17)
|
|
34
33
|
racc (1.8.1)
|
|
35
34
|
rainbow (3.1.1)
|
|
36
|
-
rake (13.
|
|
37
|
-
regexp_parser (2.
|
|
38
|
-
|
|
35
|
+
rake (13.4.2)
|
|
36
|
+
regexp_parser (2.13.1)
|
|
37
|
+
reline (0.7.0)
|
|
38
|
+
io-console (~> 0.5)
|
|
39
39
|
rspec (3.13.2)
|
|
40
40
|
rspec-core (~> 3.13.0)
|
|
41
41
|
rspec-expectations (~> 3.13.0)
|
|
@@ -45,40 +45,44 @@ GEM
|
|
|
45
45
|
rspec-expectations (3.13.5)
|
|
46
46
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
47
47
|
rspec-support (~> 3.13.0)
|
|
48
|
-
rspec-mocks (3.13.
|
|
48
|
+
rspec-mocks (3.13.8)
|
|
49
49
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
50
50
|
rspec-support (~> 3.13.0)
|
|
51
|
-
rspec-support (3.13.
|
|
52
|
-
rubocop (1.
|
|
53
|
-
json (
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
rspec-support (3.13.7)
|
|
52
|
+
rubocop (1.91.0)
|
|
53
|
+
json (>= 2.3)
|
|
54
|
+
language_server-protocol (~> 3.17.0.2)
|
|
55
|
+
lint_roller (~> 1.1.0)
|
|
56
|
+
parallel (>= 1.10)
|
|
57
|
+
parser (>= 3.3.0.2)
|
|
56
58
|
rainbow (>= 2.2.2, < 4.0)
|
|
57
|
-
regexp_parser (>=
|
|
58
|
-
|
|
59
|
-
rubocop-ast (>= 1.28.0, < 2.0)
|
|
59
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
60
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
60
61
|
ruby-progressbar (~> 1.7)
|
|
61
|
-
unicode-display_width (>= 2.4.0, <
|
|
62
|
-
rubocop-ast (1.
|
|
63
|
-
parser (>= 3.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
63
|
+
rubocop-ast (1.50.0)
|
|
64
|
+
parser (>= 3.3.7.2)
|
|
65
|
+
prism (~> 1.7)
|
|
66
|
+
rubocop-performance (1.26.1)
|
|
67
|
+
lint_roller (~> 1.1)
|
|
68
|
+
rubocop (>= 1.75.0, < 2.0)
|
|
69
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
67
70
|
ruby-progressbar (1.13.0)
|
|
68
|
-
|
|
69
|
-
standard (1.28.5)
|
|
71
|
+
standard (1.57.0)
|
|
70
72
|
language_server-protocol (~> 3.17.0.2)
|
|
71
73
|
lint_roller (~> 1.0)
|
|
72
|
-
rubocop (~> 1.
|
|
74
|
+
rubocop (~> 1.91.0)
|
|
73
75
|
standard-custom (~> 1.0.0)
|
|
74
|
-
standard-performance (~> 1.
|
|
76
|
+
standard-performance (~> 1.8)
|
|
75
77
|
standard-custom (1.0.2)
|
|
76
78
|
lint_roller (~> 1.0)
|
|
77
79
|
rubocop (~> 1.50)
|
|
78
|
-
standard-performance (1.0
|
|
79
|
-
lint_roller (~> 1.
|
|
80
|
-
rubocop-performance (~> 1.
|
|
81
|
-
unicode-display_width (
|
|
80
|
+
standard-performance (1.9.0)
|
|
81
|
+
lint_roller (~> 1.1)
|
|
82
|
+
rubocop-performance (~> 1.26.0)
|
|
83
|
+
unicode-display_width (3.3.0)
|
|
84
|
+
unicode-emoji (~> 4.3)
|
|
85
|
+
unicode-emoji (4.3.0)
|
|
82
86
|
|
|
83
87
|
PLATFORMS
|
|
84
88
|
arm64-darwin-23
|
|
@@ -90,9 +94,9 @@ PLATFORMS
|
|
|
90
94
|
DEPENDENCIES
|
|
91
95
|
pry-byebug (~> 3.9)
|
|
92
96
|
qdrant-ruby!
|
|
93
|
-
rake (~> 13.
|
|
97
|
+
rake (~> 13.4)
|
|
94
98
|
rspec (~> 3.13)
|
|
95
|
-
standard (~> 1.
|
|
99
|
+
standard (~> 1.57.0)
|
|
96
100
|
|
|
97
101
|
BUNDLED WITH
|
|
98
|
-
2.
|
|
102
|
+
2.5.3
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Qdrant
|
|
2
2
|
|
|
3
3
|
<p>
|
|
4
|
-
<img alt='Qdrant logo' src='https://qdrant.tech/
|
|
4
|
+
<img alt='Qdrant logo' src='https://qdrant.tech/img/brand-resources-logos/qdrant-brandmark-red.svg' height='50' />
|
|
5
5
|
+
|
|
6
6
|
<img alt='Ruby logo' src='https://user-images.githubusercontent.com/541665/230231593-43861278-4550-421d-a543-fd3553aac4f6.png' height='40' />
|
|
7
7
|
</p>
|
|
@@ -13,7 +13,7 @@ Part of the [Langchain.rb](https://github.com/andreibondarev/langchainrb) stack.
|
|
|
13
13
|

|
|
14
14
|
[](https://badge.fury.io/rb/qdrant-ruby)
|
|
15
15
|
[](http://rubydoc.info/gems/qdrant-ruby)
|
|
16
|
-
[](https://github.com/andreibondarev/qdrant-ruby/blob/main/LICENSE.txt)
|
|
17
17
|
[](https://discord.gg/WDARp7J2n8)
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
@@ -52,6 +52,7 @@ client.collections.get(collection_name: "string")
|
|
|
52
52
|
client.collections.create(
|
|
53
53
|
collection_name: "string", # required
|
|
54
54
|
vectors: {}, # required
|
|
55
|
+
sparse_vectors: nil,
|
|
55
56
|
shard_number: nil,
|
|
56
57
|
replication_factor: nil,
|
|
57
58
|
write_consistency_factor: nil,
|
|
@@ -59,7 +60,6 @@ client.collections.create(
|
|
|
59
60
|
hnsw_config: nil,
|
|
60
61
|
wal_config: nil,
|
|
61
62
|
optimizers_config: nil,
|
|
62
|
-
init_from: nil,
|
|
63
63
|
quantization_config: nil
|
|
64
64
|
)
|
|
65
65
|
|
|
@@ -73,11 +73,14 @@ client.collections.update(
|
|
|
73
73
|
# Drop collection and all associated data
|
|
74
74
|
client.collections.delete(collection_name: "string")
|
|
75
75
|
|
|
76
|
-
# Get list of all aliases
|
|
76
|
+
# Get list of all aliases for a collection
|
|
77
77
|
client.collections.aliases(
|
|
78
|
-
collection_name: "string" #
|
|
78
|
+
collection_name: "string" # required
|
|
79
79
|
)
|
|
80
80
|
|
|
81
|
+
# Get list of all aliases across all collections
|
|
82
|
+
client.aliases
|
|
83
|
+
|
|
81
84
|
# Update aliases of the collections
|
|
82
85
|
client.collections.update_aliases(
|
|
83
86
|
actions: [{
|
|
@@ -101,9 +104,7 @@ client.collections.create_index(
|
|
|
101
104
|
# Delete field index for collection
|
|
102
105
|
client.collections.delete_index(
|
|
103
106
|
collection_name: "string", # required
|
|
104
|
-
field_name: "string"
|
|
105
|
-
wait: "boolean",
|
|
106
|
-
ordering: "ordering"
|
|
107
|
+
field_name: "string" # required
|
|
107
108
|
)
|
|
108
109
|
|
|
109
110
|
# Get cluster information for a collection
|
|
@@ -118,8 +119,7 @@ client.collections.update_cluster(
|
|
|
118
119
|
shard_id: "int",
|
|
119
120
|
to_peer_id: "int",
|
|
120
121
|
from_peer_id: "int"
|
|
121
|
-
}
|
|
122
|
-
timeout: "int"
|
|
122
|
+
}
|
|
123
123
|
)
|
|
124
124
|
|
|
125
125
|
# Create new snapshot for a collection
|
|
@@ -167,28 +167,57 @@ client.points.get(
|
|
|
167
167
|
client.points.get_all(
|
|
168
168
|
collection_name: "string", # required
|
|
169
169
|
ids: "[int]", # required
|
|
170
|
-
with_payload: "boolean"
|
|
171
|
-
with_vector: "boolean"
|
|
170
|
+
with_payload: "boolean",
|
|
171
|
+
with_vector: "boolean",
|
|
172
|
+
consistency: "int/string"
|
|
172
173
|
)
|
|
173
174
|
|
|
174
|
-
#
|
|
175
|
+
# Retrieve full information of points by ids. Same as `get_all`.
|
|
175
176
|
client.points.list(
|
|
176
177
|
collection_name: "string", # required
|
|
177
178
|
ids: "[int/string]", # required
|
|
178
179
|
with_payload: nil,
|
|
179
180
|
with_vector: nil,
|
|
180
181
|
consistency: nil
|
|
181
|
-
|
|
182
182
|
)
|
|
183
183
|
|
|
184
|
-
#
|
|
184
|
+
# Insert or update points. Either `points:` or `batch:` is required.
|
|
185
185
|
client.points.upsert(
|
|
186
186
|
collection_name: "string", # required
|
|
187
|
-
|
|
187
|
+
points: [], # record-oriented: array of point hashes
|
|
188
|
+
batch: {}, # column-oriented: hash of parallel arrays
|
|
188
189
|
wait: "boolean",
|
|
189
190
|
ordering: "string"
|
|
190
191
|
)
|
|
191
192
|
|
|
193
|
+
# Record-oriented upsert: each point is a hash with its own id, vector and payload
|
|
194
|
+
client.points.upsert(
|
|
195
|
+
collection_name: "my_collection",
|
|
196
|
+
points: [
|
|
197
|
+
{id: 1, vector: [0.9, 0.1, 0.1], payload: {color: "red"}},
|
|
198
|
+
{id: 2, vector: [0.1, 0.9, 0.1], payload: {color: "green"}},
|
|
199
|
+
{id: 3, vector: [0.1, 0.1, 0.9], payload: {color: "blue"}}
|
|
200
|
+
]
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Column-oriented (batch) upsert: ids, vectors and payloads are passed as parallel arrays
|
|
204
|
+
client.points.upsert(
|
|
205
|
+
collection_name: "my_collection",
|
|
206
|
+
batch: {
|
|
207
|
+
ids: [1, 2, 3],
|
|
208
|
+
vectors: [
|
|
209
|
+
[0.9, 0.1, 0.1],
|
|
210
|
+
[0.1, 0.9, 0.1],
|
|
211
|
+
[0.1, 0.1, 0.9]
|
|
212
|
+
],
|
|
213
|
+
payloads: [
|
|
214
|
+
{color: "red"},
|
|
215
|
+
{color: "green"},
|
|
216
|
+
{color: "blue"}
|
|
217
|
+
]
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
|
|
192
221
|
# Delete points
|
|
193
222
|
client.points.delete(
|
|
194
223
|
collection_name: "string", # required
|
|
@@ -249,6 +278,28 @@ client.points.scroll(
|
|
|
249
278
|
consistency: "int/string"
|
|
250
279
|
)
|
|
251
280
|
|
|
281
|
+
# Universally query points: covers search, recommend and discover, plus hybrid and multi-stage queries
|
|
282
|
+
client.points.query(
|
|
283
|
+
collection_name: "string", # required
|
|
284
|
+
query: "[float]/{}", # a vector, a point ID, or a query object such as {recommend: {positive: [1]}}
|
|
285
|
+
using: "string",
|
|
286
|
+
prefetch: {},
|
|
287
|
+
filter: {},
|
|
288
|
+
params: {},
|
|
289
|
+
score_threshold: "float",
|
|
290
|
+
limit: "int",
|
|
291
|
+
offset: "int",
|
|
292
|
+
with_payload: "boolean",
|
|
293
|
+
with_vector: "boolean",
|
|
294
|
+
lookup_from: {},
|
|
295
|
+
shard_key: "string",
|
|
296
|
+
consistency: "int/string",
|
|
297
|
+
timeout: "int"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# DEPRECATED (since Qdrant 1.13): `search`, `batch_search`, `recommend` and `batch_recommend`
|
|
301
|
+
# still work but are no longer documented by Qdrant and will be removed. Use `points.query` instead.
|
|
302
|
+
|
|
252
303
|
# Retrieve closest points based on vector similarity and given filtering conditions
|
|
253
304
|
client.points.search(
|
|
254
305
|
collection_name: "string", # required
|
|
@@ -281,9 +332,9 @@ client.points.recommend(
|
|
|
281
332
|
offset: "int",
|
|
282
333
|
with_payload: "boolean",
|
|
283
334
|
with_vector: "boolean",
|
|
284
|
-
score_threshold: "float"
|
|
335
|
+
score_threshold: "float",
|
|
285
336
|
using: "string",
|
|
286
|
-
lookup_from: {}
|
|
337
|
+
lookup_from: {}
|
|
287
338
|
)
|
|
288
339
|
|
|
289
340
|
# Look for the points which are closer to stored positive examples and at the same time further to negative examples.
|
|
@@ -304,58 +355,33 @@ client.points.count(
|
|
|
304
355
|
### Snapshots
|
|
305
356
|
```ruby
|
|
306
357
|
# Get list of snapshots of the whole storage
|
|
307
|
-
client.snapshots.list
|
|
308
|
-
collection_name: "string" # optional
|
|
309
|
-
)
|
|
358
|
+
client.snapshots.list
|
|
310
359
|
|
|
311
360
|
# Create new snapshot of the whole storage
|
|
312
|
-
client.snapshots.create
|
|
313
|
-
collection_name: "string" # required
|
|
314
|
-
)
|
|
361
|
+
client.snapshots.create
|
|
315
362
|
|
|
316
363
|
# Delete snapshot of the whole storage
|
|
317
364
|
client.snapshots.delete(
|
|
318
|
-
|
|
319
|
-
snapshot_name: "string" # required
|
|
365
|
+
snapshot_name: "string" # required
|
|
320
366
|
)
|
|
321
367
|
|
|
322
368
|
# Download specified snapshot of the whole storage as a file
|
|
323
369
|
client.snapshots.download(
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
filepath: "~/Downloads/backup.txt" # required
|
|
327
|
-
)
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
# Get the backup
|
|
331
|
-
client.backups.get(
|
|
332
|
-
backend: "filesystem",
|
|
333
|
-
id: "my-first-backup"
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
# Restore backup
|
|
337
|
-
client.backups.restore(
|
|
338
|
-
backend: "filesystem",
|
|
339
|
-
id: "my-first-backup"
|
|
340
|
-
)
|
|
341
|
-
|
|
342
|
-
# Check the backup restore status
|
|
343
|
-
client.backups.restore_status(
|
|
344
|
-
backend: "filesystem",
|
|
345
|
-
id: "my-first-backup"
|
|
370
|
+
snapshot_name: "string", # required
|
|
371
|
+
filepath: "~/Downloads/backup.snapshot" # required
|
|
346
372
|
)
|
|
347
373
|
```
|
|
348
374
|
|
|
349
375
|
### Cluster
|
|
350
376
|
```ruby
|
|
351
377
|
# Get information about the current state and composition of the cluster
|
|
352
|
-
client.
|
|
378
|
+
client.clusters.info
|
|
353
379
|
|
|
354
380
|
# Tries to recover current peer Raft state.
|
|
355
|
-
client.
|
|
381
|
+
client.clusters.recover
|
|
356
382
|
|
|
357
383
|
# Tries to remove peer from the cluster. Will return an error if peer has shards on it.
|
|
358
|
-
client.
|
|
384
|
+
client.clusters.remove_peer(
|
|
359
385
|
peer_id: "int", # required
|
|
360
386
|
force: "boolean"
|
|
361
387
|
)
|
|
@@ -373,12 +399,14 @@ client.metrics(
|
|
|
373
399
|
anonymize: "boolean" # optional
|
|
374
400
|
)
|
|
375
401
|
|
|
402
|
+
# DEPRECATED: the lock API was removed in Qdrant 1.16. These only work against older servers.
|
|
403
|
+
|
|
376
404
|
# Get lock options. If write is locked, all write operations and collection creation are forbidden
|
|
377
405
|
client.locks
|
|
378
406
|
|
|
379
407
|
# Set lock options. If write is locked, all write operations and collection creation are forbidden. Returns previous lock options
|
|
380
408
|
client.set_lock(
|
|
381
|
-
write: "boolean" # required
|
|
409
|
+
write: "boolean", # required
|
|
382
410
|
error_message: "string"
|
|
383
411
|
)
|
|
384
412
|
```
|
|
@@ -387,6 +415,16 @@ client.set_lock(
|
|
|
387
415
|
|
|
388
416
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
389
417
|
|
|
418
|
+
The default `bundle exec rake` task runs `lint` (standardrb auto-format), `spec`, and `e2e` (a hosted end-to-end smoke test against a real Qdrant deployment) in sequence. Because the E2E step needs real credentials, run it with your hosted endpoint and API key exported:
|
|
419
|
+
|
|
420
|
+
```sh
|
|
421
|
+
export QDRANT_URL="https://<hosted-qdrant-endpoint>"
|
|
422
|
+
export QDRANT_API_KEY="<project-api-key>"
|
|
423
|
+
bundle exec rake
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
The individual steps are also available as `bundle exec rake lint`, `bundle exec rake spec`, and `bundle exec rake e2e`. Run the hosted check against an isolated or disposable project, since it creates and deletes a temporary collection.
|
|
427
|
+
|
|
390
428
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
391
429
|
|
|
392
430
|
## Contributing
|
data/Rakefile
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "bundler/gem_tasks"
|
|
4
|
+
require "rbconfig"
|
|
4
5
|
require "rspec/core/rake_task"
|
|
5
6
|
|
|
6
7
|
RSpec::Core::RakeTask.new(:spec)
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
desc "Run the standardrb linter (auto-fix)"
|
|
10
|
+
task :lint do
|
|
11
|
+
sh "bundle exec standardrb --fix"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
desc "Run the hosted Qdrant API end-to-end smoke test"
|
|
15
|
+
task :e2e do
|
|
16
|
+
exec RbConfig.ruby, File.expand_path("script/e2e.rb", __dir__)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
task default: %i[lint spec e2e]
|
data/lib/qdrant/base.rb
CHANGED
|
@@ -7,5 +7,13 @@ module Qdrant
|
|
|
7
7
|
def initialize(client:)
|
|
8
8
|
@client = client
|
|
9
9
|
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# Point the warning at the caller's code, skipping frames inside the gem and Forwardable.
|
|
14
|
+
def deprecation_warning(message)
|
|
15
|
+
uplevel = caller_locations.index { |loc| !loc.path.start_with?(__dir__) && !loc.path.end_with?("/forwardable.rb") }
|
|
16
|
+
warn("[DEPRECATION] Qdrant: #{message}", uplevel: uplevel + 1)
|
|
17
|
+
end
|
|
10
18
|
end
|
|
11
19
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
|
|
5
|
+
require_relative "request"
|
|
6
|
+
require_relative "response"
|
|
7
|
+
|
|
8
|
+
module Qdrant
|
|
9
|
+
class Client
|
|
10
|
+
class Connection
|
|
11
|
+
def initialize(url:, api_key:, raise_error:, logger:)
|
|
12
|
+
@uri = url
|
|
13
|
+
@api_key = api_key
|
|
14
|
+
@raise_error = raise_error
|
|
15
|
+
@logger = logger
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def get(path, &block)
|
|
19
|
+
execute(Net::HTTP::Get, path, &block)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def post(path, &block)
|
|
23
|
+
execute(Net::HTTP::Post, path, &block)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def put(path, &block)
|
|
27
|
+
execute(Net::HTTP::Put, path, &block)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def patch(path, &block)
|
|
31
|
+
execute(Net::HTTP::Patch, path, &block)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def delete(path, &block)
|
|
35
|
+
execute(Net::HTTP::Delete, path, &block)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def execute(verb, path, &block)
|
|
41
|
+
response = RequestBuilder
|
|
42
|
+
.new(verb, @uri, path, @api_key, @logger)
|
|
43
|
+
.tap(&block)
|
|
44
|
+
.build
|
|
45
|
+
.perform(@raise_error)
|
|
46
|
+
|
|
47
|
+
ResponseBuilder.new(response).build
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module Qdrant
|
|
8
|
+
class Client
|
|
9
|
+
RequestData = Struct.new(:params, :body)
|
|
10
|
+
|
|
11
|
+
class RequestBuilder
|
|
12
|
+
def initialize(verb, base_url, path, api_key, logger)
|
|
13
|
+
raise ArgumentError, "unsupported HTTP verb" unless verb < Net::HTTPRequest
|
|
14
|
+
|
|
15
|
+
@verb = verb
|
|
16
|
+
@base_url = base_url
|
|
17
|
+
@path = path
|
|
18
|
+
@api_key = api_key
|
|
19
|
+
@logger = logger
|
|
20
|
+
|
|
21
|
+
@request = RequestData.new({}, nil)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def tap
|
|
25
|
+
yield @request if block_given?
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def build
|
|
30
|
+
Request.new build_uri, @verb, @request.body, @api_key, @logger
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def build_uri
|
|
36
|
+
path, query = @path.to_s.split("?", 2)
|
|
37
|
+
|
|
38
|
+
URI.join(@base_url, path).tap do |uri|
|
|
39
|
+
if (query_pairs = URI.decode_www_form(query.to_s) + @request.params.to_a).any?
|
|
40
|
+
uri.query = URI.encode_www_form(query_pairs)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Request
|
|
47
|
+
def initialize(uri, verb, body, api_key, logger)
|
|
48
|
+
@logger = logger
|
|
49
|
+
@uri = uri
|
|
50
|
+
@verb = verb
|
|
51
|
+
|
|
52
|
+
@data = verb.new(uri.request_uri).tap do |request|
|
|
53
|
+
request["api-key"] = api_key if api_key
|
|
54
|
+
|
|
55
|
+
if body
|
|
56
|
+
request.body = JSON.generate(body)
|
|
57
|
+
request["Content-Type"] = request["Accept"] = "application/json"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
logger.info("#{verb_name} #{uri}")
|
|
62
|
+
logger.info("Request headers: #{redacted_headers.inspect}")
|
|
63
|
+
logger.info("Request body: #{@data.body}") if @data.body
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def perform(raise_error)
|
|
67
|
+
@logger.info("Performing Request: #{verb_name} #{@uri}")
|
|
68
|
+
|
|
69
|
+
response = Net::HTTP.new(@uri.host, @uri.port).tap do |h|
|
|
70
|
+
h.use_ssl = true if @uri.scheme == "https"
|
|
71
|
+
end.request(@data)
|
|
72
|
+
|
|
73
|
+
response.value if raise_error
|
|
74
|
+
|
|
75
|
+
@logger.info("Response status: #{response.code}")
|
|
76
|
+
response
|
|
77
|
+
rescue => e
|
|
78
|
+
@logger.error("#{verb_name} #{@uri} failed: #{e.class}: #{e.message}")
|
|
79
|
+
raise
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def verb_name
|
|
85
|
+
@verb.name.split("::").last.upcase
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def redacted_headers
|
|
89
|
+
@data.to_hash.tap do |headers|
|
|
90
|
+
headers["api-key"] = "[FILTERED]" if headers.key?("api-key")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Qdrant
|
|
6
|
+
class Client
|
|
7
|
+
Response = Struct.new(:status, :headers, :body)
|
|
8
|
+
|
|
9
|
+
class ResponseBuilder
|
|
10
|
+
JSON_CONTENT_TYPE_REGEX = /\bjson\z/
|
|
11
|
+
|
|
12
|
+
def initialize(response)
|
|
13
|
+
@response = response
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def build
|
|
17
|
+
Response.new(@response.code.to_i, @response.to_hash, parse_body)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def parse_body
|
|
23
|
+
body = @response.body
|
|
24
|
+
return body unless !body.nil? && !body.empty? && json_response?
|
|
25
|
+
|
|
26
|
+
JSON.parse(body)
|
|
27
|
+
rescue JSON::ParserError
|
|
28
|
+
# Fallback to raw body if JSON parsing fails unexpectedly
|
|
29
|
+
body
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def json_response?
|
|
33
|
+
content_type = @response["Content-Type"]
|
|
34
|
+
return false if content_type.nil?
|
|
35
|
+
|
|
36
|
+
media_type = content_type.split(";").first.to_s.strip
|
|
37
|
+
media_type.match?(JSON_CONTENT_TYPE_REGEX)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/qdrant/client.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "logger"
|
|
4
4
|
require "forwardable"
|
|
5
5
|
|
|
6
|
+
require_relative "client/connection"
|
|
7
|
+
|
|
6
8
|
module Qdrant
|
|
7
9
|
class Client
|
|
8
10
|
extend Forwardable
|
|
@@ -14,11 +16,11 @@ module Qdrant
|
|
|
14
16
|
def initialize(
|
|
15
17
|
url:,
|
|
16
18
|
api_key: nil,
|
|
17
|
-
adapter: Faraday.default_adapter,
|
|
18
19
|
raise_error: false,
|
|
19
|
-
logger: nil
|
|
20
|
+
logger: nil,
|
|
21
|
+
adapter: nil # Deprecated. Doesn't select the transport. Should be removed in subsequent releases
|
|
20
22
|
)
|
|
21
|
-
@url = url
|
|
23
|
+
@url = normalize_url(url)
|
|
22
24
|
@api_key = api_key
|
|
23
25
|
@adapter = adapter
|
|
24
26
|
@raise_error = raise_error
|
|
@@ -26,20 +28,16 @@ module Qdrant
|
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def connection
|
|
29
|
-
@connection ||=
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
faraday.response :raise_error if raise_error
|
|
36
|
-
faraday.response :json, content_type: /\bjson$/
|
|
37
|
-
faraday.adapter adapter
|
|
38
|
-
end
|
|
31
|
+
@connection ||= Connection.new(
|
|
32
|
+
url: url,
|
|
33
|
+
api_key: api_key,
|
|
34
|
+
raise_error: raise_error,
|
|
35
|
+
logger: logger
|
|
36
|
+
)
|
|
39
37
|
end
|
|
40
38
|
|
|
41
39
|
def aliases
|
|
42
|
-
|
|
40
|
+
Qdrant::Aliases.new(client: self).list
|
|
43
41
|
end
|
|
44
42
|
|
|
45
43
|
def collections
|
|
@@ -61,5 +59,14 @@ module Qdrant
|
|
|
61
59
|
def points
|
|
62
60
|
@points ||= Qdrant::Points.new(client: self)
|
|
63
61
|
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def normalize_url(url)
|
|
66
|
+
raise ArgumentError, "url must be a String" unless url.is_a?(String)
|
|
67
|
+
return url if url.start_with?("http://", "https://")
|
|
68
|
+
|
|
69
|
+
"https://#{url}"
|
|
70
|
+
end
|
|
64
71
|
end
|
|
65
72
|
end
|
data/lib/qdrant/clusters.rb
CHANGED
data/lib/qdrant/collections.rb
CHANGED
|
@@ -31,6 +31,8 @@ module Qdrant
|
|
|
31
31
|
init_from: nil,
|
|
32
32
|
quantization_config: nil
|
|
33
33
|
)
|
|
34
|
+
deprecation_warning("`init_from:` was removed in Qdrant 1.16 and is ignored by the server.") unless init_from.nil?
|
|
35
|
+
|
|
34
36
|
response = client.connection.put("#{PATH}/#{collection_name}") do |req|
|
|
35
37
|
req.body = {}
|
|
36
38
|
req.body["vectors"] = vectors
|
|
@@ -176,7 +178,7 @@ module Qdrant
|
|
|
176
178
|
priority: nil,
|
|
177
179
|
wait: nil
|
|
178
180
|
)
|
|
179
|
-
response = client.connection.
|
|
181
|
+
response = client.connection.put("#{PATH}/#{collection_name}/snapshots/recover") do |req|
|
|
180
182
|
req.params["wait"] = wait unless wait.nil?
|
|
181
183
|
|
|
182
184
|
req.body = {
|
data/lib/qdrant/points.rb
CHANGED
|
@@ -4,10 +4,10 @@ module Qdrant
|
|
|
4
4
|
class Points < Base
|
|
5
5
|
PATH = "points"
|
|
6
6
|
|
|
7
|
-
#
|
|
7
|
+
# Retrieve full information of points by ids. Same as `get_all`.
|
|
8
8
|
def list(
|
|
9
9
|
collection_name:,
|
|
10
|
-
ids
|
|
10
|
+
ids:,
|
|
11
11
|
with_payload: nil,
|
|
12
12
|
with_vector: nil,
|
|
13
13
|
consistency: nil
|
|
@@ -51,7 +51,6 @@ module Qdrant
|
|
|
51
51
|
ordering: nil,
|
|
52
52
|
filter: nil
|
|
53
53
|
)
|
|
54
|
-
|
|
55
54
|
raise ArgumentError, "Either points or filter should be provided" if points.nil? && filter.nil?
|
|
56
55
|
|
|
57
56
|
response = client.connection.post("collections/#{collection_name}/#{PATH}/delete") do |req|
|
|
@@ -213,6 +212,8 @@ module Qdrant
|
|
|
213
212
|
score_threshold: nil,
|
|
214
213
|
consistency: nil
|
|
215
214
|
)
|
|
215
|
+
deprecation_warning("`points.search` is deprecated since Qdrant 1.13 and will be removed from the server; use `points.query` instead.")
|
|
216
|
+
|
|
216
217
|
response = client.connection.post("collections/#{collection_name}/#{PATH}/search") do |req|
|
|
217
218
|
req.params["consistency"] = consistency unless consistency.nil?
|
|
218
219
|
|
|
@@ -235,6 +236,8 @@ module Qdrant
|
|
|
235
236
|
searches:,
|
|
236
237
|
consistency: nil
|
|
237
238
|
)
|
|
239
|
+
deprecation_warning("`points.batch_search` is deprecated since Qdrant 1.13 and will be removed from the server; use `points.query` instead.")
|
|
240
|
+
|
|
238
241
|
response = client.connection.post("collections/#{collection_name}/#{PATH}/search/batch") do |req|
|
|
239
242
|
req.params["consistency"] = consistency unless consistency.nil?
|
|
240
243
|
|
|
@@ -260,6 +263,8 @@ module Qdrant
|
|
|
260
263
|
lookup_from: nil,
|
|
261
264
|
consistency: nil
|
|
262
265
|
)
|
|
266
|
+
deprecation_warning("`points.recommend` is deprecated since Qdrant 1.13 and will be removed from the server; use `points.query` instead.")
|
|
267
|
+
|
|
263
268
|
response = client.connection.post("collections/#{collection_name}/#{PATH}/recommend") do |req|
|
|
264
269
|
req.params["consistency"] = consistency unless consistency.nil?
|
|
265
270
|
|
|
@@ -285,6 +290,8 @@ module Qdrant
|
|
|
285
290
|
searches:,
|
|
286
291
|
consistency: nil
|
|
287
292
|
)
|
|
293
|
+
deprecation_warning("`points.batch_recommend` is deprecated since Qdrant 1.13 and will be removed from the server; use `points.query` instead.")
|
|
294
|
+
|
|
288
295
|
response = client.connection.post("collections/#{collection_name}/#{PATH}/recommend/batch") do |req|
|
|
289
296
|
req.params["consistency"] = consistency unless consistency.nil?
|
|
290
297
|
|
data/lib/qdrant/service.rb
CHANGED
|
@@ -27,6 +27,8 @@ module Qdrant
|
|
|
27
27
|
write:,
|
|
28
28
|
error_message: nil
|
|
29
29
|
)
|
|
30
|
+
deprecation_warning("the lock API was removed in Qdrant 1.16; `set_lock` only works against older servers.")
|
|
31
|
+
|
|
30
32
|
response = client.connection.post("locks") do |req|
|
|
31
33
|
req.body = {
|
|
32
34
|
write: write
|
|
@@ -38,6 +40,8 @@ module Qdrant
|
|
|
38
40
|
|
|
39
41
|
# Get lock options. If write is locked, all write operations and collection creation are forbidden
|
|
40
42
|
def locks
|
|
43
|
+
deprecation_warning("the lock API was removed in Qdrant 1.16; `locks` only works against older servers.")
|
|
44
|
+
|
|
41
45
|
response = client.connection.get("locks")
|
|
42
46
|
response.body
|
|
43
47
|
end
|
data/lib/qdrant/version.rb
CHANGED
data/script/e2e.rb
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "logger"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
require "qdrant"
|
|
7
|
+
|
|
8
|
+
def assert!(condition, message)
|
|
9
|
+
raise "e2e assertion failed: #{message}" unless condition
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def ok_body!(body, operation)
|
|
13
|
+
assert!(body.is_a?(Hash), "#{operation} returned #{body.class}, expected Hash")
|
|
14
|
+
assert!(body["status"] == "ok", "#{operation} returned status #{body["status"].inspect}")
|
|
15
|
+
body
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
url = ENV.fetch("QDRANT_URL", "").strip
|
|
19
|
+
api_key = ENV.fetch("QDRANT_API_KEY", "").strip
|
|
20
|
+
|
|
21
|
+
abort "QDRANT_URL must be set to an https:// hosted endpoint" if url.empty? || !url.start_with?("https://")
|
|
22
|
+
abort "QDRANT_API_KEY must be set" if api_key.empty?
|
|
23
|
+
|
|
24
|
+
logger = Logger.new($stderr)
|
|
25
|
+
logger.level = Logger::WARN
|
|
26
|
+
|
|
27
|
+
client = Qdrant::Client.new(
|
|
28
|
+
url: url,
|
|
29
|
+
api_key: api_key,
|
|
30
|
+
raise_error: true,
|
|
31
|
+
logger: logger
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
collection_name = "qdrant_ruby_e2e_#{Process.pid}_#{Time.now.utc.strftime("%Y%m%d%H%M%S")}_#{SecureRandom.hex(4)}"
|
|
35
|
+
collection_created = false
|
|
36
|
+
failure = nil
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
body = ok_body!(client.collections.list, "collections.list")
|
|
40
|
+
assert!(body.dig("result", "collections").is_a?(Array), "collections.list result is not an Array")
|
|
41
|
+
puts "e2e: connection and authentication passed"
|
|
42
|
+
|
|
43
|
+
body = client.collections.create(
|
|
44
|
+
collection_name: collection_name,
|
|
45
|
+
vectors: {size: 3, distance: "Cosine"}
|
|
46
|
+
)
|
|
47
|
+
collection_created = true
|
|
48
|
+
ok_body!(body, "collections.create")
|
|
49
|
+
assert!(body["result"] == true, "collections.create did not return result=true")
|
|
50
|
+
puts "e2e: collection created (#{collection_name})"
|
|
51
|
+
|
|
52
|
+
body = ok_body!(client.collections.get(collection_name: collection_name), "collections.get")
|
|
53
|
+
status = body.dig("result", "status")
|
|
54
|
+
assert!(%w[green yellow].include?(status), "collection status was #{status.inspect}")
|
|
55
|
+
|
|
56
|
+
points = [
|
|
57
|
+
{id: 1, vector: [1.0, 0.0, 0.0], payload: {"source" => "hosted-e2e", "rank" => 1}},
|
|
58
|
+
{id: 2, vector: [0.0, 1.0, 0.0], payload: {"source" => "hosted-e2e", "rank" => 2}}
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
body = ok_body!(
|
|
62
|
+
client.points.upsert(collection_name: collection_name, wait: true, points: points),
|
|
63
|
+
"points.upsert"
|
|
64
|
+
)
|
|
65
|
+
assert!(body.dig("result", "status") == "completed", "points.upsert did not return a completed status")
|
|
66
|
+
|
|
67
|
+
body = ok_body!(client.points.get(collection_name: collection_name, id: 1), "points.get")
|
|
68
|
+
assert!(body.dig("result", "id") == 1, "points.get returned the wrong point")
|
|
69
|
+
|
|
70
|
+
body = ok_body!(
|
|
71
|
+
client.points.get_all(
|
|
72
|
+
collection_name: collection_name,
|
|
73
|
+
ids: [1, 2],
|
|
74
|
+
with_payload: true,
|
|
75
|
+
with_vector: true
|
|
76
|
+
),
|
|
77
|
+
"points.get_all"
|
|
78
|
+
)
|
|
79
|
+
results = body.fetch("result")
|
|
80
|
+
assert!(results.is_a?(Array) && results.length == 2, "points.get_all did not return two points")
|
|
81
|
+
assert!(results.map { |point| point["id"] }.sort == [1, 2], "points.get_all returned unexpected IDs")
|
|
82
|
+
assert!(results.find do |point|
|
|
83
|
+
point["id"] == 1
|
|
84
|
+
end.dig("payload", "source") == "hosted-e2e", "point payload was not persisted")
|
|
85
|
+
puts "e2e: point write and read round trip passed"
|
|
86
|
+
|
|
87
|
+
body = ok_body!(
|
|
88
|
+
client.points.query(
|
|
89
|
+
collection_name: collection_name,
|
|
90
|
+
query: [1.0, 0.0, 0.0],
|
|
91
|
+
limit: 1,
|
|
92
|
+
with_payload: true,
|
|
93
|
+
with_vector: false
|
|
94
|
+
),
|
|
95
|
+
"points.query"
|
|
96
|
+
)
|
|
97
|
+
hit = body.dig("result", "points").first
|
|
98
|
+
assert!(hit && hit["id"] == 1, "points.query did not return point 1 first")
|
|
99
|
+
assert!(hit["score"].to_f > 0.99, "points.query score was #{hit["score"].inspect}")
|
|
100
|
+
|
|
101
|
+
body = ok_body!(client.points.count(collection_name: collection_name, exact: true), "points.count")
|
|
102
|
+
assert!(body.dig("result", "count") == 2, "points.count did not return 2")
|
|
103
|
+
puts "e2e: query and count passed"
|
|
104
|
+
|
|
105
|
+
body = ok_body!(client.points.delete(collection_name: collection_name, points: [1, 2], wait: true), "points.delete")
|
|
106
|
+
assert!(body.dig("result", "status") == "completed", "points.delete did not return a completed status")
|
|
107
|
+
body = ok_body!(client.points.count(collection_name: collection_name, exact: true), "points.count after delete")
|
|
108
|
+
assert!(body.dig("result", "count") == 0, "points.count after delete did not return 0")
|
|
109
|
+
puts "e2e: point deletion passed"
|
|
110
|
+
rescue => e
|
|
111
|
+
failure = e
|
|
112
|
+
warn "e2e failed: #{e.class}: #{e.message}"
|
|
113
|
+
ensure
|
|
114
|
+
if collection_created
|
|
115
|
+
begin
|
|
116
|
+
body = ok_body!(client.collections.delete(collection_name: collection_name), "collections.delete cleanup")
|
|
117
|
+
assert!(body["result"] == true, "collections.delete cleanup did not return result=true")
|
|
118
|
+
puts "e2e: temporary collection deleted"
|
|
119
|
+
rescue => e
|
|
120
|
+
failure ||= e
|
|
121
|
+
warn "e2e cleanup failed: #{e.class}: #{e.message}"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
if failure
|
|
127
|
+
exit 1
|
|
128
|
+
else
|
|
129
|
+
puts "e2e passed"
|
|
130
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: qdrant-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Bondarev
|
|
@@ -10,25 +10,19 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: logger
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
19
|
-
- - "<"
|
|
20
|
-
- !ruby/object:Gem::Version
|
|
21
|
-
version: '3'
|
|
18
|
+
version: '0'
|
|
22
19
|
type: :runtime
|
|
23
20
|
prerelease: false
|
|
24
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
25
22
|
requirements:
|
|
26
23
|
- - ">="
|
|
27
24
|
- !ruby/object:Gem::Version
|
|
28
|
-
version:
|
|
29
|
-
- - "<"
|
|
30
|
-
- !ruby/object:Gem::Version
|
|
31
|
-
version: '3'
|
|
25
|
+
version: '0'
|
|
32
26
|
- !ruby/object:Gem::Dependency
|
|
33
27
|
name: pry-byebug
|
|
34
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -63,6 +57,9 @@ files:
|
|
|
63
57
|
- lib/qdrant/aliases.rb
|
|
64
58
|
- lib/qdrant/base.rb
|
|
65
59
|
- lib/qdrant/client.rb
|
|
60
|
+
- lib/qdrant/client/connection.rb
|
|
61
|
+
- lib/qdrant/client/request.rb
|
|
62
|
+
- lib/qdrant/client/response.rb
|
|
66
63
|
- lib/qdrant/clusters.rb
|
|
67
64
|
- lib/qdrant/collections.rb
|
|
68
65
|
- lib/qdrant/error.rb
|
|
@@ -70,6 +67,7 @@ files:
|
|
|
70
67
|
- lib/qdrant/service.rb
|
|
71
68
|
- lib/qdrant/snapshots.rb
|
|
72
69
|
- lib/qdrant/version.rb
|
|
70
|
+
- script/e2e.rb
|
|
73
71
|
- sig/qdrant.rbs
|
|
74
72
|
homepage: https://github.com/andreibondarev/qdrant-ruby
|
|
75
73
|
licenses:
|
|
@@ -85,7 +83,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
85
83
|
requirements:
|
|
86
84
|
- - ">="
|
|
87
85
|
- !ruby/object:Gem::Version
|
|
88
|
-
version:
|
|
86
|
+
version: '3.3'
|
|
89
87
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
88
|
requirements:
|
|
91
89
|
- - ">="
|