Lindorm 图引擎可以将向量作为顶点属性存储,并通过向量引擎提供的近似最近邻检索能力,在 Gremlin 查询中查找与查询向量最相似的顶点。该能力适用于语义检索、相似内容推荐、以图关系扩展向量召回结果等场景。本文以文档检索为例,介绍向量检索得分,以及使用hasVector对顶点执行 KNN(K-Nearest Neighbor)检索。
前提条件
已开通 Lindorm 图引擎,并已配置访问白名单。
已开通 Lindorm 向量引擎。
已获取图引擎的 HTTP 连接地址、数据库名、用户名和密码。
向量检索得分说明
详细介绍与计算方法可以参考向量引擎的相关文档附录:向量检索得分(_score)的计算方法。
图引擎根据向量引擎返回的distance计算得分:
score = 1 / (1 + distance)L2和COSINE的距离及得分范围如下。
距离类型 | distance 定义 | distance 范围 | _score 范围 |
|
|
|
|
|
|
|
|
典型得分如下:
场景 | distance | _score |
L2:向量完全相同 |
|
|
L2:平方距离为 1 |
|
|
L2:平方距离为 4 |
|
|
COSINE:同方向 |
|
|
COSINE:正交 |
|
|
COSINE:反方向 |
|
|
使用得分时,请注意以下事项:
COSINE的理论最低得分是1/3,不是0。如果业务向量的余弦相似度只位于[0,1],得分范围实际为[0.5,1]。L2的得分可以无限接近0。不同距离类型的得分分布不同。建议分别根据业务数据校准阈值,不要直接共用同一个阈值。
对顶点执行 KNN 检索
对顶点的向量检索通常包括以下步骤:
使用
hasVector召回与查询向量最相似的顶点。根据需要返回相似度得分、过滤低相关结果,或继续执行图遍历。
hasVector KNN 检索基本语法
hasVector提供以下两种调用形式:
hasVector(vectorProperty, queryVector, topK)
hasVector(vectorProperty, queryVector, topK, searchParams)参数说明如下。
参数 | 是否必选 | 说明 |
| 是 | 已定义向量索引的顶点属性名。 |
| 是 | 查询向量。数组元素必须为数值,维度必须与属性定义一致。 |
| 是 | 从向量索引召回的最大结果数,必须大于 |
| 否 | 检索参数 Map,例如 |
hasVector必须紧跟只包含一个顶点类型的hasLabel使用。例如,以下查询返回document中与[1,0,0,0]最接近的 4 个顶点 ID:
g.V().hasLabel('document')
.hasVector('embedding', [1.0d, 0.0d, 0.0d, 0.0d], 4)
.id()对应的 curl 请求如下:
curl -sS -u "$GRAPH_AUTH" -X POST \
"$GRAPH_ENDPOINT/gremlin/$GRAPH_DB" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
--data '{
"gremlin": "g.V().hasLabel(\"document\").hasVector(\"embedding\",[1.0d,0.0d,0.0d,0.0d],4).id()"
}'示例数据的返回可能如下:
{
"requestId": "<request-id>",
"status": {
"message": "",
"code": 200,
"attributes": {
"@type": "g:Map",
"@value": []
}
},
"result": {
"data": {
"@type": "g:List",
"@value": [
"doc_same",
"doc_scaled",
"doc_orthogonal",
"doc_opposite"
]
},
"meta": {
"@type": "g:Map",
"@value": []
}
}
}业务结果位于result.data["@value"]中。以上示例依次返回doc_same、doc_scaled、doc_orthogonal、doc_opposite;结果按照_score从大到小返回。
hasVector KNN 检索常见用法
返回顶点及其业务属性
使用valueMap(true)返回顶点 ID、Label 和所有属性:
g.V().hasLabel('document')
.hasVector('embedding', [1.0d, 0.0d, 0.0d, 0.0d], 1)
.valueMap(true)默认不开启with_score,因此结果中不包含_score。
如果只需要部分业务属性,可以显式指定属性名:
g.V().hasLabel('document')
.hasVector('embedding', [1.0d, 0.0d, 0.0d, 0.0d], 4)
.valueMap('title', 'category')返回相似度得分
在第四个参数中设置with_score=true:
g.V().hasLabel('document')
.hasVector(
'embedding',
[1.0d, 0.0d, 0.0d, 0.0d],
4,
{"with_score": true}
)
.valueMap('title', 'category', '_score')下面展示解开 GraphSON 类型包装后的逻辑结果:
[
{"title":["Exact match"],"category":["guide"],"_score":[1.0]},
{"title":["Same direction different length"],"category":["guide"],"_score":[0.5]},
{"title":["Orthogonal vector"],"category":["reference"],"_score":[0.33333334]},
{"title":["Opposite vector"],"category":["archive"],"_score":[0.2]}
]GraphSON 中的_score按顶点属性的多值形式返回,因此在valueMap()结果中通常是只包含一个浮点数的列表。客户端需要按列表形式解析。
如果只需要得分数值列表,可以使用:
g.V().hasLabel('document')
.hasVector(
'embedding',
[1.0d, 0.0d, 0.0d, 0.0d],
4,
{"with_score": true}
)
.values('_score')
.fold()使用min_score过滤低相关结果
min_score用于设置最低相关性得分。以下查询先从向量索引中最多召回 100 个顶点,再仅保留得分不低于0.8的结果:
g.V().hasLabel('document')
.hasVector(
'embedding',
[1.0d, 0.0d, 0.0d, 0.0d],
100,
{"min_score": 0.8, "with_score": true}
)
.valueMap('title', '_score')min_score与最大距离的换算关系如下:
maxDistance = 1 / min_score - 1对于L2和COSINE,建议将min_score设置在(0,1]。0表示基本不进行距离过滤;大于1时不会命中这两类距离的结果。对于COSINE,阈值不大于1/3时,理论上无法过滤有效的非零向量。
调整索引检索参数
searchParams支持以下参数。
参数 | 取值范围 | 说明 |
|
| 最低相关性得分。 |
| 布尔值 | 是否返回 |
| 整数 | HNSW 检索的候选宽度。 |
| 整数 | IVF 检索时访问的聚类中心数。 |
| 浮点数 | 扩大候选集,并基于原始向量进行重排。 |
参数应与索引类型匹配。例如,HNSW 索引主要使用ef_search,IVF 索引主要使用nprobe,量化索引可使用reorder_factor。图引擎会校验并透传这些参数,但不保证与当前索引类型不匹配的参数能够改变召回结果。未知参数会导致查询报错。
以下示例扩大向量召回范围、设置最低得分,并最终返回前 20 个结果:
g.V().hasLabel('document')
.hasVector(
'embedding',
[1.0d, 0.0d, 0.0d, 0.0d],
100,
{
"min_score": 0.75,
"with_score": true,
"ef_search": 100,
"reorder_factor": 10
}
)
.limit(20)
.valueMap('title', '_score')向量召回后继续过滤或遍历
可以在hasVector后继续使用普通 Gremlin 步骤。例如,以下查询在向量召回结果中筛选状态为online的顶点:
g.V().hasLabel('document')
.hasVector(
'embedding',
[1.0d, 0.0d, 0.0d, 0.0d],
100,
{"with_score": true}
)
.has('status', 'online')
.limit(10)
.valueMap('title', 'status', '_score')topK限制的是向量索引的召回数量,后续过滤不会自动补充候选。例如,topK=10的 10 个候选中只有 3 个满足status='online',查询最多返回 3 个结果。需要返回更多过滤后结果时,应适当增大topK,再使用limit()限制最终数量。
普通属性过滤必须放在hasVector之后。hasVector会作为向量检索的起始步骤,位于它之前的普通过滤步骤不会参与最终查询。请勿使用以下写法:
// 错误示例:status 过滤不会按预期生效
g.V().hasLabel('document')
.has('status', 'online')
.hasVector('embedding', [1.0d, 0.0d, 0.0d, 0.0d], 100)使用参数绑定
查询向量和检索参数可以通过请求的bindings传入,避免在 Gremlin 字符串中拼接较长的向量:
curl -sS -u "$GRAPH_AUTH" -X POST \
"$GRAPH_ENDPOINT/gremlin/$GRAPH_DB" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
--data '{
"gremlin": "g.V().hasLabel(\"document\").hasVector(\"embedding\",queryVector,100,params).limit(10).valueMap(\"title\",\"_score\")",
"bindings": {
"queryVector": [1.0, 0.0, 0.0, 0.0],
"params": {
"min_score": 0.8,
"with_score": true,
"ef_search": 100
}
}
}'使用限制
hasVector仅用于顶点向量属性的 KNN 检索。hasVector前必须有且只有一个hasLabel,不能省略 Label,也不能同时指定多个 Label。hasVector的topK必须大于0。查询向量必须为数值数组,维度必须与 Schema 定义一致。
_score是保留属性名,只在设置with_score=true的本次查询中存在,并且不可修改或删除。with_score只接受布尔值true、false或对应的字符串形式,其他值会导致查询报错。未知的
searchParams参数会导致查询报错。普通属性过滤应放在
hasVector之后;如需保证过滤后的结果数量,应扩大topK后再使用limit()。