dify_llm 1.9.2 → 1.14.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/README.md +27 -8
- data/lib/generators/ruby_llm/agent/agent_generator.rb +36 -0
- data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +6 -0
- data/lib/generators/ruby_llm/agent/templates/instructions.txt.erb.tt +0 -0
- data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +110 -41
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +14 -15
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +8 -11
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/helpers/messages_helper.rb.tt +25 -0
- data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +16 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +31 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +31 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/new.html.erb.tt +9 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +27 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_content.html.erb.tt +1 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +23 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +10 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +2 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +4 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +21 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +17 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +40 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +27 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +19 -7
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +5 -3
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +9 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_content.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_error.html.erb.tt +8 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +6 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +2 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +4 -7
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +9 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +5 -7
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +8 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +16 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +11 -12
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +27 -17
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +3 -4
- data/lib/generators/ruby_llm/generator_helpers.rb +37 -17
- data/lib/generators/ruby_llm/install/install_generator.rb +22 -18
- data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +1 -1
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +4 -1
- data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +4 -10
- data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +2 -1
- data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +2 -2
- data/lib/generators/ruby_llm/schema/schema_generator.rb +26 -0
- data/lib/generators/ruby_llm/schema/templates/schema.rb.tt +2 -0
- data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +9 -0
- data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/tool/tool_generator.rb +96 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +19 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +50 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +7 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +49 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +2 -4
- data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +1 -1
- data/lib/ruby_llm/active_record/acts_as.rb +10 -4
- data/lib/ruby_llm/active_record/acts_as_legacy.rb +132 -27
- data/lib/ruby_llm/active_record/chat_methods.rb +132 -28
- data/lib/ruby_llm/active_record/message_methods.rb +58 -8
- data/lib/ruby_llm/active_record/model_methods.rb +1 -1
- data/lib/ruby_llm/active_record/payload_helpers.rb +26 -0
- data/lib/ruby_llm/active_record/tool_call_methods.rb +15 -0
- data/lib/ruby_llm/agent.rb +365 -0
- data/lib/ruby_llm/aliases.json +106 -61
- data/lib/ruby_llm/attachment.rb +8 -3
- data/lib/ruby_llm/chat.rb +150 -22
- data/lib/ruby_llm/configuration.rb +65 -65
- data/lib/ruby_llm/connection.rb +11 -7
- data/lib/ruby_llm/content.rb +6 -2
- data/lib/ruby_llm/error.rb +37 -1
- data/lib/ruby_llm/message.rb +43 -15
- data/lib/ruby_llm/model/info.rb +15 -13
- data/lib/ruby_llm/models.json +25039 -12260
- data/lib/ruby_llm/models.rb +185 -24
- data/lib/ruby_llm/provider.rb +26 -4
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +5 -119
- data/lib/ruby_llm/providers/anthropic/chat.rb +149 -17
- data/lib/ruby_llm/providers/anthropic/media.rb +2 -2
- data/lib/ruby_llm/providers/anthropic/models.rb +3 -9
- data/lib/ruby_llm/providers/anthropic/streaming.rb +25 -1
- data/lib/ruby_llm/providers/anthropic/tools.rb +20 -0
- data/lib/ruby_llm/providers/anthropic.rb +5 -1
- 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 +148 -0
- data/lib/ruby_llm/providers/bedrock/auth.rb +122 -0
- data/lib/ruby_llm/providers/bedrock/chat.rb +357 -28
- data/lib/ruby_llm/providers/bedrock/media.rb +62 -33
- data/lib/ruby_llm/providers/bedrock/models.rb +104 -65
- data/lib/ruby_llm/providers/bedrock/streaming.rb +309 -8
- data/lib/ruby_llm/providers/bedrock.rb +69 -52
- data/lib/ruby_llm/providers/deepseek/capabilities.rb +4 -114
- data/lib/ruby_llm/providers/deepseek.rb +5 -1
- data/lib/ruby_llm/providers/dify/chat.rb +82 -7
- data/lib/ruby_llm/providers/dify/media.rb +2 -2
- data/lib/ruby_llm/providers/dify/streaming.rb +26 -4
- data/lib/ruby_llm/providers/dify.rb +4 -0
- data/lib/ruby_llm/providers/gemini/capabilities.rb +45 -207
- data/lib/ruby_llm/providers/gemini/chat.rb +88 -6
- data/lib/ruby_llm/providers/gemini/images.rb +1 -1
- data/lib/ruby_llm/providers/gemini/models.rb +2 -4
- data/lib/ruby_llm/providers/gemini/streaming.rb +34 -2
- data/lib/ruby_llm/providers/gemini/tools.rb +35 -3
- data/lib/ruby_llm/providers/gemini.rb +4 -0
- data/lib/ruby_llm/providers/gpustack/capabilities.rb +20 -0
- data/lib/ruby_llm/providers/gpustack/chat.rb +1 -1
- data/lib/ruby_llm/providers/gpustack.rb +8 -0
- data/lib/ruby_llm/providers/mistral/capabilities.rb +8 -0
- data/lib/ruby_llm/providers/mistral/chat.rb +59 -1
- data/lib/ruby_llm/providers/mistral.rb +4 -0
- data/lib/ruby_llm/providers/ollama/capabilities.rb +20 -0
- data/lib/ruby_llm/providers/ollama/chat.rb +1 -1
- data/lib/ruby_llm/providers/ollama.rb +11 -1
- data/lib/ruby_llm/providers/openai/capabilities.rb +96 -192
- data/lib/ruby_llm/providers/openai/chat.rb +101 -7
- data/lib/ruby_llm/providers/openai/media.rb +5 -2
- data/lib/ruby_llm/providers/openai/models.rb +2 -4
- data/lib/ruby_llm/providers/openai/streaming.rb +11 -3
- data/lib/ruby_llm/providers/openai/temperature.rb +28 -0
- data/lib/ruby_llm/providers/openai/tools.rb +27 -2
- data/lib/ruby_llm/providers/openai.rb +11 -1
- data/lib/ruby_llm/providers/openrouter/chat.rb +168 -0
- data/lib/ruby_llm/providers/openrouter/images.rb +69 -0
- data/lib/ruby_llm/providers/openrouter/streaming.rb +74 -0
- data/lib/ruby_llm/providers/openrouter.rb +37 -1
- data/lib/ruby_llm/providers/perplexity/capabilities.rb +34 -99
- data/lib/ruby_llm/providers/perplexity/models.rb +12 -14
- data/lib/ruby_llm/providers/perplexity.rb +4 -0
- data/lib/ruby_llm/providers/vertexai/models.rb +1 -1
- data/lib/ruby_llm/providers/vertexai.rb +23 -7
- 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 +32 -0
- data/lib/ruby_llm/stream_accumulator.rb +120 -18
- data/lib/ruby_llm/streaming.rb +60 -57
- data/lib/ruby_llm/thinking.rb +49 -0
- data/lib/ruby_llm/tokens.rb +47 -0
- data/lib/ruby_llm/tool.rb +48 -3
- data/lib/ruby_llm/tool_call.rb +6 -3
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm.rb +14 -8
- data/lib/tasks/models.rake +61 -22
- data/lib/tasks/release.rake +1 -1
- data/lib/tasks/ruby_llm.rake +9 -1
- data/lib/tasks/vcr.rake +33 -1
- metadata +67 -16
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +0 -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 -71
- data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +0 -67
- data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +0 -80
- data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +0 -78
data/lib/ruby_llm/models.rb
CHANGED
|
@@ -16,6 +16,21 @@ module RubyLLM
|
|
|
16
16
|
'openrouter' => 'openrouter',
|
|
17
17
|
'perplexity' => 'perplexity'
|
|
18
18
|
}.freeze
|
|
19
|
+
PROVIDER_PREFERENCE = %w[
|
|
20
|
+
openai
|
|
21
|
+
anthropic
|
|
22
|
+
gemini
|
|
23
|
+
vertexai
|
|
24
|
+
bedrock
|
|
25
|
+
openrouter
|
|
26
|
+
deepseek
|
|
27
|
+
mistral
|
|
28
|
+
perplexity
|
|
29
|
+
xai
|
|
30
|
+
azure
|
|
31
|
+
ollama
|
|
32
|
+
gpustack
|
|
33
|
+
].freeze
|
|
19
34
|
|
|
20
35
|
class << self
|
|
21
36
|
def instance
|
|
@@ -32,30 +47,60 @@ module RubyLLM
|
|
|
32
47
|
|
|
33
48
|
def read_from_json(file = RubyLLM.config.model_registry_file)
|
|
34
49
|
data = File.exist?(file) ? File.read(file) : '[]'
|
|
35
|
-
JSON.parse(data, symbolize_names: true).map { |model| Model::Info.new(model) }
|
|
50
|
+
models = JSON.parse(data, symbolize_names: true).map { |model| Model::Info.new(model) }
|
|
51
|
+
filter_models(models)
|
|
36
52
|
rescue JSON::ParserError
|
|
37
53
|
[]
|
|
38
54
|
end
|
|
39
55
|
|
|
40
56
|
def refresh!(remote_only: false)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
57
|
+
existing_models = load_existing_models
|
|
58
|
+
|
|
59
|
+
provider_fetch = fetch_provider_models(remote_only: remote_only)
|
|
60
|
+
log_provider_fetch(provider_fetch)
|
|
61
|
+
|
|
62
|
+
models_dev_fetch = fetch_models_dev_models(existing_models)
|
|
63
|
+
log_models_dev_fetch(models_dev_fetch)
|
|
64
|
+
|
|
65
|
+
merged_models = merge_with_existing(existing_models, provider_fetch, models_dev_fetch)
|
|
44
66
|
@instance = new(merged_models)
|
|
45
67
|
end
|
|
46
68
|
|
|
47
|
-
def
|
|
69
|
+
def fetch_provider_models(remote_only: true) # rubocop:disable Metrics/PerceivedComplexity
|
|
48
70
|
config = RubyLLM.config
|
|
71
|
+
provider_classes = remote_only ? Provider.remote_providers.values : Provider.providers.values
|
|
49
72
|
configured_classes = if remote_only
|
|
50
73
|
Provider.configured_remote_providers(config)
|
|
51
74
|
else
|
|
52
75
|
Provider.configured_providers(config)
|
|
53
76
|
end
|
|
54
|
-
configured = configured_classes.
|
|
77
|
+
configured = configured_classes.select { |klass| provider_classes.include?(klass) }
|
|
78
|
+
result = {
|
|
79
|
+
models: [],
|
|
80
|
+
fetched_providers: [],
|
|
81
|
+
configured_names: configured.map(&:name),
|
|
82
|
+
failed: []
|
|
83
|
+
}
|
|
55
84
|
|
|
56
|
-
|
|
85
|
+
provider_classes.each do |provider_class|
|
|
86
|
+
next if remote_only && provider_class.local?
|
|
87
|
+
next unless provider_class.configured?(config)
|
|
57
88
|
|
|
58
|
-
|
|
89
|
+
begin
|
|
90
|
+
result[:models].concat(provider_class.new(config).list_models)
|
|
91
|
+
result[:fetched_providers] << provider_class.slug
|
|
92
|
+
rescue StandardError => e
|
|
93
|
+
result[:failed] << { name: provider_class.name, slug: provider_class.slug, error: e }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
result[:fetched_providers].uniq!
|
|
98
|
+
result
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Backwards-compatible wrapper used by specs.
|
|
102
|
+
def fetch_from_providers(remote_only: true)
|
|
103
|
+
fetch_provider_models(remote_only: remote_only)[:models]
|
|
59
104
|
end
|
|
60
105
|
|
|
61
106
|
def resolve(model_id, provider: nil, assume_exists: false, config: nil) # rubocop:disable Metrics/PerceivedComplexity
|
|
@@ -64,7 +109,7 @@ module RubyLLM
|
|
|
64
109
|
|
|
65
110
|
if provider_class
|
|
66
111
|
temp_instance = provider_class.new(config)
|
|
67
|
-
assume_exists = true if temp_instance.local?
|
|
112
|
+
assume_exists = true if temp_instance.local? || temp_instance.assume_models_exist?
|
|
68
113
|
end
|
|
69
114
|
|
|
70
115
|
if assume_exists
|
|
@@ -103,7 +148,7 @@ module RubyLLM
|
|
|
103
148
|
instance.respond_to?(method, include_private) || super
|
|
104
149
|
end
|
|
105
150
|
|
|
106
|
-
def
|
|
151
|
+
def fetch_models_dev_models(existing_models) # rubocop:disable Metrics/PerceivedComplexity
|
|
107
152
|
RubyLLM.logger.info 'Fetching models from models.dev API...'
|
|
108
153
|
|
|
109
154
|
connection = Connection.basic do |f|
|
|
@@ -121,7 +166,52 @@ module RubyLLM
|
|
|
121
166
|
Model::Info.new(models_dev_model_to_info(model_data, provider_slug, provider_key.to_s))
|
|
122
167
|
end
|
|
123
168
|
end
|
|
124
|
-
models.reject { |model| model.provider.nil? || model.id.nil? }
|
|
169
|
+
{ models: models.reject { |model| model.provider.nil? || model.id.nil? }, fetched: true }
|
|
170
|
+
rescue StandardError => e
|
|
171
|
+
RubyLLM.logger.warn("Failed to fetch models.dev (#{e.class}: #{e.message}). Keeping existing.")
|
|
172
|
+
{
|
|
173
|
+
models: existing_models.select { |model| model.metadata[:source] == 'models.dev' },
|
|
174
|
+
fetched: false
|
|
175
|
+
}
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def load_existing_models
|
|
179
|
+
existing_models = instance&.all
|
|
180
|
+
existing_models = read_from_json if existing_models.nil? || existing_models.empty?
|
|
181
|
+
existing_models
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def log_provider_fetch(provider_fetch)
|
|
185
|
+
RubyLLM.logger.info "Fetching models from providers: #{provider_fetch[:configured_names].join(', ')}"
|
|
186
|
+
provider_fetch[:failed].each do |failure|
|
|
187
|
+
RubyLLM.logger.warn(
|
|
188
|
+
"Failed to fetch #{failure[:name]} models (#{failure[:error].class}: #{failure[:error].message}). " \
|
|
189
|
+
'Keeping existing.'
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def log_models_dev_fetch(models_dev_fetch)
|
|
195
|
+
return if models_dev_fetch[:fetched]
|
|
196
|
+
|
|
197
|
+
RubyLLM.logger.warn('Using cached models.dev data due to fetch failure.')
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def merge_with_existing(existing_models, provider_fetch, models_dev_fetch)
|
|
201
|
+
existing_by_provider = existing_models.group_by(&:provider)
|
|
202
|
+
preserved_models = existing_by_provider
|
|
203
|
+
.except(*provider_fetch[:fetched_providers])
|
|
204
|
+
.values
|
|
205
|
+
.flatten
|
|
206
|
+
|
|
207
|
+
provider_models = provider_fetch[:models] + preserved_models
|
|
208
|
+
models_dev_models = if models_dev_fetch[:fetched]
|
|
209
|
+
models_dev_fetch[:models]
|
|
210
|
+
else
|
|
211
|
+
existing_models.select { |model| model.metadata[:source] == 'models.dev' }
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
merge_models(provider_models, models_dev_models)
|
|
125
215
|
end
|
|
126
216
|
|
|
127
217
|
def merge_models(provider_models, models_dev_models)
|
|
@@ -143,15 +233,36 @@ module RubyLLM
|
|
|
143
233
|
end
|
|
144
234
|
end
|
|
145
235
|
|
|
146
|
-
models.sort_by { |m| [m.provider, m.id] }
|
|
236
|
+
filter_models(models).sort_by { |m| [m.provider, m.id] }
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def filter_models(models)
|
|
240
|
+
models.reject do |model|
|
|
241
|
+
model.provider.to_s == 'vertexai' && model.id.to_s.include?('/')
|
|
242
|
+
end
|
|
147
243
|
end
|
|
148
244
|
|
|
149
245
|
def find_models_dev_model(key, models_dev_by_key)
|
|
150
246
|
# Direct match
|
|
151
247
|
return models_dev_by_key[key] if models_dev_by_key[key]
|
|
152
248
|
|
|
153
|
-
# VertexAI uses same models as Gemini
|
|
154
249
|
provider, model_id = key.split(':', 2)
|
|
250
|
+
if provider == 'bedrock'
|
|
251
|
+
normalized_id = model_id.sub(/^[a-z]{2}\./, '')
|
|
252
|
+
context_override = nil
|
|
253
|
+
normalized_id = normalized_id.gsub(/:(\d+)k\b/) do
|
|
254
|
+
context_override = Regexp.last_match(1).to_i * 1000
|
|
255
|
+
''
|
|
256
|
+
end
|
|
257
|
+
bedrock_model = models_dev_by_key["bedrock:#{normalized_id}"]
|
|
258
|
+
if bedrock_model
|
|
259
|
+
data = bedrock_model.to_h.merge(id: model_id)
|
|
260
|
+
data[:context_window] = context_override if context_override
|
|
261
|
+
return Model::Info.new(data)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# VertexAI uses same models as Gemini
|
|
155
266
|
return unless provider == 'vertexai'
|
|
156
267
|
|
|
157
268
|
gemini_model = models_dev_by_key["gemini:#{model_id}"]
|
|
@@ -162,28 +273,61 @@ module RubyLLM
|
|
|
162
273
|
end
|
|
163
274
|
|
|
164
275
|
def index_by_key(models)
|
|
165
|
-
models.
|
|
166
|
-
|
|
276
|
+
models.to_h do |model|
|
|
277
|
+
["#{model.provider}:#{model.id}", model]
|
|
167
278
|
end
|
|
168
279
|
end
|
|
169
280
|
|
|
170
|
-
def add_provider_metadata(models_dev_model, provider_model)
|
|
281
|
+
def add_provider_metadata(models_dev_model, provider_model) # rubocop:disable Metrics/PerceivedComplexity
|
|
171
282
|
data = models_dev_model.to_h
|
|
283
|
+
data[:name] = provider_model.name if blank_value?(data[:name])
|
|
284
|
+
data[:family] = provider_model.family if blank_value?(data[:family])
|
|
285
|
+
data[:created_at] = provider_model.created_at if blank_value?(data[:created_at])
|
|
286
|
+
data[:context_window] = provider_model.context_window if blank_value?(data[:context_window])
|
|
287
|
+
data[:max_output_tokens] = provider_model.max_output_tokens if blank_value?(data[:max_output_tokens])
|
|
288
|
+
data[:modalities] = provider_model.modalities.to_h if blank_value?(data[:modalities])
|
|
289
|
+
data[:pricing] = provider_model.pricing.to_h if blank_value?(data[:pricing])
|
|
172
290
|
data[:metadata] = provider_model.metadata.merge(data[:metadata] || {})
|
|
173
291
|
data[:capabilities] = (models_dev_model.capabilities + provider_model.capabilities).uniq
|
|
292
|
+
normalize_embedding_modalities(data)
|
|
174
293
|
Model::Info.new(data)
|
|
175
294
|
end
|
|
176
295
|
|
|
296
|
+
def normalize_embedding_modalities(data)
|
|
297
|
+
return unless data[:id].to_s.include?('embedding')
|
|
298
|
+
|
|
299
|
+
modalities = data[:modalities].to_h
|
|
300
|
+
modalities[:input] = ['text'] if modalities[:input].nil? || modalities[:input].empty?
|
|
301
|
+
modalities[:output] = ['embeddings']
|
|
302
|
+
data[:modalities] = modalities
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def blank_value?(value)
|
|
306
|
+
return true if value.nil?
|
|
307
|
+
return value.empty? if value.is_a?(String) || value.is_a?(Array)
|
|
308
|
+
|
|
309
|
+
if value.is_a?(Hash)
|
|
310
|
+
return true if value.empty?
|
|
311
|
+
|
|
312
|
+
return value.values.all? { |nested| blank_value?(nested) }
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
false
|
|
316
|
+
end
|
|
317
|
+
|
|
177
318
|
def models_dev_model_to_info(model_data, provider_slug, provider_key)
|
|
178
319
|
modalities = normalize_models_dev_modalities(model_data[:modalities])
|
|
179
320
|
capabilities = models_dev_capabilities(model_data, modalities)
|
|
180
321
|
|
|
181
|
-
|
|
322
|
+
created_date = [model_data[:release_date], model_data[:last_updated]]
|
|
323
|
+
.find { |value| !value.to_s.strip.empty? }
|
|
324
|
+
|
|
325
|
+
data = {
|
|
182
326
|
id: model_data[:id],
|
|
183
327
|
name: model_data[:name] || model_data[:id],
|
|
184
328
|
provider: provider_slug,
|
|
185
329
|
family: model_data[:family],
|
|
186
|
-
created_at:
|
|
330
|
+
created_at: created_date ? "#{created_date} 00:00:00 UTC" : nil,
|
|
187
331
|
context_window: model_data.dig(:limit, :context),
|
|
188
332
|
max_output_tokens: model_data.dig(:limit, :output),
|
|
189
333
|
knowledge_cutoff: normalize_models_dev_knowledge(model_data[:knowledge]),
|
|
@@ -192,6 +336,9 @@ module RubyLLM
|
|
|
192
336
|
pricing: models_dev_pricing(model_data[:cost]),
|
|
193
337
|
metadata: models_dev_metadata(model_data, provider_key)
|
|
194
338
|
}
|
|
339
|
+
|
|
340
|
+
normalize_embedding_modalities(data)
|
|
341
|
+
data
|
|
195
342
|
end
|
|
196
343
|
|
|
197
344
|
def models_dev_capabilities(model_data, modalities)
|
|
@@ -261,7 +408,7 @@ module RubyLLM
|
|
|
261
408
|
end
|
|
262
409
|
|
|
263
410
|
def initialize(models = nil)
|
|
264
|
-
@models = models || self.class.load_models
|
|
411
|
+
@models = self.class.filter_models(models || self.class.load_models)
|
|
265
412
|
end
|
|
266
413
|
|
|
267
414
|
def load_from_json!(file = RubyLLM.config.model_registry_file)
|
|
@@ -325,8 +472,8 @@ module RubyLLM
|
|
|
325
472
|
def find_with_provider(model_id, provider)
|
|
326
473
|
resolved_id = Aliases.resolve(model_id, provider)
|
|
327
474
|
resolved_id = resolve_bedrock_region_id(resolved_id) if provider.to_s == 'bedrock'
|
|
328
|
-
all.find { |m| m.id ==
|
|
329
|
-
all.find { |m| m.id ==
|
|
475
|
+
all.find { |m| m.id == resolved_id && m.provider == provider.to_s } ||
|
|
476
|
+
all.find { |m| m.id == model_id && m.provider == provider.to_s } ||
|
|
330
477
|
raise(ModelNotFoundError, "Unknown model: #{model_id} for provider: #{provider}")
|
|
331
478
|
end
|
|
332
479
|
|
|
@@ -345,9 +492,23 @@ module RubyLLM
|
|
|
345
492
|
end
|
|
346
493
|
|
|
347
494
|
def find_without_provider(model_id)
|
|
348
|
-
all.
|
|
349
|
-
|
|
350
|
-
|
|
495
|
+
exact_matches = all.select { |m| m.id == model_id }
|
|
496
|
+
return preferred_match(exact_matches) if exact_matches.any?
|
|
497
|
+
|
|
498
|
+
resolved_id = Aliases.resolve(model_id)
|
|
499
|
+
alias_matches = all.select { |m| m.id == resolved_id }
|
|
500
|
+
return preferred_match(alias_matches) if alias_matches.any?
|
|
501
|
+
|
|
502
|
+
raise(ModelNotFoundError, "Unknown model: #{model_id}")
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def preferred_match(candidates)
|
|
506
|
+
return candidates.first if candidates.size == 1
|
|
507
|
+
|
|
508
|
+
candidates.min_by do |model|
|
|
509
|
+
index = PROVIDER_PREFERENCE.index(model.provider)
|
|
510
|
+
index || PROVIDER_PREFERENCE.length
|
|
511
|
+
end
|
|
351
512
|
end
|
|
352
513
|
end
|
|
353
514
|
end
|
data/lib/ruby_llm/provider.rb
CHANGED
|
@@ -37,17 +37,21 @@ module RubyLLM
|
|
|
37
37
|
self.class.configuration_requirements
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
# rubocop:disable Metrics/ParameterLists
|
|
41
|
+
def complete(messages, tools:, temperature:, model:, params: {}, headers: {}, schema: nil, thinking: nil,
|
|
42
|
+
tool_prefs: nil, &)
|
|
41
43
|
normalized_temperature = maybe_normalize_temperature(temperature, model)
|
|
42
44
|
|
|
43
45
|
payload = Utils.deep_merge(
|
|
44
46
|
render_payload(
|
|
45
47
|
messages,
|
|
46
48
|
tools: tools,
|
|
49
|
+
tool_prefs: tool_prefs,
|
|
47
50
|
temperature: normalized_temperature,
|
|
48
51
|
model: model,
|
|
49
52
|
stream: block_given?,
|
|
50
|
-
schema: schema
|
|
53
|
+
schema: schema,
|
|
54
|
+
thinking: thinking
|
|
51
55
|
),
|
|
52
56
|
params
|
|
53
57
|
)
|
|
@@ -58,6 +62,7 @@ module RubyLLM
|
|
|
58
62
|
sync_response @connection, payload, headers
|
|
59
63
|
end
|
|
60
64
|
end
|
|
65
|
+
# rubocop:enable Metrics/ParameterLists
|
|
61
66
|
|
|
62
67
|
def list_models
|
|
63
68
|
response = @connection.get models_url
|
|
@@ -101,16 +106,24 @@ module RubyLLM
|
|
|
101
106
|
self.class.remote?
|
|
102
107
|
end
|
|
103
108
|
|
|
109
|
+
def assume_models_exist?
|
|
110
|
+
self.class.assume_models_exist?
|
|
111
|
+
end
|
|
112
|
+
|
|
104
113
|
def parse_error(response)
|
|
105
114
|
return if response.body.empty?
|
|
106
115
|
|
|
107
116
|
body = try_parse_json(response.body)
|
|
108
117
|
case body
|
|
109
118
|
when Hash
|
|
119
|
+
error = body['error']
|
|
120
|
+
return error if error.is_a?(String)
|
|
121
|
+
|
|
110
122
|
body.dig('error', 'message')
|
|
111
123
|
when Array
|
|
112
124
|
body.map do |part|
|
|
113
|
-
part
|
|
125
|
+
error = part['error']
|
|
126
|
+
error.is_a?(String) ? error : part.dig('error', 'message')
|
|
114
127
|
end.join('. ')
|
|
115
128
|
else
|
|
116
129
|
body
|
|
@@ -144,13 +157,17 @@ module RubyLLM
|
|
|
144
157
|
end
|
|
145
158
|
|
|
146
159
|
def capabilities
|
|
147
|
-
|
|
160
|
+
nil
|
|
148
161
|
end
|
|
149
162
|
|
|
150
163
|
def configuration_requirements
|
|
151
164
|
[]
|
|
152
165
|
end
|
|
153
166
|
|
|
167
|
+
def configuration_options
|
|
168
|
+
[]
|
|
169
|
+
end
|
|
170
|
+
|
|
154
171
|
def local?
|
|
155
172
|
false
|
|
156
173
|
end
|
|
@@ -159,12 +176,17 @@ module RubyLLM
|
|
|
159
176
|
!local?
|
|
160
177
|
end
|
|
161
178
|
|
|
179
|
+
def assume_models_exist?
|
|
180
|
+
false
|
|
181
|
+
end
|
|
182
|
+
|
|
162
183
|
def configured?(config)
|
|
163
184
|
configuration_requirements.all? { |req| config.send(req) }
|
|
164
185
|
end
|
|
165
186
|
|
|
166
187
|
def register(name, provider_class)
|
|
167
188
|
providers[name.to_sym] = provider_class
|
|
189
|
+
RubyLLM::Configuration.register_provider_options(provider_class.configuration_options)
|
|
168
190
|
end
|
|
169
191
|
|
|
170
192
|
def resolve(name)
|
|
@@ -3,130 +3,16 @@
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module Providers
|
|
5
5
|
class Anthropic
|
|
6
|
-
#
|
|
6
|
+
# Provider-level capability checks used outside the model registry.
|
|
7
7
|
module Capabilities
|
|
8
8
|
module_function
|
|
9
9
|
|
|
10
|
-
def
|
|
11
|
-
|
|
10
|
+
def supports_tool_choice?(_model_id)
|
|
11
|
+
true
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def
|
|
15
|
-
|
|
16
|
-
when /claude-3-7-sonnet/, /claude-3-5/ then 8_192
|
|
17
|
-
else 4_096
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def get_input_price(model_id)
|
|
22
|
-
PRICES.dig(model_family(model_id), :input) || default_input_price
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def get_output_price(model_id)
|
|
26
|
-
PRICES.dig(model_family(model_id), :output) || default_output_price
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def supports_vision?(model_id)
|
|
30
|
-
!model_id.match?(/claude-[12]/)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def supports_functions?(model_id)
|
|
34
|
-
model_id.match?(/claude-3/)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def supports_json_mode?(model_id)
|
|
38
|
-
model_id.match?(/claude-3/)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def supports_extended_thinking?(model_id)
|
|
42
|
-
model_id.match?(/claude-3-7-sonnet/)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def model_family(model_id)
|
|
46
|
-
case model_id
|
|
47
|
-
when /claude-3-7-sonnet/ then 'claude-3-7-sonnet'
|
|
48
|
-
when /claude-3-5-sonnet/ then 'claude-3-5-sonnet'
|
|
49
|
-
when /claude-3-5-haiku/ then 'claude-3-5-haiku'
|
|
50
|
-
when /claude-3-opus/ then 'claude-3-opus'
|
|
51
|
-
when /claude-3-sonnet/ then 'claude-3-sonnet'
|
|
52
|
-
when /claude-3-haiku/ then 'claude-3-haiku'
|
|
53
|
-
else 'claude-2'
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def model_type(_)
|
|
58
|
-
'chat'
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
PRICES = {
|
|
62
|
-
'claude-3-7-sonnet': { input: 3.0, output: 15.0 },
|
|
63
|
-
'claude-3-5-sonnet': { input: 3.0, output: 15.0 },
|
|
64
|
-
'claude-3-5-haiku': { input: 0.80, output: 4.0 },
|
|
65
|
-
'claude-3-opus': { input: 15.0, output: 75.0 },
|
|
66
|
-
'claude-3-haiku': { input: 0.25, output: 1.25 },
|
|
67
|
-
'claude-2': { input: 3.0, output: 15.0 }
|
|
68
|
-
}.freeze
|
|
69
|
-
|
|
70
|
-
def default_input_price
|
|
71
|
-
3.0
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def default_output_price
|
|
75
|
-
15.0
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def modalities_for(model_id)
|
|
79
|
-
modalities = {
|
|
80
|
-
input: ['text'],
|
|
81
|
-
output: ['text']
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
unless model_id.match?(/claude-[12]/)
|
|
85
|
-
modalities[:input] << 'image'
|
|
86
|
-
modalities[:input] << 'pdf'
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
modalities
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def capabilities_for(model_id)
|
|
93
|
-
capabilities = ['streaming']
|
|
94
|
-
|
|
95
|
-
if model_id.match?(/claude-3/)
|
|
96
|
-
capabilities << 'function_calling'
|
|
97
|
-
capabilities << 'batch'
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
capabilities << 'reasoning' if model_id.match?(/claude-3-7|-4/)
|
|
101
|
-
capabilities << 'citations' if model_id.match?(/claude-3\.5|claude-3-7/)
|
|
102
|
-
capabilities
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def pricing_for(model_id)
|
|
106
|
-
family = model_family(model_id)
|
|
107
|
-
prices = PRICES.fetch(family.to_sym, { input: default_input_price, output: default_output_price })
|
|
108
|
-
|
|
109
|
-
standard_pricing = {
|
|
110
|
-
input_per_million: prices[:input],
|
|
111
|
-
output_per_million: prices[:output]
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
batch_pricing = {
|
|
115
|
-
input_per_million: prices[:input] * 0.5,
|
|
116
|
-
output_per_million: prices[:output] * 0.5
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if model_id.match?(/claude-3-7/)
|
|
120
|
-
standard_pricing[:reasoning_output_per_million] = prices[:output] * 2.5
|
|
121
|
-
batch_pricing[:reasoning_output_per_million] = prices[:output] * 1.25
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
{
|
|
125
|
-
text_tokens: {
|
|
126
|
-
standard: standard_pricing,
|
|
127
|
-
batch: batch_pricing
|
|
128
|
-
}
|
|
129
|
-
}
|
|
14
|
+
def supports_tool_parallel_control?(_model_id)
|
|
15
|
+
true
|
|
130
16
|
end
|
|
131
17
|
end
|
|
132
18
|
end
|