ruby_llm 1.7.0 → 1.8.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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +30 -18
  4. data/lib/generators/ruby_llm/generator_helpers.rb +129 -0
  5. data/lib/generators/ruby_llm/install/install_generator.rb +110 -0
  6. data/lib/generators/ruby_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +9 -0
  7. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +2 -3
  8. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +3 -6
  9. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +3 -6
  10. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +4 -5
  11. data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +21 -13
  12. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +121 -0
  13. data/lib/ruby_llm/attachment.rb +5 -0
  14. data/lib/ruby_llm/configuration.rb +2 -0
  15. data/lib/ruby_llm/mime_type.rb +4 -0
  16. data/lib/ruby_llm/model/info.rb +4 -0
  17. data/lib/ruby_llm/models.json +780 -511
  18. data/lib/ruby_llm/models.rb +7 -3
  19. data/lib/ruby_llm/moderation.rb +56 -0
  20. data/lib/ruby_llm/provider.rb +6 -0
  21. data/lib/ruby_llm/providers/gemini/capabilities.rb +5 -0
  22. data/lib/ruby_llm/providers/openai/moderation.rb +34 -0
  23. data/lib/ruby_llm/providers/openai.rb +1 -0
  24. data/lib/ruby_llm/railtie.rb +1 -1
  25. data/lib/ruby_llm/version.rb +1 -1
  26. data/lib/ruby_llm.rb +4 -0
  27. metadata +7 -3
  28. data/lib/generators/ruby_llm/install_generator.rb +0 -217
  29. data/lib/generators/ruby_llm/upgrade_to_v1_7_generator.rb +0 -160
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a7c8f498c7fee7618711944119c65a33f8a2236a87cbd55d51b5e5a5d25000f
4
- data.tar.gz: 2bb4675e6bcad95099330783697c87c7d6eade98c159a6b00247c7e179f7bd9a
3
+ metadata.gz: def0767fe1f3e052f42bfddc4d3b69733d51cf8d39d057a08145307ef5f362a0
4
+ data.tar.gz: fba9605e4458efb173dff1d131f0124c8252c4d71ca7a23527eb0d0a2543c75a
5
5
  SHA512:
6
- metadata.gz: 06e41bd54bcf1c7c094750b67c494decf50e9db53a250d4732455742d688aa08c72669ba64f4cc771fa3a1ad5197f646ce58308be1f3fd7db84b21282180135b
7
- data.tar.gz: '08c739bbdb44380720e817704d9f980b49b6a7bafd80d890a3207531afe746d9039e2e3e0d6adf82e496b8ce16d68fa0ee8359ccf63cecf08de778e07fe6b632'
6
+ metadata.gz: 622b3b02ba5fb0cc0c49d7bc2d515e92abd0fa5714db70c8b0af64868cee9229b05ddefa6a990ab5f3788e7e07589628bd9f347f921346449a2154575e1e1ac1
7
+ data.tar.gz: 890020ebc2817a3a388278510eaa2b31699eb660cce29ed1c19ebd5a67b3775f8ff83d9be58b31bac825719c6683d7ffcc321251a6f89dc58bafd135ea51ec25
data/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  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) — *Claude Code for your documents*
11
11
 
12
- [![Gem Version](https://badge.fury.io/rb/ruby_llm.svg?a=7)](https://badge.fury.io/rb/ruby_llm)
12
+ [![Gem Version](https://badge.fury.io/rb/ruby_llm.svg?a=9)](https://badge.fury.io/rb/ruby_llm)
13
13
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
14
14
  [![Gem Downloads](https://img.shields.io/gem/dt/ruby_llm)](https://rubygems.org/gems/ruby_llm)
15
15
  [![codecov](https://codecov.io/gh/crmne/ruby_llm/branch/main/graph/badge.svg?a=2)](https://codecov.io/gh/crmne/ruby_llm)
@@ -41,6 +41,7 @@ chat.ask "What's the best way to learn Ruby?"
41
41
  ```ruby
42
42
  # Analyze any file type
43
43
  chat.ask "What's in this image?", with: "ruby_conf.jpg"
44
+ chat.ask "What's happening in this video?", with: "video.mp4"
44
45
  chat.ask "Describe this meeting", with: "meeting.wav"
45
46
  chat.ask "Summarize this document", with: "contract.pdf"
46
47
  chat.ask "Explain this code", with: "app.rb"
@@ -100,7 +101,7 @@ response = chat.with_schema(ProductSchema).ask "Analyze this product", with: "pr
100
101
  ## Features
101
102
 
102
103
  * **Chat:** Conversational AI with `RubyLLM.chat`
103
- * **Vision:** Analyze images and screenshots
104
+ * **Vision:** Analyze images and videos
104
105
  * **Audio:** Transcribe and understand speech
105
106
  * **Documents:** Extract from PDFs, CSVs, JSON, any file type
106
107
  * **Image generation:** Create images with `RubyLLM.paint`
@@ -38,35 +38,39 @@ module RubyLLM
38
38
  @model_names ||= parse_model_mappings
39
39
  @model_names[type]
40
40
  end
41
+
42
+ define_method("#{type}_table_name") do
43
+ table_name_for(send("#{type}_model_name"))
44
+ end
41
45
  end
42
46
 
43
47
  def create_views
44
48
  # Chat views
45
- template 'views/chats/index.html.erb', "app/views/#{chat_model_name.tableize}/index.html.erb"
46
- template 'views/chats/new.html.erb', "app/views/#{chat_model_name.tableize}/new.html.erb"
47
- template 'views/chats/show.html.erb', "app/views/#{chat_model_name.tableize}/show.html.erb"
49
+ template 'views/chats/index.html.erb', "app/views/#{chat_table_name}/index.html.erb"
50
+ template 'views/chats/new.html.erb', "app/views/#{chat_table_name}/new.html.erb"
51
+ template 'views/chats/show.html.erb', "app/views/#{chat_table_name}/show.html.erb"
48
52
  template 'views/chats/_chat.html.erb',
49
- "app/views/#{chat_model_name.tableize}/_#{chat_model_name.underscore}.html.erb"
50
- template 'views/chats/_form.html.erb', "app/views/#{chat_model_name.tableize}/_form.html.erb"
53
+ "app/views/#{chat_table_name}/_#{chat_model_name.underscore}.html.erb"
54
+ template 'views/chats/_form.html.erb', "app/views/#{chat_table_name}/_form.html.erb"
51
55
 
52
56
  # Message views
53
57
  template 'views/messages/_message.html.erb',
54
- "app/views/#{message_model_name.tableize}/_#{message_model_name.underscore}.html.erb"
55
- template 'views/messages/_form.html.erb', "app/views/#{message_model_name.tableize}/_form.html.erb"
58
+ "app/views/#{message_table_name}/_#{message_model_name.underscore}.html.erb"
59
+ template 'views/messages/_form.html.erb', "app/views/#{message_table_name}/_form.html.erb"
56
60
  template 'views/messages/create.turbo_stream.erb',
57
- "app/views/#{message_model_name.tableize}/create.turbo_stream.erb"
61
+ "app/views/#{message_table_name}/create.turbo_stream.erb"
58
62
 
59
63
  # Model views
60
- template 'views/models/index.html.erb', "app/views/#{model_model_name.tableize}/index.html.erb"
61
- template 'views/models/show.html.erb', "app/views/#{model_model_name.tableize}/show.html.erb"
64
+ template 'views/models/index.html.erb', "app/views/#{model_table_name}/index.html.erb"
65
+ template 'views/models/show.html.erb', "app/views/#{model_table_name}/show.html.erb"
62
66
  template 'views/models/_model.html.erb',
63
- "app/views/#{model_model_name.tableize}/_#{model_model_name.underscore}.html.erb"
67
+ "app/views/#{model_table_name}/_#{model_model_name.underscore}.html.erb"
64
68
  end
65
69
 
66
70
  def create_controllers
67
- template 'controllers/chats_controller.rb', "app/controllers/#{chat_model_name.tableize}_controller.rb"
68
- template 'controllers/messages_controller.rb', "app/controllers/#{message_model_name.tableize}_controller.rb"
69
- template 'controllers/models_controller.rb', "app/controllers/#{model_model_name.tableize}_controller.rb"
71
+ template 'controllers/chats_controller.rb', "app/controllers/#{chat_table_name}_controller.rb"
72
+ template 'controllers/messages_controller.rb', "app/controllers/#{message_table_name}_controller.rb"
73
+ template 'controllers/models_controller.rb', "app/controllers/#{model_table_name}_controller.rb"
70
74
  end
71
75
 
72
76
  def create_jobs
@@ -75,7 +79,7 @@ module RubyLLM
75
79
 
76
80
  def add_routes
77
81
  model_routes = <<~ROUTES.strip
78
- resources :#{model_model_name.tableize}, only: [:index, :show] do
82
+ resources :#{model_table_name}, only: [:index, :show] do
79
83
  collection do
80
84
  post :refresh
81
85
  end
@@ -83,8 +87,8 @@ module RubyLLM
83
87
  ROUTES
84
88
  route model_routes
85
89
  chat_routes = <<~ROUTES.strip
86
- resources :#{chat_model_name.tableize} do
87
- resources :#{message_model_name.tableize}, only: [:create]
90
+ resources :#{chat_table_name} do
91
+ resources :#{message_table_name}, only: [:create]
88
92
  end
89
93
  ROUTES
90
94
  route chat_routes
@@ -107,9 +111,17 @@ module RubyLLM
107
111
  return unless behavior == :invoke
108
112
 
109
113
  say "\n ✅ Chat UI installed!", :green
110
- say "\n Start your server and visit http://localhost:3000/#{chat_model_name.tableize}", :cyan
114
+ say "\n Start your server and visit http://localhost:3000/#{chat_table_name}", :cyan
111
115
  say "\n"
112
116
  end
117
+
118
+ private
119
+
120
+ def table_name_for(model_name)
121
+ # Convert namespaced model names to proper table names
122
+ # e.g., "Assistant::Chat" -> "assistant_chats" (not "assistant/chats")
123
+ model_name.underscore.pluralize.tr('/', '_')
124
+ end
113
125
  end
114
126
  end
115
127
  end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ # Shared helpers for RubyLLM generators
5
+ module GeneratorHelpers
6
+ def parse_model_mappings
7
+ @model_names = {
8
+ chat: 'Chat',
9
+ message: 'Message',
10
+ tool_call: 'ToolCall',
11
+ model: 'Model'
12
+ }
13
+
14
+ model_mappings.each do |mapping|
15
+ if mapping.include?(':')
16
+ key, value = mapping.split(':', 2)
17
+ @model_names[key.to_sym] = value.classify
18
+ end
19
+ end
20
+
21
+ @model_names
22
+ end
23
+
24
+ %i[chat message tool_call model].each do |type|
25
+ define_method("#{type}_model_name") do
26
+ @model_names ||= parse_model_mappings
27
+ @model_names[type]
28
+ end
29
+
30
+ define_method("#{type}_table_name") do
31
+ table_name_for(send("#{type}_model_name"))
32
+ end
33
+ end
34
+
35
+ def acts_as_chat_declaration
36
+ params = []
37
+
38
+ add_association_params(params, :messages, message_table_name, message_model_name, plural: true)
39
+ add_association_params(params, :model, model_table_name, model_model_name)
40
+
41
+ "acts_as_chat#{" #{params.join(', ')}" if params.any?}"
42
+ end
43
+
44
+ def acts_as_message_declaration
45
+ params = []
46
+
47
+ add_association_params(params, :chat, chat_table_name, chat_model_name)
48
+ add_association_params(params, :tool_calls, tool_call_table_name, tool_call_model_name, plural: true)
49
+ add_association_params(params, :model, model_table_name, model_model_name)
50
+
51
+ "acts_as_message#{" #{params.join(', ')}" if params.any?}"
52
+ end
53
+
54
+ def acts_as_model_declaration
55
+ params = []
56
+
57
+ add_association_params(params, :chats, chat_table_name, chat_model_name, plural: true)
58
+
59
+ "acts_as_model#{" #{params.join(', ')}" if params.any?}"
60
+ end
61
+
62
+ def acts_as_tool_call_declaration
63
+ params = []
64
+
65
+ add_association_params(params, :message, message_table_name, message_model_name)
66
+
67
+ "acts_as_tool_call#{" #{params.join(', ')}" if params.any?}"
68
+ end
69
+
70
+ def create_namespace_modules
71
+ namespaces = []
72
+
73
+ [chat_model_name, message_model_name, tool_call_model_name, model_model_name].each do |model_name|
74
+ if model_name.include?('::')
75
+ namespace = model_name.split('::').first
76
+ namespaces << namespace unless namespaces.include?(namespace)
77
+ end
78
+ end
79
+
80
+ namespaces.each do |namespace|
81
+ module_path = "app/models/#{namespace.underscore}.rb"
82
+ next if File.exist?(Rails.root.join(module_path))
83
+
84
+ create_file module_path do
85
+ <<~RUBY
86
+ module #{namespace}
87
+ def self.table_name_prefix
88
+ "#{namespace.underscore}_"
89
+ end
90
+ end
91
+ RUBY
92
+ end
93
+ end
94
+ end
95
+
96
+ def migration_version
97
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
98
+ end
99
+
100
+ def postgresql?
101
+ ::ActiveRecord::Base.connection.adapter_name.downcase.include?('postgresql')
102
+ rescue StandardError
103
+ false
104
+ end
105
+
106
+ def table_exists?(table_name)
107
+ ::ActiveRecord::Base.connection.table_exists?(table_name)
108
+ rescue StandardError
109
+ false
110
+ end
111
+
112
+ private
113
+
114
+ def add_association_params(params, default_assoc, table_name, model_name, plural: false)
115
+ assoc = plural ? table_name.to_sym : table_name.singularize.to_sym
116
+
117
+ return if assoc == default_assoc
118
+
119
+ params << "#{default_assoc}: :#{assoc}"
120
+ params << "#{default_assoc.to_s.singularize}_class: '#{model_name}'" if model_name != assoc.to_s.classify
121
+ end
122
+
123
+ def table_name_for(model_name)
124
+ # Convert namespaced model names to proper table names
125
+ # e.g., "Assistant::Chat" -> "assistant_chats" (not "assistant/chats")
126
+ model_name.underscore.pluralize.tr('/', '_')
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,110 @@
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
+ # Generator for RubyLLM Rails models and migrations
9
+ class InstallGenerator < Rails::Generators::Base
10
+ include Rails::Generators::Migration
11
+ include RubyLLM::GeneratorHelpers
12
+
13
+ namespace 'ruby_llm:install'
14
+
15
+ source_root File.expand_path('templates', __dir__)
16
+
17
+ argument :model_mappings, type: :array, default: [], banner: 'chat:ChatName message:MessageName ...'
18
+
19
+ class_option :skip_active_storage, type: :boolean, default: false,
20
+ desc: 'Skip ActiveStorage installation and attachment setup'
21
+
22
+ desc 'Creates models and migrations for RubyLLM Rails integration\n' \
23
+ 'Usage: rails g ruby_llm:install [chat:ChatName] [message:MessageName] ...'
24
+
25
+ def self.next_migration_number(dirname)
26
+ ::ActiveRecord::Generators::Base.next_migration_number(dirname)
27
+ end
28
+
29
+ def create_migration_files
30
+ # Create migrations with timestamps to ensure proper order
31
+ # First create chats table
32
+ migration_template 'create_chats_migration.rb.tt',
33
+ "db/migrate/create_#{chat_table_name}.rb"
34
+
35
+ # Then create messages table
36
+ sleep 1 # Ensure different timestamp
37
+ migration_template 'create_messages_migration.rb.tt',
38
+ "db/migrate/create_#{message_table_name}.rb"
39
+
40
+ # Then create tool_calls table
41
+ sleep 1 # Ensure different timestamp
42
+ migration_template 'create_tool_calls_migration.rb.tt',
43
+ "db/migrate/create_#{tool_call_table_name}.rb"
44
+
45
+ # Create models table
46
+ sleep 1 # Ensure different timestamp
47
+ migration_template 'create_models_migration.rb.tt',
48
+ "db/migrate/create_#{model_table_name}.rb"
49
+
50
+ # Add references to chats, tool_calls and messages.
51
+ sleep 1 # Ensure different timestamp
52
+ migration_template 'add_references_to_chats_tool_calls_and_messages_migration.rb.tt',
53
+ 'db/migrate/add_references_to_' \
54
+ "#{chat_table_name}_#{tool_call_table_name}_and_#{message_table_name}.rb"
55
+ end
56
+
57
+ def create_model_files
58
+ create_namespace_modules
59
+
60
+ template 'chat_model.rb.tt', "app/models/#{chat_model_name.underscore}.rb"
61
+ template 'message_model.rb.tt', "app/models/#{message_model_name.underscore}.rb"
62
+ template 'tool_call_model.rb.tt', "app/models/#{tool_call_model_name.underscore}.rb"
63
+
64
+ template 'model_model.rb.tt', "app/models/#{model_model_name.underscore}.rb"
65
+ end
66
+
67
+ def create_initializer
68
+ template 'initializer.rb.tt', 'config/initializers/ruby_llm.rb'
69
+ end
70
+
71
+ def install_active_storage
72
+ return if options[:skip_active_storage]
73
+
74
+ say ' Installing ActiveStorage for file attachments...', :cyan
75
+ rails_command 'active_storage:install'
76
+ end
77
+
78
+ def show_install_info
79
+ say "\n ✅ RubyLLM installed!", :green
80
+
81
+ say ' ✅ ActiveStorage configured for file attachments support', :green unless options[:skip_active_storage]
82
+
83
+ say "\n Next steps:", :yellow
84
+ say ' 1. Run: rails db:migrate'
85
+ say ' 2. Set your API keys in config/initializers/ruby_llm.rb'
86
+
87
+ say " 3. Start chatting: #{chat_model_name}.create!(model: 'gpt-4.1-nano').ask('Hello!')"
88
+
89
+ say "\n 🚀 Model registry is database-backed!", :cyan
90
+ say ' Models automatically load from the database'
91
+ say ' Pass model names as strings - RubyLLM handles the rest!'
92
+ say " Specify provider when needed: Chat.create!(model: 'gemini-2.5-flash', provider: 'vertexai')"
93
+
94
+ if options[:skip_active_storage]
95
+ say "\n 📎 Note: ActiveStorage was skipped", :yellow
96
+ say ' File attachments won\'t work without ActiveStorage.'
97
+ say ' To enable later:'
98
+ say ' 1. Run: rails active_storage:install && rails db:migrate'
99
+ say " 2. Add to your #{message_model_name} model: has_many_attached :attachments"
100
+ end
101
+
102
+ say "\n 📚 Documentation: https://rubyllm.com", :cyan
103
+
104
+ say "\n ❤️ Love RubyLLM?", :magenta
105
+ say ' • ⭐ Star on GitHub: https://github.com/crmne/ruby_llm'
106
+ say ' • 🐦 Follow for updates: https://x.com/paolino'
107
+ say "\n"
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,9 @@
1
+ class AddReferencesTo<%= "#{chat_model_name.gsub('::', '').pluralize}#{tool_call_model_name.gsub('::', '').pluralize}And#{message_model_name.gsub('::', '').pluralize}" %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ add_reference :<%= chat_table_name %>, :<%= model_table_name.singularize %>, foreign_key: true
4
+ add_reference :<%= tool_call_table_name %>, :<%= message_table_name.singularize %>, null: false, foreign_key: true
5
+ add_reference :<%= message_table_name %>, :<%= chat_table_name.singularize %>, null: false, foreign_key: true
6
+ add_reference :<%= message_table_name %>, :<%= model_table_name.singularize %>, foreign_key: true
7
+ add_reference :<%= message_table_name %>, :<%= tool_call_table_name.singularize %>, foreign_key: true
8
+ end
9
+ end
@@ -1,7 +1,6 @@
1
- class Create<%= chat_model_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
1
+ class Create<%= chat_model_name.gsub('::', '').pluralize %> < ActiveRecord::Migration<%= migration_version %>
2
2
  def change
3
- create_table :<%= chat_model_name.tableize %> do |t|
4
- t.references :<%= model_model_name.tableize.singularize %>, foreign_key: true
3
+ create_table :<%= chat_table_name %> do |t|
5
4
  t.timestamps
6
5
  end
7
6
  end
@@ -1,16 +1,13 @@
1
- class Create<%= message_model_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
1
+ class Create<%= message_model_name.gsub('::', '').pluralize %> < ActiveRecord::Migration<%= migration_version %>
2
2
  def change
3
- create_table :<%= message_model_name.tableize %> do |t|
4
- t.references :<%= chat_model_name.tableize.singularize %>, null: false, foreign_key: true
3
+ create_table :<%= message_table_name %> do |t|
5
4
  t.string :role, null: false
6
5
  t.text :content
7
- t.references :<%= model_model_name.tableize.singularize %>, foreign_key: true
8
6
  t.integer :input_tokens
9
7
  t.integer :output_tokens
10
- t.references :<%= tool_call_model_name.tableize.singularize %>, foreign_key: true
11
8
  t.timestamps
12
9
  end
13
10
 
14
- add_index :<%= message_model_name.tableize %>, :role
11
+ add_index :<%= message_table_name %>, :role
15
12
  end
16
13
  end
@@ -1,6 +1,6 @@
1
- class Create<%= model_model_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
1
+ class Create<%= model_model_name.gsub('::', '').pluralize %> < ActiveRecord::Migration<%= migration_version %>
2
2
  def change
3
- create_table :<%= model_model_name.tableize %> do |t|
3
+ create_table :<%= model_table_name %> do |t|
4
4
  t.string :model_id, null: false
5
5
  t.string :name, null: false
6
6
  t.string :provider, null: false
@@ -34,10 +34,7 @@ class Create<%= model_model_name.pluralize %> < ActiveRecord::Migration<%= migra
34
34
  # Load models from JSON
35
35
  say_with_time "Loading models from models.json" do
36
36
  RubyLLM.models.load_from_json!
37
- model_class = '<%= model_model_name %>'.constantize
38
- model_class.save_to_database
39
-
40
- "Loaded #{model_class.count} models"
37
+ <%= model_model_name %>.save_to_database
41
38
  end
42
39
  end
43
40
  end
@@ -1,15 +1,14 @@
1
1
  <%#- # Migration for creating tool_calls table with database-specific JSON handling -%>
2
- class Create<%= tool_call_model_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
2
+ class Create<%= tool_call_model_name.gsub('::', '').pluralize %> < ActiveRecord::Migration<%= migration_version %>
3
3
  def change
4
- create_table :<%= tool_call_model_name.tableize %> do |t|
5
- t.references :<%= message_model_name.tableize.singularize %>, null: false, foreign_key: true
4
+ create_table :<%= tool_call_table_name %> do |t|
6
5
  t.string :tool_call_id, null: false
7
6
  t.string :name, null: false
8
7
  t.<%= postgresql? ? 'jsonb' : 'json' %> :arguments, default: {}
9
8
  t.timestamps
10
9
  end
11
10
 
12
- add_index :<%= tool_call_model_name.tableize %>, :tool_call_id, unique: true
13
- add_index :<%= tool_call_model_name.tableize %>, :name
11
+ add_index :<%= tool_call_table_name %>, :tool_call_id, unique: true
12
+ add_index :<%= tool_call_table_name %>, :name
14
13
  end
15
14
  end
@@ -3,36 +3,44 @@ class MigrateToRubyLLMModelReferences < ActiveRecord::Migration<%= migration_ver
3
3
  model_class = <%= model_model_name %>
4
4
  chat_class = <%= chat_model_name %>
5
5
  message_class = <%= message_model_name %>
6
+ <% if @model_table_already_existed %>
7
+ # Load models from models.json if Model table already existed
8
+ say_with_time "Loading models from models.json" do
9
+ RubyLLM.models.load_from_json!
10
+ model_class.save_to_database
11
+ "Loaded #{model_class.count} models"
12
+ end
13
+ <% end %>
6
14
 
7
15
  # Then check for any models in existing data that aren't in models.json
8
16
  say_with_time "Checking for additional models in existing data" do
9
- collect_and_create_models(chat_class, :<%= chat_model_name.tableize %>, model_class)
10
- collect_and_create_models(message_class, :<%= message_model_name.tableize %>, model_class)
17
+ collect_and_create_models(chat_class, :<%= chat_table_name %>, model_class)
18
+ collect_and_create_models(message_class, :<%= message_table_name %>, model_class)
11
19
  model_class.count
12
20
  end
13
21
 
14
22
  # Migrate foreign keys
15
- migrate_foreign_key(:<%= chat_model_name.tableize %>, chat_class, model_class, :<%= model_model_name.underscore %>)
16
- migrate_foreign_key(:<%= message_model_name.tableize %>, message_class, model_class, :<%= model_model_name.underscore %>)
23
+ migrate_foreign_key(:<%= chat_table_name %>, chat_class, model_class, :<%= model_table_name.singularize %>)
24
+ migrate_foreign_key(:<%= message_table_name %>, message_class, model_class, :<%= model_table_name.singularize %>)
17
25
  end
18
26
 
19
27
  def down
20
28
  # Remove foreign key references
21
- if column_exists?(:<%= message_model_name.tableize %>, :<%= model_model_name.underscore %>_id)
22
- remove_reference :<%= message_model_name.tableize %>, :<%= model_model_name.underscore %>, foreign_key: true
29
+ if column_exists?(:<%= message_table_name %>, :<%= model_table_name.singularize %>_id)
30
+ remove_reference :<%= message_table_name %>, :<%= model_table_name.singularize %>, foreign_key: true
23
31
  end
24
32
 
25
- if column_exists?(:<%= chat_model_name.tableize %>, :<%= model_model_name.underscore %>_id)
26
- remove_reference :<%= chat_model_name.tableize %>, :<%= model_model_name.underscore %>, foreign_key: true
33
+ if column_exists?(:<%= chat_table_name %>, :<%= model_table_name.singularize %>_id)
34
+ remove_reference :<%= chat_table_name %>, :<%= model_table_name.singularize %>, foreign_key: true
27
35
  end
28
36
 
29
37
  # Restore original model_id string columns
30
- if column_exists?(:<%= message_model_name.tableize %>, :model_id_string)
31
- rename_column :<%= message_model_name.tableize %>, :model_id_string, :model_id
38
+ if column_exists?(:<%= message_table_name %>, :model_id_string)
39
+ rename_column :<%= message_table_name %>, :model_id_string, :model_id
32
40
  end
33
41
 
34
- if column_exists?(:<%= chat_model_name.tableize %>, :model_id_string)
35
- rename_column :<%= chat_model_name.tableize %>, :model_id_string, :model_id
42
+ if column_exists?(:<%= chat_table_name %>, :model_id_string)
43
+ rename_column :<%= chat_table_name %>, :model_id_string, :model_id
36
44
  end
37
45
  end
38
46
 
@@ -134,4 +142,4 @@ class MigrateToRubyLLMModelReferences < ActiveRecord::Migration<%= migration_ver
134
142
  model_class.find_by(model_id: model_id)
135
143
  end
136
144
  end
137
- end
145
+ end
@@ -0,0 +1,121 @@
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
+ class UpgradeToV17Generator < Rails::Generators::Base # rubocop:disable Style/Documentation
9
+ include Rails::Generators::Migration
10
+ include RubyLLM::GeneratorHelpers
11
+
12
+ namespace 'ruby_llm:upgrade_to_v1_7'
13
+ source_root File.expand_path('templates', __dir__)
14
+
15
+ # Override source_paths to include install templates
16
+ def self.source_paths
17
+ [
18
+ File.expand_path('templates', __dir__),
19
+ File.expand_path('../install/templates', __dir__)
20
+ ]
21
+ end
22
+
23
+ argument :model_mappings, type: :array, default: [], banner: 'chat:ChatName message:MessageName ...'
24
+
25
+ desc 'Upgrades existing RubyLLM apps to v1.7 with new Rails-like API\n' \
26
+ 'Usage: rails g ruby_llm:upgrade_to_v1_7 [chat:ChatName] [message:MessageName] ...'
27
+
28
+ def self.next_migration_number(dirname)
29
+ ::ActiveRecord::Generators::Base.next_migration_number(dirname)
30
+ end
31
+
32
+ def create_migration_file
33
+ @model_table_already_existed = table_exists?(table_name_for(model_model_name))
34
+
35
+ # First check if models table exists, if not create it
36
+ unless @model_table_already_existed
37
+ migration_template 'create_models_migration.rb.tt',
38
+ "db/migrate/create_#{table_name_for(model_model_name)}.rb",
39
+ migration_version: migration_version,
40
+ model_model_name: model_model_name
41
+
42
+ sleep 1 # Ensure different timestamp
43
+ end
44
+
45
+ migration_template 'migration.rb.tt',
46
+ 'db/migrate/migrate_to_ruby_llm_model_references.rb',
47
+ migration_version: migration_version,
48
+ chat_model_name: chat_model_name,
49
+ message_model_name: message_model_name,
50
+ tool_call_model_name: tool_call_model_name,
51
+ model_model_name: model_model_name,
52
+ model_table_already_existed: @model_table_already_existed
53
+ end
54
+
55
+ def create_model_file
56
+ create_namespace_modules
57
+
58
+ template 'model_model.rb.tt', "app/models/#{model_model_name.underscore}.rb"
59
+ end
60
+
61
+ def update_existing_models
62
+ update_model_acts_as(chat_model_name, 'acts_as_chat', acts_as_chat_declaration)
63
+ update_model_acts_as(message_model_name, 'acts_as_message', acts_as_message_declaration)
64
+ update_model_acts_as(tool_call_model_name, 'acts_as_tool_call', acts_as_tool_call_declaration)
65
+ end
66
+
67
+ def update_initializer
68
+ initializer_path = 'config/initializers/ruby_llm.rb'
69
+
70
+ unless File.exist?(initializer_path)
71
+ say_status :warning, 'No initializer found. Creating one...', :yellow
72
+ template 'initializer.rb.tt', initializer_path
73
+ return
74
+ end
75
+
76
+ initializer_content = File.read(initializer_path)
77
+
78
+ return if initializer_content.include?('config.use_new_acts_as')
79
+
80
+ inject_into_file initializer_path, before: /^end/ do
81
+ lines = ["\n # Enable the new Rails-like API", ' config.use_new_acts_as = true']
82
+ lines << " config.model_registry_class = \"#{model_model_name}\"" if model_model_name != 'Model'
83
+ lines << "\n"
84
+ lines.join("\n")
85
+ end
86
+ end
87
+
88
+ def show_next_steps
89
+ say_status :success, 'Upgrade prepared!', :green
90
+ say <<~INSTRUCTIONS
91
+
92
+ Next steps:
93
+ 1. Review the generated migrations
94
+ 2. Run: rails db:migrate
95
+ 3. Update your code to use the new API: #{chat_model_name}.create! now has the same signature as RubyLLM.chat
96
+
97
+ ⚠️ If you get "undefined method 'acts_as_model'" during migration:
98
+ Add this to config/application.rb BEFORE your Application class:
99
+
100
+ RubyLLM.configure do |config|
101
+ config.use_new_acts_as = true
102
+ end
103
+
104
+ 📚 See the full migration guide: https://rubyllm.com/upgrading-to-1-7/
105
+
106
+ INSTRUCTIONS
107
+ end
108
+
109
+ private
110
+
111
+ def update_model_acts_as(model_name, old_acts_as, new_acts_as)
112
+ model_path = "app/models/#{model_name.underscore}.rb"
113
+ return unless File.exist?(Rails.root.join(model_path))
114
+
115
+ content = File.read(Rails.root.join(model_path))
116
+ return unless content.match?(/^\s*#{old_acts_as}/)
117
+
118
+ gsub_file model_path, /^\s*#{old_acts_as}.*$/, " #{new_acts_as}"
119
+ end
120
+ end
121
+ end
@@ -76,6 +76,7 @@ module RubyLLM
76
76
 
77
77
  def type
78
78
  return :image if image?
79
+ return :video if video?
79
80
  return :audio if audio?
80
81
  return :pdf if pdf?
81
82
  return :text if text?
@@ -87,6 +88,10 @@ module RubyLLM
87
88
  RubyLLM::MimeType.image? mime_type
88
89
  end
89
90
 
91
+ def video?
92
+ RubyLLM::MimeType.video? mime_type
93
+ end
94
+
90
95
  def audio?
91
96
  RubyLLM::MimeType.audio? mime_type
92
97
  end