ruby_llm 1.10.0 → 1.12.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 +4 -4
- data/README.md +14 -2
- data/lib/ruby_llm/active_record/acts_as_legacy.rb +41 -7
- data/lib/ruby_llm/active_record/chat_methods.rb +41 -7
- data/lib/ruby_llm/agent.rb +323 -0
- data/lib/ruby_llm/aliases.json +50 -32
- data/lib/ruby_llm/chat.rb +27 -3
- data/lib/ruby_llm/configuration.rb +4 -0
- data/lib/ruby_llm/models.json +19806 -5991
- data/lib/ruby_llm/models.rb +35 -6
- data/lib/ruby_llm/provider.rb +13 -1
- data/lib/ruby_llm/providers/anthropic/media.rb +2 -2
- data/lib/ruby_llm/providers/azure/chat.rb +29 -0
- data/lib/ruby_llm/providers/azure/embeddings.rb +24 -0
- data/lib/ruby_llm/providers/azure/media.rb +45 -0
- data/lib/ruby_llm/providers/azure/models.rb +14 -0
- data/lib/ruby_llm/providers/azure.rb +56 -0
- data/lib/ruby_llm/providers/bedrock/auth.rb +122 -0
- data/lib/ruby_llm/providers/bedrock/chat.rb +297 -56
- data/lib/ruby_llm/providers/bedrock/media.rb +62 -33
- data/lib/ruby_llm/providers/bedrock/models.rb +88 -65
- data/lib/ruby_llm/providers/bedrock/streaming.rb +305 -8
- data/lib/ruby_llm/providers/bedrock.rb +61 -52
- data/lib/ruby_llm/providers/openai/media.rb +1 -1
- data/lib/ruby_llm/providers/xai/chat.rb +15 -0
- data/lib/ruby_llm/providers/xai/models.rb +75 -0
- data/lib/ruby_llm/providers/xai.rb +28 -0
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm.rb +14 -8
- data/lib/tasks/models.rake +10 -4
- data/lib/tasks/vcr.rake +32 -0
- metadata +16 -13
- data/lib/ruby_llm/providers/bedrock/capabilities.rb +0 -167
- data/lib/ruby_llm/providers/bedrock/signing.rb +0 -831
- data/lib/ruby_llm/providers/bedrock/streaming/base.rb +0 -51
- data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +0 -128
- data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +0 -67
- data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +0 -85
- data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +0 -78
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
class XAI
|
|
6
|
+
# Models metadata for xAI list models.
|
|
7
|
+
module Models
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
IMAGE_MODELS = %w[grok-2-image-1212].freeze
|
|
11
|
+
VISION_MODELS = %w[
|
|
12
|
+
grok-2-vision-1212
|
|
13
|
+
grok-4-0709
|
|
14
|
+
grok-4-fast-non-reasoning
|
|
15
|
+
grok-4-fast-reasoning
|
|
16
|
+
grok-4-1-fast-non-reasoning
|
|
17
|
+
grok-4-1-fast-reasoning
|
|
18
|
+
].freeze
|
|
19
|
+
REASONING_MODELS = %w[
|
|
20
|
+
grok-3-mini
|
|
21
|
+
grok-4-0709
|
|
22
|
+
grok-4-fast-reasoning
|
|
23
|
+
grok-4-1-fast-reasoning
|
|
24
|
+
grok-code-fast-1
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
def parse_list_models_response(response, slug, _capabilities)
|
|
28
|
+
Array(response.body['data']).map do |model_data|
|
|
29
|
+
model_id = model_data['id']
|
|
30
|
+
|
|
31
|
+
Model::Info.new(
|
|
32
|
+
id: model_id,
|
|
33
|
+
name: format_display_name(model_id),
|
|
34
|
+
provider: slug,
|
|
35
|
+
family: 'grok',
|
|
36
|
+
created_at: model_data['created'] ? Time.at(model_data['created']) : nil,
|
|
37
|
+
context_window: nil,
|
|
38
|
+
max_output_tokens: nil,
|
|
39
|
+
modalities: modalities_for(model_id),
|
|
40
|
+
capabilities: capabilities_for(model_id),
|
|
41
|
+
pricing: {},
|
|
42
|
+
metadata: {
|
|
43
|
+
object: model_data['object'],
|
|
44
|
+
owned_by: model_data['owned_by']
|
|
45
|
+
}.compact
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def modalities_for(model_id)
|
|
51
|
+
if IMAGE_MODELS.include?(model_id)
|
|
52
|
+
{ input: ['text'], output: ['image'] }
|
|
53
|
+
else
|
|
54
|
+
input = ['text']
|
|
55
|
+
input << 'image' if VISION_MODELS.include?(model_id)
|
|
56
|
+
{ input: input, output: ['text'] }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def capabilities_for(model_id)
|
|
61
|
+
return [] if IMAGE_MODELS.include?(model_id)
|
|
62
|
+
|
|
63
|
+
capabilities = %w[streaming function_calling structured_output]
|
|
64
|
+
capabilities << 'reasoning' if REASONING_MODELS.include?(model_id)
|
|
65
|
+
capabilities << 'vision' if VISION_MODELS.include?(model_id)
|
|
66
|
+
capabilities
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def format_display_name(model_id)
|
|
70
|
+
model_id.tr('-', ' ').split.map(&:capitalize).join(' ')
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
# xAI API integration
|
|
6
|
+
class XAI < OpenAI
|
|
7
|
+
include XAI::Chat
|
|
8
|
+
include XAI::Models
|
|
9
|
+
|
|
10
|
+
def api_base
|
|
11
|
+
'https://api.x.ai/v1'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def headers
|
|
15
|
+
{
|
|
16
|
+
'Authorization' => "Bearer #{@config.xai_api_key}",
|
|
17
|
+
'Content-Type' => 'application/json'
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
def configuration_requirements
|
|
23
|
+
%i[xai_api_key]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/ruby_llm/version.rb
CHANGED
data/lib/ruby_llm.rb
CHANGED
|
@@ -9,23 +9,27 @@ require 'json'
|
|
|
9
9
|
require 'logger'
|
|
10
10
|
require 'marcel'
|
|
11
11
|
require 'securerandom'
|
|
12
|
+
require 'date'
|
|
13
|
+
require 'time'
|
|
12
14
|
require 'zeitwerk'
|
|
13
15
|
|
|
14
16
|
loader = Zeitwerk::Loader.for_gem
|
|
15
17
|
loader.inflector.inflect(
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'openai' => 'OpenAI',
|
|
18
|
+
'azure' => 'Azure',
|
|
19
|
+
'UI' => 'UI',
|
|
19
20
|
'api' => 'API',
|
|
20
|
-
'deepseek' => 'DeepSeek',
|
|
21
|
-
'perplexity' => 'Perplexity',
|
|
22
21
|
'bedrock' => 'Bedrock',
|
|
23
|
-
'
|
|
22
|
+
'deepseek' => 'DeepSeek',
|
|
24
23
|
'gpustack' => 'GPUStack',
|
|
24
|
+
'llm' => 'LLM',
|
|
25
25
|
'mistral' => 'Mistral',
|
|
26
|
-
'
|
|
26
|
+
'openai' => 'OpenAI',
|
|
27
|
+
'openrouter' => 'OpenRouter',
|
|
27
28
|
'pdf' => 'PDF',
|
|
28
|
-
'
|
|
29
|
+
'perplexity' => 'Perplexity',
|
|
30
|
+
'ruby_llm' => 'RubyLLM',
|
|
31
|
+
'vertexai' => 'VertexAI',
|
|
32
|
+
'xai' => 'XAI'
|
|
29
33
|
)
|
|
30
34
|
loader.ignore("#{__dir__}/tasks")
|
|
31
35
|
loader.ignore("#{__dir__}/generators")
|
|
@@ -90,6 +94,7 @@ module RubyLLM
|
|
|
90
94
|
end
|
|
91
95
|
|
|
92
96
|
RubyLLM::Provider.register :anthropic, RubyLLM::Providers::Anthropic
|
|
97
|
+
RubyLLM::Provider.register :azure, RubyLLM::Providers::Azure
|
|
93
98
|
RubyLLM::Provider.register :bedrock, RubyLLM::Providers::Bedrock
|
|
94
99
|
RubyLLM::Provider.register :deepseek, RubyLLM::Providers::DeepSeek
|
|
95
100
|
RubyLLM::Provider.register :gemini, RubyLLM::Providers::Gemini
|
|
@@ -100,6 +105,7 @@ RubyLLM::Provider.register :openai, RubyLLM::Providers::OpenAI
|
|
|
100
105
|
RubyLLM::Provider.register :openrouter, RubyLLM::Providers::OpenRouter
|
|
101
106
|
RubyLLM::Provider.register :perplexity, RubyLLM::Providers::Perplexity
|
|
102
107
|
RubyLLM::Provider.register :vertexai, RubyLLM::Providers::VertexAI
|
|
108
|
+
RubyLLM::Provider.register :xai, RubyLLM::Providers::XAI
|
|
103
109
|
|
|
104
110
|
if defined?(Rails::Railtie)
|
|
105
111
|
require 'ruby_llm/railtie'
|
data/lib/tasks/models.rake
CHANGED
|
@@ -39,15 +39,18 @@ end
|
|
|
39
39
|
|
|
40
40
|
def configure_from_env
|
|
41
41
|
RubyLLM.configure do |config|
|
|
42
|
-
config.openai_api_key = ENV.fetch('OPENAI_API_KEY', nil)
|
|
43
42
|
config.anthropic_api_key = ENV.fetch('ANTHROPIC_API_KEY', nil)
|
|
44
|
-
config.
|
|
43
|
+
config.azure_api_base = ENV.fetch('AZURE_API_BASE', nil)
|
|
44
|
+
config.azure_api_key = ENV.fetch('AZURE_API_KEY', nil)
|
|
45
45
|
config.deepseek_api_key = ENV.fetch('DEEPSEEK_API_KEY', nil)
|
|
46
|
-
config.
|
|
47
|
-
config.openrouter_api_key = ENV.fetch('OPENROUTER_API_KEY', nil)
|
|
46
|
+
config.gemini_api_key = ENV.fetch('GEMINI_API_KEY', nil)
|
|
48
47
|
config.mistral_api_key = ENV.fetch('MISTRAL_API_KEY', nil)
|
|
48
|
+
config.openai_api_key = ENV.fetch('OPENAI_API_KEY', nil)
|
|
49
|
+
config.openrouter_api_key = ENV.fetch('OPENROUTER_API_KEY', nil)
|
|
50
|
+
config.perplexity_api_key = ENV.fetch('PERPLEXITY_API_KEY', nil)
|
|
49
51
|
config.vertexai_location = ENV.fetch('GOOGLE_CLOUD_LOCATION', nil)
|
|
50
52
|
config.vertexai_project_id = ENV.fetch('GOOGLE_CLOUD_PROJECT', nil)
|
|
53
|
+
config.xai_api_key = ENV.fetch('XAI_API_KEY', nil)
|
|
51
54
|
configure_bedrock(config)
|
|
52
55
|
config.request_timeout = 30
|
|
53
56
|
end
|
|
@@ -330,6 +333,7 @@ def generate_aliases # rubocop:disable Metrics/PerceivedComplexity
|
|
|
330
333
|
# OpenAI models
|
|
331
334
|
models['openai'].each do |model|
|
|
332
335
|
openrouter_model = "openai/#{model}"
|
|
336
|
+
azure_model = models['azure'].include?(model) ? model : nil
|
|
333
337
|
next unless models['openrouter'].include?(openrouter_model)
|
|
334
338
|
|
|
335
339
|
alias_key = model.gsub('-latest', '')
|
|
@@ -337,6 +341,7 @@ def generate_aliases # rubocop:disable Metrics/PerceivedComplexity
|
|
|
337
341
|
'openai' => model,
|
|
338
342
|
'openrouter' => openrouter_model
|
|
339
343
|
}
|
|
344
|
+
aliases[alias_key]['azure'] = azure_model if azure_model
|
|
340
345
|
end
|
|
341
346
|
|
|
342
347
|
anthropic_latest = group_anthropic_models_by_base_name(models['anthropic'])
|
|
@@ -357,6 +362,7 @@ def generate_aliases # rubocop:disable Metrics/PerceivedComplexity
|
|
|
357
362
|
aliases[base_name] = { 'anthropic' => latest_model }
|
|
358
363
|
aliases[base_name]['openrouter'] = openrouter_model if openrouter_model
|
|
359
364
|
aliases[base_name]['bedrock'] = bedrock_model if bedrock_model
|
|
365
|
+
aliases[base_name]['azure'] = latest_model if models['azure'].include?(latest_model)
|
|
360
366
|
end
|
|
361
367
|
|
|
362
368
|
models['bedrock'].each do |bedrock_model|
|
data/lib/tasks/vcr.rake
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'dotenv/load'
|
|
4
|
+
require 'time'
|
|
4
5
|
|
|
5
6
|
def record_all_cassettes(cassette_dir)
|
|
6
7
|
FileUtils.rm_rf(cassette_dir)
|
|
@@ -73,6 +74,31 @@ def run_tests
|
|
|
73
74
|
system('bundle exec rspec') || abort('Tests failed')
|
|
74
75
|
end
|
|
75
76
|
|
|
77
|
+
def retimestamp_cassettes(cassette_dir)
|
|
78
|
+
timestamp = Time.now.utc.httpdate
|
|
79
|
+
updated_files = 0
|
|
80
|
+
updated_entries = 0
|
|
81
|
+
|
|
82
|
+
Dir.glob("#{cassette_dir}/**/*.yml").each do |file|
|
|
83
|
+
content = File.read(file)
|
|
84
|
+
replacements = 0
|
|
85
|
+
|
|
86
|
+
updated = content.gsub(/^(\s*recorded_at:\s*).+$/) do
|
|
87
|
+
replacements += 1
|
|
88
|
+
"#{Regexp.last_match(1)}#{timestamp}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
next if replacements.zero?
|
|
92
|
+
|
|
93
|
+
File.write(file, updated)
|
|
94
|
+
updated_files += 1
|
|
95
|
+
updated_entries += replacements
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
puts "Updated #{updated_entries} recorded_at entries in #{updated_files} cassette files."
|
|
99
|
+
puts "New recorded_at value: #{timestamp}"
|
|
100
|
+
end
|
|
101
|
+
|
|
76
102
|
namespace :vcr do
|
|
77
103
|
desc 'Record VCR cassettes (rake vcr:record[all] or vcr:record[openai,anthropic])'
|
|
78
104
|
task :record, :providers do |_, args|
|
|
@@ -89,4 +115,10 @@ namespace :vcr do
|
|
|
89
115
|
record_for_providers(providers, cassette_dir)
|
|
90
116
|
end
|
|
91
117
|
end
|
|
118
|
+
|
|
119
|
+
desc 'Update recorded_at timestamps for all cassette entries'
|
|
120
|
+
task :retimestamp do
|
|
121
|
+
cassette_dir = 'spec/fixtures/vcr_cassettes'
|
|
122
|
+
retimestamp_cassettes(cassette_dir)
|
|
123
|
+
end
|
|
92
124
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_llm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Carmine Paolino
|
|
@@ -43,28 +43,28 @@ dependencies:
|
|
|
43
43
|
requirements:
|
|
44
44
|
- - ">="
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version:
|
|
46
|
+
version: '2.0'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version:
|
|
53
|
+
version: '2.0'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: faraday-retry
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
57
57
|
requirements:
|
|
58
58
|
- - ">="
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '
|
|
60
|
+
version: '2.0'
|
|
61
61
|
type: :runtime
|
|
62
62
|
prerelease: false
|
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '
|
|
67
|
+
version: '2.0'
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
69
|
name: faraday-multipart
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -192,6 +192,7 @@ files:
|
|
|
192
192
|
- lib/ruby_llm/active_record/chat_methods.rb
|
|
193
193
|
- lib/ruby_llm/active_record/message_methods.rb
|
|
194
194
|
- lib/ruby_llm/active_record/model_methods.rb
|
|
195
|
+
- lib/ruby_llm/agent.rb
|
|
195
196
|
- lib/ruby_llm/aliases.json
|
|
196
197
|
- lib/ruby_llm/aliases.rb
|
|
197
198
|
- lib/ruby_llm/attachment.rb
|
|
@@ -226,18 +227,17 @@ files:
|
|
|
226
227
|
- lib/ruby_llm/providers/anthropic/models.rb
|
|
227
228
|
- lib/ruby_llm/providers/anthropic/streaming.rb
|
|
228
229
|
- lib/ruby_llm/providers/anthropic/tools.rb
|
|
230
|
+
- lib/ruby_llm/providers/azure.rb
|
|
231
|
+
- lib/ruby_llm/providers/azure/chat.rb
|
|
232
|
+
- lib/ruby_llm/providers/azure/embeddings.rb
|
|
233
|
+
- lib/ruby_llm/providers/azure/media.rb
|
|
234
|
+
- lib/ruby_llm/providers/azure/models.rb
|
|
229
235
|
- lib/ruby_llm/providers/bedrock.rb
|
|
230
|
-
- lib/ruby_llm/providers/bedrock/
|
|
236
|
+
- lib/ruby_llm/providers/bedrock/auth.rb
|
|
231
237
|
- lib/ruby_llm/providers/bedrock/chat.rb
|
|
232
238
|
- lib/ruby_llm/providers/bedrock/media.rb
|
|
233
239
|
- lib/ruby_llm/providers/bedrock/models.rb
|
|
234
|
-
- lib/ruby_llm/providers/bedrock/signing.rb
|
|
235
240
|
- lib/ruby_llm/providers/bedrock/streaming.rb
|
|
236
|
-
- lib/ruby_llm/providers/bedrock/streaming/base.rb
|
|
237
|
-
- lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb
|
|
238
|
-
- lib/ruby_llm/providers/bedrock/streaming/message_processing.rb
|
|
239
|
-
- lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb
|
|
240
|
-
- lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb
|
|
241
241
|
- lib/ruby_llm/providers/deepseek.rb
|
|
242
242
|
- lib/ruby_llm/providers/deepseek/capabilities.rb
|
|
243
243
|
- lib/ruby_llm/providers/deepseek/chat.rb
|
|
@@ -290,6 +290,9 @@ files:
|
|
|
290
290
|
- lib/ruby_llm/providers/vertexai/models.rb
|
|
291
291
|
- lib/ruby_llm/providers/vertexai/streaming.rb
|
|
292
292
|
- lib/ruby_llm/providers/vertexai/transcription.rb
|
|
293
|
+
- lib/ruby_llm/providers/xai.rb
|
|
294
|
+
- lib/ruby_llm/providers/xai/chat.rb
|
|
295
|
+
- lib/ruby_llm/providers/xai/models.rb
|
|
293
296
|
- lib/ruby_llm/railtie.rb
|
|
294
297
|
- lib/ruby_llm/stream_accumulator.rb
|
|
295
298
|
- lib/ruby_llm/streaming.rb
|
|
@@ -332,7 +335,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
332
335
|
- !ruby/object:Gem::Version
|
|
333
336
|
version: '0'
|
|
334
337
|
requirements: []
|
|
335
|
-
rubygems_version:
|
|
338
|
+
rubygems_version: 4.0.3
|
|
336
339
|
specification_version: 4
|
|
337
340
|
summary: One beautiful Ruby API for GPT, Claude, Gemini, and more.
|
|
338
341
|
test_files: []
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
module Providers
|
|
5
|
-
class Bedrock
|
|
6
|
-
# Determines capabilities and pricing for AWS Bedrock models
|
|
7
|
-
module Capabilities
|
|
8
|
-
module_function
|
|
9
|
-
|
|
10
|
-
def context_window_for(model_id)
|
|
11
|
-
case model_id
|
|
12
|
-
when /anthropic\.claude-2/ then 100_000
|
|
13
|
-
else 200_000
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def max_tokens_for(_model_id)
|
|
18
|
-
4_096
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def input_price_for(model_id)
|
|
22
|
-
PRICES.dig(model_family(model_id), :input) || default_input_price
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def output_price_for(model_id)
|
|
26
|
-
PRICES.dig(model_family(model_id), :output) || default_output_price
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def supports_chat?(model_id)
|
|
30
|
-
model_id.match?(/anthropic\.claude/)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def supports_streaming?(model_id)
|
|
34
|
-
model_id.match?(/anthropic\.claude/)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def supports_images?(model_id)
|
|
38
|
-
model_id.match?(/anthropic\.claude/)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def supports_vision?(model_id)
|
|
42
|
-
model_id.match?(/anthropic\.claude/)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def supports_functions?(model_id)
|
|
46
|
-
model_id.match?(/anthropic\.claude/)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def supports_audio?(_model_id)
|
|
50
|
-
false
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def supports_json_mode?(model_id)
|
|
54
|
-
model_id.match?(/anthropic\.claude/)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def format_display_name(model_id)
|
|
58
|
-
model_id.then { |id| humanize(id) }
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def model_type(_model_id)
|
|
62
|
-
'chat'
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def supports_structured_output?(_model_id)
|
|
66
|
-
false
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Model family patterns for capability lookup
|
|
70
|
-
MODEL_FAMILIES = {
|
|
71
|
-
/anthropic\.claude-3-opus/ => :claude3_opus,
|
|
72
|
-
/anthropic\.claude-3-sonnet/ => :claude3_sonnet,
|
|
73
|
-
/anthropic\.claude-3-5-sonnet/ => :claude3_sonnet,
|
|
74
|
-
/anthropic\.claude-3-7-sonnet/ => :claude3_sonnet,
|
|
75
|
-
/anthropic\.claude-3-haiku/ => :claude3_haiku,
|
|
76
|
-
/anthropic\.claude-3-5-haiku/ => :claude3_5_haiku,
|
|
77
|
-
/anthropic\.claude-v2/ => :claude2,
|
|
78
|
-
/anthropic\.claude-instant/ => :claude_instant
|
|
79
|
-
}.freeze
|
|
80
|
-
|
|
81
|
-
def model_family(model_id)
|
|
82
|
-
MODEL_FAMILIES.find { |pattern, _family| model_id.match?(pattern) }&.last || :other
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Pricing information for Bedrock models (per million tokens)
|
|
86
|
-
PRICES = {
|
|
87
|
-
claude3_opus: { input: 15.0, output: 75.0 },
|
|
88
|
-
claude3_sonnet: { input: 3.0, output: 15.0 },
|
|
89
|
-
claude3_haiku: { input: 0.25, output: 1.25 },
|
|
90
|
-
claude3_5_haiku: { input: 0.8, output: 4.0 },
|
|
91
|
-
claude2: { input: 8.0, output: 24.0 },
|
|
92
|
-
claude_instant: { input: 0.8, output: 2.4 }
|
|
93
|
-
}.freeze
|
|
94
|
-
|
|
95
|
-
def default_input_price
|
|
96
|
-
0.1
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def default_output_price
|
|
100
|
-
0.2
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def humanize(id)
|
|
104
|
-
id.tr('-', ' ')
|
|
105
|
-
.split('.')
|
|
106
|
-
.last
|
|
107
|
-
.split
|
|
108
|
-
.map(&:capitalize)
|
|
109
|
-
.join(' ')
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def modalities_for(model_id)
|
|
113
|
-
modalities = {
|
|
114
|
-
input: ['text'],
|
|
115
|
-
output: ['text']
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if model_id.match?(/anthropic\.claude/) && supports_vision?(model_id)
|
|
119
|
-
modalities[:input] << 'image'
|
|
120
|
-
modalities[:input] << 'pdf'
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
modalities
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def capabilities_for(model_id)
|
|
127
|
-
capabilities = []
|
|
128
|
-
|
|
129
|
-
capabilities << 'streaming' if model_id.match?(/anthropic\.claude/)
|
|
130
|
-
|
|
131
|
-
capabilities << 'function_calling' if supports_functions?(model_id)
|
|
132
|
-
|
|
133
|
-
capabilities << 'reasoning' if model_id.match?(/claude-3-7/)
|
|
134
|
-
|
|
135
|
-
if model_id.match?(/claude-3\.5|claude-3-7/)
|
|
136
|
-
capabilities << 'batch'
|
|
137
|
-
capabilities << 'citations'
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
capabilities
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def pricing_for(model_id)
|
|
144
|
-
family = model_family(model_id)
|
|
145
|
-
prices = PRICES.fetch(family, { input: default_input_price, output: default_output_price })
|
|
146
|
-
|
|
147
|
-
standard_pricing = {
|
|
148
|
-
input_per_million: prices[:input],
|
|
149
|
-
output_per_million: prices[:output]
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
batch_pricing = {
|
|
153
|
-
input_per_million: prices[:input] * 0.5,
|
|
154
|
-
output_per_million: prices[:output] * 0.5
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
{
|
|
158
|
-
text_tokens: {
|
|
159
|
-
standard: standard_pricing,
|
|
160
|
-
batch: batch_pricing
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
end
|