ruby_llm 1.0.1 → 1.1.0rc1
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 +28 -12
- data/lib/ruby_llm/active_record/acts_as.rb +46 -7
- data/lib/ruby_llm/aliases.json +65 -0
- data/lib/ruby_llm/aliases.rb +56 -0
- data/lib/ruby_llm/chat.rb +10 -9
- data/lib/ruby_llm/configuration.rb +4 -0
- data/lib/ruby_llm/error.rb +15 -4
- data/lib/ruby_llm/models.json +1163 -303
- data/lib/ruby_llm/models.rb +40 -11
- data/lib/ruby_llm/provider.rb +32 -39
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +8 -9
- data/lib/ruby_llm/providers/anthropic/chat.rb +31 -4
- data/lib/ruby_llm/providers/anthropic/streaming.rb +12 -6
- data/lib/ruby_llm/providers/anthropic.rb +4 -0
- data/lib/ruby_llm/providers/bedrock/capabilities.rb +168 -0
- data/lib/ruby_llm/providers/bedrock/chat.rb +108 -0
- data/lib/ruby_llm/providers/bedrock/models.rb +84 -0
- data/lib/ruby_llm/providers/bedrock/signing.rb +831 -0
- data/lib/ruby_llm/providers/bedrock/streaming/base.rb +46 -0
- data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +63 -0
- data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +79 -0
- data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +90 -0
- data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +91 -0
- data/lib/ruby_llm/providers/bedrock/streaming.rb +36 -0
- data/lib/ruby_llm/providers/bedrock.rb +83 -0
- data/lib/ruby_llm/providers/deepseek/chat.rb +17 -0
- data/lib/ruby_llm/providers/deepseek.rb +5 -0
- data/lib/ruby_llm/providers/gemini/capabilities.rb +50 -34
- data/lib/ruby_llm/providers/gemini/chat.rb +8 -15
- data/lib/ruby_llm/providers/gemini/images.rb +5 -10
- data/lib/ruby_llm/providers/gemini/streaming.rb +35 -76
- data/lib/ruby_llm/providers/gemini/tools.rb +12 -12
- data/lib/ruby_llm/providers/gemini.rb +4 -0
- data/lib/ruby_llm/providers/openai/capabilities.rb +146 -206
- data/lib/ruby_llm/providers/openai/streaming.rb +9 -13
- data/lib/ruby_llm/providers/openai.rb +4 -0
- data/lib/ruby_llm/streaming.rb +96 -0
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm.rb +6 -3
- data/lib/tasks/browser_helper.rb +97 -0
- data/lib/tasks/capability_generator.rb +123 -0
- data/lib/tasks/capability_scraper.rb +224 -0
- data/lib/tasks/cli_helper.rb +22 -0
- data/lib/tasks/code_validator.rb +29 -0
- data/lib/tasks/model_updater.rb +66 -0
- data/lib/tasks/models.rake +28 -193
- data/lib/tasks/vcr.rake +13 -30
- metadata +27 -19
- data/.github/workflows/cicd.yml +0 -158
- data/.github/workflows/docs.yml +0 -53
- data/.gitignore +0 -59
- data/.overcommit.yml +0 -26
- data/.rspec +0 -3
- data/.rubocop.yml +0 -10
- data/.yardopts +0 -12
- data/CONTRIBUTING.md +0 -207
- data/Gemfile +0 -33
- data/Rakefile +0 -9
- data/bin/console +0 -17
- data/bin/setup +0 -6
- data/ruby_llm.gemspec +0 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f1ca42c438f99a40f6ada6ab87e23377403d9d6c8db6049d54960308b6dd42f
|
4
|
+
data.tar.gz: 0467734c40a0a64505b8f4de2eea4b929e2b0c615fedb3fe88d75f8cf20040de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed886fb15081ce27309c0e583223e62adf171907f0f047344763e9eae5ede4dbb96657bb1b69dde9e68bc1ed56acedb93717273067236e40104dddcc09886f33
|
7
|
+
data.tar.gz: 7f2aa9c50cfd1a525273256ff014712944c976229f6b90fe338decaf2525bcb82f19dec187d14795ce0891a7fceadb5c048905c8afa8066b0ddbe37d54c48edd
|
data/README.md
CHANGED
@@ -2,17 +2,20 @@
|
|
2
2
|
|
3
3
|
A delightful Ruby way to work with AI. No configuration madness, no complex callbacks, no handler hell – just beautiful, expressive Ruby code.
|
4
4
|
|
5
|
-
<div style="display: flex; align-items: center; flex-wrap: wrap;
|
5
|
+
<div style="display: flex; align-items: center; flex-wrap: wrap; margin-bottom: 1em">
|
6
6
|
<img src="https://upload.wikimedia.org/wikipedia/commons/4/4d/OpenAI_Logo.svg" alt="OpenAI" height="40" width="120">
|
7
|
-
 
|
7
|
+
|
8
8
|
<img src="https://upload.wikimedia.org/wikipedia/commons/7/78/Anthropic_logo.svg" alt="Anthropic" height="40" width="120">
|
9
|
-
 
|
9
|
+
|
10
10
|
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8a/Google_Gemini_logo.svg" alt="Google" height="40" width="120">
|
11
|
-
 
|
11
|
+
|
12
|
+
<img src="https://registry.npmmirror.com/@lobehub/icons-static-svg/latest/files/icons/bedrock-color.svg" alt="Bedrock" height="40">
|
13
|
+
<img src="https://registry.npmmirror.com/@lobehub/icons-static-svg/latest/files/icons/bedrock-text.svg" alt="Bedrock" height="40" width="120">
|
14
|
+
|
12
15
|
<img src="https://upload.wikimedia.org/wikipedia/commons/e/ec/DeepSeek_logo.svg" alt="DeepSeek" height="40" width="120">
|
13
16
|
</div>
|
14
17
|
|
15
|
-
<a href="https://badge.fury.io/rb/ruby_llm"><img src="https://badge.fury.io/rb/ruby_llm.svg
|
18
|
+
<a href="https://badge.fury.io/rb/ruby_llm"><img src="https://badge.fury.io/rb/ruby_llm.svg" alt="Gem Version" /></a>
|
16
19
|
<a href="https://github.com/testdouble/standard"><img src="https://img.shields.io/badge/code_style-standard-brightgreen.svg" alt="Ruby Style Guide" /></a>
|
17
20
|
<a href="https://rubygems.org/gems/ruby_llm"><img alt="Gem Downloads" src="https://img.shields.io/gem/dt/ruby_llm"></a>
|
18
21
|
<a href="https://codecov.io/gh/crmne/ruby_llm"><img src="https://codecov.io/gh/crmne/ruby_llm/branch/main/graph/badge.svg" alt="codecov" /></a>
|
@@ -27,7 +30,7 @@ RubyLLM fixes all that. One beautiful API for everything. One consistent format.
|
|
27
30
|
|
28
31
|
## Features
|
29
32
|
|
30
|
-
- 💬 **Chat** with OpenAI, Anthropic, Gemini, and DeepSeek models
|
33
|
+
- 💬 **Chat** with OpenAI, Anthropic, Gemini, AWS Bedrock Anthropic, and DeepSeek models
|
31
34
|
- 👁️ **Vision and Audio** understanding
|
32
35
|
- 📄 **PDF Analysis** for analyzing documents
|
33
36
|
- 🖼️ **Image generation** with DALL-E and other providers
|
@@ -99,10 +102,16 @@ Configure with your API keys:
|
|
99
102
|
|
100
103
|
```ruby
|
101
104
|
RubyLLM.configure do |config|
|
102
|
-
config.openai_api_key = ENV
|
103
|
-
config.anthropic_api_key = ENV
|
104
|
-
config.gemini_api_key = ENV
|
105
|
-
config.deepseek_api_key = ENV
|
105
|
+
config.openai_api_key = ENV.fetch('OPENAI_API_KEY', nil)
|
106
|
+
config.anthropic_api_key = ENV.fetch('ANTHROPIC_API_KEY', nil)
|
107
|
+
config.gemini_api_key = ENV.fetch('GEMINI_API_KEY', nil)
|
108
|
+
config.deepseek_api_key = ENV.fetch('DEEPSEEK_API_KEY', nil)
|
109
|
+
|
110
|
+
# Bedrock
|
111
|
+
config.bedrock_api_key = ENV.fetch('AWS_ACCESS_KEY_ID', nil)
|
112
|
+
config.bedrock_secret_key = ENV.fetch('AWS_SECRET_ACCESS_KEY', nil)
|
113
|
+
config.bedrock_region = ENV.fetch('AWS_REGION', nil)
|
114
|
+
config.bedrock_session_token = ENV.fetch('AWS_SESSION_TOKEN', nil)
|
106
115
|
end
|
107
116
|
```
|
108
117
|
|
@@ -126,6 +135,9 @@ chat.ask "Tell me a story about a Ruby programmer" do |chunk|
|
|
126
135
|
print chunk.content
|
127
136
|
end
|
128
137
|
|
138
|
+
# Set personality or behavior with instructions (aka system prompts) - available from 1.1.0
|
139
|
+
chat.with_instructions "You are a friendly Ruby expert who loves to help beginners"
|
140
|
+
|
129
141
|
# Understand content in multiple forms
|
130
142
|
chat.ask "Compare these diagrams", with: { image: ["diagram1.png", "diagram2.png"] }
|
131
143
|
chat.ask "Summarize this document", with: { pdf: "contract.pdf" }
|
@@ -156,8 +168,12 @@ class ToolCall < ApplicationRecord
|
|
156
168
|
acts_as_tool_call
|
157
169
|
end
|
158
170
|
|
159
|
-
# In
|
160
|
-
chat = Chat.create!
|
171
|
+
# In a background job
|
172
|
+
chat = Chat.create! model_id: "gpt-4o-mini"
|
173
|
+
|
174
|
+
# Set personality or behavior with instructions (aka system prompts) - they're persisted too! - available from 1.1.0
|
175
|
+
chat.with_instructions "You are a friendly Ruby expert who loves to help beginners"
|
176
|
+
|
161
177
|
chat.ask("What's your favorite Ruby gem?") do |chunk|
|
162
178
|
Turbo::StreamsChannel.broadcast_append_to(
|
163
179
|
chat,
|
@@ -9,7 +9,7 @@ module RubyLLM
|
|
9
9
|
extend ActiveSupport::Concern
|
10
10
|
|
11
11
|
class_methods do # rubocop:disable Metrics/BlockLength
|
12
|
-
def acts_as_chat(message_class: 'Message', tool_call_class: 'ToolCall')
|
12
|
+
def acts_as_chat(message_class: 'Message', tool_call_class: 'ToolCall')
|
13
13
|
include ChatMethods
|
14
14
|
|
15
15
|
@message_class = message_class.to_s
|
@@ -21,12 +21,6 @@ module RubyLLM
|
|
21
21
|
dependent: :destroy
|
22
22
|
|
23
23
|
delegate :complete,
|
24
|
-
:with_tool,
|
25
|
-
:with_tools,
|
26
|
-
:with_model,
|
27
|
-
:with_temperature,
|
28
|
-
:on_new_message,
|
29
|
-
:on_end_message,
|
30
24
|
:add_message,
|
31
25
|
to: :to_llm
|
32
26
|
end
|
@@ -85,6 +79,51 @@ module RubyLLM
|
|
85
79
|
.on_end_message { |msg| persist_message_completion(msg) }
|
86
80
|
end
|
87
81
|
|
82
|
+
def with_instructions(instructions, replace: false)
|
83
|
+
transaction do
|
84
|
+
# If replace is true, remove existing system messages
|
85
|
+
messages.where(role: :system).destroy_all if replace
|
86
|
+
|
87
|
+
# Create the new system message
|
88
|
+
messages.create!(
|
89
|
+
role: :system,
|
90
|
+
content: instructions
|
91
|
+
)
|
92
|
+
end
|
93
|
+
to_llm.with_instructions(instructions)
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def with_tool(tool)
|
98
|
+
to_llm.with_tool(tool)
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
def with_tools(*tools)
|
103
|
+
to_llm.with_tools(*tools)
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
def with_model(model_id, provider: nil)
|
108
|
+
to_llm.with_model(model_id, provider: provider)
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
def with_temperature(temperature)
|
113
|
+
to_llm.with_temperature(temperature)
|
114
|
+
self
|
115
|
+
end
|
116
|
+
|
117
|
+
def on_new_message(&)
|
118
|
+
to_llm.on_new_message(&)
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
def on_end_message(&)
|
123
|
+
to_llm.on_end_message(&)
|
124
|
+
self
|
125
|
+
end
|
126
|
+
|
88
127
|
def ask(message, &)
|
89
128
|
message = { role: :user, content: message }
|
90
129
|
messages.create!(**message)
|
@@ -0,0 +1,65 @@
|
|
1
|
+
{
|
2
|
+
"claude-3-5-sonnet": {
|
3
|
+
"anthropic": "claude-3-5-sonnet-20241022",
|
4
|
+
"bedrock": "anthropic.claude-3-5-sonnet-20241022-v2:0"
|
5
|
+
},
|
6
|
+
"claude-3-5-haiku": {
|
7
|
+
"anthropic": "claude-3-5-haiku-20241022",
|
8
|
+
"bedrock": "anthropic.claude-3-5-haiku-20241022-v1:0"
|
9
|
+
},
|
10
|
+
"claude-3-7-sonnet": {
|
11
|
+
"anthropic": "claude-3-7-sonnet-20250219",
|
12
|
+
"bedrock": "us.anthropic.claude-3-7-sonnet-20250219-v1:0"
|
13
|
+
},
|
14
|
+
"claude-3-opus": {
|
15
|
+
"anthropic": "claude-3-opus-20240229",
|
16
|
+
"bedrock": "anthropic.claude-3-opus-20240229-v1:0"
|
17
|
+
},
|
18
|
+
"claude-3-sonnet": {
|
19
|
+
"anthropic": "claude-3-sonnet-20240229",
|
20
|
+
"bedrock": "anthropic.claude-3-sonnet-20240229-v1:0"
|
21
|
+
},
|
22
|
+
"claude-3-haiku": {
|
23
|
+
"anthropic": "claude-3-haiku-20240307",
|
24
|
+
"bedrock": "anthropic.claude-3-haiku-20240307-v1:0"
|
25
|
+
},
|
26
|
+
"claude-3": {
|
27
|
+
"anthropic": "claude-3-sonnet-20240229",
|
28
|
+
"bedrock": "anthropic.claude-3-sonnet-20240229-v1:0"
|
29
|
+
},
|
30
|
+
"claude-2": {
|
31
|
+
"anthropic": "claude-2.0",
|
32
|
+
"bedrock": "anthropic.claude-2.0"
|
33
|
+
},
|
34
|
+
"claude-2-1": {
|
35
|
+
"anthropic": "claude-2.1",
|
36
|
+
"bedrock": "anthropic.claude-2.1"
|
37
|
+
},
|
38
|
+
"gpt-4o": {
|
39
|
+
"openai": "gpt-4o-2024-11-20"
|
40
|
+
},
|
41
|
+
"gpt-4o-mini": {
|
42
|
+
"openai": "gpt-4o-mini-2024-07-18"
|
43
|
+
},
|
44
|
+
"gpt-4-turbo": {
|
45
|
+
"openai": "gpt-4-turbo-2024-04-09"
|
46
|
+
},
|
47
|
+
"gemini-1.5-flash": {
|
48
|
+
"gemini": "gemini-1.5-flash-002"
|
49
|
+
},
|
50
|
+
"gemini-1.5-flash-8b": {
|
51
|
+
"gemini": "gemini-1.5-flash-8b-001"
|
52
|
+
},
|
53
|
+
"gemini-1.5-pro": {
|
54
|
+
"gemini": "gemini-1.5-pro-002"
|
55
|
+
},
|
56
|
+
"gemini-2.0-flash": {
|
57
|
+
"gemini": "gemini-2.0-flash-001"
|
58
|
+
},
|
59
|
+
"o1": {
|
60
|
+
"openai": "o1-2024-12-17"
|
61
|
+
},
|
62
|
+
"o3-mini": {
|
63
|
+
"openai": "o3-mini-2025-01-31"
|
64
|
+
}
|
65
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
# Manages model aliases, allowing users to reference models by simpler names
|
5
|
+
# that map to specific model versions across different providers.
|
6
|
+
#
|
7
|
+
# Aliases are defined in aliases.json and follow the format:
|
8
|
+
# {
|
9
|
+
# "simple-name": {
|
10
|
+
# "provider1": "specific-version-for-provider1",
|
11
|
+
# "provider2": "specific-version-for-provider2"
|
12
|
+
# }
|
13
|
+
# }
|
14
|
+
class Aliases
|
15
|
+
class << self
|
16
|
+
# Resolves a model ID to its provider-specific version
|
17
|
+
#
|
18
|
+
# @param model_id [String] the model identifier or alias
|
19
|
+
# @param provider_slug [String, Symbol, nil] optional provider to resolve for
|
20
|
+
# @return [String] the resolved model ID or the original if no alias exists
|
21
|
+
def resolve(model_id, provider = nil)
|
22
|
+
return model_id unless aliases[model_id]
|
23
|
+
|
24
|
+
if provider
|
25
|
+
aliases[model_id][provider.to_s] || model_id
|
26
|
+
else
|
27
|
+
# Get native provider's version
|
28
|
+
aliases[model_id].values.first || model_id
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the loaded aliases mapping
|
33
|
+
# @return [Hash] the aliases mapping
|
34
|
+
def aliases
|
35
|
+
@aliases ||= load_aliases
|
36
|
+
end
|
37
|
+
|
38
|
+
# Loads aliases from the JSON file
|
39
|
+
# @return [Hash] the loaded aliases
|
40
|
+
def load_aliases
|
41
|
+
file_path = File.expand_path('aliases.json', __dir__)
|
42
|
+
if File.exist?(file_path)
|
43
|
+
JSON.parse(File.read(file_path))
|
44
|
+
else
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Reloads aliases from disk
|
50
|
+
# @return [Hash] the reloaded aliases
|
51
|
+
def reload!
|
52
|
+
@aliases = load_aliases
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/ruby_llm/chat.rb
CHANGED
@@ -13,9 +13,9 @@ module RubyLLM
|
|
13
13
|
|
14
14
|
attr_reader :model, :messages, :tools
|
15
15
|
|
16
|
-
def initialize(model: nil)
|
16
|
+
def initialize(model: nil, provider: nil)
|
17
17
|
model_id = model || RubyLLM.config.default_model
|
18
|
-
|
18
|
+
with_model(model_id, provider: provider)
|
19
19
|
@temperature = 0.7
|
20
20
|
@messages = []
|
21
21
|
@tools = {}
|
@@ -32,6 +32,11 @@ module RubyLLM
|
|
32
32
|
|
33
33
|
alias say ask
|
34
34
|
|
35
|
+
def with_instructions(instructions)
|
36
|
+
add_message role: :system, content: instructions
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
35
40
|
def with_tool(tool)
|
36
41
|
unless @model.supports_functions
|
37
42
|
raise UnsupportedFunctionsError, "Model #{@model.id} doesn't support function calling"
|
@@ -47,13 +52,9 @@ module RubyLLM
|
|
47
52
|
self
|
48
53
|
end
|
49
54
|
|
50
|
-
def
|
51
|
-
@model = Models.find model_id
|
52
|
-
@provider =
|
53
|
-
end
|
54
|
-
|
55
|
-
def with_model(model_id)
|
56
|
-
self.model = model_id
|
55
|
+
def with_model(model_id, provider: nil)
|
56
|
+
@model = Models.find model_id, provider
|
57
|
+
@provider = Provider.providers[@model.provider.to_sym] || raise(Error, "Unknown provider: #{@model.provider}")
|
57
58
|
self
|
58
59
|
end
|
59
60
|
|
data/lib/ruby_llm/error.rb
CHANGED
@@ -19,15 +19,21 @@ module RubyLLM
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
|
22
|
+
# Error classes for non-HTTP errors
|
23
|
+
class ConfigurationError < StandardError; end
|
23
24
|
class InvalidRoleError < StandardError; end
|
25
|
+
class ModelNotFoundError < StandardError; end
|
24
26
|
class UnsupportedFunctionsError < StandardError; end
|
25
|
-
|
26
|
-
|
27
|
-
class ServiceUnavailableError < Error; end
|
27
|
+
|
28
|
+
# Error classes for different HTTP status codes
|
28
29
|
class BadRequestError < Error; end
|
30
|
+
class ForbiddenError < Error; end
|
31
|
+
class OverloadedError < Error; end
|
32
|
+
class PaymentRequiredError < Error; end
|
29
33
|
class RateLimitError < Error; end
|
30
34
|
class ServerError < Error; end
|
35
|
+
class ServiceUnavailableError < Error; end
|
36
|
+
class UnauthorizedError < Error; end
|
31
37
|
|
32
38
|
# Faraday middleware that maps provider-specific API errors to RubyLLM errors.
|
33
39
|
# Uses provider's parse_error method to extract meaningful error messages.
|
@@ -56,12 +62,17 @@ module RubyLLM
|
|
56
62
|
raise UnauthorizedError.new(response, message || 'Invalid API key - check your credentials')
|
57
63
|
when 402
|
58
64
|
raise PaymentRequiredError.new(response, message || 'Payment required - please top up your account')
|
65
|
+
when 403
|
66
|
+
raise ForbiddenError.new(response,
|
67
|
+
message || 'Forbidden - you do not have permission to access this resource')
|
59
68
|
when 429
|
60
69
|
raise RateLimitError.new(response, message || 'Rate limit exceeded - please wait a moment')
|
61
70
|
when 500
|
62
71
|
raise ServerError.new(response, message || 'API server error - please try again')
|
63
72
|
when 502..503
|
64
73
|
raise ServiceUnavailableError.new(response, message || 'API server unavailable - please try again later')
|
74
|
+
when 529
|
75
|
+
raise OverloadedError.new(response, message || 'Service overloaded - please try again later')
|
65
76
|
else
|
66
77
|
raise Error.new(response, message || 'An unknown error occurred')
|
67
78
|
end
|