activerecord-turbopuffer-adapter 0.1.5 → 0.1.7
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/README.md +12 -0
- data/lib/active_record/connection_adapters/turbopuffer_adapter.rb +2 -2
- data/lib/turbopuffer/active_record/schema/attribute.rb +10 -2
- data/lib/turbopuffer/active_record/schema/model.rb +8 -3
- data/lib/turbopuffer/active_record/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: dbe88ca95c2999749426d72f89dd7d0327a53d5a08bf6a0b2446340d6b17e173
|
|
4
|
+
data.tar.gz: c6c1611fe66ea9ca7dff51fde19ce3843249d8b91e955aa21ba500d9fe8efe1a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4cd6c79fd3b22a79ca2d18a8ed68765360b7e63c9a3f2d4e3bb82d644553bbf7fcd93a05489f58c3285c5967ec3f2e2f522bafa7e4c1c9debf5190b4f626c85c
|
|
7
|
+
data.tar.gz: c8f7cdae01828498d93c66d345d34bdbf5cda39ff46ff13ec7126bfe97b42bba3d9c4b2337fbce08640755f87d49d42b9283cec227f9605d6b1af02bb9b1f998
|
data/README.md
CHANGED
|
@@ -56,6 +56,17 @@ class Document < ApplicationRecord
|
|
|
56
56
|
end
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
Alternatively, turbopuffer can generate embeddings for you. Declare `embed` on a string attribute and the vector is computed on write:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
class Document < ApplicationRecord
|
|
63
|
+
turbopuffer_attribute "id", "uuid", not_null: 1
|
|
64
|
+
turbopuffer_attribute "body", "string", embed: "openai/text-embedding-3-small"
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
turbopuffer stores the vector in a computed `embed_body` attribute. `embed` also accepts a hash (`model:`, `attribute:`, `dims:`, `dtype:`) to name the vector attribute or set its size. Embedding is billed per token by model, see https://turbopuffer.com/docs/embedding for the supported models.
|
|
69
|
+
|
|
59
70
|
### Creating records
|
|
60
71
|
|
|
61
72
|
```ruby
|
|
@@ -104,6 +115,7 @@ Document.find("018f...")
|
|
|
104
115
|
|
|
105
116
|
Document.rank_by("vector", "ANN", query_vector).limit(10)
|
|
106
117
|
Document.rank_by("text", "BM25", "quick walrus").limit(10)
|
|
118
|
+
Document.rank_by("body", "ANN", ["Embed", "sea mammals"]).limit(10)
|
|
107
119
|
|
|
108
120
|
Document.consistency(:eventual).where(published: true)
|
|
109
121
|
|
|
@@ -151,7 +151,7 @@ module ActiveRecord
|
|
|
151
151
|
schema: model.turbopuffer_schema_hash
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
if model.turbopuffer_attributes.any?
|
|
154
|
+
if model.turbopuffer_attributes.any? { |attribute| attribute.ann || attribute.embed }
|
|
155
155
|
tpuf_insert_args[:distance_metric] = model.turbopuffer_distance_metric
|
|
156
156
|
end
|
|
157
157
|
|
|
@@ -272,7 +272,7 @@ module ActiveRecord
|
|
|
272
272
|
|
|
273
273
|
def turbopuffer_select(namespace, query)
|
|
274
274
|
fields = if query.include_attributes == [ "*" ]
|
|
275
|
-
column_definitions(query.namespace).map(&:name)
|
|
275
|
+
column_definitions(query.namespace).reject(&:vector?).map(&:name)
|
|
276
276
|
else
|
|
277
277
|
query.include_attributes
|
|
278
278
|
end
|
|
@@ -3,17 +3,22 @@ module Turbopuffer
|
|
|
3
3
|
module Schema
|
|
4
4
|
class Attribute
|
|
5
5
|
TYPES = %r{\A(?:\[\])?(?:string|uuid|int|uint|float|bool|datetime)\z|\Abytes\z|\A\[\d+\](?:f16|f32|i8)\z|\A\{\}f16\z}
|
|
6
|
+
VECTOR_TYPES = %r{\A\[\d+\]|\A\{\}}
|
|
6
7
|
|
|
7
|
-
attr_reader :name, :type, :filterable, :full_text_search, :ann, :glob, :regex, :notnull
|
|
8
|
+
attr_reader :name, :type, :filterable, :full_text_search, :ann, :glob, :regex, :embed, :notnull
|
|
8
9
|
|
|
9
10
|
def initialize(name, type, not_null: 0, filterable: false, full_text_search: false,
|
|
10
|
-
ann: false, glob: false, regex: false)
|
|
11
|
+
ann: false, glob: false, regex: false, embed: nil)
|
|
11
12
|
type = type.to_s
|
|
12
13
|
|
|
13
14
|
unless TYPES.match?(type)
|
|
14
15
|
raise ArgumentError, "unknown turbopuffer type #{type.inspect} for attribute #{name.inspect}"
|
|
15
16
|
end
|
|
16
17
|
|
|
18
|
+
if embed && type != "string"
|
|
19
|
+
raise ArgumentError, "embed is only supported on string attributes, #{name.inspect} is #{type.inspect}"
|
|
20
|
+
end
|
|
21
|
+
|
|
17
22
|
@name = name
|
|
18
23
|
@type = type
|
|
19
24
|
@filterable = filterable
|
|
@@ -21,9 +26,12 @@ module Turbopuffer
|
|
|
21
26
|
@ann = ann
|
|
22
27
|
@glob = glob
|
|
23
28
|
@regex = regex
|
|
29
|
+
@embed = embed
|
|
24
30
|
|
|
25
31
|
@notnull = not_null
|
|
26
32
|
end
|
|
33
|
+
|
|
34
|
+
def vector? = VECTOR_TYPES.match?(type)
|
|
27
35
|
end
|
|
28
36
|
end
|
|
29
37
|
end
|
|
@@ -48,10 +48,10 @@ module Turbopuffer
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
class_methods do
|
|
51
|
-
def turbopuffer_attribute(name, type, not_null: 0, filterable: false,
|
|
52
|
-
|
|
51
|
+
def turbopuffer_attribute(name, type, not_null: 0, filterable: false, full_text_search: false,
|
|
52
|
+
ann: false, glob: false, regex: false, embed: nil)
|
|
53
53
|
attribute = ::Turbopuffer::ActiveRecord::Schema::Attribute.new(
|
|
54
|
-
name.to_s, type, not_null:, filterable:, full_text_search:, ann:, glob:, regex:
|
|
54
|
+
name.to_s, type, not_null:, filterable:, full_text_search:, ann:, glob:, regex:, embed:
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
self.turbopuffer_attributes =
|
|
@@ -78,6 +78,10 @@ module Turbopuffer
|
|
|
78
78
|
order(::Arel::Nodes::RankByNode.new(expression))
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
def with_vectors
|
|
82
|
+
select(*turbopuffer_attributes.map(&:name))
|
|
83
|
+
end
|
|
84
|
+
|
|
81
85
|
def glob(pattern, case_sensitive: true)
|
|
82
86
|
::Turbopuffer::ActiveRecord::Glob.new(pattern, case_sensitive:)
|
|
83
87
|
end
|
|
@@ -115,6 +119,7 @@ module Turbopuffer
|
|
|
115
119
|
attrs[:ann] = true if attribute.ann
|
|
116
120
|
attrs[:glob] = true if attribute.glob
|
|
117
121
|
attrs[:regex] = true if attribute.regex
|
|
122
|
+
attrs[:embed] = attribute.embed if attribute.embed
|
|
118
123
|
attrs[:sparse_knn] = { distance_metric: "dot_product" } if attribute.type == "{}f16"
|
|
119
124
|
|
|
120
125
|
schema[attribute.name] = attrs
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-turbopuffer-adapter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Richard Monette
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-10-06 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activerecord
|