rubycanusellm 0.3.0 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76737cbccfa63deb73a8a13e57c7202d42eb0689595ddae1b1cedf122f262e91
4
- data.tar.gz: 282fda500fb7f36bebcd7e4fc3683a25e264e483b63e264da5aeb21b5cf229ef
3
+ metadata.gz: bb98a91b1d857f47d5ed3905d0fc520175297366268125d382fe928a91a993a3
4
+ data.tar.gz: '0284438324a2fc028176c0405a3af011a0457617ca930abc7cbcc4012f204fa1'
5
5
  SHA512:
6
- metadata.gz: 9ffb10c5216314c8ae733b3be09391197a8e785308c42555764e7bc0144e958638dfb8a253e5d818ed6ba77eb69ee07e1b9279f4a7aafd1aca6e7f0e26f8c1b1
7
- data.tar.gz: de69c64ab2ee56df386974bd9f99ffbcbc374cd4661b6394ee3295338f02ecee6cab439fbcd0a6bd3b8d9e54b045ef065fda7a8e290d4f6c6a95cce69cdc7949
6
+ metadata.gz: 9b7d8455fca4b21d60597e8d49a765a5a210d6c314b855cceb9ca21c46924555ea2841a7bda802fb55cdfd7ad49721d9a49d2a67edafd37f8427da98ba1fe522
7
+ data.tar.gz: d7c5818f91bc9a7973b20df9a465deaa4c4878e59e48c0859ef18bc4f0a5c6e4e261ac96458d9a05af90a901cf2b6614fb2fe4588339e1598c6a9e9b403aad11
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.1] - 2026-04-03
4
+
5
+ ### Added
6
+
7
+ - Voyage AI provider for embeddings (recommended by Anthropic)
8
+ - Configurable embedding provider (`embedding_provider`, `embedding_api_key`)
9
+ - Anthropic users can now use Voyage AI or OpenAI for embeddings
10
+ - Embedding validation with clear error messages
11
+
3
12
  ## [0.2.0] - 2026-04-01
4
13
 
5
14
  ### Added
data/README.md CHANGED
@@ -63,10 +63,11 @@ That's it. Same code, different provider.
63
63
 
64
64
  ## Supported Providers
65
65
 
66
- | Provider | Models | Status |
67
- |----------|--------|--------|
68
- | OpenAI | gpt-4o-mini, gpt-4o, etc. | ✅ |
69
- | Anthropic | claude-sonnet-4-20250514, etc. | ✅ |
66
+ | Provider | Models | Status | Notes |
67
+ |----------|--------|--------|-------|
68
+ | OpenAI | gpt-4o-mini, gpt-4o, etc. | ✅ | Chat + Embeddings |
69
+ | Anthropic | claude-sonnet-4-20250514, etc. | ✅ | Chat only |
70
+ | Voyage AI | voyage-3.5, voyage-4, etc. | ✅ | Embeddings only |
70
71
 
71
72
  ## API Reference
72
73
 
@@ -77,6 +78,8 @@ RubyCanUseLLM.configure do |config|
77
78
  config.api_key = "your-key" # required
78
79
  config.model = "gpt-4o-mini" # optional, has sensible defaults
79
80
  config.timeout = 30 # optional, default 30s
81
+ config.embedding_provider = :voyage # optional, for separate embedding provider
82
+ config.embedding_api_key = "key" # required when embedding_provider is set
80
83
  end
81
84
  ```
82
85
 
@@ -118,6 +121,56 @@ response.total_tokens # 15
118
121
  response.raw # original provider response
119
122
  ```
120
123
 
124
+ ### Embeddings
125
+
126
+ ```ruby
127
+ response = RubyCanUseLLM.embed("Hello world")
128
+ response.embedding # [0.1, 0.2, ...]
129
+ response.tokens # 3
130
+ response.model # "text-embedding-3-small"
131
+ ```
132
+
133
+ **OpenAI users** — embeddings work out of the box, no extra config needed:
134
+ ```ruby
135
+ RubyCanUseLLM.configure do |config|
136
+ config.provider = :openai
137
+ config.api_key = ENV["OPENAI_API_KEY"]
138
+ end
139
+
140
+ RubyCanUseLLM.embed("Hello world")
141
+ ```
142
+
143
+ **Anthropic users with Voyage AI** (recommended by Anthropic):
144
+ ```ruby
145
+ RubyCanUseLLM.configure do |config|
146
+ config.provider = :anthropic
147
+ config.api_key = ENV["ANTHROPIC_API_KEY"]
148
+ config.embedding_provider = :voyage
149
+ config.embedding_api_key = ENV["VOYAGE_API_KEY"]
150
+ end
151
+
152
+ RubyCanUseLLM.embed("Hello world")
153
+ ```
154
+
155
+ **Anthropic users with OpenAI for embeddings:**
156
+ ```ruby
157
+ RubyCanUseLLM.configure do |config|
158
+ config.provider = :anthropic
159
+ config.api_key = ENV["ANTHROPIC_API_KEY"]
160
+ config.embedding_provider = :openai
161
+ config.embedding_api_key = ENV["OPENAI_API_KEY"]
162
+ end
163
+
164
+ RubyCanUseLLM.embed("Hello world")
165
+ ```
166
+
167
+ **Cosine similarity:**
168
+ ```ruby
169
+ a = RubyCanUseLLM.embed("cat")
170
+ b = RubyCanUseLLM.embed("dog")
171
+ a.cosine_similarity(b.embedding) # 0.87
172
+ ```
173
+
121
174
  ### Error Handling
122
175
  ```ruby
123
176
  begin
@@ -150,7 +203,9 @@ end
150
203
  - [x] `generate:completion` command
151
204
  - [x] v0.1.0 release
152
205
  - [x] Streaming support
153
- - [ ] Embeddings + `generate:embedding`
206
+ - [x] Embeddings + configurable embedding provider
207
+ - [x] Voyage AI provider (embeddings)
208
+ - [ ] `generate:embedding` command
154
209
  - [ ] Mistral and Ollama providers
155
210
  - [ ] Tool calling
156
211
 
@@ -2,15 +2,18 @@
2
2
 
3
3
  module RubyCanUseLLM
4
4
  class Configuration
5
- SUPPORTED_PROVIDERS = %i[openai anthropic].freeze
5
+ SUPPORTED_PROVIDERS = %i[openai anthropic voyage].freeze
6
+ EMBEDDING_PROVIDERS = %i[openai voyage].freeze
6
7
 
7
- attr_accessor :provider, :api_key, :model, :timeout
8
+ attr_accessor :provider, :api_key, :model, :timeout, :embedding_provider, :embedding_api_key
8
9
 
9
10
  def initialize
10
11
  @provider = nil
11
12
  @api_key = nil
12
13
  @model = nil
13
14
  @timeout = 30
15
+ @embedding_provider = nil
16
+ @embedding_api_key = nil
14
17
  end
15
18
 
16
19
  def validate!
@@ -18,5 +21,15 @@ module RubyCanUseLLM
18
21
  raise Error, "api_key is required" if api_key.nil? || api_key.empty?
19
22
  raise Error, "Unknown provider: #{provider}. Supported: #{SUPPORTED_PROVIDERS.join(", ")}" unless SUPPORTED_PROVIDERS.include?(provider)
20
23
  end
24
+
25
+ def validate_embedding!
26
+ effective = embedding_provider || provider
27
+ unless EMBEDDING_PROVIDERS.include?(effective)
28
+ raise Error, "#{provider} does not support embeddings. Set config.embedding_provider to :openai or :voyage and provide config.embedding_api_key"
29
+ end
30
+ if embedding_provider && (embedding_api_key.nil? || embedding_api_key.empty?)
31
+ raise Error, "embedding_api_key is required when embedding_provider is set"
32
+ end
33
+ end
21
34
  end
22
35
  end
@@ -0,0 +1,66 @@
1
+ require "net/http"
2
+ require "json"
3
+
4
+ module RubyCanUseLLM
5
+ module Providers
6
+ class Voyage < Base
7
+ API_URL = "https://api.voyageai.com/v1/embeddings"
8
+
9
+ def chat(messages, **options)
10
+ raise Error, "Voyage AI only supports embeddings, not chat completions"
11
+ end
12
+
13
+ def embed(text, **options)
14
+ body = {
15
+ model: options[:model] || "voyage-3.5",
16
+ input: text
17
+ }
18
+ response = embedding_request(body)
19
+ parse_embedding(response)
20
+ end
21
+
22
+ private
23
+
24
+ def embedding_request(body)
25
+ uri = URI(API_URL)
26
+ http = Net::HTTP.new(uri.host, uri.port)
27
+ http.use_ssl = true
28
+ http.read_timeout = config.timeout
29
+
30
+ req = Net::HTTP::Post.new(uri)
31
+ req["Authorization"] = "Bearer #{config.api_key}"
32
+ req["Content-Type"] = "application/json"
33
+ req.body = body.to_json
34
+
35
+ handle_response(http.request(req))
36
+ rescue Net::ReadTimeout, Net::OpenTimeout
37
+ raise TimeoutError, "Request to Voyage AI timed out after #{config.timeout}s"
38
+ end
39
+
40
+ def handle_response(response)
41
+ case response.code.to_i
42
+ when 200
43
+ JSON.parse(response.body)
44
+ when 401
45
+ raise AuthenticationError, "Invalid Voyage AI API key"
46
+ when 429
47
+ raise RateLimitError, "Voyage AI rate limit exceeded"
48
+ else
49
+ raise ProviderError, "Voyage AI error (#{response.code}): #{response.body}"
50
+ end
51
+ end
52
+
53
+ def parse_embedding(data)
54
+ embedding = data.dig("data", 0, "embedding")
55
+ usage = data["usage"]
56
+
57
+ EmbeddingResponse.new(
58
+ embedding: embedding,
59
+ model: data["model"],
60
+ tokens: usage["total_tokens"],
61
+ raw: data
62
+ )
63
+ end
64
+ end
65
+ end
66
+ end
@@ -13,4 +13,10 @@ RubyCanUseLLM.configure do |config|
13
13
 
14
14
  # Request timeout in seconds (default: 30)
15
15
  # config.timeout = 30
16
+
17
+ # Embedding provider (optional, defaults to main provider)
18
+ # Anthropic doesn't support embeddings natively.
19
+ # Use :voyage (recommended by Anthropic) or :openai for embeddings.
20
+ # config.embedding_provider = :voyage
21
+ # config.embedding_api_key = ENV["VOYAGE_API_KEY"]
16
22
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubycanusellm
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.1"
5
5
  end
data/lib/rubycanusellm.rb CHANGED
@@ -8,12 +8,14 @@ require_relative "rubycanusellm/chunk"
8
8
  require_relative "rubycanusellm/providers/base"
9
9
  require_relative "rubycanusellm/providers/openai"
10
10
  require_relative "rubycanusellm/providers/anthropic"
11
+ require_relative "rubycanusellm/providers/voyage"
11
12
  require_relative "rubycanusellm/embedding_response"
12
13
 
13
14
  module RubyCanUseLLM
14
15
  PROVIDERS = {
15
16
  openai: Providers::OpenAI,
16
- anthropic: Providers::Anthropic
17
+ anthropic: Providers::Anthropic,
18
+ voyage: Providers::Voyage
17
19
  }.freeze
18
20
 
19
21
  class << self
@@ -28,6 +30,7 @@ module RubyCanUseLLM
28
30
  def reset!
29
31
  @configuration = Configuration.new
30
32
  @client = nil
33
+ @embedding_client = nil
31
34
  end
32
35
 
33
36
  def client
@@ -37,12 +40,27 @@ module RubyCanUseLLM
37
40
  end.new(configuration)
38
41
  end
39
42
 
43
+ def embedding_client
44
+ configuration.validate_embedding!
45
+ if configuration.embedding_provider
46
+ @embedding_client ||= begin
47
+ cfg = Configuration.new
48
+ cfg.provider = configuration.embedding_provider
49
+ cfg.api_key = configuration.embedding_api_key
50
+ cfg.timeout = configuration.timeout
51
+ PROVIDERS[configuration.embedding_provider].new(cfg)
52
+ end
53
+ else
54
+ client
55
+ end
56
+ end
57
+
40
58
  def chat(messages, **options, &block)
41
59
  client.chat(messages, **options, &block)
42
60
  end
43
61
 
44
62
  def embed(text, **options)
45
- client.embed(text, **options)
63
+ embedding_client.embed(text, **options)
46
64
  end
47
65
  end
48
66
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubycanusellm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juan Manuel Guzman Nava
@@ -36,6 +36,7 @@ files:
36
36
  - lib/rubycanusellm/providers/anthropic.rb
37
37
  - lib/rubycanusellm/providers/base.rb
38
38
  - lib/rubycanusellm/providers/openai.rb
39
+ - lib/rubycanusellm/providers/voyage.rb
39
40
  - lib/rubycanusellm/response.rb
40
41
  - lib/rubycanusellm/templates/completion.rb.tt
41
42
  - lib/rubycanusellm/templates/config.rb.tt