Author search-engine relevance regression tests for Elasticsearch, OpenSearch, and Apache Solr. Core workflow on the Elasticsearch Ranking Evaluation API (`POST {index}/_rank_eval`) - judgment lists (query + expected docs at ranks), per-query metrics (Precision@K, Recall@K, MRR, DCG, ERR), reproducible test corpora; pair with Quepid + Splainer for interactive judgment authoring. Per-engine references cover the OpenSearch delta (Search Relevance Workbench, neural query DSL, hybrid BM25 + neural pipelines, ES-to-OS migration parity) and the Apache Solr delta (no _rank_eval: debugQuery score explain, LTR feature/model store REST, eDisMax qf/pf/mm tuning, external nDCG harness). Use before changing analyzers, synonyms, boosts, or query templates on an Elasticsearch, OpenSearch, or Solr index that serves user-facing search, so the NDCG / MRR baseline is captured first.
74
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Per the OpenSearch search-relevance docs, _rank_eval is
Elasticsearch-fork-compatible. The OpenSearch-specific surfaces
worth testing: neural search, hybrid query, and the Search
Relevance Workbench UI.
_rank_evalOpenSearch's _rank_eval accepts the same JSON as Elasticsearch's -
endpoint + metrics identical per the OpenSearch search-relevance
docs. The main skill's Step 1 judgment list (CSV
(query, doc_id, rating) on the 4-point scale) and Step 3 request
body are reusable verbatim against an OpenSearch cluster. One extra
sourcing option exists here: the Search Relevance Workbench's
pairwise judgment UI + bulk import (Step 3 below).
Per the OpenSearch search-relevance docs, the Search Relevance Workbench plugin (UI in OpenSearch Dashboards) provides:
Workbench experiments are the easiest pre-tuning baseline-and-compare workflow.
OpenSearch supports k-NN vector search natively. Test setup:
PUT my_index
{
"settings": { "index.knn": true },
"mappings": {
"properties": {
"embedding": {
"type": "knn_vector",
"dimension": 768,
"method": { "name": "hnsw", "engine": "lucene" }
},
"title": { "type": "text" }
}
}
}Query:
POST my_index/_search
{
"query": {
"neural": {
"embedding": {
"query_text": "running shoes for marathon",
"model_id": "<sentence-transformer-model>",
"k": 10
}
}
}
}Test that neural results meet a recall@10 target against a held-out ground truth set:
def test_neural_recall_at_10():
ground_truth = load_ground_truth("tests/marathon_queries.json")
for query in ground_truth["queries"]:
results = neural_search(query["text"], k=10)
retrieved_ids = {r["_id"] for r in results}
relevant_ids = set(query["relevant_ids"])
recall = len(retrieved_ids & relevant_ids) / len(relevant_ids)
assert recall >= 0.85, f"Recall {recall:.2f} below 0.85 for query: {query['text']}"Pair with vector-search-recall-tests for HNSW parameter tuning.
POST my_index/_search?search_pipeline=hybrid_pipeline
{
"query": {
"hybrid": {
"queries": [
{ "match": { "title": "running shoes" } },
{ "neural": { "embedding": { "query_text": "running shoes", "k": 10 } } }
]
}
}
}Hybrid weighting set up via search pipeline:
PUT _search/pipeline/hybrid_pipeline
{
"phase_results_processors": [
{
"normalization-processor": {
"normalization": { "technique": "min_max" },
"combination": {
"technique": "arithmetic_mean",
"parameters": { "weights": [0.3, 0.7] }
}
}
}
]
}Test that hybrid weights matter:
def test_hybrid_weight_change_shifts_results():
bm25_heavy_results = search_with_pipeline("hybrid_pipeline_03_07") # 0.3 BM25 / 0.7 neural
neural_heavy_results = search_with_pipeline("hybrid_pipeline_07_03")
assert bm25_heavy_results != neural_heavy_resultsIdentical to the main skill's Step 5 - run it against the OpenSearch
cluster with its own pinned baseline file (e.g. tests/baseline-os.json).
Run the same judgment list against both clusters; metric scores should be within ε:
def test_es_os_parity():
es_score = rank_eval_against("http://es:9200/products", judgments)
os_score = rank_eval_against("http://os:9200/products", judgments)
delta = abs(es_score - os_score)
assert delta < 0.05, f"ES vs OS NDCG diff {delta:.2f} > 0.05"If the index settings (analyzers, mappings) are identical, scores should match. Differences point to subtle config drift.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Test only BM25 path when neural enabled | Neural regression slips silently | Step 3 + Step 4 |
| Use neural without warm-up for tests | Cold cache → flaky latency tests | Warm before measuring |
| Set hybrid weights without testing both extremes | Subtle BM25/neural balance change ships | Step 4 |
| Skip migration parity test | OS deviation from ES surfaces in prod | Step 6 |
| Trust default analyzers across ES/OS | Subtle stemmer differences | Pin analyzer config |
_rank_eval.elasticsearch-relevance-tests SKILL.md - compatible Rank
Eval API + judgment formatvector-search-recall-tests -
vector search precision/recall tooling