qdrant-ruby 0.9.10 → 0.10.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 +7 -1
- data/Gemfile +2 -2
- data/Gemfile.lock +51 -47
- data/README.md +12 -2
- data/Rakefile +12 -1
- 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 +21 -14
- data/lib/qdrant/points.rb +0 -1
- 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: 0207e687f622cdcf258c493bb8b75c783c9847574abe41fcf4489f93ba40752c
|
|
4
|
+
data.tar.gz: 48df2d6de390314ff6d6b18f2d155d118c81c417d8f21c2032ce2681094945b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 65b92ac01bc6346e03f1a4ce928292a5ce4ed0eff4fc36083199b2d6db3b096a9b9a9ca2e07de0d2936490e83b338c3ce0d2d70bafd60a8617aa20aa438c43c5
|
|
7
|
+
data.tar.gz: f301dd5bc6d3014af75ee6ae9916937b2324ab7abd444199b3c7ba843c8192ad1c610fb827183894e597c61552e81695d1b78f8674353c541521485213296439
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.10.0] - 2026-08-12
|
|
4
|
+
- Drop Faraday dependency
|
|
5
|
+
|
|
6
|
+
## [0.9.10] - 2025-11-23
|
|
7
|
+
|
|
3
8
|
## [0.9.9] - 2024-04-11
|
|
4
9
|
- Allow sparse_vectors to be passed to collections.create
|
|
5
10
|
- Support query endpoint
|
|
@@ -14,7 +19,8 @@
|
|
|
14
19
|
- 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
20
|
|
|
16
21
|
## [0.9.5] - 2024-01-12
|
|
17
|
-
- Bugfix: ArgumentError for filter in points delete
|
|
22
|
+
- Bugfix: ArgumentError for filter in points delete
|
|
23
|
+
|
|
18
24
|
## [0.9.4] - 2023-08-31
|
|
19
25
|
- Introduce `Points#get_all()` method
|
|
20
26
|
|
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.10.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 (2.21.2)
|
|
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.1.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.12.0)
|
|
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.
|
|
51
|
+
rspec-support (3.13.7)
|
|
52
|
+
rubocop (1.88.2)
|
|
53
53
|
json (~> 2.3)
|
|
54
|
-
|
|
55
|
-
|
|
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.56.0)
|
|
70
72
|
language_server-protocol (~> 3.17.0.2)
|
|
71
73
|
lint_roller (~> 1.0)
|
|
72
|
-
rubocop (~> 1.
|
|
74
|
+
rubocop (~> 1.88.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 (2.
|
|
80
|
+
standard-performance (1.9.0)
|
|
81
|
+
lint_roller (~> 1.1)
|
|
82
|
+
rubocop-performance (~> 1.26.0)
|
|
83
|
+
unicode-display_width (3.2.0)
|
|
84
|
+
unicode-emoji (~> 4.1)
|
|
85
|
+
unicode-emoji (4.2.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.56.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
|
|
@@ -387,6 +387,16 @@ client.set_lock(
|
|
|
387
387
|
|
|
388
388
|
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
389
|
|
|
390
|
+
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:
|
|
391
|
+
|
|
392
|
+
```sh
|
|
393
|
+
export QDRANT_URL="https://<hosted-qdrant-endpoint>"
|
|
394
|
+
export QDRANT_API_KEY="<project-api-key>"
|
|
395
|
+
bundle exec rake
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
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.
|
|
399
|
+
|
|
390
400
|
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
401
|
|
|
392
402
|
## 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]
|
|
@@ -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,16 +28,12 @@ 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
|
|
@@ -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/points.rb
CHANGED
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.search(
|
|
89
|
+
collection_name: collection_name,
|
|
90
|
+
vector: [1.0, 0.0, 0.0],
|
|
91
|
+
limit: 1,
|
|
92
|
+
with_payload: true,
|
|
93
|
+
with_vector: false
|
|
94
|
+
),
|
|
95
|
+
"points.search"
|
|
96
|
+
)
|
|
97
|
+
hit = body.fetch("result").first
|
|
98
|
+
assert!(hit && hit["id"] == 1, "points.search did not return point 1 first")
|
|
99
|
+
assert!(hit["score"].to_f > 0.99, "points.search 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: search 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.10.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
|
- - ">="
|