ruby_llm 1.11.0 → 1.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +12 -0
- data/lib/ruby_llm/active_record/acts_as.rb +0 -2
- data/lib/ruby_llm/active_record/acts_as_legacy.rb +97 -27
- data/lib/ruby_llm/active_record/chat_methods.rb +73 -19
- data/lib/ruby_llm/agent.rb +326 -0
- data/lib/ruby_llm/aliases.json +47 -29
- data/lib/ruby_llm/chat.rb +27 -3
- data/lib/ruby_llm/configuration.rb +3 -0
- data/lib/ruby_llm/content.rb +6 -0
- data/lib/ruby_llm/models.json +19090 -5190
- data/lib/ruby_llm/models.rb +35 -6
- data/lib/ruby_llm/provider.rb +8 -0
- data/lib/ruby_llm/providers/azure/chat.rb +29 -0
- data/lib/ruby_llm/providers/azure/embeddings.rb +24 -0
- data/lib/ruby_llm/providers/azure/media.rb +45 -0
- data/lib/ruby_llm/providers/azure/models.rb +14 -0
- data/lib/ruby_llm/providers/azure.rb +56 -0
- data/lib/ruby_llm/providers/bedrock/auth.rb +122 -0
- data/lib/ruby_llm/providers/bedrock/chat.rb +296 -64
- data/lib/ruby_llm/providers/bedrock/media.rb +62 -33
- data/lib/ruby_llm/providers/bedrock/models.rb +88 -65
- data/lib/ruby_llm/providers/bedrock/streaming.rb +305 -8
- data/lib/ruby_llm/providers/bedrock.rb +61 -52
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm.rb +4 -0
- data/lib/tasks/models.rake +10 -5
- data/lib/tasks/vcr.rake +32 -0
- metadata +17 -17
- data/lib/ruby_llm/providers/bedrock/capabilities.rb +0 -167
- data/lib/ruby_llm/providers/bedrock/signing.rb +0 -831
- data/lib/ruby_llm/providers/bedrock/streaming/base.rb +0 -51
- data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +0 -128
- data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +0 -67
- data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +0 -85
- data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +0 -78
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6f2aa1f16058fca83243f2b098b0a3f454fb9383e410a00b04955cd5b48cbf54
|
|
4
|
+
data.tar.gz: fb4591fe16b50449dc1baf90f77a2b92baa986a51b9573f5c0b79dc231d9a9b9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 687200d2c127d604e0bbff56c888ef5bb5ab2938b585ec2f8959f77b74ad35ae0d0dbad588068ccccd3219ab0aa4208dbc47b8436606c7b12ec1de10cf2928c2
|
|
7
|
+
data.tar.gz: 9aab7e7a79aa98b2772e4a01f4269c17a1969755276fde9b0fb4b3311a81dd4ff3a4a1e4e4c4a1f0fa863a162caaa4f3d3cd05cc044cfc5116ceaaa878963737
|
data/README.md
CHANGED
|
@@ -95,6 +95,17 @@ end
|
|
|
95
95
|
chat.with_tool(Weather).ask "What's the weather in Berlin?"
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
+
```ruby
|
|
99
|
+
# Define an agent with instructions + tools
|
|
100
|
+
class WeatherAssistant < RubyLLM::Agent
|
|
101
|
+
model "gpt-4.1-nano"
|
|
102
|
+
instructions "Be concise and always use tools for weather."
|
|
103
|
+
tools Weather
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
WeatherAssistant.new.ask "What's the weather in Berlin?"
|
|
107
|
+
```
|
|
108
|
+
|
|
98
109
|
```ruby
|
|
99
110
|
# Get structured output
|
|
100
111
|
class ProductSchema < RubyLLM::Schema
|
|
@@ -118,6 +129,7 @@ response = chat.with_schema(ProductSchema).ask "Analyze this product", with: "pr
|
|
|
118
129
|
* **Embeddings:** Generate embeddings with `RubyLLM.embed`
|
|
119
130
|
* **Moderation:** Content safety with `RubyLLM.moderate`
|
|
120
131
|
* **Tools:** Let AI call your Ruby methods
|
|
132
|
+
* **Agents:** Reusable assistants with `RubyLLM::Agent`
|
|
121
133
|
* **Structured output:** JSON schemas that just work
|
|
122
134
|
* **Streaming:** Real-time responses with blocks
|
|
123
135
|
* **Rails:** ActiveRecord integration with `acts_as_chat`
|
|
@@ -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',
|
|
@@ -95,19 +93,19 @@ module RubyLLM
|
|
|
95
93
|
end
|
|
96
94
|
@chat.reset_messages!
|
|
97
95
|
|
|
98
|
-
messages.
|
|
96
|
+
ordered_messages = order_messages_for_llm(messages.to_a)
|
|
97
|
+
ordered_messages.each do |msg|
|
|
99
98
|
@chat.add_message(msg.to_llm)
|
|
100
99
|
end
|
|
101
100
|
|
|
102
101
|
setup_persistence_callbacks
|
|
103
102
|
end
|
|
104
103
|
|
|
105
|
-
def with_instructions(instructions, replace:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
to_llm.with_instructions(instructions)
|
|
104
|
+
def with_instructions(instructions, append: false, replace: nil)
|
|
105
|
+
append = append_instructions?(append:, replace:)
|
|
106
|
+
persist_system_instruction(instructions, append:)
|
|
107
|
+
|
|
108
|
+
to_llm.with_instructions(instructions, append:, replace:)
|
|
111
109
|
self
|
|
112
110
|
end
|
|
113
111
|
|
|
@@ -185,14 +183,34 @@ module RubyLLM
|
|
|
185
183
|
self
|
|
186
184
|
end
|
|
187
185
|
|
|
188
|
-
def
|
|
189
|
-
|
|
190
|
-
|
|
186
|
+
def add_message(message_or_attributes)
|
|
187
|
+
llm_message = message_or_attributes.is_a?(RubyLLM::Message) ? message_or_attributes : RubyLLM::Message.new(message_or_attributes)
|
|
188
|
+
content, attachments_to_persist = prepare_content_for_storage(llm_message.content)
|
|
189
|
+
|
|
190
|
+
attrs = { role: llm_message.role, content: }
|
|
191
|
+
tool_call_foreign_key = messages.klass.tool_call_foreign_key
|
|
192
|
+
if llm_message.tool_call_id && tool_call_foreign_key
|
|
193
|
+
tool_call_id = find_tool_call_id(llm_message.tool_call_id)
|
|
194
|
+
attrs[tool_call_foreign_key] = tool_call_id if tool_call_id
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
message_record = messages.create!(attrs)
|
|
198
|
+
persist_content(message_record, attachments_to_persist) if attachments_to_persist.present?
|
|
199
|
+
persist_tool_calls(llm_message.tool_calls, message_record:) if llm_message.tool_calls.present?
|
|
200
|
+
|
|
191
201
|
message_record
|
|
192
202
|
end
|
|
193
203
|
|
|
194
|
-
def
|
|
195
|
-
|
|
204
|
+
def create_user_message(content, with: nil)
|
|
205
|
+
RubyLLM.logger.warn(
|
|
206
|
+
'`create_user_message` is deprecated and will be removed in RubyLLM 2.0. ' \
|
|
207
|
+
'Use `add_message(role: :user, content: ...)` instead.'
|
|
208
|
+
)
|
|
209
|
+
add_message(role: :user, content: build_content(content, with))
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def ask(message = nil, with: nil, &)
|
|
213
|
+
add_message(role: :user, content: build_content(message, with))
|
|
196
214
|
complete(&)
|
|
197
215
|
end
|
|
198
216
|
|
|
@@ -233,6 +251,40 @@ module RubyLLM
|
|
|
233
251
|
end
|
|
234
252
|
end
|
|
235
253
|
|
|
254
|
+
def replace_persisted_system_instructions(instructions)
|
|
255
|
+
system_messages = messages.where(role: :system).order(:id).to_a
|
|
256
|
+
|
|
257
|
+
if system_messages.empty?
|
|
258
|
+
messages.create!(role: :system, content: instructions)
|
|
259
|
+
return
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
primary_message = system_messages.shift
|
|
263
|
+
primary_message.update!(content: instructions) if primary_message.content != instructions
|
|
264
|
+
system_messages.each(&:destroy!)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def append_instructions?(append:, replace:)
|
|
268
|
+
return append if replace.nil?
|
|
269
|
+
|
|
270
|
+
append || (replace == false)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def persist_system_instruction(instructions, append:)
|
|
274
|
+
transaction do
|
|
275
|
+
if append
|
|
276
|
+
messages.create!(role: :system, content: instructions)
|
|
277
|
+
else
|
|
278
|
+
replace_persisted_system_instructions(instructions)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def order_messages_for_llm(messages)
|
|
284
|
+
system_messages, non_system_messages = messages.partition { |msg| msg.role.to_s == 'system' }
|
|
285
|
+
system_messages + non_system_messages
|
|
286
|
+
end
|
|
287
|
+
|
|
236
288
|
def setup_persistence_callbacks
|
|
237
289
|
return @chat if @chat.instance_variable_get(:@_persistence_callbacks_setup)
|
|
238
290
|
|
|
@@ -247,21 +299,13 @@ module RubyLLM
|
|
|
247
299
|
@message = messages.create!(role: :assistant, content: '')
|
|
248
300
|
end
|
|
249
301
|
|
|
250
|
-
def persist_message_completion(message)
|
|
302
|
+
def persist_message_completion(message)
|
|
251
303
|
return unless message
|
|
252
304
|
|
|
253
305
|
tool_call_id = find_tool_call_id(message.tool_call_id) if message.tool_call_id
|
|
254
306
|
|
|
255
307
|
transaction do
|
|
256
|
-
content = message.content
|
|
257
|
-
attachments_to_persist = nil
|
|
258
|
-
|
|
259
|
-
if content.is_a?(RubyLLM::Content)
|
|
260
|
-
attachments_to_persist = content.attachments if content.attachments.any?
|
|
261
|
-
content = content.text
|
|
262
|
-
elsif content.is_a?(Hash) || content.is_a?(Array)
|
|
263
|
-
content = content.to_json
|
|
264
|
-
end
|
|
308
|
+
content, attachments_to_persist = prepare_content_for_storage(message.content)
|
|
265
309
|
|
|
266
310
|
@message.update!(
|
|
267
311
|
role: message.role,
|
|
@@ -278,14 +322,17 @@ module RubyLLM
|
|
|
278
322
|
end
|
|
279
323
|
end
|
|
280
324
|
|
|
281
|
-
def persist_tool_calls(tool_calls)
|
|
282
|
-
|
|
325
|
+
def persist_tool_calls(tool_calls, message_record: @message)
|
|
326
|
+
tool_call_assoc = message_record.respond_to?(:tool_calls) ? message_record.tool_calls : nil
|
|
327
|
+
return unless tool_call_assoc
|
|
328
|
+
|
|
329
|
+
supports_thought_signature = tool_call_assoc.klass.column_names.include?('thought_signature')
|
|
283
330
|
|
|
284
331
|
tool_calls.each_value do |tool_call|
|
|
285
332
|
attributes = tool_call.to_h
|
|
286
333
|
attributes.delete(:thought_signature) unless supports_thought_signature
|
|
287
334
|
attributes[:tool_call_id] = attributes.delete(:id)
|
|
288
|
-
|
|
335
|
+
tool_call_assoc.create!(**attributes)
|
|
289
336
|
end
|
|
290
337
|
end
|
|
291
338
|
|
|
@@ -329,6 +376,29 @@ module RubyLLM
|
|
|
329
376
|
RubyLLM.logger.warn "Failed to process attachment #{source}: #{e.message}"
|
|
330
377
|
nil
|
|
331
378
|
end
|
|
379
|
+
|
|
380
|
+
def build_content(message, attachments)
|
|
381
|
+
return message if content_like?(message)
|
|
382
|
+
|
|
383
|
+
RubyLLM::Content.new(message, attachments)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def content_like?(object)
|
|
387
|
+
object.is_a?(RubyLLM::Content) || object.is_a?(RubyLLM::Content::Raw)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def prepare_content_for_storage(content)
|
|
391
|
+
attachments = nil
|
|
392
|
+
|
|
393
|
+
if content.is_a?(RubyLLM::Content)
|
|
394
|
+
attachments = content.attachments if content.attachments.any?
|
|
395
|
+
[content.text, attachments]
|
|
396
|
+
elsif content.is_a?(Hash) || content.is_a?(Array)
|
|
397
|
+
[content.to_json, attachments]
|
|
398
|
+
else
|
|
399
|
+
[content, attachments]
|
|
400
|
+
end
|
|
401
|
+
end
|
|
332
402
|
end
|
|
333
403
|
|
|
334
404
|
# Methods mixed into message models.
|
|
@@ -83,19 +83,19 @@ module RubyLLM
|
|
|
83
83
|
)
|
|
84
84
|
@chat.reset_messages!
|
|
85
85
|
|
|
86
|
-
messages_association.
|
|
86
|
+
ordered_messages = order_messages_for_llm(messages_association.to_a)
|
|
87
|
+
ordered_messages.each do |msg|
|
|
87
88
|
@chat.add_message(msg.to_llm)
|
|
88
89
|
end
|
|
89
90
|
|
|
90
91
|
setup_persistence_callbacks
|
|
91
92
|
end
|
|
92
93
|
|
|
93
|
-
def with_instructions(instructions, replace:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
to_llm.with_instructions(instructions)
|
|
94
|
+
def with_instructions(instructions, append: false, replace: nil)
|
|
95
|
+
append = append_instructions?(append:, replace:)
|
|
96
|
+
persist_system_instruction(instructions, append:)
|
|
97
|
+
|
|
98
|
+
to_llm.with_instructions(instructions, append:, replace:)
|
|
99
99
|
self
|
|
100
100
|
end
|
|
101
101
|
|
|
@@ -178,22 +178,32 @@ module RubyLLM
|
|
|
178
178
|
self
|
|
179
179
|
end
|
|
180
180
|
|
|
181
|
-
def
|
|
182
|
-
|
|
181
|
+
def add_message(message_or_attributes)
|
|
182
|
+
llm_message = message_or_attributes.is_a?(RubyLLM::Message) ? message_or_attributes : RubyLLM::Message.new(message_or_attributes)
|
|
183
|
+
content_text, attachments, content_raw = prepare_content_for_storage(llm_message.content)
|
|
184
|
+
|
|
185
|
+
attrs = { role: llm_message.role, content: content_text }
|
|
186
|
+
parent_tool_call_assoc = messages_association.klass.reflect_on_association(:parent_tool_call)
|
|
187
|
+
if parent_tool_call_assoc && llm_message.tool_call_id
|
|
188
|
+
tool_call_id = find_tool_call_id(llm_message.tool_call_id)
|
|
189
|
+
attrs[parent_tool_call_assoc.foreign_key] = tool_call_id if tool_call_id
|
|
190
|
+
end
|
|
183
191
|
|
|
184
|
-
message_record = messages_association.
|
|
185
|
-
message_record.
|
|
186
|
-
message_record.content_raw = content_raw if message_record.respond_to?(:content_raw=)
|
|
187
|
-
message_record.save!
|
|
192
|
+
message_record = messages_association.create!(attrs)
|
|
193
|
+
message_record.update!(content_raw:) if message_record.respond_to?(:content_raw=)
|
|
188
194
|
|
|
189
|
-
persist_content(message_record, with) if with.present?
|
|
190
195
|
persist_content(message_record, attachments) if attachments.present?
|
|
196
|
+
persist_tool_calls(llm_message.tool_calls, message_record:) if llm_message.tool_calls.present?
|
|
191
197
|
|
|
192
198
|
message_record
|
|
193
199
|
end
|
|
194
200
|
|
|
195
|
-
def
|
|
196
|
-
|
|
201
|
+
def create_user_message(content, with: nil)
|
|
202
|
+
add_message(role: :user, content: build_content(content, with))
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def ask(message = nil, with: nil, &)
|
|
206
|
+
add_message(role: :user, content: build_content(message, with))
|
|
197
207
|
complete(&)
|
|
198
208
|
end
|
|
199
209
|
|
|
@@ -244,6 +254,40 @@ module RubyLLM
|
|
|
244
254
|
@chat
|
|
245
255
|
end
|
|
246
256
|
|
|
257
|
+
def replace_persisted_system_instructions(instructions)
|
|
258
|
+
system_messages = messages_association.where(role: :system).order(:id).to_a
|
|
259
|
+
|
|
260
|
+
if system_messages.empty?
|
|
261
|
+
messages_association.create!(role: :system, content: instructions)
|
|
262
|
+
return
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
primary_message = system_messages.shift
|
|
266
|
+
primary_message.update!(content: instructions) if primary_message.content != instructions
|
|
267
|
+
system_messages.each(&:destroy!)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def append_instructions?(append:, replace:)
|
|
271
|
+
return append if replace.nil?
|
|
272
|
+
|
|
273
|
+
append || (replace == false)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def persist_system_instruction(instructions, append:)
|
|
277
|
+
transaction do
|
|
278
|
+
if append
|
|
279
|
+
messages_association.create!(role: :system, content: instructions)
|
|
280
|
+
else
|
|
281
|
+
replace_persisted_system_instructions(instructions)
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def order_messages_for_llm(messages)
|
|
287
|
+
system_messages, non_system_messages = messages.partition { |msg| msg.role.to_s == 'system' }
|
|
288
|
+
system_messages + non_system_messages
|
|
289
|
+
end
|
|
290
|
+
|
|
247
291
|
def persist_new_message
|
|
248
292
|
@message = messages_association.create!(role: :assistant, content: '')
|
|
249
293
|
end
|
|
@@ -289,15 +333,15 @@ module RubyLLM
|
|
|
289
333
|
end
|
|
290
334
|
# rubocop:enable Metrics/PerceivedComplexity
|
|
291
335
|
|
|
292
|
-
def persist_tool_calls(tool_calls)
|
|
293
|
-
tool_call_klass =
|
|
336
|
+
def persist_tool_calls(tool_calls, message_record: @message)
|
|
337
|
+
tool_call_klass = message_record.tool_calls_association.klass
|
|
294
338
|
supports_thought_signature = tool_call_klass.column_names.include?('thought_signature')
|
|
295
339
|
|
|
296
340
|
tool_calls.each_value do |tool_call|
|
|
297
341
|
attributes = tool_call.to_h
|
|
298
342
|
attributes.delete(:thought_signature) unless supports_thought_signature
|
|
299
343
|
attributes[:tool_call_id] = attributes.delete(:id)
|
|
300
|
-
|
|
344
|
+
message_record.tool_calls_association.create!(**attributes)
|
|
301
345
|
end
|
|
302
346
|
end
|
|
303
347
|
|
|
@@ -352,6 +396,16 @@ module RubyLLM
|
|
|
352
396
|
nil
|
|
353
397
|
end
|
|
354
398
|
|
|
399
|
+
def build_content(message, attachments)
|
|
400
|
+
return message if content_like?(message)
|
|
401
|
+
|
|
402
|
+
RubyLLM::Content.new(message, attachments)
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def content_like?(object)
|
|
406
|
+
object.is_a?(RubyLLM::Content) || object.is_a?(RubyLLM::Content::Raw)
|
|
407
|
+
end
|
|
408
|
+
|
|
355
409
|
def prepare_content_for_storage(content)
|
|
356
410
|
attachments = nil
|
|
357
411
|
content_raw = nil
|