dify_llm 1.9.1 → 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/templates/migration.rb.tt +1 -1
- 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 +199 -62
- data/lib/ruby_llm/attachment.rb +15 -4
- 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 +37560 -14094
- data/lib/ruby_llm/models.rb +321 -38
- data/lib/ruby_llm/models_schema.json +2 -2
- 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 +107 -62
- 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 +82 -60
- data/lib/ruby_llm/thinking.rb +49 -0
- data/lib/ruby_llm/tokens.rb +47 -0
- data/lib/ruby_llm/tool.rb +49 -4
- 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 +62 -23
- 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
|
@@ -5,6 +5,33 @@ module RubyLLM
|
|
|
5
5
|
class Models
|
|
6
6
|
include Enumerable
|
|
7
7
|
|
|
8
|
+
MODELS_DEV_PROVIDER_MAP = {
|
|
9
|
+
'openai' => 'openai',
|
|
10
|
+
'anthropic' => 'anthropic',
|
|
11
|
+
'google' => 'gemini',
|
|
12
|
+
'google-vertex' => 'vertexai',
|
|
13
|
+
'amazon-bedrock' => 'bedrock',
|
|
14
|
+
'deepseek' => 'deepseek',
|
|
15
|
+
'mistral' => 'mistral',
|
|
16
|
+
'openrouter' => 'openrouter',
|
|
17
|
+
'perplexity' => 'perplexity'
|
|
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
|
|
34
|
+
|
|
8
35
|
class << self
|
|
9
36
|
def instance
|
|
10
37
|
@instance ||= new
|
|
@@ -20,30 +47,60 @@ module RubyLLM
|
|
|
20
47
|
|
|
21
48
|
def read_from_json(file = RubyLLM.config.model_registry_file)
|
|
22
49
|
data = File.exist?(file) ? File.read(file) : '[]'
|
|
23
|
-
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)
|
|
24
52
|
rescue JSON::ParserError
|
|
25
53
|
[]
|
|
26
54
|
end
|
|
27
55
|
|
|
28
56
|
def refresh!(remote_only: false)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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)
|
|
32
66
|
@instance = new(merged_models)
|
|
33
67
|
end
|
|
34
68
|
|
|
35
|
-
def
|
|
69
|
+
def fetch_provider_models(remote_only: true) # rubocop:disable Metrics/PerceivedComplexity
|
|
36
70
|
config = RubyLLM.config
|
|
71
|
+
provider_classes = remote_only ? Provider.remote_providers.values : Provider.providers.values
|
|
37
72
|
configured_classes = if remote_only
|
|
38
73
|
Provider.configured_remote_providers(config)
|
|
39
74
|
else
|
|
40
75
|
Provider.configured_providers(config)
|
|
41
76
|
end
|
|
42
|
-
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
|
+
}
|
|
84
|
+
|
|
85
|
+
provider_classes.each do |provider_class|
|
|
86
|
+
next if remote_only && provider_class.local?
|
|
87
|
+
next unless provider_class.configured?(config)
|
|
88
|
+
|
|
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
|
|
43
96
|
|
|
44
|
-
|
|
97
|
+
result[:fetched_providers].uniq!
|
|
98
|
+
result
|
|
99
|
+
end
|
|
45
100
|
|
|
46
|
-
|
|
101
|
+
# Backwards-compatible wrapper used by specs.
|
|
102
|
+
def fetch_from_providers(remote_only: true)
|
|
103
|
+
fetch_provider_models(remote_only: remote_only)[:models]
|
|
47
104
|
end
|
|
48
105
|
|
|
49
106
|
def resolve(model_id, provider: nil, assume_exists: false, config: nil) # rubocop:disable Metrics/PerceivedComplexity
|
|
@@ -52,7 +109,7 @@ module RubyLLM
|
|
|
52
109
|
|
|
53
110
|
if provider_class
|
|
54
111
|
temp_instance = provider_class.new(config)
|
|
55
|
-
assume_exists = true if temp_instance.local?
|
|
112
|
+
assume_exists = true if temp_instance.local? || temp_instance.assume_models_exist?
|
|
56
113
|
end
|
|
57
114
|
|
|
58
115
|
if assume_exists
|
|
@@ -91,70 +148,267 @@ module RubyLLM
|
|
|
91
148
|
instance.respond_to?(method, include_private) || super
|
|
92
149
|
end
|
|
93
150
|
|
|
94
|
-
def
|
|
95
|
-
RubyLLM.logger.info 'Fetching models from
|
|
151
|
+
def fetch_models_dev_models(existing_models) # rubocop:disable Metrics/PerceivedComplexity
|
|
152
|
+
RubyLLM.logger.info 'Fetching models from models.dev API...'
|
|
96
153
|
|
|
97
154
|
connection = Connection.basic do |f|
|
|
98
155
|
f.request :json
|
|
99
156
|
f.response :json, parser_options: { symbolize_names: true }
|
|
100
157
|
end
|
|
101
|
-
response = connection.get 'https://api.
|
|
102
|
-
|
|
103
|
-
|
|
158
|
+
response = connection.get 'https://models.dev/api.json'
|
|
159
|
+
providers = response.body || {}
|
|
160
|
+
|
|
161
|
+
models = providers.flat_map do |provider_key, provider_data|
|
|
162
|
+
provider_slug = MODELS_DEV_PROVIDER_MAP[provider_key.to_s]
|
|
163
|
+
next [] unless provider_slug
|
|
164
|
+
|
|
165
|
+
(provider_data[:models] || {}).values.map do |model_data|
|
|
166
|
+
Model::Info.new(models_dev_model_to_info(model_data, provider_slug, provider_key.to_s))
|
|
167
|
+
end
|
|
168
|
+
end
|
|
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
|
+
}
|
|
104
176
|
end
|
|
105
177
|
|
|
106
|
-
def
|
|
107
|
-
|
|
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)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def merge_models(provider_models, models_dev_models)
|
|
218
|
+
models_dev_by_key = index_by_key(models_dev_models)
|
|
108
219
|
provider_by_key = index_by_key(provider_models)
|
|
109
220
|
|
|
110
|
-
all_keys =
|
|
221
|
+
all_keys = models_dev_by_key.keys | provider_by_key.keys
|
|
111
222
|
|
|
112
223
|
models = all_keys.map do |key|
|
|
113
|
-
|
|
224
|
+
models_dev_model = find_models_dev_model(key, models_dev_by_key)
|
|
114
225
|
provider_model = provider_by_key[key]
|
|
115
226
|
|
|
116
|
-
if
|
|
117
|
-
add_provider_metadata(
|
|
118
|
-
elsif
|
|
119
|
-
|
|
227
|
+
if models_dev_model && provider_model
|
|
228
|
+
add_provider_metadata(models_dev_model, provider_model)
|
|
229
|
+
elsif models_dev_model
|
|
230
|
+
models_dev_model
|
|
120
231
|
else
|
|
121
232
|
provider_model
|
|
122
233
|
end
|
|
123
234
|
end
|
|
124
235
|
|
|
125
|
-
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
|
|
126
243
|
end
|
|
127
244
|
|
|
128
|
-
def
|
|
245
|
+
def find_models_dev_model(key, models_dev_by_key)
|
|
129
246
|
# Direct match
|
|
130
|
-
return
|
|
247
|
+
return models_dev_by_key[key] if models_dev_by_key[key]
|
|
131
248
|
|
|
132
|
-
# VertexAI uses same models as Gemini
|
|
133
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
|
|
134
266
|
return unless provider == 'vertexai'
|
|
135
267
|
|
|
136
|
-
gemini_model =
|
|
268
|
+
gemini_model = models_dev_by_key["gemini:#{model_id}"]
|
|
137
269
|
return unless gemini_model
|
|
138
270
|
|
|
139
|
-
# Return Gemini's
|
|
271
|
+
# Return Gemini's models.dev data but with VertexAI as provider
|
|
140
272
|
Model::Info.new(gemini_model.to_h.merge(provider: 'vertexai'))
|
|
141
273
|
end
|
|
142
274
|
|
|
143
275
|
def index_by_key(models)
|
|
144
|
-
models.
|
|
145
|
-
|
|
276
|
+
models.to_h do |model|
|
|
277
|
+
["#{model.provider}:#{model.id}", model]
|
|
146
278
|
end
|
|
147
279
|
end
|
|
148
280
|
|
|
149
|
-
def add_provider_metadata(
|
|
150
|
-
data =
|
|
281
|
+
def add_provider_metadata(models_dev_model, provider_model) # rubocop:disable Metrics/PerceivedComplexity
|
|
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])
|
|
151
290
|
data[:metadata] = provider_model.metadata.merge(data[:metadata] || {})
|
|
291
|
+
data[:capabilities] = (models_dev_model.capabilities + provider_model.capabilities).uniq
|
|
292
|
+
normalize_embedding_modalities(data)
|
|
152
293
|
Model::Info.new(data)
|
|
153
294
|
end
|
|
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
|
+
|
|
318
|
+
def models_dev_model_to_info(model_data, provider_slug, provider_key)
|
|
319
|
+
modalities = normalize_models_dev_modalities(model_data[:modalities])
|
|
320
|
+
capabilities = models_dev_capabilities(model_data, modalities)
|
|
321
|
+
|
|
322
|
+
created_date = [model_data[:release_date], model_data[:last_updated]]
|
|
323
|
+
.find { |value| !value.to_s.strip.empty? }
|
|
324
|
+
|
|
325
|
+
data = {
|
|
326
|
+
id: model_data[:id],
|
|
327
|
+
name: model_data[:name] || model_data[:id],
|
|
328
|
+
provider: provider_slug,
|
|
329
|
+
family: model_data[:family],
|
|
330
|
+
created_at: created_date ? "#{created_date} 00:00:00 UTC" : nil,
|
|
331
|
+
context_window: model_data.dig(:limit, :context),
|
|
332
|
+
max_output_tokens: model_data.dig(:limit, :output),
|
|
333
|
+
knowledge_cutoff: normalize_models_dev_knowledge(model_data[:knowledge]),
|
|
334
|
+
modalities: modalities,
|
|
335
|
+
capabilities: capabilities,
|
|
336
|
+
pricing: models_dev_pricing(model_data[:cost]),
|
|
337
|
+
metadata: models_dev_metadata(model_data, provider_key)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
normalize_embedding_modalities(data)
|
|
341
|
+
data
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def models_dev_capabilities(model_data, modalities)
|
|
345
|
+
capabilities = []
|
|
346
|
+
capabilities << 'function_calling' if model_data[:tool_call]
|
|
347
|
+
capabilities << 'structured_output' if model_data[:structured_output]
|
|
348
|
+
capabilities << 'reasoning' if model_data[:reasoning]
|
|
349
|
+
capabilities << 'vision' if modalities[:input].intersect?(%w[image video pdf])
|
|
350
|
+
capabilities.uniq
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def models_dev_pricing(cost)
|
|
354
|
+
return {} unless cost
|
|
355
|
+
|
|
356
|
+
text_standard = {
|
|
357
|
+
input_per_million: cost[:input],
|
|
358
|
+
output_per_million: cost[:output],
|
|
359
|
+
cached_input_per_million: cost[:cache_read],
|
|
360
|
+
reasoning_output_per_million: cost[:reasoning]
|
|
361
|
+
}.compact
|
|
362
|
+
|
|
363
|
+
audio_standard = {
|
|
364
|
+
input_per_million: cost[:input_audio],
|
|
365
|
+
output_per_million: cost[:output_audio]
|
|
366
|
+
}.compact
|
|
367
|
+
|
|
368
|
+
pricing = {}
|
|
369
|
+
pricing[:text_tokens] = { standard: text_standard } if text_standard.any?
|
|
370
|
+
pricing[:audio_tokens] = { standard: audio_standard } if audio_standard.any?
|
|
371
|
+
pricing
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def models_dev_metadata(model_data, provider_key)
|
|
375
|
+
metadata = {
|
|
376
|
+
source: 'models.dev',
|
|
377
|
+
provider_id: provider_key,
|
|
378
|
+
open_weights: model_data[:open_weights],
|
|
379
|
+
attachment: model_data[:attachment],
|
|
380
|
+
temperature: model_data[:temperature],
|
|
381
|
+
last_updated: model_data[:last_updated],
|
|
382
|
+
status: model_data[:status],
|
|
383
|
+
interleaved: model_data[:interleaved],
|
|
384
|
+
cost: model_data[:cost],
|
|
385
|
+
limit: model_data[:limit],
|
|
386
|
+
knowledge: model_data[:knowledge]
|
|
387
|
+
}
|
|
388
|
+
metadata.compact
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def normalize_models_dev_modalities(modalities)
|
|
392
|
+
normalized = { input: [], output: [] }
|
|
393
|
+
return normalized unless modalities
|
|
394
|
+
|
|
395
|
+
normalized[:input] = Array(modalities[:input]).compact
|
|
396
|
+
normalized[:output] = Array(modalities[:output]).compact
|
|
397
|
+
normalized
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def normalize_models_dev_knowledge(value)
|
|
401
|
+
return if value.nil?
|
|
402
|
+
return value if value.is_a?(Date)
|
|
403
|
+
|
|
404
|
+
Date.parse(value.to_s)
|
|
405
|
+
rescue ArgumentError
|
|
406
|
+
nil
|
|
407
|
+
end
|
|
154
408
|
end
|
|
155
409
|
|
|
156
410
|
def initialize(models = nil)
|
|
157
|
-
@models = models || self.class.load_models
|
|
411
|
+
@models = self.class.filter_models(models || self.class.load_models)
|
|
158
412
|
end
|
|
159
413
|
|
|
160
414
|
def load_from_json!(file = RubyLLM.config.model_registry_file)
|
|
@@ -217,15 +471,44 @@ module RubyLLM
|
|
|
217
471
|
|
|
218
472
|
def find_with_provider(model_id, provider)
|
|
219
473
|
resolved_id = Aliases.resolve(model_id, provider)
|
|
220
|
-
|
|
221
|
-
|
|
474
|
+
resolved_id = resolve_bedrock_region_id(resolved_id) if provider.to_s == 'bedrock'
|
|
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 } ||
|
|
222
477
|
raise(ModelNotFoundError, "Unknown model: #{model_id} for provider: #{provider}")
|
|
223
478
|
end
|
|
224
479
|
|
|
480
|
+
def resolve_bedrock_region_id(model_id)
|
|
481
|
+
region = RubyLLM.config.bedrock_region.to_s
|
|
482
|
+
return model_id if region.empty?
|
|
483
|
+
|
|
484
|
+
candidate_id = Providers::Bedrock::Models.with_region_prefix(model_id, region)
|
|
485
|
+
return model_id if candidate_id == model_id
|
|
486
|
+
|
|
487
|
+
candidate = all.find { |m| m.provider == 'bedrock' && m.id == candidate_id }
|
|
488
|
+
return model_id unless candidate
|
|
489
|
+
|
|
490
|
+
inference_types = Array(candidate.metadata[:inference_types] || candidate.metadata['inference_types'])
|
|
491
|
+
Providers::Bedrock::Models.normalize_inference_profile_id(model_id, inference_types, region)
|
|
492
|
+
end
|
|
493
|
+
|
|
225
494
|
def find_without_provider(model_id)
|
|
226
|
-
all.
|
|
227
|
-
|
|
228
|
-
|
|
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
|
|
229
512
|
end
|
|
230
513
|
end
|
|
231
514
|
end
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"type": "array",
|
|
56
56
|
"items": {
|
|
57
57
|
"type": "string",
|
|
58
|
-
"enum": ["text", "image", "audio", "embeddings", "moderation"]
|
|
58
|
+
"enum": ["text", "image", "audio", "video", "embeddings", "moderation"]
|
|
59
59
|
},
|
|
60
60
|
"uniqueItems": true,
|
|
61
61
|
"description": "Supported output modalities"
|
|
@@ -165,4 +165,4 @@
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
|
-
}
|
|
168
|
+
}
|
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
|