Documentation
¶
Index ¶
- Variables
- func CompileWithFunctions(script string, extraEnv map[string]interface{}) (*vm.Program, error)
- func GeoDecay(lat1, lon1, lat2, lon2, scale float64) float64
- func GeoDistance(lat1, lon1, lat2, lon2 float64) float64
- func GeoInRange(lat1, lon1, lat2, lon2, radius float64) bool
- func GeoLinear(lat1, lon1, lat2, lon2, maxDist float64) float64
- func RegisterFunctions(baseEnv ...expr.Option) []expr.Option
- func SetVolatility(annual, downside float64)
- type RerankRequest
- type Reranker
- type ScriptScoreConfig
- type ScriptScorer
Constants ¶
This section is empty.
Variables ¶
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
─── CompileWithFunctions ─────────────────────────────────── CompileWithFunctions 一键编译表达式
func GeoDecay ¶
─── 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 ¶
─── 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 ¶
─── GeoInRange ───────────────────────────────────────────── GeoInRange 判断点是否在指定半径范围内
用途:
在 script_score 表达式中作为条件 GeoInRange(doc.lat, doc.lon, 39.9, 116.4, 3000) ? 50 : -100
如果在范围内,加 50 分;不在范围内,扣 100 分
func GeoLinear ¶
─── GeoLinear ────────────────────────────────────────────── GeoLinear 线性距离衰减,返回 0-1 之间的分数 距离=0:分数=1.0 距离=maxDist:分数=0 距离>maxDist:分数=0(超出范围)
比高斯衰减更简单,但不够"自然" 适用范围:明确距离范围的场景(比如"3公里内的店铺")
func RegisterFunctions ¶ added in v0.12.0
─── 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 对单个文档执行表达式计算
执行流程:
- 从 source 中提取需要的字段值
- 构建环境变量 env
- 调用 expr.Run() 执行表达式
- 返回计算结果
参数:
bm25Score:bluge 原始 BM25 分数 source:文档的 _source 内容
返回:
表达式计算结果(新的分数)
注意:如果字段不存在或报错,返回原始 BM25 分数(兜底)