ruby_llm 1.14.0 → 1.15.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 +7 -5
- data/lib/generators/ruby_llm/generator_helpers.rb +8 -0
- data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +1 -1
- data/lib/ruby_llm/active_record/acts_as.rb +3 -0
- data/lib/ruby_llm/active_record/acts_as_legacy.rb +52 -25
- data/lib/ruby_llm/active_record/chat_methods.rb +47 -23
- data/lib/ruby_llm/active_record/message_methods.rb +19 -14
- data/lib/ruby_llm/active_record/model_methods.rb +7 -9
- data/lib/ruby_llm/active_record/payload_helpers.rb +29 -0
- data/lib/ruby_llm/active_record/tool_call_methods.rb +5 -15
- data/lib/ruby_llm/agent.rb +3 -2
- data/lib/ruby_llm/aliases.json +53 -14
- data/lib/ruby_llm/attachment.rb +11 -27
- data/lib/ruby_llm/chat.rb +62 -21
- data/lib/ruby_llm/cost.rb +224 -0
- data/lib/ruby_llm/image.rb +37 -4
- data/lib/ruby_llm/message.rb +20 -0
- data/lib/ruby_llm/model/info.rb +17 -0
- data/lib/ruby_llm/model/pricing_category.rb +13 -2
- data/lib/ruby_llm/models.json +26511 -24930
- data/lib/ruby_llm/models.rb +2 -1
- data/lib/ruby_llm/models_schema.json +3 -0
- data/lib/ruby_llm/provider.rb +10 -3
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +1 -133
- data/lib/ruby_llm/providers/anthropic/models.rb +2 -8
- data/lib/ruby_llm/providers/anthropic/tools.rb +4 -1
- data/lib/ruby_llm/providers/bedrock/chat.rb +24 -13
- data/lib/ruby_llm/providers/bedrock/streaming.rb +4 -1
- data/lib/ruby_llm/providers/deepseek/capabilities.rb +1 -119
- data/lib/ruby_llm/providers/gemini/capabilities.rb +45 -215
- data/lib/ruby_llm/providers/gemini/chat.rb +8 -1
- data/lib/ruby_llm/providers/gemini/images.rb +2 -2
- data/lib/ruby_llm/providers/gemini/models.rb +2 -4
- data/lib/ruby_llm/providers/gemini/streaming.rb +4 -1
- data/lib/ruby_llm/providers/gemini/tools.rb +3 -1
- data/lib/ruby_llm/providers/mistral/capabilities.rb +6 -1
- data/lib/ruby_llm/providers/mistral/chat.rb +55 -4
- data/lib/ruby_llm/providers/openai/capabilities.rb +157 -195
- data/lib/ruby_llm/providers/openai/chat.rb +45 -6
- data/lib/ruby_llm/providers/openai/images.rb +58 -6
- data/lib/ruby_llm/providers/openai/models.rb +2 -4
- data/lib/ruby_llm/providers/openai/streaming.rb +5 -6
- data/lib/ruby_llm/providers/openrouter/chat.rb +30 -6
- data/lib/ruby_llm/providers/openrouter/images.rb +2 -2
- data/lib/ruby_llm/providers/openrouter/models.rb +1 -1
- data/lib/ruby_llm/providers/openrouter/streaming.rb +5 -6
- data/lib/ruby_llm/providers/perplexity/capabilities.rb +34 -99
- data/lib/ruby_llm/providers/perplexity/models.rb +12 -14
- data/lib/ruby_llm/railtie.rb +6 -0
- data/lib/ruby_llm/tokens.rb +8 -0
- data/lib/ruby_llm/tool.rb +24 -7
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm.rb +2 -4
- data/lib/tasks/models.rake +13 -12
- metadata +21 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3cfdd21451b6e3daac0463dfdb7655892967e5a4da6b00846f353809a9abe18a
|
|
4
|
+
data.tar.gz: e1505ddd57326601298bb8975c20049a195eb87babd62b08ac0e6340c9e653f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7222e575d4f1e5cf620fdc37f4c255c7ededeee945d7b77fac4129c2947d6f78f7d5641b45ed39470180e5bf2dfd48b36e0a6c16579e4e4835564c48b6907920
|
|
7
|
+
data.tar.gz: ce3fb0f0e8b3665fafe56717cf8fcb10733008821633b024013d0f6e5d0bc90ef47f25b0109aa0e80ee3df4002e2f9b6a20cd98595031aa57235588cb4677052
|
data/README.md
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
Battle tested at [<picture><source media="(prefers-color-scheme: dark)" srcset="https://chatwithwork.com/logotype-dark.svg"><img src="https://chatwithwork.com/logotype.svg" alt="Chat with Work" height="30" align="absmiddle"></picture>](https://chatwithwork.com) — *Your AI coworker*
|
|
11
11
|
|
|
12
|
-
[](https://badge.fury.io/rb/ruby_llm)
|
|
13
13
|
[](https://github.com/rubocop/rubocop)
|
|
14
14
|
[](https://rubygems.org/gems/ruby_llm)
|
|
15
|
-
[](https://codecov.io/gh/crmne/ruby_llm)
|
|
16
16
|
|
|
17
17
|
<a href="https://trendshift.io/repositories/13640" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13640" alt="crmne%2Fruby_llm | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
|
18
18
|
</div>
|
|
@@ -24,6 +24,10 @@ Battle tested at [<picture><source media="(prefers-color-scheme: dark)" srcset="
|
|
|
24
24
|
|
|
25
25
|
Build chatbots, AI agents, RAG applications. Works with OpenAI, xAI, Anthropic, Google, AWS, local models, and any OpenAI-compatible API.
|
|
26
26
|
|
|
27
|
+
## From zero to AI chat app in under two minutes
|
|
28
|
+
|
|
29
|
+
https://github.com/user-attachments/assets/65422091-9338-47da-a303-92b918bd1345
|
|
30
|
+
|
|
27
31
|
## Why RubyLLM?
|
|
28
32
|
|
|
29
33
|
Every AI provider ships their own bloated client. Different APIs. Different response formats. Different conventions. It's exhausting.
|
|
@@ -82,9 +86,7 @@ RubyLLM.moderate "Check if this text is safe"
|
|
|
82
86
|
```ruby
|
|
83
87
|
# Let AI use your code
|
|
84
88
|
class Weather < RubyLLM::Tool
|
|
85
|
-
|
|
86
|
-
param :latitude
|
|
87
|
-
param :longitude
|
|
89
|
+
desc "Get current weather"
|
|
88
90
|
|
|
89
91
|
def execute(latitude:, longitude:)
|
|
90
92
|
url = "https://api.open-meteo.com/v1/forecast?latitude=#{latitude}&longitude=#{longitude}¤t=temperature_2m,wind_speed_10m"
|
|
@@ -87,6 +87,7 @@ module RubyLLM
|
|
|
87
87
|
|
|
88
88
|
add_association_params(params, :message, message_table_name, message_model_name,
|
|
89
89
|
owner_table: tool_call_table_name, owner_model_name: tool_call_model_name)
|
|
90
|
+
add_result_foreign_key_param(params)
|
|
90
91
|
|
|
91
92
|
"acts_as_tool_call#{" #{params.join(', ')}" if params.any?}"
|
|
92
93
|
end
|
|
@@ -178,6 +179,13 @@ module RubyLLM
|
|
|
178
179
|
"#{owner_model_name.demodulize.underscore}_id"
|
|
179
180
|
end
|
|
180
181
|
|
|
182
|
+
def add_result_foreign_key_param(params)
|
|
183
|
+
foreign_key = "#{tool_call_table_name.singularize}_id"
|
|
184
|
+
default_foreign_key = "#{tool_call_model_name.demodulize.underscore}_id"
|
|
185
|
+
|
|
186
|
+
params << "result_foreign_key: :#{foreign_key}" if foreign_key != default_foreign_key
|
|
187
|
+
end
|
|
188
|
+
|
|
181
189
|
# Convert namespaced model names to proper table names
|
|
182
190
|
# e.g., "Assistant::Chat" -> "assistant_chats" (not "assistant/chats")
|
|
183
191
|
def table_name_for(model_name)
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'active_support/concern'
|
|
4
|
+
require 'active_support/inflector'
|
|
5
|
+
|
|
3
6
|
module RubyLLM
|
|
4
7
|
module ActiveRecord
|
|
5
8
|
# Adds chat and message persistence capabilities to ActiveRecord models.
|
|
@@ -160,27 +163,33 @@ module RubyLLM
|
|
|
160
163
|
self
|
|
161
164
|
end
|
|
162
165
|
|
|
163
|
-
def on_new_message(&
|
|
164
|
-
to_llm
|
|
166
|
+
def on_new_message(&)
|
|
167
|
+
to_llm.on_new_message(&)
|
|
168
|
+
self
|
|
169
|
+
end
|
|
165
170
|
|
|
166
|
-
|
|
171
|
+
def on_end_message(&)
|
|
172
|
+
to_llm.on_end_message(&)
|
|
173
|
+
self
|
|
174
|
+
end
|
|
167
175
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
block&.call
|
|
171
|
-
end
|
|
176
|
+
def before_message(...)
|
|
177
|
+
to_llm.before_message(...)
|
|
172
178
|
self
|
|
173
179
|
end
|
|
174
180
|
|
|
175
|
-
def
|
|
176
|
-
to_llm
|
|
181
|
+
def after_message(...)
|
|
182
|
+
to_llm.after_message(...)
|
|
183
|
+
self
|
|
184
|
+
end
|
|
177
185
|
|
|
178
|
-
|
|
186
|
+
def before_tool_call(...)
|
|
187
|
+
to_llm.before_tool_call(...)
|
|
188
|
+
self
|
|
189
|
+
end
|
|
179
190
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
block&.call(msg)
|
|
183
|
-
end
|
|
191
|
+
def after_tool_result(...)
|
|
192
|
+
to_llm.after_tool_result(...)
|
|
184
193
|
self
|
|
185
194
|
end
|
|
186
195
|
|
|
@@ -319,8 +328,8 @@ module RubyLLM
|
|
|
319
328
|
def setup_persistence_callbacks
|
|
320
329
|
return @chat if @chat.instance_variable_get(:@_persistence_callbacks_setup)
|
|
321
330
|
|
|
322
|
-
@chat.
|
|
323
|
-
@chat.
|
|
331
|
+
@chat.before_message { persist_new_message }
|
|
332
|
+
@chat.after_message { |msg| persist_message_completion(msg) }
|
|
324
333
|
|
|
325
334
|
@chat.instance_variable_set(:@_persistence_callbacks_setup, true)
|
|
326
335
|
@chat
|
|
@@ -383,8 +392,8 @@ module RubyLLM
|
|
|
383
392
|
case attachment
|
|
384
393
|
when ActionDispatch::Http::UploadedFile, ActiveStorage::Blob
|
|
385
394
|
attachment
|
|
386
|
-
when ActiveStorage::Attached::One, ActiveStorage::Attached::Many
|
|
387
|
-
attachment
|
|
395
|
+
when ActiveStorage::Attachment, ActiveStorage::Attached::One, ActiveStorage::Attached::Many
|
|
396
|
+
active_storage_blobs(attachment)
|
|
388
397
|
when Hash
|
|
389
398
|
attachment.values.map { |v| prepare_for_active_storage(v) }
|
|
390
399
|
else
|
|
@@ -398,16 +407,28 @@ module RubyLLM
|
|
|
398
407
|
|
|
399
408
|
attachment = source.is_a?(RubyLLM::Attachment) ? source : RubyLLM::Attachment.new(source)
|
|
400
409
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
410
|
+
if attachment.active_storage?
|
|
411
|
+
active_storage_blobs(attachment.source)
|
|
412
|
+
else
|
|
413
|
+
{
|
|
414
|
+
io: StringIO.new(attachment.content),
|
|
415
|
+
filename: attachment.filename,
|
|
416
|
+
content_type: attachment.mime_type
|
|
417
|
+
}
|
|
418
|
+
end
|
|
406
419
|
rescue StandardError => e
|
|
407
420
|
RubyLLM.logger.warn "Failed to process attachment #{source}: #{e.message}"
|
|
408
421
|
nil
|
|
409
422
|
end
|
|
410
423
|
|
|
424
|
+
def active_storage_blobs(attachment)
|
|
425
|
+
case attachment
|
|
426
|
+
when ActiveStorage::Blob then attachment
|
|
427
|
+
when ActiveStorage::Attachment, ActiveStorage::Attached::One then attachment.blob
|
|
428
|
+
when ActiveStorage::Attached::Many then attachment.blobs
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
411
432
|
def build_content(message, attachments)
|
|
412
433
|
return message if content_like?(message)
|
|
413
434
|
|
|
@@ -473,9 +494,15 @@ module RubyLLM
|
|
|
473
494
|
end
|
|
474
495
|
|
|
475
496
|
def extract_content
|
|
476
|
-
|
|
497
|
+
text_content = if content.respond_to?(:to_plain_text)
|
|
498
|
+
content.to_plain_text
|
|
499
|
+
else
|
|
500
|
+
content
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
return text_content unless respond_to?(:attachments) && attachments.attached?
|
|
477
504
|
|
|
478
|
-
RubyLLM::Content.new(
|
|
505
|
+
RubyLLM::Content.new(text_content).tap do |content_obj|
|
|
479
506
|
@_tempfiles = []
|
|
480
507
|
|
|
481
508
|
attachments.each do |attachment|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'active_support/concern'
|
|
4
|
+
|
|
3
5
|
module RubyLLM
|
|
4
6
|
module ActiveRecord
|
|
5
7
|
# Methods mixed into chat models.
|
|
@@ -154,27 +156,33 @@ module RubyLLM
|
|
|
154
156
|
self
|
|
155
157
|
end
|
|
156
158
|
|
|
157
|
-
def on_new_message(&
|
|
158
|
-
to_llm
|
|
159
|
+
def on_new_message(&)
|
|
160
|
+
to_llm.on_new_message(&)
|
|
161
|
+
self
|
|
162
|
+
end
|
|
159
163
|
|
|
160
|
-
|
|
164
|
+
def on_end_message(&)
|
|
165
|
+
to_llm.on_end_message(&)
|
|
166
|
+
self
|
|
167
|
+
end
|
|
161
168
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
block&.call
|
|
165
|
-
end
|
|
169
|
+
def before_message(...)
|
|
170
|
+
to_llm.before_message(...)
|
|
166
171
|
self
|
|
167
172
|
end
|
|
168
173
|
|
|
169
|
-
def
|
|
170
|
-
to_llm
|
|
174
|
+
def after_message(...)
|
|
175
|
+
to_llm.after_message(...)
|
|
176
|
+
self
|
|
177
|
+
end
|
|
171
178
|
|
|
172
|
-
|
|
179
|
+
def before_tool_call(...)
|
|
180
|
+
to_llm.before_tool_call(...)
|
|
181
|
+
self
|
|
182
|
+
end
|
|
173
183
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
block&.call(msg)
|
|
177
|
-
end
|
|
184
|
+
def after_tool_result(...)
|
|
185
|
+
to_llm.after_tool_result(...)
|
|
178
186
|
self
|
|
179
187
|
end
|
|
180
188
|
|
|
@@ -208,6 +216,10 @@ module RubyLLM
|
|
|
208
216
|
message_record
|
|
209
217
|
end
|
|
210
218
|
|
|
219
|
+
def cost
|
|
220
|
+
RubyLLM::Cost.aggregate(messages_association.map(&:cost))
|
|
221
|
+
end
|
|
222
|
+
|
|
211
223
|
def create_user_message(content, with: nil)
|
|
212
224
|
add_message(role: :user, content: build_content(content, with))
|
|
213
225
|
end
|
|
@@ -258,8 +270,8 @@ module RubyLLM
|
|
|
258
270
|
def setup_persistence_callbacks
|
|
259
271
|
return @chat if @chat.instance_variable_get(:@_persistence_callbacks_setup)
|
|
260
272
|
|
|
261
|
-
@chat.
|
|
262
|
-
@chat.
|
|
273
|
+
@chat.before_message { persist_new_message }
|
|
274
|
+
@chat.after_message { |msg| persist_message_completion(msg) }
|
|
263
275
|
|
|
264
276
|
@chat.instance_variable_set(:@_persistence_callbacks_setup, true)
|
|
265
277
|
@chat
|
|
@@ -402,8 +414,8 @@ module RubyLLM
|
|
|
402
414
|
case attachment
|
|
403
415
|
when ActionDispatch::Http::UploadedFile, ActiveStorage::Blob
|
|
404
416
|
attachment
|
|
405
|
-
when ActiveStorage::Attached::One, ActiveStorage::Attached::Many
|
|
406
|
-
attachment
|
|
417
|
+
when ActiveStorage::Attachment, ActiveStorage::Attached::One, ActiveStorage::Attached::Many
|
|
418
|
+
active_storage_blobs(attachment)
|
|
407
419
|
when Hash
|
|
408
420
|
attachment.values.map { |v| prepare_for_active_storage(v) }
|
|
409
421
|
else
|
|
@@ -417,16 +429,28 @@ module RubyLLM
|
|
|
417
429
|
|
|
418
430
|
attachment = source.is_a?(RubyLLM::Attachment) ? source : RubyLLM::Attachment.new(source)
|
|
419
431
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
432
|
+
if attachment.active_storage?
|
|
433
|
+
active_storage_blobs(attachment.source)
|
|
434
|
+
else
|
|
435
|
+
{
|
|
436
|
+
io: StringIO.new(attachment.content),
|
|
437
|
+
filename: attachment.filename,
|
|
438
|
+
content_type: attachment.mime_type
|
|
439
|
+
}
|
|
440
|
+
end
|
|
425
441
|
rescue StandardError => e
|
|
426
442
|
RubyLLM.logger.warn "Failed to process attachment #{source}: #{e.message}"
|
|
427
443
|
nil
|
|
428
444
|
end
|
|
429
445
|
|
|
446
|
+
def active_storage_blobs(attachment)
|
|
447
|
+
case attachment
|
|
448
|
+
when ActiveStorage::Blob then attachment
|
|
449
|
+
when ActiveStorage::Attachment, ActiveStorage::Attached::One then attachment.blob
|
|
450
|
+
when ActiveStorage::Attached::Many then attachment.blobs
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
|
|
430
454
|
def build_content(message, attachments)
|
|
431
455
|
return message if content_like?(message)
|
|
432
456
|
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'active_support/concern'
|
|
4
|
+
require 'ruby_llm/active_record/payload_helpers'
|
|
5
|
+
|
|
3
6
|
module RubyLLM
|
|
4
7
|
module ActiveRecord
|
|
5
8
|
# Methods mixed into message models.
|
|
6
9
|
module MessageMethods
|
|
7
10
|
extend ActiveSupport::Concern
|
|
11
|
+
include PayloadHelpers
|
|
8
12
|
|
|
9
13
|
class_methods do
|
|
10
14
|
attr_reader :chat_class, :tool_call_class, :chat_foreign_key, :tool_call_foreign_key
|
|
@@ -39,6 +43,18 @@ module RubyLLM
|
|
|
39
43
|
)
|
|
40
44
|
end
|
|
41
45
|
|
|
46
|
+
def cost
|
|
47
|
+
RubyLLM::Cost.new(tokens:, model: model_association)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def cache_read_tokens
|
|
51
|
+
cached_value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def cache_write_tokens
|
|
55
|
+
cache_creation_value
|
|
56
|
+
end
|
|
57
|
+
|
|
42
58
|
def to_partial_path
|
|
43
59
|
partial_prefix = self.class.name.underscore.pluralize
|
|
44
60
|
role_partial = if to_llm.tool_call?
|
|
@@ -52,10 +68,7 @@ module RubyLLM
|
|
|
52
68
|
end
|
|
53
69
|
|
|
54
70
|
def tool_error_message
|
|
55
|
-
|
|
56
|
-
return unless payload.is_a?(Hash)
|
|
57
|
-
|
|
58
|
-
payload['error'] || payload[:error]
|
|
71
|
+
payload_error_message(content)
|
|
59
72
|
end
|
|
60
73
|
|
|
61
74
|
private
|
|
@@ -101,7 +114,8 @@ module RubyLLM
|
|
|
101
114
|
def extract_content
|
|
102
115
|
return RubyLLM::Content::Raw.new(content_raw) if has_attribute?(:content_raw) && content_raw.present?
|
|
103
116
|
|
|
104
|
-
content_value =
|
|
117
|
+
content_value = content
|
|
118
|
+
content_value = content_value.to_plain_text if content_value.respond_to?(:to_plain_text)
|
|
105
119
|
|
|
106
120
|
return content_value unless respond_to?(:attachments) && attachments.attached?
|
|
107
121
|
|
|
@@ -128,15 +142,6 @@ module RubyLLM
|
|
|
128
142
|
@_tempfiles << tempfile
|
|
129
143
|
tempfile
|
|
130
144
|
end
|
|
131
|
-
|
|
132
|
-
def parse_payload(value)
|
|
133
|
-
return value if value.is_a?(Hash) || value.is_a?(Array)
|
|
134
|
-
return if value.blank?
|
|
135
|
-
|
|
136
|
-
JSON.parse(value)
|
|
137
|
-
rescue JSON::ParserError
|
|
138
|
-
nil
|
|
139
|
-
end
|
|
140
145
|
end
|
|
141
146
|
end
|
|
142
147
|
end
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'active_support/concern'
|
|
4
|
+
require 'active_support/core_ext/module/delegation'
|
|
5
|
+
|
|
3
6
|
module RubyLLM
|
|
4
7
|
module ActiveRecord
|
|
5
8
|
# Methods mixed into model registry models.
|
|
@@ -10,15 +13,7 @@ module RubyLLM
|
|
|
10
13
|
def refresh!
|
|
11
14
|
RubyLLM.models.refresh!
|
|
12
15
|
|
|
13
|
-
|
|
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
|
|
16
|
+
save_to_database
|
|
22
17
|
end
|
|
23
18
|
|
|
24
19
|
def save_to_database
|
|
@@ -76,8 +71,11 @@ module RubyLLM
|
|
|
76
71
|
|
|
77
72
|
delegate :supports?, :supports_vision?, :supports_functions?, :type,
|
|
78
73
|
:input_price_per_million, :output_price_per_million,
|
|
74
|
+
:cache_read_input_price_per_million, :cache_write_input_price_per_million,
|
|
75
|
+
:cached_input_price_per_million, :cache_creation_input_price_per_million,
|
|
79
76
|
:function_calling?, :structured_output?, :batch?,
|
|
80
77
|
:reasoning?, :citations?, :streaming?, :provider_class, :label,
|
|
78
|
+
:cost_for,
|
|
81
79
|
to: :to_llm
|
|
82
80
|
end
|
|
83
81
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/core_ext/object/blank'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module RubyLLM
|
|
7
|
+
module ActiveRecord
|
|
8
|
+
# Shared helpers for parsing serialized payloads on ActiveRecord-backed models.
|
|
9
|
+
module PayloadHelpers
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def payload_error_message(value)
|
|
13
|
+
payload = parse_payload(value)
|
|
14
|
+
return unless payload.is_a?(Hash)
|
|
15
|
+
|
|
16
|
+
payload['error'] || payload[:error]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def parse_payload(value)
|
|
20
|
+
return value if value.is_a?(Hash) || value.is_a?(Array)
|
|
21
|
+
return if value.blank?
|
|
22
|
+
|
|
23
|
+
JSON.parse(value)
|
|
24
|
+
rescue JSON::ParserError
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -1,27 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'active_support/concern'
|
|
4
|
+
require 'ruby_llm/active_record/payload_helpers'
|
|
5
|
+
|
|
3
6
|
module RubyLLM
|
|
4
7
|
module ActiveRecord
|
|
5
8
|
# Methods mixed into tool call models.
|
|
6
9
|
module ToolCallMethods
|
|
7
10
|
extend ActiveSupport::Concern
|
|
11
|
+
include PayloadHelpers
|
|
8
12
|
|
|
9
13
|
def tool_error_message
|
|
10
|
-
|
|
11
|
-
return unless payload.is_a?(Hash)
|
|
12
|
-
|
|
13
|
-
payload['error'] || payload[:error]
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
def parse_payload(value)
|
|
19
|
-
return value if value.is_a?(Hash) || value.is_a?(Array)
|
|
20
|
-
return if value.blank?
|
|
21
|
-
|
|
22
|
-
JSON.parse(value)
|
|
23
|
-
rescue JSON::ParserError
|
|
24
|
-
nil
|
|
14
|
+
payload_error_message(arguments)
|
|
25
15
|
end
|
|
26
16
|
end
|
|
27
17
|
end
|
data/lib/ruby_llm/agent.rb
CHANGED
|
@@ -359,7 +359,8 @@ module RubyLLM
|
|
|
359
359
|
|
|
360
360
|
def_delegators :chat, :model, :messages, :tools, :params, :headers, :schema, :ask, :say, :with_tool, :with_tools,
|
|
361
361
|
:with_model, :with_temperature, :with_thinking, :with_context, :with_params, :with_headers,
|
|
362
|
-
:with_schema, :on_new_message, :on_end_message, :on_tool_call, :on_tool_result, :
|
|
363
|
-
:
|
|
362
|
+
:with_schema, :on_new_message, :on_end_message, :on_tool_call, :on_tool_result, :before_message,
|
|
363
|
+
:after_message, :before_tool_call, :after_tool_result, :each, :complete, :add_message,
|
|
364
|
+
:reset_messages!, :cost
|
|
364
365
|
end
|
|
365
366
|
end
|
data/lib/ruby_llm/aliases.json
CHANGED
|
@@ -9,17 +9,13 @@
|
|
|
9
9
|
},
|
|
10
10
|
"claude-3-5-sonnet": {
|
|
11
11
|
"anthropic": "claude-3-5-sonnet-20241022",
|
|
12
|
-
"
|
|
13
|
-
"bedrock": "anthropic.claude-3-5-sonnet-20240620-v1:0:200k"
|
|
12
|
+
"bedrock": "anthropic.claude-3-5-sonnet-20241022-v2:0"
|
|
14
13
|
},
|
|
15
14
|
"claude-3-7-sonnet": {
|
|
16
15
|
"anthropic": "claude-3-7-sonnet-20250219",
|
|
17
16
|
"openrouter": "anthropic/claude-3.7-sonnet",
|
|
18
17
|
"bedrock": "anthropic.claude-3-7-sonnet-20250219-v1:0"
|
|
19
18
|
},
|
|
20
|
-
"claude-3-7-sonnet-latest": {
|
|
21
|
-
"anthropic": "claude-3-7-sonnet-latest"
|
|
22
|
-
},
|
|
23
19
|
"claude-3-haiku": {
|
|
24
20
|
"anthropic": "claude-3-haiku-20240307",
|
|
25
21
|
"openrouter": "anthropic/claude-3-haiku",
|
|
@@ -64,6 +60,12 @@
|
|
|
64
60
|
"bedrock": "anthropic.claude-opus-4-6-v1",
|
|
65
61
|
"azure": "claude-opus-4-6"
|
|
66
62
|
},
|
|
63
|
+
"claude-opus-4-7": {
|
|
64
|
+
"anthropic": "claude-opus-4-7",
|
|
65
|
+
"openrouter": "anthropic/claude-opus-4.7",
|
|
66
|
+
"bedrock": "anthropic.claude-opus-4-7",
|
|
67
|
+
"azure": "claude-opus-4-7"
|
|
68
|
+
},
|
|
67
69
|
"claude-sonnet-4": {
|
|
68
70
|
"anthropic": "claude-sonnet-4-20250514",
|
|
69
71
|
"openrouter": "anthropic/claude-sonnet-4",
|
|
@@ -88,6 +90,14 @@
|
|
|
88
90
|
"deepseek": "deepseek-chat",
|
|
89
91
|
"openrouter": "deepseek/deepseek-chat"
|
|
90
92
|
},
|
|
93
|
+
"deepseek-v4-flash": {
|
|
94
|
+
"deepseek": "deepseek-v4-flash",
|
|
95
|
+
"openrouter": "deepseek/deepseek-v4-flash"
|
|
96
|
+
},
|
|
97
|
+
"deepseek-v4-pro": {
|
|
98
|
+
"deepseek": "deepseek-v4-pro",
|
|
99
|
+
"openrouter": "deepseek/deepseek-v4-pro"
|
|
100
|
+
},
|
|
91
101
|
"gemini-1.5-flash": {
|
|
92
102
|
"gemini": "gemini-1.5-flash",
|
|
93
103
|
"vertexai": "gemini-1.5-flash"
|
|
@@ -190,7 +200,8 @@
|
|
|
190
200
|
},
|
|
191
201
|
"gemini-3.1-flash-lite-preview": {
|
|
192
202
|
"gemini": "gemini-3.1-flash-lite-preview",
|
|
193
|
-
"openrouter": "google/gemini-3.1-flash-lite-preview"
|
|
203
|
+
"openrouter": "google/gemini-3.1-flash-lite-preview",
|
|
204
|
+
"vertexai": "gemini-3.1-flash-lite-preview"
|
|
194
205
|
},
|
|
195
206
|
"gemini-3.1-pro-preview": {
|
|
196
207
|
"gemini": "gemini-3.1-pro-preview",
|
|
@@ -206,6 +217,10 @@
|
|
|
206
217
|
"gemini": "gemini-embedding-001",
|
|
207
218
|
"vertexai": "gemini-embedding-001"
|
|
208
219
|
},
|
|
220
|
+
"gemini-embedding-2": {
|
|
221
|
+
"gemini": "gemini-embedding-2",
|
|
222
|
+
"vertexai": "gemini-embedding-2"
|
|
223
|
+
},
|
|
209
224
|
"gemini-flash": {
|
|
210
225
|
"gemini": "gemini-flash-latest",
|
|
211
226
|
"vertexai": "gemini-flash-latest"
|
|
@@ -230,6 +245,14 @@
|
|
|
230
245
|
"gemini": "gemma-3n-e4b-it",
|
|
231
246
|
"openrouter": "google/gemma-3n-e4b-it"
|
|
232
247
|
},
|
|
248
|
+
"gemma-4-26b-a4b-it": {
|
|
249
|
+
"gemini": "gemma-4-26b-a4b-it",
|
|
250
|
+
"openrouter": "google/gemma-4-26b-a4b-it"
|
|
251
|
+
},
|
|
252
|
+
"gemma-4-31b-it": {
|
|
253
|
+
"gemini": "gemma-4-31b-it",
|
|
254
|
+
"openrouter": "google/gemma-4-31b-it"
|
|
255
|
+
},
|
|
233
256
|
"gpt-3.5-turbo": {
|
|
234
257
|
"openai": "gpt-3.5-turbo",
|
|
235
258
|
"openrouter": "openai/gpt-3.5-turbo"
|
|
@@ -247,18 +270,10 @@
|
|
|
247
270
|
"openrouter": "openai/gpt-4",
|
|
248
271
|
"azure": "gpt-4"
|
|
249
272
|
},
|
|
250
|
-
"gpt-4-1106-preview": {
|
|
251
|
-
"openai": "gpt-4-1106-preview",
|
|
252
|
-
"openrouter": "openai/gpt-4-1106-preview"
|
|
253
|
-
},
|
|
254
273
|
"gpt-4-turbo": {
|
|
255
274
|
"openai": "gpt-4-turbo",
|
|
256
275
|
"openrouter": "openai/gpt-4-turbo"
|
|
257
276
|
},
|
|
258
|
-
"gpt-4-turbo-preview": {
|
|
259
|
-
"openai": "gpt-4-turbo-preview",
|
|
260
|
-
"openrouter": "openai/gpt-4-turbo-preview"
|
|
261
|
-
},
|
|
262
277
|
"gpt-4.1": {
|
|
263
278
|
"openai": "gpt-4.1",
|
|
264
279
|
"openrouter": "openai/gpt-4.1",
|
|
@@ -373,10 +388,26 @@
|
|
|
373
388
|
"openai": "gpt-5.4",
|
|
374
389
|
"openrouter": "openai/gpt-5.4"
|
|
375
390
|
},
|
|
391
|
+
"gpt-5.4-mini": {
|
|
392
|
+
"openai": "gpt-5.4-mini",
|
|
393
|
+
"openrouter": "openai/gpt-5.4-mini"
|
|
394
|
+
},
|
|
395
|
+
"gpt-5.4-nano": {
|
|
396
|
+
"openai": "gpt-5.4-nano",
|
|
397
|
+
"openrouter": "openai/gpt-5.4-nano"
|
|
398
|
+
},
|
|
376
399
|
"gpt-5.4-pro": {
|
|
377
400
|
"openai": "gpt-5.4-pro",
|
|
378
401
|
"openrouter": "openai/gpt-5.4-pro"
|
|
379
402
|
},
|
|
403
|
+
"gpt-5.5": {
|
|
404
|
+
"openai": "gpt-5.5",
|
|
405
|
+
"openrouter": "openai/gpt-5.5"
|
|
406
|
+
},
|
|
407
|
+
"gpt-5.5-pro": {
|
|
408
|
+
"openai": "gpt-5.5-pro",
|
|
409
|
+
"openrouter": "openai/gpt-5.5-pro"
|
|
410
|
+
},
|
|
380
411
|
"gpt-audio": {
|
|
381
412
|
"openai": "gpt-audio",
|
|
382
413
|
"openrouter": "openai/gpt-audio"
|
|
@@ -385,6 +416,14 @@
|
|
|
385
416
|
"openai": "gpt-audio-mini",
|
|
386
417
|
"openrouter": "openai/gpt-audio-mini"
|
|
387
418
|
},
|
|
419
|
+
"lyria-3-clip-preview": {
|
|
420
|
+
"gemini": "lyria-3-clip-preview",
|
|
421
|
+
"openrouter": "google/lyria-3-clip-preview"
|
|
422
|
+
},
|
|
423
|
+
"lyria-3-pro-preview": {
|
|
424
|
+
"gemini": "lyria-3-pro-preview",
|
|
425
|
+
"openrouter": "google/lyria-3-pro-preview"
|
|
426
|
+
},
|
|
388
427
|
"o1": {
|
|
389
428
|
"openai": "o1",
|
|
390
429
|
"openrouter": "openai/o1"
|