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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +60 -5
- data/lib/rubycanusellm/configuration.rb +15 -2
- data/lib/rubycanusellm/providers/voyage.rb +66 -0
- data/lib/rubycanusellm/templates/config.rb.tt +6 -0
- data/lib/rubycanusellm/version.rb +1 -1
- data/lib/rubycanusellm.rb +20 -2
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bb98a91b1d857f47d5ed3905d0fc520175297366268125d382fe928a91a993a3
|
|
4
|
+
data.tar.gz: '0284438324a2fc028176c0405a3af011a0457617ca930abc7cbcc4012f204fa1'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
- [
|
|
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
|
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
|
-
|
|
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.
|
|
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
|