Ollama: Strip image data from VCR recorded requests

Posted . Visible to the public.

When testing Ollama vision requests with VCR, the recorded cassettes will contain the full base64-encoded image payloads. A single image can easily be hundreds of kilobytes; a handful of recorded interactions will balloon your repository size significantly.

The VCR configuration below solves two problems:

  1. It scrubs base64 image data from cassettes before they are written to disk, replacing each image with a placeholder string
  2. It filters Basic Auth credentials so they are never committed

It also registers a custom request matcher so that playback still matches recorded cassettes even though the image data was stripped.

# spec/support/vcr.rb
VCR.configure do |config|
  config.cassette_library_dir = Rails.root.join('spec', 'vcr')
  config.hook_into :webmock
  config.filter_sensitive_data('<BASIC_AUTH_OLLAMA_API_KEY>') do
    Base64.strict_encode64("#{Rails.application.credentials.ollama_basic_auth_user!}:#{Rails.application.credentials.ollama_basic_auth_password!}")
  end
  config.ignore_localhost = true

  scrub_base64_images = lambda do |body|
    return body if body.blank?
    json = JSON.parse(body)
    if json.is_a?(Hash) && json['messages'].is_a?(Array)
      json['messages'].each do |msg|
        if msg['images'].is_a?(Array)
          msg['images'] = msg['images'].map { |_img| "<BASE64_IMAGE_PLACEHOLDER>" }
        end
      end
    end
    json
  rescue JSON::ParserError
    body
  end

  config.before_record do |interaction|
    cleaned_json = scrub_base64_images.call(interaction.request.body)
    interaction.request.body = cleaned_json.to_json if cleaned_json.is_a?(Hash)
  end

  config.register_request_matcher :body_ignoring_images do |live_request, recorded_request|
    scrub_base64_images.call(live_request.body) == JSON.parse(recorded_request.body)
  end
end

RSpec.configure do |config|
  config.around(:each, :vcr) do |example|
    name = example.metadata[:full_description].split(/\s+/, 2).join('/').underscore.gsub(/[^\w\/]+/, '_')
    options = example.metadata.slice(:record, :match_requests_on).except(:example_group)
    options[:match_requests_on] ||= [:method, :uri, :body_ignoring_images]

    VCR.use_cassette(name, options) { example.call }
  end
end
Profile picture of Michael Leimstädtner
Michael Leimstädtner
License
Source code in this card is licensed under the MIT License.
Posted by Michael Leimstädtner to makandra dev (2026-04-16 14:08)