ruby_llm 1.12.0 → 1.14.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 +11 -5
- data/lib/generators/ruby_llm/agent/agent_generator.rb +36 -0
- data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +6 -0
- data/lib/generators/ruby_llm/agent/templates/instructions.txt.erb.tt +0 -0
- data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +110 -41
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +14 -15
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +8 -11
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/helpers/messages_helper.rb.tt +25 -0
- data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +16 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +31 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +31 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/new.html.erb.tt +9 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +27 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_content.html.erb.tt +1 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +23 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +10 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +2 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +4 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +21 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +17 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +40 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +27 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +19 -7
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +5 -3
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +9 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_content.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_error.html.erb.tt +8 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +6 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +2 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +4 -7
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +9 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +5 -7
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +8 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +16 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +11 -12
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +27 -17
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +3 -4
- data/lib/generators/ruby_llm/generator_helpers.rb +37 -17
- data/lib/generators/ruby_llm/install/install_generator.rb +22 -18
- 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_models_migration.rb.tt +4 -10
- data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +2 -2
- data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +2 -2
- data/lib/generators/ruby_llm/schema/schema_generator.rb +26 -0
- data/lib/generators/ruby_llm/schema/templates/schema.rb.tt +2 -0
- data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +9 -0
- data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/tool/tool_generator.rb +96 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +1 -1
- data/lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +7 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +49 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +2 -4
- data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +1 -1
- data/lib/ruby_llm/active_record/acts_as.rb +10 -4
- data/lib/ruby_llm/active_record/acts_as_legacy.rb +87 -20
- data/lib/ruby_llm/active_record/chat_methods.rb +80 -22
- data/lib/ruby_llm/active_record/message_methods.rb +17 -0
- data/lib/ruby_llm/active_record/model_methods.rb +1 -1
- data/lib/ruby_llm/active_record/payload_helpers.rb +26 -0
- data/lib/ruby_llm/active_record/tool_call_methods.rb +15 -0
- data/lib/ruby_llm/agent.rb +50 -8
- data/lib/ruby_llm/aliases.json +60 -21
- data/lib/ruby_llm/attachment.rb +4 -1
- data/lib/ruby_llm/chat.rb +113 -12
- data/lib/ruby_llm/configuration.rb +65 -66
- data/lib/ruby_llm/connection.rb +11 -7
- data/lib/ruby_llm/content.rb +6 -2
- data/lib/ruby_llm/error.rb +37 -1
- data/lib/ruby_llm/message.rb +5 -3
- data/lib/ruby_llm/model/info.rb +15 -13
- data/lib/ruby_llm/models.json +12279 -13517
- data/lib/ruby_llm/models.rb +16 -6
- data/lib/ruby_llm/provider.rb +10 -1
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +5 -119
- data/lib/ruby_llm/providers/anthropic/chat.rb +22 -5
- data/lib/ruby_llm/providers/anthropic/models.rb +3 -9
- data/lib/ruby_llm/providers/anthropic/tools.rb +20 -0
- data/lib/ruby_llm/providers/anthropic.rb +5 -1
- data/lib/ruby_llm/providers/azure/chat.rb +1 -1
- data/lib/ruby_llm/providers/azure/embeddings.rb +1 -1
- data/lib/ruby_llm/providers/azure/models.rb +1 -1
- data/lib/ruby_llm/providers/azure.rb +92 -0
- data/lib/ruby_llm/providers/bedrock/chat.rb +50 -5
- data/lib/ruby_llm/providers/bedrock/models.rb +17 -1
- data/lib/ruby_llm/providers/bedrock/streaming.rb +8 -4
- data/lib/ruby_llm/providers/bedrock.rb +9 -1
- data/lib/ruby_llm/providers/deepseek/capabilities.rb +4 -114
- data/lib/ruby_llm/providers/deepseek.rb +5 -1
- data/lib/ruby_llm/providers/gemini/capabilities.rb +45 -207
- data/lib/ruby_llm/providers/gemini/chat.rb +20 -4
- data/lib/ruby_llm/providers/gemini/images.rb +1 -1
- data/lib/ruby_llm/providers/gemini/models.rb +2 -4
- data/lib/ruby_llm/providers/gemini/streaming.rb +2 -1
- data/lib/ruby_llm/providers/gemini/tools.rb +19 -0
- data/lib/ruby_llm/providers/gemini.rb +4 -0
- data/lib/ruby_llm/providers/gpustack/capabilities.rb +20 -0
- data/lib/ruby_llm/providers/gpustack.rb +8 -0
- data/lib/ruby_llm/providers/mistral/capabilities.rb +8 -0
- data/lib/ruby_llm/providers/mistral/chat.rb +2 -1
- data/lib/ruby_llm/providers/mistral.rb +4 -0
- data/lib/ruby_llm/providers/ollama/capabilities.rb +20 -0
- data/lib/ruby_llm/providers/ollama.rb +11 -1
- data/lib/ruby_llm/providers/openai/capabilities.rb +95 -195
- data/lib/ruby_llm/providers/openai/chat.rb +15 -5
- data/lib/ruby_llm/providers/openai/media.rb +4 -1
- data/lib/ruby_llm/providers/openai/models.rb +2 -4
- data/lib/ruby_llm/providers/openai/temperature.rb +2 -2
- data/lib/ruby_llm/providers/openai/tools.rb +27 -2
- data/lib/ruby_llm/providers/openai.rb +10 -0
- data/lib/ruby_llm/providers/openrouter/chat.rb +19 -5
- data/lib/ruby_llm/providers/openrouter/images.rb +69 -0
- data/lib/ruby_llm/providers/openrouter.rb +35 -1
- data/lib/ruby_llm/providers/perplexity/capabilities.rb +34 -99
- data/lib/ruby_llm/providers/perplexity/models.rb +12 -14
- data/lib/ruby_llm/providers/perplexity.rb +4 -0
- data/lib/ruby_llm/providers/vertexai/models.rb +1 -1
- data/lib/ruby_llm/providers/vertexai.rb +18 -6
- data/lib/ruby_llm/providers/xai.rb +4 -0
- data/lib/ruby_llm/stream_accumulator.rb +10 -5
- data/lib/ruby_llm/streaming.rb +7 -7
- data/lib/ruby_llm/tool.rb +48 -3
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/tasks/models.rake +33 -7
- data/lib/tasks/release.rake +1 -1
- data/lib/tasks/ruby_llm.rake +9 -1
- data/lib/tasks/vcr.rake +1 -1
- metadata +56 -15
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +0 -13
|
@@ -53,9 +53,9 @@ module RubyLLM
|
|
|
53
53
|
params = []
|
|
54
54
|
|
|
55
55
|
add_association_params(params, :messages, message_table_name, message_model_name,
|
|
56
|
-
owner_table: chat_table_name, plural: true)
|
|
56
|
+
owner_table: chat_table_name, owner_model_name: chat_model_name, plural: true)
|
|
57
57
|
add_association_params(params, :model, model_table_name, model_model_name,
|
|
58
|
-
owner_table: chat_table_name)
|
|
58
|
+
owner_table: chat_table_name, owner_model_name: chat_model_name)
|
|
59
59
|
|
|
60
60
|
"acts_as_chat#{" #{params.join(', ')}" if params.any?}"
|
|
61
61
|
end
|
|
@@ -64,11 +64,11 @@ module RubyLLM
|
|
|
64
64
|
params = []
|
|
65
65
|
|
|
66
66
|
add_association_params(params, :chat, chat_table_name, chat_model_name,
|
|
67
|
-
owner_table: message_table_name)
|
|
67
|
+
owner_table: message_table_name, owner_model_name: message_model_name)
|
|
68
68
|
add_association_params(params, :tool_calls, tool_call_table_name, tool_call_model_name,
|
|
69
|
-
owner_table: message_table_name, plural: true)
|
|
69
|
+
owner_table: message_table_name, owner_model_name: message_model_name, plural: true)
|
|
70
70
|
add_association_params(params, :model, model_table_name, model_model_name,
|
|
71
|
-
owner_table: message_table_name)
|
|
71
|
+
owner_table: message_table_name, owner_model_name: message_model_name)
|
|
72
72
|
|
|
73
73
|
"acts_as_message#{" #{params.join(', ')}" if params.any?}"
|
|
74
74
|
end
|
|
@@ -77,7 +77,7 @@ module RubyLLM
|
|
|
77
77
|
params = []
|
|
78
78
|
|
|
79
79
|
add_association_params(params, :chats, chat_table_name, chat_model_name,
|
|
80
|
-
owner_table: model_table_name, plural: true)
|
|
80
|
+
owner_table: model_table_name, owner_model_name: model_model_name, plural: true)
|
|
81
81
|
|
|
82
82
|
"acts_as_model#{" #{params.join(', ')}" if params.any?}"
|
|
83
83
|
end
|
|
@@ -86,7 +86,7 @@ module RubyLLM
|
|
|
86
86
|
params = []
|
|
87
87
|
|
|
88
88
|
add_association_params(params, :message, message_table_name, message_model_name,
|
|
89
|
-
owner_table: tool_call_table_name)
|
|
89
|
+
owner_table: tool_call_table_name, owner_model_name: tool_call_model_name)
|
|
90
90
|
|
|
91
91
|
"acts_as_tool_call#{" #{params.join(', ')}" if params.any?}"
|
|
92
92
|
end
|
|
@@ -121,6 +121,10 @@ module RubyLLM
|
|
|
121
121
|
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
+
def create_migration_class_name(table_name)
|
|
125
|
+
"create_#{table_name}".camelize
|
|
126
|
+
end
|
|
127
|
+
|
|
124
128
|
def postgresql?
|
|
125
129
|
::ActiveRecord::Base.connection.adapter_name.downcase.include?('postgresql')
|
|
126
130
|
rescue StandardError
|
|
@@ -141,22 +145,38 @@ module RubyLLM
|
|
|
141
145
|
|
|
142
146
|
private
|
|
143
147
|
|
|
144
|
-
|
|
148
|
+
# rubocop:disable Metrics/ParameterLists
|
|
149
|
+
def add_association_params(params, default_assoc, table_name, model_name,
|
|
150
|
+
owner_table:, owner_model_name:, plural: false)
|
|
145
151
|
assoc = plural ? table_name.to_sym : table_name.singularize.to_sym
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
# belongs_to: foreign key is on the owner table pointing to associated table
|
|
150
|
-
foreign_key = if plural || default_assoc.to_s.pluralize == default_assoc.to_s # has_many or has_one
|
|
151
|
-
"#{owner_table.singularize}_id"
|
|
152
|
-
else # belongs_to
|
|
153
|
-
"#{table_name.singularize}_id"
|
|
154
|
-
end
|
|
152
|
+
collection_association = collection_association?(default_assoc, plural)
|
|
153
|
+
foreign_key = inferred_foreign_key(table_name, owner_table, collection_association)
|
|
154
|
+
default_foreign_key = default_inferred_foreign_key(assoc, owner_model_name, collection_association)
|
|
155
155
|
|
|
156
156
|
params << "#{default_assoc}: :#{assoc}" if assoc != default_assoc
|
|
157
157
|
params << "#{default_assoc.to_s.singularize}_class: '#{model_name}'" if model_name != assoc.to_s.classify
|
|
158
158
|
params << "#{default_assoc}_foreign_key: :#{foreign_key}" if foreign_key != default_foreign_key
|
|
159
159
|
end
|
|
160
|
+
# rubocop:enable Metrics/ParameterLists
|
|
161
|
+
|
|
162
|
+
def collection_association?(default_assoc, plural)
|
|
163
|
+
plural || default_assoc.to_s.pluralize == default_assoc.to_s
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def inferred_foreign_key(table_name, owner_table, collection_association)
|
|
167
|
+
return "#{table_name.singularize}_id" unless collection_association
|
|
168
|
+
|
|
169
|
+
"#{owner_table.singularize}_id"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Rails default inference:
|
|
173
|
+
# belongs_to :assoc -> assoc_id
|
|
174
|
+
# has_many/has_one -> owner demodulized model name + _id
|
|
175
|
+
def default_inferred_foreign_key(association_name, owner_model_name, collection_association)
|
|
176
|
+
return "#{association_name}_id" unless collection_association
|
|
177
|
+
|
|
178
|
+
"#{owner_model_name.demodulize.underscore}_id"
|
|
179
|
+
end
|
|
160
180
|
|
|
161
181
|
# Convert namespaced model names to proper table names
|
|
162
182
|
# e.g., "Assistant::Chat" -> "assistant_chats" (not "assistant/chats")
|
|
@@ -21,7 +21,7 @@ module RubyLLM
|
|
|
21
21
|
desc: 'Skip ActiveStorage installation and attachment setup'
|
|
22
22
|
|
|
23
23
|
desc 'Creates models and migrations for RubyLLM Rails integration\n' \
|
|
24
|
-
'Usage: rails g ruby_llm:install [chat:ChatName] [message:MessageName] ...'
|
|
24
|
+
'Usage: bin/rails g ruby_llm:install [chat:ChatName] [message:MessageName] ...'
|
|
25
25
|
|
|
26
26
|
def self.next_migration_number(dirname)
|
|
27
27
|
::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
@@ -30,20 +30,12 @@ module RubyLLM
|
|
|
30
30
|
def create_migration_files
|
|
31
31
|
migration_template 'create_chats_migration.rb.tt',
|
|
32
32
|
"db/migrate/create_#{chat_table_name}.rb"
|
|
33
|
-
|
|
34
|
-
sleep 1 # Ensure different timestamp
|
|
35
33
|
migration_template 'create_messages_migration.rb.tt',
|
|
36
34
|
"db/migrate/create_#{message_table_name}.rb"
|
|
37
|
-
|
|
38
|
-
sleep 1 # Ensure different timestamp
|
|
39
35
|
migration_template 'create_tool_calls_migration.rb.tt',
|
|
40
36
|
"db/migrate/create_#{tool_call_table_name}.rb"
|
|
41
|
-
|
|
42
|
-
sleep 1 # Ensure different timestamp
|
|
43
37
|
migration_template 'create_models_migration.rb.tt',
|
|
44
38
|
"db/migrate/create_#{model_table_name}.rb"
|
|
45
|
-
|
|
46
|
-
sleep 1 # Ensure different timestamp
|
|
47
39
|
migration_template 'add_references_to_chats_tool_calls_and_messages_migration.rb.tt',
|
|
48
40
|
'db/migrate/add_references_to_' \
|
|
49
41
|
"#{chat_table_name}_#{tool_call_table_name}_and_#{message_table_name}.rb"
|
|
@@ -63,6 +55,13 @@ module RubyLLM
|
|
|
63
55
|
template 'initializer.rb.tt', 'config/initializers/ruby_llm.rb'
|
|
64
56
|
end
|
|
65
57
|
|
|
58
|
+
def create_convention_directories
|
|
59
|
+
%w[agents tools schemas prompts].each do |name|
|
|
60
|
+
empty_directory "app/#{name}"
|
|
61
|
+
create_file "app/#{name}/.gitkeep" unless File.exist?(Rails.root.join("app/#{name}/.gitkeep"))
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
66
65
|
def install_active_storage
|
|
67
66
|
return if options[:skip_active_storage]
|
|
68
67
|
|
|
@@ -76,21 +75,18 @@ module RubyLLM
|
|
|
76
75
|
say ' ✅ ActiveStorage configured for file attachments support', :green unless options[:skip_active_storage]
|
|
77
76
|
|
|
78
77
|
say "\n Next steps:", :yellow
|
|
79
|
-
say ' 1. Run: rails db:migrate'
|
|
80
|
-
say ' 2.
|
|
81
|
-
|
|
82
|
-
say " 3. Start chatting: #{chat_model_name}.create!(model: 'gpt-4.1-nano').ask('Hello!')"
|
|
78
|
+
say ' 1. Run: bin/rails db:migrate'
|
|
79
|
+
say ' 2. Run: bin/rails ruby_llm:load_models'
|
|
80
|
+
say ' 3. Set your API keys in config/initializers/ruby_llm.rb'
|
|
83
81
|
|
|
84
|
-
say "
|
|
85
|
-
say
|
|
86
|
-
say ' Pass model names as strings - RubyLLM handles the rest!'
|
|
87
|
-
say " Specify provider when needed: Chat.create!(model: 'gemini-2.5-flash', provider: 'vertexai')"
|
|
82
|
+
say " 4. Start chatting: #{chat_model_name}.create!(model: 'gpt-5-nano').ask('Hello!')"
|
|
83
|
+
say " 5. Optional UI: #{chat_ui_generator_command}"
|
|
88
84
|
|
|
89
85
|
if options[:skip_active_storage]
|
|
90
86
|
say "\n 📎 Note: ActiveStorage was skipped", :yellow
|
|
91
87
|
say ' File attachments won\'t work without ActiveStorage.'
|
|
92
88
|
say ' To enable later:'
|
|
93
|
-
say ' 1. Run: rails active_storage:install && rails db:migrate'
|
|
89
|
+
say ' 1. Run: bin/rails active_storage:install && bin/rails db:migrate'
|
|
94
90
|
say " 2. Add to your #{message_model_name} model: has_many_attached :attachments"
|
|
95
91
|
end
|
|
96
92
|
|
|
@@ -101,6 +97,14 @@ module RubyLLM
|
|
|
101
97
|
say ' • 🐦 Follow for updates: https://x.com/paolino'
|
|
102
98
|
say "\n"
|
|
103
99
|
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def chat_ui_generator_command
|
|
104
|
+
mappings = model_mappings.join(' ')
|
|
105
|
+
mappings = " #{mappings}" unless mappings.empty?
|
|
106
|
+
"bin/rails generate ruby_llm:chat_ui#{mappings}"
|
|
107
|
+
end
|
|
104
108
|
end
|
|
105
109
|
end
|
|
106
110
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class <%= create_migration_class_name(chat_table_name) %> < ActiveRecord::Migration<%= migration_version %>
|
|
2
2
|
def change
|
|
3
3
|
create_table :<%= chat_table_name %> do |t|
|
|
4
4
|
t.timestamps
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class <%= create_migration_class_name(message_table_name) %> < ActiveRecord::Migration<%= migration_version %>
|
|
2
2
|
def change
|
|
3
3
|
create_table :<%= message_table_name %> do |t|
|
|
4
4
|
t.string :role, null: false
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class <%= create_migration_class_name(model_table_name) %> < ActiveRecord::Migration<%= migration_version %>
|
|
2
2
|
def change
|
|
3
3
|
create_table :<%= model_table_name %> do |t|
|
|
4
4
|
t.string :model_id, null: false
|
|
@@ -27,19 +27,13 @@ class Create<%= model_model_name.gsub('::', '').pluralize %> < ActiveRecord::Mig
|
|
|
27
27
|
<% end %>
|
|
28
28
|
t.timestamps
|
|
29
29
|
|
|
30
|
-
t.index [:provider, :model_id], unique: true
|
|
30
|
+
t.index [ :provider, :model_id ], unique: true
|
|
31
31
|
t.index :provider
|
|
32
32
|
t.index :family
|
|
33
|
-
<% if postgresql?
|
|
33
|
+
<% if postgresql? -%>
|
|
34
34
|
t.index :capabilities, using: :gin
|
|
35
35
|
t.index :modalities, using: :gin
|
|
36
|
-
<% end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Load models from JSON
|
|
40
|
-
say_with_time "Loading models from models.json" do
|
|
41
|
-
RubyLLM.models.load_from_json!
|
|
42
|
-
<%= model_model_name %>.save_to_database
|
|
36
|
+
<% end -%>
|
|
43
37
|
end
|
|
44
38
|
end
|
|
45
39
|
end
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<%#- # Migration for creating tool_calls table with database-specific JSON handling -%>
|
|
2
|
-
class
|
|
2
|
+
class <%= create_migration_class_name(tool_call_table_name) %> < ActiveRecord::Migration<%= migration_version %>
|
|
3
3
|
def change
|
|
4
4
|
create_table :<%= tool_call_table_name %> do |t|
|
|
5
5
|
t.string :tool_call_id, null: false
|
|
6
6
|
t.string :name, null: false
|
|
7
|
-
t.
|
|
7
|
+
t.text :thought_signature
|
|
8
8
|
<% if postgresql? %>
|
|
9
9
|
t.jsonb :arguments, default: {}
|
|
10
10
|
<% elsif mysql? %>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
RubyLLM.configure do |config|
|
|
2
|
-
config.openai_api_key = ENV
|
|
3
|
-
# config.default_model = "gpt-
|
|
2
|
+
config.openai_api_key = ENV.fetch("OPENAI_API_KEY", Rails.application.credentials.dig(:openai_api_key))
|
|
3
|
+
# config.default_model = "gpt-5-nano"
|
|
4
4
|
|
|
5
5
|
# Use the new association-based acts_as API (recommended)
|
|
6
6
|
config.use_new_acts_as = true
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
module Generators
|
|
7
|
+
# Generator for RubyLLM schema classes.
|
|
8
|
+
class SchemaGenerator < Rails::Generators::NamedBase
|
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
|
10
|
+
|
|
11
|
+
namespace 'ruby_llm:schema'
|
|
12
|
+
|
|
13
|
+
desc 'Creates a RubyLLM schema class'
|
|
14
|
+
|
|
15
|
+
def create_schema_file
|
|
16
|
+
template 'schema.rb.tt', File.join('app/schemas', class_path, "#{file_name}.rb")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def schema_class_name
|
|
22
|
+
class_name.end_with?('Schema') ? class_name : "#{class_name}Schema"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%% tool_call_error = tool_call.tool_error_message %>
|
|
2
|
+
<%% if tool_call_error.present? %>
|
|
3
|
+
<%%= render "messages/error", message: tool_calls, title: "Tool Call Error", error_message: tool_call_error %>
|
|
4
|
+
<%% else %>
|
|
5
|
+
<div id="message_<%%= tool_calls.id %>" class="message"
|
|
6
|
+
style="margin-bottom: 20px; padding: 10px; border-left: 3px solid #6b7280; background: #f9fafb;">
|
|
7
|
+
<div style="font-weight: bold; margin-bottom: 5px;"><%= tool_display_name %> Call</div>
|
|
8
|
+
<pre style="white-space: pre-wrap; margin: 0;"><%%= tool_call.name %>(<%%= tool_call.arguments.map { |k, v| "#{k}: #{v.inspect}" }.join(", ") %>)</pre>
|
|
9
|
+
<div style="font-size: 0.85em; color: #666; margin-top: 5px;">
|
|
10
|
+
<%%= tool_calls.created_at&.strftime("%I:%M %p") %>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<%% end %>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%% error_message = tool.tool_error_message %>
|
|
2
|
+
<%% if error_message.present? %>
|
|
3
|
+
<%%= render "messages/error", message: tool, title: "Tool Result Error", error_message: error_message %>
|
|
4
|
+
<%% else %>
|
|
5
|
+
<div id="message_<%%= tool.id %>" class="message"
|
|
6
|
+
style="margin-bottom: 20px; padding: 10px; border-left: 3px solid #6b7280; background: #f9fafb;">
|
|
7
|
+
<div style="font-weight: bold; margin-bottom: 5px;"><%= tool_display_name %> Result</div>
|
|
8
|
+
<pre style="white-space: pre-wrap; margin: 0;"><%%= tool.content.presence || "(no output)" %></pre>
|
|
9
|
+
<div style="font-size: 0.85em; color: #666; margin-top: 5px;">
|
|
10
|
+
<%%= tool.created_at&.strftime("%I:%M %p") %>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<%% end %>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
module Generators
|
|
7
|
+
# Generator for RubyLLM tool classes and related message partials.
|
|
8
|
+
class ToolGenerator < Rails::Generators::NamedBase
|
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
|
10
|
+
|
|
11
|
+
namespace 'ruby_llm:tool'
|
|
12
|
+
|
|
13
|
+
check_class_collision suffix: 'Tool'
|
|
14
|
+
|
|
15
|
+
desc 'Creates a RubyLLM tool class and matching tool call/result view partials'
|
|
16
|
+
|
|
17
|
+
def create_tool_file
|
|
18
|
+
template 'tool.rb.tt', File.join('app/tools', class_path, "#{file_name}_tool.rb")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def create_tool_view_partials
|
|
22
|
+
empty_directory 'app/views/messages/tool_calls'
|
|
23
|
+
empty_directory 'app/views/messages/tool_results'
|
|
24
|
+
|
|
25
|
+
create_tool_call_partial
|
|
26
|
+
create_tool_result_partial
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def create_tool_call_partial
|
|
32
|
+
destination_path = File.join('app/views/messages/tool_calls', "_#{tool_partial_name}.html.erb")
|
|
33
|
+
default_partial_path = File.join(destination_root, 'app/views/messages/tool_calls/_default.html.erb')
|
|
34
|
+
|
|
35
|
+
if File.exist?(default_partial_path)
|
|
36
|
+
default_markup = tool_named_call_markup(File.read(default_partial_path))
|
|
37
|
+
indented_markup = indent_non_empty_lines(default_markup, 2)
|
|
38
|
+
create_file destination_path, <<~ERB
|
|
39
|
+
<% tool_call_error = tool_call.tool_error_message %>
|
|
40
|
+
<% if tool_call_error.present? %>
|
|
41
|
+
<%= render "messages/error", message: tool_calls, title: "Tool Call Error", error_message: tool_call_error %>
|
|
42
|
+
<% else %>
|
|
43
|
+
#{indented_markup}<% end %>
|
|
44
|
+
ERB
|
|
45
|
+
else
|
|
46
|
+
template 'tool_call.html.erb.tt', destination_path
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
strip_trailing_whitespace(destination_path)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def create_tool_result_partial
|
|
53
|
+
destination_path = File.join('app/views/messages/tool_results', "_#{tool_partial_name}.html.erb")
|
|
54
|
+
default_partial_path = File.join(destination_root, 'app/views/messages/tool_results/_default.html.erb')
|
|
55
|
+
|
|
56
|
+
if File.exist?(default_partial_path)
|
|
57
|
+
create_file destination_path, tool_named_result_markup(File.read(default_partial_path))
|
|
58
|
+
else
|
|
59
|
+
template 'tool_result.html.erb.tt', destination_path
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
strip_trailing_whitespace(destination_path)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def tool_named_call_markup(markup)
|
|
66
|
+
markup.sub('Tool Call', "#{tool_display_name} Call")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def tool_named_result_markup(markup)
|
|
70
|
+
markup.sub(/\bTool\b(?!\s*Result)/, "#{tool_display_name} Result")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def tool_display_name
|
|
74
|
+
class_name.demodulize
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def tool_partial_name
|
|
78
|
+
file_name.delete_suffix('_tool')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def indent_non_empty_lines(markup, spaces)
|
|
82
|
+
indentation = ' ' * spaces
|
|
83
|
+
markup.lines.map { |line| line.strip.empty? ? line : "#{indentation}#{line}" }.join
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def strip_trailing_whitespace(path)
|
|
87
|
+
content = File.read(path)
|
|
88
|
+
stripped_content = content.lines.map(&:rstrip).join("\n")
|
|
89
|
+
stripped_content = "#{stripped_content}\n" unless stripped_content.end_with?("\n")
|
|
90
|
+
return if content == stripped_content
|
|
91
|
+
|
|
92
|
+
File.write(path, stripped_content)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
require 'rails/generators/active_record'
|
|
5
|
+
require_relative '../generator_helpers'
|
|
6
|
+
|
|
7
|
+
module RubyLLM
|
|
8
|
+
module Generators
|
|
9
|
+
# Generator to fix tool call thought signature column type for MySQL safety.
|
|
10
|
+
class UpgradeToV114Generator < Rails::Generators::Base
|
|
11
|
+
include Rails::Generators::Migration
|
|
12
|
+
include RubyLLM::Generators::GeneratorHelpers
|
|
13
|
+
|
|
14
|
+
namespace 'ruby_llm:upgrade_to_v1_14'
|
|
15
|
+
source_root File.expand_path('templates', __dir__)
|
|
16
|
+
|
|
17
|
+
argument :model_mappings, type: :array, default: [], banner: 'tool_call:ToolCallName'
|
|
18
|
+
|
|
19
|
+
desc 'Updates tool call thought_signature column to text introduced in v1.14.0'
|
|
20
|
+
|
|
21
|
+
def self.next_migration_number(dirname)
|
|
22
|
+
::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def create_migration_file
|
|
26
|
+
parse_model_mappings
|
|
27
|
+
|
|
28
|
+
migration_template 'add_v1_14_tool_call_columns.rb.tt',
|
|
29
|
+
'db/migrate/add_ruby_llm_v1_14_columns.rb',
|
|
30
|
+
migration_version: migration_version,
|
|
31
|
+
tool_call_table_name: tool_call_table_name
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def show_next_steps
|
|
35
|
+
say_status :success, 'Upgrade prepared!', :green
|
|
36
|
+
say <<~INSTRUCTIONS
|
|
37
|
+
|
|
38
|
+
Next steps:
|
|
39
|
+
1. Review the generated migration
|
|
40
|
+
2. Run: bin/rails db:migrate
|
|
41
|
+
3. Restart your application server
|
|
42
|
+
|
|
43
|
+
📚 See the v1.14.0 release notes for details on thought signature persistence.
|
|
44
|
+
|
|
45
|
+
INSTRUCTIONS
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -25,7 +25,7 @@ module RubyLLM
|
|
|
25
25
|
argument :model_mappings, type: :array, default: [], banner: 'chat:ChatName message:MessageName ...'
|
|
26
26
|
|
|
27
27
|
desc 'Upgrades existing RubyLLM apps to v1.7 with new Rails-like API\n' \
|
|
28
|
-
'Usage: rails g ruby_llm:upgrade_to_v1_7 [chat:ChatName] [message:MessageName] ...'
|
|
28
|
+
'Usage: bin/rails g ruby_llm:upgrade_to_v1_7 [chat:ChatName] [message:MessageName] ...'
|
|
29
29
|
|
|
30
30
|
def self.next_migration_number(dirname)
|
|
31
31
|
::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
@@ -40,8 +40,6 @@ module RubyLLM
|
|
|
40
40
|
"db/migrate/create_#{table_name_for(model_model_name)}.rb",
|
|
41
41
|
migration_version: migration_version,
|
|
42
42
|
model_model_name: model_model_name
|
|
43
|
-
|
|
44
|
-
sleep 1 # Ensure different timestamp
|
|
45
43
|
end
|
|
46
44
|
|
|
47
45
|
migration_template 'migration.rb.tt',
|
|
@@ -93,7 +91,7 @@ module RubyLLM
|
|
|
93
91
|
|
|
94
92
|
Next steps:
|
|
95
93
|
1. Review the generated migrations
|
|
96
|
-
2. Run: rails db:migrate
|
|
94
|
+
2. Run: bin/rails db:migrate
|
|
97
95
|
3. Update your code to use the new API: #{chat_model_name}.create! now has the same signature as RubyLLM.chat
|
|
98
96
|
|
|
99
97
|
⚠️ If you get "undefined method 'acts_as_model'" during migration:
|
|
@@ -37,7 +37,7 @@ module RubyLLM
|
|
|
37
37
|
|
|
38
38
|
Next steps:
|
|
39
39
|
1. Review the generated migration
|
|
40
|
-
2. Run: rails db:migrate
|
|
40
|
+
2. Run: bin/rails db:migrate
|
|
41
41
|
3. Restart your application server
|
|
42
42
|
|
|
43
43
|
📚 See the v1.9.0 release notes for details on cached token tracking and raw content support.
|
|
@@ -12,15 +12,21 @@ module RubyLLM
|
|
|
12
12
|
# Monkey-patch Models to use database when ActsAs is active
|
|
13
13
|
RubyLLM::Models.class_eval do
|
|
14
14
|
def self.load_models
|
|
15
|
-
read_from_database
|
|
15
|
+
database_models = read_from_database
|
|
16
|
+
return database_models if database_models.any?
|
|
17
|
+
|
|
18
|
+
RubyLLM.logger.debug { 'Model registry is empty in database, falling back to JSON registry' }
|
|
19
|
+
read_from_json
|
|
16
20
|
rescue StandardError => e
|
|
17
|
-
RubyLLM.logger.debug "Failed to load models from database: #{e.message}, falling back to JSON"
|
|
21
|
+
RubyLLM.logger.debug { "Failed to load models from database: #{e.message}, falling back to JSON" }
|
|
18
22
|
read_from_json
|
|
19
23
|
end
|
|
20
24
|
|
|
21
25
|
def self.read_from_database
|
|
22
26
|
model_class = RubyLLM.config.model_registry_class
|
|
23
27
|
model_class = model_class.constantize if model_class.is_a?(String)
|
|
28
|
+
return [] unless model_class.table_exists?
|
|
29
|
+
|
|
24
30
|
model_class.all.map(&:to_llm)
|
|
25
31
|
end
|
|
26
32
|
|
|
@@ -53,8 +59,6 @@ module RubyLLM
|
|
|
53
59
|
foreign_key: model_foreign_key,
|
|
54
60
|
optional: true
|
|
55
61
|
|
|
56
|
-
delegate :add_message, to: :to_llm
|
|
57
|
-
|
|
58
62
|
define_method :messages_association do
|
|
59
63
|
send(messages_association_name)
|
|
60
64
|
end
|
|
@@ -144,6 +148,8 @@ module RubyLLM
|
|
|
144
148
|
|
|
145
149
|
def acts_as_tool_call(message: :message, message_class: nil, message_foreign_key: nil, # rubocop:disable Metrics/ParameterLists
|
|
146
150
|
result: :result, result_class: nil, result_foreign_key: nil)
|
|
151
|
+
include RubyLLM::ActiveRecord::ToolCallMethods
|
|
152
|
+
|
|
147
153
|
class_attribute :message_association_name, :result_association_name, :message_class, :result_class
|
|
148
154
|
|
|
149
155
|
self.message_association_name = message
|