ruby_llm_community 0.0.1 → 0.0.2
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/LICENSE +22 -0
- data/README.md +172 -0
- data/lib/generators/ruby_llm/install/templates/INSTALL_INFO.md.tt +108 -0
- data/lib/generators/ruby_llm/install/templates/chat_model.rb.tt +3 -0
- data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +8 -0
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +15 -0
- data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +14 -0
- data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +6 -0
- data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +3 -0
- data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +3 -0
- data/lib/generators/ruby_llm/install_generator.rb +121 -0
- data/lib/ruby_llm/active_record/acts_as.rb +382 -0
- data/lib/ruby_llm/aliases.json +217 -0
- data/lib/ruby_llm/aliases.rb +56 -0
- data/lib/ruby_llm/attachment.rb +164 -0
- data/lib/ruby_llm/chat.rb +219 -0
- data/lib/ruby_llm/chunk.rb +6 -0
- data/lib/ruby_llm/configuration.rb +75 -0
- data/lib/ruby_llm/connection.rb +126 -0
- data/lib/ruby_llm/content.rb +52 -0
- data/lib/ruby_llm/context.rb +29 -0
- data/lib/ruby_llm/embedding.rb +30 -0
- data/lib/ruby_llm/error.rb +84 -0
- data/lib/ruby_llm/image.rb +53 -0
- data/lib/ruby_llm/message.rb +76 -0
- data/lib/ruby_llm/mime_type.rb +67 -0
- data/lib/ruby_llm/model/info.rb +101 -0
- data/lib/ruby_llm/model/modalities.rb +22 -0
- data/lib/ruby_llm/model/pricing.rb +51 -0
- data/lib/ruby_llm/model/pricing_category.rb +48 -0
- data/lib/ruby_llm/model/pricing_tier.rb +34 -0
- data/lib/ruby_llm/model.rb +7 -0
- data/lib/ruby_llm/models.json +29924 -0
- data/lib/ruby_llm/models.rb +218 -0
- data/lib/ruby_llm/models_schema.json +168 -0
- data/lib/ruby_llm/provider.rb +219 -0
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +179 -0
- data/lib/ruby_llm/providers/anthropic/chat.rb +106 -0
- data/lib/ruby_llm/providers/anthropic/embeddings.rb +20 -0
- data/lib/ruby_llm/providers/anthropic/media.rb +92 -0
- data/lib/ruby_llm/providers/anthropic/models.rb +48 -0
- data/lib/ruby_llm/providers/anthropic/streaming.rb +43 -0
- data/lib/ruby_llm/providers/anthropic/tools.rb +108 -0
- data/lib/ruby_llm/providers/anthropic.rb +37 -0
- data/lib/ruby_llm/providers/bedrock/capabilities.rb +167 -0
- data/lib/ruby_llm/providers/bedrock/chat.rb +65 -0
- data/lib/ruby_llm/providers/bedrock/media.rb +61 -0
- data/lib/ruby_llm/providers/bedrock/models.rb +82 -0
- data/lib/ruby_llm/providers/bedrock/signing.rb +831 -0
- data/lib/ruby_llm/providers/bedrock/streaming/base.rb +63 -0
- data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +63 -0
- data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +79 -0
- data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +90 -0
- data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +91 -0
- data/lib/ruby_llm/providers/bedrock/streaming.rb +36 -0
- data/lib/ruby_llm/providers/bedrock.rb +83 -0
- data/lib/ruby_llm/providers/deepseek/capabilities.rb +131 -0
- data/lib/ruby_llm/providers/deepseek/chat.rb +17 -0
- data/lib/ruby_llm/providers/deepseek.rb +30 -0
- data/lib/ruby_llm/providers/gemini/capabilities.rb +351 -0
- data/lib/ruby_llm/providers/gemini/chat.rb +139 -0
- data/lib/ruby_llm/providers/gemini/embeddings.rb +39 -0
- data/lib/ruby_llm/providers/gemini/images.rb +48 -0
- data/lib/ruby_llm/providers/gemini/media.rb +55 -0
- data/lib/ruby_llm/providers/gemini/models.rb +41 -0
- data/lib/ruby_llm/providers/gemini/streaming.rb +58 -0
- data/lib/ruby_llm/providers/gemini/tools.rb +82 -0
- data/lib/ruby_llm/providers/gemini.rb +36 -0
- data/lib/ruby_llm/providers/gpustack/chat.rb +17 -0
- data/lib/ruby_llm/providers/gpustack/models.rb +55 -0
- data/lib/ruby_llm/providers/gpustack.rb +33 -0
- data/lib/ruby_llm/providers/mistral/capabilities.rb +163 -0
- data/lib/ruby_llm/providers/mistral/chat.rb +26 -0
- data/lib/ruby_llm/providers/mistral/embeddings.rb +36 -0
- data/lib/ruby_llm/providers/mistral/models.rb +49 -0
- data/lib/ruby_llm/providers/mistral.rb +32 -0
- data/lib/ruby_llm/providers/ollama/chat.rb +28 -0
- data/lib/ruby_llm/providers/ollama/media.rb +50 -0
- data/lib/ruby_llm/providers/ollama.rb +29 -0
- data/lib/ruby_llm/providers/openai/capabilities.rb +306 -0
- data/lib/ruby_llm/providers/openai/chat.rb +86 -0
- data/lib/ruby_llm/providers/openai/embeddings.rb +36 -0
- data/lib/ruby_llm/providers/openai/images.rb +38 -0
- data/lib/ruby_llm/providers/openai/media.rb +81 -0
- data/lib/ruby_llm/providers/openai/models.rb +39 -0
- data/lib/ruby_llm/providers/openai/response.rb +115 -0
- data/lib/ruby_llm/providers/openai/response_media.rb +76 -0
- data/lib/ruby_llm/providers/openai/streaming.rb +190 -0
- data/lib/ruby_llm/providers/openai/tools.rb +100 -0
- data/lib/ruby_llm/providers/openai.rb +44 -0
- data/lib/ruby_llm/providers/openai_base.rb +44 -0
- data/lib/ruby_llm/providers/openrouter/models.rb +88 -0
- data/lib/ruby_llm/providers/openrouter.rb +26 -0
- data/lib/ruby_llm/providers/perplexity/capabilities.rb +138 -0
- data/lib/ruby_llm/providers/perplexity/chat.rb +17 -0
- data/lib/ruby_llm/providers/perplexity/models.rb +42 -0
- data/lib/ruby_llm/providers/perplexity.rb +52 -0
- data/lib/ruby_llm/railtie.rb +17 -0
- data/lib/ruby_llm/stream_accumulator.rb +97 -0
- data/lib/ruby_llm/streaming.rb +162 -0
- data/lib/ruby_llm/tool.rb +100 -0
- data/lib/ruby_llm/tool_call.rb +31 -0
- data/lib/ruby_llm/utils.rb +49 -0
- data/lib/ruby_llm/version.rb +5 -0
- data/lib/ruby_llm.rb +98 -0
- data/lib/tasks/aliases.rake +235 -0
- data/lib/tasks/models_docs.rake +224 -0
- data/lib/tasks/models_update.rake +108 -0
- data/lib/tasks/release.rake +32 -0
- data/lib/tasks/vcr.rake +99 -0
- 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
|