ruby_llm_community 0.0.1 → 0.0.3

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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +22 -0
  3. data/README.md +172 -0
  4. data/lib/generators/ruby_llm/install/templates/INSTALL_INFO.md.tt +108 -0
  5. data/lib/generators/ruby_llm/install/templates/chat_model.rb.tt +3 -0
  6. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +8 -0
  7. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +15 -0
  8. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +14 -0
  9. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +6 -0
  10. data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +3 -0
  11. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +3 -0
  12. data/lib/generators/ruby_llm/install_generator.rb +121 -0
  13. data/lib/ruby_llm/active_record/acts_as.rb +382 -0
  14. data/lib/ruby_llm/aliases.json +217 -0
  15. data/lib/ruby_llm/aliases.rb +56 -0
  16. data/lib/ruby_llm/attachment.rb +164 -0
  17. data/lib/ruby_llm/chat.rb +226 -0
  18. data/lib/ruby_llm/chunk.rb +6 -0
  19. data/lib/ruby_llm/configuration.rb +73 -0
  20. data/lib/ruby_llm/connection.rb +126 -0
  21. data/lib/ruby_llm/content.rb +52 -0
  22. data/lib/ruby_llm/context.rb +29 -0
  23. data/lib/ruby_llm/embedding.rb +30 -0
  24. data/lib/ruby_llm/error.rb +84 -0
  25. data/lib/ruby_llm/image.rb +53 -0
  26. data/lib/ruby_llm/message.rb +81 -0
  27. data/lib/ruby_llm/mime_type.rb +67 -0
  28. data/lib/ruby_llm/model/info.rb +101 -0
  29. data/lib/ruby_llm/model/modalities.rb +22 -0
  30. data/lib/ruby_llm/model/pricing.rb +51 -0
  31. data/lib/ruby_llm/model/pricing_category.rb +48 -0
  32. data/lib/ruby_llm/model/pricing_tier.rb +34 -0
  33. data/lib/ruby_llm/model.rb +7 -0
  34. data/lib/ruby_llm/models.json +29924 -0
  35. data/lib/ruby_llm/models.rb +214 -0
  36. data/lib/ruby_llm/models_schema.json +168 -0
  37. data/lib/ruby_llm/provider.rb +221 -0
  38. data/lib/ruby_llm/providers/anthropic/capabilities.rb +179 -0
  39. data/lib/ruby_llm/providers/anthropic/chat.rb +120 -0
  40. data/lib/ruby_llm/providers/anthropic/embeddings.rb +20 -0
  41. data/lib/ruby_llm/providers/anthropic/media.rb +116 -0
  42. data/lib/ruby_llm/providers/anthropic/models.rb +56 -0
  43. data/lib/ruby_llm/providers/anthropic/streaming.rb +45 -0
  44. data/lib/ruby_llm/providers/anthropic/tools.rb +108 -0
  45. data/lib/ruby_llm/providers/anthropic.rb +37 -0
  46. data/lib/ruby_llm/providers/bedrock/capabilities.rb +167 -0
  47. data/lib/ruby_llm/providers/bedrock/chat.rb +76 -0
  48. data/lib/ruby_llm/providers/bedrock/media.rb +73 -0
  49. data/lib/ruby_llm/providers/bedrock/models.rb +82 -0
  50. data/lib/ruby_llm/providers/bedrock/signing.rb +831 -0
  51. data/lib/ruby_llm/providers/bedrock/streaming/base.rb +63 -0
  52. data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +71 -0
  53. data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +79 -0
  54. data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +92 -0
  55. data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +91 -0
  56. data/lib/ruby_llm/providers/bedrock/streaming.rb +36 -0
  57. data/lib/ruby_llm/providers/bedrock.rb +83 -0
  58. data/lib/ruby_llm/providers/deepseek/capabilities.rb +131 -0
  59. data/lib/ruby_llm/providers/deepseek/chat.rb +17 -0
  60. data/lib/ruby_llm/providers/deepseek.rb +30 -0
  61. data/lib/ruby_llm/providers/gemini/capabilities.rb +351 -0
  62. data/lib/ruby_llm/providers/gemini/chat.rb +146 -0
  63. data/lib/ruby_llm/providers/gemini/embeddings.rb +39 -0
  64. data/lib/ruby_llm/providers/gemini/images.rb +48 -0
  65. data/lib/ruby_llm/providers/gemini/media.rb +55 -0
  66. data/lib/ruby_llm/providers/gemini/models.rb +41 -0
  67. data/lib/ruby_llm/providers/gemini/streaming.rb +66 -0
  68. data/lib/ruby_llm/providers/gemini/tools.rb +82 -0
  69. data/lib/ruby_llm/providers/gemini.rb +36 -0
  70. data/lib/ruby_llm/providers/gpustack/chat.rb +17 -0
  71. data/lib/ruby_llm/providers/gpustack/models.rb +55 -0
  72. data/lib/ruby_llm/providers/gpustack.rb +33 -0
  73. data/lib/ruby_llm/providers/mistral/capabilities.rb +163 -0
  74. data/lib/ruby_llm/providers/mistral/chat.rb +26 -0
  75. data/lib/ruby_llm/providers/mistral/embeddings.rb +36 -0
  76. data/lib/ruby_llm/providers/mistral/models.rb +49 -0
  77. data/lib/ruby_llm/providers/mistral.rb +32 -0
  78. data/lib/ruby_llm/providers/ollama/chat.rb +28 -0
  79. data/lib/ruby_llm/providers/ollama/media.rb +50 -0
  80. data/lib/ruby_llm/providers/ollama.rb +29 -0
  81. data/lib/ruby_llm/providers/openai/capabilities.rb +306 -0
  82. data/lib/ruby_llm/providers/openai/chat.rb +87 -0
  83. data/lib/ruby_llm/providers/openai/embeddings.rb +36 -0
  84. data/lib/ruby_llm/providers/openai/images.rb +38 -0
  85. data/lib/ruby_llm/providers/openai/media.rb +81 -0
  86. data/lib/ruby_llm/providers/openai/models.rb +39 -0
  87. data/lib/ruby_llm/providers/openai/response.rb +116 -0
  88. data/lib/ruby_llm/providers/openai/response_media.rb +76 -0
  89. data/lib/ruby_llm/providers/openai/streaming.rb +191 -0
  90. data/lib/ruby_llm/providers/openai/tools.rb +100 -0
  91. data/lib/ruby_llm/providers/openai.rb +44 -0
  92. data/lib/ruby_llm/providers/openai_base.rb +44 -0
  93. data/lib/ruby_llm/providers/openrouter/models.rb +88 -0
  94. data/lib/ruby_llm/providers/openrouter.rb +26 -0
  95. data/lib/ruby_llm/providers/perplexity/capabilities.rb +138 -0
  96. data/lib/ruby_llm/providers/perplexity/chat.rb +17 -0
  97. data/lib/ruby_llm/providers/perplexity/models.rb +42 -0
  98. data/lib/ruby_llm/providers/perplexity.rb +52 -0
  99. data/lib/ruby_llm/railtie.rb +17 -0
  100. data/lib/ruby_llm/stream_accumulator.rb +103 -0
  101. data/lib/ruby_llm/streaming.rb +162 -0
  102. data/lib/ruby_llm/tool.rb +100 -0
  103. data/lib/ruby_llm/tool_call.rb +31 -0
  104. data/lib/ruby_llm/utils.rb +49 -0
  105. data/lib/ruby_llm/version.rb +5 -0
  106. data/lib/ruby_llm.rb +98 -0
  107. data/lib/tasks/aliases.rake +235 -0
  108. data/lib/tasks/models_docs.rake +224 -0
  109. data/lib/tasks/models_update.rake +108 -0
  110. data/lib/tasks/release.rake +32 -0
  111. data/lib/tasks/vcr.rake +99 -0
  112. metadata +128 -7
@@ -0,0 +1,382 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module ActiveRecord
5
+ # Adds chat and message persistence capabilities to ActiveRecord models.
6
+ # Provides a clean interface for storing chat history, message metadata,
7
+ # and attachments in your database.
8
+ module ActsAs
9
+ extend ActiveSupport::Concern
10
+
11
+ class_methods do # rubocop:disable Metrics/BlockLength
12
+ def acts_as_chat(message_class: 'Message', tool_call_class: 'ToolCall')
13
+ include ChatMethods
14
+
15
+ @message_class = message_class.to_s
16
+ @tool_call_class = tool_call_class.to_s
17
+
18
+ has_many :messages,
19
+ -> { order(created_at: :asc) },
20
+ class_name: @message_class,
21
+ inverse_of: :chat,
22
+ dependent: :destroy
23
+
24
+ delegate :add_message, to: :to_llm
25
+ end
26
+
27
+ def acts_as_message(chat_class: 'Chat',
28
+ chat_foreign_key: nil,
29
+ tool_call_class: 'ToolCall',
30
+ tool_call_foreign_key: nil,
31
+ touch_chat: false)
32
+ include MessageMethods
33
+
34
+ @chat_class = chat_class.to_s
35
+ @chat_foreign_key = chat_foreign_key || ActiveSupport::Inflector.foreign_key(@chat_class)
36
+
37
+ @tool_call_class = tool_call_class.to_s
38
+ @tool_call_foreign_key = tool_call_foreign_key || ActiveSupport::Inflector.foreign_key(@tool_call_class)
39
+
40
+ belongs_to :chat,
41
+ class_name: @chat_class,
42
+ foreign_key: @chat_foreign_key,
43
+ inverse_of: :messages,
44
+ touch: touch_chat
45
+
46
+ has_many :tool_calls,
47
+ class_name: @tool_call_class,
48
+ dependent: :destroy
49
+
50
+ belongs_to :parent_tool_call,
51
+ class_name: @tool_call_class,
52
+ foreign_key: @tool_call_foreign_key,
53
+ optional: true,
54
+ inverse_of: :result
55
+
56
+ delegate :tool_call?, :tool_result?, :tool_results, to: :to_llm
57
+ end
58
+
59
+ def acts_as_tool_call(message_class: 'Message', message_foreign_key: nil, result_foreign_key: nil)
60
+ @message_class = message_class.to_s
61
+ @message_foreign_key = message_foreign_key || ActiveSupport::Inflector.foreign_key(@message_class)
62
+ @result_foreign_key = result_foreign_key || ActiveSupport::Inflector.foreign_key(name)
63
+
64
+ belongs_to :message,
65
+ class_name: @message_class,
66
+ foreign_key: @message_foreign_key,
67
+ inverse_of: :tool_calls
68
+
69
+ has_one :result,
70
+ class_name: @message_class,
71
+ foreign_key: @result_foreign_key,
72
+ inverse_of: :parent_tool_call,
73
+ dependent: :nullify
74
+ end
75
+ end
76
+ end
77
+
78
+ # Methods mixed into chat models to handle message persistence and
79
+ # provide a conversation interface.
80
+ module ChatMethods
81
+ extend ActiveSupport::Concern
82
+
83
+ class_methods do
84
+ attr_reader :tool_call_class
85
+ end
86
+
87
+ def to_llm(context: nil)
88
+ @chat ||= if context
89
+ context.chat(model: model_id)
90
+ else
91
+ RubyLLM.chat(model: model_id)
92
+ end
93
+ @chat.reset_messages!
94
+
95
+ messages.each do |msg|
96
+ @chat.add_message(msg.to_llm)
97
+ end
98
+
99
+ setup_persistence_callbacks
100
+ end
101
+
102
+ def with_instructions(instructions, replace: false)
103
+ transaction do
104
+ messages.where(role: :system).destroy_all if replace
105
+ messages.create!(role: :system, content: instructions)
106
+ end
107
+ to_llm.with_instructions(instructions)
108
+ self
109
+ end
110
+
111
+ def with_tool(...)
112
+ to_llm.with_tool(...)
113
+ self
114
+ end
115
+
116
+ def with_tools(...)
117
+ to_llm.with_tools(...)
118
+ self
119
+ end
120
+
121
+ def with_model(...)
122
+ update(model_id: to_llm.with_model(...).model.id)
123
+ self
124
+ end
125
+
126
+ def with_temperature(...)
127
+ to_llm.with_temperature(...)
128
+ self
129
+ end
130
+
131
+ def with_context(context)
132
+ to_llm(context: context)
133
+ self
134
+ end
135
+
136
+ def with_params(...)
137
+ to_llm.with_params(...)
138
+ self
139
+ end
140
+
141
+ def with_headers(...)
142
+ to_llm.with_headers(...)
143
+ self
144
+ end
145
+
146
+ def with_schema(...)
147
+ to_llm.with_schema(...)
148
+ self
149
+ end
150
+
151
+ def on_new_message(&block)
152
+ to_llm
153
+
154
+ existing_callback = @chat.instance_variable_get(:@on)[:new_message]
155
+
156
+ @chat.on_new_message do
157
+ existing_callback&.call
158
+ block&.call
159
+ end
160
+ self
161
+ end
162
+
163
+ def on_end_message(&block)
164
+ to_llm
165
+
166
+ existing_callback = @chat.instance_variable_get(:@on)[:end_message]
167
+
168
+ @chat.on_end_message do |msg|
169
+ existing_callback&.call(msg)
170
+ block&.call(msg)
171
+ end
172
+ self
173
+ end
174
+
175
+ def on_tool_call(...)
176
+ to_llm.on_tool_call(...)
177
+ self
178
+ end
179
+
180
+ def on_tool_result(...)
181
+ to_llm.on_tool_result(...)
182
+ self
183
+ end
184
+
185
+ def create_user_message(content, with: nil)
186
+ message_record = messages.create!(role: :user, content: content)
187
+ persist_content(message_record, with) if with.present?
188
+ message_record
189
+ end
190
+
191
+ def ask(message, with: nil, &)
192
+ create_user_message(message, with:)
193
+ complete(&)
194
+ end
195
+
196
+ alias say ask
197
+
198
+ def complete(...)
199
+ to_llm.complete(...)
200
+ rescue RubyLLM::Error => e
201
+ cleanup_failed_messages if @message&.persisted? && @message.content.blank?
202
+ cleanup_orphaned_tool_results
203
+ raise e
204
+ end
205
+
206
+ private
207
+
208
+ def cleanup_failed_messages
209
+ RubyLLM.logger.debug "RubyLLM: API call failed, destroying message: #{@message.id}"
210
+ @message.destroy
211
+ end
212
+
213
+ def cleanup_orphaned_tool_results
214
+ loop do
215
+ messages.reload
216
+ last = messages.order(:id).last
217
+
218
+ break unless last&.tool_call? || last&.tool_result?
219
+
220
+ last.destroy
221
+ end
222
+ end
223
+
224
+ def setup_persistence_callbacks
225
+ # Only set up once per chat instance
226
+ return @chat if @chat.instance_variable_get(:@_persistence_callbacks_setup)
227
+
228
+ # Set up persistence callbacks (user callbacks will be chained via on_new_message/on_end_message methods)
229
+ @chat.on_new_message { persist_new_message }
230
+ @chat.on_end_message { |msg| persist_message_completion(msg) }
231
+
232
+ @chat.instance_variable_set(:@_persistence_callbacks_setup, true)
233
+ @chat
234
+ end
235
+
236
+ def persist_new_message
237
+ @message = messages.create!(role: :assistant, content: '')
238
+ end
239
+
240
+ def persist_message_completion(message)
241
+ return unless message
242
+
243
+ tool_call_id = find_tool_call_id(message.tool_call_id) if message.tool_call_id
244
+
245
+ transaction do
246
+ # Convert parsed JSON back to JSON string for storage
247
+ content = message.content
248
+ content = content.to_json if content.is_a?(Hash) || content.is_a?(Array)
249
+
250
+ @message.update!(
251
+ role: message.role,
252
+ content: content,
253
+ model_id: message.model_id,
254
+ input_tokens: message.input_tokens,
255
+ output_tokens: message.output_tokens
256
+ )
257
+ @message.write_attribute(@message.class.tool_call_foreign_key, tool_call_id) if tool_call_id
258
+ @message.save!
259
+ persist_tool_calls(message.tool_calls) if message.tool_calls.present?
260
+ end
261
+ end
262
+
263
+ def persist_tool_calls(tool_calls)
264
+ tool_calls.each_value do |tool_call|
265
+ attributes = tool_call.to_h
266
+ attributes[:tool_call_id] = attributes.delete(:id)
267
+ @message.tool_calls.create!(**attributes)
268
+ end
269
+ end
270
+
271
+ def find_tool_call_id(tool_call_id)
272
+ self.class.tool_call_class.constantize.find_by(tool_call_id: tool_call_id)&.id
273
+ end
274
+
275
+ def persist_content(message_record, attachments)
276
+ return unless message_record.respond_to?(:attachments)
277
+
278
+ attachables = prepare_for_active_storage(attachments)
279
+ message_record.attachments.attach(attachables) if attachables.any?
280
+ end
281
+
282
+ def prepare_for_active_storage(attachments)
283
+ Utils.to_safe_array(attachments).filter_map do |attachment|
284
+ case attachment
285
+ when ActionDispatch::Http::UploadedFile, ActiveStorage::Blob
286
+ attachment
287
+ when ActiveStorage::Attached::One, ActiveStorage::Attached::Many
288
+ attachment.blobs
289
+ when Hash
290
+ attachment.values.map { |v| prepare_for_active_storage(v) }
291
+ else
292
+ convert_to_active_storage_format(attachment)
293
+ end
294
+ end.flatten.compact
295
+ end
296
+
297
+ def convert_to_active_storage_format(source)
298
+ return if source.blank?
299
+
300
+ # Let RubyLLM::Attachment handle the heavy lifting
301
+ attachment = RubyLLM::Attachment.new(source)
302
+
303
+ {
304
+ io: StringIO.new(attachment.content),
305
+ filename: attachment.filename,
306
+ content_type: attachment.mime_type
307
+ }
308
+ rescue StandardError => e
309
+ RubyLLM.logger.warn "Failed to process attachment #{source}: #{e.message}"
310
+ nil
311
+ end
312
+ end
313
+
314
+ # Methods mixed into message models to handle serialization and
315
+ # provide a clean interface to the underlying message data.
316
+ module MessageMethods
317
+ extend ActiveSupport::Concern
318
+
319
+ class_methods do
320
+ attr_reader :chat_class, :tool_call_class, :chat_foreign_key, :tool_call_foreign_key
321
+ end
322
+
323
+ def to_llm
324
+ RubyLLM::Message.new(
325
+ role: role.to_sym,
326
+ content: extract_content,
327
+ tool_calls: extract_tool_calls,
328
+ tool_call_id: extract_tool_call_id,
329
+ input_tokens: input_tokens,
330
+ output_tokens: output_tokens,
331
+ model_id: model_id
332
+ )
333
+ end
334
+
335
+ private
336
+
337
+ def extract_tool_calls
338
+ tool_calls.to_h do |tool_call|
339
+ [
340
+ tool_call.tool_call_id,
341
+ RubyLLM::ToolCall.new(
342
+ id: tool_call.tool_call_id,
343
+ name: tool_call.name,
344
+ arguments: tool_call.arguments
345
+ )
346
+ ]
347
+ end
348
+ end
349
+
350
+ def extract_tool_call_id
351
+ parent_tool_call&.tool_call_id
352
+ end
353
+
354
+ def extract_content
355
+ return content unless respond_to?(:attachments) && attachments.attached?
356
+
357
+ RubyLLM::Content.new(content).tap do |content_obj|
358
+ @_tempfiles = []
359
+
360
+ attachments.each do |attachment|
361
+ tempfile = download_attachment(attachment)
362
+ content_obj.add_attachment(tempfile, filename: attachment.filename.to_s)
363
+ end
364
+ end
365
+ end
366
+
367
+ def download_attachment(attachment)
368
+ ext = File.extname(attachment.filename.to_s)
369
+ basename = File.basename(attachment.filename.to_s, ext)
370
+ tempfile = Tempfile.new([basename, ext])
371
+ tempfile.binmode
372
+
373
+ attachment.download { |chunk| tempfile.write(chunk) }
374
+
375
+ tempfile.flush
376
+ tempfile.rewind
377
+ @_tempfiles << tempfile
378
+ tempfile
379
+ end
380
+ end
381
+ end
382
+ end
@@ -0,0 +1,217 @@
1
+ {
2
+ "chatgpt-4o": {
3
+ "openai": "chatgpt-4o-latest",
4
+ "openrouter": "openai/chatgpt-4o-latest"
5
+ },
6
+ "claude-3-5-haiku": {
7
+ "anthropic": "claude-3-5-haiku-20241022",
8
+ "openrouter": "anthropic/claude-3.5-haiku",
9
+ "bedrock": "anthropic.claude-3-5-haiku-20241022-v1:0"
10
+ },
11
+ "claude-3-5-sonnet": {
12
+ "anthropic": "claude-3-5-sonnet-20241022",
13
+ "openrouter": "anthropic/claude-3.5-sonnet",
14
+ "bedrock": "anthropic.claude-3-5-sonnet-20240620-v1:0:200k"
15
+ },
16
+ "claude-3-7-sonnet": {
17
+ "anthropic": "claude-3-7-sonnet-20250219",
18
+ "openrouter": "anthropic/claude-3.7-sonnet",
19
+ "bedrock": "us.anthropic.claude-3-7-sonnet-20250219-v1:0"
20
+ },
21
+ "claude-3-haiku": {
22
+ "anthropic": "claude-3-haiku-20240307",
23
+ "openrouter": "anthropic/claude-3-haiku",
24
+ "bedrock": "anthropic.claude-3-haiku-20240307-v1:0:200k"
25
+ },
26
+ "claude-3-opus": {
27
+ "anthropic": "claude-3-opus-20240229",
28
+ "openrouter": "anthropic/claude-3-opus",
29
+ "bedrock": "anthropic.claude-3-opus-20240229-v1:0:200k"
30
+ },
31
+ "claude-3-sonnet": {
32
+ "bedrock": "anthropic.claude-3-sonnet-20240229-v1:0"
33
+ },
34
+ "claude-opus-4": {
35
+ "anthropic": "claude-opus-4-20250514",
36
+ "openrouter": "anthropic/claude-opus-4",
37
+ "bedrock": "us.anthropic.claude-opus-4-1-20250805-v1:0"
38
+ },
39
+ "claude-opus-4-1": {
40
+ "anthropic": "claude-opus-4-1-20250805",
41
+ "openrouter": "anthropic/claude-opus-4.1",
42
+ "bedrock": "us.anthropic.claude-opus-4-1-20250805-v1:0"
43
+ },
44
+ "claude-sonnet-4": {
45
+ "anthropic": "claude-sonnet-4-20250514",
46
+ "openrouter": "anthropic/claude-sonnet-4",
47
+ "bedrock": "us.anthropic.claude-sonnet-4-20250514-v1:0"
48
+ },
49
+ "deepseek-chat": {
50
+ "deepseek": "deepseek-chat",
51
+ "openrouter": "deepseek/deepseek-chat"
52
+ },
53
+ "gemini-2.0-flash-001": {
54
+ "gemini": "gemini-2.0-flash-001",
55
+ "openrouter": "google/gemini-2.0-flash-001"
56
+ },
57
+ "gemini-2.0-flash-lite-001": {
58
+ "gemini": "gemini-2.0-flash-lite-001",
59
+ "openrouter": "google/gemini-2.0-flash-lite-001"
60
+ },
61
+ "gemini-2.5-flash": {
62
+ "gemini": "gemini-2.5-flash",
63
+ "openrouter": "google/gemini-2.5-flash"
64
+ },
65
+ "gemini-2.5-flash-lite": {
66
+ "gemini": "gemini-2.5-flash-lite",
67
+ "openrouter": "google/gemini-2.5-flash-lite"
68
+ },
69
+ "gemini-2.5-flash-lite-preview-06-17": {
70
+ "gemini": "gemini-2.5-flash-lite-preview-06-17",
71
+ "openrouter": "google/gemini-2.5-flash-lite-preview-06-17"
72
+ },
73
+ "gemini-2.5-pro": {
74
+ "gemini": "gemini-2.5-pro",
75
+ "openrouter": "google/gemini-2.5-pro"
76
+ },
77
+ "gemini-2.5-pro-preview-05-06": {
78
+ "gemini": "gemini-2.5-pro-preview-05-06",
79
+ "openrouter": "google/gemini-2.5-pro-preview-05-06"
80
+ },
81
+ "gemma-3-12b-it": {
82
+ "gemini": "gemma-3-12b-it",
83
+ "openrouter": "google/gemma-3-12b-it"
84
+ },
85
+ "gemma-3-27b-it": {
86
+ "gemini": "gemma-3-27b-it",
87
+ "openrouter": "google/gemma-3-27b-it"
88
+ },
89
+ "gemma-3-4b-it": {
90
+ "gemini": "gemma-3-4b-it",
91
+ "openrouter": "google/gemma-3-4b-it"
92
+ },
93
+ "gemma-3n-e4b-it": {
94
+ "gemini": "gemma-3n-e4b-it",
95
+ "openrouter": "google/gemma-3n-e4b-it"
96
+ },
97
+ "gpt-3.5-turbo": {
98
+ "openai": "gpt-3.5-turbo",
99
+ "openrouter": "openai/gpt-3.5-turbo"
100
+ },
101
+ "gpt-3.5-turbo-16k": {
102
+ "openai": "gpt-3.5-turbo-16k",
103
+ "openrouter": "openai/gpt-3.5-turbo-16k"
104
+ },
105
+ "gpt-3.5-turbo-instruct": {
106
+ "openai": "gpt-3.5-turbo-instruct",
107
+ "openrouter": "openai/gpt-3.5-turbo-instruct"
108
+ },
109
+ "gpt-4": {
110
+ "openai": "gpt-4",
111
+ "openrouter": "openai/gpt-4"
112
+ },
113
+ "gpt-4-1106-preview": {
114
+ "openai": "gpt-4-1106-preview",
115
+ "openrouter": "openai/gpt-4-1106-preview"
116
+ },
117
+ "gpt-4-turbo": {
118
+ "openai": "gpt-4-turbo",
119
+ "openrouter": "openai/gpt-4-turbo"
120
+ },
121
+ "gpt-4-turbo-preview": {
122
+ "openai": "gpt-4-turbo-preview",
123
+ "openrouter": "openai/gpt-4-turbo-preview"
124
+ },
125
+ "gpt-4.1": {
126
+ "openai": "gpt-4.1",
127
+ "openrouter": "openai/gpt-4.1"
128
+ },
129
+ "gpt-4.1-mini": {
130
+ "openai": "gpt-4.1-mini",
131
+ "openrouter": "openai/gpt-4.1-mini"
132
+ },
133
+ "gpt-4.1-nano": {
134
+ "openai": "gpt-4.1-nano",
135
+ "openrouter": "openai/gpt-4.1-nano"
136
+ },
137
+ "gpt-4o": {
138
+ "openai": "gpt-4o",
139
+ "openrouter": "openai/gpt-4o"
140
+ },
141
+ "gpt-4o-2024-05-13": {
142
+ "openai": "gpt-4o-2024-05-13",
143
+ "openrouter": "openai/gpt-4o-2024-05-13"
144
+ },
145
+ "gpt-4o-2024-08-06": {
146
+ "openai": "gpt-4o-2024-08-06",
147
+ "openrouter": "openai/gpt-4o-2024-08-06"
148
+ },
149
+ "gpt-4o-2024-11-20": {
150
+ "openai": "gpt-4o-2024-11-20",
151
+ "openrouter": "openai/gpt-4o-2024-11-20"
152
+ },
153
+ "gpt-4o-mini": {
154
+ "openai": "gpt-4o-mini",
155
+ "openrouter": "openai/gpt-4o-mini"
156
+ },
157
+ "gpt-4o-mini-2024-07-18": {
158
+ "openai": "gpt-4o-mini-2024-07-18",
159
+ "openrouter": "openai/gpt-4o-mini-2024-07-18"
160
+ },
161
+ "gpt-4o-mini-search-preview": {
162
+ "openai": "gpt-4o-mini-search-preview",
163
+ "openrouter": "openai/gpt-4o-mini-search-preview"
164
+ },
165
+ "gpt-4o-search-preview": {
166
+ "openai": "gpt-4o-search-preview",
167
+ "openrouter": "openai/gpt-4o-search-preview"
168
+ },
169
+ "gpt-5": {
170
+ "openai": "gpt-5",
171
+ "openrouter": "openai/gpt-5"
172
+ },
173
+ "gpt-5-mini": {
174
+ "openai": "gpt-5-mini",
175
+ "openrouter": "openai/gpt-5-mini"
176
+ },
177
+ "gpt-5-nano": {
178
+ "openai": "gpt-5-nano",
179
+ "openrouter": "openai/gpt-5-nano"
180
+ },
181
+ "gpt-oss-120b": {
182
+ "openai": "gpt-oss-120b",
183
+ "openrouter": "openai/gpt-oss-120b"
184
+ },
185
+ "o1": {
186
+ "openai": "o1",
187
+ "openrouter": "openai/o1"
188
+ },
189
+ "o1-mini": {
190
+ "openai": "o1-mini",
191
+ "openrouter": "openai/o1-mini"
192
+ },
193
+ "o1-mini-2024-09-12": {
194
+ "openai": "o1-mini-2024-09-12",
195
+ "openrouter": "openai/o1-mini-2024-09-12"
196
+ },
197
+ "o1-pro": {
198
+ "openai": "o1-pro",
199
+ "openrouter": "openai/o1-pro"
200
+ },
201
+ "o3": {
202
+ "openai": "o3",
203
+ "openrouter": "openai/o3"
204
+ },
205
+ "o3-mini": {
206
+ "openai": "o3-mini",
207
+ "openrouter": "openai/o3-mini"
208
+ },
209
+ "o3-pro": {
210
+ "openai": "o3-pro",
211
+ "openrouter": "openai/o3-pro"
212
+ },
213
+ "o4-mini": {
214
+ "openai": "o4-mini",
215
+ "openrouter": "openai/o4-mini"
216
+ }
217
+ }
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ # Manages model aliases, allowing users to reference models by simpler names
5
+ # that map to specific model versions across different providers.
6
+ #
7
+ # Aliases are defined in aliases.json and follow the format:
8
+ # {
9
+ # "simple-name": {
10
+ # "provider1": "specific-version-for-provider1",
11
+ # "provider2": "specific-version-for-provider2"
12
+ # }
13
+ # }
14
+ class Aliases
15
+ class << self
16
+ # Resolves a model ID to its provider-specific version
17
+ #
18
+ # @param model_id [String] the model identifier or alias
19
+ # @param provider_slug [String, Symbol, nil] optional provider to resolve for
20
+ # @return [String] the resolved model ID or the original if no alias exists
21
+ def resolve(model_id, provider = nil)
22
+ return model_id unless aliases[model_id]
23
+
24
+ if provider
25
+ aliases[model_id][provider.to_s] || model_id
26
+ else
27
+ # Get native provider's version
28
+ aliases[model_id].values.first || model_id
29
+ end
30
+ end
31
+
32
+ # Returns the loaded aliases mapping
33
+ # @return [Hash] the aliases mapping
34
+ def aliases
35
+ @aliases ||= load_aliases
36
+ end
37
+
38
+ # Loads aliases from the JSON file
39
+ # @return [Hash] the loaded aliases
40
+ def load_aliases
41
+ file_path = File.expand_path('aliases.json', __dir__)
42
+ if File.exist?(file_path)
43
+ JSON.parse(File.read(file_path))
44
+ else
45
+ {}
46
+ end
47
+ end
48
+
49
+ # Reloads aliases from disk
50
+ # @return [Hash] the reloaded aliases
51
+ def reload!
52
+ @aliases = load_aliases
53
+ end
54
+ end
55
+ end
56
+ end