ruby_llm 1.6.4 → 1.7.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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -3
  3. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +127 -0
  4. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +39 -0
  5. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +24 -0
  6. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +14 -0
  7. data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +12 -0
  8. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +16 -0
  9. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +29 -0
  10. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +16 -0
  11. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +11 -0
  12. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +23 -0
  13. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +21 -0
  14. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +10 -0
  15. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +9 -0
  16. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +16 -0
  17. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +30 -0
  18. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +18 -0
  19. data/lib/generators/ruby_llm/generator_helpers.rb +129 -0
  20. data/lib/generators/ruby_llm/install/install_generator.rb +104 -0
  21. data/lib/generators/ruby_llm/install/templates/chat_model.rb.tt +2 -2
  22. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +4 -4
  23. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +8 -7
  24. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +40 -0
  25. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +6 -5
  26. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +10 -4
  27. data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +4 -3
  28. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +3 -0
  29. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +2 -2
  30. data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +145 -0
  31. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +121 -0
  32. data/lib/ruby_llm/active_record/acts_as.rb +111 -327
  33. data/lib/ruby_llm/active_record/acts_as_legacy.rb +398 -0
  34. data/lib/ruby_llm/active_record/chat_methods.rb +336 -0
  35. data/lib/ruby_llm/active_record/message_methods.rb +72 -0
  36. data/lib/ruby_llm/active_record/model_methods.rb +84 -0
  37. data/lib/ruby_llm/aliases.json +54 -13
  38. data/lib/ruby_llm/attachment.rb +20 -0
  39. data/lib/ruby_llm/chat.rb +5 -5
  40. data/lib/ruby_llm/configuration.rb +9 -0
  41. data/lib/ruby_llm/connection.rb +4 -4
  42. data/lib/ruby_llm/model/info.rb +12 -0
  43. data/lib/ruby_llm/models.json +3579 -2029
  44. data/lib/ruby_llm/models.rb +51 -22
  45. data/lib/ruby_llm/provider.rb +3 -3
  46. data/lib/ruby_llm/providers/anthropic/chat.rb +2 -2
  47. data/lib/ruby_llm/providers/anthropic/media.rb +1 -1
  48. data/lib/ruby_llm/providers/bedrock/chat.rb +2 -2
  49. data/lib/ruby_llm/providers/bedrock/models.rb +19 -1
  50. data/lib/ruby_llm/providers/gemini/chat.rb +1 -1
  51. data/lib/ruby_llm/providers/gemini/media.rb +1 -1
  52. data/lib/ruby_llm/providers/gpustack/chat.rb +11 -0
  53. data/lib/ruby_llm/providers/gpustack/media.rb +45 -0
  54. data/lib/ruby_llm/providers/gpustack/models.rb +44 -8
  55. data/lib/ruby_llm/providers/gpustack.rb +1 -0
  56. data/lib/ruby_llm/providers/ollama/media.rb +2 -6
  57. data/lib/ruby_llm/providers/ollama/models.rb +36 -0
  58. data/lib/ruby_llm/providers/ollama.rb +1 -0
  59. data/lib/ruby_llm/providers/openai/chat.rb +1 -1
  60. data/lib/ruby_llm/providers/openai/media.rb +4 -4
  61. data/lib/ruby_llm/providers/openai/tools.rb +11 -6
  62. data/lib/ruby_llm/providers/openai.rb +2 -2
  63. data/lib/ruby_llm/providers/vertexai/chat.rb +14 -0
  64. data/lib/ruby_llm/providers/vertexai/embeddings.rb +32 -0
  65. data/lib/ruby_llm/providers/vertexai/models.rb +130 -0
  66. data/lib/ruby_llm/providers/vertexai/streaming.rb +14 -0
  67. data/lib/ruby_llm/providers/vertexai.rb +55 -0
  68. data/lib/ruby_llm/railtie.rb +20 -3
  69. data/lib/ruby_llm/streaming.rb +1 -1
  70. data/lib/ruby_llm/utils.rb +5 -9
  71. data/lib/ruby_llm/version.rb +1 -1
  72. data/lib/ruby_llm.rb +4 -3
  73. data/lib/tasks/models.rake +39 -28
  74. data/lib/tasks/ruby_llm.rake +15 -0
  75. data/lib/tasks/vcr.rake +2 -2
  76. metadata +38 -3
  77. data/lib/generators/ruby_llm/install/templates/INSTALL_INFO.md.tt +0 -108
  78. data/lib/generators/ruby_llm/install_generator.rb +0 -121
@@ -0,0 +1,336 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module ActiveRecord
5
+ # Methods mixed into chat models.
6
+ module ChatMethods
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ before_save :resolve_model_from_strings
11
+ end
12
+
13
+ attr_accessor :assume_model_exists, :context
14
+
15
+ def model=(value)
16
+ @model_string = value if value.is_a?(String)
17
+ return if value.is_a?(String)
18
+
19
+ if self.class.model_association_name == :model
20
+ super
21
+ else
22
+ self.model_association = value
23
+ end
24
+ end
25
+
26
+ def model_id=(value)
27
+ @model_string = value
28
+ end
29
+
30
+ def model_id
31
+ model_association&.model_id
32
+ end
33
+
34
+ def provider=(value)
35
+ @provider_string = value
36
+ end
37
+
38
+ def provider
39
+ model_association&.provider
40
+ end
41
+
42
+ private
43
+
44
+ def resolve_model_from_strings # rubocop:disable Metrics/PerceivedComplexity
45
+ config = context&.config || RubyLLM.config
46
+ @model_string ||= config.default_model unless model_association
47
+ return unless @model_string
48
+
49
+ model_info, _provider = Models.resolve(
50
+ @model_string,
51
+ provider: @provider_string,
52
+ assume_exists: assume_model_exists || false,
53
+ config: config
54
+ )
55
+
56
+ model_class = self.class.model_class.constantize
57
+ model_record = model_class.find_or_create_by!(
58
+ model_id: model_info.id,
59
+ provider: model_info.provider
60
+ ) do |m|
61
+ m.name = model_info.name || model_info.id
62
+ m.family = model_info.family
63
+ m.context_window = model_info.context_window
64
+ m.max_output_tokens = model_info.max_output_tokens
65
+ m.capabilities = model_info.capabilities || []
66
+ m.modalities = model_info.modalities || {}
67
+ m.pricing = model_info.pricing || {}
68
+ m.metadata = model_info.metadata || {}
69
+ end
70
+
71
+ self.model_association = model_record
72
+ @model_string = nil
73
+ @provider_string = nil
74
+ end
75
+
76
+ public
77
+
78
+ def to_llm
79
+ model_record = model_association
80
+ @chat ||= (context || RubyLLM).chat(
81
+ model: model_record.model_id,
82
+ provider: model_record.provider.to_sym
83
+ )
84
+ @chat.reset_messages!
85
+
86
+ messages_association.each do |msg|
87
+ @chat.add_message(msg.to_llm)
88
+ end
89
+
90
+ setup_persistence_callbacks
91
+ end
92
+
93
+ def with_instructions(instructions, replace: false)
94
+ transaction do
95
+ messages_association.where(role: :system).destroy_all if replace
96
+ messages_association.create!(role: :system, content: instructions)
97
+ end
98
+ to_llm.with_instructions(instructions)
99
+ self
100
+ end
101
+
102
+ def with_tool(...)
103
+ to_llm.with_tool(...)
104
+ self
105
+ end
106
+
107
+ def with_tools(...)
108
+ to_llm.with_tools(...)
109
+ self
110
+ end
111
+
112
+ def with_model(model_name, provider: nil, assume_exists: false)
113
+ self.model = model_name
114
+ self.provider = provider if provider
115
+ self.assume_model_exists = assume_exists
116
+ resolve_model_from_strings
117
+ save!
118
+ to_llm.with_model(model.model_id, provider: model.provider.to_sym, assume_exists:)
119
+ self
120
+ end
121
+
122
+ def with_temperature(...)
123
+ to_llm.with_temperature(...)
124
+ self
125
+ end
126
+
127
+ def with_params(...)
128
+ to_llm.with_params(...)
129
+ self
130
+ end
131
+
132
+ def with_headers(...)
133
+ to_llm.with_headers(...)
134
+ self
135
+ end
136
+
137
+ def with_schema(...)
138
+ to_llm.with_schema(...)
139
+ self
140
+ end
141
+
142
+ def on_new_message(&block)
143
+ to_llm
144
+
145
+ existing_callback = @chat.instance_variable_get(:@on)[:new_message]
146
+
147
+ @chat.on_new_message do
148
+ existing_callback&.call
149
+ block&.call
150
+ end
151
+ self
152
+ end
153
+
154
+ def on_end_message(&block)
155
+ to_llm
156
+
157
+ existing_callback = @chat.instance_variable_get(:@on)[:end_message]
158
+
159
+ @chat.on_end_message do |msg|
160
+ existing_callback&.call(msg)
161
+ block&.call(msg)
162
+ end
163
+ self
164
+ end
165
+
166
+ def on_tool_call(...)
167
+ to_llm.on_tool_call(...)
168
+ self
169
+ end
170
+
171
+ def on_tool_result(...)
172
+ to_llm.on_tool_result(...)
173
+ self
174
+ end
175
+
176
+ def create_user_message(content, with: nil)
177
+ message_record = messages_association.create!(role: :user, content: content)
178
+ persist_content(message_record, with) if with.present?
179
+ message_record
180
+ end
181
+
182
+ def ask(message, with: nil, &)
183
+ create_user_message(message, with:)
184
+ complete(&)
185
+ end
186
+
187
+ alias say ask
188
+
189
+ def complete(...)
190
+ to_llm.complete(...)
191
+ rescue RubyLLM::Error => e
192
+ cleanup_failed_messages if @message&.persisted? && @message.content.blank?
193
+ cleanup_orphaned_tool_results
194
+ raise e
195
+ end
196
+
197
+ private
198
+
199
+ def cleanup_failed_messages
200
+ RubyLLM.logger.warn "RubyLLM: API call failed, destroying message: #{@message.id}"
201
+ @message.destroy
202
+ end
203
+
204
+ def cleanup_orphaned_tool_results # rubocop:disable Metrics/PerceivedComplexity
205
+ messages_association.reload
206
+ last = messages_association.order(:id).last
207
+
208
+ return unless last&.tool_call? || last&.tool_result?
209
+
210
+ if last.tool_call?
211
+ last.destroy
212
+ elsif last.tool_result?
213
+ tool_call_message = last.parent_tool_call.message
214
+ expected_results = tool_call_message.tool_calls.pluck(:id)
215
+ actual_results = tool_call_message.tool_results.pluck(:tool_call_id)
216
+
217
+ if expected_results.sort != actual_results.sort
218
+ tool_call_message.tool_results.each(&:destroy)
219
+ tool_call_message.destroy
220
+ end
221
+ end
222
+ end
223
+
224
+ def setup_persistence_callbacks
225
+ return @chat if @chat.instance_variable_get(:@_persistence_callbacks_setup)
226
+
227
+ @chat.on_new_message { persist_new_message }
228
+ @chat.on_end_message { |msg| persist_message_completion(msg) }
229
+
230
+ @chat.instance_variable_set(:@_persistence_callbacks_setup, true)
231
+ @chat
232
+ end
233
+
234
+ def persist_new_message
235
+ @message = messages_association.create!(role: :assistant, content: '')
236
+ end
237
+
238
+ def persist_message_completion(message) # rubocop:disable Metrics/PerceivedComplexity
239
+ return unless message
240
+
241
+ tool_call_id = find_tool_call_id(message.tool_call_id) if message.tool_call_id
242
+
243
+ transaction do
244
+ content = message.content
245
+ attachments_to_persist = nil
246
+
247
+ if content.is_a?(RubyLLM::Content)
248
+ attachments_to_persist = content.attachments if content.attachments.any?
249
+ content = content.text
250
+ elsif content.is_a?(Hash) || content.is_a?(Array)
251
+ content = content.to_json
252
+ end
253
+
254
+ attrs = {
255
+ role: message.role,
256
+ content: content,
257
+ input_tokens: message.input_tokens,
258
+ output_tokens: message.output_tokens
259
+ }
260
+
261
+ # Add model association dynamically
262
+ attrs[self.class.model_association_name] = model_association
263
+
264
+ if tool_call_id
265
+ parent_tool_call_assoc = @message.class.reflect_on_association(:parent_tool_call)
266
+ attrs[parent_tool_call_assoc.foreign_key] = tool_call_id
267
+ end
268
+
269
+ @message.update!(attrs)
270
+
271
+ persist_content(@message, attachments_to_persist) if attachments_to_persist
272
+ persist_tool_calls(message.tool_calls) if message.tool_calls.present?
273
+ end
274
+ end
275
+
276
+ def persist_tool_calls(tool_calls)
277
+ tool_calls.each_value do |tool_call|
278
+ attributes = tool_call.to_h
279
+ attributes[:tool_call_id] = attributes.delete(:id)
280
+ @message.tool_calls_association.create!(**attributes)
281
+ end
282
+ end
283
+
284
+ def find_tool_call_id(tool_call_id)
285
+ messages = messages_association
286
+ message_class = messages.klass
287
+ tool_calls_assoc = message_class.tool_calls_association_name
288
+ tool_call_table_name = message_class.reflect_on_association(tool_calls_assoc).table_name
289
+
290
+ message_with_tool_call = messages.joins(tool_calls_assoc)
291
+ .find_by(tool_call_table_name => { tool_call_id: tool_call_id })
292
+ return nil unless message_with_tool_call
293
+
294
+ tool_call = message_with_tool_call.tool_calls_association.find_by(tool_call_id: tool_call_id)
295
+ tool_call&.id
296
+ end
297
+
298
+ def persist_content(message_record, attachments)
299
+ return unless message_record.respond_to?(:attachments)
300
+
301
+ attachables = prepare_for_active_storage(attachments)
302
+ message_record.attachments.attach(attachables) if attachables.any?
303
+ end
304
+
305
+ def prepare_for_active_storage(attachments)
306
+ Utils.to_safe_array(attachments).filter_map do |attachment|
307
+ case attachment
308
+ when ActionDispatch::Http::UploadedFile, ActiveStorage::Blob
309
+ attachment
310
+ when ActiveStorage::Attached::One, ActiveStorage::Attached::Many
311
+ attachment.blobs
312
+ when Hash
313
+ attachment.values.map { |v| prepare_for_active_storage(v) }
314
+ else
315
+ convert_to_active_storage_format(attachment)
316
+ end
317
+ end.flatten.compact
318
+ end
319
+
320
+ def convert_to_active_storage_format(source)
321
+ return if source.blank?
322
+
323
+ attachment = source.is_a?(RubyLLM::Attachment) ? source : RubyLLM::Attachment.new(source)
324
+
325
+ {
326
+ io: StringIO.new(attachment.content),
327
+ filename: attachment.filename,
328
+ content_type: attachment.mime_type
329
+ }
330
+ rescue StandardError => e
331
+ RubyLLM.logger.warn "Failed to process attachment #{source}: #{e.message}"
332
+ nil
333
+ end
334
+ end
335
+ end
336
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module ActiveRecord
5
+ # Methods mixed into message models.
6
+ module MessageMethods
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ attr_reader :chat_class, :tool_call_class, :chat_foreign_key, :tool_call_foreign_key
11
+ end
12
+
13
+ def to_llm
14
+ RubyLLM::Message.new(
15
+ role: role.to_sym,
16
+ content: extract_content,
17
+ tool_calls: extract_tool_calls,
18
+ tool_call_id: extract_tool_call_id,
19
+ input_tokens: input_tokens,
20
+ output_tokens: output_tokens,
21
+ model_id: model_association&.model_id
22
+ )
23
+ end
24
+
25
+ private
26
+
27
+ def extract_tool_calls
28
+ tool_calls_association.to_h do |tool_call|
29
+ [
30
+ tool_call.tool_call_id,
31
+ RubyLLM::ToolCall.new(
32
+ id: tool_call.tool_call_id,
33
+ name: tool_call.name,
34
+ arguments: tool_call.arguments
35
+ )
36
+ ]
37
+ end
38
+ end
39
+
40
+ def extract_tool_call_id
41
+ parent_tool_call&.tool_call_id
42
+ end
43
+
44
+ def extract_content
45
+ return content unless respond_to?(:attachments) && attachments.attached?
46
+
47
+ RubyLLM::Content.new(content).tap do |content_obj|
48
+ @_tempfiles = []
49
+
50
+ attachments.each do |attachment|
51
+ tempfile = download_attachment(attachment)
52
+ content_obj.add_attachment(tempfile, filename: attachment.filename.to_s)
53
+ end
54
+ end
55
+ end
56
+
57
+ def download_attachment(attachment)
58
+ ext = File.extname(attachment.filename.to_s)
59
+ basename = File.basename(attachment.filename.to_s, ext)
60
+ tempfile = Tempfile.new([basename, ext])
61
+ tempfile.binmode
62
+
63
+ attachment.download { |chunk| tempfile.write(chunk) }
64
+
65
+ tempfile.flush
66
+ tempfile.rewind
67
+ @_tempfiles << tempfile
68
+ tempfile
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module ActiveRecord
5
+ # Methods mixed into model registry models.
6
+ module ModelMethods
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do # rubocop:disable Metrics/BlockLength
10
+ def refresh!
11
+ RubyLLM.models.refresh!
12
+
13
+ transaction do
14
+ RubyLLM.models.all.each do |model_info|
15
+ model = find_or_initialize_by(
16
+ model_id: model_info.id,
17
+ provider: model_info.provider
18
+ )
19
+ model.update!(from_llm_attributes(model_info))
20
+ end
21
+ end
22
+ end
23
+
24
+ def save_to_database
25
+ transaction do
26
+ RubyLLM.models.all.each do |model_info|
27
+ model = find_or_initialize_by(
28
+ model_id: model_info.id,
29
+ provider: model_info.provider
30
+ )
31
+ model.update!(from_llm_attributes(model_info))
32
+ end
33
+ end
34
+ end
35
+
36
+ def from_llm(model_info)
37
+ new(from_llm_attributes(model_info))
38
+ end
39
+
40
+ private
41
+
42
+ def from_llm_attributes(model_info)
43
+ {
44
+ model_id: model_info.id,
45
+ name: model_info.name,
46
+ provider: model_info.provider,
47
+ family: model_info.family,
48
+ model_created_at: model_info.created_at,
49
+ context_window: model_info.context_window,
50
+ max_output_tokens: model_info.max_output_tokens,
51
+ knowledge_cutoff: model_info.knowledge_cutoff,
52
+ modalities: model_info.modalities.to_h,
53
+ capabilities: model_info.capabilities,
54
+ pricing: model_info.pricing.to_h,
55
+ metadata: model_info.metadata
56
+ }
57
+ end
58
+ end
59
+
60
+ def to_llm
61
+ RubyLLM::Model::Info.new(
62
+ id: model_id,
63
+ name: name,
64
+ provider: provider,
65
+ family: family,
66
+ created_at: model_created_at,
67
+ context_window: context_window,
68
+ max_output_tokens: max_output_tokens,
69
+ knowledge_cutoff: knowledge_cutoff,
70
+ modalities: modalities&.deep_symbolize_keys || {},
71
+ capabilities: capabilities,
72
+ pricing: pricing&.deep_symbolize_keys || {},
73
+ metadata: metadata&.deep_symbolize_keys || {}
74
+ )
75
+ end
76
+
77
+ delegate :supports?, :supports_vision?, :supports_functions?, :type,
78
+ :input_price_per_million, :output_price_per_million,
79
+ :function_calling?, :structured_output?, :batch?,
80
+ :reasoning?, :citations?, :streaming?,
81
+ to: :to_llm
82
+ end
83
+ end
84
+ end
@@ -50,21 +50,57 @@
50
50
  "deepseek": "deepseek-chat",
51
51
  "openrouter": "deepseek/deepseek-chat"
52
52
  },
53
+ "gemini-1.5-flash": {
54
+ "gemini": "gemini-1.5-flash",
55
+ "vertexai": "gemini-1.5-flash"
56
+ },
57
+ "gemini-1.5-flash-002": {
58
+ "gemini": "gemini-1.5-flash-002",
59
+ "vertexai": "gemini-1.5-flash-002"
60
+ },
61
+ "gemini-1.5-flash-8b": {
62
+ "gemini": "gemini-1.5-flash-8b",
63
+ "vertexai": "gemini-1.5-flash-8b"
64
+ },
65
+ "gemini-1.5-pro": {
66
+ "gemini": "gemini-1.5-pro",
67
+ "vertexai": "gemini-1.5-pro"
68
+ },
69
+ "gemini-1.5-pro-002": {
70
+ "gemini": "gemini-1.5-pro-002",
71
+ "vertexai": "gemini-1.5-pro-002"
72
+ },
73
+ "gemini-2.0-flash": {
74
+ "gemini": "gemini-2.0-flash",
75
+ "vertexai": "gemini-2.0-flash"
76
+ },
53
77
  "gemini-2.0-flash-001": {
54
78
  "gemini": "gemini-2.0-flash-001",
55
- "openrouter": "google/gemini-2.0-flash-001"
79
+ "openrouter": "google/gemini-2.0-flash-001",
80
+ "vertexai": "gemini-2.0-flash-001"
81
+ },
82
+ "gemini-2.0-flash-exp": {
83
+ "gemini": "gemini-2.0-flash-exp",
84
+ "vertexai": "gemini-2.0-flash-exp"
56
85
  },
57
86
  "gemini-2.0-flash-lite-001": {
58
87
  "gemini": "gemini-2.0-flash-lite-001",
59
- "openrouter": "google/gemini-2.0-flash-lite-001"
88
+ "openrouter": "google/gemini-2.0-flash-lite-001",
89
+ "vertexai": "gemini-2.0-flash-lite-001"
60
90
  },
61
91
  "gemini-2.5-flash": {
62
92
  "gemini": "gemini-2.5-flash",
63
- "openrouter": "google/gemini-2.5-flash"
93
+ "openrouter": "google/gemini-2.5-flash",
94
+ "vertexai": "gemini-2.5-flash"
95
+ },
96
+ "gemini-2.5-flash-image-preview": {
97
+ "gemini": "gemini-2.5-flash-image-preview",
98
+ "openrouter": "google/gemini-2.5-flash-image-preview"
64
99
  },
65
100
  "gemini-2.5-flash-lite": {
66
101
  "gemini": "gemini-2.5-flash-lite",
67
- "openrouter": "google/gemini-2.5-flash-lite"
102
+ "openrouter": "google/gemini-2.5-flash-lite",
103
+ "vertexai": "gemini-2.5-flash-lite"
68
104
  },
69
105
  "gemini-2.5-flash-lite-preview-06-17": {
70
106
  "gemini": "gemini-2.5-flash-lite-preview-06-17",
@@ -72,12 +108,21 @@
72
108
  },
73
109
  "gemini-2.5-pro": {
74
110
  "gemini": "gemini-2.5-pro",
75
- "openrouter": "google/gemini-2.5-pro"
111
+ "openrouter": "google/gemini-2.5-pro",
112
+ "vertexai": "gemini-2.5-pro"
76
113
  },
77
114
  "gemini-2.5-pro-preview-05-06": {
78
115
  "gemini": "gemini-2.5-pro-preview-05-06",
79
116
  "openrouter": "google/gemini-2.5-pro-preview-05-06"
80
117
  },
118
+ "gemini-embedding-001": {
119
+ "gemini": "gemini-embedding-001",
120
+ "vertexai": "gemini-embedding-001"
121
+ },
122
+ "gemini-exp-1206": {
123
+ "gemini": "gemini-exp-1206",
124
+ "vertexai": "gemini-exp-1206"
125
+ },
81
126
  "gemma-3-12b-it": {
82
127
  "gemini": "gemma-3-12b-it",
83
128
  "openrouter": "google/gemma-3-12b-it"
@@ -182,14 +227,6 @@
182
227
  "openai": "gpt-5-nano",
183
228
  "openrouter": "openai/gpt-5-nano"
184
229
  },
185
- "gpt-oss-120b": {
186
- "openai": "gpt-oss-120b",
187
- "openrouter": "openai/gpt-oss-120b"
188
- },
189
- "gpt-oss-20b": {
190
- "openai": "gpt-oss-20b",
191
- "openrouter": "openai/gpt-oss-20b"
192
- },
193
230
  "o1": {
194
231
  "openai": "o1",
195
232
  "openrouter": "openai/o1"
@@ -221,5 +258,9 @@
221
258
  "o4-mini": {
222
259
  "openai": "o4-mini",
223
260
  "openrouter": "openai/o4-mini"
261
+ },
262
+ "text-embedding-004": {
263
+ "gemini": "text-embedding-004",
264
+ "vertexai": "text-embedding-004"
224
265
  }
225
266
  }
@@ -65,6 +65,15 @@ module RubyLLM
65
65
  Base64.strict_encode64(content)
66
66
  end
67
67
 
68
+ def for_llm
69
+ case type
70
+ when :text
71
+ "<file name='#{filename}' mime_type='#{mime_type}'>#{content}</file>"
72
+ else
73
+ "data:#{mime_type};base64,#{encoded}"
74
+ end
75
+ end
76
+
68
77
  def type
69
78
  return :image if image?
70
79
  return :audio if audio?
@@ -82,6 +91,17 @@ module RubyLLM
82
91
  RubyLLM::MimeType.audio? mime_type
83
92
  end
84
93
 
94
+ def format
95
+ case mime_type
96
+ when 'audio/mpeg'
97
+ 'mp3'
98
+ when 'audio/wav', 'audio/wave', 'audio/x-wav'
99
+ 'wav'
100
+ else
101
+ mime_type.split('/').last
102
+ end
103
+ end
104
+
85
105
  def pdf?
86
106
  RubyLLM::MimeType.pdf? mime_type
87
107
  end
data/lib/ruby_llm/chat.rb CHANGED
@@ -126,7 +126,7 @@ module RubyLLM
126
126
  messages,
127
127
  tools: @tools,
128
128
  temperature: @temperature,
129
- model: @model.id,
129
+ model: @model,
130
130
  params: @params,
131
131
  headers: @headers,
132
132
  schema: @schema,
@@ -163,6 +163,10 @@ module RubyLLM
163
163
  @messages.clear
164
164
  end
165
165
 
166
+ def instance_variables
167
+ super - %i[@connection @config]
168
+ end
169
+
166
170
  private
167
171
 
168
172
  def wrap_streaming_block(&block)
@@ -204,9 +208,5 @@ module RubyLLM
204
208
  args = tool_call.arguments
205
209
  tool.call(args)
206
210
  end
207
-
208
- def instance_variables
209
- super - %i[@connection @config]
210
- end
211
211
  end
212
212
  end
@@ -10,6 +10,8 @@ module RubyLLM
10
10
  :openai_use_system_role,
11
11
  :anthropic_api_key,
12
12
  :gemini_api_key,
13
+ :vertexai_project_id,
14
+ :vertexai_location,
13
15
  :deepseek_api_key,
14
16
  :perplexity_api_key,
15
17
  :bedrock_api_key,
@@ -25,6 +27,10 @@ module RubyLLM
25
27
  :default_model,
26
28
  :default_embedding_model,
27
29
  :default_image_model,
30
+ # Model registry
31
+ :model_registry_class,
32
+ # Rails integration
33
+ :use_new_acts_as,
28
34
  # Connection configuration
29
35
  :request_timeout,
30
36
  :max_retries,
@@ -50,6 +56,9 @@ module RubyLLM
50
56
  @default_embedding_model = 'text-embedding-3-small'
51
57
  @default_image_model = 'gpt-image-1'
52
58
 
59
+ @model_registry_class = 'Model'
60
+ @use_new_acts_as = false
61
+
53
62
  @log_file = $stdout
54
63
  @log_level = ENV['RUBYLLM_DEBUG'] ? Logger::DEBUG : Logger::INFO
55
64
  @log_stream_debug = ENV['RUBYLLM_STREAM_DEBUG'] == 'true'