使用cava编写排序脚本

cava排序脚本简介

Opensearch支持两阶段排序,粗排和精排。目前cava实现的排序脚本只支持在精排阶段生效。本文只聚焦在通过cava如何编写排序脚本,至于脚本的创建和使用请参考排序脚本api使用手册,另外也可以通过我们提供的命令行工具来创建排序脚本。与排序表达式相比,使用cava编写排序脚本具有更强的灵活性,可以定制性。用户可以在脚本中通过cava支持的语法和opensearch提供的feature lib来实现自己的业务逻辑。

cava排序脚本接口

为了能够使用自定义的脚本进行算分排序,用户需要实现opensearch提供的算分接口类。接口类的代码如下:

package users.scorer;
import com.aliyun.opensearch.cava.framework.OpsScoreParams;
import com.aliyun.opensearch.cava.framework.OpsScorerInitParams;

class BasicSimilarityScorer {
    //可以定义一些成员变量
    boolean init(OpsScorerInitParams params) {
        //实现你的代码,初始请求级别的变量,比如类的成员变量
        return true;
    }

    double score(OpsScoreParams params) {
        double score = 0;
        //实现你的代码,并将算分结果赋值给score
        return score;
    }
};

接口类BasicSimilarityScorer在包users.scorer下面,用户不能修改类名或者包名,否则编译会报错。BasicSimilarityScorer提供了init和score两个方法,用户可以在这个两个方法中实现自己的业务逻辑。

在opensearch中,对于每一个请求opensearch会先调用BasicSimilarityScorer的init方法初始化一些请求级别的变量(比如类的成员变量),该方法对于每个请求只会执行一次,如果返回失败请求终止。然后对于每个命中的且参与精排的文档,opensearch会依次调用score方法对文档进行算分,最终会根据算分结果进行排序。

init接口的输入参数为OpsScorerInitParams,通过该参数用户可以获取一些请求级别的资源。我们建议BasicSimilarityScorer的成员变量在init阶段进行初始化。

score接口的输入参数为OpsScoreParams,通过该参数用户可以获取一些请求和doc级别的资源。score接口对于每个参与算分的文档都会调用一次,所以对于请求级别的资源(比如kvpairs中的一些参数,一些feature对象的创建)尽量不要在score接口中获取(可以在init接口中获取),对于doc级别的资源可以在score接口中进行获取。

init和score的函数定义用户不能修改(比如改变返回值类型或者输入参数),否则编译的时候会报错。

cava排序脚本示例

package users.scorer;
import cava.lang.CString;
import com.aliyun.opensearch.cava.framework.OpsScoreParams;
import com.aliyun.opensearch.cava.framework.OpsScorerInitParams;
import com.aliyun.opensearch.cava.framework.OpsRequest;
import com.aliyun.opensearch.cava.framework.OpsKvPairs;
import com.aliyun.opensearch.cava.framework.OpsDoc;
import com.aliyun.opensearch.cava.features.similarity.TextRelevance; //引用需要使用的特征
class BasicSimilarityScorer {
    TextRelevance _textRelevance; //定义算分特征作为成员变量

    boolean init(OpsScorerInitParams params) {
        if (!params.getDoc().requireAttribute("shop_margin")) { //算分中使用的属性字段,需要在init接口中声明
            return false;
        }
        _textRelevance = TextRelevance.create(params, "default", "name"); //算分特征在init接口中声明
        return true;
    }

    double score(OpsScoreParams params) {
        float shopMargin = params.getDoc().docFieldFloat("shop_margin"); //获取文档中字段的值
        float textScore = _textRelevance.evaluate(params); //计算特征分数
        double score = textScore * 30.0 + shopMargin;
        return score;
    }
}

开发排序脚本注意事项

  • Opensearch提供的特征类,建议定义为BasicSimilarityScorer的成员变量,并在init函数中初始化,在score函数中进行算分,如果在score阶段初始化会造成极大的性能浪费。

  • 对于请求中自定义的参数,建议定义为BasicSimilarityScorer的成员变量,并在init函数中获取它的值。

  • 如果要获取文档中的字段,字段首先要在应用结构中定义为属性字段,然后再init函数中进行声明,在score函数中获取具体的值。

  • 由于目前只支持用户上传单个文件,所以只能在users.scorer包下面定义类。

  • 可以使用import关键字引用opensearch提供的系统库,但是目前不支持import com.aliyun.opensearch.cava.framework.*;这种语法。

  • 对于单次查询请求,排序脚本运行时使用内存的上限是40M,如果内存使用超过限制,查询请求会报错并返回,所以不要在脚本中进行大块内存操作,特别是不要在score函数中频繁的进行new操作,或者使用大量的字符串。对于内存超过限制的请求,opensearch在报错的同时也会有结果返回(尽量保证有结果),只是其中一部分文档使用了排序脚本进行算分,一部分没有。

  • 在排序脚本中可以使用for循环,或者进行函数调用。为了避免死循环和无限次函数调用,对于单次查询请求,opensearch限制排序脚本中for循环和函数调用的次数不能超过100000次,如果超过限制查询请求会报错并提前返回。

  • 使用排序脚本排序的文档数受限于rerank_size参数,用户可以通过该参数来调整参与排序的文档个数,从而避免触发内存和循环次数的限制。

阿里云首页 智能开放搜索 OpenSearch 相关技术圈