ruby_llm 1.5.0 → 1.6.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/ruby_llm/active_record/acts_as.rb +46 -6
  4. data/lib/ruby_llm/aliases.json +27 -3
  5. data/lib/ruby_llm/chat.rb +27 -6
  6. data/lib/ruby_llm/configuration.rb +7 -18
  7. data/lib/ruby_llm/connection.rb +11 -6
  8. data/lib/ruby_llm/context.rb +2 -3
  9. data/lib/ruby_llm/embedding.rb +3 -4
  10. data/lib/ruby_llm/error.rb +2 -2
  11. data/lib/ruby_llm/image.rb +3 -4
  12. data/lib/ruby_llm/message.rb +4 -0
  13. data/lib/ruby_llm/model/info.rb +2 -2
  14. data/lib/ruby_llm/models.json +7692 -7067
  15. data/lib/ruby_llm/models.rb +22 -31
  16. data/lib/ruby_llm/models_schema.json +168 -0
  17. data/lib/ruby_llm/provider.rb +150 -89
  18. data/lib/ruby_llm/providers/anthropic/capabilities.rb +1 -2
  19. data/lib/ruby_llm/providers/anthropic/chat.rb +1 -1
  20. data/lib/ruby_llm/providers/anthropic/embeddings.rb +1 -1
  21. data/lib/ruby_llm/providers/anthropic/media.rb +1 -1
  22. data/lib/ruby_llm/providers/anthropic/models.rb +1 -1
  23. data/lib/ruby_llm/providers/anthropic/streaming.rb +1 -1
  24. data/lib/ruby_llm/providers/anthropic/tools.rb +1 -1
  25. data/lib/ruby_llm/providers/anthropic.rb +17 -22
  26. data/lib/ruby_llm/providers/bedrock/capabilities.rb +3 -63
  27. data/lib/ruby_llm/providers/bedrock/chat.rb +5 -4
  28. data/lib/ruby_llm/providers/bedrock/media.rb +1 -1
  29. data/lib/ruby_llm/providers/bedrock/models.rb +5 -6
  30. data/lib/ruby_llm/providers/bedrock/signing.rb +1 -1
  31. data/lib/ruby_llm/providers/bedrock/streaming/base.rb +5 -4
  32. data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +1 -1
  33. data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +1 -1
  34. data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +1 -1
  35. data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +1 -1
  36. data/lib/ruby_llm/providers/bedrock/streaming.rb +1 -1
  37. data/lib/ruby_llm/providers/bedrock.rb +26 -31
  38. data/lib/ruby_llm/providers/deepseek/capabilities.rb +16 -57
  39. data/lib/ruby_llm/providers/deepseek/chat.rb +1 -1
  40. data/lib/ruby_llm/providers/deepseek.rb +12 -17
  41. data/lib/ruby_llm/providers/gemini/capabilities.rb +4 -1
  42. data/lib/ruby_llm/providers/gemini/chat.rb +1 -1
  43. data/lib/ruby_llm/providers/gemini/embeddings.rb +1 -1
  44. data/lib/ruby_llm/providers/gemini/images.rb +1 -1
  45. data/lib/ruby_llm/providers/gemini/media.rb +1 -1
  46. data/lib/ruby_llm/providers/gemini/models.rb +1 -1
  47. data/lib/ruby_llm/providers/gemini/streaming.rb +1 -1
  48. data/lib/ruby_llm/providers/gemini/tools.rb +1 -7
  49. data/lib/ruby_llm/providers/gemini.rb +18 -23
  50. data/lib/ruby_llm/providers/gpustack/chat.rb +1 -1
  51. data/lib/ruby_llm/providers/gpustack/models.rb +1 -1
  52. data/lib/ruby_llm/providers/gpustack.rb +16 -19
  53. data/lib/ruby_llm/providers/mistral/capabilities.rb +31 -19
  54. data/lib/ruby_llm/providers/mistral/chat.rb +1 -1
  55. data/lib/ruby_llm/providers/mistral/embeddings.rb +1 -1
  56. data/lib/ruby_llm/providers/mistral/models.rb +1 -1
  57. data/lib/ruby_llm/providers/mistral.rb +14 -19
  58. data/lib/ruby_llm/providers/ollama/chat.rb +1 -1
  59. data/lib/ruby_llm/providers/ollama/media.rb +1 -1
  60. data/lib/ruby_llm/providers/ollama.rb +13 -18
  61. data/lib/ruby_llm/providers/openai/capabilities.rb +2 -2
  62. data/lib/ruby_llm/providers/openai/chat.rb +2 -2
  63. data/lib/ruby_llm/providers/openai/embeddings.rb +1 -1
  64. data/lib/ruby_llm/providers/openai/images.rb +1 -1
  65. data/lib/ruby_llm/providers/openai/media.rb +1 -1
  66. data/lib/ruby_llm/providers/openai/models.rb +1 -1
  67. data/lib/ruby_llm/providers/openai/streaming.rb +1 -1
  68. data/lib/ruby_llm/providers/openai/tools.rb +1 -1
  69. data/lib/ruby_llm/providers/openai.rb +24 -36
  70. data/lib/ruby_llm/providers/openrouter/models.rb +1 -1
  71. data/lib/ruby_llm/providers/openrouter.rb +9 -14
  72. data/lib/ruby_llm/providers/perplexity/capabilities.rb +1 -30
  73. data/lib/ruby_llm/providers/perplexity/chat.rb +1 -1
  74. data/lib/ruby_llm/providers/perplexity/models.rb +1 -1
  75. data/lib/ruby_llm/providers/perplexity.rb +13 -18
  76. data/lib/ruby_llm/stream_accumulator.rb +3 -3
  77. data/lib/ruby_llm/streaming.rb +16 -3
  78. data/lib/ruby_llm/tool.rb +19 -0
  79. data/lib/ruby_llm/utils.rb +12 -0
  80. data/lib/ruby_llm/version.rb +1 -1
  81. data/lib/tasks/models_docs.rake +18 -11
  82. data/lib/tasks/models_update.rake +31 -4
  83. metadata +2 -1
@@ -5,51 +5,39 @@ module RubyLLM
5
5
  # OpenAI API integration. Handles chat completion, function calling,
6
6
  # and OpenAI's unique streaming format. Supports GPT-4, GPT-3.5,
7
7
  # and other OpenAI models.
8
- module OpenAI
9
- extend Provider
10
- extend OpenAI::Chat
11
- extend OpenAI::Embeddings
12
- extend OpenAI::Models
13
- extend OpenAI::Streaming
14
- extend OpenAI::Tools
15
- extend OpenAI::Images
16
- extend OpenAI::Media
17
-
18
- def self.extended(base)
19
- base.extend(Provider)
20
- base.extend(OpenAI::Chat)
21
- base.extend(OpenAI::Embeddings)
22
- base.extend(OpenAI::Models)
23
- base.extend(OpenAI::Streaming)
24
- base.extend(OpenAI::Tools)
25
- base.extend(OpenAI::Images)
26
- base.extend(OpenAI::Media)
8
+ class OpenAI < Provider
9
+ include OpenAI::Chat
10
+ include OpenAI::Embeddings
11
+ include OpenAI::Models
12
+ include OpenAI::Streaming
13
+ include OpenAI::Tools
14
+ include OpenAI::Images
15
+ include OpenAI::Media
16
+
17
+ def api_base
18
+ @config.openai_api_base || 'https://api.openai.com/v1'
27
19
  end
28
20
 
29
- module_function
30
-
31
- def api_base(config)
32
- config.openai_api_base || 'https://api.openai.com/v1'
33
- end
34
-
35
- def headers(config)
21
+ def headers
36
22
  {
37
- 'Authorization' => "Bearer #{config.openai_api_key}",
38
- 'OpenAI-Organization' => config.openai_organization_id,
39
- 'OpenAI-Project' => config.openai_project_id
23
+ 'Authorization' => "Bearer #{@config.openai_api_key}",
24
+ 'OpenAI-Organization' => @config.openai_organization_id,
25
+ 'OpenAI-Project' => @config.openai_project_id
40
26
  }.compact
41
27
  end
42
28
 
43
- def capabilities
44
- OpenAI::Capabilities
29
+ def maybe_normalize_temperature(temperature, model_id)
30
+ OpenAI::Capabilities.normalize_temperature(temperature, model_id)
45
31
  end
46
32
 
47
- def slug
48
- 'openai'
49
- end
33
+ class << self
34
+ def capabilities
35
+ OpenAI::Capabilities
36
+ end
50
37
 
51
- def configuration_requirements
52
- %i[openai_api_key]
38
+ def configuration_requirements
39
+ %i[openai_api_key]
40
+ end
53
41
  end
54
42
  end
55
43
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module RubyLLM
4
4
  module Providers
5
- module OpenRouter
5
+ class OpenRouter
6
6
  # Models methods of the OpenRouter API integration
7
7
  module Models
8
8
  module_function
@@ -3,28 +3,23 @@
3
3
  module RubyLLM
4
4
  module Providers
5
5
  # OpenRouter API integration.
6
- module OpenRouter
7
- extend OpenAI
8
- extend OpenRouter::Models
6
+ class OpenRouter < OpenAI
7
+ include OpenRouter::Models
9
8
 
10
- module_function
11
-
12
- def api_base(_config)
9
+ def api_base
13
10
  'https://openrouter.ai/api/v1'
14
11
  end
15
12
 
16
- def headers(config)
13
+ def headers
17
14
  {
18
- 'Authorization' => "Bearer #{config.openrouter_api_key}"
15
+ 'Authorization' => "Bearer #{@config.openrouter_api_key}"
19
16
  }
20
17
  end
21
18
 
22
- def slug
23
- 'openrouter'
24
- end
25
-
26
- def configuration_requirements
27
- %i[openrouter_api_key]
19
+ class << self
20
+ def configuration_requirements
21
+ %i[openrouter_api_key]
22
+ end
28
23
  end
29
24
  end
30
25
  end
@@ -2,14 +2,11 @@
2
2
 
3
3
  module RubyLLM
4
4
  module Providers
5
- module Perplexity
5
+ class Perplexity
6
6
  # Determines capabilities and pricing for Perplexity models
7
7
  module Capabilities
8
8
  module_function
9
9
 
10
- # Returns the context window size for the given model ID
11
- # @param model_id [String] the model identifier
12
- # @return [Integer] the context window size in tokens
13
10
  def context_window_for(model_id)
14
11
  case model_id
15
12
  when /sonar-pro/ then 200_000
@@ -17,9 +14,6 @@ module RubyLLM
17
14
  end
18
15
  end
19
16
 
20
- # Returns the maximum number of tokens that can be generated
21
- # @param model_id [String] the model identifier
22
- # @return [Integer] the maximum number of tokens
23
17
  def max_tokens_for(model_id)
24
18
  case model_id
25
19
  when /sonar-(?:pro|reasoning-pro)/ then 8_192
@@ -27,23 +21,14 @@ module RubyLLM
27
21
  end
28
22
  end
29
23
 
30
- # Returns the price per million tokens for input
31
- # @param model_id [String] the model identifier
32
- # @return [Float] the price per million tokens in USD
33
24
  def input_price_for(model_id)
34
25
  PRICES.dig(model_family(model_id), :input) || 1.0
35
26
  end
36
27
 
37
- # Returns the price per million tokens for output
38
- # @param model_id [String] the model identifier
39
- # @return [Float] the price per million tokens in USD
40
28
  def output_price_for(model_id)
41
29
  PRICES.dig(model_family(model_id), :output) || 1.0
42
30
  end
43
31
 
44
- # Determines if the model supports vision capabilities
45
- # @param model_id [String] the model identifier
46
- # @return [Boolean] true if the model supports vision
47
32
  def supports_vision?(model_id)
48
33
  case model_id
49
34
  when /sonar-reasoning-pro/, /sonar-reasoning/, /sonar-pro/, /sonar/ then true
@@ -51,21 +36,14 @@ module RubyLLM
51
36
  end
52
37
  end
53
38
 
54
- # Determines if the model supports function calling
55
- # @param model_id [String] the model identifier
56
- # @return [Boolean] true if the model supports functions
57
39
  def supports_functions?(_model_id)
58
40
  false
59
41
  end
60
42
 
61
- # Determines if the model supports JSON mode
62
43
  def supports_json_mode?(_model_id)
63
44
  true
64
45
  end
65
46
 
66
- # Formats the model ID into a human-readable display name
67
- # @param model_id [String] the model identifier
68
- # @return [String] the formatted display name
69
47
  def format_display_name(model_id)
70
48
  case model_id
71
49
  when 'sonar' then 'Sonar'
@@ -80,16 +58,10 @@ module RubyLLM
80
58
  end
81
59
  end
82
60
 
83
- # Returns the model type
84
- # @param model_id [String] the model identifier
85
- # @return [String] the model type (e.g., 'chat')
86
61
  def model_type(_model_id)
87
62
  'chat'
88
63
  end
89
64
 
90
- # Returns the model family
91
- # @param model_id [String] the model identifier
92
- # @return [Symbol] the model family
93
65
  def model_family(model_id)
94
66
  case model_id
95
67
  when 'sonar' then :sonar
@@ -123,7 +95,6 @@ module RubyLLM
123
95
  output_per_million: prices[:output]
124
96
  }
125
97
 
126
- # Add special pricing if available
127
98
  standard_pricing[:citation_per_million] = prices[:citation] if prices[:citation]
128
99
  standard_pricing[:reasoning_per_million] = prices[:reasoning] if prices[:reasoning]
129
100
  standard_pricing[:search_per_thousand] = prices[:search_queries] if prices[:search_queries]
@@ -2,7 +2,7 @@
2
2
 
3
3
  module RubyLLM
4
4
  module Providers
5
- module Perplexity
5
+ class Perplexity
6
6
  # Chat formatting for Perplexity provider
7
7
  module Chat
8
8
  module_function
@@ -2,7 +2,7 @@
2
2
 
3
3
  module RubyLLM
4
4
  module Providers
5
- module Perplexity
5
+ class Perplexity
6
6
  # Models methods of the Perplexity API integration
7
7
  module Models
8
8
  def list_models(**)
@@ -3,34 +3,29 @@
3
3
  module RubyLLM
4
4
  module Providers
5
5
  # Perplexity API integration.
6
- module Perplexity
7
- extend OpenAI
8
- extend Perplexity::Chat
9
- extend Perplexity::Models
6
+ class Perplexity < OpenAI
7
+ include Perplexity::Chat
8
+ include Perplexity::Models
10
9
 
11
- module_function
12
-
13
- def api_base(_config)
10
+ def api_base
14
11
  'https://api.perplexity.ai'
15
12
  end
16
13
 
17
- def headers(config)
14
+ def headers
18
15
  {
19
- 'Authorization' => "Bearer #{config.perplexity_api_key}",
16
+ 'Authorization' => "Bearer #{@config.perplexity_api_key}",
20
17
  'Content-Type' => 'application/json'
21
18
  }
22
19
  end
23
20
 
24
- def capabilities
25
- Perplexity::Capabilities
26
- end
27
-
28
- def slug
29
- 'perplexity'
30
- end
21
+ class << self
22
+ def capabilities
23
+ Perplexity::Capabilities
24
+ end
31
25
 
32
- def configuration_requirements
33
- %i[perplexity_api_key]
26
+ def configuration_requirements
27
+ %i[perplexity_api_key]
28
+ end
34
29
  end
35
30
 
36
31
  def parse_error(response)
@@ -16,7 +16,7 @@ module RubyLLM
16
16
  end
17
17
 
18
18
  def add(chunk)
19
- RubyLLM.logger.debug chunk.inspect
19
+ RubyLLM.logger.debug chunk.inspect if RubyLLM.config.log_stream_debug
20
20
  @model_id ||= chunk.model_id
21
21
 
22
22
  if chunk.tool_call?
@@ -26,7 +26,7 @@ module RubyLLM
26
26
  end
27
27
 
28
28
  count_tokens chunk
29
- RubyLLM.logger.debug inspect
29
+ RubyLLM.logger.debug inspect if RubyLLM.config.log_stream_debug
30
30
  end
31
31
 
32
32
  def to_message(response)
@@ -62,7 +62,7 @@ module RubyLLM
62
62
  end
63
63
 
64
64
  def accumulate_tool_calls(new_tool_calls)
65
- RubyLLM.logger.debug "Accumulating tool calls: #{new_tool_calls}"
65
+ RubyLLM.logger.debug "Accumulating tool calls: #{new_tool_calls}" if RubyLLM.config.log_stream_debug
66
66
  new_tool_calls.each_value do |tool_call|
67
67
  if tool_call.id
68
68
  tool_call_id = tool_call.id.empty? ? SecureRandom.uuid : tool_call.id
@@ -8,10 +8,12 @@ module RubyLLM
8
8
  module Streaming
9
9
  module_function
10
10
 
11
- def stream_response(connection, payload, &block)
11
+ def stream_response(connection, payload, additional_headers = {}, &block)
12
12
  accumulator = StreamAccumulator.new
13
13
 
14
14
  response = connection.post stream_url, payload do |req|
15
+ # Merge additional headers, with existing headers taking precedence
16
+ req.headers = additional_headers.merge(req.headers) unless additional_headers.empty?
15
17
  if req.options.respond_to?(:on_data)
16
18
  # Handle Faraday 2.x streaming with on_data method
17
19
  req.options.on_data = handle_stream do |chunk|
@@ -27,7 +29,9 @@ module RubyLLM
27
29
  end
28
30
  end
29
31
 
30
- accumulator.to_message(response)
32
+ message = accumulator.to_message(response)
33
+ RubyLLM.logger.debug "Stream completed: #{message.inspect}"
34
+ message
31
35
  end
32
36
 
33
37
  def handle_stream(&block)
@@ -56,7 +60,7 @@ module RubyLLM
56
60
  end
57
61
 
58
62
  def process_stream_chunk(chunk, parser, env, &)
59
- RubyLLM.logger.debug "Received chunk: #{chunk}"
63
+ RubyLLM.logger.debug "Received chunk: #{chunk}" if RubyLLM.config.log_stream_debug
60
64
 
61
65
  if error_chunk?(chunk)
62
66
  handle_error_chunk(chunk, env)
@@ -145,5 +149,14 @@ module RubyLLM
145
149
  rescue JSON::ParserError => e
146
150
  RubyLLM.logger.debug "Failed to parse error event: #{e.message}"
147
151
  end
152
+
153
+ # Default implementation - providers should override this method
154
+ def parse_streaming_error(data)
155
+ error_data = JSON.parse(data)
156
+ [500, error_data['message'] || 'Unknown streaming error']
157
+ rescue JSON::ParserError => e
158
+ RubyLLM.logger.debug "Failed to parse streaming error: #{e.message}"
159
+ [500, "Failed to parse error: #{data}"]
160
+ end
148
161
  end
149
162
  end
data/lib/ruby_llm/tool.rb CHANGED
@@ -32,6 +32,19 @@ module RubyLLM
32
32
  # end
33
33
  # end
34
34
  class Tool
35
+ # Stops conversation continuation after tool execution
36
+ class Halt
37
+ attr_reader :content
38
+
39
+ def initialize(content)
40
+ @content = content
41
+ end
42
+
43
+ def to_s
44
+ @content.to_s
45
+ end
46
+ end
47
+
35
48
  class << self
36
49
  def description(text = nil)
37
50
  return @description unless text
@@ -77,5 +90,11 @@ module RubyLLM
77
90
  def execute(...)
78
91
  raise NotImplementedError, 'Subclasses must implement #execute'
79
92
  end
93
+
94
+ protected
95
+
96
+ def halt(message)
97
+ Halt.new(message)
98
+ end
80
99
  end
81
100
  end
@@ -24,6 +24,18 @@ module RubyLLM
24
24
  end
25
25
  end
26
26
 
27
+ def to_time(value)
28
+ return unless value
29
+
30
+ value.is_a?(Time) ? value : Time.parse(value.to_s)
31
+ end
32
+
33
+ def to_date(value)
34
+ return unless value
35
+
36
+ value.is_a?(Date) ? value : Date.parse(value.to_s)
37
+ end
38
+
27
39
  def deep_merge(params, payload)
28
40
  params.merge(payload) do |_key, params_value, payload_value|
29
41
  if params_value.is_a?(Hash) && payload_value.is_a?(Hash)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyLLM
4
- VERSION = '1.5.0'
4
+ VERSION = '1.6.0'
5
5
  end
@@ -12,8 +12,8 @@ namespace :models do
12
12
  output = generate_models_markdown
13
13
 
14
14
  # Write the output
15
- File.write('docs/available-models.md', output)
16
- puts 'Generated docs/available-models.md'
15
+ File.write('docs/_reference/available-models.md', output)
16
+ puts 'Generated docs/_reference/available-models.md'
17
17
  end
18
18
  end
19
19
 
@@ -22,15 +22,16 @@ def generate_models_markdown
22
22
  ---
23
23
  layout: default
24
24
  title: Available Models
25
- nav_order: 5
26
- permalink: /available-models
25
+ nav_order: 1
27
26
  description: Browse hundreds of AI models from every major provider. Always up-to-date, automatically generated.
27
+ redirect_from:
28
+ - /guides/available-models
28
29
  ---
29
30
 
30
- # Available Models
31
+ # {{ page.title }}
31
32
  {: .no_toc }
32
33
 
33
- Every model, every provider, always current. Your complete AI model reference.
34
+ {{ page.description }}
34
35
  {: .fs-6 .fw-300 }
35
36
 
36
37
  ## Table of contents
@@ -41,6 +42,13 @@ def generate_models_markdown
41
42
 
42
43
  ---
43
44
 
45
+ After reading this guide, you will know:
46
+
47
+ * How RubyLLM's model registry works and where data comes from
48
+ * How to find models by provider, capability, or purpose
49
+ * What information is available for each model
50
+ * How to use model aliases for simpler configuration
51
+
44
52
  ## How Model Data Works
45
53
 
46
54
  RubyLLM's model registry combines data from multiple sources:
@@ -78,12 +86,12 @@ def generate_models_markdown
78
86
  end
79
87
 
80
88
  def generate_provider_sections
81
- RubyLLM::Provider.providers.keys.map do |provider|
89
+ RubyLLM::Provider.providers.map do |provider, provider_class|
82
90
  models = RubyLLM.models.by_provider(provider)
83
91
  next if models.none?
84
92
 
85
93
  <<~PROVIDER
86
- ### #{provider.to_s.capitalize} (#{models.count})
94
+ ### #{provider_class.name} (#{models.count})
87
95
 
88
96
  #{models_table(models)}
89
97
  PROVIDER
@@ -167,15 +175,14 @@ end
167
175
  def models_table(models)
168
176
  return '*No models found*' if models.none?
169
177
 
170
- headers = ['Model', 'ID', 'Provider', 'Context', 'Max Output', 'Standard Pricing (per 1M tokens)']
171
- alignment = [':--', ':--', ':--', '--:', '--:', ':--']
178
+ headers = ['Model', 'Provider', 'Context', 'Max Output', 'Standard Pricing (per 1M tokens)']
179
+ alignment = [':--', ':--', '--:', '--:', ':--']
172
180
 
173
181
  rows = models.sort_by { |m| [m.provider, m.name] }.map do |model|
174
182
  # Format pricing information
175
183
  pricing = standard_pricing_display(model)
176
184
 
177
185
  [
178
- model.name,
179
186
  model.id,
180
187
  model.provider,
181
188
  model.context_window || '-',
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'dotenv/load'
4
4
  require 'ruby_llm'
5
+ require 'json-schema'
5
6
 
6
7
  task default: ['models:update']
7
8
 
@@ -49,6 +50,9 @@ def refresh_models
49
50
  elsif models.all.size == initial_count && initial_count.positive?
50
51
  puts 'Warning: Model list unchanged.'
51
52
  else
53
+ puts 'Validating models...'
54
+ validate_models!(models)
55
+
52
56
  puts "Saving models.json (#{models.all.size} models)"
53
57
  models.save_models
54
58
  end
@@ -56,12 +60,34 @@ def refresh_models
56
60
  @models = models
57
61
  end
58
62
 
63
+ def validate_models!(models)
64
+ schema_path = File.expand_path('../ruby_llm/models_schema.json', __dir__)
65
+ models_data = models.all.map(&:to_h)
66
+
67
+ validation_errors = JSON::Validator.fully_validate(schema_path, models_data)
68
+
69
+ unless validation_errors.empty?
70
+ # Save failed models for inspection
71
+ failed_path = File.expand_path('../ruby_llm/models.failed.json', __dir__)
72
+ File.write(failed_path, JSON.pretty_generate(models_data))
73
+
74
+ puts 'ERROR: Models validation failed:'
75
+ puts "\nValidation errors:"
76
+ validation_errors.first(10).each { |error| puts " - #{error}" }
77
+ puts " ... and #{validation_errors.size - 10} more errors" if validation_errors.size > 10
78
+ puts "-> Failed models saved to: #{failed_path}"
79
+ exit(1)
80
+ end
81
+
82
+ puts '✓ Models validation passed'
83
+ end
84
+
59
85
  def display_model_stats
60
86
  puts "\nModel count:"
61
87
  provider_counts = @models.all.group_by(&:provider).transform_values(&:count)
62
88
 
63
- RubyLLM::Provider.providers.each_key do |sym|
64
- name = sym.to_s.capitalize
89
+ RubyLLM::Provider.providers.each do |sym, provider_class|
90
+ name = provider_class.name
65
91
  count = provider_counts[sym.to_s] || 0
66
92
  status = status(sym)
67
93
  puts " #{name}: #{count} models #{status}"
@@ -71,9 +97,10 @@ def display_model_stats
71
97
  end
72
98
 
73
99
  def status(provider_sym)
74
- if RubyLLM::Provider.providers[provider_sym].local?
100
+ provider_class = RubyLLM::Provider.providers[provider_sym]
101
+ if provider_class.local?
75
102
  ' (LOCAL - SKIP)'
76
- elsif RubyLLM::Provider.providers[provider_sym].configured?
103
+ elsif provider_class.configured?(RubyLLM.config)
77
104
  ' (OK)'
78
105
  else
79
106
  ' (NOT CONFIGURED)'
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.5.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino
@@ -168,6 +168,7 @@ files:
168
168
  - lib/ruby_llm/model/pricing_tier.rb
169
169
  - lib/ruby_llm/models.json
170
170
  - lib/ruby_llm/models.rb
171
+ - lib/ruby_llm/models_schema.json
171
172
  - lib/ruby_llm/provider.rb
172
173
  - lib/ruby_llm/providers/anthropic.rb
173
174
  - lib/ruby_llm/providers/anthropic/capabilities.rb