{"record":{"id":"68cf4bb2fdf4ec8d","repo":"ankane/searchkick","slug":"the-elasticsearch-gem-must-be-8","errorCode":null,"errorMessage":"The `elasticsearch` gem must be 8+","messagePattern":"The `elasticsearch` gem must be 8\\+","errorType":"exception","errorClass":"Searchkick::Error","httpStatus":null,"severity":"error","filePath":"lib/searchkick.rb","lineNumber":98,"sourceCode":"        elsif defined?(OpenSearch::Client)\n          :opensearch\n        elsif defined?(Elasticsearch::Client)\n          :elasticsearch\n        else\n          raise Error, \"No client found - install the `elasticsearch` or `opensearch-ruby` gem\"\n        end\n\n      if client_type == :opensearch\n        OpenSearch::Client.new({\n          url: ENV[\"OPENSEARCH_URL\"],\n          transport_options: {request: {timeout: timeout}},\n          retry_on_failure: 2\n        }.deep_merge(client_options)) do |f|\n          f.use Searchkick::Middleware\n          f.request :aws_sigv4, signer_middleware_aws_params if aws_credentials\n        end\n      else\n        raise Error, \"The `elasticsearch` gem must be 8+\" if Elasticsearch::VERSION.to_i < 8\n\n        Elasticsearch::Client.new({\n          url: ENV[\"ELASTICSEARCH_URL\"],\n          transport_options: {request: {timeout: timeout}},\n          retry_on_failure: 2\n        }.deep_merge(client_options)) do |f|\n          f.use Searchkick::Middleware\n          f.request :aws_sigv4, signer_middleware_aws_params if aws_credentials\n        end\n      end\n    end\n  end\n\n  def self.env\n    @env ||= ENV[\"RAILS_ENV\"] || ENV[\"RACK_ENV\"] || \"development\"\n  end\n\n  def self.search_timeout","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/ankane/searchkick/blob/93e901a75b11a25101668a616e006b158251b16e/lib/searchkick.rb#L80-L116","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use Searchkick.multi_search with two queries - one keyword, one vector - and interleave/merge the results yourself","For pure vector search, pass term '*': Product.search('*', knn: {field: :embedding, vector: vec, distance: 'cosine'})","To combine lexical filters with vectors, keep term '*' and use where: (it becomes the knn filter), not a text term"],"exampleFix":"# before\nProduct.search('apple', knn: {field: :embedding, vector: vec, distance: 'cosine'})\n# => ArgumentError: Use Searchkick.multi_search for hybrid search\n\n# after\nqueries = [\n  Product.search('apple'),\n  Product.search('*', knn: {field: :embedding, vector: vec, distance: 'cosine'})\n]\nkeyword, vector = Searchkick.multi_search(queries)\n# merge/rank keyword.results and vector.results yourself","handlingStrategy":"validation","validationCode":"def hybrid_search(term, vector)\n  if term.nil? || term == '*'\n    [Product.search('*', knn: {field: :embedding, vector: vector, distance: 'cosine'})]\n  else\n    [\n      Product.search(term),\n      Product.search('*', knn: {field: :embedding, vector: vector, distance: 'cosine'})\n    ]\n  end\nend\nSearchkick.multi_search(hybrid_search(term, vec))","typeGuard":null,"tryCatchPattern":"begin\n  Product.search(term, knn: knn_opts)\nrescue ArgumentError\n  # fall back to multi_search hybrid path\n  Searchkick.multi_search([Product.search(term), Product.search('*', knn: knn_opts)])\nend","preventionTips":["Keep vector search behind a helper that forces term '*' for the knn leg","Centralize hybrid search in one service object so no call site mixes term with knn:","Document the merge strategy (interleave, rrf) next to the helper"],"tags":["searchkick","knn","vector-search","hybrid-search","argumenterror"],"backgroundTag":"unsupported-query-combination","analyzedSha":"93e901a75b11a25101668a616e006b158251b16e","analyzedAt":"2026-08-21T19:06:23.767Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}