-
-
Notifications
You must be signed in to change notification settings - Fork 294
Rails Generator for RubyLLM Models #75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 16 commits
487d4e4
3577f31
24467aa
2d50909
6e68d36
dacb148
f469f60
844614b
131e51c
f40d1b4
5eeedf9
9d8c2d6
61d66ab
a239b34
8316ce5
cf4a560
492188f
52d761a
cc3d23f
9eba7bc
7cded01
21c4ee0
fda4df1
6270f22
f5454aa
5be36c1
1f8d3b0
a350738
1ed3c2e
f16858a
26bcf50
c352d5f
b064713
5a9b3b8
224b24e
1fe4320
882f3fc
4789f80
d2196bd
bf2244a
145d2d2
5982876
50befbe
9d92db3
4cc4054
3ecfeaf
d66c305
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| # RubyLLM Rails Setup Complete! | ||
|
|
||
| Thanks for installing RubyLLM in your Rails application. Here's what was created: | ||
|
|
||
| ## Models | ||
|
|
||
| - `<%= options[:chat_model_name] %>` - Stores chat sessions and their associated model ID | ||
| - `<%= options[:message_model_name] %>` - Stores individual messages in a chat | ||
| - `<%= options[:tool_call_model_name] %>` - Stores tool calls made by language models | ||
|
|
||
| ## Configuration Options | ||
|
|
||
| The generator supports the following options to customize model names: | ||
|
|
||
| ```bash | ||
| rails generate ruby_llm:install \ | ||
| --chat-model-name=Conversation \ | ||
| --message-model-name=ChatMessage \ | ||
| --tool-call-model-name=FunctionCall | ||
| ``` | ||
|
|
||
| This is useful when you need to avoid namespace collisions with existing models in your application. Table names will be automatically derived from the model names following Rails conventions. | ||
|
|
||
| ## Next Steps | ||
|
|
||
| 1. **Run migrations:** | ||
| ```bash | ||
| rails db:migrate | ||
| ``` | ||
|
|
||
| 2. **Set your API keys** in `config/initializers/ruby_llm.rb` or using environment variables: | ||
| ```ruby | ||
| # config/initializers/ruby_llm.rb | ||
| RubyLLM.configure do |config| | ||
| config.openai_api_key = ENV["OPENAI_API_KEY"] | ||
| config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"] | ||
| # etc. | ||
| end | ||
| ``` | ||
|
|
||
| 3. **Start using RubyLLM in your code:** | ||
| ```ruby | ||
| # Create a new chat | ||
| chat = <%= options[:chat_model_name] %>.create!(model_id: "gpt-4o-mini") | ||
|
|
||
| # Ask a question | ||
| response = chat.ask("What's the best Ruby web framework?") | ||
|
|
||
| # Get chat history | ||
| chat.messages | ||
| ``` | ||
|
|
||
| 4. **For streaming responses** with ActionCable or Turbo: | ||
| ```ruby | ||
| chat.ask("Tell me about Ruby on Rails") do |chunk| | ||
| Turbo::StreamsChannel.broadcast_append_to( | ||
| chat, target: "response", partial: "messages/chunk", locals: { chunk: chunk } | ||
| ) | ||
| end | ||
| ``` | ||
|
|
||
| ## Advanced Usage | ||
|
|
||
| - Add more fields to your models as needed | ||
| - Customize the views to match your application design | ||
| - Create a controller for chat interactions | ||
|
|
||
| For more information, visit the [RubyLLM Documentation](https://github.com/crmne/ruby_llm) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| class <%= options[:chat_model_name] %> < ApplicationRecord | ||
| <%= acts_as_chat_declaration %> | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| class Create<%= options[:chat_model_name].pluralize %> < ActiveRecord::Migration<%= migration_version %> | ||
| def change | ||
| create_table :<%= options[:chat_model_name].tableize %> do |t| | ||
| t.string :model_id | ||
| t.timestamps | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # This migration must be run AFTER create_<%= options[:chat_model_name].tableize %> and create_<%= options[:tool_call_model_name].tableize %> migrations | ||
|
||
| # to ensure proper foreign key references | ||
| class Create<%= options[:message_model_name].pluralize %> < ActiveRecord::Migration<%= migration_version %> | ||
| def change | ||
| create_table :<%= options[:message_model_name].tableize %> do |t| | ||
| t.references :<%= options[:chat_model_name].tableize.singularize %>, null: false, foreign_key: true | ||
| t.string :role | ||
| t.text :content | ||
| t.string :model_id | ||
| t.integer :input_tokens | ||
| t.integer :output_tokens | ||
| t.references :<%= options[:tool_call_model_name].tableize.singularize %> | ||
| t.timestamps | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <%#- # Migration for creating tool_calls table with database-specific JSON handling -%> | ||
| class Create<%= options[:tool_call_model_name].pluralize %> < ActiveRecord::Migration<%= migration_version %> | ||
| def change | ||
| create_table :<%= options[:tool_call_model_name].tableize %> do |t| | ||
| t.references :<%= options[:message_model_name].tableize.singularize %>, null: false, foreign_key: true | ||
| t.string :tool_call_id, null: false | ||
| t.string :name, null: false | ||
| t.<%= postgresql? ? 'jsonb' : 'json' %> :arguments, default: {} | ||
| t.timestamps | ||
| end | ||
|
|
||
| add_index :<%= options[:tool_call_model_name].tableize %>, :tool_call_id | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # RubyLLM configuration | ||
| RubyLLM.configure do |config| | ||
| # Set your API keys here or use environment variables | ||
| # config.openai_api_key = ENV["OPENAI_API_KEY"] | ||
| # config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"] | ||
| # config.gemini_api_key = ENV["GEMINI_API_KEY"] | ||
| # config.deepseek_api_key = ENV["DEEPSEEK_API_KEY"] | ||
|
|
||
| # Uncomment to set a default model | ||
| # config.default_model = "gpt-4o-mini" | ||
|
|
||
| # Uncomment to set default options | ||
| # config.default_options = { temperature: 0.7 } | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| class <%= options[:message_model_name] %> < ApplicationRecord | ||
| <%= acts_as_message_declaration %> | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| class <%= options[:tool_call_model_name] %> < ApplicationRecord | ||
| <%= acts_as_tool_call_declaration %> | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require 'rails/generators' | ||
| require 'rails/generators/active_record' | ||
|
|
||
| module RubyLLM | ||
| # Generator for RubyLLM Rails models and migrations | ||
| class InstallGenerator < Rails::Generators::Base | ||
| include Rails::Generators::Migration | ||
| namespace "ruby_llm:install" | ||
|
|
||
| source_root File.expand_path('install/templates', __dir__) | ||
|
|
||
| class_option :chat_model_name, type: :string, default: 'Chat', | ||
| desc: 'Name of the Chat model class' | ||
| class_option :message_model_name, type: :string, default: 'Message', | ||
| desc: 'Name of the Message model class' | ||
| class_option :tool_call_model_name, type: :string, default: 'ToolCall', | ||
| desc: 'Name of the ToolCall model class' | ||
|
|
||
| desc 'Creates model files for Chat, Message, and ToolCall, and creates migrations for RubyLLM Rails integration' | ||
|
|
||
| def self.next_migration_number(dirname) | ||
| ::ActiveRecord::Generators::Base.next_migration_number(dirname) | ||
| end | ||
|
|
||
| def migration_version | ||
| "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]" | ||
| end | ||
|
|
||
| def postgresql? | ||
| ActiveRecord::Base.connection.adapter_name.downcase.include?('postgresql') | ||
| rescue StandardError | ||
| false | ||
| end | ||
|
|
||
| def acts_as_chat_declaration | ||
| acts_as_chat_params = [] | ||
| if options[:message_model_name] | ||
| acts_as_chat_params << "message_class: \"#{options[:message_model_name]}\"" | ||
| end | ||
| if options[:tool_call_model_name] | ||
| acts_as_chat_params << "tool_call_class: \"#{options[:tool_call_model_name]}\"" | ||
| end | ||
| if acts_as_chat_params.any? | ||
| "acts_as_chat #{acts_as_chat_params.join(', ')}" | ||
| else | ||
| "acts_as_chat" | ||
| end | ||
| end | ||
|
|
||
| def acts_as_message_declaration | ||
| acts_as_message_params = [] | ||
| if options[:chat_model_name] | ||
| acts_as_message_params << "chat_class: \"#{options[:chat_model_name]}\"" | ||
| end | ||
| if options[:tool_call_model_name] | ||
| acts_as_message_params << "tool_call_class: \"#{options[:tool_call_model_name]}\"" | ||
| end | ||
| if acts_as_message_params.any? | ||
| "acts_as_message #{acts_as_message_params.join(', ')}" | ||
| else | ||
| "acts_as_message" | ||
| end | ||
| end | ||
|
|
||
| def acts_as_tool_call_declaration | ||
| acts_as_tool_call_params = [] | ||
| if options[:message_model_name] | ||
| acts_as_tool_call_params << "message_class: \"#{options[:message_model_name]}\"" | ||
| end | ||
| if acts_as_tool_call_params.any? | ||
| "acts_as_tool_call #{acts_as_tool_call_params.join(', ')}" | ||
| else | ||
| "acts_as_tool_call" | ||
| end | ||
| end | ||
|
|
||
| def create_migration_files | ||
| # Use a fixed timestamp for testing and to ensure they're sequential | ||
| # @migration_number = Time.now.utc.strftime('%Y%m%d%H%M%S') | ||
| migration_template 'create_chats_migration.rb.tt', "db/migrate/create_#{options[:chat_model_name].tableize}.rb" | ||
|
||
|
|
||
| # Increment timestamp for the next migration | ||
| # @migration_number = (@migration_number.to_i + 1).to_s | ||
| migration_template 'create_messages_migration.rb.tt', "db/migrate/create_#{options[:message_model_name].tableize}.rb" | ||
|
|
||
| # Increment timestamp again for the final migration | ||
| # @migration_number = (@migration_number.to_i + 2).to_s | ||
| migration_template 'create_tool_calls_migration.rb.tt', "db/migrate/create_#{options[:tool_call_model_name].tableize}.rb" | ||
| end | ||
|
|
||
| def create_model_files | ||
| template 'chat_model.rb.tt', "app/models/#{options[:chat_model_name].underscore}.rb" | ||
| template 'message_model.rb.tt', "app/models/#{options[:message_model_name].underscore}.rb" | ||
| template 'tool_call_model.rb.tt', "app/models/#{options[:tool_call_model_name].underscore}.rb" | ||
| end | ||
|
|
||
| def create_initializer | ||
| template 'initializer.rb.tt', 'config/initializers/ruby_llm.rb' | ||
| end | ||
|
|
||
| def show_readme | ||
| content = ERB.new(File.read(source_paths.first + '/README.md.tt')).result(binding) | ||
| say content | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,5 +8,15 @@ class Railtie < Rails::Railtie | |
| include RubyLLM::ActiveRecord::ActsAs | ||
| end | ||
| end | ||
|
|
||
| # Include rake tasks if applicable | ||
|
||
| rake_tasks do | ||
| # Task definitions go here if needed | ||
| end | ||
|
|
||
| # Register generators | ||
| generators do | ||
| require 'generators/ruby_llm/install_generator' | ||
| end | ||
| end | ||
| end | ||
Uh oh!
There was an error while loading. Please reload this page.