Elasticsearch has a really nifty feature called function_score that allows you to modify the scores of documents. It took me a while to figure out the exact syntax of function_score , so I’m sharing it here.

I’m building a WordPress product recommendation plugin that finds related products. It basically does something like this:

size: 4,

query: {

match: { title: article.title },

}

random_score

This basic query got the job done. But, I wanted slightly different results every time the user refreshed the page. To do this I added a function_score query with a random_score function:

size: 4,

query: {

function_score: {

functions: [

{

random_score: {},

},

],

score_mode: 'avg',

query: {

match: { title: article.title },

}

}

}

The random_score function generates a random value between 0 and 1 . Then the score of my documents is averaged with the random_score . There are other score_modes like multiply , min , sum , etc.

field_value_factor

Later, I decided that users would be more likely to click on products that are on sale, so I used a field_value_factor function to boost fields by their discount.

size: 4,

query: {

function_score: {

functions: [

{

field_value_factor: {

field: 'price_difference_percent',

missing: 0

},

},

{

random_score: {},

},

],

score_mode: 'avg',

query: {

match: { title: article.title },

}

}

}

The field_value_factor takes the price_difference_percent value of a document and uses that as part of the score. A price_difference_percent of 0 means 0% off the original price , whereas 0.9 means 90% off the original price. This worked out pretty nicely, since random_score generates values between 0 and 1.

I tried using likes_count to boost more popular documents:

size: 4,

query: {

function_score: {

functions: [

{

field_value_factor: {

field: 'likes_count',

factor: 0.01,

modifier: log1p,

missing: 0

},

},

{

field_value_factor: {

field: 'price_difference_percent',

missing: 0

},

},

{

random_score: {},

},

],

score_mode: 'avg',

query: {

match: { title: article.title },

}

}

}

Like price_difference_percent I used a field_value_factor function. The values for likes_count vary between 0 and 1000. I tweaked the factor and modifier options to get likes_count to return a value between 0 and 1 . Unfortunately, I wasn’t happy with the influence that likes_count had on the product recommendations, so I removed it.

gauss

I also wanted to boost products that were recently created. A product that hasn’t been sold in two years is less likely to be bought today.

size: 4,

query: {

function_score: {

functions: [

{

gauss: {

created_at: {

scale: "10d"

}

},

},

{

field_value_factor: {

field: 'price_difference_percent',

missing: 0

},

},

{

random_score: {},

},

],

score_mode: 'avg',

query: {

match: { title: article.title },

}

}

}

When using the gauss function with a date, it automatically sets the origin to now and decays the score from then. By default, gauss decays dates by milliseconds, but I don’t need that sort of precision so I decay dates by 10 day intervals.

Before I discovered the gauss function, I used a horrible hack to boost recent products: