ruby_llm 1.13.2 → 1.14.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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -7
  3. data/lib/generators/ruby_llm/agent/agent_generator.rb +36 -0
  4. data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +6 -0
  5. data/lib/generators/ruby_llm/agent/templates/instructions.txt.erb.tt +0 -0
  6. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +110 -41
  7. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +14 -15
  8. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +8 -11
  9. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +2 -2
  10. data/lib/generators/ruby_llm/chat_ui/templates/helpers/messages_helper.rb.tt +25 -0
  11. data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +1 -1
  12. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +16 -0
  13. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +31 -0
  14. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +31 -0
  15. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/new.html.erb.tt +9 -0
  16. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +27 -0
  17. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +14 -0
  18. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_content.html.erb.tt +1 -0
  19. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +13 -0
  20. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +23 -0
  21. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +10 -0
  22. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +2 -0
  23. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +4 -0
  24. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +14 -0
  25. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +13 -0
  26. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +21 -0
  27. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +17 -0
  28. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +40 -0
  29. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +27 -0
  30. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +2 -2
  31. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +2 -2
  32. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +19 -7
  33. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +1 -1
  34. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +5 -3
  35. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +9 -0
  36. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_content.html.erb.tt +1 -1
  37. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_error.html.erb.tt +8 -0
  38. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +1 -1
  39. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +6 -0
  40. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +2 -0
  41. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +4 -7
  42. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +9 -0
  43. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +5 -7
  44. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +8 -0
  45. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +16 -0
  46. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +11 -12
  47. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +27 -17
  48. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +3 -4
  49. data/lib/generators/ruby_llm/generator_helpers.rb +33 -17
  50. data/lib/generators/ruby_llm/install/install_generator.rb +21 -18
  51. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +3 -4
  52. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +1 -1
  53. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +2 -2
  54. data/lib/generators/ruby_llm/schema/schema_generator.rb +26 -0
  55. data/lib/generators/ruby_llm/schema/templates/schema.rb.tt +2 -0
  56. data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +9 -0
  57. data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +13 -0
  58. data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +13 -0
  59. data/lib/generators/ruby_llm/tool/tool_generator.rb +96 -0
  60. data/lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +1 -1
  61. data/lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +7 -0
  62. data/lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +49 -0
  63. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +2 -4
  64. data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +1 -1
  65. data/lib/ruby_llm/active_record/acts_as.rb +2 -0
  66. data/lib/ruby_llm/active_record/acts_as_legacy.rb +2 -0
  67. data/lib/ruby_llm/active_record/chat_methods.rb +13 -6
  68. data/lib/ruby_llm/active_record/message_methods.rb +17 -0
  69. data/lib/ruby_llm/active_record/model_methods.rb +1 -1
  70. data/lib/ruby_llm/active_record/payload_helpers.rb +26 -0
  71. data/lib/ruby_llm/active_record/tool_call_methods.rb +15 -0
  72. data/lib/ruby_llm/agent.rb +11 -0
  73. data/lib/ruby_llm/aliases.json +34 -15
  74. data/lib/ruby_llm/attachment.rb +3 -0
  75. data/lib/ruby_llm/configuration.rb +54 -73
  76. data/lib/ruby_llm/connection.rb +1 -3
  77. data/lib/ruby_llm/error.rb +5 -0
  78. data/lib/ruby_llm/model/info.rb +14 -12
  79. data/lib/ruby_llm/models.json +7446 -10126
  80. data/lib/ruby_llm/models.rb +10 -3
  81. data/lib/ruby_llm/provider.rb +5 -0
  82. data/lib/ruby_llm/providers/anthropic/capabilities.rb +1 -133
  83. data/lib/ruby_llm/providers/anthropic/models.rb +2 -8
  84. data/lib/ruby_llm/providers/anthropic.rb +4 -0
  85. data/lib/ruby_llm/providers/azure.rb +4 -0
  86. data/lib/ruby_llm/providers/bedrock.rb +4 -0
  87. data/lib/ruby_llm/providers/deepseek/capabilities.rb +1 -119
  88. data/lib/ruby_llm/providers/deepseek.rb +4 -0
  89. data/lib/ruby_llm/providers/gemini/capabilities.rb +45 -215
  90. data/lib/ruby_llm/providers/gemini/models.rb +2 -4
  91. data/lib/ruby_llm/providers/gemini.rb +4 -0
  92. data/lib/ruby_llm/providers/gpustack.rb +4 -0
  93. data/lib/ruby_llm/providers/mistral.rb +4 -0
  94. data/lib/ruby_llm/providers/ollama.rb +4 -0
  95. data/lib/ruby_llm/providers/openai/capabilities.rb +95 -203
  96. data/lib/ruby_llm/providers/openai/models.rb +2 -4
  97. data/lib/ruby_llm/providers/openai.rb +10 -0
  98. data/lib/ruby_llm/providers/openrouter/images.rb +1 -1
  99. data/lib/ruby_llm/providers/openrouter.rb +4 -0
  100. data/lib/ruby_llm/providers/perplexity/capabilities.rb +34 -99
  101. data/lib/ruby_llm/providers/perplexity/models.rb +12 -14
  102. data/lib/ruby_llm/providers/perplexity.rb +4 -0
  103. data/lib/ruby_llm/providers/vertexai.rb +4 -0
  104. data/lib/ruby_llm/providers/xai.rb +4 -0
  105. data/lib/ruby_llm/version.rb +1 -1
  106. data/lib/tasks/release.rake +1 -1
  107. data/lib/tasks/ruby_llm.rake +6 -5
  108. data/lib/tasks/vcr.rake +1 -1
  109. metadata +49 -11
  110. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +0 -13
@@ -47,7 +47,8 @@ module RubyLLM
47
47
 
48
48
  def read_from_json(file = RubyLLM.config.model_registry_file)
49
49
  data = File.exist?(file) ? File.read(file) : '[]'
50
- JSON.parse(data, symbolize_names: true).map { |model| Model::Info.new(model) }
50
+ models = JSON.parse(data, symbolize_names: true).map { |model| Model::Info.new(model) }
51
+ filter_models(models)
51
52
  rescue JSON::ParserError
52
53
  []
53
54
  end
@@ -232,7 +233,13 @@ module RubyLLM
232
233
  end
233
234
  end
234
235
 
235
- models.sort_by { |m| [m.provider, m.id] }
236
+ filter_models(models).sort_by { |m| [m.provider, m.id] }
237
+ end
238
+
239
+ def filter_models(models)
240
+ models.reject do |model|
241
+ model.provider.to_s == 'vertexai' && model.id.to_s.include?('/')
242
+ end
236
243
  end
237
244
 
238
245
  def find_models_dev_model(key, models_dev_by_key)
@@ -401,7 +408,7 @@ module RubyLLM
401
408
  end
402
409
 
403
410
  def initialize(models = nil)
404
- @models = models || self.class.load_models
411
+ @models = self.class.filter_models(models || self.class.load_models)
405
412
  end
406
413
 
407
414
  def load_from_json!(file = RubyLLM.config.model_registry_file)
@@ -164,6 +164,10 @@ module RubyLLM
164
164
  []
165
165
  end
166
166
 
167
+ def configuration_options
168
+ []
169
+ end
170
+
167
171
  def local?
168
172
  false
169
173
  end
@@ -182,6 +186,7 @@ module RubyLLM
182
186
 
183
187
  def register(name, provider_class)
184
188
  providers[name.to_sym] = provider_class
189
+ RubyLLM::Configuration.register_provider_options(provider_class.configuration_options)
185
190
  end
186
191
 
187
192
  def resolve(name)
@@ -3,37 +3,10 @@
3
3
  module RubyLLM
4
4
  module Providers
5
5
  class Anthropic
6
- # Determines capabilities and pricing for Anthropic models
6
+ # Provider-level capability checks used outside the model registry.
7
7
  module Capabilities
8
8
  module_function
9
9
 
10
- def determine_context_window(_model_id)
11
- 200_000
12
- end
13
-
14
- def determine_max_tokens(model_id)
15
- case model_id
16
- when /claude-3-7-sonnet/, /claude-3-5/ then 8_192
17
- else 4_096
18
- end
19
- end
20
-
21
- def get_input_price(model_id)
22
- PRICES.dig(model_family(model_id), :input) || default_input_price
23
- end
24
-
25
- def get_output_price(model_id)
26
- PRICES.dig(model_family(model_id), :output) || default_output_price
27
- end
28
-
29
- def supports_vision?(model_id)
30
- !model_id.match?(/claude-[12]/)
31
- end
32
-
33
- def supports_functions?(model_id)
34
- !model_id.match?(/claude-[12]/)
35
- end
36
-
37
10
  def supports_tool_choice?(_model_id)
38
11
  true
39
12
  end
@@ -41,111 +14,6 @@ module RubyLLM
41
14
  def supports_tool_parallel_control?(_model_id)
42
15
  true
43
16
  end
44
-
45
- def supports_json_mode?(model_id)
46
- !model_id.match?(/claude-[12]/)
47
- end
48
-
49
- def supports_structured_output?(model_id)
50
- match = model_id.match(/claude-(?:sonnet|opus|haiku)-(\d+)-(\d+)/)
51
- return false unless match
52
-
53
- major = match[1].to_i
54
- minor = match[2].to_i
55
- major > 4 || (major == 4 && minor >= 5)
56
- end
57
-
58
- def supports_extended_thinking?(model_id)
59
- model_id.match?(/claude-3-7-sonnet/)
60
- end
61
-
62
- def model_family(model_id)
63
- case model_id
64
- when /claude-3-7-sonnet/ then 'claude-3-7-sonnet'
65
- when /claude-3-5-sonnet/ then 'claude-3-5-sonnet'
66
- when /claude-3-5-haiku/ then 'claude-3-5-haiku'
67
- when /claude-3-opus/ then 'claude-3-opus'
68
- when /claude-3-sonnet/ then 'claude-3-sonnet'
69
- when /claude-3-haiku/ then 'claude-3-haiku'
70
- else 'claude-2'
71
- end
72
- end
73
-
74
- def model_type(_)
75
- 'chat'
76
- end
77
-
78
- PRICES = {
79
- 'claude-3-7-sonnet': { input: 3.0, output: 15.0 },
80
- 'claude-3-5-sonnet': { input: 3.0, output: 15.0 },
81
- 'claude-3-5-haiku': { input: 0.80, output: 4.0 },
82
- 'claude-3-opus': { input: 15.0, output: 75.0 },
83
- 'claude-3-haiku': { input: 0.25, output: 1.25 },
84
- 'claude-2': { input: 3.0, output: 15.0 }
85
- }.freeze
86
-
87
- def default_input_price
88
- 3.0
89
- end
90
-
91
- def default_output_price
92
- 15.0
93
- end
94
-
95
- def modalities_for(model_id)
96
- modalities = {
97
- input: ['text'],
98
- output: ['text']
99
- }
100
-
101
- unless model_id.match?(/claude-[12]/)
102
- modalities[:input] << 'image'
103
- modalities[:input] << 'pdf'
104
- end
105
-
106
- modalities
107
- end
108
-
109
- def capabilities_for(model_id)
110
- capabilities = ['streaming']
111
-
112
- unless model_id.match?(/claude-[12]/)
113
- capabilities << 'function_calling'
114
- capabilities << 'batch'
115
- end
116
-
117
- capabilities << 'structured_output' if supports_structured_output?(model_id)
118
- capabilities << 'reasoning' if model_id.match?(/claude-3-7-sonnet|claude-(?:sonnet|opus|haiku)-4/)
119
- capabilities << 'citations' if model_id.match?(/claude-3\.5|claude-3-7/)
120
- capabilities
121
- end
122
-
123
- def pricing_for(model_id)
124
- family = model_family(model_id)
125
- prices = PRICES.fetch(family.to_sym, { input: default_input_price, output: default_output_price })
126
-
127
- standard_pricing = {
128
- input_per_million: prices[:input],
129
- output_per_million: prices[:output]
130
- }
131
-
132
- batch_pricing = {
133
- input_per_million: prices[:input] * 0.5,
134
- output_per_million: prices[:output] * 0.5
135
- }
136
-
137
- if model_id.match?(/claude-3-7/)
138
- standard_pricing[:reasoning_output_per_million] = prices[:output] * 2.5
139
- batch_pricing[:reasoning_output_per_million] = prices[:output] * 1.25
140
- end
141
-
142
- {
143
- text_tokens: {
144
- standard: standard_pricing,
145
- batch: batch_pricing
146
- }
147
- }
148
- end
149
17
  end
150
18
  end
151
19
  end
@@ -11,21 +11,15 @@ module RubyLLM
11
11
  'v1/models'
12
12
  end
13
13
 
14
- def parse_list_models_response(response, slug, capabilities)
14
+ def parse_list_models_response(response, slug, _capabilities)
15
15
  Array(response.body['data']).map do |model_data|
16
16
  model_id = model_data['id']
17
17
 
18
18
  Model::Info.new(
19
19
  id: model_id,
20
- name: model_data['display_name'],
20
+ name: model_data['display_name'] || model_id,
21
21
  provider: slug,
22
- family: capabilities.model_family(model_id),
23
22
  created_at: Time.parse(model_data['created_at']),
24
- context_window: capabilities.determine_context_window(model_id),
25
- max_output_tokens: capabilities.determine_max_tokens(model_id),
26
- modalities: capabilities.modalities_for(model_id),
27
- capabilities: capabilities.capabilities_for(model_id),
28
- pricing: capabilities.pricing_for(model_id),
29
23
  metadata: {}
30
24
  )
31
25
  end
@@ -27,6 +27,10 @@ module RubyLLM
27
27
  Anthropic::Capabilities
28
28
  end
29
29
 
30
+ def configuration_options
31
+ %i[anthropic_api_key anthropic_api_base]
32
+ end
33
+
30
34
  def configuration_requirements
31
35
  %i[anthropic_api_key]
32
36
  end
@@ -44,6 +44,10 @@ module RubyLLM
44
44
  end
45
45
 
46
46
  class << self
47
+ def configuration_options
48
+ %i[azure_api_base azure_api_key azure_ai_auth_token]
49
+ end
50
+
47
51
  def configuration_requirements
48
52
  %i[azure_api_base]
49
53
  end
@@ -53,6 +53,10 @@ module RubyLLM
53
53
  end
54
54
 
55
55
  class << self
56
+ def configuration_options
57
+ %i[bedrock_api_key bedrock_secret_key bedrock_region bedrock_session_token]
58
+ end
59
+
56
60
  def configuration_requirements
57
61
  %i[bedrock_api_key bedrock_secret_key bedrock_region]
58
62
  end
@@ -3,44 +3,10 @@
3
3
  module RubyLLM
4
4
  module Providers
5
5
  class DeepSeek
6
- # Determines capabilities and pricing for DeepSeek models
6
+ # Provider-level capability checks used outside the model registry.
7
7
  module Capabilities
8
8
  module_function
9
9
 
10
- def context_window_for(model_id)
11
- case model_id
12
- when /deepseek-(?:chat|reasoner)/ then 64_000
13
- else 32_768
14
- end
15
- end
16
-
17
- def max_tokens_for(model_id)
18
- case model_id
19
- when /deepseek-(?:chat|reasoner)/ then 8_192
20
- else 4_096
21
- end
22
- end
23
-
24
- def input_price_for(model_id)
25
- PRICES.dig(model_family(model_id), :input_miss) || default_input_price
26
- end
27
-
28
- def output_price_for(model_id)
29
- PRICES.dig(model_family(model_id), :output) || default_output_price
30
- end
31
-
32
- def cache_hit_price_for(model_id)
33
- PRICES.dig(model_family(model_id), :input_hit) || default_cache_hit_price
34
- end
35
-
36
- def supports_vision?(_model_id)
37
- false
38
- end
39
-
40
- def supports_functions?(model_id)
41
- model_id.match?(/deepseek-chat/)
42
- end
43
-
44
10
  def supports_tool_choice?(_model_id)
45
11
  true
46
12
  end
@@ -48,90 +14,6 @@ module RubyLLM
48
14
  def supports_tool_parallel_control?(_model_id)
49
15
  false
50
16
  end
51
-
52
- def supports_json_mode?(_model_id)
53
- false
54
- end
55
-
56
- def format_display_name(model_id)
57
- case model_id
58
- when 'deepseek-chat' then 'DeepSeek V3'
59
- when 'deepseek-reasoner' then 'DeepSeek R1'
60
- else
61
- model_id.split('-')
62
- .map(&:capitalize)
63
- .join(' ')
64
- end
65
- end
66
-
67
- def model_type(_model_id)
68
- 'chat'
69
- end
70
-
71
- def model_family(model_id)
72
- case model_id
73
- when /deepseek-reasoner/ then :reasoner
74
- else :chat
75
- end
76
- end
77
-
78
- PRICES = {
79
- chat: {
80
- input_hit: 0.07,
81
- input_miss: 0.27,
82
- output: 1.10
83
- },
84
- reasoner: {
85
- input_hit: 0.14,
86
- input_miss: 0.55,
87
- output: 2.19
88
- }
89
- }.freeze
90
-
91
- def default_input_price
92
- 0.27
93
- end
94
-
95
- def default_output_price
96
- 1.10
97
- end
98
-
99
- def default_cache_hit_price
100
- 0.07
101
- end
102
-
103
- def modalities_for(_model_id)
104
- {
105
- input: ['text'],
106
- output: ['text']
107
- }
108
- end
109
-
110
- def capabilities_for(model_id)
111
- capabilities = ['streaming']
112
-
113
- capabilities << 'function_calling' if model_id.match?(/deepseek-chat/)
114
-
115
- capabilities
116
- end
117
-
118
- def pricing_for(model_id)
119
- family = model_family(model_id)
120
- prices = PRICES.fetch(family, { input_miss: default_input_price, output: default_output_price })
121
-
122
- standard_pricing = {
123
- input_per_million: prices[:input_miss],
124
- output_per_million: prices[:output]
125
- }
126
-
127
- standard_pricing[:cached_input_per_million] = prices[:input_hit] if prices[:input_hit]
128
-
129
- {
130
- text_tokens: {
131
- standard: standard_pricing
132
- }
133
- }
134
- end
135
17
  end
136
18
  end
137
19
  end
@@ -21,6 +21,10 @@ module RubyLLM
21
21
  DeepSeek::Capabilities
22
22
  end
23
23
 
24
+ def configuration_options
25
+ %i[deepseek_api_key deepseek_api_base]
26
+ end
27
+
24
28
  def configuration_requirements
25
29
  %i[deepseek_api_key]
26
30
  end