ruby_llm 2.0.0.rc3 → 2.0.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/.rdoc_options +2 -2
- data/README.md +34 -23
- data/lib/generators/ruby_llm/generator_helpers.rb +4 -1
- 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_ruby_llm_records_migration.rb.tt +5 -5
- data/lib/generators/ruby_llm/upgrade/online_copy_migration/data.rb +9 -0
- data/lib/generators/ruby_llm/upgrade/online_copy_migration.rb +5 -1
- data/lib/generators/ruby_llm/upgrade/templates/backfill_v2_data.rb.tt +6 -2
- data/lib/generators/ruby_llm/upgrade/templates/finish_v2_upgrade.rb.tt +8 -2
- data/lib/generators/ruby_llm/upgrade/templates/prepare_v2_upgrade.rb.tt +2 -2
- data/lib/generators/ruby_llm/upgrade/templates/ruby_llm_upgrade.rb.tt +1 -1
- data/lib/generators/ruby_llm/upgrade/templates/upgrade_initializer.rb.tt +1 -1
- data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +11 -0
- data/lib/generators/ruby_llm/upgrade/upgrade_migration.rb +2 -1
- data/lib/ruby_llm/active_record/attachment_helpers.rb +81 -3
- data/lib/ruby_llm/active_record/chat_methods.rb +11 -8
- data/lib/ruby_llm/agent.rb +17 -17
- data/lib/ruby_llm/batch.rb +2 -2
- data/lib/ruby_llm/chat.rb +17 -17
- data/lib/ruby_llm/embedding.rb +1 -1
- data/lib/ruby_llm/error.rb +1 -1
- data/lib/ruby_llm/image.rb +1 -1
- data/lib/ruby_llm/message.rb +15 -1
- data/lib/ruby_llm/models.json +1654 -1791
- data/lib/ruby_llm/moderation.rb +1 -1
- data/lib/ruby_llm/ocr.rb +1 -1
- data/lib/ruby_llm/protocol.rb +36 -13
- data/lib/ruby_llm/protocols/anthropic/chat.rb +1 -6
- data/lib/ruby_llm/protocols/chat_completions/chat.rb +2 -11
- data/lib/ruby_llm/protocols/converse/chat.rb +0 -1
- data/lib/ruby_llm/protocols/responses/chat.rb +0 -10
- data/lib/ruby_llm/protocols/vertexai/research.rb +8 -8
- data/lib/ruby_llm/provider.rb +4 -4
- data/lib/ruby_llm/providers/openrouter/chat.rb +5 -5
- data/lib/ruby_llm/rerank.rb +1 -1
- data/lib/ruby_llm/research_job.rb +4 -4
- data/lib/ruby_llm/server_tool_call.rb +2 -2
- data/lib/ruby_llm/speech.rb +1 -1
- data/lib/ruby_llm/tools/{server_tools.rb → provider_tools.rb} +7 -7
- data/lib/ruby_llm/transcription.rb +1 -1
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm/video_job.rb +1 -1
- data/lib/ruby_llm.rb +2 -2
- metadata +7 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 42dd9dfdb910f8e5df4eb6114ed59586a13e4a51797173d3f3bdb13b6d59c28b
|
|
4
|
+
data.tar.gz: fa4e0aa374c915ab5e97146bcacb438fc2c3fca0c029ed51c1aea6c934769da8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e5489b6fdd071de5c4281c9cc8eff9d345dd10876cb6773144873d2e58c3ec400109d208706d4c04c12d93328f5864f839907521fd751accc29aa9a5a753602
|
|
7
|
+
data.tar.gz: 19c33af936f0626262ad3e469c955b98aee8599b15fe088ba745e31584f91ebbf166cabc981bef4bef526f6e6c96800aebe07dc133ca732313ca58598aa6273f
|
data/.rdoc_options
CHANGED
|
@@ -17,8 +17,8 @@ exclude:
|
|
|
17
17
|
- \.json$
|
|
18
18
|
footer_content:
|
|
19
19
|
Documentation:
|
|
20
|
-
Guides: https://rubyllm.com/
|
|
21
|
-
Upgrading: https://rubyllm.com/
|
|
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>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
+
[Browse models and pricing](https://rubyllm.com/available-models/) · [Connect a provider](https://rubyllm.com/configuration-providers/)
|
|
38
34
|
|
|
39
|
-
##
|
|
35
|
+
## Start with one line. Add files, tools, and agents
|
|
40
36
|
|
|
41
|
-
These examples use **2.0.0
|
|
37
|
+
These examples use **2.0.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/).
|
|
42
38
|
|
|
43
39
|
```ruby
|
|
44
|
-
# Just ask
|
|
45
|
-
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
|
-
##
|
|
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
|
|
@@ -170,7 +167,7 @@ response.parsed
|
|
|
170
167
|
* **Tools:** Let AI call your Ruby methods
|
|
171
168
|
* **Tool approval:** Park a run until a human approves with `requires_approval`
|
|
172
169
|
* **The agentic loop:** Drive it yourself with `ask_later`, `step`, and `complete?`
|
|
173
|
-
* **
|
|
170
|
+
* **Provider tools:** Web search, code execution, and MCP connectors with `with_provider_tools`
|
|
174
171
|
* **Agents:** Reusable assistants with `RubyLLM::Agent`
|
|
175
172
|
* **Prompt templates:** ERB prompts in `app/prompts`, rendered with `RubyLLM.render_prompt`
|
|
176
173
|
* **Workflows:** Correlate multi-agent runs in your telemetry with `RubyLLM.workflow`
|
|
@@ -192,10 +189,10 @@ response.parsed
|
|
|
192
189
|
|
|
193
190
|
## Installation
|
|
194
191
|
|
|
195
|
-
Install
|
|
192
|
+
Install RubyLLM 2.0:
|
|
196
193
|
|
|
197
194
|
```bash
|
|
198
|
-
bundle add ruby_llm --version 2.0.0
|
|
195
|
+
bundle add ruby_llm --version 2.0.0
|
|
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/
|
|
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
|
-
|
|
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/
|
|
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
|
|
|
@@ -138,7 +138,10 @@ module RubyLLM
|
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
def reference_type
|
|
141
|
-
Rails.application.
|
|
141
|
+
application = Rails.application if Rails.respond_to?(:application)
|
|
142
|
+
return :bigint unless application
|
|
143
|
+
|
|
144
|
+
application.config.generators.options.dig(:active_record, :primary_key_type) || :bigint
|
|
142
145
|
end
|
|
143
146
|
|
|
144
147
|
def create_migration_class_name(table_name)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
class <%= create_migration_class_name(chat_table_name) %> < ActiveRecord::Migration<%= migration_version %>
|
|
2
2
|
def change
|
|
3
|
-
create_table :<%= chat_table_name %> do |t|
|
|
3
|
+
create_table :<%= chat_table_name %>, id: :<%= reference_type %> do |t|
|
|
4
4
|
t.references :ruby_llm_model, null: false, foreign_key: { to_table: :ruby_llm_models }, type: :<%= reference_type %>
|
|
5
5
|
t.boolean :cancelled, null: false, default: false
|
|
6
6
|
t.timestamps
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
class <%= create_migration_class_name(message_table_name) %> < ActiveRecord::Migration<%= migration_version %>
|
|
2
2
|
def change
|
|
3
|
-
create_table :<%= message_table_name %> do |t|
|
|
3
|
+
create_table :<%= message_table_name %>, id: :<%= reference_type %> do |t|
|
|
4
4
|
t.references :<%= chat_table_name.singularize %>, null: false, foreign_key: { to_table: :<%= chat_table_name %> }, type: :<%= reference_type %>
|
|
5
5
|
t.string :role, null: false
|
|
6
6
|
t.text :content
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
class
|
|
1
|
+
class <%= create_migration_class_name('ruby_llm_records') %> < ActiveRecord::Migration<%= migration_version %>
|
|
2
2
|
def change
|
|
3
|
-
create_table :ruby_llm_models do |t|
|
|
3
|
+
create_table :ruby_llm_models, id: :<%= reference_type %> do |t|
|
|
4
4
|
t.string :model_id, null: false
|
|
5
5
|
t.string :name, null: false
|
|
6
6
|
t.string :provider, null: false
|
|
@@ -37,7 +37,7 @@ class CreateRubyLlmRecords < ActiveRecord::Migration<%= migration_version %>
|
|
|
37
37
|
<% end -%>
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
create_table :ruby_llm_tool_calls do |t|
|
|
40
|
+
create_table :ruby_llm_tool_calls, id: :<%= reference_type %> do |t|
|
|
41
41
|
t.references :message, polymorphic: true, null: false, type: :<%= reference_type %>, index: false
|
|
42
42
|
t.references :result, polymorphic: true, type: :<%= reference_type %>, index: false
|
|
43
43
|
t.string :tool_call_id, null: false
|
|
@@ -60,7 +60,7 @@ class CreateRubyLlmRecords < ActiveRecord::Migration<%= migration_version %>
|
|
|
60
60
|
t.index :name
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
create_table :ruby_llm_usages do |t|
|
|
63
|
+
create_table :ruby_llm_usages, id: :<%= reference_type %> do |t|
|
|
64
64
|
t.references :chat, polymorphic: true, null: false, type: :<%= reference_type %>, index: false
|
|
65
65
|
t.references :message, polymorphic: true, type: :<%= reference_type %>, index: false
|
|
66
66
|
t.string :operation, null: false
|
|
@@ -87,7 +87,7 @@ class CreateRubyLlmRecords < ActiveRecord::Migration<%= migration_version %>
|
|
|
87
87
|
t.check_constraint "status IN (<%= usage_statuses_sql %>)"
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
create_table :ruby_llm_batches do |t|
|
|
90
|
+
create_table :ruby_llm_batches, id: :<%= reference_type %> do |t|
|
|
91
91
|
t.string :provider_batch_id, null: false
|
|
92
92
|
t.string :provider, null: false
|
|
93
93
|
t.string :status, null: false
|
|
@@ -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
|
|
@@ -382,7 +382,7 @@ class <%= prepare_migration_class_name %> < ActiveRecord::Migration<%= migration
|
|
|
382
382
|
|
|
383
383
|
def create_usages
|
|
384
384
|
unless table_exists?(:ruby_llm_usages)
|
|
385
|
-
create_table :ruby_llm_usages do |table|
|
|
385
|
+
create_table :ruby_llm_usages, id: :<%= reference_type %> do |table|
|
|
386
386
|
table.references :chat, polymorphic: true, null: false,
|
|
387
387
|
type: reference_type_for(:<%= chat_table_name %>), index: false
|
|
388
388
|
table.references :message, polymorphic: true,
|
|
@@ -417,7 +417,7 @@ class <%= prepare_migration_class_name %> < ActiveRecord::Migration<%= migration
|
|
|
417
417
|
|
|
418
418
|
def create_batches
|
|
419
419
|
unless table_exists?(:ruby_llm_batches)
|
|
420
|
-
create_table :ruby_llm_batches do |table|
|
|
420
|
+
create_table :ruby_llm_batches, id: :<%= reference_type %> do |table|
|
|
421
421
|
table.string :provider_batch_id, null: false
|
|
422
422
|
table.string :provider, null: false
|
|
423
423
|
table.string :status, null: false
|
|
@@ -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
|
|
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
|
|
@@ -58,18 +58,96 @@ module RubyLLM
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def plain_text_content(content_value)
|
|
61
|
+
return action_text_plain_text(content_value) if action_text_content?(content_value)
|
|
61
62
|
return content_value.to_plain_text if content_value.respond_to?(:to_plain_text)
|
|
62
63
|
|
|
63
64
|
content_value
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
def action_text_attachment_sources(content_value)
|
|
67
|
-
return [] unless
|
|
68
|
+
return [] unless action_text_content?(content_value)
|
|
68
69
|
|
|
70
|
+
action_text_attachables(content_value).flat_map do |attachable|
|
|
71
|
+
action_text_attachable_sources(attachable)
|
|
72
|
+
end.compact
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def action_text_content?(content_value)
|
|
76
|
+
content_value.respond_to?(:body) && action_text_body?(content_value.body)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def preload_action_text_embeds(messages)
|
|
80
|
+
return unless defined?(ActionText::Attachment)
|
|
81
|
+
|
|
82
|
+
rich_texts = messages.filter_map do |message|
|
|
83
|
+
rich_text = message.rich_text_content
|
|
84
|
+
body = rich_text&.body
|
|
85
|
+
rich_text if action_text_body?(body) && action_text_embeds?(body)
|
|
86
|
+
end
|
|
87
|
+
return if rich_texts.empty?
|
|
88
|
+
|
|
89
|
+
::ActiveRecord::Associations::Preloader.new(
|
|
90
|
+
records: rich_texts,
|
|
91
|
+
associations: { embeds_attachments: :blob }
|
|
92
|
+
).call
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def action_text_embeds?(body)
|
|
96
|
+
body.fragment.find_all(ActionText::Attachment.tag_name).any? { |node| action_text_blob_id(node) }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def action_text_body?(body)
|
|
100
|
+
defined?(ActionText::Attachment) && body.respond_to?(:fragment) && body.respond_to?(:attachables) &&
|
|
101
|
+
body.respond_to?(:sanitize_content_attachment)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def action_text_plain_text(content_value)
|
|
105
|
+
body = content_value.body
|
|
106
|
+
preloaded_blobs = action_text_preloaded_blobs(content_value).index_by { |blob| blob.id.to_s }
|
|
107
|
+
rendered = body.fragment.replace(ActionText::Attachment.tag_name) do |node|
|
|
108
|
+
if node.key?('content')
|
|
109
|
+
sanitized_content = body.sanitize_content_attachment(node.remove_attribute('content').to_s)
|
|
110
|
+
node['content'] = sanitized_content if sanitized_content.present?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
attachable = action_text_attachable_for_node(node, preloaded_blobs)
|
|
114
|
+
ActionText::Attachment.from_node(node, attachable).to_plain_text
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
ActionText::Content.new(rendered, canonicalize: false).fragment.to_plain_text
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def action_text_attachables(content_value)
|
|
69
121
|
body = content_value.body
|
|
70
|
-
|
|
122
|
+
preloaded_blobs = action_text_preloaded_blobs(content_value).index_by { |blob| blob.id.to_s }
|
|
123
|
+
body.fragment.find_all(ActionText::Attachment.tag_name).map do |node|
|
|
124
|
+
action_text_attachable_for_node(node, preloaded_blobs)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
71
127
|
|
|
72
|
-
|
|
128
|
+
def action_text_attachable_for_node(node, preloaded_blobs)
|
|
129
|
+
preloaded_blobs[action_text_blob_id(node)] || ActionText::Attachable.from_node(node)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def action_text_blob_id(node)
|
|
133
|
+
sgid = node['sgid']
|
|
134
|
+
gid = SignedGlobalID.parse(sgid, for: ActionText::Attachable::LOCATOR_NAME) if sgid
|
|
135
|
+
return unless gid && gid.app == GlobalID.app && gid.model_name == ActiveStorage::Blob.name
|
|
136
|
+
|
|
137
|
+
gid.model_id.to_s
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def action_text_preloaded_blobs(content_value)
|
|
141
|
+
return [] unless content_value.class.respond_to?(:reflect_on_association) &&
|
|
142
|
+
content_value.class.reflect_on_association(:embeds_attachments)
|
|
143
|
+
|
|
144
|
+
association = content_value.association(:embeds_attachments)
|
|
145
|
+
return [] unless association.loaded?
|
|
146
|
+
|
|
147
|
+
content_value.embeds_attachments.filter_map do |attachment|
|
|
148
|
+
blob_association = attachment.association(:blob)
|
|
149
|
+
blob_association.target if blob_association.loaded?
|
|
150
|
+
end
|
|
73
151
|
end
|
|
74
152
|
|
|
75
153
|
def action_text_attachable_sources(attachable)
|
|
@@ -212,7 +212,7 @@ module RubyLLM
|
|
|
212
212
|
# RubyLLM::Chat. Each behaves exactly as documented on RubyLLM::Chat,
|
|
213
213
|
# then returns the record so calls chain.
|
|
214
214
|
CHAINABLE_CHAT_DELEGATES = %i[
|
|
215
|
-
with_tools with_tool_options
|
|
215
|
+
with_tools with_tool_options with_provider_tools with_fallbacks with_temperature
|
|
216
216
|
with_max_output_tokens with_thinking with_citations with_caching
|
|
217
217
|
with_end_user with_compaction
|
|
218
218
|
with_provider_options with_headers with_schema
|
|
@@ -233,10 +233,10 @@ module RubyLLM
|
|
|
233
233
|
# Applies Chat#with_tool_options and returns this record.
|
|
234
234
|
|
|
235
235
|
##
|
|
236
|
-
# :method:
|
|
237
|
-
# :call-seq:
|
|
236
|
+
# :method: with_provider_tools
|
|
237
|
+
# :call-seq: with_provider_tools(*tools, **tools_with_options)
|
|
238
238
|
#
|
|
239
|
-
# Applies Chat#
|
|
239
|
+
# Applies Chat#with_provider_tools and returns this record.
|
|
240
240
|
|
|
241
241
|
##
|
|
242
242
|
# :method: with_fallbacks
|
|
@@ -407,10 +407,10 @@ module RubyLLM
|
|
|
407
407
|
# Delegates to Chat#schema. See that method for arguments and return values.
|
|
408
408
|
|
|
409
409
|
##
|
|
410
|
-
# :method:
|
|
411
|
-
# :call-seq:
|
|
410
|
+
# :method: provider_tools
|
|
411
|
+
# :call-seq: provider_tools
|
|
412
412
|
#
|
|
413
|
-
# Delegates to Chat#
|
|
413
|
+
# Delegates to Chat#provider_tools. See that method for arguments and return values.
|
|
414
414
|
|
|
415
415
|
##
|
|
416
416
|
# :method: temperature
|
|
@@ -449,7 +449,7 @@ module RubyLLM
|
|
|
449
449
|
|
|
450
450
|
PASSTHROUGH_CHAT_DELEGATES = %i[
|
|
451
451
|
caching citations compaction concurrency end_user fallbacks headers max_output_tokens provider_options
|
|
452
|
-
schema
|
|
452
|
+
schema provider_tools temperature thinking tool_options tools
|
|
453
453
|
add_completion count_tokens each render
|
|
454
454
|
].freeze
|
|
455
455
|
|
|
@@ -778,8 +778,11 @@ module RubyLLM
|
|
|
778
778
|
|
|
779
779
|
associations = %i[ruby_llm_tool_calls ruby_llm_parent_tool_call ruby_llm_usages]
|
|
780
780
|
associations << { attachments_attachments: :blob } if attachment_association?(assoc.klass)
|
|
781
|
+
rich_text_reflection = assoc.klass.reflect_on_association(:rich_text_content)
|
|
782
|
+
associations << :rich_text_content if rich_text_reflection
|
|
781
783
|
|
|
782
784
|
::ActiveRecord::Associations::Preloader.new(records: messages, associations: associations).call
|
|
785
|
+
preload_action_text_embeds(messages) if rich_text_reflection
|
|
783
786
|
messages
|
|
784
787
|
end
|
|
785
788
|
|
data/lib/ruby_llm/agent.rb
CHANGED
|
@@ -44,7 +44,7 @@ module RubyLLM
|
|
|
44
44
|
DUPED_INHERITED_CONFIG = {
|
|
45
45
|
:@chat_kwargs => {},
|
|
46
46
|
:@tools => [],
|
|
47
|
-
:@
|
|
47
|
+
:@provider_tools => [],
|
|
48
48
|
:@tool_options => {},
|
|
49
49
|
:@caching => nil,
|
|
50
50
|
:@compaction => nil,
|
|
@@ -66,7 +66,7 @@ module RubyLLM
|
|
|
66
66
|
|
|
67
67
|
# Chat methods that return the wrapped chat so calls can be chained there.
|
|
68
68
|
CHAINABLE_CHAT_DELEGATES = %i[
|
|
69
|
-
with_instructions with_tools
|
|
69
|
+
with_instructions with_tools with_provider_tools with_tool_options with_model
|
|
70
70
|
with_temperature with_max_output_tokens with_thinking with_citations
|
|
71
71
|
with_end_user with_compaction with_caching with_context with_provider_options
|
|
72
72
|
with_headers with_schema with_fallbacks
|
|
@@ -77,7 +77,7 @@ module RubyLLM
|
|
|
77
77
|
|
|
78
78
|
# Chat values and operations whose return values pass through unchanged.
|
|
79
79
|
PASSTHROUGH_CHAT_DELEGATES = %i[
|
|
80
|
-
model provider messages tools
|
|
80
|
+
model provider messages tools provider_tools tool_options provider_options headers schema concurrency
|
|
81
81
|
caching citations compaction context end_user fallbacks thinking temperature max_output_tokens
|
|
82
82
|
each complete? cancelled? awaiting_approval? pending_approvals
|
|
83
83
|
add_message add_completion tokens cost render
|
|
@@ -150,17 +150,17 @@ module RubyLLM
|
|
|
150
150
|
end
|
|
151
151
|
|
|
152
152
|
# Enables provider-executed tools for chats this agent builds, applied
|
|
153
|
-
# via Chat#
|
|
153
|
+
# via Chat#with_provider_tools. Accepts the same aliases, options, and
|
|
154
154
|
# raw Hashes; a block defers evaluation until the chat is built.
|
|
155
155
|
# Called with no arguments, returns the declared entries.
|
|
156
156
|
#
|
|
157
|
-
#
|
|
158
|
-
#
|
|
157
|
+
# provider_tools :web_search
|
|
158
|
+
# provider_tools web_search: { allowed_domains: ["ruby-lang.org"] }
|
|
159
159
|
#
|
|
160
|
-
def
|
|
161
|
-
return @
|
|
160
|
+
def provider_tools(*tools, **tools_with_options, &block)
|
|
161
|
+
return @provider_tools || [] if tools.empty? && tools_with_options.empty? && !block_given?
|
|
162
162
|
|
|
163
|
-
@
|
|
163
|
+
@provider_tools = block_given? ? block : RubyLLM::Tools::ProviderTools.normalize(tools, tools_with_options)
|
|
164
164
|
end
|
|
165
165
|
|
|
166
166
|
# Adds system instructions for chats this agent builds. Accepts a string,
|
|
@@ -667,8 +667,8 @@ module RubyLLM
|
|
|
667
667
|
options = evaluate(tool_options, runtime)
|
|
668
668
|
chat.with_tool_options(**options) if options && !options.empty?
|
|
669
669
|
|
|
670
|
-
|
|
671
|
-
chat.
|
|
670
|
+
provider_tools_to_apply = Array(evaluate(provider_tools, runtime)).compact
|
|
671
|
+
chat.with_provider_tools(*provider_tools_to_apply) if provider_tools_to_apply.any?
|
|
672
672
|
end
|
|
673
673
|
|
|
674
674
|
def apply_passthrough_options(chat)
|
|
@@ -873,10 +873,10 @@ module RubyLLM
|
|
|
873
873
|
# Delegates to Chat#with_tools. See that method for arguments and return values.
|
|
874
874
|
|
|
875
875
|
##
|
|
876
|
-
# :method:
|
|
877
|
-
# :call-seq:
|
|
876
|
+
# :method: with_provider_tools
|
|
877
|
+
# :call-seq: with_provider_tools(*tools, **tools_with_options)
|
|
878
878
|
#
|
|
879
|
-
# Delegates to Chat#
|
|
879
|
+
# Delegates to Chat#with_provider_tools. See that method for arguments and return values.
|
|
880
880
|
|
|
881
881
|
##
|
|
882
882
|
# :method: with_tool_options
|
|
@@ -1053,10 +1053,10 @@ module RubyLLM
|
|
|
1053
1053
|
# Delegates to Chat#tools. See that method for arguments and return values.
|
|
1054
1054
|
|
|
1055
1055
|
##
|
|
1056
|
-
# :method:
|
|
1057
|
-
# :call-seq:
|
|
1056
|
+
# :method: provider_tools
|
|
1057
|
+
# :call-seq: provider_tools
|
|
1058
1058
|
#
|
|
1059
|
-
# Delegates to Chat#
|
|
1059
|
+
# Delegates to Chat#provider_tools. See that method for arguments and return values.
|
|
1060
1060
|
|
|
1061
1061
|
##
|
|
1062
1062
|
# :method: tool_options
|