ruby_llm 1.6.3 → 1.7.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.
- checksums.yaml +4 -4
- data/README.md +6 -3
- data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +115 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +39 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +24 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +12 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +16 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +29 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +16 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +11 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +23 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +21 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +10 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +9 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +16 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +30 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +18 -0
- data/lib/generators/ruby_llm/install/templates/chat_model.rb.tt +2 -2
- data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +4 -4
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +8 -7
- data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +43 -0
- data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +6 -5
- data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +10 -4
- data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +4 -3
- data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +3 -0
- data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +2 -2
- data/lib/generators/ruby_llm/install_generator.rb +129 -33
- data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +137 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_7_generator.rb +160 -0
- data/lib/ruby_llm/active_record/acts_as.rb +112 -319
- data/lib/ruby_llm/active_record/acts_as_legacy.rb +398 -0
- data/lib/ruby_llm/active_record/chat_methods.rb +336 -0
- data/lib/ruby_llm/active_record/message_methods.rb +72 -0
- data/lib/ruby_llm/active_record/model_methods.rb +84 -0
- data/lib/ruby_llm/aliases.json +58 -13
- data/lib/ruby_llm/attachment.rb +20 -0
- data/lib/ruby_llm/chat.rb +8 -7
- data/lib/ruby_llm/configuration.rb +9 -0
- data/lib/ruby_llm/connection.rb +4 -4
- data/lib/ruby_llm/model/info.rb +12 -0
- data/lib/ruby_llm/models.json +3579 -2029
- data/lib/ruby_llm/models.rb +51 -22
- data/lib/ruby_llm/provider.rb +3 -3
- data/lib/ruby_llm/providers/anthropic/chat.rb +2 -2
- data/lib/ruby_llm/providers/anthropic/media.rb +1 -1
- data/lib/ruby_llm/providers/anthropic/tools.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/chat.rb +2 -2
- data/lib/ruby_llm/providers/bedrock/models.rb +19 -1
- data/lib/ruby_llm/providers/gemini/chat.rb +53 -25
- data/lib/ruby_llm/providers/gemini/media.rb +1 -1
- data/lib/ruby_llm/providers/gpustack/chat.rb +11 -0
- data/lib/ruby_llm/providers/gpustack/media.rb +45 -0
- data/lib/ruby_llm/providers/gpustack/models.rb +44 -8
- data/lib/ruby_llm/providers/gpustack.rb +1 -0
- data/lib/ruby_llm/providers/ollama/media.rb +2 -6
- data/lib/ruby_llm/providers/ollama/models.rb +36 -0
- data/lib/ruby_llm/providers/ollama.rb +1 -0
- data/lib/ruby_llm/providers/openai/chat.rb +1 -1
- data/lib/ruby_llm/providers/openai/media.rb +4 -4
- data/lib/ruby_llm/providers/openai/tools.rb +11 -6
- data/lib/ruby_llm/providers/openai.rb +2 -2
- data/lib/ruby_llm/providers/vertexai/chat.rb +14 -0
- data/lib/ruby_llm/providers/vertexai/embeddings.rb +32 -0
- data/lib/ruby_llm/providers/vertexai/models.rb +130 -0
- data/lib/ruby_llm/providers/vertexai/streaming.rb +14 -0
- data/lib/ruby_llm/providers/vertexai.rb +55 -0
- data/lib/ruby_llm/railtie.rb +20 -3
- data/lib/ruby_llm/streaming.rb +1 -1
- data/lib/ruby_llm/utils.rb +5 -9
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm.rb +4 -3
- data/lib/tasks/models.rake +525 -0
- data/lib/tasks/release.rake +37 -2
- data/lib/tasks/ruby_llm.rake +15 -0
- data/lib/tasks/vcr.rake +2 -2
- metadata +37 -5
- data/lib/generators/ruby_llm/install/templates/INSTALL_INFO.md.tt +0 -108
- data/lib/tasks/aliases.rake +0 -205
- data/lib/tasks/models_docs.rake +0 -214
- data/lib/tasks/models_update.rake +0 -108
@@ -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
|
data/lib/ruby_llm/aliases.json
CHANGED
@@ -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"
|
@@ -150,6 +195,10 @@
|
|
150
195
|
"openai": "gpt-4o-2024-11-20",
|
151
196
|
"openrouter": "openai/gpt-4o-2024-11-20"
|
152
197
|
},
|
198
|
+
"gpt-4o-audio-preview": {
|
199
|
+
"openai": "gpt-4o-audio-preview",
|
200
|
+
"openrouter": "openai/gpt-4o-audio-preview"
|
201
|
+
},
|
153
202
|
"gpt-4o-mini": {
|
154
203
|
"openai": "gpt-4o-mini",
|
155
204
|
"openrouter": "openai/gpt-4o-mini"
|
@@ -178,14 +227,6 @@
|
|
178
227
|
"openai": "gpt-5-nano",
|
179
228
|
"openrouter": "openai/gpt-5-nano"
|
180
229
|
},
|
181
|
-
"gpt-oss-120b": {
|
182
|
-
"openai": "gpt-oss-120b",
|
183
|
-
"openrouter": "openai/gpt-oss-120b"
|
184
|
-
},
|
185
|
-
"gpt-oss-20b": {
|
186
|
-
"openai": "gpt-oss-20b",
|
187
|
-
"openrouter": "openai/gpt-oss-20b"
|
188
|
-
},
|
189
230
|
"o1": {
|
190
231
|
"openai": "o1",
|
191
232
|
"openrouter": "openai/o1"
|
@@ -217,5 +258,9 @@
|
|
217
258
|
"o4-mini": {
|
218
259
|
"openai": "o4-mini",
|
219
260
|
"openrouter": "openai/o4-mini"
|
261
|
+
},
|
262
|
+
"text-embedding-004": {
|
263
|
+
"gemini": "text-embedding-004",
|
264
|
+
"vertexai": "text-embedding-004"
|
220
265
|
}
|
221
266
|
}
|
data/lib/ruby_llm/attachment.rb
CHANGED
@@ -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
|
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)
|
@@ -181,7 +185,7 @@ module RubyLLM
|
|
181
185
|
end
|
182
186
|
end
|
183
187
|
|
184
|
-
def handle_tool_calls(response, &)
|
188
|
+
def handle_tool_calls(response, &) # rubocop:disable Metrics/PerceivedComplexity
|
185
189
|
halt_result = nil
|
186
190
|
|
187
191
|
response.tool_calls.each_value do |tool_call|
|
@@ -189,7 +193,8 @@ module RubyLLM
|
|
189
193
|
@on[:tool_call]&.call(tool_call)
|
190
194
|
result = execute_tool tool_call
|
191
195
|
@on[:tool_result]&.call(result)
|
192
|
-
|
196
|
+
content = result.is_a?(Content) ? result : result.to_s
|
197
|
+
message = add_message role: :tool, content:, tool_call_id: tool_call.id
|
193
198
|
@on[:end_message]&.call(message)
|
194
199
|
|
195
200
|
halt_result = result if result.is_a?(Tool::Halt)
|
@@ -203,9 +208,5 @@ module RubyLLM
|
|
203
208
|
args = tool_call.arguments
|
204
209
|
tool.call(args)
|
205
210
|
end
|
206
|
-
|
207
|
-
def instance_variables
|
208
|
-
super - %i[@connection @config]
|
209
|
-
end
|
210
211
|
end
|
211
212
|
end
|