dify_llm 1.6.4 → 1.7.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +127 -0
  4. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +39 -0
  5. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +24 -0
  6. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +14 -0
  7. data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +12 -0
  8. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +16 -0
  9. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +29 -0
  10. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +16 -0
  11. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +11 -0
  12. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +23 -0
  13. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +21 -0
  14. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +10 -0
  15. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +9 -0
  16. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +16 -0
  17. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +30 -0
  18. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +18 -0
  19. data/lib/generators/ruby_llm/generator_helpers.rb +129 -0
  20. data/lib/generators/ruby_llm/install/install_generator.rb +104 -0
  21. data/lib/generators/ruby_llm/install/templates/chat_model.rb.tt +1 -1
  22. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +3 -3
  23. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +6 -6
  24. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +3 -6
  25. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +5 -5
  26. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +6 -3
  27. data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +1 -1
  28. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +1 -1
  29. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +1 -1
  30. data/lib/generators/ruby_llm/{migrate_model_fields → upgrade_to_v1_7}/templates/migration.rb.tt +23 -20
  31. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +121 -0
  32. data/lib/ruby_llm/active_record/acts_as.rb +88 -58
  33. data/lib/ruby_llm/active_record/chat_methods.rb +51 -30
  34. data/lib/ruby_llm/active_record/message_methods.rb +2 -2
  35. data/lib/ruby_llm/aliases.json +0 -8
  36. data/lib/ruby_llm/configuration.rb +5 -0
  37. data/lib/ruby_llm/models.json +1724 -1497
  38. data/lib/ruby_llm/railtie.rb +4 -12
  39. data/lib/ruby_llm/version.rb +1 -1
  40. data/lib/ruby_llm.rb +2 -1
  41. metadata +27 -9
  42. data/lib/generators/ruby_llm/install/templates/create_chats_legacy_migration.rb.tt +0 -8
  43. data/lib/generators/ruby_llm/install/templates/create_messages_legacy_migration.rb.tt +0 -16
  44. data/lib/generators/ruby_llm/install_generator.rb +0 -184
  45. data/lib/generators/ruby_llm/migrate_model_fields_generator.rb +0 -84
@@ -0,0 +1,104 @@
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 (must come before tool_calls due to foreign key)
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 (references messages)
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
+ end
50
+
51
+ def create_model_files
52
+ create_namespace_modules
53
+
54
+ template 'chat_model.rb.tt', "app/models/#{chat_model_name.underscore}.rb"
55
+ template 'message_model.rb.tt', "app/models/#{message_model_name.underscore}.rb"
56
+ template 'tool_call_model.rb.tt', "app/models/#{tool_call_model_name.underscore}.rb"
57
+
58
+ template 'model_model.rb.tt', "app/models/#{model_model_name.underscore}.rb"
59
+ end
60
+
61
+ def create_initializer
62
+ template 'initializer.rb.tt', 'config/initializers/ruby_llm.rb'
63
+ end
64
+
65
+ def install_active_storage
66
+ return if options[:skip_active_storage]
67
+
68
+ say ' Installing ActiveStorage for file attachments...', :cyan
69
+ rails_command 'active_storage:install'
70
+ end
71
+
72
+ def show_install_info
73
+ say "\n ✅ RubyLLM installed!", :green
74
+
75
+ say ' ✅ ActiveStorage configured for file attachments support', :green unless options[:skip_active_storage]
76
+
77
+ say "\n Next steps:", :yellow
78
+ say ' 1. Run: rails db:migrate'
79
+ say ' 2. Set your API keys in config/initializers/ruby_llm.rb'
80
+
81
+ say " 3. Start chatting: #{chat_model_name}.create!(model: 'gpt-4.1-nano').ask('Hello!')"
82
+
83
+ say "\n 🚀 Model registry is database-backed!", :cyan
84
+ say ' Models automatically load from the database'
85
+ say ' Pass model names as strings - RubyLLM handles the rest!'
86
+ say " Specify provider when needed: Chat.create!(model: 'gemini-2.5-flash', provider: 'vertexai')"
87
+
88
+ if options[:skip_active_storage]
89
+ say "\n 📎 Note: ActiveStorage was skipped", :yellow
90
+ say ' File attachments won\'t work without ActiveStorage.'
91
+ say ' To enable later:'
92
+ say ' 1. Run: rails active_storage:install && rails db:migrate'
93
+ say " 2. Add to your #{message_model_name} model: has_many_attached :attachments"
94
+ end
95
+
96
+ say "\n 📚 Documentation: https://rubyllm.com", :cyan
97
+
98
+ say "\n ❤️ Love RubyLLM?", :magenta
99
+ say ' • ⭐ Star on GitHub: https://github.com/crmne/ruby_llm'
100
+ say ' • 🐦 Follow for updates: https://x.com/paolino'
101
+ say "\n"
102
+ end
103
+ end
104
+ end
@@ -1,3 +1,3 @@
1
- class <%= options[:chat_model_name] %> < ApplicationRecord
1
+ class <%= chat_model_name %> < ApplicationRecord
2
2
  <%= acts_as_chat_declaration %>
3
3
  end
@@ -1,7 +1,7 @@
1
- class Create<%= options[: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 :<%= options[:chat_model_name].tableize %> do |t|
4
- t.references :<%= options[:model_model_name].tableize.singularize %>, foreign_key: true
3
+ create_table :<%= chat_table_name %> do |t|
4
+ t.references :<%= model_table_name.singularize %>, foreign_key: true
5
5
  t.timestamps
6
6
  end
7
7
  end
@@ -1,16 +1,16 @@
1
- class Create<%= options[: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 :<%= options[:message_model_name].tableize %> do |t|
4
- t.references :<%= options[:chat_model_name].tableize.singularize %>, null: false, foreign_key: true
3
+ create_table :<%= message_table_name %> do |t|
4
+ t.references :<%= chat_table_name.singularize %>, null: false, foreign_key: true
5
5
  t.string :role, null: false
6
6
  t.text :content
7
- t.references :<%= options[:model_model_name].tableize.singularize %>, foreign_key: true
7
+ t.references :<%= model_table_name.singularize %>, foreign_key: true
8
8
  t.integer :input_tokens
9
9
  t.integer :output_tokens
10
- t.references :<%= options[:tool_call_model_name].tableize.singularize %>, foreign_key: true
10
+ t.references :<%= tool_call_table_name.singularize %>, foreign_key: true
11
11
  t.timestamps
12
12
  end
13
13
 
14
- add_index :<%= options[:message_model_name].tableize %>, :role
14
+ add_index :<%= message_table_name %>, :role
15
15
  end
16
16
  end
@@ -1,6 +1,6 @@
1
- class Create<%= options[: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 :<%= options[: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<%= options[:model_model_name].pluralize %> < ActiveRecord::Migratio
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 = '<%= options[: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,15 @@
1
1
  <%#- # Migration for creating tool_calls table with database-specific JSON handling -%>
2
- class Create<%= options[: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 :<%= options[:tool_call_model_name].tableize %> do |t|
5
- t.references :<%= options[:message_model_name].tableize.singularize %>, null: false, foreign_key: true
4
+ create_table :<%= tool_call_table_name %> do |t|
5
+ t.references :<%= message_table_name.singularize %>, null: false, foreign_key: true
6
6
  t.string :tool_call_id, null: false
7
7
  t.string :name, null: false
8
8
  t.<%= postgresql? ? 'jsonb' : 'json' %> :arguments, default: {}
9
9
  t.timestamps
10
10
  end
11
11
 
12
- add_index :<%= options[:tool_call_model_name].tableize %>, :tool_call_id, unique: true
13
- add_index :<%= options[:tool_call_model_name].tableize %>, :name
12
+ add_index :<%= tool_call_table_name %>, :tool_call_id, unique: true
13
+ add_index :<%= tool_call_table_name %>, :name
14
14
  end
15
15
  end
@@ -2,8 +2,11 @@ RubyLLM.configure do |config|
2
2
  config.openai_api_key = Rails.application.credentials.dig(:openai_api_key)
3
3
  # config.default_model = "gpt-4.1-nano"
4
4
 
5
- <% unless skip_model_registry? -%>
6
- # DB-backed model registry for rich model metadata
7
- config.model_registry_class = "<%= options[:model_model_name] %>"
5
+ # Use the new association-based acts_as API (recommended)
6
+ config.use_new_acts_as = true
7
+ <% if model_model_name != 'Model' -%>
8
+
9
+ # Custom model registry class name
10
+ config.model_registry_class = "<%= model_model_name %>"
8
11
  <% end -%>
9
12
  end
@@ -1,4 +1,4 @@
1
- class <%= options[:message_model_name] %> < ApplicationRecord
1
+ class <%= message_model_name %> < ApplicationRecord
2
2
  <%= acts_as_message_declaration %><% unless options[:skip_active_storage] %>
3
3
  has_many_attached :attachments<% end %>
4
4
  end
@@ -1,3 +1,3 @@
1
- class <%= options[:model_model_name] %> < ApplicationRecord
1
+ class <%= model_model_name %> < ApplicationRecord
2
2
  <%= acts_as_model_declaration %>
3
3
  end
@@ -1,3 +1,3 @@
1
- class <%= options[:tool_call_model_name] %> < ApplicationRecord
1
+ class <%= tool_call_model_name %> < ApplicationRecord
2
2
  <%= acts_as_tool_call_declaration %>
3
3
  end
@@ -1,43 +1,46 @@
1
1
  class MigrateToRubyLLMModelReferences < ActiveRecord::Migration<%= migration_version %>
2
2
  def up
3
- # Ensure Models table exists
4
- unless table_exists?(:<%= options[:model_model_name].tableize %>)
5
- raise "Models table doesn't exist. Please run 'rails generate ruby_llm:install' with model registry first."
3
+ model_class = <%= model_model_name %>
4
+ chat_class = <%= chat_model_name %>
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"
6
12
  end
7
-
8
- model_class = <%= options[:model_model_name] %>
9
- chat_class = <%= options[:chat_model_name] %>
10
- message_class = <%= options[:message_model_name] %>
13
+ <% end %>
11
14
 
12
15
  # Then check for any models in existing data that aren't in models.json
13
16
  say_with_time "Checking for additional models in existing data" do
14
- collect_and_create_models(chat_class, :<%= options[:chat_model_name].tableize %>, model_class)
15
- collect_and_create_models(message_class, :<%= options[: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)
16
19
  model_class.count
17
20
  end
18
21
 
19
22
  # Migrate foreign keys
20
- migrate_foreign_key(:<%= options[:chat_model_name].tableize %>, chat_class, model_class, :<%= options[:model_model_name].underscore %>)
21
- migrate_foreign_key(:<%= options[:message_model_name].tableize %>, message_class, model_class, :<%= options[: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 %>)
22
25
  end
23
26
 
24
27
  def down
25
28
  # Remove foreign key references
26
- if column_exists?(:<%= options[:message_model_name].tableize %>, :<%= options[:model_model_name].underscore %>_id)
27
- remove_reference :<%= options[:message_model_name].tableize %>, :<%= options[: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
28
31
  end
29
32
 
30
- if column_exists?(:<%= options[:chat_model_name].tableize %>, :<%= options[:model_model_name].underscore %>_id)
31
- remove_reference :<%= options[:chat_model_name].tableize %>, :<%= options[: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
32
35
  end
33
36
 
34
37
  # Restore original model_id string columns
35
- if column_exists?(:<%= options[:message_model_name].tableize %>, :model_id_string)
36
- rename_column :<%= options[: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
37
40
  end
38
41
 
39
- if column_exists?(:<%= options[:chat_model_name].tableize %>, :model_id_string)
40
- rename_column :<%= options[: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
41
44
  end
42
45
  end
43
46
 
@@ -139,4 +142,4 @@ class MigrateToRubyLLMModelReferences < ActiveRecord::Migration<%= migration_ver
139
142
  model_class.find_by(model_id: model_id)
140
143
  end
141
144
  end
142
- 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
@@ -31,105 +31,135 @@ module RubyLLM
31
31
  end
32
32
 
33
33
  class_methods do # rubocop:disable Metrics/BlockLength
34
- def acts_as_chat(message_class: 'Message', tool_call_class: 'ToolCall',
35
- model_class: 'Model', model_foreign_key: nil)
34
+ def acts_as_chat(messages: :messages, message_class: nil,
35
+ model: :model, model_class: nil)
36
36
  include RubyLLM::ActiveRecord::ChatMethods
37
37
 
38
- @message_class = message_class.to_s
39
- @tool_call_class = tool_call_class.to_s
40
- @model_class = model_class.to_s
41
- @model_foreign_key = model_foreign_key || ActiveSupport::Inflector.foreign_key(@model_class)
38
+ class_attribute :messages_association_name, :model_association_name, :message_class, :model_class
42
39
 
43
- has_many :messages,
40
+ self.messages_association_name = messages
41
+ self.model_association_name = model
42
+ self.message_class = (message_class || messages.to_s.classify).to_s
43
+ self.model_class = (model_class || model.to_s.classify).to_s
44
+
45
+ has_many messages,
44
46
  -> { order(created_at: :asc) },
45
- class_name: @message_class,
46
- inverse_of: :chat,
47
+ class_name: self.message_class,
47
48
  dependent: :destroy
48
49
 
49
- belongs_to :model,
50
- class_name: @model_class,
51
- foreign_key: @model_foreign_key,
50
+ belongs_to model,
51
+ class_name: self.model_class,
52
52
  optional: true
53
53
 
54
54
  delegate :add_message, to: :to_llm
55
+
56
+ define_method :messages_association do
57
+ send(messages_association_name)
58
+ end
59
+
60
+ define_method :model_association do
61
+ send(model_association_name)
62
+ end
63
+
64
+ define_method :'model_association=' do |value|
65
+ send("#{model_association_name}=", value)
66
+ end
55
67
  end
56
68
 
57
- def acts_as_model(chat_class: 'Chat')
69
+ def acts_as_model(chats: :chats, chat_class: nil)
58
70
  include RubyLLM::ActiveRecord::ModelMethods
59
71
 
60
- @chat_class = chat_class.to_s
72
+ class_attribute :chats_association_name, :chat_class
73
+
74
+ self.chats_association_name = chats
75
+ self.chat_class = (chat_class || chats.to_s.classify).to_s
61
76
 
62
77
  validates :model_id, presence: true, uniqueness: { scope: :provider }
63
78
  validates :provider, presence: true
64
79
  validates :name, presence: true
65
80
 
66
- has_many :chats,
67
- class_name: @chat_class,
68
- foreign_key: ActiveSupport::Inflector.foreign_key(name)
81
+ has_many chats, class_name: self.chat_class
82
+
83
+ define_method :chats_association do
84
+ send(chats_association_name)
85
+ end
69
86
  end
70
87
 
71
- def acts_as_message(chat_class: 'Chat', # rubocop:disable Metrics/ParameterLists
72
- chat_foreign_key: nil,
73
- tool_call_class: 'ToolCall',
74
- tool_call_foreign_key: nil,
75
- model_class: 'Model',
76
- model_foreign_key: nil,
77
- touch_chat: false)
88
+ def acts_as_message(chat: :chat, chat_class: nil, touch_chat: false, # rubocop:disable Metrics/ParameterLists
89
+ tool_calls: :tool_calls, tool_call_class: nil,
90
+ model: :model, model_class: nil)
78
91
  include RubyLLM::ActiveRecord::MessageMethods
79
92
 
80
- @chat_class = chat_class.to_s
81
- @chat_foreign_key = chat_foreign_key || ActiveSupport::Inflector.foreign_key(@chat_class)
82
-
83
- @tool_call_class = tool_call_class.to_s
84
- @tool_call_foreign_key = tool_call_foreign_key || ActiveSupport::Inflector.foreign_key(@tool_call_class)
93
+ class_attribute :chat_association_name, :tool_calls_association_name, :model_association_name,
94
+ :chat_class, :tool_call_class, :model_class
85
95
 
86
- @model_class = model_class.to_s
87
- @model_foreign_key = model_foreign_key || ActiveSupport::Inflector.foreign_key(@model_class)
96
+ self.chat_association_name = chat
97
+ self.tool_calls_association_name = tool_calls
98
+ self.model_association_name = model
99
+ self.chat_class = (chat_class || chat.to_s.classify).to_s
100
+ self.tool_call_class = (tool_call_class || tool_calls.to_s.classify).to_s
101
+ self.model_class = (model_class || model.to_s.classify).to_s
88
102
 
89
- belongs_to :chat,
90
- class_name: @chat_class,
91
- foreign_key: @chat_foreign_key,
92
- inverse_of: :messages,
103
+ belongs_to chat,
104
+ class_name: self.chat_class,
93
105
  touch: touch_chat
94
106
 
95
- has_many :tool_calls,
96
- class_name: @tool_call_class,
107
+ has_many tool_calls,
108
+ class_name: self.tool_call_class,
97
109
  dependent: :destroy
98
110
 
99
111
  belongs_to :parent_tool_call,
100
- class_name: @tool_call_class,
101
- foreign_key: @tool_call_foreign_key,
102
- optional: true,
103
- inverse_of: :result
112
+ class_name: self.tool_call_class,
113
+ foreign_key: ActiveSupport::Inflector.foreign_key(tool_calls.to_s.singularize),
114
+ optional: true
104
115
 
105
116
  has_many :tool_results,
106
- through: :tool_calls,
117
+ through: tool_calls,
107
118
  source: :result,
108
- class_name: @message_class
119
+ class_name: name
109
120
 
110
- belongs_to :model,
111
- class_name: @model_class,
112
- foreign_key: @model_foreign_key,
121
+ belongs_to model,
122
+ class_name: self.model_class,
113
123
  optional: true
114
124
 
115
125
  delegate :tool_call?, :tool_result?, to: :to_llm
126
+
127
+ define_method :chat_association do
128
+ send(chat_association_name)
129
+ end
130
+
131
+ define_method :tool_calls_association do
132
+ send(tool_calls_association_name)
133
+ end
134
+
135
+ define_method :model_association do
136
+ send(model_association_name)
137
+ end
116
138
  end
117
139
 
118
- def acts_as_tool_call(message_class: 'Message', message_foreign_key: nil, result_foreign_key: nil)
119
- @message_class = message_class.to_s
120
- @message_foreign_key = message_foreign_key || ActiveSupport::Inflector.foreign_key(@message_class)
121
- @result_foreign_key = result_foreign_key || ActiveSupport::Inflector.foreign_key(name)
140
+ def acts_as_tool_call(message: :message, message_class: nil,
141
+ result: :result, result_class: nil)
142
+ class_attribute :message_association_name, :result_association_name, :message_class, :result_class
122
143
 
123
- belongs_to :message,
124
- class_name: @message_class,
125
- foreign_key: @message_foreign_key,
126
- inverse_of: :tool_calls
144
+ self.message_association_name = message
145
+ self.result_association_name = result
146
+ self.message_class = (message_class || message.to_s.classify).to_s
147
+ self.result_class = (result_class || self.message_class).to_s
127
148
 
128
- has_one :result,
129
- class_name: @message_class,
130
- foreign_key: @result_foreign_key,
131
- inverse_of: :parent_tool_call,
149
+ belongs_to message,
150
+ class_name: self.message_class
151
+
152
+ has_one result,
153
+ class_name: self.result_class,
132
154
  dependent: :nullify
155
+
156
+ define_method :message_association do
157
+ send(message_association_name)
158
+ end
159
+
160
+ define_method :result_association do
161
+ send(result_association_name)
162
+ end
133
163
  end
134
164
  end
135
165
  end