ruby_llm-voyage 0.1.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 +7 -0
- data/CHANGELOG.md +45 -0
- data/CONTRIBUTING.md +72 -0
- data/LICENSE.txt +21 -0
- data/README.md +389 -0
- data/SECURITY.md +31 -0
- data/docs/RELEASING.md +91 -0
- data/lib/ruby_llm/providers/voyage/contextualized_embeddings.rb +62 -0
- data/lib/ruby_llm/providers/voyage/embeddings.rb +80 -0
- data/lib/ruby_llm/providers/voyage/files.rb +126 -0
- data/lib/ruby_llm/providers/voyage/reranking.rb +47 -0
- data/lib/ruby_llm/providers/voyage.rb +80 -0
- data/lib/ruby_llm/voyage/results.rb +74 -0
- data/lib/ruby_llm/voyage/vector_decoder.rb +39 -0
- data/lib/ruby_llm/voyage/version.rb +18 -0
- data/lib/ruby_llm/voyage.rb +258 -0
- data/lib/ruby_llm-voyage.rb +4 -0
- metadata +91 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ruby_llm'
|
|
4
|
+
require 'ruby_llm/voyage/version'
|
|
5
|
+
require 'ruby_llm/voyage/results'
|
|
6
|
+
require 'ruby_llm/voyage/vector_decoder'
|
|
7
|
+
require 'ruby_llm/providers/voyage'
|
|
8
|
+
|
|
9
|
+
# RubyLLM framework namespace.
|
|
10
|
+
module RubyLLM
|
|
11
|
+
# Voyage retrieval APIs for RubyLLM: embeddings, reranking, contextualized
|
|
12
|
+
# chunk embeddings, and files.
|
|
13
|
+
module Voyage
|
|
14
|
+
# Generates embeddings with a Voyage text embedding model.
|
|
15
|
+
#
|
|
16
|
+
# This facade exposes Voyage-specific options that are not first-class
|
|
17
|
+
# keywords on +RubyLLM.embed+ in RubyLLM 1.x. It returns the same
|
|
18
|
+
# {RubyLLM::Embedding} result type and emits the same instrumentation event.
|
|
19
|
+
#
|
|
20
|
+
# @param text [String, Array<String>] one input or a batch of inputs
|
|
21
|
+
# @param model [String, nil] Voyage model ID, such as +voyage-4-lite+;
|
|
22
|
+
# defaults to +config.default_embedding_model+
|
|
23
|
+
# @param dimensions [Integer, nil] requested output dimensions; defaults
|
|
24
|
+
# to +config.voyage_default_output_dimension+
|
|
25
|
+
# @param input_type [String, Symbol, nil] +query+, +document+, or +nil+
|
|
26
|
+
# @param truncation [Boolean, nil] whether Voyage may truncate long inputs
|
|
27
|
+
# @param output_dtype [String, Symbol, nil] +float+, +int8+, +uint8+,
|
|
28
|
+
# +binary+, +ubinary+, or +nil+
|
|
29
|
+
# @param encoding_format [String, Symbol, nil] +base64+ or +nil+
|
|
30
|
+
# @param provider_options [Hash] trusted fields merged into the Voyage
|
|
31
|
+
# request after named options
|
|
32
|
+
# @param context [RubyLLM::Context, nil] isolated RubyLLM configuration
|
|
33
|
+
# @return [RubyLLM::Embedding] vectors, model ID, and input-token usage
|
|
34
|
+
# @raise [RubyLLM::ConfigurationError] when the Voyage API key is missing
|
|
35
|
+
# @raise [RubyLLM::Error] when Voyage returns an unsuccessful response
|
|
36
|
+
#
|
|
37
|
+
# @example Embed a retrieval query
|
|
38
|
+
# embedding = RubyLLM::Voyage.embed(
|
|
39
|
+
# "How do I configure SSO?",
|
|
40
|
+
# model: "voyage-4-lite",
|
|
41
|
+
# input_type: :query,
|
|
42
|
+
# dimensions: 512
|
|
43
|
+
# )
|
|
44
|
+
# embedding.vectors #=> Array<Float>
|
|
45
|
+
def self.embed(text, model: nil, dimensions: nil, input_type: UNSET, truncation: UNSET, output_dtype: UNSET,
|
|
46
|
+
encoding_format: UNSET, provider_options: {}, context: nil)
|
|
47
|
+
validate_input!(:text, text)
|
|
48
|
+
config = context&.config || RubyLLM.config
|
|
49
|
+
model ||= config.default_embedding_model
|
|
50
|
+
provider = Providers::Voyage.new(config)
|
|
51
|
+
payload = {
|
|
52
|
+
provider: provider.slug,
|
|
53
|
+
model: model,
|
|
54
|
+
input: text,
|
|
55
|
+
dimensions: dimensions,
|
|
56
|
+
input_type: input_type,
|
|
57
|
+
truncation: truncation,
|
|
58
|
+
output_dtype: output_dtype,
|
|
59
|
+
encoding_format: encoding_format
|
|
60
|
+
}.reject { |_key, value| value.equal?(UNSET) }
|
|
61
|
+
|
|
62
|
+
RubyLLM.instrument('embedding.ruby_llm', payload, config:) do |event|
|
|
63
|
+
result = provider.embed(
|
|
64
|
+
text,
|
|
65
|
+
model:,
|
|
66
|
+
dimensions:,
|
|
67
|
+
input_type:,
|
|
68
|
+
truncation:,
|
|
69
|
+
output_dtype:,
|
|
70
|
+
encoding_format:,
|
|
71
|
+
provider_options:
|
|
72
|
+
)
|
|
73
|
+
event[:result] = result
|
|
74
|
+
event[:response_model] = result.model
|
|
75
|
+
event[:input_tokens] = result.input_tokens
|
|
76
|
+
result
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Embeds search text for the query side of retrieval.
|
|
81
|
+
#
|
|
82
|
+
# Equivalent to {.embed} with +input_type: 'query'+. Pair with
|
|
83
|
+
# {.embed_documents} so Voyage optimizes both sides of retrieval, and
|
|
84
|
+
# configure +default_embedding_model+ and +voyage_default_output_dimension+ once
|
|
85
|
+
# to keep call sites small.
|
|
86
|
+
#
|
|
87
|
+
# @param text [String, Array<String>] one search input or a batch
|
|
88
|
+
# @param options [Hash] the same options as {.embed}, except +input_type+
|
|
89
|
+
# @return [RubyLLM::Embedding]
|
|
90
|
+
# @raise [ArgumentError] when +input_type+ is passed
|
|
91
|
+
#
|
|
92
|
+
# @example
|
|
93
|
+
# RubyLLM::Voyage.embed_query('How do I configure SSO?').vectors
|
|
94
|
+
def self.embed_query(text, **options)
|
|
95
|
+
embed_side(text, 'query', __method__, options)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Embeds content for the document side of retrieval.
|
|
99
|
+
#
|
|
100
|
+
# Equivalent to {.embed} with +input_type: 'document'+. Accepts one text
|
|
101
|
+
# or a batch of up to 1,000.
|
|
102
|
+
#
|
|
103
|
+
# @param texts [String, Array<String>] content to index
|
|
104
|
+
# @param options [Hash] the same options as {.embed}, except +input_type+
|
|
105
|
+
# @return [RubyLLM::Embedding]
|
|
106
|
+
# @raise [ArgumentError] when +input_type+ is passed
|
|
107
|
+
#
|
|
108
|
+
# @example
|
|
109
|
+
# RubyLLM::Voyage.embed_documents(articles.map(&:content)).vectors
|
|
110
|
+
def self.embed_documents(texts, **options)
|
|
111
|
+
embed_side(texts, 'document', __method__, options)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Reranks documents for a query with a Voyage reranker.
|
|
115
|
+
# @param query [String]
|
|
116
|
+
# @param documents [Array<String>]
|
|
117
|
+
# @param model [String]
|
|
118
|
+
# @param top_k [Integer, nil]
|
|
119
|
+
# @param return_documents [Boolean]
|
|
120
|
+
# @param truncation [Boolean]
|
|
121
|
+
# @param provider_options [Hash]
|
|
122
|
+
# @param context [RubyLLM::Context, nil]
|
|
123
|
+
# @return [RubyLLM::Voyage::Reranking]
|
|
124
|
+
def self.rerank(query:, documents:, model: 'rerank-2.5-lite', top_k: nil, return_documents: UNSET,
|
|
125
|
+
truncation: UNSET, provider_options: {}, context: nil)
|
|
126
|
+
validate_input!(:query, query)
|
|
127
|
+
validate_input!(:documents, documents)
|
|
128
|
+
config = context&.config || RubyLLM.config
|
|
129
|
+
provider = Providers::Voyage.new(config)
|
|
130
|
+
payload = { model: model, query: query, document_count: documents.length }
|
|
131
|
+
RubyLLM.instrument('rerank.voyage', payload, config:) do |event|
|
|
132
|
+
result = provider.rerank(
|
|
133
|
+
query:, documents:, model:, top_k:, return_documents:, truncation:, provider_options:
|
|
134
|
+
)
|
|
135
|
+
event[:result] = result
|
|
136
|
+
event[:response_model] = result.model
|
|
137
|
+
event[:input_tokens] = result.input_tokens
|
|
138
|
+
result
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Generates context-aware embeddings for grouped document chunks.
|
|
143
|
+
# @param inputs [Array<String>, Array<Array<String>>]
|
|
144
|
+
# @param model [String]
|
|
145
|
+
# @param input_type [String, Symbol, nil]
|
|
146
|
+
# @param dimensions [Integer, nil] defaults to
|
|
147
|
+
# +config.voyage_default_output_dimension+
|
|
148
|
+
# @param output_dtype [String, Symbol, nil]
|
|
149
|
+
# @param encoding_format [String, Symbol, nil]
|
|
150
|
+
# @param auto_chunk [Boolean, nil]
|
|
151
|
+
# @param chunk_size [Integer, nil]
|
|
152
|
+
# @param chunk_overlap [Integer, nil]
|
|
153
|
+
# @param provider_options [Hash]
|
|
154
|
+
# @param context [RubyLLM::Context, nil]
|
|
155
|
+
# @return [RubyLLM::Voyage::ContextualizedEmbedding]
|
|
156
|
+
def self.contextualized_embed(inputs, model: 'voyage-context-4', input_type: UNSET, dimensions: nil,
|
|
157
|
+
output_dtype: UNSET, encoding_format: UNSET, auto_chunk: nil, chunk_size: nil,
|
|
158
|
+
chunk_overlap: nil, provider_options: {}, context: nil)
|
|
159
|
+
validate_input!(:inputs, inputs)
|
|
160
|
+
config = context&.config || RubyLLM.config
|
|
161
|
+
provider = Providers::Voyage.new(config)
|
|
162
|
+
payload = { model: model, input_count: inputs.length, input_type: input_type, auto_chunk: auto_chunk }
|
|
163
|
+
.reject { |_key, value| value.equal?(UNSET) }
|
|
164
|
+
RubyLLM.instrument('contextualized_embedding.voyage', payload, config:) do |event|
|
|
165
|
+
result = provider.contextualized_embed(
|
|
166
|
+
inputs, model:, input_type:, dimensions:, output_dtype:, encoding_format:, auto_chunk:, chunk_size:,
|
|
167
|
+
chunk_overlap:, provider_options:
|
|
168
|
+
)
|
|
169
|
+
event[:result] = result
|
|
170
|
+
event[:response_model] = result.model
|
|
171
|
+
event[:input_tokens] = result.input_tokens
|
|
172
|
+
result
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Uploads a JSONL file to Voyage.
|
|
177
|
+
# @param path [String, Pathname]
|
|
178
|
+
# @param purpose [String]
|
|
179
|
+
# @param context [RubyLLM::Context, nil]
|
|
180
|
+
# @return [RubyLLM::Voyage::VoyageFile]
|
|
181
|
+
def self.upload_file(path, purpose: 'batch', context: nil)
|
|
182
|
+
provider_for(context).upload_file(path, purpose:)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Retrieves metadata for one Voyage file.
|
|
186
|
+
# @param file_id [String]
|
|
187
|
+
# @param context [RubyLLM::Context, nil]
|
|
188
|
+
# @return [RubyLLM::Voyage::VoyageFile]
|
|
189
|
+
def self.retrieve_file(file_id, context: nil)
|
|
190
|
+
provider_for(context).retrieve_file(file_id)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Lists Voyage files with cursor pagination.
|
|
194
|
+
# @param purpose [String, nil]
|
|
195
|
+
# @param limit [Integer, nil]
|
|
196
|
+
# @param order [String, Symbol, nil]
|
|
197
|
+
# @param after [String, nil]
|
|
198
|
+
# @param context [RubyLLM::Context, nil]
|
|
199
|
+
# @return [RubyLLM::Voyage::Page]
|
|
200
|
+
def self.list_files(purpose: nil, limit: nil, order: nil, after: nil, context: nil)
|
|
201
|
+
provider_for(context).list_files(purpose:, limit:, order: order&.to_s, after:)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Downloads a Voyage file's raw contents.
|
|
205
|
+
# @param file_id [String]
|
|
206
|
+
# @param context [RubyLLM::Context, nil]
|
|
207
|
+
# @return [String]
|
|
208
|
+
def self.file_content(file_id, context: nil)
|
|
209
|
+
provider_for(context).file_content(file_id)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Deletes one or more Voyage files atomically.
|
|
213
|
+
# @param file_ids [Array<String>]
|
|
214
|
+
# @param context [RubyLLM::Context, nil]
|
|
215
|
+
# @return [RubyLLM::Voyage::FileDeletion]
|
|
216
|
+
def self.delete_files(file_ids, context: nil)
|
|
217
|
+
provider_for(context).delete_files(file_ids)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
class << self
|
|
221
|
+
# Alias of {.retrieve_file}, matching the +find_file+ naming used by
|
|
222
|
+
# RubyLLM's provider file API.
|
|
223
|
+
alias find_file retrieve_file
|
|
224
|
+
|
|
225
|
+
# Alias of {.file_content}, matching the +download_file+ naming used by
|
|
226
|
+
# RubyLLM's provider file API.
|
|
227
|
+
alias download_file file_content
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Builds a configured Voyage provider for resource operations.
|
|
231
|
+
# @api private
|
|
232
|
+
def self.provider_for(context)
|
|
233
|
+
Providers::Voyage.new(context&.config || RubyLLM.config)
|
|
234
|
+
end
|
|
235
|
+
private_class_method :provider_for
|
|
236
|
+
|
|
237
|
+
# Fails fast on inputs Voyage would reject, before any network request.
|
|
238
|
+
# @api private
|
|
239
|
+
def self.validate_input!(name, value)
|
|
240
|
+
raise ArgumentError, "#{name} cannot be nil" if value.nil?
|
|
241
|
+
raise ArgumentError, "#{name} cannot be empty" if value.respond_to?(:empty?) && value.empty?
|
|
242
|
+
end
|
|
243
|
+
private_class_method :validate_input!
|
|
244
|
+
|
|
245
|
+
# Embeds one retrieval side with a fixed input type.
|
|
246
|
+
# @api private
|
|
247
|
+
def self.embed_side(text, input_type, method_name, options)
|
|
248
|
+
if options.key?(:input_type)
|
|
249
|
+
raise ArgumentError, "#{method_name} sets input_type: '#{input_type}'; use embed to pass another input_type"
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
embed(text, input_type: input_type, **options)
|
|
253
|
+
end
|
|
254
|
+
private_class_method :embed_side
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
RubyLLM::Provider.register :voyage, RubyLLM::Providers::Voyage
|
metadata
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ruby_llm-voyage
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Daniel Insley
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ruby_llm
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.16'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '2.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '1.16'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '2.0'
|
|
32
|
+
description: A RubyLLM extension for Voyage embeddings, reranking, contextualized
|
|
33
|
+
chunks, and files.
|
|
34
|
+
email:
|
|
35
|
+
- dan@insley.ca
|
|
36
|
+
executables: []
|
|
37
|
+
extensions: []
|
|
38
|
+
extra_rdoc_files:
|
|
39
|
+
- CHANGELOG.md
|
|
40
|
+
- LICENSE.txt
|
|
41
|
+
- README.md
|
|
42
|
+
files:
|
|
43
|
+
- CHANGELOG.md
|
|
44
|
+
- CONTRIBUTING.md
|
|
45
|
+
- LICENSE.txt
|
|
46
|
+
- README.md
|
|
47
|
+
- SECURITY.md
|
|
48
|
+
- docs/RELEASING.md
|
|
49
|
+
- lib/ruby_llm-voyage.rb
|
|
50
|
+
- lib/ruby_llm/providers/voyage.rb
|
|
51
|
+
- lib/ruby_llm/providers/voyage/contextualized_embeddings.rb
|
|
52
|
+
- lib/ruby_llm/providers/voyage/embeddings.rb
|
|
53
|
+
- lib/ruby_llm/providers/voyage/files.rb
|
|
54
|
+
- lib/ruby_llm/providers/voyage/reranking.rb
|
|
55
|
+
- lib/ruby_llm/voyage.rb
|
|
56
|
+
- lib/ruby_llm/voyage/results.rb
|
|
57
|
+
- lib/ruby_llm/voyage/vector_decoder.rb
|
|
58
|
+
- lib/ruby_llm/voyage/version.rb
|
|
59
|
+
homepage: https://github.com/dinsley/ruby_llm-voyage
|
|
60
|
+
licenses:
|
|
61
|
+
- MIT
|
|
62
|
+
metadata:
|
|
63
|
+
homepage_uri: https://github.com/dinsley/ruby_llm-voyage
|
|
64
|
+
source_code_uri: https://github.com/dinsley/ruby_llm-voyage/tree/v0.1.0
|
|
65
|
+
changelog_uri: https://github.com/dinsley/ruby_llm-voyage/blob/main/CHANGELOG.md
|
|
66
|
+
bug_tracker_uri: https://github.com/dinsley/ruby_llm-voyage/issues
|
|
67
|
+
documentation_uri: https://www.rubydoc.info/gems/ruby_llm-voyage/0.1.0
|
|
68
|
+
allowed_push_host: https://rubygems.org
|
|
69
|
+
rubygems_mfa_required: 'true'
|
|
70
|
+
rdoc_options:
|
|
71
|
+
- "--main"
|
|
72
|
+
- README.md
|
|
73
|
+
- "--markup"
|
|
74
|
+
- markdown
|
|
75
|
+
require_paths:
|
|
76
|
+
- lib
|
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: 3.1.3
|
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
requirements: []
|
|
88
|
+
rubygems_version: 4.0.10
|
|
89
|
+
specification_version: 4
|
|
90
|
+
summary: Voyage retrieval APIs for RubyLLM
|
|
91
|
+
test_files: []
|