LlamaIndex 集成阿里云 Lindorm 向量存储:LindormVectorStore 实战指南
本篇技术指南围绕 LlamaIndex 官方仓库中的 Lindorm 向量存储集成(llama-index-vector-stores-lindorm)展开,系统讲解如何在 LlamaIndex 中使用阿里云 Lindorm 搜索引擎作为向量数据库,涵盖索引自动创建、IVFPQ/HNSW 索引映射参数、数据写入、kNN 检索、元数据过滤、词法检索、混合检索与删除操作。读者完成本文阅读后,将能够独立配置 Lindorm 实例接入 LlamaIndex,并针对不同召回场景正确选择检索模式与索引参数。
一、集成概览:Lindorm 在 LlamaIndex 中的定位
阿里云 Lindorm 是一款多模数据库,其搜索引擎(LindormSearch)提供兼容 OpenSearch/Elasticsearch 协议的能力,并原生支持向量检索。在 LlamaIndex 中,该能力被封装为 LindormVectorStore 向量存储实现,位于集成包 llama-index-vector-stores-lindorm。
该集成的核心能力(与官方 README.md 一致)包括:
- 纯向量检索(kNN 近似检索)
- 带元数据过滤的向量检索(pre-filter / post-filter)
- 词法(文本)检索
- 向量 + 词法混合检索(Hybrid Search)
- 完整的同步与异步接口(
add/async_add、query/aquery、delete/adelete)
从代码结构看,集成包由两个核心类组成(base.py):
LindormVectorClient:底层客户端,封装"单个启用了向量搜索的 Lindorm 索引"的全部操作,包括建索引、批量写入、构造各类查询;LindormVectorStore:面向 LlamaIndex 的向量存储适配层,继承自BasePydanticVectorStore,负责把 LlamaIndex 的BaseNode/VectorStoreQuery语义翻译给底层客户端。
两者的导出关系见 init.py。
二、安装与依赖
根据集成包 pyproject.toml 与 README.md,需要安装三个包:
pip install llama-index
pip install opensearch-py
pip install llama-index-vector-stores-lindorm
依赖约束如下(来自 pyproject.toml):
opensearch-py[async]>=2.4.2,<3:Lindorm 客户端基于 OpenSearch Python SDK 实现(因为 LindormSearch 兼容 OpenSearch 协议),且依赖其异步能力;llama-index-core>=0.13.0,<0.15:核心框架依赖。
需要说明的是,底层客户端内部对 opensearchpy 的导入是延迟的——如果环境中缺少该 SDK,初始化时会抛出提示信息:"Could not import OpenSearch Python SDK. Please install it with pip install opensearch-py"(见 base.py)。
三、连接 Lindorm 实例并初始化客户端
3.1 前置信息
使用前需要先获取 Lindorm 搜索引擎实例的连接信息:实例的公网/私网访问地址(host)、端口(port)、账号与密码。官方文档中建议通过以下途径准备环境(此处仅给出操作指引,对应文档位于阿里云官方帮助中心,搜索关键词即可找到):
- 创建 Lindorm 实例;
- 查看实例访问地址与端口;
- 使用 curl 命令验证与 LindormSearch 的连通性。
3.2 初始化 LindormVectorClient
LindormVectorClient 的初始化签名如下(base.py):
LindormVectorClient(
host, port, username, password, index, dimension,
text_field="content",
max_chunk_bytes=1 * 1024 * 1024,
os_client=None,
**kwargs,
)
必填参数:
| 参数 | 说明 |
|---|---|
host |
Lindorm 搜索引擎的兼容 Elasticsearch 主机地址 |
port |
Lindorm 实例端口 |
username / password |
实例访问账号与密码 |
index |
索引名称(不存在时会在初始化阶段自动创建) |
dimension |
向量维度,必须与所选 embedding 模型的输出维度一致 |
可选参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
text_field |
"content" |
文档正文存储的字段名 |
max_chunk_bytes |
1 * 1024 * 1024 |
批量写入时单个数据块的最大字节数 |
os_client |
None |
可传入外部构造好的 OpenSearch 异步客户端,缺省时内部自动创建 |
初始化时的关键行为是自动建索引:客户端会先查询索引是否存在,捕获 NotFoundError 后按内部构造的 mapping 创建索引并执行 refresh(base.py)。因此该索引要么尚未创建,要么是此前由本类创建过的——这是该类文档字符串中明确说明的假设前提。
四、索引映射与算法参数详解
初始化时客户端会自动生成索引 mapping(base.py),核心结构为:
{
"settings": {"index": {"number_of_shards": 4, "knn": true}},
"mappings": {
"_source": {"excludes": ["vector_field"]},
"properties": {
"vector_field": {
"type": "knn_vector",
"dimension": 1536,
"data_type": "float",
"method": {
"engine": "lvector",
"name": "ivfpq",
"space_type": "l2",
"parameters": {...}
}
}
}
}
}
其中 vector_field 默认值为 "vector_field",向量以 knn_vector 类型、float 数据存储,并通过 method 指定索引算法。相关参数均通过 kwargs 传入,分为三类。
4.1 通用 mapping 参数
| 参数 | 默认值 | 取值/说明 |
|---|---|---|
method_name |
"ivfpq" |
索引算法:ivfpq 或 hnsw;传入其他值会抛出 RuntimeError |
engine |
"lvector" |
Lindorm 向量引擎标识 |
space_type |
"l2" |
距离度量:l2、cosinesimil、innerproduct |
vector_field |
"vector_field" |
向量字段名 |
4.2 Lindorm 搜索扩展参数
| 参数 | 默认值 | 说明 |
|---|---|---|
filter_type |
"post_filter" |
过滤方式:pre_filter(先过滤后检索)或 post_filter(先检索后过滤);传入其他值会抛出 ValueError |
nprobe |
"1" |
查询时访问的聚类簇数量,取值范围 1 到 method.parameters.nlist,以字符串形式传入 |
reorder_factor |
"10" |
重排序因子,用于提升召回精度但会增加性能开销,取值范围 1 到 200,默认 10,字符串形式 |
4.3 IVFPQ 专用参数
当 method_name="ivfpq" 时(base.py):
| 参数 | 默认值 | 说明 |
|---|---|---|
m |
等于 dimension |
子空间数量,范围 2 到 32768 |
nlist |
10000 |
聚类中心数量,范围 2 到 1000000 |
centroids_use_hnsw |
True |
搜索聚类中心时是否使用 HNSW 算法 |
centroids_hnsw_m |
16 |
聚类中心 HNSW 图的边数,范围 1 到 100 |
centroids_hnsw_ef_search |
100 |
k-NN 检索时动态列表大小,值越大检索越准但越慢 |
centroids_hnsw_ef_construct |
100 |
k-NN 构图时动态列表大小,值越大图越准但建索引越慢 |
4.4 HNSW 专用参数
当 method_name="hnsw" 时(base.py):
| 参数 | 默认值 | 说明 |
|---|---|---|
m |
16 |
图中每层最大出边数,范围 1 到 100 |
ef_construction |
100 |
建索引时动态列表长度,范围 1 到 1000 |
五、典型初始化示例
以下代码来自集成包 README.md 与 base.py 中的官方示例:
from llama_index.vector_stores.lindorm import (
LindormVectorStore,
LindormVectorClient,
)
# lindorm instance info
host = "ld-bp******jm*******-proxy-search-pub.lindorm.aliyuncs.com"
port = 30070
username = "your_username"
password = "your_password"
# index to demonstrate the VectorStore impl
index_name = "lindorm_test_index"
# extension param of lindorm search,
# number of cluster units to query; between 1 and method.parameters.nlist.
nprobe = "a number(string type)"
# extension param of lindorm search, usually used to improve recall accuracy,
# but it increases performance overhead; between 1 and 200; default: 10.
reorder_factor = "a number(string type)"
# LindormVectorClient encapsulates logic for a single index with vector search enabled
client = LindormVectorClient(
host=host,
port=port,
username=username,
password=password,
index=index_name,
dimension=1536, # match with your embedding model
nprobe=nprobe,
reorder_factor=reorder_factor,
# filter_type="pre_filter/post_filter(default)"
)
# initialize vector store
vector_store = LindormVectorStore(client)
几点实操提醒:
dimension必须与 embedding 模型输出维度一致(示例使用 1536,对应常见的 OpenAI text-embedding-ada-002 一档模型维度);nprobe与reorder_factor在代码中以字符串形式传入(其类型注释为str);- 若需使用
pre_filter过滤策略,在初始化时显式传入filter_type="pre_filter"。
六、数据写入:add / async_add
LindormVectorStore 通过 add(同步)与 async_add(异步)两个入口写入节点(base.py):
def add(self, nodes: List[BaseNode], **add_kwargs: Any) -> List[str]:
return asyncio.get_event_loop().run_until_complete(
self.async_add(nodes, **add_kwargs)
)
async def async_add(self, nodes: List[BaseNode], **add_kwargs: Any) -> List[str]:
await self._client.index_results(nodes)
return [result.node_id for result in nodes]
- 同步方法是对异步逻辑的封装(
run_until_complete),因此要求在可运行事件循环的上下文中调用; - 返回值为写入节点的
node_id列表。
底层 index_results 会把每个节点拆分为四类数据后批量写入(base.py):
node_id作为文档_id;node.get_embedding()作为向量;node.get_content(metadata_mode=MetadataMode.NONE)作为正文文本(写入text_field);node_to_metadata_dict(node, remove_text=True)作为元数据。
批量写入使用 opensearchpy.helpers.async_bulk,受 max_chunk_bytes 约束,写入完成后立即执行 refresh 以保证可见性(base.py)。
七、检索:三种查询模式与元数据过滤
7.1 查询入口
查询统一走 query / aquery 接口,入参为 LlamaIndex 的 VectorStoreQuery 对象(base.py),实际委托给底层客户端的 aquery:
async def aquery(self, query: VectorStoreQuery, **kwargs: Any) -> VectorStoreQueryResult:
query_embedding = cast(List[float], query.query_embedding)
return await self._client.aquery(
query.mode,
query.query_str,
query_embedding,
query.similarity_top_k,
filters=query.filters,
)
底层 aquery 根据 query.mode 分发到三种查询构造器(base.py):
| 查询模式 | 走通的分支 | 说明 |
|---|---|---|
VectorStoreQueryMode.HYBRID |
_hybrid_search_query |
向量 + 词法混合检索;此时必须提供 query_str,否则抛出 "Please specify the lexical_query for hybrid search." |
VectorStoreQueryMode.TEXT_SEARCH |
_lexical_search_query |
纯词法检索,基于 text_field 字段做 match 匹配 |
其他模式(默认/DEFAULT) |
_knn_search_query |
纯向量 kNN 近似检索 |
7.2 kNN 检索与过滤策略
_knn_search_query 的核心逻辑(base.py):
- 无元数据过滤时:构造纯近似 kNN 查询,请求体为
query.knn+ 扩展的ext.lvector(携带nprobe与reorder_factor); - 有元数据过滤时:在 kNN 查询中嵌入过滤条件。过滤条件组合规则为:
FilterCondition.AND→bool.must,FilterCondition.OR→bool.should;其他条件(如 NOT)当前不受支持,会抛出ValueError。
MetadataFilter 支持的操作符映射(base.py):
| FilterOperator | 生成的 DSL | 说明 |
|---|---|---|
GTE / LTE / GT / LT |
range 查询 |
数值范围过滤 |
EQ |
term 查询 |
精确匹配 |
| 其他 | — | 抛出 ValueError("Unsupported filter operator") |
同时要注意源码中的明确说明:纯近似 kNN 检索本身不支持元数据过滤,一旦带过滤条件,查询会走带 filter 的精确 kNN 路径。
7.3 词法检索与混合检索
- 词法检索:构造
bool.must+match查询作用于text_field,若存在元数据过滤则追加到bool.filter(base.py); - 混合检索:将词法查询的
bool.must条件注入 kNN 查询的过滤上下文,两者组合后在单个请求中完成向量 + 文本联合召回(base.py)。
7.4 结果解析
检索结果通过 _to_query_result 转换为 VectorStoreQueryResult(base.py),转换逻辑为:
- 优先通过
metadata_dict_to_node重建节点(覆盖正文文本); - 若元数据无法解析(旧数据兼容),则退化为直接构造
TextNode,从node_info中恢复start_char_idx/end_char_idx,并从relationships恢复关系; - 最终返回
nodes、ids与similarities(即_score)三要素。
八、删除操作
delete / adelete 以 ref_doc_id 为入参,删除该文档对应的全部节点(base.py)。底层通过 delete_by_query 执行(base.py):
search_query = {"query": {"term": {"doc_id.keyword": {"value": doc_id}}}}
await self._os_client.delete_by_query(index=self._index, body=search_query)
即按 doc_id.keyword 字段精确匹配并删除相关节点。
九、测试验证与可复现示例
集成包自带完整测试,可作为理解 API 行为与验证环境的参考:
- tests/test_vector_stores_lindorm.py:验证
LindormVectorStore正确继承自BasePydanticVectorStore; - tests/test_lindorm_client.py:端到端集成测试,覆盖以下场景:
test_add_nodes:批量写入 1000 个节点,断言返回 ID 数量一致;test_simple_query:5 维向量纯 kNN 检索;test_query_with_metadata_filter:mark_id字段GTE/LTE+AND组合过滤检索;test_lexical_query:词法检索(注意:查询词的最小检索粒度是一个 token);test_hybrid_query:混合检索;test_delete_node:按ref_doc_id删除后,用relationships.SOURCE.node_id过滤查询确认节点已不可见。
测试中索引维度为 5、nprobe="2"、reorder_factor="10",并针对占位配置(含 < 字符)做了跳过处理——这意味着运行集成测试需要真实的 Lindorm 实例连接信息,无实例时测试会被 pytest.skip 跳过。
十、使用注意事项小结
- 协议与依赖:Lindorm 搜索引擎兼容 OpenSearch 协议,因此必须安装
opensearch-py,且本集成使用的是其异步 API; - 索引自动创建:
LindormVectorClient初始化时若索引不存在会自动创建,默认number_of_shards=4、knn=true; - 参数范围约束:
nprobe上限为nlist,reorder_factor上限为 200,filter_type仅支持pre_filter/post_filter,非法取值会在初始化阶段报错; - 过滤能力边界:纯近似 kNN 不带过滤;带过滤时条件组合仅支持 AND / OR,
FilterCondition.NOT尚未实现(源码中以 TODO 标注); - 混合检索前置条件:
HYBRID模式必须提供query_str,否则直接抛错; - 同步方法依赖事件循环:
add、query、delete均为异步逻辑的同步包装,需要在可运行asyncio事件循环的环境中调用; - 维度一致性:
dimension必须与 embedding 模型输出严格一致,建索引后不可随意变更。
通过本文的配置与调用示例,读者可以将阿里云 Lindorm 无缝接入 LlamaIndex 的索引构建与检索链路,并在纯向量、带过滤、词法、混合四种召回策略之间按需切换。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0632
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00