ranking

package
v0.27.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 30, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// DefaultAnnualVolatility is used by sharpe_ratio when no custom value is set.
	DefaultAnnualVolatility atomic.Int64
	// DefaultDownsideVolatility is used by sortino_ratio when no custom value is set.
	DefaultDownsideVolatility atomic.Int64
)

Functions

func CompileWithFunctions added in v0.12.0

func CompileWithFunctions(script string, extraEnv map[string]interface{}) (*vm.Program, error)

─── CompileWithFunctions ─────────────────────────────────── CompileWithFunctions 一键编译表达式

func GeoDecay

func GeoDecay(lat1, lon1, lat2, lon2, scale float64) float64

─── GeoDecay ─────────────────────────────────────────────── GeoDecay 高斯距离衰减,返回 0-1 之间的分数 越靠近原点,分数越高 越远离原点,分数按高斯函数衰减

用途:在排序表达式中使用

geo_decay(doc.lat, doc.lon, 39.9, 116.4, 3000) * 0.3

距离=0:分数=1.0(最高) 距离=scale:分数≈0.3679(e^(-1)) 距离=∞:分数→0

高斯衰减 vs 线性衰减:

高斯:开头下降慢,中间加速,最后又变慢
线性:匀速下降
高斯更"自然",符合人的认知(附近都一样近,远了才觉得远)

func GeoDistance

func GeoDistance(lat1, lon1, lat2, lon2 float64) float64

─── GeoDistance ──────────────────────────────────────────── GeoDistance 计算两个经纬度之间的 haversine 距离(米)

参数:

lat1, lon1:起点经纬度(度)
lat2, lon2:终点经纬度(度)

返回值:

两点之间的球面距离(米)

haversine 公式的数学原理:

hav(θ) = hav(Δlat) + cos(lat1)·cos(lat2)·hav(Δlon)
其中 hav(θ) = sin²(θ/2)
d = 2R · arcsin(√hav(θ))

简化后的算法:

a = sin²(Δlat/2) + cos(lat1)·cos(lat2)·sin²(Δlon/2)
c = 2 · atan2(√a, √(1-a))
d = R · c

性能:~123ns/op,零内存分配 可以在搜索排序循环中直接调用,不影响性能

面试题:为什么用 atan2 而不是 asin?

数值稳定性:atan2 对所有输入值都稳定
asin 在输入接近 1 时不稳定(导数趋于无穷)

func GeoInRange

func GeoInRange(lat1, lon1, lat2, lon2, radius float64) bool

─── GeoInRange ───────────────────────────────────────────── GeoInRange 判断点是否在指定半径范围内

用途:

  在 script_score 表达式中作为条件

	GeoInRange(doc.lat, doc.lon, 39.9, 116.4, 3000) ? 50 : -100

如果在范围内,加 50 分;不在范围内,扣 100 分

func GeoLinear

func GeoLinear(lat1, lon1, lat2, lon2, maxDist float64) float64

─── GeoLinear ────────────────────────────────────────────── GeoLinear 线性距离衰减,返回 0-1 之间的分数 距离=0:分数=1.0 距离=maxDist:分数=0 距离>maxDist:分数=0(超出范围)

比高斯衰减更简单,但不够"自然" 适用范围:明确距离范围的场景(比如"3公里内的店铺")

func RegisterFunctions added in v0.12.0

func RegisterFunctions(baseEnv ...expr.Option) []expr.Option

─── RegisterFunctions ────────────────────────────────────── RegisterFunctions 返回 expr.Option 列表,包含全部内置函数 用法:

opts := RegisterFunctions(expr.Env(map[string]interface{}{"bm25": 0.0, "doc": map[string]interface{}{}}))
program, _ := expr.Compile(script, opts...)

func SetVolatility added in v0.27.0

func SetVolatility(annual, downside float64)

SetVolatility overrides the default volatility assumptions for sharpe_ratio and sortino_ratio. Pass 0 to keep the current value for either.

Types

type RerankRequest

type RerankRequest struct {
	Hits   []meta.Hit // BM25 搜索结果,包含 _score 和 _source
	Query  string     // 用户的搜索词
	UserID string     // 用户 ID(用于个性化)
}

─── RerankRequest ────────────────────────────────────────── RerankRequest 重排序请求 包含了重排序需要的所有信息

Hits:BM25 的原始搜索结果 Query:用户的查询语句 UserID:当前搜索的用户 ID(可用于个性化排序)

type Reranker

type Reranker interface {
	// Rerank 对搜索结果进行重排序
	// 接收一个 RerankRequest,包含现有的搜索结果和查询信息
	// 返回重排序后的结果
	Rerank(req *RerankRequest) ([]meta.Hit, error)
}

─── Reranker 接口 ────────────────────────────────────────── Reranker 重排序接口 实现了这个接口的结构体,可以插入到 zinc 的搜索流程中 对 BM25 的搜索结果进行二次排序

使用示例:

type MyReranker struct{}
func (r *MyReranker) Rerank(req *RerankRequest) ([]meta.Hit, error) {
    // 比如按销量排序
    sort.Slice(req.Hits, func(i, j int) bool {
        return req.Hits[i].Score > req.Hits[j].Score
    })
    return req.Hits, nil
}

// 注入到 Index
index.reranker = &MyReranker{}

type ScriptScoreConfig

type ScriptScoreConfig struct {
	Script      string   `json:"script"`       // 表达式,如 "bm25 * 0.3 + doc.sales * 0.01"
	Fields      []string `json:"fields"`       // 涉及的文档字段
	ExtractSize int      `json:"extract_size"` // BM25 召回数量
}

─── ScriptScoreConfig ────────────────────────────────────── ScriptScoreConfig 是 script_score 的配置,从 JSON 请求解析

一个完整的 script_score 请求示例:

{
  "query": { "match": { "title": "手机" } },
  "script_score": {
    "script": "bm25 * 0.3 + doc.sales * 0.01 + doc.rating * 0.1",
    "fields": ["sales", "rating"],
    "extract_size": 500
  }
}

Script:用户写的表达式,决定每个文档的最终分数 Fields:表达式中用到的文档字段名,引擎需要预先从 _source 读取 ExtractSize:BM25 召回多少条文档后执行表达式重排序

500 意味着先 BM25 搜 500 条,然后用表达式重排序取前 20

type ScriptScorer

type ScriptScorer struct {
	// contains filtered or unexported fields
}

─── ScriptScorer ─────────────────────────────────────────── ScriptScorer 是表达式执行器 编译阶段(一次)→ 执行阶段(每条文档一次)

为什么不每次重新编译?

Compile 耗时 ~2ms,Run 耗时 ~500ns
如果每次请求都 Compile,延迟会急剧增加
所以先 Compile 一次,缓存起来,后续只 Run

怎么缓存?

sync.Map 做编译缓存,key=script 字符串,value=*vm.Program
相同 script 的请求直接取缓存的 Program

func NewScriptScorer

func NewScriptScorer(cfg *ScriptScoreConfig) (*ScriptScorer, error)

─── NewScriptScorer ──────────────────────────────────────── NewScriptScorer 创建表达式执行器并预编译

Compile 这一步很关键:

用户写的表达式是字符串,比如 "bm25 * 0.3 + doc.sales * 0.01"
Compile 会把这个字符串解析成 AST(抽象语法树)
然后编译成字节码(bytecode),类似 Java 的 .class 文件
最后 Run 的时候执行这些字节码

为什么需要 Env?

Env 告诉编译器:表达式里可以使用哪些变量
这里声明了 bm25 和 doc 两个变量
所以用户在表达式中只能写 bm25.xxx 和 doc.xxx
不能写 os.Exec("rm -rf /")——这就是安全性

func (*ScriptScorer) Score

func (s *ScriptScorer) Score(bm25Score float64, source map[string]interface{}) (float64, error)

─── Score ────────────────────────────────────────────────── Score 对单个文档执行表达式计算

执行流程:

  1. 从 source 中提取需要的字段值
  2. 构建环境变量 env
  3. 调用 expr.Run() 执行表达式
  4. 返回计算结果

参数:

bm25Score:bluge 原始 BM25 分数
source:文档的 _source 内容

返回:

表达式计算结果(新的分数)

注意:如果字段不存在或报错,返回原始 BM25 分数(兜底)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL