ruby_llm 1.12.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e8ff2f4da0c39e4909925217affa2b76908207e2c936a6dc05b56cffb2781863
4
- data.tar.gz: 0eebb76434b049d4332a247e7581bb721d4dd86b0496be8655d15a4adcb42f65
3
+ metadata.gz: 6f2aa1f16058fca83243f2b098b0a3f454fb9383e410a00b04955cd5b48cbf54
4
+ data.tar.gz: fb4591fe16b50449dc1baf90f77a2b92baa986a51b9573f5c0b79dc231d9a9b9
5
5
  SHA512:
6
- metadata.gz: 95a3eb5a1c6a50c69dd8166044d99d77848fc8dcdd5d31b748e8303a3e8c5756cc86a8ce0220dd3c643bc0b32080f6c2125c79f5e437bb654725a119b4b4b601
7
- data.tar.gz: 5efbd317193ca7f5e28df0a2d9bcc7ffd8a1740f40d29e7b872bed8a2ee910db376dc2dc890723b0c253718404993e55deab87356d414afdfd741d9392bd5e0f
6
+ metadata.gz: 687200d2c127d604e0bbff56c888ef5bb5ab2938b585ec2f8959f77b74ad35ae0d0dbad588068ccccd3219ab0aa4208dbc47b8436606c7b12ec1de10cf2928c2
7
+ data.tar.gz: 9aab7e7a79aa98b2772e4a01f4269c17a1969755276fde9b0fb4b3311a81dd4ff3a4a1e4e4c4a1f0fa863a162caaa4f3d3cd05cc044cfc5116ceaaa878963737
@@ -53,8 +53,6 @@ module RubyLLM
53
53
  foreign_key: model_foreign_key,
54
54
  optional: true
55
55
 
56
- delegate :add_message, to: :to_llm
57
-
58
56
  define_method :messages_association do
59
57
  send(messages_association_name)
60
58
  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',
@@ -185,14 +183,34 @@ module RubyLLM
185
183
  self
186
184
  end
187
185
 
188
- def create_user_message(content, with: nil)
189
- message_record = messages.create!(role: :user, content: content)
190
- persist_content(message_record, with) if with.present?
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 ask(message, with: nil, &)
195
- create_user_message(message, with:)
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
 
@@ -281,21 +299,13 @@ module RubyLLM
281
299
  @message = messages.create!(role: :assistant, content: '')
282
300
  end
283
301
 
284
- def persist_message_completion(message) # rubocop:disable Metrics/PerceivedComplexity
302
+ def persist_message_completion(message)
285
303
  return unless message
286
304
 
287
305
  tool_call_id = find_tool_call_id(message.tool_call_id) if message.tool_call_id
288
306
 
289
307
  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
308
+ content, attachments_to_persist = prepare_content_for_storage(message.content)
299
309
 
300
310
  @message.update!(
301
311
  role: message.role,
@@ -312,14 +322,17 @@ module RubyLLM
312
322
  end
313
323
  end
314
324
 
315
- def persist_tool_calls(tool_calls)
316
- supports_thought_signature = tool_calls.klass.column_names.include?('thought_signature')
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')
317
330
 
318
331
  tool_calls.each_value do |tool_call|
319
332
  attributes = tool_call.to_h
320
333
  attributes.delete(:thought_signature) unless supports_thought_signature
321
334
  attributes[:tool_call_id] = attributes.delete(:id)
322
- @message.tool_calls.create!(**attributes)
335
+ tool_call_assoc.create!(**attributes)
323
336
  end
324
337
  end
325
338
 
@@ -363,6 +376,29 @@ module RubyLLM
363
376
  RubyLLM.logger.warn "Failed to process attachment #{source}: #{e.message}"
364
377
  nil
365
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
366
402
  end
367
403
 
368
404
  # Methods mixed into message models.
@@ -178,22 +178,32 @@ module RubyLLM
178
178
  self
179
179
  end
180
180
 
181
- def create_user_message(content, with: nil)
182
- content_text, attachments, content_raw = prepare_content_for_storage(content)
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.build(role: :user)
185
- message_record.content = content_text
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 ask(message, with: nil, &)
196
- create_user_message(message, with:)
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
 
@@ -323,15 +333,15 @@ module RubyLLM
323
333
  end
324
334
  # rubocop:enable Metrics/PerceivedComplexity
325
335
 
326
- def persist_tool_calls(tool_calls)
327
- tool_call_klass = @message.tool_calls_association.klass
336
+ def persist_tool_calls(tool_calls, message_record: @message)
337
+ tool_call_klass = message_record.tool_calls_association.klass
328
338
  supports_thought_signature = tool_call_klass.column_names.include?('thought_signature')
329
339
 
330
340
  tool_calls.each_value do |tool_call|
331
341
  attributes = tool_call.to_h
332
342
  attributes.delete(:thought_signature) unless supports_thought_signature
333
343
  attributes[:tool_call_id] = attributes.delete(:id)
334
- @message.tool_calls_association.create!(**attributes)
344
+ message_record.tool_calls_association.create!(**attributes)
335
345
  end
336
346
  end
337
347
 
@@ -386,6 +396,16 @@ module RubyLLM
386
396
  nil
387
397
  end
388
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
+
389
409
  def prepare_content_for_storage(content)
390
410
  attachments = nil
391
411
  content_raw = nil
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'erb'
4
+ require 'forwardable'
4
5
  require 'pathname'
5
6
 
6
7
  module RubyLLM
7
8
  # Base class for simple, class-configured agents.
8
9
  class Agent
10
+ extend Forwardable
9
11
  include Enumerable
10
12
 
11
13
  class << self
@@ -316,8 +318,9 @@ module RubyLLM
316
318
 
317
319
  attr_reader :chat
318
320
 
319
- delegate :ask, :say, :complete, :add_message, :messages,
320
- :on_new_message, :on_end_message, :on_tool_call, :on_tool_result, :each,
321
- to: :chat
321
+ def_delegators :chat, :model, :messages, :tools, :params, :headers, :schema, :ask, :say, :with_tool, :with_tools,
322
+ :with_model, :with_temperature, :with_thinking, :with_context, :with_params, :with_headers,
323
+ :with_schema, :on_new_message, :on_end_message, :on_tool_call, :on_tool_result, :each, :complete,
324
+ :add_message, :reset_messages!
322
325
  end
323
326
  end
@@ -35,10 +35,16 @@ module RubyLLM
35
35
 
36
36
  def process_attachments_array_or_string(attachments)
37
37
  Utils.to_safe_array(attachments).each do |file|
38
+ next if blank_attachment_entry?(file)
39
+
38
40
  add_attachment(file)
39
41
  end
40
42
  end
41
43
 
44
+ def blank_attachment_entry?(file)
45
+ file.nil? || (file.is_a?(String) && file.strip.empty?)
46
+ end
47
+
42
48
  def process_attachments(attachments)
43
49
  if attachments.is_a?(Hash)
44
50
  attachments.each_value { |attachment| process_attachments_array_or_string(attachment) }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyLLM
4
- VERSION = '1.12.0'
4
+ VERSION = '1.12.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.0
4
+ version: 1.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino
@@ -99,28 +99,28 @@ dependencies:
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '1.0'
102
+ version: '1'
103
103
  type: :runtime
104
104
  prerelease: false
105
105
  version_requirements: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
- version: '1.0'
109
+ version: '1'
110
110
  - !ruby/object:Gem::Dependency
111
111
  name: ruby_llm-schema
112
112
  requirement: !ruby/object:Gem::Requirement
113
113
  requirements:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
- version: 0.2.1
116
+ version: '0'
117
117
  type: :runtime
118
118
  prerelease: false
119
119
  version_requirements: !ruby/object:Gem::Requirement
120
120
  requirements:
121
121
  - - "~>"
122
122
  - !ruby/object:Gem::Version
123
- version: 0.2.1
123
+ version: '0'
124
124
  - !ruby/object:Gem::Dependency
125
125
  name: zeitwerk
126
126
  requirement: !ruby/object:Gem::Requirement