ruby_llm 1.12.0 → 1.13.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 +2 -0
- data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +1 -1
- data/lib/generators/ruby_llm/generator_helpers.rb +4 -0
- data/lib/generators/ruby_llm/install/install_generator.rb +5 -4
- data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +1 -1
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +1 -1
- data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +1 -6
- data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +1 -1
- data/lib/ruby_llm/active_record/acts_as.rb +8 -4
- data/lib/ruby_llm/active_record/acts_as_legacy.rb +85 -20
- data/lib/ruby_llm/active_record/chat_methods.rb +67 -16
- data/lib/ruby_llm/agent.rb +39 -8
- data/lib/ruby_llm/aliases.json +19 -9
- data/lib/ruby_llm/chat.rb +107 -11
- data/lib/ruby_llm/configuration.rb +18 -0
- data/lib/ruby_llm/connection.rb +10 -4
- data/lib/ruby_llm/content.rb +6 -2
- data/lib/ruby_llm/error.rb +32 -1
- data/lib/ruby_llm/message.rb +5 -3
- data/lib/ruby_llm/model/info.rb +1 -1
- data/lib/ruby_llm/models.json +3535 -2894
- data/lib/ruby_llm/models.rb +5 -3
- data/lib/ruby_llm/provider.rb +5 -1
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +22 -4
- data/lib/ruby_llm/providers/anthropic/chat.rb +22 -5
- data/lib/ruby_llm/providers/anthropic/models.rb +1 -1
- data/lib/ruby_llm/providers/anthropic/tools.rb +20 -0
- data/lib/ruby_llm/providers/anthropic.rb +1 -1
- data/lib/ruby_llm/providers/azure/chat.rb +1 -1
- data/lib/ruby_llm/providers/azure/embeddings.rb +1 -1
- data/lib/ruby_llm/providers/azure/models.rb +1 -1
- data/lib/ruby_llm/providers/azure.rb +88 -0
- data/lib/ruby_llm/providers/bedrock/chat.rb +50 -5
- data/lib/ruby_llm/providers/bedrock/models.rb +17 -1
- data/lib/ruby_llm/providers/bedrock/streaming.rb +8 -4
- data/lib/ruby_llm/providers/bedrock.rb +5 -1
- data/lib/ruby_llm/providers/deepseek/capabilities.rb +8 -0
- data/lib/ruby_llm/providers/deepseek.rb +1 -1
- data/lib/ruby_llm/providers/gemini/capabilities.rb +8 -0
- data/lib/ruby_llm/providers/gemini/chat.rb +19 -4
- data/lib/ruby_llm/providers/gemini/images.rb +1 -1
- data/lib/ruby_llm/providers/gemini/streaming.rb +1 -1
- data/lib/ruby_llm/providers/gemini/tools.rb +19 -0
- data/lib/ruby_llm/providers/gpustack/capabilities.rb +20 -0
- data/lib/ruby_llm/providers/gpustack.rb +4 -0
- data/lib/ruby_llm/providers/mistral/capabilities.rb +8 -0
- data/lib/ruby_llm/providers/mistral/chat.rb +2 -1
- data/lib/ruby_llm/providers/ollama/capabilities.rb +20 -0
- data/lib/ruby_llm/providers/ollama.rb +7 -1
- data/lib/ruby_llm/providers/openai/capabilities.rb +10 -2
- data/lib/ruby_llm/providers/openai/chat.rb +15 -5
- data/lib/ruby_llm/providers/openai/media.rb +4 -1
- data/lib/ruby_llm/providers/openai/temperature.rb +2 -2
- data/lib/ruby_llm/providers/openai/tools.rb +27 -2
- data/lib/ruby_llm/providers/openrouter/chat.rb +19 -5
- data/lib/ruby_llm/providers/openrouter/images.rb +69 -0
- data/lib/ruby_llm/providers/openrouter.rb +31 -1
- data/lib/ruby_llm/providers/vertexai/models.rb +1 -1
- data/lib/ruby_llm/providers/vertexai.rb +14 -6
- data/lib/ruby_llm/stream_accumulator.rb +10 -5
- data/lib/ruby_llm/streaming.rb +6 -6
- data/lib/ruby_llm/tool.rb +48 -3
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/tasks/models.rake +33 -7
- data/lib/tasks/release.rake +1 -1
- data/lib/tasks/ruby_llm.rake +7 -0
- data/lib/tasks/vcr.rake +1 -1
- metadata +8 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: efa046d43a24a45287ca68b9ec54c12374dd7a716a41bda2226974925067f547
|
|
4
|
+
data.tar.gz: e7d5187b9cb8d543e8b3b6b2590569cdcedec745f7ff35b6944d68768f803a6c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 61a499df157a4276b21aaaebab88518ad8a57a497dc032307bce6a02522ae45eccad01e7989ae1dfafb7149502eb5878d1604cba99ab3f4e2c0a34ef33e43c56
|
|
7
|
+
data.tar.gz: e2a03066ca8fdc265316bff01a9afed4c16a8e9bb32d7ed7f05bb594a00b3897c621039800b7fa8a75eb729b4511af133e240fdd054065d9aa82617899664595
|
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@ class <%= chat_job_class_name %> < ApplicationJob
|
|
|
3
3
|
<%= chat_variable_name %> = <%= chat_model_name %>.find(<%= chat_variable_name %>_id)
|
|
4
4
|
|
|
5
5
|
<%= chat_variable_name %>.ask(content) do |chunk|
|
|
6
|
-
if chunk.content && !chunk.content.
|
|
6
|
+
if chunk.content && !chunk.content.empty?
|
|
7
7
|
<%= message_variable_name %> = <%= chat_variable_name %>.<%= message_table_name %>.last
|
|
8
8
|
<%= message_variable_name %>.broadcast_append_chunk(chunk.content)
|
|
9
9
|
end
|
|
@@ -121,6 +121,10 @@ module RubyLLM
|
|
|
121
121
|
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
+
def create_migration_class_name(table_name)
|
|
125
|
+
"create_#{table_name}".camelize
|
|
126
|
+
end
|
|
127
|
+
|
|
124
128
|
def postgresql?
|
|
125
129
|
::ActiveRecord::Base.connection.adapter_name.downcase.include?('postgresql')
|
|
126
130
|
rescue StandardError
|
|
@@ -77,12 +77,13 @@ module RubyLLM
|
|
|
77
77
|
|
|
78
78
|
say "\n Next steps:", :yellow
|
|
79
79
|
say ' 1. Run: rails db:migrate'
|
|
80
|
-
say ' 2.
|
|
80
|
+
say ' 2. Run: rails ruby_llm:load_models'
|
|
81
|
+
say ' 3. Set your API keys in config/initializers/ruby_llm.rb'
|
|
81
82
|
|
|
82
|
-
say "
|
|
83
|
+
say " 4. Start chatting: #{chat_model_name}.create!(model: 'gpt-4.1-nano').ask('Hello!')"
|
|
83
84
|
|
|
84
|
-
say "\n 🚀 Model registry
|
|
85
|
-
say ' Models
|
|
85
|
+
say "\n 🚀 Model registry supports database + JSON fallback!", :cyan
|
|
86
|
+
say ' Models load from database when present, otherwise from models.json'
|
|
86
87
|
say ' Pass model names as strings - RubyLLM handles the rest!'
|
|
87
88
|
say " Specify provider when needed: Chat.create!(model: 'gemini-2.5-flash', provider: 'vertexai')"
|
|
88
89
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class <%= create_migration_class_name(chat_table_name) %> < ActiveRecord::Migration<%= migration_version %>
|
|
2
2
|
def change
|
|
3
3
|
create_table :<%= chat_table_name %> do |t|
|
|
4
4
|
t.timestamps
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class <%= create_migration_class_name(message_table_name) %> < ActiveRecord::Migration<%= migration_version %>
|
|
2
2
|
def change
|
|
3
3
|
create_table :<%= message_table_name %> do |t|
|
|
4
4
|
t.string :role, null: false
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class <%= create_migration_class_name(model_table_name) %> < ActiveRecord::Migration<%= migration_version %>
|
|
2
2
|
def change
|
|
3
3
|
create_table :<%= model_table_name %> do |t|
|
|
4
4
|
t.string :model_id, null: false
|
|
@@ -36,10 +36,5 @@ class Create<%= model_model_name.gsub('::', '').pluralize %> < ActiveRecord::Mig
|
|
|
36
36
|
<% end %>
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
# Load models from JSON
|
|
40
|
-
say_with_time "Loading models from models.json" do
|
|
41
|
-
RubyLLM.models.load_from_json!
|
|
42
|
-
<%= model_model_name %>.save_to_database
|
|
43
|
-
end
|
|
44
39
|
end
|
|
45
40
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<%#- # Migration for creating tool_calls table with database-specific JSON handling -%>
|
|
2
|
-
class
|
|
2
|
+
class <%= create_migration_class_name(tool_call_table_name) %> < ActiveRecord::Migration<%= migration_version %>
|
|
3
3
|
def change
|
|
4
4
|
create_table :<%= tool_call_table_name %> do |t|
|
|
5
5
|
t.string :tool_call_id, null: false
|
|
@@ -12,15 +12,21 @@ module RubyLLM
|
|
|
12
12
|
# Monkey-patch Models to use database when ActsAs is active
|
|
13
13
|
RubyLLM::Models.class_eval do
|
|
14
14
|
def self.load_models
|
|
15
|
-
read_from_database
|
|
15
|
+
database_models = read_from_database
|
|
16
|
+
return database_models if database_models.any?
|
|
17
|
+
|
|
18
|
+
RubyLLM.logger.debug { 'Model registry is empty in database, falling back to JSON registry' }
|
|
19
|
+
read_from_json
|
|
16
20
|
rescue StandardError => e
|
|
17
|
-
RubyLLM.logger.debug "Failed to load models from database: #{e.message}, falling back to JSON"
|
|
21
|
+
RubyLLM.logger.debug { "Failed to load models from database: #{e.message}, falling back to JSON" }
|
|
18
22
|
read_from_json
|
|
19
23
|
end
|
|
20
24
|
|
|
21
25
|
def self.read_from_database
|
|
22
26
|
model_class = RubyLLM.config.model_registry_class
|
|
23
27
|
model_class = model_class.constantize if model_class.is_a?(String)
|
|
28
|
+
return [] unless model_class.table_exists?
|
|
29
|
+
|
|
24
30
|
model_class.all.map(&:to_llm)
|
|
25
31
|
end
|
|
26
32
|
|
|
@@ -53,8 +59,6 @@ module RubyLLM
|
|
|
53
59
|
foreign_key: model_foreign_key,
|
|
54
60
|
optional: true
|
|
55
61
|
|
|
56
|
-
delegate :add_message, to: :to_llm
|
|
57
|
-
|
|
58
62
|
define_method :messages_association do
|
|
59
63
|
send(messages_association_name)
|
|
60
64
|
end
|
|
@@ -18,8 +18,6 @@ module RubyLLM
|
|
|
18
18
|
class_name: @message_class,
|
|
19
19
|
inverse_of: :chat,
|
|
20
20
|
dependent: :destroy
|
|
21
|
-
|
|
22
|
-
delegate :add_message, to: :to_llm
|
|
23
21
|
end
|
|
24
22
|
|
|
25
23
|
def acts_as_message(chat_class: 'Chat',
|
|
@@ -99,6 +97,7 @@ module RubyLLM
|
|
|
99
97
|
ordered_messages.each do |msg|
|
|
100
98
|
@chat.add_message(msg.to_llm)
|
|
101
99
|
end
|
|
100
|
+
reapply_runtime_instructions(@chat)
|
|
102
101
|
|
|
103
102
|
setup_persistence_callbacks
|
|
104
103
|
end
|
|
@@ -111,6 +110,14 @@ module RubyLLM
|
|
|
111
110
|
self
|
|
112
111
|
end
|
|
113
112
|
|
|
113
|
+
def with_runtime_instructions(instructions, append: false, replace: nil)
|
|
114
|
+
append = append_instructions?(append:, replace:)
|
|
115
|
+
store_runtime_instruction(instructions, append:)
|
|
116
|
+
|
|
117
|
+
to_llm.with_instructions(instructions, append:, replace:)
|
|
118
|
+
self
|
|
119
|
+
end
|
|
120
|
+
|
|
114
121
|
def with_tool(...)
|
|
115
122
|
to_llm.with_tool(...)
|
|
116
123
|
self
|
|
@@ -185,14 +192,34 @@ module RubyLLM
|
|
|
185
192
|
self
|
|
186
193
|
end
|
|
187
194
|
|
|
188
|
-
def
|
|
189
|
-
|
|
190
|
-
|
|
195
|
+
def add_message(message_or_attributes)
|
|
196
|
+
llm_message = message_or_attributes.is_a?(RubyLLM::Message) ? message_or_attributes : RubyLLM::Message.new(message_or_attributes)
|
|
197
|
+
content, attachments_to_persist = prepare_content_for_storage(llm_message.content)
|
|
198
|
+
|
|
199
|
+
attrs = { role: llm_message.role, content: }
|
|
200
|
+
tool_call_foreign_key = messages.klass.tool_call_foreign_key
|
|
201
|
+
if llm_message.tool_call_id && tool_call_foreign_key
|
|
202
|
+
tool_call_id = find_tool_call_id(llm_message.tool_call_id)
|
|
203
|
+
attrs[tool_call_foreign_key] = tool_call_id if tool_call_id
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
message_record = messages.create!(attrs)
|
|
207
|
+
persist_content(message_record, attachments_to_persist) if attachments_to_persist.present?
|
|
208
|
+
persist_tool_calls(llm_message.tool_calls, message_record:) if llm_message.tool_calls.present?
|
|
209
|
+
|
|
191
210
|
message_record
|
|
192
211
|
end
|
|
193
212
|
|
|
194
|
-
def
|
|
195
|
-
|
|
213
|
+
def create_user_message(content, with: nil)
|
|
214
|
+
RubyLLM.logger.warn(
|
|
215
|
+
'`create_user_message` is deprecated and will be removed in RubyLLM 2.0. ' \
|
|
216
|
+
'Use `add_message(role: :user, content: ...)` instead.'
|
|
217
|
+
)
|
|
218
|
+
add_message(role: :user, content: build_content(content, with))
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def ask(message = nil, with: nil, &)
|
|
222
|
+
add_message(role: :user, content: build_content(message, with))
|
|
196
223
|
complete(&)
|
|
197
224
|
end
|
|
198
225
|
|
|
@@ -267,6 +294,26 @@ module RubyLLM
|
|
|
267
294
|
system_messages + non_system_messages
|
|
268
295
|
end
|
|
269
296
|
|
|
297
|
+
def runtime_instructions
|
|
298
|
+
@runtime_instructions ||= []
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def store_runtime_instruction(instructions, append:)
|
|
302
|
+
if append
|
|
303
|
+
runtime_instructions << instructions
|
|
304
|
+
else
|
|
305
|
+
@runtime_instructions = [instructions]
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def reapply_runtime_instructions(chat)
|
|
310
|
+
return if runtime_instructions.empty?
|
|
311
|
+
|
|
312
|
+
first, *rest = runtime_instructions
|
|
313
|
+
chat.with_instructions(first)
|
|
314
|
+
rest.each { |instruction| chat.with_instructions(instruction, append: true) }
|
|
315
|
+
end
|
|
316
|
+
|
|
270
317
|
def setup_persistence_callbacks
|
|
271
318
|
return @chat if @chat.instance_variable_get(:@_persistence_callbacks_setup)
|
|
272
319
|
|
|
@@ -281,21 +328,13 @@ module RubyLLM
|
|
|
281
328
|
@message = messages.create!(role: :assistant, content: '')
|
|
282
329
|
end
|
|
283
330
|
|
|
284
|
-
def persist_message_completion(message)
|
|
331
|
+
def persist_message_completion(message)
|
|
285
332
|
return unless message
|
|
286
333
|
|
|
287
334
|
tool_call_id = find_tool_call_id(message.tool_call_id) if message.tool_call_id
|
|
288
335
|
|
|
289
336
|
transaction do
|
|
290
|
-
content = message.content
|
|
291
|
-
attachments_to_persist = nil
|
|
292
|
-
|
|
293
|
-
if content.is_a?(RubyLLM::Content)
|
|
294
|
-
attachments_to_persist = content.attachments if content.attachments.any?
|
|
295
|
-
content = content.text
|
|
296
|
-
elsif content.is_a?(Hash) || content.is_a?(Array)
|
|
297
|
-
content = content.to_json
|
|
298
|
-
end
|
|
337
|
+
content, attachments_to_persist = prepare_content_for_storage(message.content)
|
|
299
338
|
|
|
300
339
|
@message.update!(
|
|
301
340
|
role: message.role,
|
|
@@ -312,14 +351,17 @@ module RubyLLM
|
|
|
312
351
|
end
|
|
313
352
|
end
|
|
314
353
|
|
|
315
|
-
def persist_tool_calls(tool_calls)
|
|
316
|
-
|
|
354
|
+
def persist_tool_calls(tool_calls, message_record: @message)
|
|
355
|
+
tool_call_assoc = message_record.respond_to?(:tool_calls) ? message_record.tool_calls : nil
|
|
356
|
+
return unless tool_call_assoc
|
|
357
|
+
|
|
358
|
+
supports_thought_signature = tool_call_assoc.klass.column_names.include?('thought_signature')
|
|
317
359
|
|
|
318
360
|
tool_calls.each_value do |tool_call|
|
|
319
361
|
attributes = tool_call.to_h
|
|
320
362
|
attributes.delete(:thought_signature) unless supports_thought_signature
|
|
321
363
|
attributes[:tool_call_id] = attributes.delete(:id)
|
|
322
|
-
|
|
364
|
+
tool_call_assoc.create!(**attributes)
|
|
323
365
|
end
|
|
324
366
|
end
|
|
325
367
|
|
|
@@ -363,6 +405,29 @@ module RubyLLM
|
|
|
363
405
|
RubyLLM.logger.warn "Failed to process attachment #{source}: #{e.message}"
|
|
364
406
|
nil
|
|
365
407
|
end
|
|
408
|
+
|
|
409
|
+
def build_content(message, attachments)
|
|
410
|
+
return message if content_like?(message)
|
|
411
|
+
|
|
412
|
+
RubyLLM::Content.new(message, attachments)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def content_like?(object)
|
|
416
|
+
object.is_a?(RubyLLM::Content) || object.is_a?(RubyLLM::Content::Raw)
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def prepare_content_for_storage(content)
|
|
420
|
+
attachments = nil
|
|
421
|
+
|
|
422
|
+
if content.is_a?(RubyLLM::Content)
|
|
423
|
+
attachments = content.attachments if content.attachments.any?
|
|
424
|
+
[content.text, attachments]
|
|
425
|
+
elsif content.is_a?(Hash) || content.is_a?(Array)
|
|
426
|
+
[content.to_json, attachments]
|
|
427
|
+
else
|
|
428
|
+
[content, attachments]
|
|
429
|
+
end
|
|
430
|
+
end
|
|
366
431
|
end
|
|
367
432
|
|
|
368
433
|
# Methods mixed into message models.
|
|
@@ -79,7 +79,8 @@ module RubyLLM
|
|
|
79
79
|
model_record = model_association
|
|
80
80
|
@chat ||= (context || RubyLLM).chat(
|
|
81
81
|
model: model_record.model_id,
|
|
82
|
-
provider: model_record.provider.to_sym
|
|
82
|
+
provider: model_record.provider.to_sym,
|
|
83
|
+
assume_model_exists: assume_model_exists || false
|
|
83
84
|
)
|
|
84
85
|
@chat.reset_messages!
|
|
85
86
|
|
|
@@ -87,6 +88,7 @@ module RubyLLM
|
|
|
87
88
|
ordered_messages.each do |msg|
|
|
88
89
|
@chat.add_message(msg.to_llm)
|
|
89
90
|
end
|
|
91
|
+
reapply_runtime_instructions(@chat)
|
|
90
92
|
|
|
91
93
|
setup_persistence_callbacks
|
|
92
94
|
end
|
|
@@ -99,6 +101,14 @@ module RubyLLM
|
|
|
99
101
|
self
|
|
100
102
|
end
|
|
101
103
|
|
|
104
|
+
def with_runtime_instructions(instructions, append: false, replace: nil)
|
|
105
|
+
append = append_instructions?(append:, replace:)
|
|
106
|
+
store_runtime_instruction(instructions, append:)
|
|
107
|
+
|
|
108
|
+
to_llm.with_instructions(instructions, append:, replace:)
|
|
109
|
+
self
|
|
110
|
+
end
|
|
111
|
+
|
|
102
112
|
def with_tool(...)
|
|
103
113
|
to_llm.with_tool(...)
|
|
104
114
|
self
|
|
@@ -178,22 +188,32 @@ module RubyLLM
|
|
|
178
188
|
self
|
|
179
189
|
end
|
|
180
190
|
|
|
181
|
-
def
|
|
182
|
-
|
|
191
|
+
def add_message(message_or_attributes)
|
|
192
|
+
llm_message = message_or_attributes.is_a?(RubyLLM::Message) ? message_or_attributes : RubyLLM::Message.new(message_or_attributes)
|
|
193
|
+
content_text, attachments, content_raw = prepare_content_for_storage(llm_message.content)
|
|
183
194
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
195
|
+
attrs = { role: llm_message.role, content: content_text }
|
|
196
|
+
parent_tool_call_assoc = messages_association.klass.reflect_on_association(:parent_tool_call)
|
|
197
|
+
if parent_tool_call_assoc && llm_message.tool_call_id
|
|
198
|
+
tool_call_id = find_tool_call_id(llm_message.tool_call_id)
|
|
199
|
+
attrs[parent_tool_call_assoc.foreign_key] = tool_call_id if tool_call_id
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
message_record = messages_association.create!(attrs)
|
|
203
|
+
message_record.update!(content_raw:) if message_record.respond_to?(:content_raw=)
|
|
188
204
|
|
|
189
|
-
persist_content(message_record, with) if with.present?
|
|
190
205
|
persist_content(message_record, attachments) if attachments.present?
|
|
206
|
+
persist_tool_calls(llm_message.tool_calls, message_record:) if llm_message.tool_calls.present?
|
|
191
207
|
|
|
192
208
|
message_record
|
|
193
209
|
end
|
|
194
210
|
|
|
195
|
-
def
|
|
196
|
-
|
|
211
|
+
def create_user_message(content, with: nil)
|
|
212
|
+
add_message(role: :user, content: build_content(content, with))
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def ask(message = nil, with: nil, &)
|
|
216
|
+
add_message(role: :user, content: build_content(message, with))
|
|
197
217
|
complete(&)
|
|
198
218
|
end
|
|
199
219
|
|
|
@@ -223,9 +243,10 @@ module RubyLLM
|
|
|
223
243
|
if last.tool_call?
|
|
224
244
|
last.destroy
|
|
225
245
|
elsif last.tool_result?
|
|
226
|
-
tool_call_message = last.parent_tool_call.
|
|
227
|
-
expected_results = tool_call_message.
|
|
228
|
-
|
|
246
|
+
tool_call_message = last.parent_tool_call.message_association
|
|
247
|
+
expected_results = tool_call_message.tool_calls_association.pluck(:id)
|
|
248
|
+
fk_column = tool_call_message.class.reflections['tool_results'].foreign_key
|
|
249
|
+
actual_results = tool_call_message.tool_results.pluck(fk_column)
|
|
229
250
|
|
|
230
251
|
if expected_results.sort != actual_results.sort
|
|
231
252
|
tool_call_message.tool_results.each(&:destroy)
|
|
@@ -278,6 +299,26 @@ module RubyLLM
|
|
|
278
299
|
system_messages + non_system_messages
|
|
279
300
|
end
|
|
280
301
|
|
|
302
|
+
def runtime_instructions
|
|
303
|
+
@runtime_instructions ||= []
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def store_runtime_instruction(instructions, append:)
|
|
307
|
+
if append
|
|
308
|
+
runtime_instructions << instructions
|
|
309
|
+
else
|
|
310
|
+
@runtime_instructions = [instructions]
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def reapply_runtime_instructions(chat)
|
|
315
|
+
return if runtime_instructions.empty?
|
|
316
|
+
|
|
317
|
+
first, *rest = runtime_instructions
|
|
318
|
+
chat.with_instructions(first)
|
|
319
|
+
rest.each { |instruction| chat.with_instructions(instruction, append: true) }
|
|
320
|
+
end
|
|
321
|
+
|
|
281
322
|
def persist_new_message
|
|
282
323
|
@message = messages_association.create!(role: :assistant, content: '')
|
|
283
324
|
end
|
|
@@ -323,15 +364,15 @@ module RubyLLM
|
|
|
323
364
|
end
|
|
324
365
|
# rubocop:enable Metrics/PerceivedComplexity
|
|
325
366
|
|
|
326
|
-
def persist_tool_calls(tool_calls)
|
|
327
|
-
tool_call_klass =
|
|
367
|
+
def persist_tool_calls(tool_calls, message_record: @message)
|
|
368
|
+
tool_call_klass = message_record.tool_calls_association.klass
|
|
328
369
|
supports_thought_signature = tool_call_klass.column_names.include?('thought_signature')
|
|
329
370
|
|
|
330
371
|
tool_calls.each_value do |tool_call|
|
|
331
372
|
attributes = tool_call.to_h
|
|
332
373
|
attributes.delete(:thought_signature) unless supports_thought_signature
|
|
333
374
|
attributes[:tool_call_id] = attributes.delete(:id)
|
|
334
|
-
|
|
375
|
+
message_record.tool_calls_association.create!(**attributes)
|
|
335
376
|
end
|
|
336
377
|
end
|
|
337
378
|
|
|
@@ -386,6 +427,16 @@ module RubyLLM
|
|
|
386
427
|
nil
|
|
387
428
|
end
|
|
388
429
|
|
|
430
|
+
def build_content(message, attachments)
|
|
431
|
+
return message if content_like?(message)
|
|
432
|
+
|
|
433
|
+
RubyLLM::Content.new(message, attachments)
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def content_like?(object)
|
|
437
|
+
object.is_a?(RubyLLM::Content) || object.is_a?(RubyLLM::Content::Raw)
|
|
438
|
+
end
|
|
439
|
+
|
|
389
440
|
def prepare_content_for_storage(content)
|
|
390
441
|
attachments = nil
|
|
391
442
|
content_raw = nil
|
data/lib/ruby_llm/agent.rb
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'erb'
|
|
4
|
+
require 'forwardable'
|
|
4
5
|
require 'pathname'
|
|
6
|
+
require 'ruby_llm/schema'
|
|
5
7
|
|
|
6
8
|
module RubyLLM
|
|
7
9
|
# Base class for simple, class-configured agents.
|
|
8
10
|
class Agent
|
|
11
|
+
extend Forwardable
|
|
9
12
|
include Enumerable
|
|
10
13
|
|
|
11
14
|
class << self
|
|
@@ -136,7 +139,10 @@ module RubyLLM
|
|
|
136
139
|
|
|
137
140
|
def render_prompt(name, chat:, inputs:, locals:)
|
|
138
141
|
path = prompt_path_for(name)
|
|
139
|
-
|
|
142
|
+
unless File.exist?(path)
|
|
143
|
+
raise RubyLLM::PromptNotFoundError,
|
|
144
|
+
"Prompt file not found for #{self}: #{path}. Create the file or use inline instructions."
|
|
145
|
+
end
|
|
140
146
|
|
|
141
147
|
resolved_locals = resolve_prompt_locals(locals, runtime: runtime_context(chat:, inputs:), chat:, inputs:)
|
|
142
148
|
ERB.new(File.read(path)).result_with_hash(resolved_locals)
|
|
@@ -175,7 +181,10 @@ module RubyLLM
|
|
|
175
181
|
value = resolved_instructions_value(chat_object, runtime, inputs:)
|
|
176
182
|
return if value.nil?
|
|
177
183
|
|
|
178
|
-
instruction_target(chat_object, persist:)
|
|
184
|
+
target = instruction_target(chat_object, persist:)
|
|
185
|
+
return target.with_runtime_instructions(value) if use_runtime_instructions?(target, persist:)
|
|
186
|
+
|
|
187
|
+
target.with_instructions(value)
|
|
179
188
|
end
|
|
180
189
|
|
|
181
190
|
def apply_tools(llm_chat, runtime)
|
|
@@ -202,10 +211,21 @@ module RubyLLM
|
|
|
202
211
|
end
|
|
203
212
|
|
|
204
213
|
def apply_schema(llm_chat, runtime)
|
|
205
|
-
value =
|
|
214
|
+
value = resolved_schema_value(runtime)
|
|
206
215
|
llm_chat.with_schema(value) if value
|
|
207
216
|
end
|
|
208
217
|
|
|
218
|
+
def resolved_schema_value(runtime)
|
|
219
|
+
value = schema
|
|
220
|
+
return value unless value.is_a?(Proc)
|
|
221
|
+
|
|
222
|
+
evaluate(value, runtime)
|
|
223
|
+
rescue NoMethodError => e
|
|
224
|
+
raise unless e.receiver.equal?(runtime)
|
|
225
|
+
|
|
226
|
+
RubyLLM::Schema.create(&value)
|
|
227
|
+
end
|
|
228
|
+
|
|
209
229
|
def llm_chat_for(chat_object)
|
|
210
230
|
chat_object.respond_to?(:to_llm) ? chat_object.to_llm : chat_object
|
|
211
231
|
end
|
|
@@ -215,7 +235,7 @@ module RubyLLM
|
|
|
215
235
|
end
|
|
216
236
|
|
|
217
237
|
def resolved_instructions_value(chat_object, runtime, inputs:)
|
|
218
|
-
value = evaluate(instructions, runtime)
|
|
238
|
+
value = evaluate(@instructions, runtime)
|
|
219
239
|
return value unless prompt_instruction?(value)
|
|
220
240
|
|
|
221
241
|
runtime.prompt(
|
|
@@ -232,10 +252,20 @@ module RubyLLM
|
|
|
232
252
|
if persist || !chat_object.respond_to?(:to_llm)
|
|
233
253
|
chat_object
|
|
234
254
|
else
|
|
235
|
-
chat_object
|
|
255
|
+
runtime_instruction_target(chat_object)
|
|
236
256
|
end
|
|
237
257
|
end
|
|
238
258
|
|
|
259
|
+
def runtime_instruction_target(chat_object)
|
|
260
|
+
return chat_object if chat_object.respond_to?(:with_runtime_instructions)
|
|
261
|
+
|
|
262
|
+
chat_object.to_llm
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def use_runtime_instructions?(target, persist:)
|
|
266
|
+
!persist && target.respond_to?(:with_runtime_instructions)
|
|
267
|
+
end
|
|
268
|
+
|
|
239
269
|
def resolve_prompt_locals(locals, runtime:, chat:, inputs:)
|
|
240
270
|
base = { chat: chat }.merge(inputs)
|
|
241
271
|
evaluated = locals.each_with_object({}) do |(key, value), acc|
|
|
@@ -316,8 +346,9 @@ module RubyLLM
|
|
|
316
346
|
|
|
317
347
|
attr_reader :chat
|
|
318
348
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
349
|
+
def_delegators :chat, :model, :messages, :tools, :params, :headers, :schema, :ask, :say, :with_tool, :with_tools,
|
|
350
|
+
:with_model, :with_temperature, :with_thinking, :with_context, :with_params, :with_headers,
|
|
351
|
+
:with_schema, :on_new_message, :on_end_message, :on_tool_call, :on_tool_result, :each, :complete,
|
|
352
|
+
:add_message, :reset_messages!
|
|
322
353
|
end
|
|
323
354
|
end
|
data/lib/ruby_llm/aliases.json
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
"chatgpt-4o": {
|
|
3
|
-
"openai": "chatgpt-4o-latest",
|
|
4
|
-
"openrouter": "openai/chatgpt-4o-latest"
|
|
5
|
-
},
|
|
6
2
|
"claude-3-5-haiku": {
|
|
7
3
|
"anthropic": "claude-3-5-haiku-20241022",
|
|
8
4
|
"openrouter": "anthropic/claude-3.5-haiku",
|
|
@@ -31,7 +27,7 @@
|
|
|
31
27
|
},
|
|
32
28
|
"claude-3-opus": {
|
|
33
29
|
"anthropic": "claude-3-opus-20240229",
|
|
34
|
-
"bedrock": "anthropic.claude-3-opus-20240229-v1:0
|
|
30
|
+
"bedrock": "anthropic.claude-3-opus-20240229-v1:0"
|
|
35
31
|
},
|
|
36
32
|
"claude-3-sonnet": {
|
|
37
33
|
"anthropic": "claude-3-sonnet-20240229",
|
|
@@ -82,6 +78,11 @@
|
|
|
82
78
|
"bedrock": "anthropic.claude-sonnet-4-5-20250929-v1:0",
|
|
83
79
|
"azure": "claude-sonnet-4-5-20250929"
|
|
84
80
|
},
|
|
81
|
+
"claude-sonnet-4-6": {
|
|
82
|
+
"anthropic": "claude-sonnet-4-6",
|
|
83
|
+
"openrouter": "anthropic/claude-sonnet-4.6",
|
|
84
|
+
"bedrock": "anthropic.claude-sonnet-4-6"
|
|
85
|
+
},
|
|
85
86
|
"deepseek-chat": {
|
|
86
87
|
"deepseek": "deepseek-chat",
|
|
87
88
|
"openrouter": "deepseek/deepseek-chat"
|
|
@@ -181,14 +182,19 @@
|
|
|
181
182
|
"openrouter": "google/gemini-3-pro-preview",
|
|
182
183
|
"vertexai": "gemini-3-pro-preview"
|
|
183
184
|
},
|
|
185
|
+
"gemini-3.1-pro-preview": {
|
|
186
|
+
"gemini": "gemini-3.1-pro-preview",
|
|
187
|
+
"openrouter": "google/gemini-3.1-pro-preview",
|
|
188
|
+
"vertexai": "gemini-3.1-pro-preview"
|
|
189
|
+
},
|
|
190
|
+
"gemini-3.1-pro-preview-customtools": {
|
|
191
|
+
"gemini": "gemini-3.1-pro-preview-customtools",
|
|
192
|
+
"vertexai": "gemini-3.1-pro-preview-customtools"
|
|
193
|
+
},
|
|
184
194
|
"gemini-embedding-001": {
|
|
185
195
|
"gemini": "gemini-embedding-001",
|
|
186
196
|
"vertexai": "gemini-embedding-001"
|
|
187
197
|
},
|
|
188
|
-
"gemini-exp-1206": {
|
|
189
|
-
"gemini": "gemini-exp-1206",
|
|
190
|
-
"vertexai": "gemini-exp-1206"
|
|
191
|
-
},
|
|
192
198
|
"gemini-flash": {
|
|
193
199
|
"gemini": "gemini-flash-latest",
|
|
194
200
|
"vertexai": "gemini-flash-latest"
|
|
@@ -347,6 +353,10 @@
|
|
|
347
353
|
"openai": "gpt-5.2-pro",
|
|
348
354
|
"openrouter": "openai/gpt-5.2-pro"
|
|
349
355
|
},
|
|
356
|
+
"gpt-5.3-codex": {
|
|
357
|
+
"openai": "gpt-5.3-codex",
|
|
358
|
+
"openrouter": "openai/gpt-5.3-codex"
|
|
359
|
+
},
|
|
350
360
|
"gpt-audio": {
|
|
351
361
|
"openai": "gpt-audio",
|
|
352
362
|
"openrouter": "openai/gpt-audio"
|