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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.3
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino
@@ -136,17 +136,40 @@ extra_rdoc_files: []
136
136
  files:
137
137
  - LICENSE
138
138
  - README.md
139
- - lib/generators/ruby_llm/install/templates/INSTALL_INFO.md.tt
139
+ - lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb
140
+ - lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt
141
+ - lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt
142
+ - lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt
143
+ - lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt
144
+ - lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt
145
+ - lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt
146
+ - lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt
147
+ - lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt
148
+ - lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt
149
+ - lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt
150
+ - lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt
151
+ - lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt
152
+ - lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt
153
+ - lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt
154
+ - lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt
140
155
  - lib/generators/ruby_llm/install/templates/chat_model.rb.tt
141
156
  - lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt
142
157
  - lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt
158
+ - lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt
143
159
  - lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt
144
160
  - lib/generators/ruby_llm/install/templates/initializer.rb.tt
145
161
  - lib/generators/ruby_llm/install/templates/message_model.rb.tt
162
+ - lib/generators/ruby_llm/install/templates/model_model.rb.tt
146
163
  - lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt
147
164
  - lib/generators/ruby_llm/install_generator.rb
165
+ - lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt
166
+ - lib/generators/ruby_llm/upgrade_to_v1_7_generator.rb
148
167
  - lib/ruby_llm.rb
149
168
  - lib/ruby_llm/active_record/acts_as.rb
169
+ - lib/ruby_llm/active_record/acts_as_legacy.rb
170
+ - lib/ruby_llm/active_record/chat_methods.rb
171
+ - lib/ruby_llm/active_record/message_methods.rb
172
+ - lib/ruby_llm/active_record/model_methods.rb
150
173
  - lib/ruby_llm/aliases.json
151
174
  - lib/ruby_llm/aliases.rb
152
175
  - lib/ruby_llm/attachment.rb
@@ -205,6 +228,7 @@ files:
205
228
  - lib/ruby_llm/providers/gemini/tools.rb
206
229
  - lib/ruby_llm/providers/gpustack.rb
207
230
  - lib/ruby_llm/providers/gpustack/chat.rb
231
+ - lib/ruby_llm/providers/gpustack/media.rb
208
232
  - lib/ruby_llm/providers/gpustack/models.rb
209
233
  - lib/ruby_llm/providers/mistral.rb
210
234
  - lib/ruby_llm/providers/mistral/capabilities.rb
@@ -214,6 +238,7 @@ files:
214
238
  - lib/ruby_llm/providers/ollama.rb
215
239
  - lib/ruby_llm/providers/ollama/chat.rb
216
240
  - lib/ruby_llm/providers/ollama/media.rb
241
+ - lib/ruby_llm/providers/ollama/models.rb
217
242
  - lib/ruby_llm/providers/openai.rb
218
243
  - lib/ruby_llm/providers/openai/capabilities.rb
219
244
  - lib/ruby_llm/providers/openai/chat.rb
@@ -229,6 +254,11 @@ files:
229
254
  - lib/ruby_llm/providers/perplexity/capabilities.rb
230
255
  - lib/ruby_llm/providers/perplexity/chat.rb
231
256
  - lib/ruby_llm/providers/perplexity/models.rb
257
+ - lib/ruby_llm/providers/vertexai.rb
258
+ - lib/ruby_llm/providers/vertexai/chat.rb
259
+ - lib/ruby_llm/providers/vertexai/embeddings.rb
260
+ - lib/ruby_llm/providers/vertexai/models.rb
261
+ - lib/ruby_llm/providers/vertexai/streaming.rb
232
262
  - lib/ruby_llm/railtie.rb
233
263
  - lib/ruby_llm/stream_accumulator.rb
234
264
  - lib/ruby_llm/streaming.rb
@@ -236,10 +266,9 @@ files:
236
266
  - lib/ruby_llm/tool_call.rb
237
267
  - lib/ruby_llm/utils.rb
238
268
  - lib/ruby_llm/version.rb
239
- - lib/tasks/aliases.rake
240
- - lib/tasks/models_docs.rake
241
- - lib/tasks/models_update.rake
269
+ - lib/tasks/models.rake
242
270
  - lib/tasks/release.rake
271
+ - lib/tasks/ruby_llm.rake
243
272
  - lib/tasks/vcr.rake
244
273
  homepage: https://rubyllm.com
245
274
  licenses:
@@ -251,6 +280,9 @@ metadata:
251
280
  documentation_uri: https://rubyllm.com
252
281
  bug_tracker_uri: https://github.com/crmne/ruby_llm/issues
253
282
  rubygems_mfa_required: 'true'
283
+ post_install_message: |
284
+ Upgrading from RubyLLM <= 1.6.x? Check the upgrade guide for new features and migration instructions
285
+ --> https://rubyllm.com/upgrading-to-1-7/
254
286
  rdoc_options: []
255
287
  require_paths:
256
288
  - lib
@@ -1,108 +0,0 @@
1
- # RubyLLM Rails Setup Complete!
2
-
3
- Thanks for installing RubyLLM in your Rails application. Here's what was created:
4
-
5
- ## Models
6
-
7
- - `<%= options[:chat_model_name] %>` - Stores chat sessions and their associated model ID
8
- - `<%= options[:message_model_name] %>` - Stores individual messages in a chat
9
- - `<%= options[:tool_call_model_name] %>` - Stores tool calls made by language models
10
-
11
- **Note:** Do not add `validates :content, presence: true` to your Message model - RubyLLM creates empty assistant messages before API calls for streaming support.
12
-
13
- ## Configuration Options
14
-
15
- The generator supports the following options to customize model names:
16
-
17
- ```bash
18
- rails generate ruby_llm:install \
19
- --chat-model-name=Conversation \
20
- --message-model-name=ChatMessage \
21
- --tool-call-model-name=FunctionCall
22
- ```
23
-
24
- 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.
25
-
26
- ## Next Steps
27
-
28
- 1. **Run migrations:**
29
- ```bash
30
- rails db:migrate
31
- ```
32
-
33
- **Database Note:** The migrations use `jsonb` for PostgreSQL and `json` for MySQL/SQLite automatically.
34
-
35
- 2. **Set your API keys** in `config/initializers/ruby_llm.rb` or using environment variables:
36
- ```ruby
37
- # config/initializers/ruby_llm.rb
38
- RubyLLM.configure do |config|
39
- config.openai_api_key = ENV['OPENAI_API_KEY']
40
- config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
41
- config.gemini_api_key = ENV['GEMINI_API_KEY']
42
- # ... add other providers as needed
43
- end
44
- ```
45
-
46
- 3. **Start using RubyLLM in your code:**
47
- ```ruby
48
- # Basic usage
49
- chat = <%= options[:chat_model_name] %>.create!(model_id: 'gpt-4.1-nano')
50
- response = chat.ask("What is Ruby on Rails?")
51
-
52
- # With file attachments (requires ActiveStorage setup)
53
- chat.ask("What's in this file?", with: "report.pdf")
54
- chat.ask("Analyze these files", with: ["image.jpg", "data.csv", "notes.txt"])
55
- ```
56
-
57
- 4. **For streaming responses** with Hotwire/Turbo:
58
- ```ruby
59
- # app/models/<%= options[:message_model_name].underscore %>.rb
60
- class <%= options[:message_model_name] %> < ApplicationRecord
61
- acts_as_message
62
-
63
- # Helper to broadcast chunks during streaming
64
- def broadcast_append_chunk(chunk_content)
65
- broadcast_append_to [ chat, "messages" ],
66
- target: dom_id(self, "content"),
67
- html: chunk_content
68
- end
69
- end
70
-
71
- # app/jobs/chat_stream_job.rb
72
- class ChatStreamJob < ApplicationJob
73
- def perform(chat_id, user_content)
74
- chat = <%= options[:chat_model_name] %>.find(chat_id)
75
- chat.ask(user_content) do |chunk|
76
- assistant_message = chat.messages.last
77
- if chunk.content && assistant_message
78
- assistant_message.broadcast_append_chunk(chunk.content)
79
- end
80
- end
81
- end
82
- end
83
-
84
- # In your controller
85
- ChatStreamJob.perform_later(@chat.id, params[:content])
86
- ```
87
-
88
- ## Optional: ActiveStorage for Attachments
89
-
90
- If you want to use file attachments (PDFs, images, etc.), set up ActiveStorage:
91
-
92
- ```bash
93
- rails active_storage:install
94
- rails db:migrate
95
- ```
96
-
97
- Then add to your Message model:
98
- ```ruby
99
- class <%= options[:message_model_name] %> < ApplicationRecord
100
- acts_as_message
101
- has_many_attached :attachments
102
- end
103
- ```
104
-
105
- ## Learn More
106
-
107
- - See the [Rails Integration Guide](https://rubyllm.com/guides/rails) for detailed examples
108
- - Visit the [RubyLLM Documentation](https://rubyllm.com) for full API reference
@@ -1,205 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- namespace :aliases do # rubocop:disable Metrics/BlockLength
6
- desc 'Generate aliases.json from models in the registry'
7
- task :generate do # rubocop:disable Metrics/BlockLength
8
- require 'ruby_llm'
9
-
10
- # Group models by provider
11
- models = Hash.new { |h, k| h[k] = [] }
12
-
13
- RubyLLM.models.all.each do |model|
14
- models[model.provider] << model.id
15
- end
16
-
17
- aliases = {}
18
-
19
- # OpenAI models
20
- models['openai'].each do |model|
21
- openrouter_model = "openai/#{model}"
22
- next unless models['openrouter'].include?(openrouter_model)
23
-
24
- alias_key = model.gsub('-latest', '')
25
- aliases[alias_key] = {
26
- 'openai' => model,
27
- 'openrouter' => openrouter_model
28
- }
29
- end
30
-
31
- anthropic_latest = group_anthropic_models_by_base_name(models['anthropic'])
32
-
33
- anthropic_latest.each do |base_name, latest_model|
34
- openrouter_variants = [
35
- "anthropic/#{base_name}", # anthropic/claude-3-5-sonnet
36
- "anthropic/#{base_name.gsub(/-(\d)/, '.\1')}", # anthropic/claude-3.5-sonnet
37
- "anthropic/#{base_name.gsub(/claude-(\d+)-(\d+)/, 'claude-\1.\2')}", # claude-3-5 -> claude-3.5
38
- "anthropic/#{base_name.gsub(/(\d+)-(\d+)/, '\1.\2')}" # any X-Y -> X.Y pattern
39
- ]
40
-
41
- openrouter_model = openrouter_variants.find { |v| models['openrouter'].include?(v) }
42
-
43
- bedrock_model = find_best_bedrock_model(latest_model, models['bedrock'])
44
-
45
- next unless openrouter_model || bedrock_model || models['anthropic'].include?(latest_model)
46
-
47
- aliases[base_name] = {
48
- 'anthropic' => latest_model
49
- }
50
-
51
- aliases[base_name]['openrouter'] = openrouter_model if openrouter_model
52
- aliases[base_name]['bedrock'] = bedrock_model if bedrock_model
53
- end
54
-
55
- models['bedrock'].each do |bedrock_model|
56
- next unless bedrock_model.start_with?('anthropic.')
57
-
58
- next unless bedrock_model =~ /anthropic\.(claude-[\d\.]+-[a-z]+)/
59
-
60
- base_name = Regexp.last_match(1)
61
- anthropic_name = base_name.tr('.', '-')
62
-
63
- next if aliases[anthropic_name]
64
-
65
- openrouter_variants = [
66
- "anthropic/#{anthropic_name}",
67
- "anthropic/#{base_name}" # Keep the dots
68
- ]
69
-
70
- openrouter_model = openrouter_variants.find { |v| models['openrouter'].include?(v) }
71
-
72
- aliases[anthropic_name] = {
73
- 'bedrock' => bedrock_model
74
- }
75
-
76
- aliases[anthropic_name]['anthropic'] = anthropic_name if models['anthropic'].include?(anthropic_name)
77
- aliases[anthropic_name]['openrouter'] = openrouter_model if openrouter_model
78
- end
79
-
80
- models['gemini'].each do |model|
81
- openrouter_variants = [
82
- "google/#{model}",
83
- "google/#{model.gsub('gemini-', 'gemini-').tr('.', '-')}",
84
- "google/#{model.gsub('gemini-', 'gemini-')}"
85
- ]
86
-
87
- openrouter_model = openrouter_variants.find { |v| models['openrouter'].include?(v) }
88
- next unless openrouter_model
89
-
90
- alias_key = model.gsub('-latest', '')
91
- aliases[alias_key] = {
92
- 'gemini' => model,
93
- 'openrouter' => openrouter_model
94
- }
95
- end
96
-
97
- models['deepseek'].each do |model|
98
- openrouter_model = "deepseek/#{model}"
99
- next unless models['openrouter'].include?(openrouter_model)
100
-
101
- alias_key = model.gsub('-latest', '')
102
- aliases[alias_key] = {
103
- 'deepseek' => model,
104
- 'openrouter' => openrouter_model
105
- }
106
- end
107
-
108
- sorted_aliases = aliases.sort.to_h
109
- File.write(RubyLLM::Aliases.aliases_file, JSON.pretty_generate(sorted_aliases))
110
-
111
- puts "Generated #{sorted_aliases.size} aliases"
112
- end
113
-
114
- def group_anthropic_models_by_base_name(anthropic_models) # rubocop:disable Rake/MethodDefinitionInTask
115
- grouped = Hash.new { |h, k| h[k] = [] }
116
-
117
- anthropic_models.each do |model|
118
- base_name = extract_base_name(model)
119
- grouped[base_name] << model
120
- end
121
-
122
- latest_models = {}
123
- grouped.each do |base_name, model_list|
124
- if model_list.size == 1
125
- latest_models[base_name] = model_list.first
126
- else
127
- latest_model = model_list.max_by { |model| extract_date_from_model(model) }
128
- latest_models[base_name] = latest_model
129
- end
130
- end
131
-
132
- latest_models
133
- end
134
-
135
- def extract_base_name(model) # rubocop:disable Rake/MethodDefinitionInTask
136
- if model =~ /^(.+)-(\d{8})$/
137
- Regexp.last_match(1)
138
- else
139
- model
140
- end
141
- end
142
-
143
- def extract_date_from_model(model) # rubocop:disable Rake/MethodDefinitionInTask
144
- if model =~ /-(\d{8})$/
145
- Regexp.last_match(1)
146
- else
147
- '00000000'
148
- end
149
- end
150
-
151
- def find_best_bedrock_model(anthropic_model, bedrock_models) # rubocop:disable Metrics/PerceivedComplexity,Rake/MethodDefinitionInTask
152
- base_pattern = case anthropic_model
153
- when 'claude-2.0', 'claude-2'
154
- 'claude-v2'
155
- when 'claude-2.1'
156
- 'claude-v2:1'
157
- when 'claude-instant-v1', 'claude-instant'
158
- 'claude-instant'
159
- else
160
- extract_base_name(anthropic_model)
161
- end
162
-
163
- matching_models = bedrock_models.select do |bedrock_model|
164
- model_without_prefix = bedrock_model.sub(/^(?:us\.)?anthropic\./, '')
165
- model_without_prefix.start_with?(base_pattern)
166
- end
167
-
168
- return nil if matching_models.empty?
169
-
170
- begin
171
- model_info = RubyLLM.models.find(anthropic_model)
172
- target_context = model_info.context_window
173
- rescue StandardError
174
- target_context = nil
175
- end
176
-
177
- if target_context
178
- target_k = target_context / 1000
179
-
180
- with_context = matching_models.select do |m|
181
- m.include?(":#{target_k}k") || m.include?(":0:#{target_k}k")
182
- end
183
-
184
- return with_context.first if with_context.any?
185
- end
186
-
187
- matching_models.min_by do |model|
188
- context_priority = if model =~ /:(?:\d+:)?(\d+)k/
189
- -Regexp.last_match(1).to_i
190
- else
191
- 0
192
- end
193
-
194
- version_priority = if model =~ /-v(\d+):/
195
- -Regexp.last_match(1).to_i
196
- else
197
- 0
198
- end
199
-
200
- # Prefer models with explicit context windows
201
- has_context_priority = model.include?('k') ? -1 : 0
202
- [has_context_priority, context_priority, version_priority]
203
- end
204
- end
205
- end
@@ -1,214 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'dotenv/load'
4
- require 'fileutils'
5
-
6
- namespace :models do
7
- desc 'Generate available models documentation'
8
- task :docs do
9
- FileUtils.mkdir_p('docs')
10
- output = generate_models_markdown
11
- File.write('docs/_reference/available-models.md', output)
12
- puts 'Generated docs/_reference/available-models.md'
13
- end
14
- end
15
-
16
- def generate_models_markdown
17
- <<~MARKDOWN
18
- ---
19
- layout: default
20
- title: Available Models
21
- nav_order: 1
22
- description: Browse hundreds of AI models from every major provider. Always up-to-date, automatically generated.
23
- redirect_from:
24
- - /guides/available-models
25
- ---
26
-
27
- # {{ page.title }}
28
- {: .no_toc }
29
-
30
- {{ page.description }}
31
- {: .fs-6 .fw-300 }
32
-
33
- ## Table of contents
34
- {: .no_toc .text-delta }
35
-
36
- 1. TOC
37
- {:toc}
38
-
39
- ---
40
-
41
- After reading this guide, you will know:
42
-
43
- * How RubyLLM's model registry works and where data comes from
44
- * How to find models by provider, capability, or purpose
45
- * What information is available for each model
46
- * How to use model aliases for simpler configuration
47
-
48
- ## How Model Data Works
49
-
50
- RubyLLM's model registry combines data from multiple sources:
51
-
52
- - **OpenAI, Anthropic, DeepSeek, Gemini**: Data from [Parsera](https://api.parsera.org/v1/llm-specs)
53
- - **OpenRouter**: Direct from OpenRouter's API
54
- - **Other providers**: Defined in `capabilities.rb` files
55
-
56
- ## Contributing Model Updates
57
-
58
- **For major providers** (OpenAI, Anthropic, DeepSeek, Gemini): File issues with [Parsera](https://github.com/parsera-labs/api-llm-specs/issues) for public model data corrections.
59
-
60
- **For other providers**: Edit `lib/ruby_llm/providers/<provider>/capabilities.rb` then run `rake models:update`.
61
-
62
- See the [Contributing Guide](https://github.com/crmne/ruby_llm/blob/main/CONTRIBUTING.md) for details.
63
-
64
- ## Last Updated
65
- {: .d-inline-block }
66
-
67
- #{Time.now.utc.strftime('%Y-%m-%d')}
68
- {: .label .label-green }
69
-
70
- ## Models by Provider
71
-
72
- #{generate_provider_sections}
73
-
74
- ## Models by Capability
75
-
76
- #{generate_capability_sections}
77
-
78
- ## Models by Modality
79
-
80
- #{generate_modality_sections}
81
- MARKDOWN
82
- end
83
-
84
- def generate_provider_sections
85
- RubyLLM::Provider.providers.filter_map do |provider, provider_class|
86
- models = RubyLLM.models.by_provider(provider)
87
- next if models.none?
88
-
89
- <<~PROVIDER
90
- ### #{provider_class.name} (#{models.count})
91
-
92
- #{models_table(models)}
93
- PROVIDER
94
- end.join("\n\n")
95
- end
96
-
97
- def generate_capability_sections
98
- capabilities = {
99
- 'Function Calling' => RubyLLM.models.select(&:function_calling?),
100
- 'Structured Output' => RubyLLM.models.select(&:structured_output?),
101
- 'Streaming' => RubyLLM.models.select { |m| m.capabilities.include?('streaming') },
102
- # 'Reasoning' => RubyLLM.models.select { |m| m.capabilities.include?('reasoning') },
103
- 'Batch Processing' => RubyLLM.models.select { |m| m.capabilities.include?('batch') }
104
- }
105
-
106
- capabilities.filter_map do |capability, models|
107
- next if models.none?
108
-
109
- <<~CAPABILITY
110
- ### #{capability} (#{models.count})
111
-
112
- #{models_table(models)}
113
- CAPABILITY
114
- end.join("\n\n")
115
- end
116
-
117
- def generate_modality_sections # rubocop:disable Metrics/PerceivedComplexity
118
- sections = []
119
-
120
- vision_models = RubyLLM.models.select { |m| (m.modalities.input || []).include?('image') }
121
- if vision_models.any?
122
- sections << <<~SECTION
123
- ### Vision Models (#{vision_models.count})
124
-
125
- Models that can process images:
126
-
127
- #{models_table(vision_models)}
128
- SECTION
129
- end
130
-
131
- audio_models = RubyLLM.models.select { |m| (m.modalities.input || []).include?('audio') }
132
- if audio_models.any?
133
- sections << <<~SECTION
134
- ### Audio Input Models (#{audio_models.count})
135
-
136
- Models that can process audio:
137
-
138
- #{models_table(audio_models)}
139
- SECTION
140
- end
141
-
142
- pdf_models = RubyLLM.models.select { |m| (m.modalities.input || []).include?('pdf') }
143
- if pdf_models.any?
144
- sections << <<~SECTION
145
- ### PDF Models (#{pdf_models.count})
146
-
147
- Models that can process PDF documents:
148
-
149
- #{models_table(pdf_models)}
150
- SECTION
151
- end
152
-
153
- embedding_models = RubyLLM.models.select { |m| (m.modalities.output || []).include?('embeddings') }
154
- if embedding_models.any?
155
- sections << <<~SECTION
156
- ### Embedding Models (#{embedding_models.count})
157
-
158
- Models that generate embeddings:
159
-
160
- #{models_table(embedding_models)}
161
- SECTION
162
- end
163
-
164
- sections.join("\n\n")
165
- end
166
-
167
- def models_table(models)
168
- return '*No models found*' if models.none?
169
-
170
- headers = ['Model', 'Provider', 'Context', 'Max Output', 'Standard Pricing (per 1M tokens)']
171
- alignment = [':--', ':--', '--:', '--:', ':--']
172
-
173
- rows = models.sort_by { |m| [m.provider, m.name] }.map do |model|
174
- pricing = standard_pricing_display(model)
175
-
176
- [
177
- model.id,
178
- model.provider,
179
- model.context_window || '-',
180
- model.max_output_tokens || '-',
181
- pricing
182
- ]
183
- end
184
-
185
- table = []
186
- table << "| #{headers.join(' | ')} |"
187
- table << "| #{alignment.join(' | ')} |"
188
-
189
- rows.each do |row|
190
- table << "| #{row.join(' | ')} |"
191
- end
192
-
193
- table.join("\n")
194
- end
195
-
196
- def standard_pricing_display(model)
197
- pricing_data = model.pricing.to_h[:text_tokens]&.dig(:standard) || {}
198
-
199
- if pricing_data.any?
200
- parts = []
201
-
202
- parts << "In: $#{format('%.2f', pricing_data[:input_per_million])}" if pricing_data[:input_per_million]
203
-
204
- parts << "Out: $#{format('%.2f', pricing_data[:output_per_million])}" if pricing_data[:output_per_million]
205
-
206
- if pricing_data[:cached_input_per_million]
207
- parts << "Cache: $#{format('%.2f', pricing_data[:cached_input_per_million])}"
208
- end
209
-
210
- return parts.join(', ') if parts.any?
211
- end
212
-
213
- '-'
214
- end