ruby_llm 1.3.0rc1 → 1.3.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ruby_llm/active_record/acts_as.rb +66 -148
  3. data/lib/ruby_llm/aliases.json +170 -42
  4. data/lib/ruby_llm/attachment.rb +164 -0
  5. data/lib/ruby_llm/chat.rb +12 -4
  6. data/lib/ruby_llm/configuration.rb +5 -1
  7. data/lib/ruby_llm/connection.rb +28 -2
  8. data/lib/ruby_llm/content.rb +9 -40
  9. data/lib/ruby_llm/error.rb +1 -0
  10. data/lib/ruby_llm/image.rb +2 -3
  11. data/lib/ruby_llm/message.rb +2 -2
  12. data/lib/ruby_llm/mime_type.rb +67 -0
  13. data/lib/ruby_llm/model/info.rb +101 -0
  14. data/lib/ruby_llm/model/modalities.rb +22 -0
  15. data/lib/ruby_llm/model/pricing.rb +51 -0
  16. data/lib/ruby_llm/model/pricing_category.rb +48 -0
  17. data/lib/ruby_llm/model/pricing_tier.rb +34 -0
  18. data/lib/ruby_llm/model.rb +7 -0
  19. data/lib/ruby_llm/models.json +2220 -1915
  20. data/lib/ruby_llm/models.rb +20 -20
  21. data/lib/ruby_llm/provider.rb +1 -1
  22. data/lib/ruby_llm/providers/anthropic/media.rb +14 -3
  23. data/lib/ruby_llm/providers/anthropic/models.rb +1 -1
  24. data/lib/ruby_llm/providers/bedrock/media.rb +7 -4
  25. data/lib/ruby_llm/providers/bedrock/models.rb +2 -2
  26. data/lib/ruby_llm/providers/gemini/images.rb +3 -2
  27. data/lib/ruby_llm/providers/gemini/media.rb +12 -24
  28. data/lib/ruby_llm/providers/gemini/models.rb +1 -1
  29. data/lib/ruby_llm/providers/ollama/media.rb +8 -4
  30. data/lib/ruby_llm/providers/openai/capabilities.rb +1 -1
  31. data/lib/ruby_llm/providers/openai/images.rb +3 -2
  32. data/lib/ruby_llm/providers/openai/media.rb +18 -8
  33. data/lib/ruby_llm/providers/openai/models.rb +1 -1
  34. data/lib/ruby_llm/providers/openrouter/models.rb +1 -1
  35. data/lib/ruby_llm/streaming.rb +46 -11
  36. data/lib/ruby_llm/utils.rb +14 -9
  37. data/lib/ruby_llm/version.rb +1 -1
  38. data/lib/tasks/aliases.rake +235 -0
  39. data/lib/tasks/release.rake +32 -0
  40. metadata +40 -25
  41. data/lib/ruby_llm/attachments/audio.rb +0 -12
  42. data/lib/ruby_llm/attachments/image.rb +0 -9
  43. data/lib/ruby_llm/attachments/pdf.rb +0 -9
  44. data/lib/ruby_llm/attachments.rb +0 -78
  45. data/lib/ruby_llm/mime_types.rb +0 -713
  46. data/lib/ruby_llm/model_info.rb +0 -237
  47. data/lib/tasks/{models.rake → models_update.rake} +13 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff16d82f5fc8836eba0943ee83bc3fe3b1558172ecc78cc6d4136fa504461e8f
4
- data.tar.gz: 73cd15c8c05f53dae87740e0022cbb4b72ebd151918d7fe97d2f9a6047264069
3
+ metadata.gz: 8a1f6385e98e9396c7f9d8021bd2e574f5f8c8aa1cc05c7f10311e2059101017
4
+ data.tar.gz: e7617280adc17488f9dbc810b424b22ee21f36c0684f0a5f37762dd290d6c0de
5
5
  SHA512:
6
- metadata.gz: d9bff16dce3e4cef7f57d8afad8cf90fd995045a6da5412588fc019ac2e57fa4137628525c603ef4df259eec6bf40d748296619671713094ca7d8e7c824c0e78
7
- data.tar.gz: 3f44c0c2783202e8f162d9d705f10061a7ca717f23a26026bbbcdb2cea214edd48c9a0458ec9a226a03eb823f3ec8f3d9a601526c01e277c24f4988bb0634521
6
+ metadata.gz: 80bcef1cb440519b7eb146bd4064f8eb5a40cf1fd5e309ac582c6be8a3aee0a33b244e8c4961bc69ac763b2665adab918238a0d8787b41c2fb6e5e7fd6343ae3
7
+ data.tar.gz: 3d95b550d6d879ad29e20bb328cb09d7191b4055180cba95e237ddab2d1173845463d62e407c777d6b76425958531fb6e7abc664939478c9e89b41565971ad2b
@@ -20,8 +20,7 @@ module RubyLLM
20
20
  class_name: @message_class,
21
21
  dependent: :destroy
22
22
 
23
- delegate :add_message,
24
- to: :to_llm
23
+ delegate :add_message, to: :to_llm
25
24
  end
26
25
 
27
26
  def acts_as_message(chat_class: 'Chat',
@@ -88,26 +87,18 @@ module RubyLLM
88
87
  @chat ||= RubyLLM.chat(model: model_id)
89
88
  @chat.reset_messages!
90
89
 
91
- # Load existing messages into chat
92
90
  messages.each do |msg|
93
91
  @chat.add_message(msg.to_llm)
94
92
  end
95
93
 
96
- # Set up message persistence
97
94
  @chat.on_new_message { persist_new_message }
98
95
  .on_end_message { |msg| persist_message_completion(msg) }
99
96
  end
100
97
 
101
98
  def with_instructions(instructions, replace: false)
102
99
  transaction do
103
- # If replace is true, remove existing system messages
104
100
  messages.where(role: :system).destroy_all if replace
105
-
106
- # Create the new system message
107
- messages.create!(
108
- role: :system,
109
- content: instructions
110
- )
101
+ messages.create!(role: :system, content: instructions)
111
102
  end
112
103
  to_llm.with_instructions(instructions)
113
104
  self
@@ -124,7 +115,7 @@ module RubyLLM
124
115
  end
125
116
 
126
117
  def with_model(...)
127
- to_llm.with_model(...)
118
+ update(model_id: to_llm.with_model(...).model.id)
128
119
  self
129
120
  end
130
121
 
@@ -133,6 +124,11 @@ module RubyLLM
133
124
  self
134
125
  end
135
126
 
127
+ def with_context(...)
128
+ to_llm.with_context(...)
129
+ self
130
+ end
131
+
136
132
  def on_new_message(...)
137
133
  to_llm.on_new_message(...)
138
134
  self
@@ -144,21 +140,8 @@ module RubyLLM
144
140
  end
145
141
 
146
142
  def create_user_message(content, with: nil)
147
- message_record = messages.create!(
148
- role: :user,
149
- content: content
150
- )
151
-
152
- if with.present?
153
- files = Array(with).reject(&:blank?)
154
-
155
- if files.any? && files.first.is_a?(ActionDispatch::Http::UploadedFile)
156
- message_record.attachments.attach(files)
157
- else
158
- attach_files(message_record, process_attachments(with))
159
- end
160
- end
161
-
143
+ message_record = messages.create!(role: :user, content: content)
144
+ persist_content(message_record, with) if with.present?
162
145
  message_record
163
146
  end
164
147
 
@@ -182,21 +165,16 @@ module RubyLLM
182
165
  private
183
166
 
184
167
  def persist_new_message
185
- @message = messages.create!(
186
- role: :assistant,
187
- content: String.new
188
- )
168
+ @message = messages.create!(role: :assistant, content: String.new)
189
169
  end
190
170
 
191
171
  def persist_message_completion(message)
192
172
  return unless message
193
173
 
194
- if message.tool_call_id
195
- tool_call_id = self.class.tool_call_class.constantize.find_by(tool_call_id: message.tool_call_id)&.id
196
- end
174
+ tool_call_id = find_tool_call_id(message.tool_call_id) if message.tool_call_id
197
175
 
198
176
  transaction do
199
- @message.update(
177
+ @message.update!(
200
178
  role: message.role,
201
179
  content: message.content,
202
180
  model_id: message.model_id,
@@ -217,84 +195,46 @@ module RubyLLM
217
195
  end
218
196
  end
219
197
 
220
- def process_attachments(attachments) # rubocop:disable Metrics/PerceivedComplexity
221
- return {} if attachments.nil?
222
-
223
- result = {}
224
- files = Array(attachments)
225
-
226
- files.each do |file|
227
- content_type = if file.respond_to?(:content_type)
228
- file.content_type
229
- elsif file.is_a?(ActiveStorage::Attachment)
230
- file.blob.content_type
231
- else
232
- RubyLLM::MimeTypes.detect_from_path(file.to_s)
233
- end
234
-
235
- if RubyLLM::MimeTypes.image?(content_type)
236
- result[:image] ||= []
237
- result[:image] << file
238
- elsif RubyLLM::MimeTypes.audio?(content_type)
239
- result[:audio] ||= []
240
- result[:audio] << file
241
- else
242
- # Default to PDF for unknown types
243
- result[:pdf] ||= []
244
- result[:pdf] << file
245
- end
246
- end
247
-
248
- result
198
+ def find_tool_call_id(tool_call_id)
199
+ self.class.tool_call_class.constantize.find_by(tool_call_id: tool_call_id)&.id
249
200
  end
250
201
 
251
- def attach_files(message, attachments_hash)
252
- return unless message.respond_to?(:attachments)
202
+ def persist_content(message_record, attachments)
203
+ return unless message_record.respond_to?(:attachments)
253
204
 
254
- %i[image audio pdf].each do |type|
255
- Array(attachments_hash[type]).each do |file_source|
256
- attach_file(message, file_source)
257
- end
258
- end
205
+ attachables = prepare_for_active_storage(attachments)
206
+ message_record.attachments.attach(attachables) if attachables.any?
259
207
  end
260
208
 
261
- def attach_file(message, file_source)
262
- if file_source.to_s.match?(%r{^https?://})
263
- # For URLs, create a special attachment that just stores the URL
264
- content_type = RubyLLM::MimeTypes.detect_from_path(file_source.to_s)
265
-
266
- # Create a minimal blob that just stores the URL
267
- blob = ActiveStorage::Blob.create_and_upload!(
268
- io: StringIO.new('URL Reference'),
269
- filename: File.basename(file_source),
270
- content_type: content_type,
271
- metadata: { original_url: file_source.to_s }
272
- )
273
- message.attachments.attach(blob)
274
- elsif file_source.respond_to?(:read)
275
- # Handle various file source types
276
- message.attachments.attach(
277
- io: file_source,
278
- filename: extract_filename(file_source),
279
- content_type: RubyLLM::MimeTypes.detect_from_path(extract_filename(file_source))
280
- ) # Already a file-like object
281
- elsif file_source.is_a?(::ActiveStorage::Attachment)
282
- # Copy from existing ActiveStorage attachment
283
- message.attachments.attach(file_source.blob)
284
- elsif file_source.is_a?(::ActiveStorage::Blob)
285
- message.attachments.attach(file_source)
286
- else
287
- # Local file path
288
- message.attachments.attach(
289
- io: File.open(file_source),
290
- filename: File.basename(file_source),
291
- content_type: RubyLLM::MimeTypes.detect_from_path(file_source)
292
- )
293
- end
209
+ def prepare_for_active_storage(attachments)
210
+ Utils.to_safe_array(attachments).filter_map do |attachment|
211
+ case attachment
212
+ when ActionDispatch::Http::UploadedFile, ActiveStorage::Blob
213
+ attachment
214
+ when ActiveStorage::Attached::One, ActiveStorage::Attached::Many
215
+ attachment.blobs
216
+ when Hash
217
+ attachment.values.map { |v| prepare_for_active_storage(v) }
218
+ else
219
+ convert_to_active_storage_format(attachment)
220
+ end
221
+ end.flatten.compact
294
222
  end
295
223
 
296
- def extract_filename(file)
297
- file.respond_to?(:original_filename) ? file.original_filename : 'attachment'
224
+ def convert_to_active_storage_format(source)
225
+ return if source.blank?
226
+
227
+ # Let RubyLLM::Attachment handle the heavy lifting
228
+ attachment = RubyLLM::Attachment.new(source)
229
+
230
+ {
231
+ io: StringIO.new(attachment.content),
232
+ filename: attachment.filename,
233
+ content_type: attachment.mime_type
234
+ }
235
+ rescue StandardError => e
236
+ RubyLLM.logger.warn "Failed to process attachment #{source}: #{e.message}"
237
+ nil
298
238
  end
299
239
  end
300
240
 
@@ -319,6 +259,8 @@ module RubyLLM
319
259
  )
320
260
  end
321
261
 
262
+ private
263
+
322
264
  def extract_tool_calls
323
265
  tool_calls.to_h do |tool_call|
324
266
  [
@@ -336,55 +278,31 @@ module RubyLLM
336
278
  parent_tool_call&.tool_call_id
337
279
  end
338
280
 
339
- def extract_content # rubocop:disable Metrics/PerceivedComplexity
281
+ def extract_content
340
282
  return content unless respond_to?(:attachments) && attachments.attached?
341
283
 
342
- content_obj = RubyLLM::Content.new(content)
343
-
344
- # We need to keep tempfiles alive for the duration of the API call
345
- @_tempfiles = []
346
-
347
- attachments.each do |attachment|
348
- attachment_data = if attachment.metadata&.key?('original_url')
349
- attachment.metadata['original_url']
350
- elsif defined?(ActiveJob) && caller.any? { |c| c.include?('active_job') }
351
- # We're in a background job - need to download the data
352
- temp_file = Tempfile.new([File.basename(attachment.filename.to_s, '.*'),
353
- File.extname(attachment.filename.to_s)])
354
- temp_file.binmode
355
- temp_file.write(attachment.download)
356
- temp_file.flush
357
- temp_file.rewind
358
-
359
- # Store the tempfile reference in the instance variable to prevent GC
360
- @_tempfiles << temp_file
361
-
362
- # Return the file object itself, not just the path
363
- temp_file
364
- else
365
- blob_path_for(attachment)
366
- end
367
-
368
- if RubyLLM::MimeTypes.image?(attachment.content_type)
369
- content_obj.add_image(attachment_data)
370
- elsif RubyLLM::MimeTypes.audio?(attachment.content_type)
371
- content_obj.add_audio(attachment_data)
372
- elsif RubyLLM::MimeTypes.pdf?(attachment.content_type)
373
- content_obj.add_pdf(attachment_data)
284
+ RubyLLM::Content.new(content).tap do |content_obj|
285
+ @_tempfiles = []
286
+
287
+ attachments.each do |attachment|
288
+ tempfile = download_attachment(attachment)
289
+ content_obj.add_attachment(tempfile, filename: attachment.filename.to_s)
374
290
  end
375
291
  end
376
-
377
- content_obj
378
292
  end
379
293
 
380
- private
294
+ def download_attachment(attachment)
295
+ ext = File.extname(attachment.filename.to_s)
296
+ basename = File.basename(attachment.filename.to_s, ext)
297
+ tempfile = Tempfile.new([basename, ext])
298
+ tempfile.binmode
381
299
 
382
- def blob_path_for(attachment)
383
- if Rails.application.routes.url_helpers.respond_to?(:rails_blob_path)
384
- Rails.application.routes.url_helpers.rails_blob_path(attachment, only_path: true)
385
- else
386
- attachment.service_url
387
- end
300
+ attachment.download { |chunk| tempfile.write(chunk) }
301
+
302
+ tempfile.flush
303
+ tempfile.rewind
304
+ @_tempfiles << tempfile
305
+ tempfile
388
306
  end
389
307
  end
390
308
  end
@@ -1,80 +1,208 @@
1
1
  {
2
- "claude-3-5-sonnet": {
3
- "anthropic": "claude-3-5-sonnet-20241022",
4
- "bedrock": "anthropic.claude-3-5-sonnet-20241022-v2:0",
5
- "openrouter": "anthropic/claude-3.5-sonnet"
2
+ "chatgpt-4o": {
3
+ "openai": "chatgpt-4o-latest",
4
+ "openrouter": "openai/chatgpt-4o-latest"
5
+ },
6
+ "claude-2.0": {
7
+ "anthropic": "claude-2.0",
8
+ "openrouter": "anthropic/claude-2.0",
9
+ "bedrock": "anthropic.claude-v2:1:200k"
10
+ },
11
+ "claude-2.1": {
12
+ "anthropic": "claude-2.1",
13
+ "openrouter": "anthropic/claude-2.1",
14
+ "bedrock": "anthropic.claude-v2:1:200k"
6
15
  },
7
16
  "claude-3-5-haiku": {
8
17
  "anthropic": "claude-3-5-haiku-20241022",
9
- "bedrock": "anthropic.claude-3-5-haiku-20241022-v1:0",
10
- "openrouter": "anthropic/claude-3.5-haiku-20241022"
18
+ "openrouter": "anthropic/claude-3.5-haiku",
19
+ "bedrock": "anthropic.claude-3-5-haiku-20241022-v1:0"
20
+ },
21
+ "claude-3-5-sonnet": {
22
+ "anthropic": "claude-3-5-sonnet-20241022",
23
+ "openrouter": "anthropic/claude-3.5-sonnet",
24
+ "bedrock": "anthropic.claude-3-5-sonnet-20240620-v1:0:200k"
11
25
  },
12
26
  "claude-3-7-sonnet": {
13
27
  "anthropic": "claude-3-7-sonnet-20250219",
14
- "bedrock": "us.anthropic.claude-3-7-sonnet-20250219-v1:0",
15
- "openrouter": "us.anthropic.claude-3-7-sonnet-20250219-v1:0"
28
+ "openrouter": "anthropic/claude-3.7-sonnet",
29
+ "bedrock": "us.anthropic.claude-3-7-sonnet-20250219-v1:0"
30
+ },
31
+ "claude-3-haiku": {
32
+ "anthropic": "claude-3-haiku-20240307",
33
+ "openrouter": "anthropic/claude-3-haiku",
34
+ "bedrock": "anthropic.claude-3-haiku-20240307-v1:0:200k"
16
35
  },
17
36
  "claude-3-opus": {
18
37
  "anthropic": "claude-3-opus-20240229",
19
- "bedrock": "anthropic.claude-3-opus-20240229-v1:0"
38
+ "openrouter": "anthropic/claude-3-opus",
39
+ "bedrock": "anthropic.claude-3-opus-20240229-v1:0:200k"
20
40
  },
21
41
  "claude-3-sonnet": {
22
42
  "anthropic": "claude-3-sonnet-20240229",
23
- "bedrock": "anthropic.claude-3-sonnet-20240229-v1:0"
43
+ "openrouter": "anthropic/claude-3-sonnet",
44
+ "bedrock": "anthropic.claude-3-sonnet-20240229-v1:0:200k"
24
45
  },
25
- "claude-3-haiku": {
26
- "anthropic": "claude-3-haiku-20240307",
27
- "bedrock": "anthropic.claude-3-haiku-20240307-v1:0"
46
+ "claude-opus-4": {
47
+ "anthropic": "claude-opus-4-20250514",
48
+ "openrouter": "anthropic/claude-opus-4",
49
+ "bedrock": "us.anthropic.claude-opus-4-20250514-v1:0"
28
50
  },
29
- "claude-3": {
30
- "anthropic": "claude-3-sonnet-20240229",
31
- "bedrock": "anthropic.claude-3-sonnet-20240229-v1:0",
32
- "openrouter": "anthropic/claude-3-sonnet"
51
+ "claude-sonnet-4": {
52
+ "anthropic": "claude-sonnet-4-20250514",
53
+ "openrouter": "anthropic/claude-sonnet-4",
54
+ "bedrock": "us.anthropic.claude-sonnet-4-20250514-v1:0"
33
55
  },
34
- "claude-2": {
35
- "anthropic": "claude-2.0",
36
- "bedrock": "anthropic.claude-2.0",
37
- "openrouter": "anthropic/claude-2"
56
+ "deepseek-chat": {
57
+ "deepseek": "deepseek-chat",
58
+ "openrouter": "deepseek/deepseek-chat"
38
59
  },
39
- "claude-2-1": {
40
- "anthropic": "claude-2.1",
41
- "bedrock": "anthropic.claude-2.1",
42
- "openrouter": "anthropic/claude-2.1"
60
+ "gemini-2.0-flash-001": {
61
+ "gemini": "gemini-2.0-flash-001",
62
+ "openrouter": "google/gemini-2.0-flash-001"
63
+ },
64
+ "gemini-2.0-flash-lite-001": {
65
+ "gemini": "gemini-2.0-flash-lite-001",
66
+ "openrouter": "google/gemini-2.0-flash-lite-001"
67
+ },
68
+ "gemini-2.5-flash-preview-05-20": {
69
+ "gemini": "gemini-2.5-flash-preview-05-20",
70
+ "openrouter": "google/gemini-2.5-flash-preview-05-20"
71
+ },
72
+ "gemini-2.5-pro-exp-03-25": {
73
+ "gemini": "gemini-2.5-pro-exp-03-25",
74
+ "openrouter": "google/gemini-2.5-pro-exp-03-25"
75
+ },
76
+ "gemma-3-12b-it": {
77
+ "gemini": "gemma-3-12b-it",
78
+ "openrouter": "google/gemma-3-12b-it"
79
+ },
80
+ "gemma-3-27b-it": {
81
+ "gemini": "gemma-3-27b-it",
82
+ "openrouter": "google/gemma-3-27b-it"
83
+ },
84
+ "gemma-3-4b-it": {
85
+ "gemini": "gemma-3-4b-it",
86
+ "openrouter": "google/gemma-3-4b-it"
87
+ },
88
+ "gpt-3.5-turbo": {
89
+ "openai": "gpt-3.5-turbo",
90
+ "openrouter": "openai/gpt-3.5-turbo"
91
+ },
92
+ "gpt-3.5-turbo-0125": {
93
+ "openai": "gpt-3.5-turbo-0125",
94
+ "openrouter": "openai/gpt-3.5-turbo-0125"
95
+ },
96
+ "gpt-3.5-turbo-1106": {
97
+ "openai": "gpt-3.5-turbo-1106",
98
+ "openrouter": "openai/gpt-3.5-turbo-1106"
99
+ },
100
+ "gpt-3.5-turbo-16k": {
101
+ "openai": "gpt-3.5-turbo-16k",
102
+ "openrouter": "openai/gpt-3.5-turbo-16k"
103
+ },
104
+ "gpt-3.5-turbo-instruct": {
105
+ "openai": "gpt-3.5-turbo-instruct",
106
+ "openrouter": "openai/gpt-3.5-turbo-instruct"
107
+ },
108
+ "gpt-4": {
109
+ "openai": "gpt-4",
110
+ "openrouter": "openai/gpt-4"
111
+ },
112
+ "gpt-4-1106-preview": {
113
+ "openai": "gpt-4-1106-preview",
114
+ "openrouter": "openai/gpt-4-1106-preview"
115
+ },
116
+ "gpt-4-turbo": {
117
+ "openai": "gpt-4-turbo",
118
+ "openrouter": "openai/gpt-4-turbo"
119
+ },
120
+ "gpt-4-turbo-preview": {
121
+ "openai": "gpt-4-turbo-preview",
122
+ "openrouter": "openai/gpt-4-turbo-preview"
123
+ },
124
+ "gpt-4.1": {
125
+ "openai": "gpt-4.1",
126
+ "openrouter": "openai/gpt-4.1"
127
+ },
128
+ "gpt-4.1-mini": {
129
+ "openai": "gpt-4.1-mini",
130
+ "openrouter": "openai/gpt-4.1-mini"
131
+ },
132
+ "gpt-4.1-nano": {
133
+ "openai": "gpt-4.1-nano",
134
+ "openrouter": "openai/gpt-4.1-nano"
135
+ },
136
+ "gpt-4.5-preview": {
137
+ "openai": "gpt-4.5-preview",
138
+ "openrouter": "openai/gpt-4.5-preview"
43
139
  },
44
140
  "gpt-4o": {
45
141
  "openai": "gpt-4o",
46
142
  "openrouter": "openai/gpt-4o"
47
143
  },
144
+ "gpt-4o-2024-05-13": {
145
+ "openai": "gpt-4o-2024-05-13",
146
+ "openrouter": "openai/gpt-4o-2024-05-13"
147
+ },
148
+ "gpt-4o-2024-08-06": {
149
+ "openai": "gpt-4o-2024-08-06",
150
+ "openrouter": "openai/gpt-4o-2024-08-06"
151
+ },
152
+ "gpt-4o-2024-11-20": {
153
+ "openai": "gpt-4o-2024-11-20",
154
+ "openrouter": "openai/gpt-4o-2024-11-20"
155
+ },
48
156
  "gpt-4o-mini": {
49
157
  "openai": "gpt-4o-mini",
50
158
  "openrouter": "openai/gpt-4o-mini"
51
159
  },
52
- "gpt-4-turbo": {
53
- "openai": "gpt-4-turbo",
54
- "openrouter": "openai/gpt-4-turbo"
160
+ "gpt-4o-mini-2024-07-18": {
161
+ "openai": "gpt-4o-mini-2024-07-18",
162
+ "openrouter": "openai/gpt-4o-mini-2024-07-18"
55
163
  },
56
- "gemini-1.5-flash": {
57
- "gemini": "gemini-1.5-flash",
58
- "openrouter": "google/gemini-flash-1.5"
164
+ "gpt-4o-mini-search-preview": {
165
+ "openai": "gpt-4o-mini-search-preview",
166
+ "openrouter": "openai/gpt-4o-mini-search-preview"
59
167
  },
60
- "gemini-1.5-flash-8b": {
61
- "gemini": "gemini-1.5-flash-8b",
62
- "openrouter": "google/gemini-flash-1.5-8b"
63
- },
64
- "gemini-1.5-pro": {
65
- "gemini": "gemini-1.5-pro",
66
- "openrouter": "google/gemini-pro-1.5"
67
- },
68
- "gemini-2.0-flash": {
69
- "gemini": "gemini-2.0-flash",
70
- "openrouter": "google/gemini-2.0-flash-001"
168
+ "gpt-4o-search-preview": {
169
+ "openai": "gpt-4o-search-preview",
170
+ "openrouter": "openai/gpt-4o-search-preview"
71
171
  },
72
172
  "o1": {
73
173
  "openai": "o1",
74
174
  "openrouter": "openai/o1"
75
175
  },
176
+ "o1-mini": {
177
+ "openai": "o1-mini",
178
+ "openrouter": "openai/o1-mini"
179
+ },
180
+ "o1-mini-2024-09-12": {
181
+ "openai": "o1-mini-2024-09-12",
182
+ "openrouter": "openai/o1-mini-2024-09-12"
183
+ },
184
+ "o1-preview": {
185
+ "openai": "o1-preview",
186
+ "openrouter": "openai/o1-preview"
187
+ },
188
+ "o1-preview-2024-09-12": {
189
+ "openai": "o1-preview-2024-09-12",
190
+ "openrouter": "openai/o1-preview-2024-09-12"
191
+ },
192
+ "o1-pro": {
193
+ "openai": "o1-pro",
194
+ "openrouter": "openai/o1-pro"
195
+ },
196
+ "o3": {
197
+ "openai": "o3",
198
+ "openrouter": "openai/o3"
199
+ },
76
200
  "o3-mini": {
77
201
  "openai": "o3-mini",
78
202
  "openrouter": "openai/o3-mini"
203
+ },
204
+ "o4-mini": {
205
+ "openai": "o4-mini",
206
+ "openrouter": "openai/o4-mini"
79
207
  }
80
208
  }