neighbor 0.4.0 → 0.4.2
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 +10 -0
- data/README.md +339 -37
- data/lib/neighbor/model.rb +14 -1
- data/lib/neighbor/railtie.rb +1 -1
- data/lib/neighbor/type/cube.rb +4 -3
- data/lib/neighbor/type/halfvec.rb +4 -4
- data/lib/neighbor/type/sparsevec.rb +2 -2
- data/lib/neighbor/type/vector.rb +4 -4
- data/lib/neighbor/utils.rb +4 -0
- data/lib/neighbor/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dfc4af6302c7098ea40f96e9d8a19706aff46a2506cad541ff18ee07fcd11019
|
4
|
+
data.tar.gz: a79b59895ca3b99a7c048eddd20cb3602b2660425ab44463e7021ab763a26f62
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11081e687de4c79428351095477137f9140bc6c0363d09c54ece8fd5f7bbe2df802d740332f4474357f9e9e57157bd1f1f4dd3671c106d24dc7e01e2f0d84e2a
|
7
|
+
data.tar.gz: f18d787b22df7bbc00c69b1f9f6262e19c0214f6ef28814a5c05d9e8c3dae357f32596994bef60112d3f53dac0c1b51f6c99af6345c7e01b2f26cef1a7b42226
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
## 0.4.2 (2024-08-27)
|
2
|
+
|
3
|
+
- Fixed error with `nil` values
|
4
|
+
|
5
|
+
## 0.4.1 (2024-08-26)
|
6
|
+
|
7
|
+
- Added `precision` option
|
8
|
+
- Added support for `bit` dimensions to model generator
|
9
|
+
- Fixed error with Numo arrays
|
10
|
+
|
1
11
|
## 0.4.0 (2024-06-25)
|
2
12
|
|
3
13
|
- Added support for `halfvec` and `sparsevec` types
|
data/README.md
CHANGED
@@ -14,7 +14,7 @@ gem "neighbor"
|
|
14
14
|
|
15
15
|
## Choose An Extension
|
16
16
|
|
17
|
-
Neighbor supports two extensions: [cube](https://www.postgresql.org/docs/current/cube.html) and [
|
17
|
+
Neighbor supports two extensions: [cube](https://www.postgresql.org/docs/current/cube.html) and [pgvector](https://github.com/pgvector/pgvector). cube ships with Postgres, while pgvector supports more dimensions and approximate nearest neighbor search.
|
18
18
|
|
19
19
|
For cube, run:
|
20
20
|
|
@@ -23,7 +23,7 @@ rails generate neighbor:cube
|
|
23
23
|
rails db:migrate
|
24
24
|
```
|
25
25
|
|
26
|
-
For
|
26
|
+
For pgvector, [install the extension](https://github.com/pgvector/pgvector#installation) and run:
|
27
27
|
|
28
28
|
```sh
|
29
29
|
rails generate neighbor:vector
|
@@ -35,7 +35,7 @@ rails db:migrate
|
|
35
35
|
Create a migration
|
36
36
|
|
37
37
|
```ruby
|
38
|
-
class AddEmbeddingToItems < ActiveRecord::Migration[7.
|
38
|
+
class AddEmbeddingToItems < ActiveRecord::Migration[7.2]
|
39
39
|
def change
|
40
40
|
add_column :items, :embedding, :cube
|
41
41
|
# or
|
@@ -70,15 +70,30 @@ Get the nearest neighbors to a vector
|
|
70
70
|
Item.nearest_neighbors(:embedding, [0.9, 1.3, 1.1], distance: "euclidean").first(5)
|
71
71
|
```
|
72
72
|
|
73
|
-
|
73
|
+
Records returned from `nearest_neighbors` will have a `neighbor_distance` attribute
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
nearest_item = item.nearest_neighbors(:embedding, distance: "euclidean").first
|
77
|
+
nearest_item.neighbor_distance
|
78
|
+
```
|
79
|
+
|
80
|
+
See the additional docs for:
|
81
|
+
|
82
|
+
- [cube](#cube)
|
83
|
+
- [pgvector](#pgvector)
|
84
|
+
|
85
|
+
Or check out some [examples](#examples)
|
86
|
+
|
87
|
+
## cube
|
88
|
+
|
89
|
+
### Distance
|
74
90
|
|
75
91
|
Supported values are:
|
76
92
|
|
77
93
|
- `euclidean`
|
78
94
|
- `cosine`
|
79
|
-
- `taxicab`
|
80
|
-
- `chebyshev`
|
81
|
-
- `inner_product` (vector only)
|
95
|
+
- `taxicab`
|
96
|
+
- `chebyshev`
|
82
97
|
|
83
98
|
For cosine distance with cube, vectors must be normalized before being stored.
|
84
99
|
|
@@ -88,18 +103,11 @@ class Item < ApplicationRecord
|
|
88
103
|
end
|
89
104
|
```
|
90
105
|
|
91
|
-
For inner product with cube, see [this example](examples/
|
106
|
+
For inner product with cube, see [this example](examples/disco/user_recs_cube.rb).
|
92
107
|
|
93
|
-
|
108
|
+
### Dimensions
|
94
109
|
|
95
|
-
|
96
|
-
nearest_item = item.nearest_neighbors(:embedding, distance: "euclidean").first
|
97
|
-
nearest_item.neighbor_distance
|
98
|
-
```
|
99
|
-
|
100
|
-
## Dimensions
|
101
|
-
|
102
|
-
The cube data type can have up to 100 dimensions by default. See the [Postgres docs](https://www.postgresql.org/docs/current/cube.html) for how to increase this. The vector data type can have up to 16,000 dimensions, and vectors with up to 2,000 dimensions can be indexed.
|
110
|
+
The `cube` type can have up to 100 dimensions by default. See the [Postgres docs](https://www.postgresql.org/docs/current/cube.html) for how to increase this.
|
103
111
|
|
104
112
|
For cube, it’s a good idea to specify the number of dimensions to ensure all records have the same number.
|
105
113
|
|
@@ -109,12 +117,29 @@ class Item < ApplicationRecord
|
|
109
117
|
end
|
110
118
|
```
|
111
119
|
|
112
|
-
##
|
120
|
+
## pgvector
|
121
|
+
|
122
|
+
### Distance
|
123
|
+
|
124
|
+
Supported values are:
|
125
|
+
|
126
|
+
- `euclidean`
|
127
|
+
- `inner_product`
|
128
|
+
- `cosine`
|
129
|
+
- `taxicab`
|
130
|
+
- `hamming`
|
131
|
+
- `jaccard`
|
113
132
|
|
114
|
-
|
133
|
+
### Dimensions
|
134
|
+
|
135
|
+
The `vector` type can have up to 16,000 dimensions, and vectors with up to 2,000 dimensions can be indexed.
|
136
|
+
|
137
|
+
### Indexing
|
138
|
+
|
139
|
+
Add an approximate index to speed up queries. Create a migration with:
|
115
140
|
|
116
141
|
```ruby
|
117
|
-
class AddIndexToItemsEmbedding < ActiveRecord::Migration[7.
|
142
|
+
class AddIndexToItemsEmbedding < ActiveRecord::Migration[7.2]
|
118
143
|
def change
|
119
144
|
add_index :items, :embedding, using: :hnsw, opclass: :vector_l2_ops
|
120
145
|
# or
|
@@ -137,9 +162,91 @@ Or the number of probes with IVFFlat
|
|
137
162
|
Item.connection.execute("SET ivfflat.probes = 3")
|
138
163
|
```
|
139
164
|
|
165
|
+
### Half-Precision Vectors
|
166
|
+
|
167
|
+
Use the `halfvec` type to store half-precision vectors
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
class AddEmbeddingToItems < ActiveRecord::Migration[7.2]
|
171
|
+
def change
|
172
|
+
add_column :items, :embedding, :halfvec, limit: 3 # dimensions
|
173
|
+
end
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
### Half-Precision Indexing
|
178
|
+
|
179
|
+
Index vectors at half precision for smaller indexes
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
class AddIndexToItemsEmbedding < ActiveRecord::Migration[7.2]
|
183
|
+
def change
|
184
|
+
add_index :items, "(embedding::halfvec(3)) vector_l2_ops", using: :hnsw
|
185
|
+
end
|
186
|
+
end
|
187
|
+
```
|
188
|
+
|
189
|
+
Get the nearest neighbors
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
Item.nearest_neighbors(:embedding, [0.9, 1.3, 1.1], distance: "euclidean", precision: "half").first(5)
|
193
|
+
```
|
194
|
+
|
195
|
+
### Binary Vectors
|
196
|
+
|
197
|
+
Use the `bit` type to store binary vectors
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
class AddEmbeddingToItems < ActiveRecord::Migration[7.2]
|
201
|
+
def change
|
202
|
+
add_column :items, :embedding, :bit, limit: 3 # dimensions
|
203
|
+
end
|
204
|
+
end
|
205
|
+
```
|
206
|
+
|
207
|
+
Get the nearest neighbors by Hamming distance
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
Item.nearest_neighbors(:embedding, "101", distance: "hamming").first(5)
|
211
|
+
```
|
212
|
+
|
213
|
+
### Binary Quantization
|
214
|
+
|
215
|
+
Use expression indexing for binary quantization
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
class AddIndexToItemsEmbedding < ActiveRecord::Migration[7.2]
|
219
|
+
def change
|
220
|
+
add_index :items, "(binary_quantize(embedding)::bit(3)) bit_hamming_ops", using: :hnsw
|
221
|
+
end
|
222
|
+
end
|
223
|
+
```
|
224
|
+
|
225
|
+
### Sparse Vectors
|
226
|
+
|
227
|
+
Use the `sparsevec` type to store sparse vectors
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
class AddEmbeddingToItems < ActiveRecord::Migration[7.2]
|
231
|
+
def change
|
232
|
+
add_column :items, :embedding, :sparsevec, limit: 3 # dimensions
|
233
|
+
end
|
234
|
+
end
|
235
|
+
```
|
236
|
+
|
237
|
+
Get the nearest neighbors
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
embedding = Neighbor::SparseVector.new({0 => 0.9, 1 => 1.3, 2 => 1.1}, 3)
|
241
|
+
Item.nearest_neighbors(:embedding, embedding, distance: "euclidean").first(5)
|
242
|
+
```
|
243
|
+
|
140
244
|
## Examples
|
141
245
|
|
142
246
|
- [OpenAI Embeddings](#openai-embeddings)
|
247
|
+
- [Cohere Embeddings](#cohere-embeddings)
|
248
|
+
- [Sentence Embeddings](#sentence-embeddings)
|
249
|
+
- [Sparse Embeddings](#sparse-embeddings)
|
143
250
|
- [Disco Recommendations](#disco-recommendations)
|
144
251
|
|
145
252
|
### OpenAI Embeddings
|
@@ -170,10 +277,10 @@ def fetch_embeddings(input)
|
|
170
277
|
}
|
171
278
|
data = {
|
172
279
|
input: input,
|
173
|
-
model: "text-embedding-
|
280
|
+
model: "text-embedding-3-small"
|
174
281
|
}
|
175
282
|
|
176
|
-
response = Net::HTTP.post(URI(url), data.to_json, headers)
|
283
|
+
response = Net::HTTP.post(URI(url), data.to_json, headers).tap(&:value)
|
177
284
|
JSON.parse(response.body)["data"].map { |v| v["embedding"] }
|
178
285
|
end
|
179
286
|
```
|
@@ -199,14 +306,221 @@ end
|
|
199
306
|
Document.insert_all!(documents)
|
200
307
|
```
|
201
308
|
|
202
|
-
And get similar
|
309
|
+
And get similar documents
|
310
|
+
|
311
|
+
```ruby
|
312
|
+
document = Document.first
|
313
|
+
document.nearest_neighbors(:embedding, distance: "cosine").first(5).map(&:content)
|
314
|
+
```
|
315
|
+
|
316
|
+
See the [complete code](examples/openai/example.rb)
|
317
|
+
|
318
|
+
### Cohere Embeddings
|
319
|
+
|
320
|
+
Generate a model
|
321
|
+
|
322
|
+
```sh
|
323
|
+
rails generate model Document content:text embedding:bit{1024}
|
324
|
+
rails db:migrate
|
325
|
+
```
|
326
|
+
|
327
|
+
And add `has_neighbors`
|
328
|
+
|
329
|
+
```ruby
|
330
|
+
class Document < ApplicationRecord
|
331
|
+
has_neighbors :embedding
|
332
|
+
end
|
333
|
+
```
|
334
|
+
|
335
|
+
Create a method to call the [embed API](https://docs.cohere.com/reference/embed)
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
def fetch_embeddings(input, input_type)
|
339
|
+
url = "https://api.cohere.com/v1/embed"
|
340
|
+
headers = {
|
341
|
+
"Authorization" => "Bearer #{ENV.fetch("CO_API_KEY")}",
|
342
|
+
"Content-Type" => "application/json"
|
343
|
+
}
|
344
|
+
data = {
|
345
|
+
texts: input,
|
346
|
+
model: "embed-english-v3.0",
|
347
|
+
input_type: input_type,
|
348
|
+
embedding_types: ["ubinary"]
|
349
|
+
}
|
350
|
+
|
351
|
+
response = Net::HTTP.post(URI(url), data.to_json, headers).tap(&:value)
|
352
|
+
JSON.parse(response.body)["embeddings"]["ubinary"].map { |e| e.map { |v| v.chr.unpack1("B*") }.join }
|
353
|
+
end
|
354
|
+
```
|
355
|
+
|
356
|
+
Pass your input
|
357
|
+
|
358
|
+
```ruby
|
359
|
+
input = [
|
360
|
+
"The dog is barking",
|
361
|
+
"The cat is purring",
|
362
|
+
"The bear is growling"
|
363
|
+
]
|
364
|
+
embeddings = fetch_embeddings(input, "search_document")
|
365
|
+
```
|
366
|
+
|
367
|
+
Store the embeddings
|
368
|
+
|
369
|
+
```ruby
|
370
|
+
documents = []
|
371
|
+
input.zip(embeddings) do |content, embedding|
|
372
|
+
documents << {content: content, embedding: embedding}
|
373
|
+
end
|
374
|
+
Document.insert_all!(documents)
|
375
|
+
```
|
376
|
+
|
377
|
+
Embed the search query
|
378
|
+
|
379
|
+
```ruby
|
380
|
+
query = "forest"
|
381
|
+
query_embedding = fetch_embeddings([query], "search_query")[0]
|
382
|
+
```
|
383
|
+
|
384
|
+
And search the documents
|
385
|
+
|
386
|
+
```ruby
|
387
|
+
Document.nearest_neighbors(:embedding, query_embedding, distance: "hamming").first(5).map(&:content)
|
388
|
+
```
|
389
|
+
|
390
|
+
See the [complete code](examples/cohere/example.rb)
|
391
|
+
|
392
|
+
### Sentence Embeddings
|
393
|
+
|
394
|
+
You can generate embeddings locally with [Informers](https://github.com/ankane/informers).
|
395
|
+
|
396
|
+
Generate a model
|
397
|
+
|
398
|
+
```sh
|
399
|
+
rails generate model Document content:text embedding:vector{384}
|
400
|
+
rails db:migrate
|
401
|
+
```
|
402
|
+
|
403
|
+
And add `has_neighbors`
|
404
|
+
|
405
|
+
```ruby
|
406
|
+
class Document < ApplicationRecord
|
407
|
+
has_neighbors :embedding
|
408
|
+
end
|
409
|
+
```
|
410
|
+
|
411
|
+
Load a [model](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2)
|
412
|
+
|
413
|
+
```ruby
|
414
|
+
model = Informers::Model.new("sentence-transformers/all-MiniLM-L6-v2")
|
415
|
+
```
|
416
|
+
|
417
|
+
Pass your input
|
418
|
+
|
419
|
+
```ruby
|
420
|
+
input = [
|
421
|
+
"The dog is barking",
|
422
|
+
"The cat is purring",
|
423
|
+
"The bear is growling"
|
424
|
+
]
|
425
|
+
embeddings = model.embed(input)
|
426
|
+
```
|
427
|
+
|
428
|
+
Store the embeddings
|
429
|
+
|
430
|
+
```ruby
|
431
|
+
documents = []
|
432
|
+
input.zip(embeddings) do |content, embedding|
|
433
|
+
documents << {content: content, embedding: embedding}
|
434
|
+
end
|
435
|
+
Document.insert_all!(documents)
|
436
|
+
```
|
437
|
+
|
438
|
+
And get similar documents
|
203
439
|
|
204
440
|
```ruby
|
205
441
|
document = Document.first
|
206
442
|
document.nearest_neighbors(:embedding, distance: "cosine").first(5).map(&:content)
|
207
443
|
```
|
208
444
|
|
209
|
-
See the [complete code](examples/
|
445
|
+
See the [complete code](examples/informers/example.rb)
|
446
|
+
|
447
|
+
### Sparse Embeddings
|
448
|
+
|
449
|
+
You can generate sparse embeddings locally with [Transformers.rb](https://github.com/ankane/transformers-ruby).
|
450
|
+
|
451
|
+
Generate a model
|
452
|
+
|
453
|
+
```sh
|
454
|
+
rails generate model Document content:text embedding:sparsevec{30522}
|
455
|
+
rails db:migrate
|
456
|
+
```
|
457
|
+
|
458
|
+
And add `has_neighbors`
|
459
|
+
|
460
|
+
```ruby
|
461
|
+
class Document < ApplicationRecord
|
462
|
+
has_neighbors :embedding
|
463
|
+
end
|
464
|
+
```
|
465
|
+
|
466
|
+
Load a [model](https://huggingface.co/opensearch-project/opensearch-neural-sparse-encoding-v1) to generate embeddings
|
467
|
+
|
468
|
+
```ruby
|
469
|
+
class EmbeddingModel
|
470
|
+
def initialize(model_id)
|
471
|
+
@model = Transformers::AutoModelForMaskedLM.from_pretrained(model_id)
|
472
|
+
@tokenizer = Transformers::AutoTokenizer.from_pretrained(model_id)
|
473
|
+
@special_token_ids = @tokenizer.special_tokens_map.map { |_, token| @tokenizer.vocab[token] }
|
474
|
+
end
|
475
|
+
|
476
|
+
def embed(input)
|
477
|
+
feature = @tokenizer.(input, padding: true, truncation: true, return_tensors: "pt", return_token_type_ids: false)
|
478
|
+
output = @model.(**feature)[0]
|
479
|
+
values = Torch.max(output * feature[:attention_mask].unsqueeze(-1), dim: 1)[0]
|
480
|
+
values = Torch.log(1 + Torch.relu(values))
|
481
|
+
values[0.., @special_token_ids] = 0
|
482
|
+
values.to_a
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
model = EmbeddingModel.new("opensearch-project/opensearch-neural-sparse-encoding-v1")
|
487
|
+
```
|
488
|
+
|
489
|
+
Pass your input
|
490
|
+
|
491
|
+
```ruby
|
492
|
+
input = [
|
493
|
+
"The dog is barking",
|
494
|
+
"The cat is purring",
|
495
|
+
"The bear is growling"
|
496
|
+
]
|
497
|
+
embeddings = model.embed(input)
|
498
|
+
```
|
499
|
+
|
500
|
+
Store the embeddings
|
501
|
+
|
502
|
+
```ruby
|
503
|
+
documents = []
|
504
|
+
input.zip(embeddings) do |content, embedding|
|
505
|
+
documents << {content: content, embedding: Neighbor::SparseVector.new(embedding)}
|
506
|
+
end
|
507
|
+
Document.insert_all!(documents)
|
508
|
+
```
|
509
|
+
|
510
|
+
Embed the search query
|
511
|
+
|
512
|
+
```ruby
|
513
|
+
query = "forest"
|
514
|
+
query_embedding = model.embed([query])[0]
|
515
|
+
```
|
516
|
+
|
517
|
+
And search the documents
|
518
|
+
|
519
|
+
```ruby
|
520
|
+
Document.nearest_neighbors(:embedding, Neighbor::SparseVector.new(query_embedding), distance: "inner_product").first(5).map(&:content)
|
521
|
+
```
|
522
|
+
|
523
|
+
See the [complete code](examples/sparse/example.rb)
|
210
524
|
|
211
525
|
### Disco Recommendations
|
212
526
|
|
@@ -252,19 +566,7 @@ movie = Movie.find_by(name: "Star Wars (1977)")
|
|
252
566
|
movie.nearest_neighbors(:factors, distance: "cosine").first(5).map(&:name)
|
253
567
|
```
|
254
568
|
|
255
|
-
See the complete code for [cube](examples/
|
256
|
-
|
257
|
-
## Upgrading
|
258
|
-
|
259
|
-
### 0.2.0
|
260
|
-
|
261
|
-
The `distance` option has been moved from `has_neighbors` to `nearest_neighbors`, and there is no longer a default. If you use cosine distance, set:
|
262
|
-
|
263
|
-
```ruby
|
264
|
-
class Item < ApplicationRecord
|
265
|
-
has_neighbors normalize: true
|
266
|
-
end
|
267
|
-
```
|
569
|
+
See the complete code for [cube](examples/disco/item_recs_cube.rb) and [pgvector](examples/disco/item_recs_vector.rb)
|
268
570
|
|
269
571
|
## History
|
270
572
|
|
data/lib/neighbor/model.rb
CHANGED
@@ -49,7 +49,7 @@ module Neighbor
|
|
49
49
|
# TODO move to normalizes when Active Record < 7.1 no longer supported
|
50
50
|
before_save do
|
51
51
|
self.class.neighbor_attributes.each do |k, v|
|
52
|
-
next unless v[:normalize]
|
52
|
+
next unless v[:normalize] && attribute_changed?(k)
|
53
53
|
value = read_attribute(k)
|
54
54
|
next if value.nil?
|
55
55
|
self[k] = Neighbor::Utils.normalize(value, column_info: self.class.columns_hash[k.to_s])
|
@@ -61,6 +61,7 @@ module Neighbor
|
|
61
61
|
scope :nearest_neighbors, ->(attribute_name, vector, options = nil) {
|
62
62
|
raise ArgumentError, "missing keyword: :distance" unless options.is_a?(Hash) && options.key?(:distance)
|
63
63
|
distance = options.delete(:distance)
|
64
|
+
precision = options.delete(:precision)
|
64
65
|
raise ArgumentError, "unknown keywords: #{options.keys.map(&:inspect).join(", ")}" if options.any?
|
65
66
|
|
66
67
|
attribute_name = attribute_name.to_sym
|
@@ -126,6 +127,18 @@ module Neighbor
|
|
126
127
|
vector = Neighbor::Utils.normalize(vector, column_info: column_info) if normalize
|
127
128
|
|
128
129
|
query = connection.quote(column_attribute.serialize(vector))
|
130
|
+
|
131
|
+
if !precision.nil?
|
132
|
+
case precision.to_s
|
133
|
+
when "half"
|
134
|
+
cast_dimensions = dimensions || column_info&.limit
|
135
|
+
raise ArgumentError, "Unknown dimensions" unless cast_dimensions
|
136
|
+
quoted_attribute += "::halfvec(#{connection.quote(cast_dimensions.to_i)})"
|
137
|
+
else
|
138
|
+
raise ArgumentError, "Invalid precision"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
129
142
|
order = "#{quoted_attribute} #{operator} #{query}"
|
130
143
|
if operator == "#"
|
131
144
|
order = "bit_count(#{order})"
|
data/lib/neighbor/railtie.rb
CHANGED
data/lib/neighbor/type/cube.rb
CHANGED
@@ -6,7 +6,8 @@ module Neighbor
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def serialize(value)
|
9
|
-
if
|
9
|
+
if Utils.array?(value)
|
10
|
+
value = value.to_a
|
10
11
|
if value.first.is_a?(Array)
|
11
12
|
value = value.map { |v| serialize_point(v) }.join(", ")
|
12
13
|
else
|
@@ -19,8 +20,8 @@ module Neighbor
|
|
19
20
|
private
|
20
21
|
|
21
22
|
def cast_value(value)
|
22
|
-
if
|
23
|
-
value
|
23
|
+
if Utils.array?(value)
|
24
|
+
value.to_a
|
24
25
|
elsif value.is_a?(Numeric)
|
25
26
|
[value]
|
26
27
|
elsif value.is_a?(String)
|
@@ -6,8 +6,8 @@ module Neighbor
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def serialize(value)
|
9
|
-
if
|
10
|
-
value = "[#{value.map(&:to_f).join(",")}]"
|
9
|
+
if Utils.array?(value)
|
10
|
+
value = "[#{value.to_a.map(&:to_f).join(",")}]"
|
11
11
|
end
|
12
12
|
super(value)
|
13
13
|
end
|
@@ -17,8 +17,8 @@ module Neighbor
|
|
17
17
|
def cast_value(value)
|
18
18
|
if value.is_a?(String)
|
19
19
|
value[1..-1].split(",").map(&:to_f)
|
20
|
-
elsif
|
21
|
-
value
|
20
|
+
elsif Utils.array?(value)
|
21
|
+
value.to_a
|
22
22
|
else
|
23
23
|
raise "can't cast #{value.class.name} to halfvec"
|
24
24
|
end
|
@@ -19,8 +19,8 @@ module Neighbor
|
|
19
19
|
value
|
20
20
|
elsif value.is_a?(String)
|
21
21
|
SparseVector.from_text(value)
|
22
|
-
elsif
|
23
|
-
value = SparseVector.new(value)
|
22
|
+
elsif Utils.array?(value)
|
23
|
+
value = SparseVector.new(value.to_a)
|
24
24
|
else
|
25
25
|
raise "can't cast #{value.class.name} to sparsevec"
|
26
26
|
end
|
data/lib/neighbor/type/vector.rb
CHANGED
@@ -6,8 +6,8 @@ module Neighbor
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def serialize(value)
|
9
|
-
if
|
10
|
-
value = "[#{value.map(&:to_f).join(",")}]"
|
9
|
+
if Utils.array?(value)
|
10
|
+
value = "[#{value.to_a.map(&:to_f).join(",")}]"
|
11
11
|
end
|
12
12
|
super(value)
|
13
13
|
end
|
@@ -17,8 +17,8 @@ module Neighbor
|
|
17
17
|
def cast_value(value)
|
18
18
|
if value.is_a?(String)
|
19
19
|
value[1..-1].split(",").map(&:to_f)
|
20
|
-
elsif
|
21
|
-
value
|
20
|
+
elsif Utils.array?(value)
|
21
|
+
value.to_a
|
22
22
|
else
|
23
23
|
raise "can't cast #{value.class.name} to vector"
|
24
24
|
end
|
data/lib/neighbor/utils.rb
CHANGED
data/lib/neighbor/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: neighbor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|