ankane/searchkick · error · ArgumentError

Use Searchkick.search to search multiple models

Error message

Use Searchkick.search to search multiple models

What it means

Every Searchkick knn (vector) search needs a distance metric. It is resolved from knn[:distance] or from the field's entry in the model's searchkick knn: {field: {distance: ...}} settings; if neither is present the query cannot be built and Searchkick raises ArgumentError 'distance required' in set_knn.

Source

Thrown at lib/searchkick.rb:177

    # convert index_name into models if possible
    # this should allow for easier upgrade
    if options[:index_name] && !options[:models] && Array(options[:index_name]).all? { |v| v.respond_to?(:searchkick_index) }
      options[:models] = options.delete(:index_name)
    end

    # make Searchkick.search(models: [Product]) and Product.search equivalent
    unless klass
      models = Array(options[:models])
      if models.size == 1
        klass = models.first
        options.delete(:models)
      end
    end

    if klass
      if (options[:models] && Array(options[:models]) != [klass]) || Array(options[:index_name]).any? { |v| v.respond_to?(:searchkick_index) && v != klass }
        raise ArgumentError, "Use Searchkick.search to search multiple models"
      end
    end

    options = options.merge(block: block) if block
    Relation.new(klass, term, **options)
  end

  def self.multi_search(queries, opaque_id: nil)
    return if queries.empty?

    queries = queries.map { |q| q.send(:query) }
    event = {
      name: "Multi Search",
      body: queries.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join
    }
    ActiveSupport::Notifications.instrument("multi_search.searchkick", event) do
      MultiSearch.new(queries, opaque_id: opaque_id).perform
    end

View on GitHub (pinned to 93e901a75b)

Solutions

  1. Set distance in the model mapping so every query inherits it: searchkick knn: {embedding: {dimensions: 384, distance: 'cosine'}} (a reindex is needed for the mapping)
  2. Or pass it per query: Product.search('*', knn: {field: :embedding, vector: vec, distance: 'cosine'})
  3. Use a supported value: 'cosine', 'euclidean', 'taxicab', 'inner_product' (plus 'chebyshev' only for exact OpenSearch)

Example fix

# before
searchkick knn: {embedding: {dimensions: 384}}
Product.search('*', knn: {field: :embedding, vector: vec})
# => ArgumentError: distance required

# after
searchkick knn: {embedding: {dimensions: 384, distance: 'cosine'}}
Product.search('*', knn: {field: :embedding, vector: vec})
Defensive patterns

Strategy: validation

Validate before calling

def knn_query(vector, field: :embedding)
  distance = Product.searchkick_options.dig(:knn, field)&.fetch(:distance, nil) || 'cosine'
  Product.search('*', knn: {field: field, vector: vector, distance: distance})
end

Try / catch

begin
  Product.search('*', knn: opts)
rescue ArgumentError => e
  raise if e.message != 'distance required'
  opts = opts.merge(distance: 'cosine')
  Product.search('*', knn: opts)
end

Prevention

When it happens

Trigger: Product.search('*', knn: {field: :embedding, vector: vec}) with no :distance in either place; declaring searchkick knn: {embedding: {dimensions: 384}} on the model but forgetting distance: 'cosine'.

Common situations: First time wiring up embeddings after adding a vector column; copy-pasting model code that omitted the distance key; passing the metric under a different key name such as metric: or similarity:.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of ankane/searchkick@93e901a75b (2026-08-21). Data as JSON: /api/errors/c2b4ec76ccef9629. Report an issue: GitHub.