ruby_llm 0.1.0.pre49 → 1.0.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.
- checksums.yaml +4 -4
 - data/.github/workflows/cicd.yml +49 -0
 - data/.gitignore +2 -1
 - data/.overcommit.yml +1 -1
 - data/CONTRIBUTING.md +207 -0
 - data/Gemfile +1 -0
 - data/README.md +50 -18
 - data/lib/ruby_llm/chat.rb +1 -1
 - data/lib/ruby_llm/models.json +389 -43
 - data/lib/ruby_llm/models.rb +22 -16
 - data/lib/ruby_llm/provider.rb +12 -2
 - data/lib/ruby_llm/providers/gemini/models.rb +0 -8
 - data/lib/ruby_llm/providers/openai/capabilities.rb +41 -4
 - data/lib/ruby_llm/tool.rb +15 -7
 - data/lib/ruby_llm/version.rb +1 -1
 - data/lib/ruby_llm.rb +2 -0
 - data/lib/tasks/models.rake +6 -10
 - data/lib/tasks/vcr.rake +114 -0
 - data/ruby_llm.gemspec +1 -0
 - metadata +18 -3
 - data/.rspec_status +0 -50
 
    
        data/lib/ruby_llm/models.rb
    CHANGED
    
    | 
         @@ -12,22 +12,26 @@ module RubyLLM 
     | 
|
| 
       12 
12 
     | 
    
         
             
              class Models
         
     | 
| 
       13 
13 
     | 
    
         
             
                include Enumerable
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
       15 
     | 
    
         
            -
                 
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
      
 15 
     | 
    
         
            +
                # Delegate class methods to the singleton instance
         
     | 
| 
      
 16 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 17 
     | 
    
         
            +
                  def instance
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @instance ||= new
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
       18 
20 
     | 
    
         | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def provider_for(model)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    Provider.for(model)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
       22 
24 
     | 
    
         | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
                   
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
      
 25 
     | 
    
         
            +
                  def models_file
         
     | 
| 
      
 26 
     | 
    
         
            +
                    File.expand_path('models.json', __dir__)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  # Class method to refresh model data
         
     | 
| 
      
 30 
     | 
    
         
            +
                  def refresh!
         
     | 
| 
      
 31 
     | 
    
         
            +
                    models = RubyLLM.providers.flat_map(&:list_models).sort_by(&:id)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    @instance = new(models)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
       28 
34 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
                # Delegate class methods to the singleton instance
         
     | 
| 
       30 
     | 
    
         
            -
                class << self
         
     | 
| 
       31 
35 
     | 
    
         
             
                  def method_missing(method, ...)
         
     | 
| 
       32 
36 
     | 
    
         
             
                    if instance.respond_to?(method)
         
     | 
| 
       33 
37 
     | 
    
         
             
                      instance.send(method, ...)
         
     | 
| 
         @@ -48,12 +52,16 @@ module RubyLLM 
     | 
|
| 
       48 
52 
     | 
    
         | 
| 
       49 
53 
     | 
    
         
             
                # Load models from the JSON file
         
     | 
| 
       50 
54 
     | 
    
         
             
                def load_models
         
     | 
| 
       51 
     | 
    
         
            -
                  data = JSON.parse(File.read( 
     | 
| 
      
 55 
     | 
    
         
            +
                  data = JSON.parse(File.read(self.class.models_file))
         
     | 
| 
       52 
56 
     | 
    
         
             
                  data.map { |model| ModelInfo.new(model.transform_keys(&:to_sym)) }
         
     | 
| 
       53 
57 
     | 
    
         
             
                rescue Errno::ENOENT
         
     | 
| 
       54 
58 
     | 
    
         
             
                  [] # Return empty array if file doesn't exist yet
         
     | 
| 
       55 
59 
     | 
    
         
             
                end
         
     | 
| 
       56 
60 
     | 
    
         | 
| 
      
 61 
     | 
    
         
            +
                def save_models
         
     | 
| 
      
 62 
     | 
    
         
            +
                  File.write(self.class.models_file, JSON.pretty_generate(all.map(&:to_h)))
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
       57 
65 
     | 
    
         
             
                # Return all models in the collection
         
     | 
| 
       58 
66 
     | 
    
         
             
                def all
         
     | 
| 
       59 
67 
     | 
    
         
             
                  @models
         
     | 
| 
         @@ -103,8 +111,6 @@ module RubyLLM 
     | 
|
| 
       103 
111 
     | 
    
         
             
                # Instance method to refresh models
         
     | 
| 
       104 
112 
     | 
    
         
             
                def refresh!
         
     | 
| 
       105 
113 
     | 
    
         
             
                  self.class.refresh!
         
     | 
| 
       106 
     | 
    
         
            -
                  # Return self for method chaining
         
     | 
| 
       107 
     | 
    
         
            -
                  self
         
     | 
| 
       108 
114 
     | 
    
         
             
                end
         
     | 
| 
       109 
115 
     | 
    
         
             
              end
         
     | 
| 
       110 
116 
     | 
    
         
             
            end
         
     | 
    
        data/lib/ruby_llm/provider.rb
    CHANGED
    
    | 
         @@ -8,8 +8,18 @@ module RubyLLM 
     | 
|
| 
       8 
8 
     | 
    
         
             
                # Common functionality for all LLM providers. Implements the core provider
         
     | 
| 
       9 
9 
     | 
    
         
             
                # interface so specific providers only need to implement a few key methods.
         
     | 
| 
       10 
10 
     | 
    
         
             
                module Methods # rubocop:disable Metrics/ModuleLength
         
     | 
| 
       11 
     | 
    
         
            -
                  def complete(messages, tools:, temperature:, model:, &block)
         
     | 
| 
       12 
     | 
    
         
            -
                     
     | 
| 
      
 11 
     | 
    
         
            +
                  def complete(messages, tools:, temperature:, model:, &block) # rubocop:disable Metrics/MethodLength
         
     | 
| 
      
 12 
     | 
    
         
            +
                    normalized_temperature = if capabilities.respond_to?(:normalize_temperature)
         
     | 
| 
      
 13 
     | 
    
         
            +
                                               capabilities.normalize_temperature(temperature, model)
         
     | 
| 
      
 14 
     | 
    
         
            +
                                             else
         
     | 
| 
      
 15 
     | 
    
         
            +
                                               temperature
         
     | 
| 
      
 16 
     | 
    
         
            +
                                             end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                    payload = render_payload(messages,
         
     | 
| 
      
 19 
     | 
    
         
            +
                                             tools: tools,
         
     | 
| 
      
 20 
     | 
    
         
            +
                                             temperature: normalized_temperature,
         
     | 
| 
      
 21 
     | 
    
         
            +
                                             model: model,
         
     | 
| 
      
 22 
     | 
    
         
            +
                                             stream: block_given?)
         
     | 
| 
       13 
23 
     | 
    
         | 
| 
       14 
24 
     | 
    
         
             
                    if block_given?
         
     | 
| 
       15 
25 
     | 
    
         
             
                      stream_response payload, &block
         
     | 
| 
         @@ -10,14 +10,6 @@ module RubyLLM 
     | 
|
| 
       10 
10 
     | 
    
         
             
                      'models'
         
     | 
| 
       11 
11 
     | 
    
         
             
                    end
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
                    def list_models
         
     | 
| 
       14 
     | 
    
         
            -
                      response = connection.get("models?key=#{RubyLLM.config.gemini_api_key}") do |req|
         
     | 
| 
       15 
     | 
    
         
            -
                        req.headers.merge! headers
         
     | 
| 
       16 
     | 
    
         
            -
                      end
         
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
                      parse_list_models_response(response, slug, capabilities)
         
     | 
| 
       19 
     | 
    
         
            -
                    end
         
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
13 
     | 
    
         
             
                    private
         
     | 
| 
       22 
14 
     | 
    
         | 
| 
       23 
15 
     | 
    
         
             
                    def parse_list_models_response(response, slug, capabilities) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
         
     | 
| 
         @@ -50,15 +50,42 @@ module RubyLLM 
     | 
|
| 
       50 
50 
     | 
    
         
             
                    # Determines if the model supports vision capabilities
         
     | 
| 
       51 
51 
     | 
    
         
             
                    # @param model_id [String] the model identifier
         
     | 
| 
       52 
52 
     | 
    
         
             
                    # @return [Boolean] true if the model supports vision
         
     | 
| 
       53 
     | 
    
         
            -
                    def supports_vision?(model_id)
         
     | 
| 
       54 
     | 
    
         
            -
                       
     | 
| 
      
 53 
     | 
    
         
            +
                    def supports_vision?(model_id) # rubocop:disable Metrics/MethodLength
         
     | 
| 
      
 54 
     | 
    
         
            +
                      supporting_patterns = [
         
     | 
| 
      
 55 
     | 
    
         
            +
                        /^o1$/,
         
     | 
| 
      
 56 
     | 
    
         
            +
                        /^o1-(?!.*mini|.*preview).*$/,
         
     | 
| 
      
 57 
     | 
    
         
            +
                        /gpt-4\.5/,
         
     | 
| 
      
 58 
     | 
    
         
            +
                        /^gpt-4o$/,
         
     | 
| 
      
 59 
     | 
    
         
            +
                        /gpt-4o-2024/,
         
     | 
| 
      
 60 
     | 
    
         
            +
                        /gpt-4o-search/,
         
     | 
| 
      
 61 
     | 
    
         
            +
                        /^gpt-4o-mini$/,
         
     | 
| 
      
 62 
     | 
    
         
            +
                        /gpt-4o-mini-2024/,
         
     | 
| 
      
 63 
     | 
    
         
            +
                        /gpt-4o-mini-search/,
         
     | 
| 
      
 64 
     | 
    
         
            +
                        /chatgpt-4o/,
         
     | 
| 
      
 65 
     | 
    
         
            +
                        /gpt-4-turbo-2024/,
         
     | 
| 
      
 66 
     | 
    
         
            +
                        /computer-use-preview/,
         
     | 
| 
      
 67 
     | 
    
         
            +
                        /omni-moderation/
         
     | 
| 
      
 68 
     | 
    
         
            +
                      ]
         
     | 
| 
      
 69 
     | 
    
         
            +
                      supporting_patterns.any? { |regex| model_id.match?(regex) }
         
     | 
| 
       55 
70 
     | 
    
         
             
                    end
         
     | 
| 
       56 
71 
     | 
    
         | 
| 
       57 
72 
     | 
    
         
             
                    # Determines if the model supports function calling
         
     | 
| 
       58 
73 
     | 
    
         
             
                    # @param model_id [String] the model identifier
         
     | 
| 
       59 
74 
     | 
    
         
             
                    # @return [Boolean] true if the model supports functions
         
     | 
| 
       60 
     | 
    
         
            -
                    def supports_functions?(model_id)
         
     | 
| 
       61 
     | 
    
         
            -
                       
     | 
| 
      
 75 
     | 
    
         
            +
                    def supports_functions?(model_id) # rubocop:disable Metrics/MethodLength
         
     | 
| 
      
 76 
     | 
    
         
            +
                      supporting_patterns = [
         
     | 
| 
      
 77 
     | 
    
         
            +
                        /^o1$/,
         
     | 
| 
      
 78 
     | 
    
         
            +
                        /gpt-4o/,
         
     | 
| 
      
 79 
     | 
    
         
            +
                        /gpt-4\.5/,
         
     | 
| 
      
 80 
     | 
    
         
            +
                        /chatgpt-4o/,
         
     | 
| 
      
 81 
     | 
    
         
            +
                        /gpt-4-turbo/,
         
     | 
| 
      
 82 
     | 
    
         
            +
                        /computer-use-preview/,
         
     | 
| 
      
 83 
     | 
    
         
            +
                        /o1-preview/,
         
     | 
| 
      
 84 
     | 
    
         
            +
                        /o1-\d{4}-\d{2}-\d{2}/,
         
     | 
| 
      
 85 
     | 
    
         
            +
                        /o1-pro/,
         
     | 
| 
      
 86 
     | 
    
         
            +
                        /o3-mini/
         
     | 
| 
      
 87 
     | 
    
         
            +
                      ]
         
     | 
| 
      
 88 
     | 
    
         
            +
                      supporting_patterns.any? { |regex| model_id.match?(regex) }
         
     | 
| 
       62 
89 
     | 
    
         
             
                    end
         
     | 
| 
       63 
90 
     | 
    
         | 
| 
       64 
91 
     | 
    
         
             
                    # Determines if the model supports audio input/output
         
     | 
| 
         @@ -230,6 +257,16 @@ module RubyLLM 
     | 
|
| 
       230 
257 
     | 
    
         
             
                        .gsub('Omni Moderation', 'Omni-Moderation')
         
     | 
| 
       231 
258 
     | 
    
         
             
                        .gsub('Text Moderation', 'Text-Moderation')
         
     | 
| 
       232 
259 
     | 
    
         
             
                    end
         
     | 
| 
      
 260 
     | 
    
         
            +
             
     | 
| 
      
 261 
     | 
    
         
            +
                    def normalize_temperature(temperature, model_id)
         
     | 
| 
      
 262 
     | 
    
         
            +
                      if model_id.match?(/o[13]/)
         
     | 
| 
      
 263 
     | 
    
         
            +
                        # O1/O3 models always use temperature 1.0
         
     | 
| 
      
 264 
     | 
    
         
            +
                        RubyLLM.logger.debug "Model #{model_id} requires temperature=1.0, ignoring provided value"
         
     | 
| 
      
 265 
     | 
    
         
            +
                        1.0
         
     | 
| 
      
 266 
     | 
    
         
            +
                      else
         
     | 
| 
      
 267 
     | 
    
         
            +
                        temperature
         
     | 
| 
      
 268 
     | 
    
         
            +
                      end
         
     | 
| 
      
 269 
     | 
    
         
            +
                    end
         
     | 
| 
       233 
270 
     | 
    
         
             
                  end
         
     | 
| 
       234 
271 
     | 
    
         
             
                end
         
     | 
| 
       235 
272 
     | 
    
         
             
              end
         
     | 
    
        data/lib/ruby_llm/tool.rb
    CHANGED
    
    | 
         @@ -18,14 +18,19 @@ module RubyLLM 
     | 
|
| 
       18 
18 
     | 
    
         
             
              # interface for defining parameters and implementing tool behavior.
         
     | 
| 
       19 
19 
     | 
    
         
             
              #
         
     | 
| 
       20 
20 
     | 
    
         
             
              # Example:
         
     | 
| 
       21 
     | 
    
         
            -
              # 
     | 
| 
       22 
     | 
    
         
            -
              #     description "Performs arithmetic calculations"
         
     | 
| 
       23 
     | 
    
         
            -
              #     param :expression, type: :string, desc: "Math expression to evaluate"
         
     | 
| 
      
 21 
     | 
    
         
            +
              #    require 'tzinfo'
         
     | 
| 
       24 
22 
     | 
    
         
             
              #
         
     | 
| 
       25 
     | 
    
         
            -
              # 
     | 
| 
       26 
     | 
    
         
            -
              # 
     | 
| 
       27 
     | 
    
         
            -
              # 
     | 
| 
       28 
     | 
    
         
            -
              # 
     | 
| 
      
 23 
     | 
    
         
            +
              #    class TimeInfo < RubyLLM::Tool
         
     | 
| 
      
 24 
     | 
    
         
            +
              #      description 'Gets the current time in various timezones'
         
     | 
| 
      
 25 
     | 
    
         
            +
              #      param :timezone, desc: "Timezone name (e.g., 'UTC', 'America/New_York')"
         
     | 
| 
      
 26 
     | 
    
         
            +
              #
         
     | 
| 
      
 27 
     | 
    
         
            +
              #      def execute(timezone:)
         
     | 
| 
      
 28 
     | 
    
         
            +
              #        time = TZInfo::Timezone.get(timezone).now.strftime('%Y-%m-%d %H:%M:%S')
         
     | 
| 
      
 29 
     | 
    
         
            +
              #        "Current time in #{timezone}: #{time}"
         
     | 
| 
      
 30 
     | 
    
         
            +
              #       rescue StandardError => e
         
     | 
| 
      
 31 
     | 
    
         
            +
              #          { error: e.message }
         
     | 
| 
      
 32 
     | 
    
         
            +
              #       end
         
     | 
| 
      
 33 
     | 
    
         
            +
              #    end
         
     | 
| 
       29 
34 
     | 
    
         
             
              class Tool
         
     | 
| 
       30 
35 
     | 
    
         
             
                class << self
         
     | 
| 
       31 
36 
     | 
    
         
             
                  def description(text = nil)
         
     | 
| 
         @@ -45,6 +50,9 @@ module RubyLLM 
     | 
|
| 
       45 
50 
     | 
    
         | 
| 
       46 
51 
     | 
    
         
             
                def name
         
     | 
| 
       47 
52 
     | 
    
         
             
                  self.class.name
         
     | 
| 
      
 53 
     | 
    
         
            +
                      .unicode_normalize(:nfkd)
         
     | 
| 
      
 54 
     | 
    
         
            +
                      .encode('ASCII', replace: '')
         
     | 
| 
      
 55 
     | 
    
         
            +
                      .gsub(/[^a-zA-Z0-9_-]/, '-')
         
     | 
| 
       48 
56 
     | 
    
         
             
                      .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
         
     | 
| 
       49 
57 
     | 
    
         
             
                      .gsub(/([a-z\d])([A-Z])/, '\1_\2')
         
     | 
| 
       50 
58 
     | 
    
         
             
                      .downcase
         
     | 
    
        data/lib/ruby_llm/version.rb
    CHANGED
    
    
    
        data/lib/ruby_llm.rb
    CHANGED
    
    | 
         @@ -17,6 +17,8 @@ loader.inflector.inflect( 
     | 
|
| 
       17 
17 
     | 
    
         
             
              'api' => 'API',
         
     | 
| 
       18 
18 
     | 
    
         
             
              'deepseek' => 'DeepSeek'
         
     | 
| 
       19 
19 
     | 
    
         
             
            )
         
     | 
| 
      
 20 
     | 
    
         
            +
            loader.ignore("#{__dir__}/ruby_llm/railtie")
         
     | 
| 
      
 21 
     | 
    
         
            +
            loader.ignore("#{__dir__}/ruby_llm/active_record")
         
     | 
| 
       20 
22 
     | 
    
         
             
            loader.setup
         
     | 
| 
       21 
23 
     | 
    
         | 
| 
       22 
24 
     | 
    
         
             
            # A delightful Ruby interface to modern AI language models.
         
     | 
    
        data/lib/tasks/models.rake
    CHANGED
    
    | 
         @@ -2,7 +2,9 @@ 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            require 'English'
         
     | 
| 
       4 
4 
     | 
    
         
             
            require 'faraday'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'fileutils'
         
     | 
| 
       5 
6 
     | 
    
         
             
            require 'nokogiri'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'ruby_llm'
         
     | 
| 
       6 
8 
     | 
    
         | 
| 
       7 
9 
     | 
    
         
             
            # URLs to process
         
     | 
| 
       8 
10 
     | 
    
         
             
            PROVIDER_DOCS = {
         
     | 
| 
         @@ -64,8 +66,6 @@ end 
     | 
|
| 
       64 
66 
     | 
    
         
             
            namespace :models do # rubocop:disable Metrics/BlockLength
         
     | 
| 
       65 
67 
     | 
    
         
             
              desc 'Update available models from providers'
         
     | 
| 
       66 
68 
     | 
    
         
             
              task :update do
         
     | 
| 
       67 
     | 
    
         
            -
                require 'ruby_llm'
         
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
       69 
69 
     | 
    
         
             
                # Configure API keys
         
     | 
| 
       70 
70 
     | 
    
         
             
                RubyLLM.configure do |config|
         
     | 
| 
       71 
71 
     | 
    
         
             
                  config.openai_api_key = ENV.fetch('OPENAI_API_KEY')
         
     | 
| 
         @@ -74,15 +74,13 @@ namespace :models do # rubocop:disable Metrics/BlockLength 
     | 
|
| 
       74 
74 
     | 
    
         
             
                  config.deepseek_api_key = ENV.fetch('DEEPSEEK_API_KEY')
         
     | 
| 
       75 
75 
     | 
    
         
             
                end
         
     | 
| 
       76 
76 
     | 
    
         | 
| 
       77 
     | 
    
         
            -
                 
     | 
| 
       78 
     | 
    
         
            -
                models 
     | 
| 
       79 
     | 
    
         
            -
                # Write to models.json
         
     | 
| 
       80 
     | 
    
         
            -
                File.write(File.expand_path('../ruby_llm/models.json', __dir__), JSON.pretty_generate(models.map(&:to_h)))
         
     | 
| 
      
 77 
     | 
    
         
            +
                models = RubyLLM.models.refresh!
         
     | 
| 
      
 78 
     | 
    
         
            +
                models.save_models
         
     | 
| 
       81 
79 
     | 
    
         | 
| 
       82 
     | 
    
         
            -
                puts "Updated models.json with #{models.size} models:"
         
     | 
| 
      
 80 
     | 
    
         
            +
                puts "Updated models.json with #{models.all.size} models:"
         
     | 
| 
       83 
81 
     | 
    
         
             
                RubyLLM::Provider.providers.each do |provider_sym, provider_module|
         
     | 
| 
       84 
82 
     | 
    
         
             
                  provider_name = provider_module.to_s.split('::').last
         
     | 
| 
       85 
     | 
    
         
            -
                  provider_models = models.select { |m| m.provider == provider_sym.to_s }
         
     | 
| 
      
 83 
     | 
    
         
            +
                  provider_models = models.all.select { |m| m.provider == provider_sym.to_s }
         
     | 
| 
       86 
84 
     | 
    
         
             
                  puts "#{provider_name} models: #{provider_models.size}"
         
     | 
| 
       87 
85 
     | 
    
         
             
                end
         
     | 
| 
       88 
86 
     | 
    
         
             
              end
         
     | 
| 
         @@ -91,8 +89,6 @@ namespace :models do # rubocop:disable Metrics/BlockLength 
     | 
|
| 
       91 
89 
     | 
    
         
             
              task :update_capabilities do # rubocop:disable Metrics/BlockLength
         
     | 
| 
       92 
90 
     | 
    
         
             
                # Check if a specific provider was requested
         
     | 
| 
       93 
91 
     | 
    
         
             
                target_provider = ENV['PROVIDER']&.to_sym
         
     | 
| 
       94 
     | 
    
         
            -
                require 'ruby_llm'
         
     | 
| 
       95 
     | 
    
         
            -
                require 'fileutils'
         
     | 
| 
       96 
92 
     | 
    
         | 
| 
       97 
93 
     | 
    
         
             
                # Configure API keys
         
     | 
| 
       98 
94 
     | 
    
         
             
                RubyLLM.configure do |config|
         
     | 
    
        data/lib/tasks/vcr.rake
    ADDED
    
    | 
         @@ -0,0 +1,114 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            # Helper functions at the top level
         
     | 
| 
      
 4 
     | 
    
         
            +
            def record_all_cassettes(cassette_dir)
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Re-record all cassettes
         
     | 
| 
      
 6 
     | 
    
         
            +
              FileUtils.rm_rf(cassette_dir)
         
     | 
| 
      
 7 
     | 
    
         
            +
              FileUtils.mkdir_p(cassette_dir)
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              puts 'Recording cassettes for all providers...'
         
     | 
| 
      
 10 
     | 
    
         
            +
              run_tests
         
     | 
| 
      
 11 
     | 
    
         
            +
              puts 'Done recording. Please review the new cassettes.'
         
     | 
| 
      
 12 
     | 
    
         
            +
            end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            def record_for_providers(providers, cassette_dir) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
         
     | 
| 
      
 15 
     | 
    
         
            +
              # Get the list of available providers from RubyLLM itself
         
     | 
| 
      
 16 
     | 
    
         
            +
              all_providers = RubyLLM::Provider.providers.keys.map(&:to_s)
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              # Check for valid providers
         
     | 
| 
      
 19 
     | 
    
         
            +
              if providers.empty?
         
     | 
| 
      
 20 
     | 
    
         
            +
                puts "Please specify providers or 'all'. Example: rake vcr:record[openai,anthropic]"
         
     | 
| 
      
 21 
     | 
    
         
            +
                puts "Available providers: #{all_providers.join(', ')}"
         
     | 
| 
      
 22 
     | 
    
         
            +
                return
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              invalid_providers = providers - all_providers
         
     | 
| 
      
 26 
     | 
    
         
            +
              if invalid_providers.any?
         
     | 
| 
      
 27 
     | 
    
         
            +
                puts "Invalid providers: #{invalid_providers.join(', ')}"
         
     | 
| 
      
 28 
     | 
    
         
            +
                puts "Available providers: #{all_providers.join(', ')}"
         
     | 
| 
      
 29 
     | 
    
         
            +
                return
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              # Get URL patterns from the providers themselves
         
     | 
| 
      
 33 
     | 
    
         
            +
              provider_patterns = get_provider_patterns(providers)
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
              puts "Finding cassettes for providers: #{providers.join(', ')}"
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              # Find and delete matching cassettes
         
     | 
| 
      
 38 
     | 
    
         
            +
              cassettes_to_delete = find_matching_cassettes(cassette_dir, provider_patterns)
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
              if cassettes_to_delete.empty?
         
     | 
| 
      
 41 
     | 
    
         
            +
                puts 'No cassettes found for the specified providers.'
         
     | 
| 
      
 42 
     | 
    
         
            +
                puts 'Running tests to record new cassettes...'
         
     | 
| 
      
 43 
     | 
    
         
            +
              else
         
     | 
| 
      
 44 
     | 
    
         
            +
                delete_cassettes(cassettes_to_delete)
         
     | 
| 
      
 45 
     | 
    
         
            +
                puts "\nRunning tests to record new cassettes..."
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
              run_tests
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
              puts "\nDone recording cassettes for #{providers.join(', ')}."
         
     | 
| 
      
 51 
     | 
    
         
            +
              puts 'Please review the updated cassettes for sensitive information.'
         
     | 
| 
      
 52 
     | 
    
         
            +
            end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
            def get_provider_patterns(providers) # rubocop:disable Metrics/MethodLength
         
     | 
| 
      
 55 
     | 
    
         
            +
              provider_patterns = {}
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
              providers.each do |provider_name|
         
     | 
| 
      
 58 
     | 
    
         
            +
                provider_module = RubyLLM::Provider.providers[provider_name.to_sym]
         
     | 
| 
      
 59 
     | 
    
         
            +
                next unless provider_module
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                # Extract the base URL from the provider's api_base method
         
     | 
| 
      
 62 
     | 
    
         
            +
                api_base = provider_module.api_base.to_s
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                # Create a regex pattern from the domain
         
     | 
| 
      
 65 
     | 
    
         
            +
                next unless api_base && !api_base.empty?
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                domain = URI.parse(api_base).host
         
     | 
| 
      
 68 
     | 
    
         
            +
                pattern = Regexp.new(Regexp.escape(domain))
         
     | 
| 
      
 69 
     | 
    
         
            +
                provider_patterns[provider_name] = pattern
         
     | 
| 
      
 70 
     | 
    
         
            +
              end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
              provider_patterns
         
     | 
| 
      
 73 
     | 
    
         
            +
            end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
            def find_matching_cassettes(dir, patterns)
         
     | 
| 
      
 76 
     | 
    
         
            +
              cassettes = []
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
              Dir.glob("#{dir}/**/*.yml").each do |file|
         
     | 
| 
      
 79 
     | 
    
         
            +
                content = File.read(file)
         
     | 
| 
      
 80 
     | 
    
         
            +
                cassettes << file if patterns.values.any? { |pattern| content.match?(pattern) }
         
     | 
| 
      
 81 
     | 
    
         
            +
              end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
              cassettes
         
     | 
| 
      
 84 
     | 
    
         
            +
            end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
            def delete_cassettes(cassettes)
         
     | 
| 
      
 87 
     | 
    
         
            +
              puts "Deleting #{cassettes.size} cassettes for re-recording:"
         
     | 
| 
      
 88 
     | 
    
         
            +
              cassettes.each do |file|
         
     | 
| 
      
 89 
     | 
    
         
            +
                puts "  - #{File.basename(file)}"
         
     | 
| 
      
 90 
     | 
    
         
            +
                File.delete(file)
         
     | 
| 
      
 91 
     | 
    
         
            +
              end
         
     | 
| 
      
 92 
     | 
    
         
            +
            end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
            def run_tests
         
     | 
| 
      
 95 
     | 
    
         
            +
              system('bundle exec rspec') || abort('Tests failed')
         
     | 
| 
      
 96 
     | 
    
         
            +
            end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
            namespace :vcr do
         
     | 
| 
      
 99 
     | 
    
         
            +
              desc 'Record VCR cassettes (rake vcr:record[all] or vcr:record[openai,anthropic])'
         
     | 
| 
      
 100 
     | 
    
         
            +
              task :record, [:providers] do |_, args|
         
     | 
| 
      
 101 
     | 
    
         
            +
                require 'fileutils'
         
     | 
| 
      
 102 
     | 
    
         
            +
                require 'ruby_llm'
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                providers = (args[:providers] || '').downcase.split(',')
         
     | 
| 
      
 105 
     | 
    
         
            +
                cassette_dir = 'spec/fixtures/vcr_cassettes'
         
     | 
| 
      
 106 
     | 
    
         
            +
                FileUtils.mkdir_p(cassette_dir)
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                if providers.include?('all')
         
     | 
| 
      
 109 
     | 
    
         
            +
                  record_all_cassettes(cassette_dir)
         
     | 
| 
      
 110 
     | 
    
         
            +
                else
         
     | 
| 
      
 111 
     | 
    
         
            +
                  record_for_providers(providers, cassette_dir)
         
     | 
| 
      
 112 
     | 
    
         
            +
                end
         
     | 
| 
      
 113 
     | 
    
         
            +
              end
         
     | 
| 
      
 114 
     | 
    
         
            +
            end
         
     | 
    
        data/ruby_llm.gemspec
    CHANGED
    
    | 
         @@ -35,6 +35,7 @@ Gem::Specification.new do |spec| 
     | 
|
| 
       35 
35 
     | 
    
         
             
              spec.require_paths = ['lib']
         
     | 
| 
       36 
36 
     | 
    
         | 
| 
       37 
37 
     | 
    
         
             
              # Runtime dependencies
         
     | 
| 
      
 38 
     | 
    
         
            +
              spec.add_dependency 'base64'
         
     | 
| 
       38 
39 
     | 
    
         
             
              spec.add_dependency 'event_stream_parser', '~> 1'
         
     | 
| 
       39 
40 
     | 
    
         
             
              spec.add_dependency 'faraday', '~> 2'
         
     | 
| 
       40 
41 
     | 
    
         
             
              spec.add_dependency 'faraday-multipart', '~> 1'
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,15 +1,29 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: ruby_llm
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version:  
     | 
| 
      
 4 
     | 
    
         
            +
              version: 1.0.1
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Carmine Paolino
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire:
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: exe
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2025- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2025-03-23 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
      
 13 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 14 
     | 
    
         
            +
              name: base64
         
     | 
| 
      
 15 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 16 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 17 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 18 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 19 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 20 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 21 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 22 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 23 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 24 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 25 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 26 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
       13 
27 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
28 
     | 
    
         
             
              name: event_stream_parser
         
     | 
| 
       15 
29 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
         @@ -96,9 +110,9 @@ files: 
     | 
|
| 
       96 
110 
     | 
    
         
             
            - ".gitignore"
         
     | 
| 
       97 
111 
     | 
    
         
             
            - ".overcommit.yml"
         
     | 
| 
       98 
112 
     | 
    
         
             
            - ".rspec"
         
     | 
| 
       99 
     | 
    
         
            -
            - ".rspec_status"
         
     | 
| 
       100 
113 
     | 
    
         
             
            - ".rubocop.yml"
         
     | 
| 
       101 
114 
     | 
    
         
             
            - ".yardopts"
         
     | 
| 
      
 115 
     | 
    
         
            +
            - CONTRIBUTING.md
         
     | 
| 
       102 
116 
     | 
    
         
             
            - Gemfile
         
     | 
| 
       103 
117 
     | 
    
         
             
            - LICENSE
         
     | 
| 
       104 
118 
     | 
    
         
             
            - README.md
         
     | 
| 
         @@ -153,6 +167,7 @@ files: 
     | 
|
| 
       153 
167 
     | 
    
         
             
            - lib/ruby_llm/tool_call.rb
         
     | 
| 
       154 
168 
     | 
    
         
             
            - lib/ruby_llm/version.rb
         
     | 
| 
       155 
169 
     | 
    
         
             
            - lib/tasks/models.rake
         
     | 
| 
      
 170 
     | 
    
         
            +
            - lib/tasks/vcr.rake
         
     | 
| 
       156 
171 
     | 
    
         
             
            - ruby_llm.gemspec
         
     | 
| 
       157 
172 
     | 
    
         
             
            homepage: https://rubyllm.com
         
     | 
| 
       158 
173 
     | 
    
         
             
            licenses:
         
     | 
    
        data/.rspec_status
    DELETED
    
    | 
         @@ -1,50 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            example_id                                         | status | run_time        |
         
     | 
| 
       2 
     | 
    
         
            -
            -------------------------------------------------- | ------ | --------------- |
         
     | 
| 
       3 
     | 
    
         
            -
            ./spec/ruby_llm/active_record/acts_as_spec.rb[1:1] | passed | 3.38 seconds    |
         
     | 
| 
       4 
     | 
    
         
            -
            ./spec/ruby_llm/active_record/acts_as_spec.rb[1:2] | passed | 2.48 seconds    |
         
     | 
| 
       5 
     | 
    
         
            -
            ./spec/ruby_llm/chat_content_spec.rb[1:1:1]        | passed | 2.74 seconds    |
         
     | 
| 
       6 
     | 
    
         
            -
            ./spec/ruby_llm/chat_content_spec.rb[1:1:2]        | passed | 1.29 seconds    |
         
     | 
| 
       7 
     | 
    
         
            -
            ./spec/ruby_llm/chat_content_spec.rb[1:1:3]        | passed | 2.54 seconds    |
         
     | 
| 
       8 
     | 
    
         
            -
            ./spec/ruby_llm/chat_content_spec.rb[1:2:1]        | passed | 2.77 seconds    |
         
     | 
| 
       9 
     | 
    
         
            -
            ./spec/ruby_llm/chat_content_spec.rb[1:2:2]        | passed | 2.1 seconds     |
         
     | 
| 
       10 
     | 
    
         
            -
            ./spec/ruby_llm/chat_pdf_spec.rb[1:1:1]            | passed | 7.75 seconds    |
         
     | 
| 
       11 
     | 
    
         
            -
            ./spec/ruby_llm/chat_pdf_spec.rb[1:1:2]            | passed | 13.88 seconds   |
         
     | 
| 
       12 
     | 
    
         
            -
            ./spec/ruby_llm/chat_spec.rb[1:1:1:1]              | passed | 1.02 seconds    |
         
     | 
| 
       13 
     | 
    
         
            -
            ./spec/ruby_llm/chat_spec.rb[1:1:1:2]              | passed | 3.95 seconds    |
         
     | 
| 
       14 
     | 
    
         
            -
            ./spec/ruby_llm/chat_spec.rb[1:1:2:1]              | passed | 0.4854 seconds  |
         
     | 
| 
       15 
     | 
    
         
            -
            ./spec/ruby_llm/chat_spec.rb[1:1:2:2]              | passed | 1.37 seconds    |
         
     | 
| 
       16 
     | 
    
         
            -
            ./spec/ruby_llm/chat_spec.rb[1:1:3:1]              | passed | 7.34 seconds    |
         
     | 
| 
       17 
     | 
    
         
            -
            ./spec/ruby_llm/chat_spec.rb[1:1:3:2]              | passed | 19.22 seconds   |
         
     | 
| 
       18 
     | 
    
         
            -
            ./spec/ruby_llm/chat_spec.rb[1:1:4:1]              | passed | 3.15 seconds    |
         
     | 
| 
       19 
     | 
    
         
            -
            ./spec/ruby_llm/chat_spec.rb[1:1:4:2]              | passed | 2.51 seconds    |
         
     | 
| 
       20 
     | 
    
         
            -
            ./spec/ruby_llm/chat_streaming_spec.rb[1:1:1:1]    | passed | 0.91374 seconds |
         
     | 
| 
       21 
     | 
    
         
            -
            ./spec/ruby_llm/chat_streaming_spec.rb[1:1:2:1]    | passed | 0.50088 seconds |
         
     | 
| 
       22 
     | 
    
         
            -
            ./spec/ruby_llm/chat_streaming_spec.rb[1:1:3:1]    | passed | 5.69 seconds    |
         
     | 
| 
       23 
     | 
    
         
            -
            ./spec/ruby_llm/chat_streaming_spec.rb[1:1:4:1]    | passed | 1.22 seconds    |
         
     | 
| 
       24 
     | 
    
         
            -
            ./spec/ruby_llm/chat_tools_spec.rb[1:1:1]          | passed | 4.26 seconds    |
         
     | 
| 
       25 
     | 
    
         
            -
            ./spec/ruby_llm/chat_tools_spec.rb[1:1:2]          | passed | 6.16 seconds    |
         
     | 
| 
       26 
     | 
    
         
            -
            ./spec/ruby_llm/chat_tools_spec.rb[1:1:3]          | passed | 11.15 seconds   |
         
     | 
| 
       27 
     | 
    
         
            -
            ./spec/ruby_llm/chat_tools_spec.rb[1:1:4]          | passed | 1.3 seconds     |
         
     | 
| 
       28 
     | 
    
         
            -
            ./spec/ruby_llm/chat_tools_spec.rb[1:1:5]          | passed | 2.71 seconds    |
         
     | 
| 
       29 
     | 
    
         
            -
            ./spec/ruby_llm/chat_tools_spec.rb[1:1:6]          | passed | 2.43 seconds    |
         
     | 
| 
       30 
     | 
    
         
            -
            ./spec/ruby_llm/chat_tools_spec.rb[1:1:7]          | passed | 2.71 seconds    |
         
     | 
| 
       31 
     | 
    
         
            -
            ./spec/ruby_llm/chat_tools_spec.rb[1:1:8]          | passed | 4 seconds       |
         
     | 
| 
       32 
     | 
    
         
            -
            ./spec/ruby_llm/chat_tools_spec.rb[1:1:9]          | passed | 2.81 seconds    |
         
     | 
| 
       33 
     | 
    
         
            -
            ./spec/ruby_llm/embeddings_spec.rb[1:1:1:1]        | passed | 0.33357 seconds |
         
     | 
| 
       34 
     | 
    
         
            -
            ./spec/ruby_llm/embeddings_spec.rb[1:1:1:2]        | passed | 0.43632 seconds |
         
     | 
| 
       35 
     | 
    
         
            -
            ./spec/ruby_llm/embeddings_spec.rb[1:1:2:1]        | passed | 0.65614 seconds |
         
     | 
| 
       36 
     | 
    
         
            -
            ./spec/ruby_llm/embeddings_spec.rb[1:1:2:2]        | passed | 2.16 seconds    |
         
     | 
| 
       37 
     | 
    
         
            -
            ./spec/ruby_llm/error_handling_spec.rb[1:1]        | passed | 0.29366 seconds |
         
     | 
| 
       38 
     | 
    
         
            -
            ./spec/ruby_llm/image_generation_spec.rb[1:1:1]    | passed | 14.16 seconds   |
         
     | 
| 
       39 
     | 
    
         
            -
            ./spec/ruby_llm/image_generation_spec.rb[1:1:2]    | passed | 16.22 seconds   |
         
     | 
| 
       40 
     | 
    
         
            -
            ./spec/ruby_llm/image_generation_spec.rb[1:1:3]    | passed | 9.1 seconds     |
         
     | 
| 
       41 
     | 
    
         
            -
            ./spec/ruby_llm/image_generation_spec.rb[1:1:4]    | passed | 0.00138 seconds |
         
     | 
| 
       42 
     | 
    
         
            -
            ./spec/ruby_llm/models_spec.rb[1:1:1]              | passed | 0.01071 seconds |
         
     | 
| 
       43 
     | 
    
         
            -
            ./spec/ruby_llm/models_spec.rb[1:1:2]              | passed | 0.00056 seconds |
         
     | 
| 
       44 
     | 
    
         
            -
            ./spec/ruby_llm/models_spec.rb[1:1:3]              | passed | 0.00336 seconds |
         
     | 
| 
       45 
     | 
    
         
            -
            ./spec/ruby_llm/models_spec.rb[1:2:1]              | passed | 0.00016 seconds |
         
     | 
| 
       46 
     | 
    
         
            -
            ./spec/ruby_llm/models_spec.rb[1:2:2]              | passed | 0.00085 seconds |
         
     | 
| 
       47 
     | 
    
         
            -
            ./spec/ruby_llm/models_spec.rb[1:3:1]              | passed | 1.44 seconds    |
         
     | 
| 
       48 
     | 
    
         
            -
            ./spec/ruby_llm/models_spec.rb[1:3:2]              | passed | 1.23 seconds    |
         
     | 
| 
       49 
     | 
    
         
            -
            ./spec/ruby_llm/models_spec.rb[1:4:1]              | passed | 0.0003 seconds  |
         
     | 
| 
       50 
     | 
    
         
            -
            ./spec/ruby_llm/models_spec.rb[1:4:2]              | passed | 0.00175 seconds |
         
     |