ankane/searchkick · error · Searchkick::Error

The `elasticsearch` gem must be 8+

Error message

The `elasticsearch` gem must be 8+

What it means

Searchkick's knn option builds a pure vector (kNN) query and only supports it on a match-all term ('*'). Combining a text term with knn in a single query would silently drop one of the two, so set_knn raises ArgumentError and requires hybrid text+vector search to be expressed as Searchkick.multi_search with a keyword query plus a separate vector query that you merge client-side.

Source

Thrown at lib/searchkick.rb:98

        elsif defined?(OpenSearch::Client)
          :opensearch
        elsif defined?(Elasticsearch::Client)
          :elasticsearch
        else
          raise Error, "No client found - install the `elasticsearch` or `opensearch-ruby` gem"
        end

      if client_type == :opensearch
        OpenSearch::Client.new({
          url: ENV["OPENSEARCH_URL"],
          transport_options: {request: {timeout: timeout}},
          retry_on_failure: 2
        }.deep_merge(client_options)) do |f|
          f.use Searchkick::Middleware
          f.request :aws_sigv4, signer_middleware_aws_params if aws_credentials
        end
      else
        raise Error, "The `elasticsearch` gem must be 8+" if Elasticsearch::VERSION.to_i < 8

        Elasticsearch::Client.new({
          url: ENV["ELASTICSEARCH_URL"],
          transport_options: {request: {timeout: timeout}},
          retry_on_failure: 2
        }.deep_merge(client_options)) do |f|
          f.use Searchkick::Middleware
          f.request :aws_sigv4, signer_middleware_aws_params if aws_credentials
        end
      end
    end
  end

  def self.env
    @env ||= ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
  end

  def self.search_timeout

View on GitHub (pinned to 93e901a75b)

Solutions

  1. Use Searchkick.multi_search with two queries - one keyword, one vector - and interleave/merge the results yourself
  2. For pure vector search, pass term '*': Product.search('*', knn: {field: :embedding, vector: vec, distance: 'cosine'})
  3. To combine lexical filters with vectors, keep term '*' and use where: (it becomes the knn filter), not a text term

Example fix

# before
Product.search('apple', knn: {field: :embedding, vector: vec, distance: 'cosine'})
# => ArgumentError: Use Searchkick.multi_search for hybrid search

# after
queries = [
  Product.search('apple'),
  Product.search('*', knn: {field: :embedding, vector: vec, distance: 'cosine'})
]
keyword, vector = Searchkick.multi_search(queries)
# merge/rank keyword.results and vector.results yourself
Defensive patterns

Strategy: validation

Validate before calling

def hybrid_search(term, vector)
  if term.nil? || term == '*'
    [Product.search('*', knn: {field: :embedding, vector: vector, distance: 'cosine'})]
  else
    [
      Product.search(term),
      Product.search('*', knn: {field: :embedding, vector: vector, distance: 'cosine'})
    ]
  end
end
Searchkick.multi_search(hybrid_search(term, vec))

Try / catch

begin
  Product.search(term, knn: knn_opts)
rescue ArgumentError
  # fall back to multi_search hybrid path
  Searchkick.multi_search([Product.search(term), Product.search('*', knn: knn_opts)])
end

Prevention

When it happens

Trigger: Product.search('apple', knn: {field: :embedding, vector: vec, distance: 'cosine'}) - any term other than '*' raises. The same call with '*' as the term works, because where-filters become the knn filter clause instead of a text query.

Common situations: Adding semantic/vector ranking to an existing keyword search and passing knn: alongside the user query; porting raw Elasticsearch DSL (bool + knn in one request) to Searchkick; assuming Searchkick performs native hybrid scoring in one request.

Related errors


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