ruby_llm_community 0.0.5 → 1.0.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 +73 -91
- data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +34 -0
- data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +5 -0
- data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +6 -0
- data/lib/generators/ruby_llm/install_generator.rb +27 -2
- data/lib/ruby_llm/active_record/acts_as.rb +168 -24
- data/lib/ruby_llm/aliases.json +62 -5
- data/lib/ruby_llm/aliases.rb +7 -25
- data/lib/ruby_llm/chat.rb +10 -17
- data/lib/ruby_llm/configuration.rb +5 -12
- data/lib/ruby_llm/connection.rb +4 -4
- data/lib/ruby_llm/connection_multipart.rb +19 -0
- data/lib/ruby_llm/content.rb +5 -2
- data/lib/ruby_llm/embedding.rb +1 -2
- data/lib/ruby_llm/error.rb +0 -8
- data/lib/ruby_llm/image.rb +23 -8
- data/lib/ruby_llm/image_attachment.rb +21 -0
- data/lib/ruby_llm/message.rb +6 -6
- data/lib/ruby_llm/model/info.rb +12 -10
- data/lib/ruby_llm/model/pricing.rb +0 -3
- data/lib/ruby_llm/model/pricing_category.rb +0 -2
- data/lib/ruby_llm/model/pricing_tier.rb +0 -1
- data/lib/ruby_llm/models.json +2485 -676
- data/lib/ruby_llm/models.rb +65 -34
- data/lib/ruby_llm/provider.rb +8 -8
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +1 -46
- data/lib/ruby_llm/providers/anthropic/chat.rb +2 -2
- data/lib/ruby_llm/providers/anthropic/media.rb +0 -1
- data/lib/ruby_llm/providers/anthropic/tools.rb +1 -2
- data/lib/ruby_llm/providers/anthropic.rb +1 -2
- data/lib/ruby_llm/providers/bedrock/chat.rb +2 -4
- data/lib/ruby_llm/providers/bedrock/media.rb +0 -1
- data/lib/ruby_llm/providers/bedrock/models.rb +0 -2
- data/lib/ruby_llm/providers/bedrock/streaming/base.rb +0 -12
- data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +0 -7
- data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +0 -12
- data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +0 -12
- data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +0 -13
- data/lib/ruby_llm/providers/bedrock/streaming.rb +0 -18
- data/lib/ruby_llm/providers/bedrock.rb +1 -2
- data/lib/ruby_llm/providers/deepseek/capabilities.rb +1 -2
- data/lib/ruby_llm/providers/deepseek/chat.rb +0 -1
- data/lib/ruby_llm/providers/gemini/capabilities.rb +28 -100
- data/lib/ruby_llm/providers/gemini/chat.rb +57 -29
- data/lib/ruby_llm/providers/gemini/embeddings.rb +0 -2
- data/lib/ruby_llm/providers/gemini/images.rb +1 -2
- data/lib/ruby_llm/providers/gemini/media.rb +0 -1
- data/lib/ruby_llm/providers/gemini/models.rb +1 -2
- data/lib/ruby_llm/providers/gemini/streaming.rb +15 -1
- data/lib/ruby_llm/providers/gemini/tools.rb +0 -5
- data/lib/ruby_llm/providers/gpustack/chat.rb +11 -1
- data/lib/ruby_llm/providers/gpustack/media.rb +45 -0
- data/lib/ruby_llm/providers/gpustack/models.rb +44 -9
- data/lib/ruby_llm/providers/gpustack.rb +1 -0
- data/lib/ruby_llm/providers/mistral/capabilities.rb +2 -10
- data/lib/ruby_llm/providers/mistral/chat.rb +0 -2
- data/lib/ruby_llm/providers/mistral/embeddings.rb +0 -3
- data/lib/ruby_llm/providers/mistral/models.rb +0 -1
- data/lib/ruby_llm/providers/ollama/chat.rb +0 -1
- data/lib/ruby_llm/providers/ollama/media.rb +1 -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/capabilities.rb +3 -16
- data/lib/ruby_llm/providers/openai/chat.rb +1 -3
- data/lib/ruby_llm/providers/openai/embeddings.rb +0 -3
- data/lib/ruby_llm/providers/openai/images.rb +73 -3
- data/lib/ruby_llm/providers/openai/media.rb +0 -1
- data/lib/ruby_llm/providers/openai/response.rb +120 -29
- data/lib/ruby_llm/providers/openai/response_media.rb +2 -2
- data/lib/ruby_llm/providers/openai/streaming.rb +107 -47
- data/lib/ruby_llm/providers/openai/tools.rb +1 -1
- data/lib/ruby_llm/providers/openai.rb +1 -3
- data/lib/ruby_llm/providers/openai_base.rb +2 -2
- data/lib/ruby_llm/providers/openrouter/models.rb +1 -16
- data/lib/ruby_llm/providers/perplexity/capabilities.rb +0 -1
- data/lib/ruby_llm/providers/perplexity/chat.rb +0 -1
- data/lib/ruby_llm/providers/perplexity.rb +1 -5
- 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 +0 -1
- data/lib/ruby_llm/stream_accumulator.rb +72 -10
- data/lib/ruby_llm/streaming.rb +16 -25
- data/lib/ruby_llm/tool.rb +2 -19
- data/lib/ruby_llm/tool_call.rb +0 -9
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm_community.rb +5 -3
- data/lib/tasks/models.rake +525 -0
- data/lib/tasks/release.rake +37 -2
- data/lib/tasks/vcr.rake +0 -7
- metadata +13 -4
- data/lib/tasks/aliases.rake +0 -235
- data/lib/tasks/models_docs.rake +0 -224
- data/lib/tasks/models_update.rake +0 -108
@@ -3,17 +3,19 @@
|
|
3
3
|
module RubyLLM
|
4
4
|
module ActiveRecord
|
5
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
6
|
module ActsAs
|
9
7
|
extend ActiveSupport::Concern
|
10
8
|
|
11
9
|
class_methods do # rubocop:disable Metrics/BlockLength
|
12
|
-
def acts_as_chat(message_class: 'Message', tool_call_class: 'ToolCall'
|
10
|
+
def acts_as_chat(message_class: 'Message', tool_call_class: 'ToolCall',
|
11
|
+
model_class: 'Model', model_foreign_key: nil, model_primary_key: nil)
|
13
12
|
include ChatMethods
|
14
13
|
|
15
14
|
@message_class = message_class.to_s
|
16
15
|
@tool_call_class = tool_call_class.to_s
|
16
|
+
@model_class = model_class.to_s
|
17
|
+
@model_foreign_key = model_foreign_key || ActiveSupport::Inflector.foreign_key(@model_class)
|
18
|
+
@model_primary_key = model_primary_key || 'model_id'
|
17
19
|
|
18
20
|
has_many :messages,
|
19
21
|
-> { order(created_at: :asc) },
|
@@ -21,13 +23,43 @@ module RubyLLM
|
|
21
23
|
inverse_of: :chat,
|
22
24
|
dependent: :destroy
|
23
25
|
|
26
|
+
# Set up model association if model registry is configured
|
27
|
+
if RubyLLM.config.model_registry_class
|
28
|
+
belongs_to :model,
|
29
|
+
class_name: @model_class,
|
30
|
+
foreign_key: @model_foreign_key,
|
31
|
+
primary_key: @model_primary_key,
|
32
|
+
optional: true
|
33
|
+
end
|
34
|
+
|
24
35
|
delegate :add_message, to: :to_llm
|
25
36
|
end
|
26
37
|
|
27
|
-
def
|
38
|
+
def acts_as_model(chat_class: 'Chat')
|
39
|
+
include ModelMethods
|
40
|
+
|
41
|
+
@chat_class = chat_class.to_s
|
42
|
+
|
43
|
+
validates :model_id, presence: true, uniqueness: { scope: :provider }
|
44
|
+
validates :name, presence: true
|
45
|
+
validates :provider, presence: true
|
46
|
+
|
47
|
+
# Set up chat association if configured
|
48
|
+
return unless RubyLLM.config.model_registry_class
|
49
|
+
|
50
|
+
has_many :chats,
|
51
|
+
class_name: @chat_class,
|
52
|
+
foreign_key: 'model_id',
|
53
|
+
primary_key: 'model_id'
|
54
|
+
end
|
55
|
+
|
56
|
+
def acts_as_message(chat_class: 'Chat', # rubocop:disable Metrics/ParameterLists
|
28
57
|
chat_foreign_key: nil,
|
29
58
|
tool_call_class: 'ToolCall',
|
30
59
|
tool_call_foreign_key: nil,
|
60
|
+
model_class: 'Model',
|
61
|
+
model_foreign_key: nil,
|
62
|
+
model_primary_key: nil,
|
31
63
|
touch_chat: false)
|
32
64
|
include MessageMethods
|
33
65
|
|
@@ -37,6 +69,10 @@ module RubyLLM
|
|
37
69
|
@tool_call_class = tool_call_class.to_s
|
38
70
|
@tool_call_foreign_key = tool_call_foreign_key || ActiveSupport::Inflector.foreign_key(@tool_call_class)
|
39
71
|
|
72
|
+
@model_class = model_class.to_s
|
73
|
+
@model_foreign_key = model_foreign_key || ActiveSupport::Inflector.foreign_key(@model_class)
|
74
|
+
@model_primary_key = model_primary_key || 'model_id'
|
75
|
+
|
40
76
|
belongs_to :chat,
|
41
77
|
class_name: @chat_class,
|
42
78
|
foreign_key: @chat_foreign_key,
|
@@ -53,7 +89,21 @@ module RubyLLM
|
|
53
89
|
optional: true,
|
54
90
|
inverse_of: :result
|
55
91
|
|
56
|
-
|
92
|
+
has_many :tool_results,
|
93
|
+
through: :tool_calls,
|
94
|
+
source: :result,
|
95
|
+
class_name: @message_class
|
96
|
+
|
97
|
+
# Set up model association if model registry is configured
|
98
|
+
if RubyLLM.config.model_registry_class
|
99
|
+
belongs_to :model,
|
100
|
+
class_name: @model_class,
|
101
|
+
foreign_key: @model_foreign_key,
|
102
|
+
primary_key: @model_primary_key,
|
103
|
+
optional: true
|
104
|
+
end
|
105
|
+
|
106
|
+
delegate :tool_call?, :tool_result?, to: :to_llm
|
57
107
|
end
|
58
108
|
|
59
109
|
def acts_as_tool_call(message_class: 'Message', message_foreign_key: nil, result_foreign_key: nil)
|
@@ -75,8 +125,7 @@ module RubyLLM
|
|
75
125
|
end
|
76
126
|
end
|
77
127
|
|
78
|
-
# Methods mixed into chat models
|
79
|
-
# provide a conversation interface.
|
128
|
+
# Methods mixed into chat models.
|
80
129
|
module ChatMethods
|
81
130
|
extend ActiveSupport::Concern
|
82
131
|
|
@@ -85,10 +134,20 @@ module RubyLLM
|
|
85
134
|
end
|
86
135
|
|
87
136
|
def to_llm(context: nil)
|
137
|
+
# If we have a model association, use both model_id and provider
|
138
|
+
# Otherwise, model_id is a string that RubyLLM can resolve
|
139
|
+
if respond_to?(:model) && model
|
140
|
+
model_to_use = model.model_id
|
141
|
+
provider_to_use = model.provider.to_sym
|
142
|
+
else
|
143
|
+
model_to_use = model_id
|
144
|
+
provider_to_use = nil
|
145
|
+
end
|
146
|
+
|
88
147
|
@chat ||= if context
|
89
|
-
context.chat(model:
|
148
|
+
context.chat(model: model_to_use, provider: provider_to_use)
|
90
149
|
else
|
91
|
-
RubyLLM.chat(model:
|
150
|
+
RubyLLM.chat(model: model_to_use, provider: provider_to_use)
|
92
151
|
end
|
93
152
|
@chat.reset_messages!
|
94
153
|
|
@@ -148,6 +207,11 @@ module RubyLLM
|
|
148
207
|
self
|
149
208
|
end
|
150
209
|
|
210
|
+
def cache_prompts(...)
|
211
|
+
to_llm.cache_prompts(...)
|
212
|
+
self
|
213
|
+
end
|
214
|
+
|
151
215
|
def on_new_message(&block)
|
152
216
|
to_llm
|
153
217
|
|
@@ -206,26 +270,33 @@ module RubyLLM
|
|
206
270
|
private
|
207
271
|
|
208
272
|
def cleanup_failed_messages
|
209
|
-
RubyLLM.logger.
|
273
|
+
RubyLLM.logger.warn "RubyLLM: API call failed, destroying message: #{@message.id}"
|
210
274
|
@message.destroy
|
211
275
|
end
|
212
276
|
|
213
|
-
def cleanup_orphaned_tool_results
|
214
|
-
|
215
|
-
|
216
|
-
last = messages.order(:id).last
|
277
|
+
def cleanup_orphaned_tool_results # rubocop:disable Metrics/PerceivedComplexity
|
278
|
+
messages.reload
|
279
|
+
last = messages.order(:id).last
|
217
280
|
|
218
|
-
|
281
|
+
return unless last&.tool_call? || last&.tool_result?
|
219
282
|
|
283
|
+
if last.tool_call?
|
220
284
|
last.destroy
|
285
|
+
elsif last.tool_result?
|
286
|
+
tool_call_message = last.parent_tool_call.message
|
287
|
+
expected_results = tool_call_message.tool_calls.pluck(:id)
|
288
|
+
actual_results = tool_call_message.tool_results.pluck(:tool_call_id)
|
289
|
+
|
290
|
+
if expected_results.sort != actual_results.sort
|
291
|
+
tool_call_message.tool_results.each(&:destroy)
|
292
|
+
tool_call_message.destroy
|
293
|
+
end
|
221
294
|
end
|
222
295
|
end
|
223
296
|
|
224
297
|
def setup_persistence_callbacks
|
225
|
-
# Only set up once per chat instance
|
226
298
|
return @chat if @chat.instance_variable_get(:@_persistence_callbacks_setup)
|
227
299
|
|
228
|
-
# Set up persistence callbacks (user callbacks will be chained via on_new_message/on_end_message methods)
|
229
300
|
@chat.on_new_message { persist_new_message }
|
230
301
|
@chat.on_end_message { |msg| persist_message_completion(msg) }
|
231
302
|
|
@@ -237,15 +308,21 @@ module RubyLLM
|
|
237
308
|
@message = messages.create!(role: :assistant, content: '')
|
238
309
|
end
|
239
310
|
|
240
|
-
def persist_message_completion(message)
|
311
|
+
def persist_message_completion(message) # rubocop:disable Metrics/PerceivedComplexity
|
241
312
|
return unless message
|
242
313
|
|
243
314
|
tool_call_id = find_tool_call_id(message.tool_call_id) if message.tool_call_id
|
244
315
|
|
245
316
|
transaction do
|
246
|
-
# Convert parsed JSON back to JSON string for storage
|
247
317
|
content = message.content
|
248
|
-
|
318
|
+
attachments_to_persist = nil
|
319
|
+
|
320
|
+
if content.is_a?(RubyLLM::Content)
|
321
|
+
attachments_to_persist = content.attachments if content.attachments.any?
|
322
|
+
content = content.text
|
323
|
+
elsif content.is_a?(Hash) || content.is_a?(Array)
|
324
|
+
content = content.to_json
|
325
|
+
end
|
249
326
|
|
250
327
|
@message.update!(
|
251
328
|
role: message.role,
|
@@ -256,6 +333,8 @@ module RubyLLM
|
|
256
333
|
)
|
257
334
|
@message.write_attribute(@message.class.tool_call_foreign_key, tool_call_id) if tool_call_id
|
258
335
|
@message.save!
|
336
|
+
|
337
|
+
persist_content(@message, attachments_to_persist) if attachments_to_persist
|
259
338
|
persist_tool_calls(message.tool_calls) if message.tool_calls.present?
|
260
339
|
end
|
261
340
|
end
|
@@ -297,8 +376,7 @@ module RubyLLM
|
|
297
376
|
def convert_to_active_storage_format(source)
|
298
377
|
return if source.blank?
|
299
378
|
|
300
|
-
|
301
|
-
attachment = RubyLLM::Attachment.new(source)
|
379
|
+
attachment = source.is_a?(RubyLLM::Attachment) ? source : RubyLLM::Attachment.new(source)
|
302
380
|
|
303
381
|
{
|
304
382
|
io: StringIO.new(attachment.content),
|
@@ -311,8 +389,7 @@ module RubyLLM
|
|
311
389
|
end
|
312
390
|
end
|
313
391
|
|
314
|
-
# Methods mixed into message models
|
315
|
-
# provide a clean interface to the underlying message data.
|
392
|
+
# Methods mixed into message models.
|
316
393
|
module MessageMethods
|
317
394
|
extend ActiveSupport::Concern
|
318
395
|
|
@@ -378,5 +455,72 @@ module RubyLLM
|
|
378
455
|
tempfile
|
379
456
|
end
|
380
457
|
end
|
458
|
+
|
459
|
+
# Methods mixed into model registry models.
|
460
|
+
module ModelMethods
|
461
|
+
extend ActiveSupport::Concern
|
462
|
+
|
463
|
+
class_methods do # rubocop:disable Metrics/BlockLength
|
464
|
+
def refresh!
|
465
|
+
RubyLLM.models.refresh!
|
466
|
+
|
467
|
+
transaction do
|
468
|
+
RubyLLM.models.all.each do |model_info|
|
469
|
+
model = find_or_initialize_by(
|
470
|
+
model_id: model_info.id,
|
471
|
+
provider: model_info.provider
|
472
|
+
)
|
473
|
+
model.update!(from_llm_attributes(model_info))
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
def from_llm(model_info)
|
479
|
+
new(from_llm_attributes(model_info))
|
480
|
+
end
|
481
|
+
|
482
|
+
private
|
483
|
+
|
484
|
+
def from_llm_attributes(model_info)
|
485
|
+
{
|
486
|
+
model_id: model_info.id,
|
487
|
+
name: model_info.name,
|
488
|
+
provider: model_info.provider,
|
489
|
+
family: model_info.family,
|
490
|
+
model_created_at: model_info.created_at,
|
491
|
+
context_window: model_info.context_window,
|
492
|
+
max_output_tokens: model_info.max_output_tokens,
|
493
|
+
knowledge_cutoff: model_info.knowledge_cutoff,
|
494
|
+
modalities: model_info.modalities.to_h,
|
495
|
+
capabilities: model_info.capabilities,
|
496
|
+
pricing: model_info.pricing.to_h,
|
497
|
+
metadata: model_info.metadata
|
498
|
+
}
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
def to_llm
|
503
|
+
RubyLLM::Model::Info.new(
|
504
|
+
id: model_id,
|
505
|
+
name: name,
|
506
|
+
provider: provider,
|
507
|
+
family: family,
|
508
|
+
created_at: model_created_at,
|
509
|
+
context_window: context_window,
|
510
|
+
max_output_tokens: max_output_tokens,
|
511
|
+
knowledge_cutoff: knowledge_cutoff,
|
512
|
+
modalities: modalities&.deep_symbolize_keys || {},
|
513
|
+
capabilities: capabilities,
|
514
|
+
pricing: pricing&.deep_symbolize_keys || {},
|
515
|
+
metadata: metadata&.deep_symbolize_keys || {}
|
516
|
+
)
|
517
|
+
end
|
518
|
+
|
519
|
+
delegate :supports?, :supports_vision?, :supports_functions?, :type,
|
520
|
+
:input_price_per_million, :output_price_per_million,
|
521
|
+
:function_calling?, :structured_output?, :batch?,
|
522
|
+
:reasoning?, :citations?, :streaming?,
|
523
|
+
to: :to_llm
|
524
|
+
end
|
381
525
|
end
|
382
526
|
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"
|
@@ -182,6 +231,10 @@
|
|
182
231
|
"openai": "gpt-oss-120b",
|
183
232
|
"openrouter": "openai/gpt-oss-120b"
|
184
233
|
},
|
234
|
+
"gpt-oss-20b": {
|
235
|
+
"openai": "gpt-oss-20b",
|
236
|
+
"openrouter": "openai/gpt-oss-20b"
|
237
|
+
},
|
185
238
|
"o1": {
|
186
239
|
"openai": "o1",
|
187
240
|
"openrouter": "openai/o1"
|
@@ -213,5 +266,9 @@
|
|
213
266
|
"o4-mini": {
|
214
267
|
"openai": "o4-mini",
|
215
268
|
"openrouter": "openai/o4-mini"
|
269
|
+
},
|
270
|
+
"text-embedding-004": {
|
271
|
+
"gemini": "text-embedding-004",
|
272
|
+
"vertexai": "text-embedding-004"
|
216
273
|
}
|
217
274
|
}
|
data/lib/ruby_llm/aliases.rb
CHANGED
@@ -1,53 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module RubyLLM
|
4
|
-
# Manages model aliases
|
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
|
-
# }
|
4
|
+
# Manages model aliases for provider-specific versions
|
14
5
|
class Aliases
|
15
6
|
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
7
|
def resolve(model_id, provider = nil)
|
22
8
|
return model_id unless aliases[model_id]
|
23
9
|
|
24
10
|
if provider
|
25
11
|
aliases[model_id][provider.to_s] || model_id
|
26
12
|
else
|
27
|
-
# Get native provider's version
|
28
13
|
aliases[model_id].values.first || model_id
|
29
14
|
end
|
30
15
|
end
|
31
16
|
|
32
|
-
# Returns the loaded aliases mapping
|
33
|
-
# @return [Hash] the aliases mapping
|
34
17
|
def aliases
|
35
18
|
@aliases ||= load_aliases
|
36
19
|
end
|
37
20
|
|
38
|
-
|
39
|
-
|
21
|
+
def aliases_file
|
22
|
+
File.expand_path('aliases.json', __dir__)
|
23
|
+
end
|
24
|
+
|
40
25
|
def load_aliases
|
41
|
-
|
42
|
-
|
43
|
-
JSON.parse(File.read(file_path))
|
26
|
+
if File.exist?(aliases_file)
|
27
|
+
JSON.parse(File.read(aliases_file))
|
44
28
|
else
|
45
29
|
{}
|
46
30
|
end
|
47
31
|
end
|
48
32
|
|
49
|
-
# Reloads aliases from disk
|
50
|
-
# @return [Hash] the reloaded aliases
|
51
33
|
def reload!
|
52
34
|
@aliases = load_aliases
|
53
35
|
end
|
data/lib/ruby_llm/chat.rb
CHANGED
@@ -1,13 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module RubyLLM
|
4
|
-
# Represents a conversation with an AI model
|
5
|
-
# streaming responses, and tool integration with a simple, conversational API.
|
6
|
-
#
|
7
|
-
# Example:
|
8
|
-
# chat = RubyLLM.chat
|
9
|
-
# chat.ask "What's the best way to learn Ruby?"
|
10
|
-
# chat.ask "Can you elaborate on that?"
|
4
|
+
# Represents a conversation with an AI model
|
11
5
|
class Chat
|
12
6
|
include Enumerable
|
13
7
|
|
@@ -22,7 +16,7 @@ module RubyLLM
|
|
22
16
|
@config = context&.config || RubyLLM.config
|
23
17
|
model_id = model || @config.default_model
|
24
18
|
with_model(model_id, provider: provider, assume_exists: assume_model_exists)
|
25
|
-
@temperature =
|
19
|
+
@temperature = nil
|
26
20
|
@messages = []
|
27
21
|
@tools = {}
|
28
22
|
@cache_prompts = { system: false, user: false, tools: false }
|
@@ -138,8 +132,8 @@ module RubyLLM
|
|
138
132
|
messages,
|
139
133
|
tools: @tools,
|
140
134
|
temperature: @temperature,
|
141
|
-
model: @model.id,
|
142
135
|
cache_prompts: @cache_prompts.dup,
|
136
|
+
model: @model,
|
143
137
|
params: @params,
|
144
138
|
headers: @headers,
|
145
139
|
schema: @schema,
|
@@ -148,7 +142,6 @@ module RubyLLM
|
|
148
142
|
|
149
143
|
@on[:new_message]&.call unless block_given?
|
150
144
|
|
151
|
-
# Parse JSON if schema was set
|
152
145
|
if @schema && response.content.is_a?(String)
|
153
146
|
begin
|
154
147
|
response.content = JSON.parse(response.content)
|
@@ -177,6 +170,10 @@ module RubyLLM
|
|
177
170
|
@messages.clear
|
178
171
|
end
|
179
172
|
|
173
|
+
def instance_variables
|
174
|
+
super - %i[@connection @config]
|
175
|
+
end
|
176
|
+
|
180
177
|
private
|
181
178
|
|
182
179
|
def wrap_streaming_block(&block)
|
@@ -191,12 +188,11 @@ module RubyLLM
|
|
191
188
|
@on[:new_message]&.call
|
192
189
|
end
|
193
190
|
|
194
|
-
# Pass chunk to user's block
|
195
191
|
block.call chunk
|
196
192
|
end
|
197
193
|
end
|
198
194
|
|
199
|
-
def handle_tool_calls(response, &)
|
195
|
+
def handle_tool_calls(response, &) # rubocop:disable Metrics/PerceivedComplexity
|
200
196
|
halt_result = nil
|
201
197
|
|
202
198
|
response.tool_calls.each_value do |tool_call|
|
@@ -204,7 +200,8 @@ module RubyLLM
|
|
204
200
|
@on[:tool_call]&.call(tool_call)
|
205
201
|
result = execute_tool tool_call
|
206
202
|
@on[:tool_result]&.call(result)
|
207
|
-
|
203
|
+
content = result.is_a?(Content) ? result : result.to_s
|
204
|
+
message = add_message role: :tool, content:, tool_call_id: tool_call.id
|
208
205
|
@on[:end_message]&.call(message)
|
209
206
|
|
210
207
|
halt_result = result if result.is_a?(Tool::Halt)
|
@@ -218,9 +215,5 @@ module RubyLLM
|
|
218
215
|
args = tool_call.arguments
|
219
216
|
tool.call(args)
|
220
217
|
end
|
221
|
-
|
222
|
-
def instance_variables
|
223
|
-
super - %i[@connection @config]
|
224
|
-
end
|
225
218
|
end
|
226
219
|
end
|
@@ -1,16 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module RubyLLM
|
4
|
-
# Global configuration for RubyLLM
|
5
|
-
# and provider-specific settings.
|
6
|
-
#
|
7
|
-
# Configure via:
|
8
|
-
# RubyLLM.configure do |config|
|
9
|
-
# config.openai_api_key = ENV['OPENAI_API_KEY']
|
10
|
-
# config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
|
11
|
-
# end
|
4
|
+
# Global configuration for RubyLLM
|
12
5
|
class Configuration
|
13
|
-
# Provider-specific configuration
|
14
6
|
attr_accessor :openai_api_key,
|
15
7
|
:openai_api_base,
|
16
8
|
:openai_organization_id,
|
@@ -18,6 +10,8 @@ module RubyLLM
|
|
18
10
|
:openai_use_system_role,
|
19
11
|
:anthropic_api_key,
|
20
12
|
:gemini_api_key,
|
13
|
+
:vertexai_project_id,
|
14
|
+
:vertexai_location,
|
21
15
|
:deepseek_api_key,
|
22
16
|
:perplexity_api_key,
|
23
17
|
:bedrock_api_key,
|
@@ -33,6 +27,8 @@ module RubyLLM
|
|
33
27
|
:default_model,
|
34
28
|
:default_embedding_model,
|
35
29
|
:default_image_model,
|
30
|
+
# Model registry
|
31
|
+
:model_registry_class,
|
36
32
|
# Connection configuration
|
37
33
|
:request_timeout,
|
38
34
|
:max_retries,
|
@@ -47,7 +43,6 @@ module RubyLLM
|
|
47
43
|
:log_stream_debug
|
48
44
|
|
49
45
|
def initialize
|
50
|
-
# Connection configuration
|
51
46
|
@request_timeout = 120
|
52
47
|
@max_retries = 3
|
53
48
|
@retry_interval = 0.1
|
@@ -55,12 +50,10 @@ module RubyLLM
|
|
55
50
|
@retry_interval_randomness = 0.5
|
56
51
|
@http_proxy = nil
|
57
52
|
|
58
|
-
# Default models
|
59
53
|
@default_model = 'gpt-4.1-nano'
|
60
54
|
@default_embedding_model = 'text-embedding-3-small'
|
61
55
|
@default_image_model = 'gpt-image-1'
|
62
56
|
|
63
|
-
# Logging configuration
|
64
57
|
@log_file = $stdout
|
65
58
|
@log_level = ENV['RUBYLLM_DEBUG'] ? Logger::DEBUG : Logger::INFO
|
66
59
|
@log_stream_debug = ENV['RUBYLLM_STREAM_DEBUG'] == 'true'
|
data/lib/ruby_llm/connection.rb
CHANGED
@@ -48,6 +48,10 @@ module RubyLLM
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
def instance_variables
|
52
|
+
super - %i[@config @connection]
|
53
|
+
end
|
54
|
+
|
51
55
|
private
|
52
56
|
|
53
57
|
def setup_timeout(faraday)
|
@@ -118,9 +122,5 @@ module RubyLLM
|
|
118
122
|
raise ConfigurationError,
|
119
123
|
"#{@provider.name} provider is not configured. Add this to your initialization:\n\n#{config_block}"
|
120
124
|
end
|
121
|
-
|
122
|
-
def instance_variables
|
123
|
-
super - %i[@config @connection]
|
124
|
-
end
|
125
125
|
end
|
126
126
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
# A connection that uses multipart/form-data for file uploads
|
5
|
+
class ConnectionMultipart < Connection
|
6
|
+
def post(url, payload, &)
|
7
|
+
@connection.post url, payload do |req|
|
8
|
+
req.headers.merge! @provider.headers if @provider.respond_to?(:headers)
|
9
|
+
req.headers['Content-Type'] = 'multipart/form-data'
|
10
|
+
yield req if block_given?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup_middleware(faraday)
|
15
|
+
super
|
16
|
+
faraday.request :multipart, content_type: 'multipart/form-data'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/ruby_llm/content.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
module RubyLLM
|
4
4
|
# Represents the content sent to or received from an LLM.
|
5
|
-
# Selects the appropriate attachment class based on the content type.
|
6
5
|
class Content
|
7
6
|
attr_reader :text, :attachments
|
8
7
|
|
@@ -19,6 +18,11 @@ module RubyLLM
|
|
19
18
|
self
|
20
19
|
end
|
21
20
|
|
21
|
+
def attach(attachment)
|
22
|
+
@attachments << attachment
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
22
26
|
def format
|
23
27
|
if @text && @attachments.empty?
|
24
28
|
@text
|
@@ -42,7 +46,6 @@ module RubyLLM
|
|
42
46
|
|
43
47
|
def process_attachments(attachments)
|
44
48
|
if attachments.is_a?(Hash)
|
45
|
-
# Ignores types (like :image, :audio, :text, :pdf) since we have robust MIME type detection
|
46
49
|
attachments.each_value { |attachment| process_attachments_array_or_string(attachment) }
|
47
50
|
else
|
48
51
|
process_attachments_array_or_string attachments
|