Script score query
Uses a script to provide a custom score for returned documents.
The script_score
query is useful if, for example, a scoring function is expensive and you only need to calculate the score of a filtered set of documents.
Example request
The following script_score
query assigns each returned document a score equal to the my-int
field value divided by 10
.
GET /_search
{
"query": {
"script_score": {
"query": {
"match": { "message": "elasticsearch" }
},
"script": {
"source": "doc['my-int'].value / 10 "
}
}
}
}
Top-level parameters for script_score
query
(Required, query object) Query used to return documents.
script
(Required, script object) Script used to compute the score of documents returned by the query
.
Final relevance scores from the script_score
query cannot be negative. To support certain search optimizations, Lucene requires scores be positive or 0
.
min_score
(Optional, float) Documents with a score lower than this floating point number are excluded from the search results.
boost
(Optional, float) Documents’ scores produced by script
are multiplied by boost
to produce final documents’ scores. Defaults to 1.0
.
Notes
Use relevance scores in a script
Within a script, you can access the _score
variable which represents the current relevance score of a document.
Predefined functions
You can use any of the available painless functions in your script
. You can also use the following predefined functions to customize scoring:
- Saturation
- Sigmoid
- Random score function
- Decay functions for numeric fields
- Decay functions for geo fields
- Decay functions for date fields
- Functions for vector fields
We suggest using these predefined functions instead of writing your own. These functions take advantage of efficiencies from Elasticsearch’ internal mechanisms.
Saturation
saturation(value,k) = value/(k + value)
"script" : {
"source" : "saturation(doc['my-int'].value, 1)"
}
Sigmoid
sigmoid(value, k, a) = value^a/ (k^a + value^a)
"script" : {
"source" : "sigmoid(doc['my-int'].value, 2, 1)"
}
Random score function
random_score
function generates scores that are uniformly distributed from 0 up to but not including 1.
randomScore
function has the following syntax: randomScore(<seed>, <fieldName>)
. It has a required parameter - seed
as an integer value, and an optional parameter - fieldName
as a string value.
"script" : {
"source" : "randomScore(100, '_seq_no')"
}
If the fieldName
parameter is omitted, the internal Lucene document ids will be used as a source of randomness. This is very efficient, but unfortunately not reproducible since documents might be renumbered by merges.
"script" : {
"source" : "randomScore(100)"
}
Note that documents that are within the same shard and have the same value for field will get the same score, so it is usually desirable to use a field that has unique values for all documents across a shard. A good default choice might be to use the _seq_no
field, whose only drawback is that scores will change if the document is updated since update operations also update the value of the _seq_no
field.
Decay functions for numeric fields
You can read more about decay functions here.
double decayNumericLinear(double origin, double scale, double offset, double decay, double docValue)
double decayNumericExp(double origin, double scale, double offset, double decay, double docValue)
double decayNumericGauss(double origin, double scale, double offset, double decay, double docValue)
"script" : {
"source" : "decayNumericLinear(params.origin, params.scale, params.offset, params.decay, doc['dval'].value)",
"params": {
"origin": 20,
"scale": 10,
"decay" : 0.5,
"offset" : 0
}
}
Using |
Decay functions for geo fields
double decayGeoLinear(String originStr, String scaleStr, String offsetStr, double decay, GeoPoint docValue)
double decayGeoExp(String originStr, String scaleStr, String offsetStr, double decay, GeoPoint docValue)
double decayGeoGauss(String originStr, String scaleStr, String offsetStr, double decay, GeoPoint docValue)
"script" : {
"source" : "decayGeoExp(params.origin, params.scale, params.offset, params.decay, doc['location'].value)",
"params": {
"origin": "40, -70.12",
"scale": "200km",
"offset": "0km",
"decay" : 0.2
}
}
Decay functions for date fields
double decayDateLinear(String originStr, String scaleStr, String offsetStr, double decay, JodaCompatibleZonedDateTime docValueDate)
double decayDateExp(String originStr, String scaleStr, String offsetStr, double decay, JodaCompatibleZonedDateTime docValueDate)
double decayDateGauss(String originStr, String scaleStr, String offsetStr, double decay, JodaCompatibleZonedDateTime docValueDate)
"script" : {
"source" : "decayDateGauss(params.origin, params.scale, params.offset, params.decay, doc['date'].value)",
"params": {
"origin": "2008-01-01T01:00:00Z",
"scale": "1h",
"offset" : "0",
"decay" : 0.5
}
}
Decay functions on dates are limited to dates in the default format and default time zone. Also calculations with now
are not supported.
Functions for vector fields
Functions for vector fields are accessible through script_score
query.
Allow expensive queries
Script score queries will not be executed if search.allow_expensive_queries
is set to false.
Faster alternatives
The script_score
query calculates the score for every matching document, or hit. There are faster alternative query types that can efficiently skip non-competitive hits:
- If you want to boost documents on some static fields, use the
rank_feature
query. - If you want to boost documents closer to a date or geographic point, use the
distance_feature
query.
Transition from the function score query
We are deprecating the function_score
query. We recommend using the script_score
query instead.
You can implement the following functions from the function_score
query using the script_score
query:
script_score
What you used in script_score
of the Function Score query, you can copy into the Script Score query. No changes here.
weight
weight
function can be implemented in the Script Score query through the following script:
"script" : {
"source" : "params.weight * _score",
"params": {
"weight": 2
}
}
random_score
Use randomScore
function as described in random score function.
field_value_factor
field_value_factor
function can be easily implemented through script:
"script" : {
"source" : "Math.log10(doc['field'].value * params.factor)",
"params" : {
"factor" : 5
}
}
For checking if a document has a missing value, you can use doc['field'].size() == 0
. For example, this script will use a value 1
if a document doesn’t have a field field
:
"script" : {
"source" : "Math.log10((doc['field'].size() == 0 ? 1 : doc['field'].value()) * params.factor)",
"params" : {
"factor" : 5
}
}
This table lists how field_value_factor
modifiers can be implemented through a script:
Modifier | Implementation in Script Score |
---|---|
| - |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
decay
functions
The script_score
query has equivalent decay functions that can be used in script.
Functions for vector fields
During vector functions’ calculation, all matched documents are linearly scanned. Thus, expect the query time grow linearly with the number of matched documents. For this reason, we recommend to limit the number of matched documents with a query
parameter.
dense_vector
functions
Let’s create an index with a dense_vector
mapping and index a couple of documents into it.
PUT my-index-000001
{
"mappings": {
"properties": {
"my_dense_vector": {
"type": "dense_vector",
"dims": 3
},
"status" : {
"type" : "keyword"
}
}
}
}
PUT my-index-000001/_doc/1
{
"my_dense_vector": [0.5, 10, 6],
"status" : "published"
}
PUT my-index-000001/_doc/2
{
"my_dense_vector": [-0.5, 10, 10],
"status" : "published"
}
POST my-index-000001/_refresh
The cosineSimilarity
function calculates the measure of cosine similarity between a given query vector and document vectors.
GET my-index-000001/_search
{
"query": {
"script_score": {
"query" : {
"bool" : {
"filter" : {
"term" : {
"status" : "published"
}
}
}
},
"script": {
"source": "cosineSimilarity(params.query_vector, 'my_dense_vector') + 1.0",
"params": {
"query_vector": [4, 3.4, -0.2]
}
}
}
}
}
To restrict the number of documents on which script score calculation is applied, provide a filter. | |
The script adds 1.0 to the cosine similarity to prevent the score from being negative. | |
To take advantage of the script optimizations, provide a query vector as a script parameter. |
If a document’s dense vector field has a number of dimensions different from the query’s vector, an error will be thrown.
The dotProduct
function calculates the measure of dot product between a given query vector and document vectors.
GET my-index-000001/_search
{
"query": {
"script_score": {
"query" : {
"bool" : {
"filter" : {
"term" : {
"status" : "published"
}
}
}
},
"script": {
"source": """
double value = dotProduct(params.query_vector, 'my_dense_vector');
return sigmoid(1, Math.E, -value);
""",
"params": {
"query_vector": [4, 3.4, -0.2]
}
}
}
}
}
Using the standard sigmoid function prevents scores from being negative. |
The l1norm
function calculates L1 distance (Manhattan distance) between a given query vector and document vectors.
GET my-index-000001/_search
{
"query": {
"script_score": {
"query" : {
"bool" : {
"filter" : {
"term" : {
"status" : "published"
}
}
}
},
"script": {
"source": "1 / (1 + l1norm(params.queryVector, 'my_dense_vector'))",
"params": {
"queryVector": [4, 3.4, -0.2]
}
}
}
}
}
Unlike |
The l2norm
function calculates L2 distance (Euclidean distance) between a given query vector and document vectors.
GET my-index-000001/_search
{
"query": {
"script_score": {
"query" : {
"bool" : {
"filter" : {
"term" : {
"status" : "published"
}
}
}
},
"script": {
"source": "1 / (1 + l2norm(params.queryVector, 'my_dense_vector'))",
"params": {
"queryVector": [4, 3.4, -0.2]
}
}
}
}
}
If a document doesn’t have a value for a vector field on which a vector function is executed, an error will be thrown.
You can check if a document has a value for the field my_vector
by doc['my_vector'].size() == 0
. Your overall script can look like this:
"source": "doc['my_vector'].size() == 0 ? 0 : cosineSimilarity(params.queryVector, 'my_vector')"
sparse_vector
functions
Deprecated in 7.6.
The sparse_vector
type is deprecated and will be removed in 8.0.
Let’s create an index with a sparse_vector
mapping and index a couple of documents into it.
PUT my_sparse_index
{
"mappings": {
"properties": {
"my_sparse_vector": {
"type": "sparse_vector"
},
"status" : {
"type" : "keyword"
}
}
}
}
PUT my_sparse_index/_doc/1
{
"my_sparse_vector": {"2": 1.5, "15" : 2, "50": -1.1, "4545": 1.1},
"status" : "published"
}
PUT my_sparse_index/_doc/2
{
"my_sparse_vector": {"2": 2.5, "10" : 1.3, "55": -2.3, "113": 1.6},
"status" : "published"
}
POST my_sparse_index/_refresh
The cosineSimilaritySparse
function calculates cosine similarity between a given query vector and document vectors.
GET my_sparse_index/_search
{
"query": {
"script_score": {
"query" : {
"bool" : {
"filter" : {
"term" : {
"status" : "published"
}
}
}
},
"script": {
"source": "cosineSimilaritySparse(params.query_vector, 'my_sparse_vector') + 1.0",
"params": {
"query_vector": {"2": 0.5, "10" : 111.3, "50": -1.3, "113": 14.8, "4545": 156.0}
}
}
}
}
}
The dotProductSparse
function calculates dot product between a given query vector and document vectors.
GET my_sparse_index/_search
{
"query": {
"script_score": {
"query" : {
"bool" : {
"filter" : {
"term" : {
"status" : "published"
}
}
}
},
"script": {
"source": """
double value = dotProductSparse(params.query_vector, 'my_sparse_vector');
return sigmoid(1, Math.E, -value);
""",
"params": {
"query_vector": {"2": 0.5, "10" : 111.3, "50": -1.3, "113": 14.8, "4545": 156.0}
}
}
}
}
}
The l1normSparse
function calculates L1 distance between a given query vector and document vectors.
GET my_sparse_index/_search
{
"query": {
"script_score": {
"query" : {
"bool" : {
"filter" : {
"term" : {
"status" : "published"
}
}
}
},
"script": {
"source": "1 / (1 + l1normSparse(params.queryVector, 'my_sparse_vector'))",
"params": {
"queryVector": {"2": 0.5, "10" : 111.3, "50": -1.3, "113": 14.8, "4545": 156.0}
}
}
}
}
}
The l2normSparse
function calculates L2 distance between a given query vector and document vectors.
GET my_sparse_index/_search
{
"query": {
"script_score": {
"query" : {
"bool" : {
"filter" : {
"term" : {
"status" : "published"
}
}
}
},
"script": {
"source": "1 / (1 + l2normSparse(params.queryVector, 'my_sparse_vector'))",
"params": {
"queryVector": {"2": 0.5, "10" : 111.3, "50": -1.3, "113": 14.8, "4545": 156.0}
}
}
}
}
}
Explain request
Using an explain request provides an explanation of how the parts of a score were computed. The script_score
query can add its own explanation by setting the explanation
parameter:
GET /my-index-000001/_explain/0
{
"query": {
"script_score": {
"query": {
"match": { "message": "elasticsearch" }
},
"script": {
"source": """
long count = doc['count'].value;
double normalizedCount = count / 10;
if (explanation != null) {
explanation.set('normalized count = count / 10 = ' + count + ' / 10 = ' + normalizedCount);
}
return normalizedCount;
"""
}
}
}
}
Note that the explanation
will be null when using in a normal _search
request, so having a conditional guard is best practice.