ruby_llm 2.0.0.rc3 → 2.0.0.rc4

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: bf1e2bc13df637c93268a6ee20e52c704c47f942be416c4d6728882ae2cc35bf
4
- data.tar.gz: b63c7e4eef810843ad1bcb91a8ad55413f90764ba7e984d310c8a6b4bec3bec0
3
+ metadata.gz: c047801a2b7a5a642296f0ae84e81ab29b641353ffe8618b3ece44db0b0a3849
4
+ data.tar.gz: 05e4624f14a8e2045408dd21007758612f0644522bae4e250902fd60d66955e1
5
5
  SHA512:
6
- metadata.gz: 030126750af8dea0303a81093d415e361fdfb7335620d8e84da3fd024acba2e5323bb7fcbd47530a6a3d5cbc24ba3c2f771db431aabd8011502bd19b9b68aa71
7
- data.tar.gz: c783cfc4c121db83f4e05867893b7b822ffeddb53010eb22a5053d37eca7a539ee26dc6181b669ac5b2fac856464a18cc8929303127c1e5ae1f1fe28f9c1f373
6
+ metadata.gz: 6e73e5f6818e70c607c3cabfc6e0dfb19f9271491375b71279e0a205f2ecff8fe0051c691c51230644d32f7ad8f377b8f8bddfcae919e33ff64015ccd9a942e7
7
+ data.tar.gz: fd4285734764621dc2cab77579b8c713aaff907930c1b6849472c470839b2a75e6975832baf4e43adeedd1d3326b92af816af4a63242bdfa2585fab2b72fdc58
data/.rdoc_options CHANGED
@@ -17,8 +17,8 @@ exclude:
17
17
  - \.json$
18
18
  footer_content:
19
19
  Documentation:
20
- Guides: https://rubyllm.com/next/
21
- Upgrading: https://rubyllm.com/next/upgrading/
20
+ Guides: https://rubyllm.com/
21
+ Upgrading: https://rubyllm.com/upgrading/
22
22
  Resources:
23
23
  GitHub: https://github.com/crmne/ruby_llm
24
24
  Issues: https://github.com/crmne/ruby_llm/issues
data/README.md CHANGED
@@ -7,7 +7,9 @@
7
7
 
8
8
  <strong>Build AI features the Ruby way</strong>
9
9
 
10
- <p>The Ruby-native AI framework. Build with chats, tools, agents, images, audio, and video through one consistent API, in plain Ruby or Rails.</p>
10
+ <p>RubyLLM is the Ruby-native AI framework. Work with models, tools, and agents through one consistent API, in plain Ruby or Rails.</p>
11
+
12
+ [Website](https://rubyllm.com/) · [Getting Started](https://rubyllm.com/getting-started/) · [What's New in 2.0](https://rubyllm.com/whats-new-in-2-0/)
11
13
 
12
14
  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) - *Fully private work AI*
13
15
 
@@ -24,26 +26,19 @@ Battle tested at [<picture><source media="(prefers-color-scheme: dark)" srcset="
24
26
 
25
27
  ---
26
28
 
27
- Work with OpenAI, xAI, Anthropic, Google, AWS, local models, and more. Seventeen providers are built in, and you can connect an OpenAI-compatible endpoint directly.
28
-
29
- ## Build a working Ruby AI chat in two minutes
30
-
31
- https://github.com/user-attachments/assets/65422091-9338-47da-a303-92b918bd1345
32
-
33
- ## Why RubyLLM?
29
+ ## 17 providers. One Ruby API.
34
30
 
35
- Use the same Ruby methods across providers. Add files to a conversation, give an agent tools, generate media, or build a search feature with embeddings and reranking. Read response text, generated files, and usage through Ruby objects.
31
+ Build with the models you want. Move between hosted and local providers without rewriting your application, or connect an OpenAI-compatible endpoint.
36
32
 
37
- In Rails, the API works on your own Chat and Message records, with Active Storage attachments, Hotwire streaming, and background jobs. RubyLLM maintains the supporting model registry, tool calls, usage ledger, and batches. A handful of small dependencies keeps it easy to bring into an existing application.
33
+ [Browse models and pricing](https://rubyllm.com/available-models/) · [Connect a provider](https://rubyllm.com/configuration-providers/)
38
34
 
39
- ## Show me the code
35
+ ## Start with one line. Add files, tools, and agents
40
36
 
41
- These examples use **2.0.0.rc3**. Follow [Getting Started](https://rubyllm.com/next/getting-started/) to install it and configure the providers you want to try.
37
+ These examples use **2.0.0.rc4 (prerelease)**. Follow [Getting Started](https://rubyllm.com/getting-started/) to install it and configure the providers you want to try. For 1.x, use the [stable-version docs](https://rubyllm.com/v1/).
42
38
 
43
39
  ```ruby
44
- # Just ask questions
45
- chat = RubyLLM.chat
46
- chat.ask "What's the best way to learn Ruby?"
40
+ # Just ask
41
+ RubyLLM.chat.ask "What's the best way to learn Ruby?"
47
42
  ```
48
43
 
49
44
  ```ruby
@@ -155,7 +150,9 @@ response = chat.with_schema(ProductSchema).ask "Analyze this product", with: "pr
155
150
  response.parsed
156
151
  ```
157
152
 
158
- ## Features
153
+ ## A complete AI framework for Ruby
154
+
155
+ Agents, workflows, RAG, images, audio, and video. Built in, with usage tracking and Rails integration to bring them into your app.
159
156
 
160
157
  * **Chat:** Conversational AI with `RubyLLM.chat`
161
158
  * **Vision:** Analyze images and videos
@@ -195,7 +192,7 @@ response.parsed
195
192
  Install the 2.0 release candidate:
196
193
 
197
194
  ```bash
198
- bundle add ruby_llm --version 2.0.0.rc3
195
+ bundle add ruby_llm --version 2.0.0.rc4
199
196
  ```
200
197
 
201
198
  Configure a provider in your script, or in `config/initializers/ruby_llm.rb` in Rails:
@@ -208,9 +205,13 @@ RubyLLM.configure do |config|
208
205
  end
209
206
  ```
210
207
 
211
- Configure the other providers used by the examples as needed: Gemini for files, xAI for video, Mistral for OCR, and Cohere for reranking. [Getting Started](https://rubyllm.com/next/getting-started/) shows each setup beside its example. If your app uses 1.16, follow the [upgrade guide](https://rubyllm.com/next/upgrading/) before deploying 2.0.
208
+ Configure the other providers used by the examples as needed: Gemini for files, xAI for video, Mistral for OCR, and Cohere for reranking. [Getting Started](https://rubyllm.com/getting-started/) shows each setup beside its example. If your app uses 1.16, follow the [upgrade guide](https://rubyllm.com/upgrading/) before deploying 2.0.
212
209
 
213
- ## Rails
210
+ ## Feels at home in Rails
211
+
212
+ Save conversations with Active Record and stream replies with Hotwire. The generators give you a working chat UI. Watch the two-minute demo.
213
+
214
+ https://github.com/user-attachments/assets/65422091-9338-47da-a303-92b918bd1345
214
215
 
215
216
  ```bash
216
217
  # Install Rails Integration
@@ -231,11 +232,21 @@ chat = Chat.create! model: "gpt-5.6-luna"
231
232
  chat.ask "What's in this file?", with: "report.pdf"
232
233
  ```
233
234
 
234
- Visit `http://localhost:3000/chats` for a ready-to-use chat interface!
235
+ Start your Rails server and visit `http://localhost:3000/chats` to try the chat interface. See the [Rails guide](https://rubyllm.com/rails/) for persistence, streaming, and background jobs.
236
+
237
+ ## AI coding assistants
238
+
239
+ Give your coding assistant the RubyLLM API and documentation that match your application. From your application directory, install the skill packaged with your gem:
240
+
241
+ ```bash
242
+ npx skills add "$(bundle show ruby_llm)" --skill rubyllm
243
+ ```
244
+
245
+ Choose your coding assistant and installation scope when prompted. See [AI Coding Assistants](https://rubyllm.com/ai-coding-assistants/) for setup and updates.
235
246
 
236
247
  ## Documentation
237
248
 
238
- [Guides](https://rubyllm.com/next/getting-started/) · [API reference](https://rubyllm.com/next/api/) · [Models](https://rubyllm.com/available-models/) · [Upgrading](https://rubyllm.com/next/upgrading/)
249
+ [Guides](https://rubyllm.com/getting-started/) · [API reference](https://rubyllm.com/api/) · [Models](https://rubyllm.com/available-models/) · [Upgrading](https://rubyllm.com/upgrading/) · [1.x docs](https://rubyllm.com/v1/)
239
250
 
240
251
  ## Contributing
241
252
 
@@ -63,6 +63,15 @@ module RubyLLM
63
63
  end
64
64
  end
65
65
 
66
+ def discard_incomplete_tool_calls
67
+ source = records(configuration.fetch('tool_call_table'))
68
+ result_key = configuration.fetch('tool_call_foreign_key')
69
+ results = messages.where.not(result_key => nil).select(result_key)
70
+ owners = legacy_messages.select(messages.primary_key)
71
+ source.where(configuration.fetch('message_foreign_key') => owners)
72
+ .where.not(source.primary_key => results).delete_all
73
+ end
74
+
66
75
  def catch_up(passes: nil)
67
76
  sync_models
68
77
  pass = 0
@@ -47,7 +47,7 @@ module RubyLLM
47
47
  end
48
48
  end
49
49
 
50
- def finish
50
+ def finish(discard_incomplete_tool_calls: false)
51
51
  with_migration_lock do
52
52
  state = states.first!
53
53
  return if state.active_version == 2 && state.status == 'active' && data.finished?
@@ -55,6 +55,10 @@ module RubyLLM
55
55
  raise 'Run the copy backfill before finish' unless data.completed?
56
56
 
57
57
  pause(state, from: 1, statuses: %w[preparing active finishing])
58
+ if discard_incomplete_tool_calls
59
+ discarded = data.discard_incomplete_tool_calls
60
+ ::ActiveRecord::Migration.say "Discarded #{discarded} incomplete legacy tool calls"
61
+ end
58
62
  data.catch_up
59
63
  data.verify
60
64
  verify_completed_work
@@ -1,15 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'generators/ruby_llm/upgrade/legacy_content_sql'
4
3
  <% if copy_mode? -%>
5
-
6
4
  require 'generators/ruby_llm/upgrade/upgrade_migration'
5
+ <% else -%>
6
+ require 'generators/ruby_llm/upgrade/legacy_content_sql'
7
7
  <% end -%>
8
8
 
9
9
  class <%= backfill_migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
10
10
  disable_ddl_transaction!
11
11
 
12
+ <% unless copy_mode? -%>
12
13
  BATCH_SIZE = 10_000
14
+ <% end -%>
13
15
  PROGRESS_TABLE = :ruby_llm_v2_backfills
14
16
 
15
17
  def up
@@ -46,6 +48,7 @@ class <%= backfill_migration_class_name %> < ActiveRecord::Migration<%= migratio
46
48
  raise 'The RubyLLM 2.0 upgrade is already finished. Do not run the backfill again.'
47
49
  end
48
50
 
51
+ <% unless copy_mode? -%>
49
52
  def backfill_required_defaults
50
53
  {
51
54
  <%= chat_table_name %>: {cancelled: false},
@@ -453,6 +456,7 @@ class <%= backfill_migration_class_name %> < ActiveRecord::Migration<%= migratio
453
456
  yield
454
457
  end
455
458
 
459
+ <% end -%>
456
460
  def progress_records = migration_record(PROGRESS_TABLE)
457
461
 
458
462
  def migration_record(table)
@@ -13,7 +13,7 @@ class <%= finish_migration_class_name %> < ActiveRecord::Migration<%= migration_
13
13
  def up
14
14
  <% if copy_mode? -%>
15
15
  RubyLLM::Generators::UpgradeMigration.for.verify_settings(<%= copy_upgrade_settings.inspect %>)
16
- RubyLLM::Generators::UpgradeMigration.for.finish do
16
+ RubyLLM::Generators::UpgradeMigration.for.finish<% if discard_incomplete_tool_calls? %>(discard_incomplete_tool_calls: true)<% end %> do
17
17
  verify_completed_backfills
18
18
  enforce_required_defaults
19
19
  relax_legacy_message_constraints
@@ -113,6 +113,7 @@ class <%= finish_migration_class_name %> < ActiveRecord::Migration<%= migration_
113
113
  raise "RubyLLM 2.0 backfills are incomplete: #{missing.join(', ')}"
114
114
  end
115
115
 
116
+ <% unless copy_mode? -%>
116
117
  def verify_message_content
117
118
  return unless column_exists?(:<%= message_table_name %>, :content_raw)
118
119
 
@@ -220,17 +221,22 @@ class <%= finish_migration_class_name %> < ActiveRecord::Migration<%= migration_
220
221
  <% end -%>
221
222
  end
222
223
 
224
+ <% end -%>
223
225
  def with_upgrade_safety(&)
224
226
  return safety_assured(&) if respond_to?(:safety_assured, true)
225
227
 
226
228
  yield
227
229
  end
228
230
 
229
- def message_value(column) = "legacy_messages.#{quote_column(column)}"
230
231
  def column_definition(table, column) = connection.columns(table).find { |candidate| candidate.name == column.to_s }
232
+ <% unless copy_mode? -%>
233
+ def message_value(column) = "legacy_messages.#{quote_column(column)}"
231
234
  def quoted_primary_key(table) = quote_column(connection.primary_key(table))
232
235
  def quote_table(table) = connection.quote_table_name(table)
236
+ <% end -%>
237
+ <% if postgresql? || !copy_mode? -%>
233
238
  def quote_column(column) = connection.quote_column_name(column)
239
+ <% end -%>
234
240
 
235
241
  def migration_record(table)
236
242
  Class.new(ActiveRecord::Base) do
@@ -15,7 +15,7 @@ module RubyLLMUpgrade # :nodoc: all
15
15
  self.enumerate_columns_in_select_statements = true
16
16
  default_scope { RubyLLMUpgrade.visible(self) }
17
17
  after_initialize { @ruby_llm_upgrade_epoch = RubyLLMUpgrade.epoch }
18
- around_save :guard_ruby_llm_upgrade, prepend: true
18
+ around_save :guard_ruby_llm_upgrade
19
19
  around_destroy :guard_ruby_llm_upgrade, prepend: true
20
20
  before_save { RubyLLMUpgrade.prepare_model(self) if ruby_llm_upgrade_kind == :chat }
21
21
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_record/base'
4
3
  require Rails.root.join('app/models/concerns/ruby_llm_upgrade')
5
4
 
6
5
  Rails.application.config.to_prepare do
6
+ require 'active_record/base'
7
7
  tool_call = if Gem::Version.new(RubyLLM::VERSION).segments.first >= 2
8
8
  RubyLLM::ActiveRecord::ToolCall
9
9
  else
@@ -26,6 +26,8 @@ module RubyLLM
26
26
  desc: 'Generate one upgrade phase; cleanup runs in a later deployment'
27
27
  class_option :mode, type: :string, enum: %w[rename copy], default: 'rename',
28
28
  desc: 'Copy prepares and backfills online, with protected 1.16 rollback until cleanup'
29
+ class_option :discard_incomplete_tool_calls, type: :boolean, default: false,
30
+ desc: 'Discard all incomplete legacy tool calls at copy finish'
29
31
 
30
32
  argument :model_mappings,
31
33
  type: :array,
@@ -39,6 +41,7 @@ module RubyLLM
39
41
  end
40
42
 
41
43
  def create_migration_files
44
+ validate_cleanup_options
42
45
  parse_model_mappings(allowed_types: MODEL_MAPPING_TYPES, defaults: MODEL_MAPPING_DEFAULTS)
43
46
  say_status :models, resolved_model_mappings
44
47
 
@@ -132,6 +135,14 @@ module RubyLLM
132
135
  private
133
136
 
134
137
  def copy_mode? = options[:mode] == 'copy'
138
+ def discard_incomplete_tool_calls? = options[:discard_incomplete_tool_calls]
139
+
140
+ def validate_cleanup_options
141
+ return unless discard_incomplete_tool_calls?
142
+ return if copy_mode? && options[:phase].in?([nil, 'finish'])
143
+
144
+ raise Thor::Error, 'Discard incomplete tool calls only with --mode copy and the finish phase'
145
+ end
135
146
 
136
147
  def copy_upgrade_settings
137
148
  {
@@ -248,7 +248,8 @@ module RubyLLM
248
248
  calls = records(:ruby_llm_tool_calls).where(message_type: configuration.fetch('message_class'), result_id: nil)
249
249
  return unless calls.exists?
250
250
 
251
- raise 'Finish pending tool calls and approvals before switching versions'
251
+ raise 'Finish pending tool calls and approvals before switching versions. ' \
252
+ 'For abandoned legacy calls, see https://rubyllm.com/upgrading/#incomplete-tool-calls'
252
253
  end
253
254
 
254
255
  def reconcile
@@ -101,7 +101,7 @@ module RubyLLM
101
101
  chats = normalize_chats(records)
102
102
 
103
103
  provider = shared_provider(chats)
104
- payload = { provider: provider.slug, provider_class: provider.class.display_name, requests: chats.size }
104
+ payload = { provider: provider.slug, provider_class: provider.name, requests: chats.size }
105
105
  RubyLLM.instrument('batch.ruby_llm', payload, config: provider.config) do |event|
106
106
  requests = chats.each_with_index.map do |chat, index|
107
107
  { custom_id: index.to_s, model: chat.model.id, payload: chat.render }
@@ -120,7 +120,7 @@ module RubyLLM
120
120
  end
121
121
 
122
122
  provider = shared_provider(requests)
123
- payload = { provider: provider.slug, provider_class: provider.class.display_name, requests: requests.size }
123
+ payload = { provider: provider.slug, provider_class: provider.name, requests: requests.size }
124
124
  RubyLLM.instrument('batch.ruby_llm', payload, config: provider.config) do |event|
125
125
  lines = requests.each_with_index.map do |request, index|
126
126
  { custom_id: index.to_s, model: request.model.id, payload: request.render, text: request.text }
data/lib/ruby_llm/chat.rb CHANGED
@@ -1003,7 +1003,7 @@ module RubyLLM
1003
1003
  {
1004
1004
  chat: self,
1005
1005
  provider: @provider.slug,
1006
- provider_class: @provider.class.display_name,
1006
+ provider_class: @provider.name,
1007
1007
  model: @model.id,
1008
1008
  model_info: @model,
1009
1009
  input_messages: messages.dup,
@@ -1347,7 +1347,7 @@ module RubyLLM
1347
1347
  payload = {
1348
1348
  chat: self,
1349
1349
  provider: @provider.slug,
1350
- provider_class: @provider.class.display_name,
1350
+ provider_class: @provider.name,
1351
1351
  model: @model.id,
1352
1352
  model_info: @model,
1353
1353
  tool: tool,
@@ -117,7 +117,7 @@ module RubyLLM
117
117
 
118
118
  payload = {
119
119
  provider: provider_instance.slug,
120
- provider_class: provider_instance.class.display_name,
120
+ provider_class: provider_instance.name,
121
121
  model: model_id,
122
122
  model_info: model,
123
123
  input: text,
@@ -75,7 +75,7 @@ module RubyLLM
75
75
  empty_tokens = Tokens.new
76
76
  payload = {
77
77
  provider: provider_instance.slug,
78
- provider_class: provider_instance.class.display_name,
78
+ provider_class: provider_instance.name,
79
79
  model: model.id,
80
80
  model_info: model,
81
81
  prompt: prompt,