ruby_llm 2.1.0 → 2.1.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: 2f0478372c3c7c58cd1452546f21068161f553949b064377738c28e74b67da9d
4
- data.tar.gz: 2f8f63b90a885326904724a95074934877251b589b41ba00d9366dd72d5b3d69
3
+ metadata.gz: 8000141c827d6b8f899bcf2ec5a23153ecf83f8196fcd35cee7af8de2f9e58e4
4
+ data.tar.gz: '069b322ad8df77cd46ee2dd9b72ec02578281ff9e6c96b349e46aff911ee85b8'
5
5
  SHA512:
6
- metadata.gz: 4028eea4e3515ffa779ada1d1464ab5ed4fa4c21418c1ebaa8fe09bb0da11fd6998f6b0132dde1cd59f876b54a43c03f89507be73e7d3e39b3b3794fbb33f3c5
7
- data.tar.gz: e57218438a1479c2d4974926d4afdf509fa18f2c57ffe0524e116feea919d1fb337d80f883dca30205a442ed754faafafb076bcc780eceb2f86b4c6372a47321
6
+ metadata.gz: 6d651244192242fc5f06cf8126932659b46dec1228ed97e3a9876dd7ee581f8972d42fbaca209698b1c42e1704ebb766ca07b450cfd7651d9192f8d46d3f8c42
7
+ data.tar.gz: 9511e7a1c10a8b73cebbefb59a26be1fd7dcec10c369f57d60b7a348d180a8037700826400d9da41b630d190b940aa73ba7c5cbdb7fbd0f4b6cffbcbe563d732
data/README.md CHANGED
@@ -21,6 +21,8 @@ Battle tested at [<picture><source media="(prefers-color-scheme: dark)" srcset="
21
21
  <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>
22
22
  </div>
23
23
 
24
+ https://github.com/user-attachments/assets/519fcb20-ab8c-4af3-9185-0953b3ba7b81
25
+
24
26
  > [!NOTE]
25
27
  > Using RubyLLM? [Share your story](https://tally.so/r/3Na02p)! Takes 5 minutes.
26
28
 
@@ -34,7 +36,7 @@ Build with the models you want. Move between hosted and local providers without
34
36
 
35
37
  ## Start with one line. Add files, tools, and agents
36
38
 
37
- These examples use **2.1.0**. Follow [Getting Started](https://rubyllm.com/getting-started/) to install it and configure the providers you want to try. For 1.x, use the [1.x docs](https://rubyllm.com/v1/).
39
+ These examples use **2.1.1**. Follow [Getting Started](https://rubyllm.com/getting-started/) to install it and configure the providers you want to try. For 1.x, use the [1.x docs](https://rubyllm.com/v1/).
38
40
 
39
41
  ```ruby
40
42
  # Just ask
@@ -249,7 +251,7 @@ Agents, evaluations, workflows, RAG, images, audio, and video. Built in, with us
249
251
  Install RubyLLM 2.1:
250
252
 
251
253
  ```bash
252
- bundle add ruby_llm --version 2.1.0
254
+ bundle add ruby_llm --version 2.1.1
253
255
  ```
254
256
 
255
257
  Configure a provider in your script, or in `config/initializers/ruby_llm.rb` in Rails:
@@ -741,10 +741,14 @@ module RubyLLM
741
741
  end
742
742
 
743
743
  def link_usage_entries(message)
744
- message.ruby_llm_usage_entries.each do |entry|
745
- record = usage_records_by_entry[entry]
746
- record&.update!(message: @message)
744
+ Usage.transaction(requires_new: true) do
745
+ message.ruby_llm_usage_entries.each do |entry|
746
+ record = usage_records_by_entry[entry]
747
+ record&.update!(message: @message)
748
+ end
747
749
  end
750
+ rescue StandardError => e
751
+ RubyLLM.logger.warn("RubyLLM could not link usage to message #{@message.id}: #{e.class}: #{e.message}")
748
752
  end
749
753
 
750
754
  def usage_records_by_entry
data/lib/ruby_llm/chat.rb CHANGED
@@ -1471,7 +1471,11 @@ module RubyLLM
1471
1471
  end
1472
1472
 
1473
1473
  def discard_missing_uploads(error)
1474
- @provider.discard_missing_uploads(messages, error, model: @model, protocol: @protocol)
1474
+ upload_messages = messages.map do |message|
1475
+ replacements = message.attachments.filter_map { |attachment| @attachment_replacements[attachment] }
1476
+ message.with_attachments(message.attachments + replacements)
1477
+ end
1478
+ @provider.discard_missing_uploads(upload_messages, error, model: @model, protocol: @protocol)
1475
1479
  end
1476
1480
 
1477
1481
  def record_usage_entry(entry)
@@ -6,8 +6,10 @@ module RubyLLM
6
6
  # 2026-07-28 revision and falls back to the initialize handshake for
7
7
  # servers that predate it, declaring only its extensions so those
8
8
  # servers never call back. A server that rejects 2026-07-28 while
9
- # listing it gets the request once more; one that answers the handshake
10
- # with a revision missing from LEGACY_VERSIONS is disconnected.
9
+ # listing it gets the request once more; one that rejects it while
10
+ # listing a revision from LEGACY_VERSIONS, or without listing any,
11
+ # gets the handshake instead; one that answers the handshake with a
12
+ # revision missing from LEGACY_VERSIONS is disconnected.
11
13
  #
12
14
  # Servers that predate 2026-07-28 may announce that their tools,
13
15
  # prompts, or resources changed on the stream of any request. Those
@@ -103,7 +105,7 @@ module RubyLLM
103
105
  result = call('server/discover', timeout: DISCOVERY_TIMEOUT)
104
106
  result if Array(result['supportedVersions']).include?(VERSION)
105
107
  rescue Error => e
106
- raise if MODERN_ERRORS.include?(e.code)
108
+ raise if MODERN_ERRORS.include?(e.code) && !predates_revision?(e)
107
109
  end
108
110
 
109
111
  def handshake
@@ -188,8 +190,19 @@ module RubyLLM
188
190
  end
189
191
 
190
192
  def offers_version?(error)
193
+ modern? && error.code == UNSUPPORTED_VERSION && supported_versions(error).include?(VERSION)
194
+ end
195
+
196
+ def predates_revision?(error)
197
+ return false unless error.code == UNSUPPORTED_VERSION
198
+
199
+ supported = supported_versions(error)
200
+ supported.empty? || supported.intersect?(LEGACY_VERSIONS)
201
+ end
202
+
203
+ def supported_versions(error)
191
204
  supported = error.data['supported'] if error.data.is_a?(Hash)
192
- modern? && error.code == UNSUPPORTED_VERSION && Array(supported).include?(VERSION)
205
+ Array(supported)
193
206
  end
194
207
 
195
208
  def message(method, params = {}, id: nil)
@@ -753,12 +753,16 @@ module RubyLLM
753
753
  end
754
754
 
755
755
  def missing_uploads(messages, error, scope)
756
- uploads = messages.flat_map(&:attachments).filter_map do |attachment|
756
+ uploads = attachment_uploads(messages, scope)
757
+ named = uploads.select { |_, upload| names_file?(error, upload.id) }
758
+ named.empty? && error.response&.status == 404 ? uploads : named
759
+ end
760
+
761
+ def attachment_uploads(messages, scope)
762
+ messages.flat_map(&:attachments).uniq(&:object_id).filter_map do |attachment|
757
763
  upload = attachment.provider_uploads[scope]
758
764
  [attachment, upload] if upload
759
765
  end
760
- named = uploads.select { |_, upload| names_file?(error, upload.id) }
761
- named.empty? && error.response&.status == 404 ? uploads : named
762
766
  end
763
767
 
764
768
  def names_file?(error, id)
@@ -29,6 +29,27 @@ module RubyLLM
29
29
  def parse_batch_completion_response(body)
30
30
  parse_embedding_response(Response.new(body), model: body['model'], text: nil)
31
31
  end
32
+
33
+ def parse_batch_result(line)
34
+ body = line.dig('response', 'body')
35
+ return super unless body.is_a?(Hash) && body['data'].is_a?(Array)
36
+ unless valid_embedding_positions?(body['data'])
37
+ return [batch_result_index(line['custom_id']), nil,
38
+ batch_failure(line['custom_id'], 'Invalid or duplicate embedding record positions')]
39
+ end
40
+
41
+ ordered = body.merge('data' => body['data'].sort_by { |row| row['index'] })
42
+ super(line.merge('response' => line['response'].merge('body' => ordered)))
43
+ end
44
+
45
+ # A row's index names the input its vector belongs to, so rows are put
46
+ # back in that order and positions that are not exactly 0...N fail the
47
+ # request rather than pairing vectors with the wrong texts.
48
+ def valid_embedding_positions?(rows)
49
+ positions = rows.map { |row| row.is_a?(Hash) ? row['index'] : nil }
50
+
51
+ positions.all?(Integer) && positions.sort == (0...positions.size).to_a
52
+ end
32
53
  end
33
54
  end
34
55
  end
@@ -185,6 +185,7 @@ module RubyLLM
185
185
  def resolve_registry_id(model_id, models, config)
186
186
  region = config.bedrock_region.to_s
187
187
  return model_id if region.empty?
188
+ return model_id if region_prefixed?(model_id)
188
189
  return model_id if mantle_model_id?(model_id)
189
190
 
190
191
  candidate = registered_profile_candidate(model_id, models, region)
@@ -197,8 +198,6 @@ module RubyLLM
197
198
  def registered_profile_candidate(model_id, models, region)
198
199
  region_prefix_candidates(region).each do |prefix|
199
200
  prefixed = prefixed_with(model_id, prefix)
200
- next if prefixed == model_id
201
-
202
201
  candidate = models.all_including_unlisted.find { |m| m.provider == 'bedrock' && m.id == prefixed }
203
202
  return candidate if candidate
204
203
  end
@@ -99,9 +99,13 @@ module RubyLLM
99
99
  custom_id, shape = line['custom_id'].split(':', 2)
100
100
  index = batch_result_index(custom_id)
101
101
  response = line['response']
102
+ body = response && response['body']
102
103
 
103
- if response && response['body']
104
- body = response['body']
104
+ if body && body['data'].is_a?(Array) && !embedding_positions?(body['data'])
105
+ return [index, nil, batch_failure(line['custom_id'], 'Invalid or duplicate embedding record positions')]
106
+ end
107
+
108
+ if body
105
109
  [index, parse_mistral_batch_body(body, shape:)]
106
110
  else
107
111
  [index, nil, batch_failure(line['custom_id'], batch_error_message(line))]
@@ -111,7 +115,17 @@ module RubyLLM
111
115
  def parse_mistral_batch_body(body, shape:)
112
116
  return parse_completion_body(body, raw: body) unless body['data'].is_a?(Array)
113
117
 
114
- parse_embedding_response(Response.new(body), model: body['model'], text: shape == 'array' ? [] : nil)
118
+ ordered = body.merge('data' => body['data'].sort_by { |row| row['index'] })
119
+ parse_embedding_response(Response.new(ordered), model: body['model'], text: shape == 'array' ? [] : nil)
120
+ end
121
+
122
+ # A row's index names the input its vector belongs to, so rows are put
123
+ # back in that order and positions that are not exactly 0...N fail the
124
+ # request rather than pairing vectors with the wrong inputs.
125
+ def embedding_positions?(rows)
126
+ positions = rows.map { |row| row.is_a?(Hash) ? row['index'] : nil }
127
+
128
+ positions.all?(Integer) && positions.sort == (0...positions.size).to_a
115
129
  end
116
130
  end
117
131
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module RubyLLM
4
4
  # The version of the ruby_llm gem, as a string.
5
- VERSION = '2.1.0'
5
+ VERSION = '2.1.1'
6
6
  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: 2.1.0
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino
@@ -30,6 +30,9 @@ dependencies:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
32
  version: 1.10.0
33
+ - - "!="
34
+ - !ruby/object:Gem::Version
35
+ version: 2.0.0
33
36
  type: :runtime
34
37
  prerelease: false
35
38
  version_requirements: !ruby/object:Gem::Requirement
@@ -37,6 +40,9 @@ dependencies:
37
40
  - - ">="
38
41
  - !ruby/object:Gem::Version
39
42
  version: 1.10.0
43
+ - - "!="
44
+ - !ruby/object:Gem::Version
45
+ version: 2.0.0
40
46
  - !ruby/object:Gem::Dependency
41
47
  name: faraday-multipart
42
48
  requirement: !ruby/object:Gem::Requirement
@@ -679,7 +685,7 @@ metadata:
679
685
  funding_uri: https://github.com/sponsors/crmne
680
686
  rubygems_mfa_required: 'true'
681
687
  post_install_message: |
682
- RubyLLM 2.1.0
688
+ RubyLLM 2.1.1
683
689
 
684
690
  Upgrading? Read the upgrade guide before you boot:
685
691