weaviate_record 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/weaviate_record/base.rb +241 -0
- data/lib/weaviate_record/concerns/attribute_concern.rb +76 -0
- data/lib/weaviate_record/concerns/record_concern.rb +80 -0
- data/lib/weaviate_record/connection.rb +53 -0
- data/lib/weaviate_record/constants.rb +25 -0
- data/lib/weaviate_record/errors.rb +69 -0
- data/lib/weaviate_record/inspect.rb +36 -0
- data/lib/weaviate_record/method_missing.rb +24 -0
- data/lib/weaviate_record/queries/ask.rb +30 -0
- data/lib/weaviate_record/queries/bm25.rb +29 -0
- data/lib/weaviate_record/queries/count.rb +21 -0
- data/lib/weaviate_record/queries/limit.rb +21 -0
- data/lib/weaviate_record/queries/near_object.rb +35 -0
- data/lib/weaviate_record/queries/near_text.rb +36 -0
- data/lib/weaviate_record/queries/near_vector.rb +32 -0
- data/lib/weaviate_record/queries/offset.rb +22 -0
- data/lib/weaviate_record/queries/order.rb +74 -0
- data/lib/weaviate_record/queries/select.rb +85 -0
- data/lib/weaviate_record/queries/where.rb +126 -0
- data/lib/weaviate_record/relation/query_builder.rb +59 -0
- data/lib/weaviate_record/relation.rb +86 -0
- data/lib/weaviate_record/schema.rb +101 -0
- data/lib/weaviate_record.rb +38 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: af84dff0fba26b232fd8b537749ff5742f5bcc0197656cc647e8b6791a55bd85
|
4
|
+
data.tar.gz: 9534adbc21943b257d0e8f0a517721dd17bb93a1d1e3c14f284e11f30499f1f9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e7d0fda549172607928d6e943426f5091d481d4cbcd4c44bb767a32ef667769091238125f79926c1070358dc1095a191e52af70d20ea915b17acbf4eeac51beb
|
7
|
+
data.tar.gz: dde3def3b42f332182c8d7f8cc115a1516f177be5abedbd516713e00474f9c1b7b9935da51ef134d9010b773df8acb95d2f867a4be043765edef53308e97b201
|
@@ -0,0 +1,241 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module WeaviateRecord
|
6
|
+
# Base class for the models to inherit from and to interact with the respective collection in weaviate.
|
7
|
+
class Base
|
8
|
+
extend ActiveModel::Naming
|
9
|
+
include ActiveModel::Conversion
|
10
|
+
include ActiveModel::Validations
|
11
|
+
include Inspect
|
12
|
+
include MethodMissing
|
13
|
+
include Concerns::RecordConcern
|
14
|
+
|
15
|
+
class << self
|
16
|
+
extend Forwardable
|
17
|
+
def_delegators(:relation, :all, :bm25, :limit, :near_text, :near_vector, :near_object,
|
18
|
+
:offset, :order, :select, :where, :ask, :destroy_all, :first, :last)
|
19
|
+
|
20
|
+
# Creates a new weaviate record and saves it and returns the record.
|
21
|
+
# Takes a key value pair of attributes that the record should be initialized with.
|
22
|
+
# If the record is not saved, it returns the unsaved record object with errors.
|
23
|
+
#
|
24
|
+
# ==== Example:
|
25
|
+
# Article.create(title: 'Hello World', content: 'This is the content of the article')
|
26
|
+
#
|
27
|
+
# # => #<Article:0x0000000105468ab0 id: "8280210b-9372-4e70-a045-beb7c12a9a24" ...>
|
28
|
+
def create(**attributes_hash)
|
29
|
+
record = new(**attributes_hash)
|
30
|
+
record.save
|
31
|
+
record
|
32
|
+
end
|
33
|
+
|
34
|
+
# Takes an _uuid_ and returns the record with that _uuid_.
|
35
|
+
# If the record is not found, it raises a +RecordNotFoundError+.
|
36
|
+
#
|
37
|
+
# ==== Example:
|
38
|
+
# Article.find('f3b1b3b1-0b3b-4b3b-8b3b-0b3b3b3b3b3b')
|
39
|
+
#
|
40
|
+
# #<Article:0x0000000105468ab0 id: "8280210b-9372-4e70-a045-beb7c12a9a24" ...>
|
41
|
+
def find(id)
|
42
|
+
result = connection.find_call(id)
|
43
|
+
if result.is_a?(Hash) && result['id']
|
44
|
+
new(_additional: meta_attributes(result), **result['properties'])
|
45
|
+
elsif result == ''
|
46
|
+
raise WeaviateRecord::Errors::RecordNotFoundError, "Couldn't find record with id=#{id}"
|
47
|
+
else
|
48
|
+
raise WeaviateRecord::Errors::ServerError, result['message']
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the count of all records in the collection.
|
53
|
+
#
|
54
|
+
# ==== Example:
|
55
|
+
# class Article < WeaviateRecord::Base
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# Article.count # => 0
|
59
|
+
#
|
60
|
+
# Article.create(title: 'Hello World', content: 'This is the content of the article')
|
61
|
+
#
|
62
|
+
# Article.count # => 1
|
63
|
+
def count
|
64
|
+
result = connection.client.query.aggs(class_name: to_s, fields: 'meta { count }')
|
65
|
+
result.dig(0, 'meta', 'count')
|
66
|
+
rescue StandardError
|
67
|
+
raise WeaviateRecord::Errors::ServerError, "unable to get the count for #{self} collection."
|
68
|
+
end
|
69
|
+
|
70
|
+
# Takes an _uuid_ and checks whether a record with that id exists or not.
|
71
|
+
#
|
72
|
+
# ==== Example:
|
73
|
+
# Article.exists?('f3b1b3b1-0b3b-4b3b-8b3b-0b3b3b3b3b3b') # => true
|
74
|
+
# Article.exists?('random_uuid') # => false
|
75
|
+
def exists?(id)
|
76
|
+
connection.check_existence(id)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def connection
|
82
|
+
@connection ||= Connection.new(self)
|
83
|
+
end
|
84
|
+
|
85
|
+
def relation
|
86
|
+
WeaviateRecord::Relation.new(self)
|
87
|
+
end
|
88
|
+
|
89
|
+
def inherited(klass)
|
90
|
+
WeaviateRecord::Schema.find_collection(klass)
|
91
|
+
super
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Creates a new record with the given attributes.
|
96
|
+
# The attributes can be passed as a hash or as key value pairs.
|
97
|
+
# It does not save the record in the weaviate database.
|
98
|
+
#
|
99
|
+
#
|
100
|
+
# ==== Example:
|
101
|
+
# Article.new(title: 'Hello World', content: 'This is the content of the article')
|
102
|
+
#
|
103
|
+
# # => #<Article:0x0000000105468ab0 ... "Hello World", content: "This is the content of the article">
|
104
|
+
#
|
105
|
+
# Article.title # => "Hello World"
|
106
|
+
# Article.content # => "This is the content of the article"
|
107
|
+
#
|
108
|
+
# Article.title = 'Not Hello World'
|
109
|
+
# Article.title # => "Not Hello World"
|
110
|
+
#
|
111
|
+
# Article.persisted? # => false
|
112
|
+
#
|
113
|
+
#
|
114
|
+
# The +custom_selected+ parameter is used to indicate whether the attributes are custom picked.
|
115
|
+
# If the attributes are custom picked, the attribute readers will be defined only for the selected attributes.
|
116
|
+
# The record with custom_selected as true cannot be saved, updated, or destroyed.
|
117
|
+
def initialize(hash = {}, custom_selected: false, **attributes)
|
118
|
+
attributes_hash = (hash.present? ? hash : attributes).deep_transform_keys(&:to_s)
|
119
|
+
@connection = WeaviateRecord::Connection.new(collection_name)
|
120
|
+
@custom_selected = custom_selected
|
121
|
+
@attributes = {}
|
122
|
+
@meta_attributes = attributes_hash['_additional'] || { 'id' => nil, 'creationTimeUnix' => nil,
|
123
|
+
'lastUpdateTimeUnix' => nil }
|
124
|
+
run_attribute_handlers(attributes_hash)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Saves the record in the weaviate database.
|
128
|
+
# Returns true if the record is saved successfully. Otherwise, it returns false.
|
129
|
+
# If the record is not saved successfully, it adds the errors to the record.
|
130
|
+
# If the record is already saved, it updates the record.
|
131
|
+
#
|
132
|
+
# ==== Example:
|
133
|
+
# class Article < WeaviateRecord::Base
|
134
|
+
# validates :title, presence: true
|
135
|
+
# end
|
136
|
+
#
|
137
|
+
# article = Article.new(title: 'Hello World', content: 'This is the content of the article')
|
138
|
+
# article.save # => true
|
139
|
+
#
|
140
|
+
# article.title = 'New title'
|
141
|
+
# article.save # => true
|
142
|
+
#
|
143
|
+
# article.title # => "New title"
|
144
|
+
#
|
145
|
+
# article = Article.new(title: '')
|
146
|
+
# article.save # => false
|
147
|
+
# article.errors.full_messages # => ["Title can't be blank"]
|
148
|
+
#
|
149
|
+
def save
|
150
|
+
result = validate_and_save
|
151
|
+
return false unless result
|
152
|
+
|
153
|
+
if result['error'].present?
|
154
|
+
errors.add(:base, message: result['error'])
|
155
|
+
false
|
156
|
+
else
|
157
|
+
@meta_attributes.merge!(self.class.meta_attributes(result))
|
158
|
+
true
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Updates the record in the weaviate database.
|
163
|
+
# Returns true if the record is updated successfully. Otherwise, it returns false.
|
164
|
+
# If the record is not updated successfully, it adds the errors to the record.
|
165
|
+
#
|
166
|
+
# ==== Example:
|
167
|
+
# class Article < WeaviateRecord::Base
|
168
|
+
# validates :title, presence: true
|
169
|
+
# end
|
170
|
+
#
|
171
|
+
# article = Article.new(title: 'Hello World', content: 'This is the content of the article')
|
172
|
+
# article.save
|
173
|
+
#
|
174
|
+
# article.update(title: 'Not Hello World') # => true
|
175
|
+
# article.title # => "Not Hello World"
|
176
|
+
#
|
177
|
+
# article.update(title: '') # => false
|
178
|
+
# article.errors.full_messages # => ["Title can't be blank"]
|
179
|
+
#
|
180
|
+
# If you try to update the meta attribute, it will raise an error
|
181
|
+
#
|
182
|
+
# ==== Example:
|
183
|
+
# article.update(id: 'new_id')
|
184
|
+
# # => WeaviateRecord::Errors::MetaAttributeError: 'cannot update meta attributes'
|
185
|
+
def update(hash = {}, **attributes)
|
186
|
+
attributes_hash = (hash.present? ? hash : attributes).deep_transform_keys(&:to_s)
|
187
|
+
validate_record_for_update(attributes_hash)
|
188
|
+
merge_attributes(attributes_hash)
|
189
|
+
return false unless valid?
|
190
|
+
|
191
|
+
result = @connection.update_call(@meta_attributes['id'], @attributes)
|
192
|
+
raise WeaviateRecord::Errors::ServerError, 'unable to update the weaviate record' unless result.is_a?(Hash)
|
193
|
+
|
194
|
+
errors.add(:base, message: result['error']) if result['error'].present?
|
195
|
+
result['id'].present?
|
196
|
+
end
|
197
|
+
|
198
|
+
# Destroys the record in the weaviate database.
|
199
|
+
# Returns true if the record is destroyed successfully. Otherwise, it returns false.
|
200
|
+
# If the record is the original record with valid id, it freezes the existing record object.
|
201
|
+
# If the record does not have a valid id, it converts all the attributes to nil.
|
202
|
+
#
|
203
|
+
# ==== Example:
|
204
|
+
# article = Article.new(title: 'Hello World', content: 'This is the content of the article')
|
205
|
+
#
|
206
|
+
# article.destroy # => false
|
207
|
+
# article.title # => nil
|
208
|
+
#
|
209
|
+
# article.title = 'New title'
|
210
|
+
# article.save
|
211
|
+
#
|
212
|
+
# article.destroy # => true
|
213
|
+
#
|
214
|
+
# article.frozen? # => true
|
215
|
+
def destroy
|
216
|
+
return self unless validate_record_for_destroy
|
217
|
+
|
218
|
+
result = @connection.delete_call(@meta_attributes['id'])
|
219
|
+
return freeze if result == true
|
220
|
+
|
221
|
+
errors.add(:base, message: result['error']) if result['error'].present?
|
222
|
+
false
|
223
|
+
end
|
224
|
+
|
225
|
+
# Checks whether the record is saved in the weaviate database or not.
|
226
|
+
# If the record is saved, it returns true. Otherwise, it returns false.
|
227
|
+
#
|
228
|
+
# ==== Example:
|
229
|
+
# article = Article.new(title: 'Hello World', content: 'This is the content of the article')
|
230
|
+
# article.save
|
231
|
+
# article.persisted? # => true
|
232
|
+
def persisted?
|
233
|
+
if @custom_selected || !respond_to?(:id)
|
234
|
+
raise WeaviateRecord::Errors::CustomQueriedRecordError,
|
235
|
+
'cannot perform persisted? action on custom queried record'
|
236
|
+
end
|
237
|
+
|
238
|
+
id.present?
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WeaviateRecord # :nodoc: all
|
4
|
+
module Concerns
|
5
|
+
module AttributeConcern
|
6
|
+
private
|
7
|
+
|
8
|
+
def list_of_valid_attributes
|
9
|
+
@list_of_valid_attributes ||= WeaviateRecord::Schema.find_collection(collection_name)
|
10
|
+
.attributes_list
|
11
|
+
end
|
12
|
+
|
13
|
+
def check_attributes(attributes_hash)
|
14
|
+
invalid_attributes = attributes_hash.keys - ['_additional', *list_of_valid_attributes]
|
15
|
+
return unless invalid_attributes.present?
|
16
|
+
|
17
|
+
raise WeaviateRecord::Errors::InvalidAttributeError,
|
18
|
+
"Invalid attributes #{invalid_attributes} for #{collection_name} record!"
|
19
|
+
end
|
20
|
+
|
21
|
+
def merge_attributes(attributes_hash)
|
22
|
+
check_attributes(attributes_hash)
|
23
|
+
attributes_hash.each do |key, value|
|
24
|
+
@attributes[key] = value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def load_attributes(attributes_hash)
|
29
|
+
if attributes_hash.empty? || !@custom_selected
|
30
|
+
list_of_valid_attributes.each do |attribute|
|
31
|
+
@attributes[attribute] = nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
attributes_hash.each do |key, value|
|
36
|
+
next if key == '_additional'
|
37
|
+
|
38
|
+
@attributes[key] = value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_attribute_writers
|
43
|
+
list_of_valid_attributes.each do |name|
|
44
|
+
define_singleton_method("#{name}=") do |value|
|
45
|
+
@attributes[name] = value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_attribute_readers
|
51
|
+
attributes_list = @custom_selected ? @attributes.each_key : list_of_valid_attributes
|
52
|
+
attributes_list.each do |name|
|
53
|
+
define_singleton_method(name) { @attributes[name] }
|
54
|
+
end
|
55
|
+
|
56
|
+
handle_timestamp_attributes
|
57
|
+
@meta_attributes.each_key do |name|
|
58
|
+
define_singleton_method(name.underscore) { @meta_attributes[name] }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle_timestamp_attributes
|
63
|
+
replace_timestamp_attribute('creationTimeUnix') if @meta_attributes.key? 'creationTimeUnix'
|
64
|
+
replace_timestamp_attribute('lastUpdateTimeUnix') if @meta_attributes.key? 'lastUpdateTimeUnix'
|
65
|
+
end
|
66
|
+
|
67
|
+
def replace_timestamp_attribute(attribute)
|
68
|
+
mapped_attribute = WeaviateRecord::Constants::SPECIAL_ATTRIBUTE_MAPPINGS.key(attribute)
|
69
|
+
@meta_attributes[mapped_attribute] = if @meta_attributes[attribute]
|
70
|
+
DateTime.strptime(@meta_attributes[attribute], '%Q')
|
71
|
+
end
|
72
|
+
@meta_attributes.delete(attribute)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WeaviateRecord # :nodoc: all
|
4
|
+
module Concerns
|
5
|
+
module RecordConcern
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include AttributeConcern
|
8
|
+
|
9
|
+
def collection_name
|
10
|
+
self.class.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def run_attribute_handlers(attributes_hash)
|
16
|
+
check_attributes(attributes_hash)
|
17
|
+
load_attributes(attributes_hash)
|
18
|
+
create_attribute_writers
|
19
|
+
create_attribute_readers
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate_record_for_update(attributes_hash)
|
23
|
+
raise ArgumentError, 'update action requires minimum one attribute' if attributes_hash.empty?
|
24
|
+
raise WeaviateRecord::Errors::MissingIdError, 'the record doesn\'t have an id' unless @meta_attributes['id']
|
25
|
+
|
26
|
+
if @custom_selected
|
27
|
+
raise WeaviateRecord::Errors::CustomQueriedRecordError,
|
28
|
+
'cannot perform update action on custom selected record'
|
29
|
+
end
|
30
|
+
|
31
|
+
check_attributes(attributes_hash)
|
32
|
+
return unless attributes_hash['_additional'] || attributes_hash[:_additional]
|
33
|
+
|
34
|
+
raise WeaviateRecord::Errors::MetaAttributeError, 'cannot update meta attributes'
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_and_save
|
38
|
+
if @custom_selected
|
39
|
+
raise WeaviateRecord::Errors::CustomQueriedRecordError, 'cannot modify custom selected record'
|
40
|
+
end
|
41
|
+
return false unless valid?
|
42
|
+
|
43
|
+
result = create_or_update_record
|
44
|
+
raise WeaviateRecord::Errors::InternalError, 'unable to save the record on Weaviate' unless result.is_a?(Hash)
|
45
|
+
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_or_update_record
|
50
|
+
if @meta_attributes['id']
|
51
|
+
@connection.update_call(@meta_attributes['id'], @attributes)
|
52
|
+
else
|
53
|
+
@connection.create_call(@attributes)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def validate_record_for_destroy
|
58
|
+
if @custom_selected
|
59
|
+
raise WeaviateRecord::Errors::CustomQueriedRecordError,
|
60
|
+
'cannot perform destroy action on custom selected record'
|
61
|
+
end
|
62
|
+
|
63
|
+
return true if @meta_attributes['id']
|
64
|
+
|
65
|
+
@attributes.each_key { |key| @attributes[key] = nil }
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
class_methods do
|
70
|
+
def meta_attributes(record)
|
71
|
+
meta = {}
|
72
|
+
meta['id'] = record['id']
|
73
|
+
meta['created_at'] = DateTime.strptime(record['creationTimeUnix'].to_s, '%Q')
|
74
|
+
meta['updated_at'] = DateTime.strptime(record['lastUpdateTimeUnix'].to_s, '%Q')
|
75
|
+
meta
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'weaviate'
|
4
|
+
|
5
|
+
module WeaviateRecord
|
6
|
+
# This module will act as a connection to Weaviate database
|
7
|
+
class Connection
|
8
|
+
# Returns a _Weaviate::Client_ object, which is used to interact with the Weaviate database.
|
9
|
+
attr_reader :client
|
10
|
+
|
11
|
+
# Creates a new Connection to the Weaviate database.
|
12
|
+
def initialize(collection_name = nil)
|
13
|
+
@collection_name = collection_name&.to_s
|
14
|
+
@client = Weaviate::Client.new(
|
15
|
+
url: ENV.fetch('WEAVIATE_DATABASE_URL'),
|
16
|
+
api_key: ENV.fetch('WEAVIATE_API_KEY', nil),
|
17
|
+
model_service: ENV['WEAVIATE_VECTORIZER_MODULE']&.to_sym,
|
18
|
+
model_service_api_key: ENV.fetch('WEAVIATE_VECTORIZER_API_KEY', nil)
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the present schema of the Weaviate database.
|
23
|
+
def schema_list
|
24
|
+
client.schema.list.deep_symbolize_keys!
|
25
|
+
end
|
26
|
+
|
27
|
+
# :enddoc:
|
28
|
+
|
29
|
+
def find_call(id)
|
30
|
+
client.objects.get(class_name: @collection_name, id: id)
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_call(properties)
|
34
|
+
client.objects.create(class_name: @collection_name, properties: properties)
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_call(id, properties)
|
38
|
+
client.objects.update(class_name: @collection_name, id: id, properties: properties)
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete_call(id)
|
42
|
+
client.objects.delete(class_name: @collection_name, id: id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def check_existence(id)
|
46
|
+
client.objects.exists?(class_name: @collection_name, id: id)
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete_where(condition)
|
50
|
+
client.objects.batch_delete(class_name: @collection_name, where: condition)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WeaviateRecord
|
4
|
+
module Constants # :nodoc:
|
5
|
+
SPECIAL_ATTRIBUTE_MAPPINGS = { 'feature_projection' => 'featureProjection',
|
6
|
+
'created_at' => 'creationTimeUnix',
|
7
|
+
'updated_at' => 'lastUpdateTimeUnix',
|
8
|
+
'explain_score' => 'explainScore' }.freeze
|
9
|
+
|
10
|
+
OPERATOR_MAPPING_HASH = { '=' => 'Equal', '==' => 'Equal', '!=' => 'NotEqual',
|
11
|
+
'>' => 'GreaterThan', '<' => 'LessThan', '>=' => 'GreaterThanEqual',
|
12
|
+
'<=' => 'LessThanEqual', 'LIKE' => 'Like', 'CONTAINS_ANY' => 'ContainsAny',
|
13
|
+
'CONTAINS_ALL' => 'ContainsAll' }.freeze
|
14
|
+
|
15
|
+
TYPE_MAPPING_HASH = { Integer => 'valueInt', String => 'valueText', Array => 'valueText',
|
16
|
+
Float => 'valueNumber', TrueClass => 'valueBoolean', FalseClass => 'valueBoolean' }.freeze
|
17
|
+
|
18
|
+
META_ATTRIBUTES = %w[vector certainty distance feature_projection classification
|
19
|
+
creation_at updated_at score explain_score summary].freeze
|
20
|
+
|
21
|
+
UUID_REGEX = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i.freeze
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
WeaviateRecord::Constants.freeze
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WeaviateRecord # :nodoc: all
|
4
|
+
module Errors
|
5
|
+
# Used when an un-queried attribute is accessed
|
6
|
+
class MissingAttributeError < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
# Used when an undefined attribute is accessed
|
10
|
+
class InvalidAttributeError < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
# Used when a collection is not found in the schema
|
14
|
+
class CollectionNotFound < StandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
# Used when a record without id is updated
|
18
|
+
class MissingIdError < StandardError
|
19
|
+
end
|
20
|
+
|
21
|
+
# Used when a custom queried record is updated
|
22
|
+
class CustomQueriedRecordError < StandardError
|
23
|
+
end
|
24
|
+
|
25
|
+
# Used when a meta attribute is updated
|
26
|
+
class MetaAttributeError < StandardError
|
27
|
+
end
|
28
|
+
|
29
|
+
# Used when there is a problem with Weaviate
|
30
|
+
class InternalError < StandardError
|
31
|
+
end
|
32
|
+
|
33
|
+
# Used when the Sorting order is invalid
|
34
|
+
class SortingOptionError < StandardError
|
35
|
+
end
|
36
|
+
|
37
|
+
# Used when the where query is invalid
|
38
|
+
class InvalidWhereQueryError < StandardError
|
39
|
+
end
|
40
|
+
|
41
|
+
# Used when the operator in where query is invalid
|
42
|
+
class InvalidOperatorError < StandardError
|
43
|
+
end
|
44
|
+
|
45
|
+
# Used when the value type in where query is invalid
|
46
|
+
class InvalidValueTypeError < StandardError
|
47
|
+
end
|
48
|
+
|
49
|
+
# Used when the record not found
|
50
|
+
class RecordNotFoundError < StandardError
|
51
|
+
end
|
52
|
+
|
53
|
+
# Used for the errors thrown by weaviate server
|
54
|
+
class ServerError < StandardError
|
55
|
+
end
|
56
|
+
|
57
|
+
# Used when destroy_all called without where conditions
|
58
|
+
class MissingWhereCondition < StandardError
|
59
|
+
end
|
60
|
+
|
61
|
+
# Raised when where condition is not getting converted to Ruby hash
|
62
|
+
class WhereQueryConversionError < StandardError
|
63
|
+
end
|
64
|
+
|
65
|
+
# Raised when required argument is empty
|
66
|
+
class EmptyPrompt < StandardError
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WeaviateRecord
|
4
|
+
module Inspect # :nodoc:
|
5
|
+
def inspect
|
6
|
+
special_attributes, inspection = format_attributes
|
7
|
+
string = to_s
|
8
|
+
string[-1] = ' '
|
9
|
+
string << "id: #{special_attributes['id'].inspect} " << inspection
|
10
|
+
string << " created_at: #{special_attributes['created_at'].inspect}" if special_attributes.key? 'created_at'
|
11
|
+
string << " updated_at: #{special_attributes['updated_at'].inspect}" if special_attributes.key? 'updated_at'
|
12
|
+
string << '>'
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def format_attributes
|
18
|
+
attributes_list = { **@meta_attributes, **@attributes }
|
19
|
+
special_attributes = attributes_list.slice('id', 'created_at', 'updated_at')
|
20
|
+
attributes_list.except!(*special_attributes.keys)
|
21
|
+
inspection = attributes_list.sort.map do |key, value|
|
22
|
+
format_for_inspect(key, value)
|
23
|
+
end.join(', ')
|
24
|
+
[special_attributes, inspection]
|
25
|
+
end
|
26
|
+
|
27
|
+
def format_for_inspect(key, value)
|
28
|
+
formatted_value = case value
|
29
|
+
when String then value.truncate(25).inspect
|
30
|
+
when Array then key == 'vector' ? "[#{value.first(4).join(', ')}...]" : value.inspect
|
31
|
+
else value.inspect
|
32
|
+
end
|
33
|
+
"#{key.underscore}: #{formatted_value}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WeaviateRecord
|
4
|
+
module MethodMissing # :nodoc:
|
5
|
+
private
|
6
|
+
|
7
|
+
def list_of_all_attributes
|
8
|
+
[*WeaviateRecord::Schema.find_collection(collection_name).attributes_list,
|
9
|
+
*WeaviateRecord::Constants::META_ATTRIBUTES]
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(method, *args, &block)
|
13
|
+
if list_of_all_attributes.include? method.to_s
|
14
|
+
raise WeaviateRecord::Errors::MissingAttributeError, "missing attribute: #{method}"
|
15
|
+
end
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def respond_to_missing?(method, include_all = false)
|
21
|
+
list_of_all_attributes.include?(method.to_s) ? true : super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WeaviateRecord
|
4
|
+
module Queries # :nodoc:
|
5
|
+
# This module provides method for ask query
|
6
|
+
module Ask
|
7
|
+
# Answers the question using the QnA module with the help of the records in the database.
|
8
|
+
#
|
9
|
+
# Requires QNA_INFERENCE_API to be set in the weaviate instance.
|
10
|
+
# The answer will be available in the _result_ key of the _answer_ meta attribute.
|
11
|
+
#
|
12
|
+
# You can also optionally pass an array of attributes to searched for the answer.
|
13
|
+
#
|
14
|
+
# ==== Example:
|
15
|
+
# Article.create(content: 'the name is Sparrow')
|
16
|
+
# Article.select(_additional: { answer: :result }).ask('whats your name?')
|
17
|
+
# # => [#<Article:0x0000000109a85de0 id: nil answer: {"result"=>"Sparrow"}>]
|
18
|
+
def ask(question, on_attributes: [])
|
19
|
+
question.to_str
|
20
|
+
raise WeaviateRecord::Errors::EmptyPrompt, 'text cannot be empty' if question.empty?
|
21
|
+
|
22
|
+
attributes = on_attributes.map(&:to_s)
|
23
|
+
@ask = "{ question: #{question.gsub('"', "'").inspect}" \
|
24
|
+
"#{", properties: #{attributes}" if attributes.present?} }"
|
25
|
+
@loaded = false
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WeaviateRecord
|
4
|
+
module Queries
|
5
|
+
# Bm25 is an algorithm to perform keyword based search on collection provided by Weaviate
|
6
|
+
# This class contains functions to perform that query
|
7
|
+
module Bm25
|
8
|
+
# Perform a keyword based search on the collection.
|
9
|
+
# You can also optionally pass an array of attributes to search in the collection.
|
10
|
+
#
|
11
|
+
# ==== Example:
|
12
|
+
# Article.create(content: 'This is a movie about friendship, action and adventure')
|
13
|
+
# # => #<Article:0x00000001052091e8 id: "983c0970-2c65-4c38-a93f-2ca9272d784b"... >
|
14
|
+
#
|
15
|
+
# Article.bm25('friendship movie')
|
16
|
+
# # => [#<Article:0x00000001052091e8 id: "983c0970-2c65-4c38-a93f-2ca9272d784b"... >]
|
17
|
+
def bm25(text, on_attributes: [])
|
18
|
+
text = text.to_str
|
19
|
+
return self if text.empty?
|
20
|
+
|
21
|
+
attributes = on_attributes.map(&:to_s)
|
22
|
+
@keyword_search = "{ query: #{text.gsub('"', "'").inspect}" \
|
23
|
+
"#{", properties: #{attributes}" if attributes.present?} }"
|
24
|
+
@loaded = false
|
25
|
+
self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|