neighbor 0.2.3 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48a9bcfda91ac7ed0af8c593216b9a84a2b488d15e9375d897f063e44bd0f5c8
4
- data.tar.gz: c4cf3ca35811336d7574eff4733078cc08e4b1cb7140cf5c5a12ad06a34506e7
3
+ metadata.gz: 5818fdfa27cc4b0678fb125e82d2fa0a3066ea173674ca541987b9084a94ac14
4
+ data.tar.gz: e874541532172dce9932a98506bd5ce7866a0f5c3e600ffd9d6473f226829368
5
5
  SHA512:
6
- metadata.gz: 83d90c764613158ca0a753796c9d1ccbe19127792ac8a73c32eddcc2c6537ad919c21e32c43be5b88833eae92fc5d723e3275e07307e17c0951e8436faed27b5
7
- data.tar.gz: 990ad6a45d982e7ababbb57112a65d1fcf8a37f3a37a6459cf4b0483aee407cfc3eda2575e6ae3ecd8389daaca8925b045d8df50c8787f0d1102e9e913b4269f
6
+ metadata.gz: 96211da20caf70383018ca626cbd83bbda5e4ffaf95e5a9b5405686b192643916325f89a652b4ba9e34e17daa1b84e8045cc644b606bab8aa6ea339ed4d7ea0d
7
+ data.tar.gz: bf855dc34617d489618417b9c807969f828358de19462458b7c09e1743102a8b9967b32b09660fab35f35543b77d588297c69971edb7442af0995cdca3b6ff8e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 0.3.1 (2023-09-25)
2
+
3
+ - Added support for passing multiple attributes to `has_neighbors`
4
+ - Fixed error with `nearest_neighbors` scope with Ruby 3.2 and Active Record 6.1
5
+
6
+ ## 0.3.0 (2023-07-24)
7
+
8
+ - Dropped support for Ruby < 3 and Active Record < 6.1
9
+
1
10
  ## 0.2.3 (2023-04-02)
2
11
 
3
12
  - Added support for dimensions to model generator
data/README.md CHANGED
@@ -117,18 +117,26 @@ For vector, add an approximate index to speed up queries. Create a migration wit
117
117
  class AddIndexToItemsNeighborVector < ActiveRecord::Migration[7.0]
118
118
  def change
119
119
  add_index :items, :embedding, using: :ivfflat, opclass: :vector_l2_ops
120
+ # or with pgvector 0.5.0+
121
+ add_index :items, :embedding, using: :hnsw, opclass: :vector_l2_ops
120
122
  end
121
123
  end
122
124
  ```
123
125
 
124
126
  Use `:vector_cosine_ops` for cosine distance and `:vector_ip_ops` for inner product.
125
127
 
126
- Set the number of probes
128
+ Set the number of probes with IVFFlat
127
129
 
128
130
  ```ruby
129
131
  Item.connection.execute("SET ivfflat.probes = 3")
130
132
  ```
131
133
 
134
+ Or the size of the dynamic candidate list with HNSW
135
+
136
+ ```ruby
137
+ Item.connection.execute("SET hnsw.ef_search = 100")
138
+ ```
139
+
132
140
  ## Examples
133
141
 
134
142
  - [OpenAI Embeddings](#openai-embeddings)
@@ -139,14 +147,14 @@ Item.connection.execute("SET ivfflat.probes = 3")
139
147
  Generate a model
140
148
 
141
149
  ```sh
142
- rails generate model Article content:text embedding:vector{1536}
150
+ rails generate model Document content:text embedding:vector{1536}
143
151
  rails db:migrate
144
152
  ```
145
153
 
146
154
  And add `has_neighbors`
147
155
 
148
156
  ```ruby
149
- class Article < ApplicationRecord
157
+ class Document < ApplicationRecord
150
158
  has_neighbors :embedding
151
159
  end
152
160
  ```
@@ -184,18 +192,18 @@ embeddings = fetch_embeddings(input)
184
192
  Store the embeddings
185
193
 
186
194
  ```ruby
187
- articles = []
195
+ documents = []
188
196
  input.zip(embeddings) do |content, embedding|
189
- articles << {content: content, embedding: embedding}
197
+ documents << {content: content, embedding: embedding}
190
198
  end
191
- Article.insert_all!(articles) # use create! for Active Record < 6
199
+ Document.insert_all!(documents)
192
200
  ```
193
201
 
194
202
  And get similar articles
195
203
 
196
204
  ```ruby
197
- article = Article.first
198
- article.nearest_neighbors(:embedding, distance: "inner_product").first(5).map(&:content)
205
+ document = Document.first
206
+ document.nearest_neighbors(:embedding, distance: "cosine").first(5).map(&:content)
199
207
  ```
200
208
 
201
209
  See the [complete code](examples/openai_embeddings.rb)
@@ -1,7 +1,11 @@
1
1
  module Neighbor
2
2
  module Model
3
- def has_neighbors(attribute_name = :neighbor_vector, dimensions: nil, normalize: nil)
4
- attribute_name = attribute_name.to_sym
3
+ def has_neighbors(*attribute_names, dimensions: nil, normalize: nil)
4
+ if attribute_names.empty?
5
+ attribute_names << :neighbor_vector
6
+ else
7
+ attribute_names.map!(&:to_sym)
8
+ end
5
9
 
6
10
  class_eval do
7
11
  @neighbor_attributes ||= {}
@@ -19,14 +23,26 @@ module Neighbor
19
23
  end
20
24
  end
21
25
 
22
- raise Error, "has_neighbors already called for #{attribute_name.inspect}" if neighbor_attributes[attribute_name]
23
- @neighbor_attributes[attribute_name] = {dimensions: dimensions, normalize: normalize}
26
+ attribute_names.each do |attribute_name|
27
+ raise Error, "has_neighbors already called for #{attribute_name.inspect}" if neighbor_attributes[attribute_name]
28
+ @neighbor_attributes[attribute_name] = {dimensions: dimensions, normalize: normalize}
29
+
30
+ attribute attribute_name, Neighbor::Vector.new(dimensions: dimensions, normalize: normalize, model: self, attribute_name: attribute_name)
31
+ end
24
32
 
25
- attribute attribute_name, Neighbor::Vector.new(dimensions: dimensions, normalize: normalize, model: self, attribute_name: attribute_name)
33
+ return if @neighbor_attributes.size != attribute_names.size
26
34
 
27
- return if @neighbor_attributes.size != 1
35
+ scope :nearest_neighbors, ->(attribute_name, vector = nil, options = nil) {
36
+ # cannot use keyword arguments with scope with Ruby 3.2 and Active Record 6.1
37
+ # https://github.com/rails/rails/issues/46934
38
+ if options.nil? && vector.is_a?(Hash)
39
+ options = vector
40
+ vector = nil
41
+ end
42
+ raise ArgumentError, "missing keyword: :distance" unless options.is_a?(Hash) && options.key?(:distance)
43
+ distance = options.delete(:distance)
44
+ raise ArgumentError, "unknown keywords: #{options.keys.map(&:inspect).join(", ")}" if options.any?
28
45
 
29
- scope :nearest_neighbors, ->(attribute_name, vector = nil, distance:) {
30
46
  if vector.nil? && !attribute_name.nil? && attribute_name.respond_to?(:to_a)
31
47
  vector = attribute_name
32
48
  attribute_name = :neighbor_vector
@@ -113,8 +129,8 @@ module Neighbor
113
129
  raise ArgumentError, "Invalid attribute" unless self.class.neighbor_attributes[attribute_name]
114
130
 
115
131
  self.class
116
- .where.not(self.class.primary_key => send(self.class.primary_key))
117
- .nearest_neighbors(attribute_name, send(attribute_name), **options)
132
+ .where.not(self.class.primary_key => self[self.class.primary_key])
133
+ .nearest_neighbors(attribute_name, self[attribute_name], **options)
118
134
  end
119
135
  end
120
136
  end
@@ -1,3 +1,3 @@
1
1
  module Neighbor
2
- VERSION = "0.2.3"
2
+ VERSION = "0.3.1"
3
3
  end
data/lib/neighbor.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  require "active_support"
3
3
 
4
4
  # modules
5
- require "neighbor/version"
5
+ require_relative "neighbor/version"
6
6
 
7
7
  module Neighbor
8
8
  class Error < StandardError; end
@@ -20,8 +20,8 @@ module Neighbor
20
20
  end
21
21
 
22
22
  ActiveSupport.on_load(:active_record) do
23
- require "neighbor/model"
24
- require "neighbor/vector"
23
+ require_relative "neighbor/model"
24
+ require_relative "neighbor/vector"
25
25
 
26
26
  extend Neighbor::Model
27
27
 
@@ -32,16 +32,7 @@ ActiveSupport.on_load(:active_record) do
32
32
  ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:vector] = {name: "vector"}
33
33
 
34
34
  # ensure schema can be loaded
35
- if ActiveRecord::VERSION::MAJOR >= 6
36
- ActiveRecord::ConnectionAdapters::TableDefinition.send(:define_column_methods, :cube, :vector)
37
- else
38
- ActiveRecord::ConnectionAdapters::TableDefinition.define_method :cube do |*args, **options|
39
- args.each { |name| column(name, :cube, options) }
40
- end
41
- ActiveRecord::ConnectionAdapters::TableDefinition.define_method :vector do |*args, **options|
42
- args.each { |name| column(name, :vector, options) }
43
- end
44
- end
35
+ ActiveRecord::ConnectionAdapters::TableDefinition.send(:define_column_methods, :cube, :vector)
45
36
 
46
37
  # prevent unknown OID warning
47
38
  if ActiveRecord::VERSION::MAJOR >= 7
@@ -51,4 +42,4 @@ ActiveSupport.on_load(:active_record) do
51
42
  end
52
43
  end
53
44
 
54
- require "neighbor/railtie" if defined?(Rails::Railtie)
45
+ require_relative "neighbor/railtie" if defined?(Rails::Railtie)
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.2.3
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-03 00:00:00.000000000 Z
11
+ date: 2023-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '5.2'
19
+ version: '6.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '5.2'
26
+ version: '6.1'
27
27
  description:
28
28
  email: andrew@ankane.org
29
29
  executables: []
@@ -54,7 +54,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
54
54
  requirements:
55
55
  - - ">="
56
56
  - !ruby/object:Gem::Version
57
- version: '2.6'
57
+ version: '3'
58
58
  required_rubygems_version: !ruby/object:Gem::Requirement
59
59
  requirements:
60
60
  - - ">="