ruby_llm 1.6.3 → 1.7.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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -3
  3. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +115 -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/install/templates/chat_model.rb.tt +2 -2
  20. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +4 -4
  21. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +8 -7
  22. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +43 -0
  23. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +6 -5
  24. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +10 -4
  25. data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +4 -3
  26. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +3 -0
  27. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +2 -2
  28. data/lib/generators/ruby_llm/install_generator.rb +129 -33
  29. data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +137 -0
  30. data/lib/generators/ruby_llm/upgrade_to_v1_7_generator.rb +160 -0
  31. data/lib/ruby_llm/active_record/acts_as.rb +112 -319
  32. data/lib/ruby_llm/active_record/acts_as_legacy.rb +398 -0
  33. data/lib/ruby_llm/active_record/chat_methods.rb +336 -0
  34. data/lib/ruby_llm/active_record/message_methods.rb +72 -0
  35. data/lib/ruby_llm/active_record/model_methods.rb +84 -0
  36. data/lib/ruby_llm/aliases.json +58 -13
  37. data/lib/ruby_llm/attachment.rb +20 -0
  38. data/lib/ruby_llm/chat.rb +8 -7
  39. data/lib/ruby_llm/configuration.rb +9 -0
  40. data/lib/ruby_llm/connection.rb +4 -4
  41. data/lib/ruby_llm/model/info.rb +12 -0
  42. data/lib/ruby_llm/models.json +3579 -2029
  43. data/lib/ruby_llm/models.rb +51 -22
  44. data/lib/ruby_llm/provider.rb +3 -3
  45. data/lib/ruby_llm/providers/anthropic/chat.rb +2 -2
  46. data/lib/ruby_llm/providers/anthropic/media.rb +1 -1
  47. data/lib/ruby_llm/providers/anthropic/tools.rb +1 -1
  48. data/lib/ruby_llm/providers/bedrock/chat.rb +2 -2
  49. data/lib/ruby_llm/providers/bedrock/models.rb +19 -1
  50. data/lib/ruby_llm/providers/gemini/chat.rb +53 -25
  51. data/lib/ruby_llm/providers/gemini/media.rb +1 -1
  52. data/lib/ruby_llm/providers/gpustack/chat.rb +11 -0
  53. data/lib/ruby_llm/providers/gpustack/media.rb +45 -0
  54. data/lib/ruby_llm/providers/gpustack/models.rb +44 -8
  55. data/lib/ruby_llm/providers/gpustack.rb +1 -0
  56. data/lib/ruby_llm/providers/ollama/media.rb +2 -6
  57. data/lib/ruby_llm/providers/ollama/models.rb +36 -0
  58. data/lib/ruby_llm/providers/ollama.rb +1 -0
  59. data/lib/ruby_llm/providers/openai/chat.rb +1 -1
  60. data/lib/ruby_llm/providers/openai/media.rb +4 -4
  61. data/lib/ruby_llm/providers/openai/tools.rb +11 -6
  62. data/lib/ruby_llm/providers/openai.rb +2 -2
  63. data/lib/ruby_llm/providers/vertexai/chat.rb +14 -0
  64. data/lib/ruby_llm/providers/vertexai/embeddings.rb +32 -0
  65. data/lib/ruby_llm/providers/vertexai/models.rb +130 -0
  66. data/lib/ruby_llm/providers/vertexai/streaming.rb +14 -0
  67. data/lib/ruby_llm/providers/vertexai.rb +55 -0
  68. data/lib/ruby_llm/railtie.rb +20 -3
  69. data/lib/ruby_llm/streaming.rb +1 -1
  70. data/lib/ruby_llm/utils.rb +5 -9
  71. data/lib/ruby_llm/version.rb +1 -1
  72. data/lib/ruby_llm.rb +4 -3
  73. data/lib/tasks/models.rake +525 -0
  74. data/lib/tasks/release.rake +37 -2
  75. data/lib/tasks/ruby_llm.rake +15 -0
  76. data/lib/tasks/vcr.rake +2 -2
  77. metadata +37 -5
  78. data/lib/generators/ruby_llm/install/templates/INSTALL_INFO.md.tt +0 -108
  79. data/lib/tasks/aliases.rake +0 -205
  80. data/lib/tasks/models_docs.rake +0 -214
  81. data/lib/tasks/models_update.rake +0 -108
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e454eaf845f9c4b9a03f6e5a12567e59d5621d1ff6db3e866be50e836de65c2
4
- data.tar.gz: 8e49e339287432cc9feee74ee9b0b49abffcc282bb6387fc9d2cdd6889f9c39c
3
+ metadata.gz: 5a7c8f498c7fee7618711944119c65a33f8a2236a87cbd55d51b5e5a5d25000f
4
+ data.tar.gz: 2bb4675e6bcad95099330783697c87c7d6eade98c159a6b00247c7e179f7bd9a
5
5
  SHA512:
6
- metadata.gz: 06e7fc0118d88631e1c41a9b4b0738b6f55b965ee4569643759489e81191317dfe4cba5e55dcf89d386ef68379b013a4a0a6c9b9b71f3dd3115f7f46186545f4
7
- data.tar.gz: 047a575afaa0cf37934e395bd49460edab5a07e29a8c7f085139c331c94d44bdc226c9c2b8781183d05a7ce6b81b08af943b86c9c3ee76f19188a491585d971b
6
+ metadata.gz: 06e41bd54bcf1c7c094750b67c494decf50e9db53a250d4732455742d688aa08c72669ba64f4cc771fa3a1ad5197f646ce58308be1f3fd7db84b21282180135b
7
+ data.tar.gz: '08c739bbdb44380720e817704d9f980b49b6a7bafd80d890a3207531afe746d9039e2e3e0d6adf82e496b8ce16d68fa0ee8359ccf63cecf08de778e07fe6b632'
data/README.md CHANGED
@@ -9,14 +9,17 @@
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=5)](https://badge.fury.io/rb/ruby_llm)
12
+ [![Gem Version](https://badge.fury.io/rb/ruby_llm.svg?a=7)](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
- [![codecov](https://codecov.io/gh/crmne/ruby_llm/branch/main/graph/badge.svg)](https://codecov.io/gh/crmne/ruby_llm)
15
+ [![codecov](https://codecov.io/gh/crmne/ruby_llm/branch/main/graph/badge.svg?a=2)](https://codecov.io/gh/crmne/ruby_llm)
16
16
 
17
17
  <a href="https://trendshift.io/repositories/13640" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13640" alt="crmne%2Fruby_llm | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
18
18
  </div>
19
19
 
20
+ > [!NOTE]
21
+ > Using RubyLLM in production? [Share your story](https://tally.so/r/3Na02p)! Takes 5 minutes.
22
+
20
23
  ---
21
24
 
22
25
  Build chatbots, AI agents, RAG applications. Works with OpenAI, Anthropic, Google, AWS, local models, and any OpenAI-compatible API.
@@ -108,7 +111,7 @@ response = chat.with_schema(ProductSchema).ask "Analyze this product", with: "pr
108
111
  * **Rails:** ActiveRecord integration with `acts_as_chat`
109
112
  * **Async:** Fiber-based concurrency
110
113
  * **Model registry:** 500+ models with capability detection and pricing
111
- * **Providers:** OpenAI, Anthropic, Gemini, Bedrock, DeepSeek, Mistral, Ollama, OpenRouter, Perplexity, GPUStack, and any OpenAI-compatible API
114
+ * **Providers:** OpenAI, Anthropic, Gemini, VertexAI, Bedrock, DeepSeek, Mistral, Ollama, OpenRouter, Perplexity, GPUStack, and any OpenAI-compatible API
112
115
 
113
116
  ## Installation
114
117
 
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module RubyLLM
6
+ module Generators
7
+ # Generates a simple chat UI scaffold for RubyLLM
8
+ class ChatUIGenerator < Rails::Generators::Base
9
+ source_root File.expand_path('templates', __dir__)
10
+
11
+ namespace 'ruby_llm:chat_ui'
12
+
13
+ argument :model_mappings, type: :array, default: [], banner: 'chat:ChatName message:MessageName ...'
14
+
15
+ desc 'Creates a chat UI scaffold with Turbo streaming\n' \
16
+ 'Usage: rails g ruby_llm:chat_ui [chat:ChatName] [message:MessageName] ...'
17
+
18
+ def parse_model_mappings
19
+ @model_names = {
20
+ chat: 'Chat',
21
+ message: 'Message',
22
+ tool_call: 'ToolCall',
23
+ model: 'Model'
24
+ }
25
+
26
+ model_mappings.each do |mapping|
27
+ if mapping.include?(':')
28
+ key, value = mapping.split(':', 2)
29
+ @model_names[key.to_sym] = value.classify
30
+ end
31
+ end
32
+
33
+ @model_names
34
+ end
35
+
36
+ %i[chat message model].each do |type|
37
+ define_method("#{type}_model_name") do
38
+ @model_names ||= parse_model_mappings
39
+ @model_names[type]
40
+ end
41
+ end
42
+
43
+ def create_views
44
+ # 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"
48
+ 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"
51
+
52
+ # Message views
53
+ 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"
56
+ template 'views/messages/create.turbo_stream.erb',
57
+ "app/views/#{message_model_name.tableize}/create.turbo_stream.erb"
58
+
59
+ # 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"
62
+ template 'views/models/_model.html.erb',
63
+ "app/views/#{model_model_name.tableize}/_#{model_model_name.underscore}.html.erb"
64
+ end
65
+
66
+ 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"
70
+ end
71
+
72
+ def create_jobs
73
+ template 'jobs/chat_response_job.rb', "app/jobs/#{chat_model_name.underscore}_response_job.rb"
74
+ end
75
+
76
+ def add_routes
77
+ model_routes = <<~ROUTES.strip
78
+ resources :#{model_model_name.tableize}, only: [:index, :show] do
79
+ collection do
80
+ post :refresh
81
+ end
82
+ end
83
+ ROUTES
84
+ route model_routes
85
+ chat_routes = <<~ROUTES.strip
86
+ resources :#{chat_model_name.tableize} do
87
+ resources :#{message_model_name.tableize}, only: [:create]
88
+ end
89
+ ROUTES
90
+ route chat_routes
91
+ end
92
+
93
+ def add_broadcasting_to_message_model
94
+ msg_var = message_model_name.underscore
95
+ chat_var = chat_model_name.underscore
96
+ broadcasting_code = "broadcasts_to ->(#{msg_var}) { \"#{chat_var}_\#{#{msg_var}.#{chat_var}_id}\" }"
97
+
98
+ inject_into_class "app/models/#{msg_var}.rb", message_model_name do
99
+ "\n #{broadcasting_code}\n"
100
+ end
101
+ rescue Errno::ENOENT
102
+ say "#{message_model_name} model not found. Add broadcasting code to your model.", :yellow
103
+ say " #{broadcasting_code}", :yellow
104
+ end
105
+
106
+ def display_post_install_message
107
+ return unless behavior == :invoke
108
+
109
+ say "\n ✅ Chat UI installed!", :green
110
+ say "\n Start your server and visit http://localhost:3000/#{chat_model_name.tableize}", :cyan
111
+ say "\n"
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,39 @@
1
+ class <%= chat_model_name.pluralize %>Controller < ApplicationController
2
+ before_action :set_<%= chat_model_name.underscore %>, only: [:show]
3
+
4
+ def index
5
+ @<%= chat_model_name.tableize %> = <%= chat_model_name %>.order(created_at: :desc)
6
+ end
7
+
8
+ def new
9
+ @<%= chat_model_name.underscore %> = <%= chat_model_name %>.new
10
+ @selected_model = params[:model]
11
+ end
12
+
13
+ def create
14
+ return unless prompt.present?
15
+
16
+ @<%= chat_model_name.underscore %> = <%= chat_model_name %>.create!(model: model)
17
+ <%= chat_model_name %>ResponseJob.perform_later(@<%= chat_model_name.underscore %>.id, prompt)
18
+
19
+ redirect_to @<%= chat_model_name.underscore %>, notice: '<%= chat_model_name.humanize %> was successfully created.'
20
+ end
21
+
22
+ def show
23
+ @<%= message_model_name.underscore %> = @<%= chat_model_name.underscore %>.<%= message_model_name.tableize %>.build
24
+ end
25
+
26
+ private
27
+
28
+ def set_<%= chat_model_name.underscore %>
29
+ @<%= chat_model_name.underscore %> = <%= chat_model_name %>.find(params[:id])
30
+ end
31
+
32
+ def model
33
+ params[:<%= chat_model_name.underscore %>][:model].presence
34
+ end
35
+
36
+ def prompt
37
+ params[:<%= chat_model_name.underscore %>][:prompt]
38
+ end
39
+ end
@@ -0,0 +1,24 @@
1
+ class <%= message_model_name.pluralize %>Controller < ApplicationController
2
+ before_action :set_<%= chat_model_name.underscore %>
3
+
4
+ def create
5
+ return unless content.present?
6
+
7
+ <%= chat_model_name %>ResponseJob.perform_later(@<%= chat_model_name.underscore %>.id, content)
8
+
9
+ respond_to do |format|
10
+ format.turbo_stream
11
+ format.html { redirect_to @<%= chat_model_name.underscore %> }
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def set_<%= chat_model_name.underscore %>
18
+ @<%= chat_model_name.underscore %> = <%= chat_model_name %>.find(params[:<%= chat_model_name.underscore %>_id])
19
+ end
20
+
21
+ def content
22
+ params[:<%= message_model_name.underscore %>][:content]
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ class <%= model_model_name.pluralize %>Controller < ApplicationController
2
+ def index
3
+ @<%= model_model_name.tableize %> = <%= model_model_name %>.all.group_by(&:provider)
4
+ end
5
+
6
+ def show
7
+ @<%= model_model_name.underscore %> = <%= model_model_name %>.find(params[:id])
8
+ end
9
+
10
+ def refresh
11
+ <%= model_model_name %>.refresh!
12
+ redirect_to <%= model_model_name.tableize %>_path, notice: "<%= model_model_name.pluralize %> refreshed successfully"
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ class <%= chat_model_name %>ResponseJob < ApplicationJob
2
+ def perform(<%= chat_model_name.underscore %>_id, content)
3
+ <%= chat_model_name.underscore %> = <%= chat_model_name %>.find(<%= chat_model_name.underscore %>_id)
4
+
5
+ <%= chat_model_name.underscore %>.ask(content) do |chunk|
6
+ if chunk.content && !chunk.content.blank?
7
+ <%= message_model_name.underscore %> = <%= chat_model_name.underscore %>.<%= message_model_name.tableize %>.last
8
+ <%= message_model_name.underscore %>.update!(content: <%= message_model_name.underscore %>.content + chunk.content)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ <div id="<%%= dom_id <%= chat_model_name.underscore %> %>">
2
+ <div>
3
+ <strong>Model:</strong>
4
+ <%%= <%= chat_model_name.underscore %>.model_association.name %>
5
+ </div>
6
+
7
+ <div>
8
+ <strong>Messages:</strong>
9
+ <%%= <%= chat_model_name.underscore %>.messages_association.count %>
10
+ </div>
11
+
12
+ <div>
13
+ <strong>Created:</strong>
14
+ <%%= <%= chat_model_name.underscore %>.created_at.strftime("%B %d, %Y at %I:%M %p") %>
15
+ </div>
16
+ </div>
@@ -0,0 +1,29 @@
1
+ <%%= form_with(model: <%= chat_model_name.underscore %>, url: <%= chat_model_name.tableize %>_path) do |form| %>
2
+ <%% if <%= chat_model_name.underscore %>.errors.any? %>
3
+ <div style="color: red">
4
+ <h2><%%= pluralize(<%= chat_model_name.underscore %>.errors.count, "error") %> prohibited this <%= chat_model_name.underscore.humanize.downcase %> from being saved:</h2>
5
+
6
+ <ul>
7
+ <%% <%= chat_model_name.underscore %>.errors.each do |error| %>
8
+ <li><%%= error.full_message %></li>
9
+ <%% end %>
10
+ </ul>
11
+ </div>
12
+ <%% end %>
13
+
14
+ <div>
15
+ <%%= form.label :model, "Select AI model:", style: "display: block" %>
16
+ <%%= form.select :model,
17
+ options_for_select(<%= model_model_name %>.pluck(:name, :model_id).unshift(["Default (#{RubyLLM.config.default_model})", nil]), @selected_model),
18
+ {},
19
+ style: "width: 100%; max-width: 600px; padding: 5px;" %>
20
+ </div>
21
+
22
+ <div style="margin-top: 15px;">
23
+ <%%= form.text_field :prompt, style: "width: 100%; max-width: 600px;", placeholder: "What would you like to discuss?", autofocus: true %>
24
+ </div>
25
+
26
+ <div>
27
+ <%%= form.submit "Start new <%= chat_model_name.underscore.humanize.downcase %>" %>
28
+ </div>
29
+ <%% end %>
@@ -0,0 +1,16 @@
1
+ <p style="color: green"><%%= notice %></p>
2
+
3
+ <%% content_for :title, "<%= chat_model_name.pluralize %>" %>
4
+
5
+ <h1><%= chat_model_name.pluralize %></h1>
6
+
7
+ <div id="<%= chat_model_name.tableize %>">
8
+ <%% @<%= chat_model_name.tableize %>.each do |<%= chat_model_name.underscore %>| %>
9
+ <%%= render <%= chat_model_name.underscore %> %>
10
+ <p>
11
+ <%%= link_to "Show this <%= chat_model_name.underscore.humanize.downcase %>", <%= chat_model_name.underscore %> %>
12
+ </p>
13
+ <%% end %>
14
+ </div>
15
+
16
+ <%%= link_to "New <%= chat_model_name.underscore.humanize.downcase %>", new_<%= chat_model_name.underscore %>_path %>
@@ -0,0 +1,11 @@
1
+ <%% content_for :title, "New <%= chat_model_name.underscore.humanize.downcase %>" %>
2
+
3
+ <h1>New <%= chat_model_name.underscore.humanize.downcase %></h1>
4
+
5
+ <%%= render "form", <%= chat_model_name.underscore %>: @<%= chat_model_name.underscore %> %>
6
+
7
+ <br>
8
+
9
+ <div>
10
+ <%%= link_to "Back to <%= chat_model_name.tableize.humanize.downcase %>", <%= chat_model_name.tableize %>_path %>
11
+ </div>
@@ -0,0 +1,23 @@
1
+ <p style="color: green"><%%= notice %></p>
2
+
3
+ <%%= turbo_stream_from "<%= chat_model_name.underscore %>_#{@<%= chat_model_name.underscore %>.id}" %>
4
+
5
+ <%% content_for :title, "<%= chat_model_name %>" %>
6
+
7
+ <h1><%= chat_model_name %> <%%= @<%= chat_model_name.underscore %>.id %></h1>
8
+
9
+ <p>Using <strong><%%= @<%= chat_model_name.underscore %>.model_association.name %></strong></p>
10
+
11
+ <div id="<%= message_model_name.tableize %>">
12
+ <%% @<%= chat_model_name.underscore %>.messages_association.where.not(id: nil).each do |<%= message_model_name.underscore %>| %>
13
+ <%%= render "<%= message_model_name.tableize %>/<%= message_model_name.underscore %>", <%= message_model_name.underscore %>: <%= message_model_name.underscore %> %>
14
+ <%% end %>
15
+ </div>
16
+
17
+ <div style="margin-top: 30px;">
18
+ <%%= render "<%= message_model_name.tableize %>/form", <%= chat_model_name.underscore %>: @<%= chat_model_name.underscore %>, <%= message_model_name.underscore %>: @<%= message_model_name.underscore %> %>
19
+ </div>
20
+
21
+ <div style="margin-top: 20px;">
22
+ <%%= link_to "Back to <%= chat_model_name.tableize.humanize.downcase %>", <%= chat_model_name.tableize %>_path %>
23
+ </div>
@@ -0,0 +1,21 @@
1
+ <%%= form_with(model: [<%= chat_model_name.underscore %>, <%= message_model_name.underscore %>], id: "new_<%= message_model_name.underscore %>") do |form| %>
2
+ <%% if <%= message_model_name.underscore %>.errors.any? %>
3
+ <div style="color: red">
4
+ <h2><%%= pluralize(<%= message_model_name.underscore %>.errors.count, "error") %> prohibited this <%= message_model_name.underscore.humanize.downcase %> from being saved:</h2>
5
+
6
+ <ul>
7
+ <%% <%= message_model_name.underscore %>.errors.each do |error| %>
8
+ <li><%%= error.full_message %></li>
9
+ <%% end %>
10
+ </ul>
11
+ </div>
12
+ <%% end %>
13
+
14
+ <div>
15
+ <%%= form.text_field :content, style: "width: 100%; max-width: 600px;", placeholder: "Message...", autofocus: true %>
16
+ </div>
17
+
18
+ <div>
19
+ <%%= form.submit "Send <%= message_model_name.underscore.humanize.downcase %>" %>
20
+ </div>
21
+ <%% end %>
@@ -0,0 +1,10 @@
1
+ <%%= tag.div id: dom_id(<%= message_model_name.underscore %>), class: "<%= message_model_name.underscore %>",
2
+ style: "margin-bottom: 20px; padding: 10px; border-left: 3px solid #{<%= message_model_name.underscore %>.role == 'user' ? '#007bff' : '#28a745'};" do %>
3
+ <div style="font-weight: bold; margin-bottom: 5px;">
4
+ <%%= <%= message_model_name.underscore %>.role&.capitalize %>
5
+ </div>
6
+ <div style="white-space: pre-wrap;"><%%= <%= message_model_name.underscore %>.content %></div>
7
+ <div style="font-size: 0.85em; color: #666; margin-top: 5px;">
8
+ <%%= <%= message_model_name.underscore %>.created_at&.strftime("%I:%M %p") %>
9
+ </div>
10
+ <%% end %>
@@ -0,0 +1,9 @@
1
+ <%%= turbo_stream.append "<%= message_model_name.tableize %>" do %>
2
+ <%% @<%= chat_model_name.underscore %>.messages_association.last(2).each do |<%= message_model_name.underscore %>| %>
3
+ <%%= render "<%= message_model_name.tableize %>/<%= message_model_name.underscore %>", <%= message_model_name.underscore %>: <%= message_model_name.underscore %> %>
4
+ <%% end %>
5
+ <%% end %>
6
+
7
+ <%%= turbo_stream.replace "new_<%= message_model_name.underscore %>" do %>
8
+ <%%= render "<%= message_model_name.tableize %>/form", <%= chat_model_name.underscore %>: @<%= chat_model_name.underscore %>, <%= message_model_name.underscore %>: @<%= chat_model_name.underscore %>.messages_association.build %>
9
+ <%% end %>
@@ -0,0 +1,16 @@
1
+ <tr id="<%%= dom_id <%= model_model_name.underscore %> %>">
2
+ <td><%%= <%= model_model_name.underscore %>.provider %></td>
3
+ <td><%%= <%= model_model_name.underscore %>.name %></td>
4
+ <td><%%= number_with_delimiter(<%= model_model_name.underscore %>.context_window) if <%= model_model_name.underscore %>.context_window %></td>
5
+ <td>
6
+ <%% if <%= model_model_name.underscore %>.pricing && <%= model_model_name.underscore %>.pricing['text_tokens'] && <%= model_model_name.underscore %>.pricing['text_tokens']['standard'] %>
7
+ <%% input = <%= model_model_name.underscore %>.pricing['text_tokens']['standard']['input_per_million'] %>
8
+ <%% output = <%= model_model_name.underscore %>.pricing['text_tokens']['standard']['output_per_million'] %>
9
+ <%% if input && output %>
10
+ $<%%= "%.2f" % input %> / $<%%= "%.2f" % output %>
11
+ <%% end %>
12
+ <%% end %>
13
+ </td>
14
+ <td><%%= link_to "Show", <%= model_model_name.underscore %> %></td>
15
+ <td><%%= link_to "Start <%= chat_model_name.underscore.humanize.downcase %>", new_<%= chat_model_name.underscore %>_path(model: <%= model_model_name.underscore %>.model_id) %></td>
16
+ </tr>
@@ -0,0 +1,30 @@
1
+ <p style="color: green"><%%= notice %></p>
2
+
3
+ <%% content_for :title, "<%= model_model_name.pluralize %>" %>
4
+
5
+ <h1><%= model_model_name.pluralize %></h1>
6
+
7
+ <p>
8
+ <%%= button_to "Refresh <%= model_model_name.pluralize %>", refresh_<%= model_model_name.tableize %>_path, method: :post %>
9
+ </p>
10
+
11
+ <div id="<%= model_model_name.tableize %>">
12
+ <table>
13
+ <thead>
14
+ <tr>
15
+ <th>Provider</th>
16
+ <th>Model</th>
17
+ <th>Context Window</th>
18
+ <th>$/1M tokens (In/Out)</th>
19
+ <th colspan="2"></th>
20
+ </tr>
21
+ </thead>
22
+ <tbody>
23
+ <%% @<%= model_model_name.tableize %>.values.flatten.each do |<%= model_model_name.underscore %>| %>
24
+ <%%= render <%= model_model_name.underscore %> %>
25
+ <%% end %>
26
+ </tbody>
27
+ </table>
28
+ </div>
29
+
30
+ <%%= link_to "Back to <%= chat_model_name.tableize.humanize.downcase %>", <%= chat_model_name.tableize %>_path %>
@@ -0,0 +1,18 @@
1
+ <%% content_for :title, @model.name %>
2
+
3
+ <h1><%%= @model.name %></h1>
4
+
5
+ <p><strong>ID:</strong> <%%= @model.model_id %></p>
6
+ <p><strong>Provider:</strong> <%%= @model.provider %></p>
7
+ <p><strong>Context Window:</strong> <%%= number_with_delimiter(@model.context_window) %> tokens</p>
8
+ <p><strong>Max Output:</strong> <%%= number_with_delimiter(@model.max_output_tokens) %> tokens</p>
9
+
10
+ <%% if @model.capabilities.any? %>
11
+ <p><strong>Capabilities:</strong> <%%= @model.capabilities.join(", ") %></p>
12
+ <%% end %>
13
+
14
+ <p>
15
+ <%%= link_to "Start chat with this model", new_chat_path(model: @model.model_id) %> |
16
+ <%%= link_to "All models", models_path %> |
17
+ <%%= link_to "Back to chats", chats_path %>
18
+ </p>
@@ -1,3 +1,3 @@
1
- class <%= options[:chat_model_name] %> < ApplicationRecord
1
+ class <%= chat_model_name %> < ApplicationRecord
2
2
  <%= acts_as_chat_declaration %>
3
- end
3
+ end
@@ -1,8 +1,8 @@
1
- class Create<%= options[:chat_model_name].pluralize %> < ActiveRecord::Migration<%= migration_version %>
1
+ class Create<%= chat_model_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
2
2
  def change
3
- create_table :<%= options[:chat_model_name].tableize %> do |t|
4
- t.string :model_id
3
+ create_table :<%= chat_model_name.tableize %> do |t|
4
+ t.references :<%= model_model_name.tableize.singularize %>, foreign_key: true
5
5
  t.timestamps
6
6
  end
7
7
  end
8
- end
8
+ end
@@ -1,15 +1,16 @@
1
- # Migration for creating messages table with references to chats and tool_calls
2
- class Create<%= options[:message_model_name].pluralize %> < ActiveRecord::Migration<%= migration_version %>
1
+ class Create<%= message_model_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
3
2
  def change
4
- create_table :<%= options[:message_model_name].tableize %> do |t|
5
- t.references :<%= options[:chat_model_name].tableize.singularize %>, null: false, foreign_key: true
6
- t.string :role
3
+ create_table :<%= message_model_name.tableize %> do |t|
4
+ t.references :<%= chat_model_name.tableize.singularize %>, null: false, foreign_key: true
5
+ t.string :role, null: false
7
6
  t.text :content
8
- t.string :model_id
7
+ t.references :<%= model_model_name.tableize.singularize %>, foreign_key: true
9
8
  t.integer :input_tokens
10
9
  t.integer :output_tokens
11
- t.references :<%= options[:tool_call_model_name].tableize.singularize %>
10
+ t.references :<%= tool_call_model_name.tableize.singularize %>, foreign_key: true
12
11
  t.timestamps
13
12
  end
13
+
14
+ add_index :<%= message_model_name.tableize %>, :role
14
15
  end
15
16
  end
@@ -0,0 +1,43 @@
1
+ class Create<%= model_model_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :<%= model_model_name.tableize %> do |t|
4
+ t.string :model_id, null: false
5
+ t.string :name, null: false
6
+ t.string :provider, null: false
7
+ t.string :family
8
+ t.datetime :model_created_at
9
+ t.integer :context_window
10
+ t.integer :max_output_tokens
11
+ t.date :knowledge_cutoff
12
+ <% if postgresql? %>
13
+ t.jsonb :modalities, default: {}
14
+ t.jsonb :capabilities, default: []
15
+ t.jsonb :pricing, default: {}
16
+ t.jsonb :metadata, default: {}
17
+ <% else %>
18
+ t.json :modalities, default: {}
19
+ t.json :capabilities, default: []
20
+ t.json :pricing, default: {}
21
+ t.json :metadata, default: {}
22
+ <% end %>
23
+ t.timestamps
24
+
25
+ t.index [:provider, :model_id], unique: true
26
+ t.index :provider
27
+ t.index :family
28
+ <% if postgresql? %>
29
+ t.index :capabilities, using: :gin
30
+ t.index :modalities, using: :gin
31
+ <% end %>
32
+ end
33
+
34
+ # Load models from JSON
35
+ say_with_time "Loading models from models.json" do
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"
41
+ end
42
+ end
43
+ end
@@ -1,14 +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.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_model_name.tableize %> do |t|
5
+ t.references :<%= message_model_name.tableize.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
12
+ add_index :<%= tool_call_model_name.tableize %>, :tool_call_id, unique: true
13
+ add_index :<%= tool_call_model_name.tableize %>, :name
13
14
  end
14
- end
15
+ end
@@ -1,6 +1,12 @@
1
1
  RubyLLM.configure do |config|
2
- config.openai_api_key = ENV["OPENAI_API_KEY"]
3
- config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
4
-
2
+ config.openai_api_key = Rails.application.credentials.dig(:openai_api_key)
5
3
  # config.default_model = "gpt-4.1-nano"
6
- end
4
+
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 %>"
11
+ <% end -%>
12
+ end
@@ -1,3 +1,4 @@
1
- class <%= options[:message_model_name] %> < ApplicationRecord
2
- <%= acts_as_message_declaration %>
3
- end
1
+ class <%= message_model_name %> < ApplicationRecord
2
+ <%= acts_as_message_declaration %><% unless options[:skip_active_storage] %>
3
+ has_many_attached :attachments<% end %>
4
+ end
@@ -0,0 +1,3 @@
1
+ class <%= model_model_name %> < ApplicationRecord
2
+ <%= acts_as_model_declaration %>
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
- end
3
+ end